Skip to content

Commit 30c457d

Browse files
authored
private/model: Add shape name validation for structs and enums (#471)
* Adds `validateShapeName` utility to validate that the shape names for structs and enums adhere to a defined format. * Fixes bug which allowed service package structs, enums to start with non alphabetic character * Fixes the incorrect enum types in mediapackage service package, changing enum types __AdTriggersElement, __PeriodTriggersElement to AdTriggersElement, PeriodTriggersElement respectively. * Adds unit tests to test the validateShapeName utility.
1 parent 1e71955 commit 30c457d

File tree

8 files changed

+381
-174
lines changed

8 files changed

+381
-174
lines changed

CHANGELOG_PENDING.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ Breaking Change
55
* To migrate your applications to use the updated wafregional and dynamodbstreams you'll need to update the package the impacted type is imported from to match the client the type is being used with.
66
* `aws`: Context has been added to EC2Metadata operations.([#461](https://github.com/aws/aws-sdk-go-v2/pull/461))
77
* Also updates utilities that directly or indirectly depend on EC2Metadata client. Signer utilities, credential providers now take in context.
8-
8+
* `private/model`: Add utility for validating shape names for structs and enums for the service packages ([#471](https://github.com/aws/aws-sdk-go-v2/pull/471))
9+
* Fixes bug which allowed service package structs, enums to start with non alphabetic character
10+
* Fixes the incorrect enum types in mediapackage service package, changing enum types __AdTriggersElement, __PeriodTriggersElement to AdTriggersElement, PeriodTriggersElement respectively.
11+
912
Services
1013
---
1114

private/model/api/load.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package api
55
import (
66
"encoding/json"
77
"fmt"
8+
"log"
89
"os"
910
"path/filepath"
1011
"sort"
@@ -172,6 +173,9 @@ func (a *API) Setup() {
172173
}
173174

174175
a.fixStutterNames()
176+
if err := a.validateShapeNames(); err != nil {
177+
log.Fatalf(err.Error())
178+
}
175179
a.renameExportable()
176180
a.applyShapeNameAliases()
177181
a.createInputOutputShapes()

private/model/api/passes.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"regexp"
88
"strings"
9+
"unicode"
910
)
1011

1112
// updateTopLevelShapeReferences moves resultWrapper, locationName, and
@@ -158,6 +159,57 @@ func (a *API) fixStutterNames() {
158159
}
159160
}
160161

162+
// regexpForValidatingShapeName is used by validateShapeName to filter acceptable shape names
163+
// that may be renamed to a new valid shape name, if not already.
164+
// The regex allows underscores(_) at the beginning of the shape name
165+
// There may be 0 or more underscores(_). The next character would be the leading character
166+
// in the renamed shape name and thus, must be an alphabetic character.
167+
// The regex allows alphanumeric characters along with underscores(_) in rest of the string.
168+
var regexForValidatingShapeName = regexp.MustCompile("^[_]*[a-zA-Z][a-zA-Z0-9_]*$")
169+
170+
// validateShapeNames is valid only for shapes of type structure or enums
171+
// We validate a shape name to check if its a valid shape name
172+
// A valid shape name would only contain alphanumeric characters and have an alphabet as leading character.
173+
//
174+
// If we encounter a shape name with underscores(_), we remove the underscores, and
175+
// follow a canonical upper camel case naming scheme to create a new shape name.
176+
// If the shape name collides with an existing shape name we return an error.
177+
// The resulting shape name must be a valid shape name or throw an error.
178+
func (a *API) validateShapeNames() error {
179+
for _, s := range a.Shapes {
180+
if s.Type == "structure" || s.IsEnum() {
181+
name := s.ShapeName
182+
if b := regexForValidatingShapeName.MatchString(name); !b {
183+
return fmt.Errorf("invalid shape name found: %v", s.ShapeName)
184+
}
185+
186+
// Slice of strings returned after we split a string
187+
// with a non alphanumeric character as delimiter.
188+
slice := strings.FieldsFunc(name, func(r rune) bool {
189+
return !unicode.IsLetter(r) && !unicode.IsNumber(r)
190+
})
191+
192+
// Build a string that follows canonical upper camel casing
193+
var b strings.Builder
194+
for _, word := range slice {
195+
b.WriteString(strings.Title(word))
196+
}
197+
198+
name = b.String()
199+
if s.ShapeName != name {
200+
if a.Shapes[name] != nil {
201+
// throw an error if shape with a new shape name already exists
202+
return fmt.Errorf("attempt to rename shape %v to %v for package %v failed, as this rename would result in shape name collision",
203+
s.ShapeName, name, a.PackageName())
204+
}
205+
debugLogger.Logf("Renaming shape %v to %v for package %v \n", s.ShapeName, name, a.PackageName())
206+
s.Rename(name)
207+
}
208+
}
209+
}
210+
return nil
211+
}
212+
161213
func (a *API) applyShapeNameAliases() {
162214
service, ok := shapeNameAliases[a.ServiceID()]
163215
if !ok {

private/model/api/passes_test.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package api
55
import (
66
"reflect"
77
"strconv"
8+
"strings"
89
"testing"
910
)
1011

@@ -501,3 +502,150 @@ func TestCreateInputOutputShapes(t *testing.T) {
501502
})
502503
}
503504
}
505+
506+
func TestValidateShapeNameMethod(t *testing.T) {
507+
cases := map[string]struct {
508+
inputShapeName string
509+
shapeType string
510+
expectedShapeName string
511+
expectedError string
512+
}{
513+
"empty case": {
514+
inputShapeName: "",
515+
shapeType: "structure",
516+
expectedShapeName: "",
517+
expectedError: "invalid shape name found",
518+
},
519+
"No rename": {
520+
inputShapeName: "Sample123Shape",
521+
shapeType: "structure",
522+
expectedShapeName: "Sample123Shape",
523+
},
524+
"starts with underscores": {
525+
inputShapeName: "__Sample123Shape",
526+
shapeType: "structure",
527+
expectedShapeName: "Sample123Shape",
528+
},
529+
"Contains underscores": {
530+
inputShapeName: "__sample_123_shape__",
531+
shapeType: "structure",
532+
expectedShapeName: "Sample123Shape",
533+
},
534+
"Starts with numeric character": {
535+
inputShapeName: "123__sampleShape",
536+
shapeType: "structure",
537+
expectedShapeName: "",
538+
expectedError: "invalid shape name found",
539+
},
540+
"Starts with non alphabetic or non underscore character": {
541+
inputShapeName: "&&SampleShape",
542+
shapeType: "structure",
543+
expectedShapeName: "",
544+
expectedError: "invalid shape name found",
545+
},
546+
"Contains non Alphanumeric or non underscore character": {
547+
inputShapeName: "Sample&__Shape",
548+
shapeType: "structure",
549+
expectedShapeName: "",
550+
expectedError: "invalid shape name found",
551+
},
552+
"Renamed Shape already exists": {
553+
inputShapeName: "__sample_shape",
554+
shapeType: "structure",
555+
expectedShapeName: "",
556+
expectedError: "rename would result in shape name collision",
557+
},
558+
"empty case for enums shape type": {
559+
inputShapeName: "",
560+
shapeType: "string",
561+
expectedShapeName: "",
562+
expectedError: "invalid shape name found",
563+
},
564+
"No rename for enums shape type": {
565+
inputShapeName: "Sample123Shape",
566+
shapeType: "string",
567+
expectedShapeName: "Sample123Shape",
568+
},
569+
"starts with underscores for enums shape type": {
570+
inputShapeName: "__Sample123Shape",
571+
shapeType: "string",
572+
expectedShapeName: "Sample123Shape",
573+
},
574+
"Contains underscores for enums shape type": {
575+
inputShapeName: "__sample_123_shape__",
576+
shapeType: "string",
577+
expectedShapeName: "Sample123Shape",
578+
},
579+
"Starts with numeric character for enums shape type": {
580+
inputShapeName: "123__sampleShape",
581+
shapeType: "string",
582+
expectedShapeName: "",
583+
expectedError: "invalid shape name found",
584+
},
585+
"Starts with non alphabetic or non underscore character for enums shape type": {
586+
inputShapeName: "&&SampleShape",
587+
shapeType: "string",
588+
expectedShapeName: "",
589+
expectedError: "invalid shape name found",
590+
},
591+
"Contains non Alphanumeric or non underscore character for enums shape type": {
592+
inputShapeName: "Sample&__Shape",
593+
shapeType: "string",
594+
expectedShapeName: "",
595+
expectedError: "invalid shape name found",
596+
},
597+
"Renamed Shape already exists for enums shape type": {
598+
inputShapeName: "__sample_shape",
599+
shapeType: "string",
600+
expectedShapeName: "",
601+
expectedError: "rename would result in shape name collision",
602+
},
603+
}
604+
605+
for name, c := range cases {
606+
operation := "FooOperation"
607+
t.Run(name, func(t *testing.T) {
608+
a := &API{
609+
Operations: map[string]*Operation{},
610+
Shapes: map[string]*Shape{},
611+
}
612+
// add another shape with name SampleShape to check for collision
613+
a.Shapes["SampleShape"] = &Shape{ShapeName: "SampleShape"}
614+
o := &Operation{
615+
Name: operation,
616+
ExportedName: operation,
617+
InputRef: ShapeRef{
618+
API: a,
619+
ShapeName: c.inputShapeName,
620+
Shape: &Shape{
621+
API: a,
622+
ShapeName: c.inputShapeName,
623+
Type: c.shapeType,
624+
Enum: []string{"x"},
625+
},
626+
},
627+
}
628+
o.InputRef.Shape.refs = append(o.InputRef.Shape.refs, &o.InputRef)
629+
a.Operations[o.Name] = o
630+
a.Shapes[c.inputShapeName] = o.InputRef.Shape
631+
632+
err := a.validateShapeNames()
633+
if err != nil || c.expectedError != "" {
634+
if err == nil {
635+
t.Fatalf("Received no error, expected error with log: \n \t %v ", c.expectedError)
636+
}
637+
if c.expectedError == "" {
638+
t.Fatalf("Expected no error, got %v", err.Error())
639+
}
640+
if e, a := err.Error(), c.expectedError; !strings.Contains(e, a) {
641+
t.Fatalf("Expected to receive error containing %v, got %v", e, a)
642+
}
643+
return
644+
}
645+
646+
if e, a := c.expectedShapeName, o.InputRef.Shape.ShapeName; e != a {
647+
t.Fatalf("Expected shape name to be %v, got %v", e, a)
648+
}
649+
})
650+
}
651+
}

service/ec2/api_enums.go

Lines changed: 17 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)