-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
resourcemanager/resourceids: adding a new SegmentNotSpecifiedError to…
… allow providing better error messages when segments are missing during parsing This means that the error messages will change from: ``` parsing segment "staticMicrosoftServiceBus": expected the segment "microsoft.servicebus" to be "Microsoft.ServiceBus" ``` to: ``` parsing the Queue ID: the segment "staticMicrosoftServiceBus" was not found Expected a Queue ID that matched: > /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers/Microsoft.ServiceBus/namespaces/namespaceValue/queues/queueValue However this value was provided: > /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/my-resource-group/providers/microsoft.servicebus/namespaces/my-service-bus/queues/my-test-queue However the parsed Resource ID was missing a value for the "staticMicrosoftServiceBus" segment (which should be the name of the Resource Provider [for example 'Microsoft.ServiceBus']). ``` Which should help better explain the issue that occurred when parsing the Resource ID
- Loading branch information
1 parent
90d1467
commit 7b399a1
Showing
5 changed files
with
589 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package resourceids | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
"strings" | ||
) | ||
|
||
var _ error = SegmentNotSpecifiedError{} | ||
|
||
type SegmentNotSpecifiedError struct { | ||
parseResult ParseResult | ||
resourceId ResourceId | ||
resourceIdName string | ||
segmentName string | ||
} | ||
|
||
// NewSegmentNotSpecifiedError returns a SegmentNotSpecifiedError for the provided Resource ID, segment and parseResult combination | ||
func NewSegmentNotSpecifiedError(id ResourceId, segmentName string, parseResult ParseResult) SegmentNotSpecifiedError { | ||
// Resource ID types must be in the format {Name}Id | ||
resourceIdName := strings.TrimSuffix(reflect.ValueOf(id).Type().Name(), "Id") | ||
return SegmentNotSpecifiedError{ | ||
resourceIdName: resourceIdName, | ||
resourceId: id, | ||
segmentName: segmentName, | ||
parseResult: parseResult, | ||
} | ||
} | ||
|
||
// Error returns a detailed error message highlighting the issues found when parsing this Resource ID Segment. | ||
func (e SegmentNotSpecifiedError) Error() string { | ||
expectedId := e.buildExpectedResourceId() | ||
description, err := e.descriptionForSegment(e.segmentName) | ||
if err != nil { | ||
// this isn't user recoverable, and will be caught at unit/acceptance test time, so `panic` is reasonable here | ||
return fmt.Sprintf("internal-error: building description for segment: %+v", err) | ||
} | ||
|
||
return fmt.Sprintf(`parsing the %[1]s ID: the segment %[2]q was not found | ||
Expected a %[1]s ID that matched: | ||
> %[3]s | ||
However this value was provided: | ||
> %[4]s | ||
However the parsed Resource ID was missing a value for the %[2]q segment | ||
(%[5]s). | ||
`, e.resourceIdName, e.segmentName, expectedId, e.parseResult.RawInput, *description) | ||
} | ||
|
||
// descriptionForSegment returns a friendly description for the Segment specified in segmentName | ||
func (e SegmentNotSpecifiedError) descriptionForSegment(segmentName string) (*string, error) { | ||
// NOTE: do not use round brackets within these error messages, since this description can be contained within one | ||
for _, segment := range e.resourceId.Segments() { | ||
if segment.Name != segmentName { | ||
continue | ||
} | ||
|
||
switch segment.Type { | ||
case ConstantSegmentType: | ||
{ | ||
if segment.PossibleValues == nil { | ||
return nil, fmt.Errorf("the Segment %q defined a Constant with no PossibleValues", segment.Name) | ||
} | ||
|
||
// intentionally format these as quoted string values | ||
values := make([]string, 0) | ||
for _, v := range *segment.PossibleValues { | ||
values = append(values, fmt.Sprintf("%q", v)) | ||
} | ||
|
||
msg := fmt.Sprintf("which should be a Constant with one of the following values [%s]", strings.Join(values, ", ")) | ||
return &msg, nil | ||
} | ||
|
||
case ResourceGroupSegmentType: | ||
{ | ||
msg := "which should be the name of the Resource Group" | ||
return &msg, nil | ||
} | ||
|
||
case ResourceProviderSegmentType: | ||
{ | ||
msg := fmt.Sprintf("which should be the name of the Resource Provider [for example '%s']", segment.ExampleValue) | ||
return &msg, nil | ||
} | ||
|
||
case ScopeSegmentType: | ||
{ | ||
msg := fmt.Sprintf("which specifies the Resource ID that should be used as a Scope [for example '%s']", segment.ExampleValue) | ||
return &msg, nil | ||
} | ||
|
||
case StaticSegmentType: | ||
{ | ||
if segment.FixedValue == nil { | ||
return nil, fmt.Errorf("the Segment %q defined a Static Segment with no FixedValue", segment.Name) | ||
} | ||
msg := fmt.Sprintf("which should be the literal value %q", *segment.FixedValue) | ||
return &msg, nil | ||
} | ||
|
||
case SubscriptionIdSegmentType: | ||
{ | ||
msg := "which should be the UUID of the Azure Subscription" | ||
return &msg, nil | ||
} | ||
|
||
case UserSpecifiedSegmentType: | ||
{ | ||
name := strings.TrimSuffix(segment.Name, "Name") | ||
msg := fmt.Sprintf("which should be the user specified value for this %s [for example %q]", name, segment.ExampleValue) | ||
return &msg, nil | ||
} | ||
|
||
default: | ||
{ | ||
return nil, fmt.Errorf("internal-error: the Segment Type %q was not implemented for Segment %q", string(segment.Type), segment.Name) | ||
} | ||
} | ||
} | ||
|
||
return nil, fmt.Errorf("the segment %q was not defined for this Resource ID", segmentName) | ||
} | ||
|
||
// buildExpectedResourceId iterates over the Resource ID to build up the "expected" value for the Resource ID | ||
// this is done using the example segment values for each segment type. | ||
func (e SegmentNotSpecifiedError) buildExpectedResourceId() string { | ||
segments := make([]string, 0) | ||
for _, v := range e.resourceId.Segments() { | ||
segments = append(segments, strings.TrimPrefix(v.ExampleValue, "/")) | ||
} | ||
|
||
out := strings.Join(segments, "/") | ||
return fmt.Sprintf("/%s", out) | ||
} |
Oops, something went wrong.