Switch to using the dep tool and update all the dependencies

This commit is contained in:
Nick Craig-Wood 2017-05-11 15:39:54 +01:00
parent 5135ff73cb
commit 98c2d2c41b
5321 changed files with 4483201 additions and 5922 deletions

View file

@ -0,0 +1,799 @@
// +build codegen
// Package api represents API abstractions for rendering service generated files.
package api
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"path"
"path/filepath"
"regexp"
"sort"
"strings"
"text/template"
)
// An API defines a service API's definition. and logic to serialize the definition.
type API struct {
Metadata Metadata
Operations map[string]*Operation
Shapes map[string]*Shape
Waiters []Waiter
Documentation string
// Set to true to avoid removing unused shapes
NoRemoveUnusedShapes bool
// Set to true to avoid renaming to 'Input/Output' postfixed shapes
NoRenameToplevelShapes bool
// Set to true to ignore service/request init methods (for testing)
NoInitMethods bool
// Set to true to ignore String() and GoString methods (for generated tests)
NoStringerMethods bool
// Set to true to not generate API service name constants
NoConstServiceNames bool
// Set to true to not generate validation shapes
NoValidataShapeMethods bool
// Set to true to not generate struct field accessors
NoGenStructFieldAccessors bool
SvcClientImportPath string
initialized bool
imports map[string]bool
name string
path string
BaseCrosslinkURL string
}
// A Metadata is the metadata about an API's definition.
type Metadata struct {
APIVersion string
EndpointPrefix string
SigningName string
ServiceAbbreviation string
ServiceFullName string
SignatureVersion string
JSONVersion string
TargetPrefix string
Protocol string
UID string
EndpointsID string
NoResolveEndpoint bool
}
var serviceAliases map[string]string
func Bootstrap() error {
b, err := ioutil.ReadFile(filepath.Join("..", "models", "customizations", "service-aliases.json"))
if err != nil {
return err
}
return json.Unmarshal(b, &serviceAliases)
}
// PackageName name of the API package
func (a *API) PackageName() string {
return strings.ToLower(a.StructName())
}
// InterfacePackageName returns the package name for the interface.
func (a *API) InterfacePackageName() string {
return a.PackageName() + "iface"
}
var nameRegex = regexp.MustCompile(`^Amazon|AWS\s*|\(.*|\s+|\W+`)
// StructName returns the struct name for a given API.
func (a *API) StructName() string {
if a.name == "" {
name := a.Metadata.ServiceAbbreviation
if name == "" {
name = a.Metadata.ServiceFullName
}
name = nameRegex.ReplaceAllString(name, "")
a.name = name
if name, ok := serviceAliases[strings.ToLower(name)]; ok {
a.name = name
}
}
return a.name
}
// UseInitMethods returns if the service's init method should be rendered.
func (a *API) UseInitMethods() bool {
return !a.NoInitMethods
}
// NiceName returns the human friendly API name.
func (a *API) NiceName() string {
if a.Metadata.ServiceAbbreviation != "" {
return a.Metadata.ServiceAbbreviation
}
return a.Metadata.ServiceFullName
}
// ProtocolPackage returns the package name of the protocol this API uses.
func (a *API) ProtocolPackage() string {
switch a.Metadata.Protocol {
case "json":
return "jsonrpc"
case "ec2":
return "ec2query"
default:
return strings.Replace(a.Metadata.Protocol, "-", "", -1)
}
}
// OperationNames returns a slice of API operations supported.
func (a *API) OperationNames() []string {
i, names := 0, make([]string, len(a.Operations))
for n := range a.Operations {
names[i] = n
i++
}
sort.Strings(names)
return names
}
// OperationList returns a slice of API operation pointers
func (a *API) OperationList() []*Operation {
list := make([]*Operation, len(a.Operations))
for i, n := range a.OperationNames() {
list[i] = a.Operations[n]
}
return list
}
// OperationHasOutputPlaceholder returns if any of the API operation input
// or output shapes are place holders.
func (a *API) OperationHasOutputPlaceholder() bool {
for _, op := range a.Operations {
if op.OutputRef.Shape.Placeholder {
return true
}
}
return false
}
// ShapeNames returns a slice of names for each shape used by the API.
func (a *API) ShapeNames() []string {
i, names := 0, make([]string, len(a.Shapes))
for n := range a.Shapes {
names[i] = n
i++
}
sort.Strings(names)
return names
}
// ShapeList returns a slice of shape pointers used by the API.
//
// Will exclude error shapes from the list of shapes returned.
func (a *API) ShapeList() []*Shape {
list := make([]*Shape, 0, len(a.Shapes))
for _, n := range a.ShapeNames() {
// Ignore error shapes in list
if s := a.Shapes[n]; !s.IsError {
list = append(list, s)
}
}
return list
}
// ShapeListErrors returns a list of the errors defined by the API model
func (a *API) ShapeListErrors() []*Shape {
list := []*Shape{}
for _, n := range a.ShapeNames() {
// Ignore error shapes in list
if s := a.Shapes[n]; s.IsError {
list = append(list, s)
}
}
return list
}
// resetImports resets the import map to default values.
func (a *API) resetImports() {
a.imports = map[string]bool{
"github.com/aws/aws-sdk-go/aws": true,
}
}
// importsGoCode returns the generated Go import code.
func (a *API) importsGoCode() string {
if len(a.imports) == 0 {
return ""
}
corePkgs, extPkgs := []string{}, []string{}
for i := range a.imports {
if strings.Contains(i, ".") {
extPkgs = append(extPkgs, i)
} else {
corePkgs = append(corePkgs, i)
}
}
sort.Strings(corePkgs)
sort.Strings(extPkgs)
code := "import (\n"
for _, i := range corePkgs {
code += fmt.Sprintf("\t%q\n", i)
}
if len(corePkgs) > 0 {
code += "\n"
}
for _, i := range extPkgs {
code += fmt.Sprintf("\t%q\n", i)
}
code += ")\n\n"
return code
}
// A tplAPI is the top level template for the API
var tplAPI = template.Must(template.New("api").Parse(`
{{ range $_, $o := .OperationList }}
{{ $o.GoCode }}
{{ end }}
{{ range $_, $s := .ShapeList }}
{{ if and $s.IsInternal (eq $s.Type "structure") }}{{ $s.GoCode }}{{ end }}
{{ end }}
{{ range $_, $s := .ShapeList }}
{{ if $s.IsEnum }}{{ $s.GoCode }}{{ end }}
{{ end }}
`))
// APIGoCode renders the API in Go code. Returning it as a string
func (a *API) APIGoCode() string {
a.resetImports()
a.imports["github.com/aws/aws-sdk-go/aws/awsutil"] = true
a.imports["github.com/aws/aws-sdk-go/aws/request"] = true
if a.OperationHasOutputPlaceholder() {
a.imports["github.com/aws/aws-sdk-go/private/protocol/"+a.ProtocolPackage()] = true
a.imports["github.com/aws/aws-sdk-go/private/protocol"] = true
}
for _, op := range a.Operations {
if op.AuthType == "none" {
a.imports["github.com/aws/aws-sdk-go/aws/credentials"] = true
break
}
}
var buf bytes.Buffer
err := tplAPI.Execute(&buf, a)
if err != nil {
panic(err)
}
code := a.importsGoCode() + strings.TrimSpace(buf.String())
return code
}
var noCrossLinkServices = map[string]struct{}{
"apigateway": struct{}{},
"budgets": struct{}{},
"cloudsearch": struct{}{},
"cloudsearchdomain": struct{}{},
"discovery": struct{}{},
"elastictranscoder": struct{}{},
"es": struct{}{},
"glacier": struct{}{},
"importexport": struct{}{},
"iot": struct{}{},
"iot-data": struct{}{},
"lambda": struct{}{},
"machinelearning": struct{}{},
"rekognition": struct{}{},
"sdb": struct{}{},
"swf": struct{}{},
}
func GetCrosslinkURL(baseURL, name, uid string, params ...string) string {
_, ok := noCrossLinkServices[strings.ToLower(name)]
if uid != "" && baseURL != "" && !ok {
return strings.Join(append([]string{baseURL, "goto", "WebAPI", uid}, params...), "/")
}
return ""
}
func (a *API) APIName() string {
return a.name
}
var tplServiceDoc = template.Must(template.New("service docs").Funcs(template.FuncMap{
"GetCrosslinkURL": GetCrosslinkURL,
}).
Parse(`
// Package {{ .PackageName }} provides the client and types for making API
// requests to {{ .Metadata.ServiceFullName }}.
{{ if .Documentation -}}
//
{{ .Documentation }}
{{ end -}}
{{ $crosslinkURL := GetCrosslinkURL $.BaseCrosslinkURL $.APIName $.Metadata.UID -}}
{{ if $crosslinkURL -}}
//
// See {{ $crosslinkURL }} for more information on this service.
{{ end -}}
//
// See {{ .PackageName }} package documentation for more information.
// https://docs.aws.amazon.com/sdk-for-go/api/service/{{ .PackageName }}/
//
// Using the Client
//
// To use the client for {{ .Metadata.ServiceFullName }} you will first need
// to create a new instance of it.
//
// When creating a client for an AWS service you'll first need to have a Session
// already created. The Session provides configuration that can be shared
// between multiple service clients. Additional configuration can be applied to
// the Session and service's client when they are constructed. The aws package's
// Config type contains several fields such as Region for the AWS Region the
// client should make API requests too. The optional Config value can be provided
// as the variadic argument for Sessions and client creation.
//
// Once the service's client is created you can use it to make API requests the
// AWS service. These clients are safe to use concurrently.
//
// // Create a session to share configuration, and load external configuration.
// sess := session.Must(session.NewSession())
//
// // Create the service's client with the session.
// svc := {{ .PackageName }}.New(sess)
//
// See the SDK's documentation for more information on how to use service clients.
// https://docs.aws.amazon.com/sdk-for-go/api/
//
// See aws package's Config type for more information on configuration options.
// https://docs.aws.amazon.com/sdk-for-go/api/aws/#Config
//
// See the {{ .Metadata.ServiceFullName }} client {{ .StructName }} for more
// information on creating the service's client.
// https://docs.aws.amazon.com/sdk-for-go/api/service/{{ .PackageName }}/#New
//
{{ $opts := .OperationNames -}}
{{ $optName := index $opts 0 -}}
{{ $opt := index .Operations $optName -}}
{{ $optInputName := $opt.InputRef.GoTypeWithPkgName -}}
// Once the client is created you can make an API request to the service.
// Each API method takes a input parameter, and returns the service response
// and an error.
//
// The API method will document which error codes the service can be returned
// by the operation if the service models the API operation's errors. These
// errors will also be available as const strings prefixed with "ErrCode".
//
// result, err := svc.{{ $opt.ExportedName }}(params)
// if err != nil {
// // Cast err to awserr.Error to handle specific error codes.
// aerr, ok := err.(awserr.Error)
// if ok && aerr.Code() == <error code to check for> {
// // Specific error code handling
// }
// return err
// }
//
// fmt.Println("{{ $optName }} result:")
// fmt.Println(result)
//
// Using the Client with Context
//
// The service's client also provides methods to make API requests with a Context
// value. This allows you to control the timeout, and cancellation of pending
// requests. These methods also take request Option as variadic parameter to apply
// additional configuration to the API request.
//
// ctx := context.Background()
//
// result, err := svc.{{ $opt.ExportedName }}WithContext(ctx, params)
//
// See the request package documentation for more information on using Context pattern
// with the SDK.
// https://docs.aws.amazon.com/sdk-for-go/api/aws/request/
`))
// A tplService defines the template for the service generated code.
var tplService = template.Must(template.New("service").Funcs(template.FuncMap{
"ServiceNameValue": func(a *API) string {
if a.NoConstServiceNames {
return fmt.Sprintf("%q", a.Metadata.EndpointPrefix)
}
return "ServiceName"
},
"EndpointsIDConstValue": func(a *API) string {
if a.NoConstServiceNames {
return fmt.Sprintf("%q", a.Metadata.EndpointPrefix)
}
if a.Metadata.EndpointPrefix == a.Metadata.EndpointsID {
return "ServiceName"
}
return fmt.Sprintf("%q", a.Metadata.EndpointsID)
},
"EndpointsIDValue": func(a *API) string {
if a.NoConstServiceNames {
return fmt.Sprintf("%q", a.Metadata.EndpointPrefix)
}
return "EndpointsID"
},
}).Parse(`
// {{ .StructName }} provides the API operation methods for making requests to
// {{ .Metadata.ServiceFullName }}. See this package's package overview docs
// for details on the service.
//
// {{ .StructName }} methods are safe to use concurrently. It is not safe to
// modify mutate any of the struct's properties though.
type {{ .StructName }} struct {
*client.Client
}
{{ if .UseInitMethods }}// Used for custom client initialization logic
var initClient func(*client.Client)
// Used for custom request initialization logic
var initRequest func(*request.Request)
{{ end }}
{{ if not .NoConstServiceNames -}}
// Service information constants
const (
ServiceName = "{{ .Metadata.EndpointPrefix }}" // Service endpoint prefix API calls made to.
EndpointsID = {{ EndpointsIDConstValue . }} // Service ID for Regions and Endpoints metadata.
)
{{- end }}
// New creates a new instance of the {{ .StructName }} client with a session.
// If additional configuration is needed for the client instance use the optional
// aws.Config parameter to add your extra config.
//
// Example:
// // Create a {{ .StructName }} client from just a session.
// svc := {{ .PackageName }}.New(mySession)
//
// // Create a {{ .StructName }} client with additional configuration
// svc := {{ .PackageName }}.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
func New(p client.ConfigProvider, cfgs ...*aws.Config) *{{ .StructName }} {
{{ if .Metadata.NoResolveEndpoint -}}
var c client.Config
if v, ok := p.(client.ConfigNoResolveEndpointProvider); ok {
c = v.ClientConfigNoResolveEndpoint(cfgs...)
} else {
c = p.ClientConfig({{ EndpointsIDValue . }}, cfgs...)
}
{{- else -}}
c := p.ClientConfig({{ EndpointsIDValue . }}, cfgs...)
{{- end }}
return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion, c.SigningName)
}
// newClient creates, initializes and returns a new service client instance.
func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion, signingName string) *{{ .StructName }} {
{{- if .Metadata.SigningName }}
if len(signingName) == 0 {
signingName = "{{ .Metadata.SigningName }}"
}
{{- end }}
svc := &{{ .StructName }}{
Client: client.New(
cfg,
metadata.ClientInfo{
ServiceName: {{ ServiceNameValue . }},
SigningName: signingName,
SigningRegion: signingRegion,
Endpoint: endpoint,
APIVersion: "{{ .Metadata.APIVersion }}",
{{ if .Metadata.JSONVersion -}}
JSONVersion: "{{ .Metadata.JSONVersion }}",
{{- end }}
{{ if .Metadata.TargetPrefix -}}
TargetPrefix: "{{ .Metadata.TargetPrefix }}",
{{- end }}
},
handlers,
),
}
// Handlers
svc.Handlers.Sign.PushBackNamed({{if eq .Metadata.SignatureVersion "v2"}}v2{{else}}v4{{end}}.SignRequestHandler)
{{- if eq .Metadata.SignatureVersion "v2" }}
svc.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler)
{{- end }}
svc.Handlers.Build.PushBackNamed({{ .ProtocolPackage }}.BuildHandler)
svc.Handlers.Unmarshal.PushBackNamed({{ .ProtocolPackage }}.UnmarshalHandler)
svc.Handlers.UnmarshalMeta.PushBackNamed({{ .ProtocolPackage }}.UnmarshalMetaHandler)
svc.Handlers.UnmarshalError.PushBackNamed({{ .ProtocolPackage }}.UnmarshalErrorHandler)
{{ if .UseInitMethods }}// Run custom client initialization if present
if initClient != nil {
initClient(svc.Client)
}
{{ end }}
return svc
}
// newRequest creates a new request for a {{ .StructName }} operation and runs any
// custom request initialization.
func (c *{{ .StructName }}) newRequest(op *request.Operation, params, data interface{}) *request.Request {
req := c.NewRequest(op, params, data)
{{ if .UseInitMethods }}// Run custom request initialization if present
if initRequest != nil {
initRequest(req)
}
{{ end }}
return req
}
`))
// ServicePackageDoc generates the contents of the doc file for the service.
//
// Will also read in the custom doc templates for the service if found.
func (a *API) ServicePackageDoc() string {
a.imports = map[string]bool{}
var buf bytes.Buffer
if err := tplServiceDoc.Execute(&buf, a); err != nil {
panic(err)
}
return buf.String()
}
// ServiceGoCode renders service go code. Returning it as a string.
func (a *API) ServiceGoCode() string {
a.resetImports()
a.imports["github.com/aws/aws-sdk-go/aws/client"] = true
a.imports["github.com/aws/aws-sdk-go/aws/client/metadata"] = true
a.imports["github.com/aws/aws-sdk-go/aws/request"] = true
if a.Metadata.SignatureVersion == "v2" {
a.imports["github.com/aws/aws-sdk-go/private/signer/v2"] = true
a.imports["github.com/aws/aws-sdk-go/aws/corehandlers"] = true
} else {
a.imports["github.com/aws/aws-sdk-go/aws/signer/v4"] = true
}
a.imports["github.com/aws/aws-sdk-go/private/protocol/"+a.ProtocolPackage()] = true
var buf bytes.Buffer
err := tplService.Execute(&buf, a)
if err != nil {
panic(err)
}
code := a.importsGoCode() + buf.String()
return code
}
// ExampleGoCode renders service example code. Returning it as a string.
func (a *API) ExampleGoCode() string {
exs := []string{}
imports := map[string]bool{}
for _, o := range a.OperationList() {
o.imports = map[string]bool{}
exs = append(exs, o.Example())
for k, v := range o.imports {
imports[k] = v
}
}
code := fmt.Sprintf("import (\n%q\n%q\n%q\n\n%q\n%q\n%q\n",
"bytes",
"fmt",
"time",
"github.com/aws/aws-sdk-go/aws",
"github.com/aws/aws-sdk-go/aws/session",
path.Join(a.SvcClientImportPath, a.PackageName()),
)
for k, _ := range imports {
code += fmt.Sprintf("%q\n", k)
}
code += ")\n\n"
code += "var _ time.Duration\nvar _ bytes.Buffer\n\n"
code += strings.Join(exs, "\n\n")
return code
}
// A tplInterface defines the template for the service interface type.
var tplInterface = template.Must(template.New("interface").Parse(`
// {{ .StructName }}API provides an interface to enable mocking the
// {{ .PackageName }}.{{ .StructName }} service client's API operation,
// paginators, and waiters. This make unit testing your code that calls out
// to the SDK's service client's calls easier.
//
// The best way to use this interface is so the SDK's service client's calls
// can be stubbed out for unit testing your code with the SDK without needing
// to inject custom request handlers into the the SDK's request pipeline.
//
// // myFunc uses an SDK service client to make a request to
// // {{.Metadata.ServiceFullName}}. {{ $opts := .OperationList }}{{ $opt := index $opts 0 }}
// func myFunc(svc {{ .InterfacePackageName }}.{{ .StructName }}API) bool {
// // Make svc.{{ $opt.ExportedName }} request
// }
//
// func main() {
// sess := session.New()
// svc := {{ .PackageName }}.New(sess)
//
// myFunc(svc)
// }
//
// In your _test.go file:
//
// // Define a mock struct to be used in your unit tests of myFunc.
// type mock{{ .StructName }}Client struct {
// {{ .InterfacePackageName }}.{{ .StructName }}API
// }
// func (m *mock{{ .StructName }}Client) {{ $opt.ExportedName }}(input {{ $opt.InputRef.GoTypeWithPkgName }}) ({{ $opt.OutputRef.GoTypeWithPkgName }}, error) {
// // mock response/functionality
// }
//
// func TestMyFunc(t *testing.T) {
// // Setup Test
// mockSvc := &mock{{ .StructName }}Client{}
//
// myfunc(mockSvc)
//
// // Verify myFunc's functionality
// }
//
// It is important to note that this interface will have breaking changes
// when the service model is updated and adds new API operations, paginators,
// and waiters. Its suggested to use the pattern above for testing, or using
// tooling to generate mocks to satisfy the interfaces.
type {{ .StructName }}API interface {
{{ range $_, $o := .OperationList }}
{{ $o.InterfaceSignature }}
{{ end }}
{{ range $_, $w := .Waiters }}
{{ $w.InterfaceSignature }}
{{ end }}
}
var _ {{ .StructName }}API = (*{{ .PackageName }}.{{ .StructName }})(nil)
`))
// InterfaceGoCode returns the go code for the service's API operations as an
// interface{}. Assumes that the interface is being created in a different
// package than the service API's package.
func (a *API) InterfaceGoCode() string {
a.resetImports()
a.imports = map[string]bool{
"github.com/aws/aws-sdk-go/aws": true,
"github.com/aws/aws-sdk-go/aws/request": true,
path.Join(a.SvcClientImportPath, a.PackageName()): true,
}
var buf bytes.Buffer
err := tplInterface.Execute(&buf, a)
if err != nil {
panic(err)
}
code := a.importsGoCode() + strings.TrimSpace(buf.String())
return code
}
// NewAPIGoCodeWithPkgName returns a string of instantiating the API prefixed
// with its package name. Takes a string depicting the Config.
func (a *API) NewAPIGoCodeWithPkgName(cfg string) string {
return fmt.Sprintf("%s.New(%s)", a.PackageName(), cfg)
}
// computes the validation chain for all input shapes
func (a *API) addShapeValidations() {
for _, o := range a.Operations {
resolveShapeValidations(o.InputRef.Shape)
}
}
// Updates the source shape and all nested shapes with the validations that
// could possibly be needed.
func resolveShapeValidations(s *Shape, ancestry ...*Shape) {
for _, a := range ancestry {
if a == s {
return
}
}
children := []string{}
for _, name := range s.MemberNames() {
ref := s.MemberRefs[name]
if s.IsRequired(name) && !s.Validations.Has(ref, ShapeValidationRequired) {
s.Validations = append(s.Validations, ShapeValidation{
Name: name, Ref: ref, Type: ShapeValidationRequired,
})
}
if ref.Shape.Min != 0 && !s.Validations.Has(ref, ShapeValidationMinVal) {
s.Validations = append(s.Validations, ShapeValidation{
Name: name, Ref: ref, Type: ShapeValidationMinVal,
})
}
switch ref.Shape.Type {
case "map", "list", "structure":
children = append(children, name)
}
}
ancestry = append(ancestry, s)
for _, name := range children {
ref := s.MemberRefs[name]
// Since this is a grab bag we will just continue since
// we can't validate because we don't know the valued shape.
if ref.JSONValue {
continue
}
nestedShape := ref.Shape.NestedShape()
var v *ShapeValidation
if len(nestedShape.Validations) > 0 {
v = &ShapeValidation{
Name: name, Ref: ref, Type: ShapeValidationNested,
}
} else {
resolveShapeValidations(nestedShape, ancestry...)
if len(nestedShape.Validations) > 0 {
v = &ShapeValidation{
Name: name, Ref: ref, Type: ShapeValidationNested,
}
}
}
if v != nil && !s.Validations.Has(v.Ref, v.Type) {
s.Validations = append(s.Validations, *v)
}
}
ancestry = ancestry[:len(ancestry)-1]
}
// A tplAPIErrors is the top level template for the API
var tplAPIErrors = template.Must(template.New("api").Parse(`
const (
{{ range $_, $s := $.ShapeListErrors }}
// {{ $s.ErrorCodeName }} for service response error code
// {{ printf "%q" $s.ErrorName }}.
{{ if $s.Docstring -}}
//
{{ $s.Docstring }}
{{ end -}}
{{ $s.ErrorCodeName }} = {{ printf "%q" $s.ErrorName }}
{{ end }}
)
`))
func (a *API) APIErrorsGoCode() string {
var buf bytes.Buffer
err := tplAPIErrors.Execute(&buf, a)
if err != nil {
panic(err)
}
return strings.TrimSpace(buf.String())
}

View file

@ -0,0 +1,54 @@
// +build 1.6,codegen
package api
import (
"testing"
)
func TestStructNameWithFullName(t *testing.T) {
a := API{
Metadata: Metadata{
ServiceFullName: "Amazon Service Name-100",
},
}
if a.StructName() != "ServiceName100" {
t.Errorf("API struct name should have been %s, but received %s", "ServiceName100", a.StructName())
}
}
func TestStructNameWithAbbreviation(t *testing.T) {
a := API{
Metadata: Metadata{
ServiceFullName: "AWS Service Name-100",
ServiceAbbreviation: "AWS SN100",
},
}
if a.StructName() != "SN100" {
t.Errorf("API struct name should have been %s, but received %s", "SN100", a.StructName())
}
}
func TestStructNameForExceptions(t *testing.T) {
serviceAliases = map[string]string{}
serviceAliases["elasticloadbalancing"] = "ELB"
serviceAliases["config"] = "ConfigService"
a := API{
Metadata: Metadata{
ServiceFullName: "Elastic Load Balancing",
},
}
if a.StructName() != "ELB" {
t.Errorf("API struct name should have been %s, but received %s", "ELB", a.StructName())
}
a = API{
Metadata: Metadata{
ServiceFullName: "AWS Config",
},
}
if a.StructName() != "ConfigService" {
t.Errorf("API struct name should have been %s, but received %s", "ConfigService", a.StructName())
}
}

View file

@ -0,0 +1,166 @@
// +build codegen
package api
import (
"io/ioutil"
"path/filepath"
"strings"
)
type service struct {
srcName string
dstName string
serviceVersion string
}
var mergeServices = map[string]service{
"dynamodbstreams": service{
dstName: "dynamodb",
srcName: "streams.dynamodb",
},
"wafregional": service{
dstName: "waf",
srcName: "waf-regional",
serviceVersion: "2015-08-24",
},
}
// customizationPasses Executes customization logic for the API by package name.
func (a *API) customizationPasses() {
var svcCustomizations = map[string]func(*API){
"s3": s3Customizations,
"cloudfront": cloudfrontCustomizations,
"rds": rdsCustomizations,
// Disable endpoint resolving for services that require customer
// to provide endpoint them selves.
"cloudsearchdomain": disableEndpointResolving,
"iotdataplane": disableEndpointResolving,
}
for k, _ := range mergeServices {
svcCustomizations[k] = mergeServicesCustomizations
}
if fn := svcCustomizations[a.PackageName()]; fn != nil {
fn(a)
}
}
// s3Customizations customizes the API generation to replace values specific to S3.
func s3Customizations(a *API) {
var strExpires *Shape
for name, s := range a.Shapes {
// Remove ContentMD5 members
if _, ok := s.MemberRefs["ContentMD5"]; ok {
delete(s.MemberRefs, "ContentMD5")
}
// Expires should be a string not time.Time since the format is not
// enforced by S3, and any value can be set to this field outside of the SDK.
if strings.HasSuffix(name, "Output") {
if ref, ok := s.MemberRefs["Expires"]; ok {
if strExpires == nil {
newShape := *ref.Shape
strExpires = &newShape
strExpires.Type = "string"
strExpires.refs = []*ShapeRef{}
}
ref.Shape.removeRef(ref)
ref.Shape = strExpires
ref.Shape.refs = append(ref.Shape.refs, &s.MemberRef)
}
}
}
s3CustRemoveHeadObjectModeledErrors(a)
}
// S3 HeadObject API call incorrect models NoSuchKey as valid
// error code that can be returned. This operation does not
// return error codes, all error codes are derived from HTTP
// status codes.
//
// aws/aws-sdk-go#1208
func s3CustRemoveHeadObjectModeledErrors(a *API) {
op, ok := a.Operations["HeadObject"]
if !ok {
return
}
op.Documentation += `
//
// See http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#RESTErrorResponses
// for more information on returned errors.`
op.ErrorRefs = []ShapeRef{}
}
// cloudfrontCustomizations customized the API generation to replace values
// specific to CloudFront.
func cloudfrontCustomizations(a *API) {
// MaxItems members should always be integers
for _, s := range a.Shapes {
if ref, ok := s.MemberRefs["MaxItems"]; ok {
ref.ShapeName = "Integer"
ref.Shape = a.Shapes["Integer"]
}
}
}
// mergeServicesCustomizations references any duplicate shapes from DynamoDB
func mergeServicesCustomizations(a *API) {
info := mergeServices[a.PackageName()]
p := strings.Replace(a.path, info.srcName, info.dstName, -1)
if info.serviceVersion != "" {
index := strings.LastIndex(p, "/")
files, _ := ioutil.ReadDir(p[:index])
if len(files) > 1 {
panic("New version was introduced")
}
p = p[:index] + "/" + info.serviceVersion
}
file := filepath.Join(p, "api-2.json")
serviceAPI := API{}
serviceAPI.Attach(file)
serviceAPI.Setup()
for n := range a.Shapes {
if _, ok := serviceAPI.Shapes[n]; ok {
a.Shapes[n].resolvePkg = "github.com/aws/aws-sdk-go/service/" + info.dstName
}
}
}
// rdsCustomizations are customization for the service/rds. This adds non-modeled fields used for presigning.
func rdsCustomizations(a *API) {
inputs := []string{
"CopyDBSnapshotInput",
"CreateDBInstanceReadReplicaInput",
"CopyDBClusterSnapshotInput",
"CreateDBClusterInput",
}
for _, input := range inputs {
if ref, ok := a.Shapes[input]; ok {
ref.MemberRefs["SourceRegion"] = &ShapeRef{
Documentation: docstring(`SourceRegion is the source region where the resource exists. This is not sent over the wire and is only used for presigning. This value should always have the same region as the source ARN.`),
ShapeName: "String",
Shape: a.Shapes["String"],
Ignore: true,
}
ref.MemberRefs["DestinationRegion"] = &ShapeRef{
Documentation: docstring(`DestinationRegion is used for presigning the request to a given region.`),
ShapeName: "String",
Shape: a.Shapes["String"],
}
}
}
}
func disableEndpointResolving(a *API) {
a.Metadata.NoResolveEndpoint = true
}

View file

@ -0,0 +1,407 @@
// +build codegen
package api
import (
"bytes"
"encoding/json"
"fmt"
"html"
"os"
"regexp"
"strings"
xhtml "golang.org/x/net/html"
)
type apiDocumentation struct {
*API
Operations map[string]string
Service string
Shapes map[string]shapeDocumentation
}
type shapeDocumentation struct {
Base string
Refs map[string]string
}
// AttachDocs attaches documentation from a JSON filename.
func (a *API) AttachDocs(filename string) {
d := apiDocumentation{API: a}
f, err := os.Open(filename)
defer f.Close()
if err != nil {
panic(err)
}
err = json.NewDecoder(f).Decode(&d)
if err != nil {
panic(err)
}
d.setup()
}
func (d *apiDocumentation) setup() {
d.API.Documentation = docstring(d.Service)
for op, doc := range d.Operations {
d.API.Operations[op].Documentation = docstring(doc)
}
for shape, info := range d.Shapes {
if sh := d.API.Shapes[shape]; sh != nil {
sh.Documentation = docstring(info.Base)
}
for ref, doc := range info.Refs {
if doc == "" {
continue
}
parts := strings.Split(ref, "$")
if sh := d.API.Shapes[parts[0]]; sh != nil {
if m := sh.MemberRefs[parts[1]]; m != nil {
m.Documentation = docstring(doc)
}
}
}
}
}
var reNewline = regexp.MustCompile(`\r?\n`)
var reMultiSpace = regexp.MustCompile(`\s+`)
var reComments = regexp.MustCompile(`<!--.*?-->`)
var reFullnameBlock = regexp.MustCompile(`<fullname>(.+?)<\/fullname>`)
var reFullname = regexp.MustCompile(`<fullname>(.*?)</fullname>`)
var reExamples = regexp.MustCompile(`<examples?>.+?<\/examples?>`)
var reEndNL = regexp.MustCompile(`\n+$`)
// docstring rewrites a string to insert godocs formatting.
func docstring(doc string) string {
doc = strings.TrimSpace(doc)
if doc == "" {
return ""
}
doc = reNewline.ReplaceAllString(doc, "")
doc = reMultiSpace.ReplaceAllString(doc, " ")
doc = reComments.ReplaceAllString(doc, "")
var fullname string
parts := reFullnameBlock.FindStringSubmatch(doc)
if len(parts) > 1 {
fullname = parts[1]
}
// Remove full name block from doc string
doc = reFullname.ReplaceAllString(doc, "")
doc = reExamples.ReplaceAllString(doc, "")
doc = generateDoc(doc)
doc = reEndNL.ReplaceAllString(doc, "")
doc = html.UnescapeString(doc)
// Replace doc with full name if doc is empty.
doc = strings.TrimSpace(doc)
if len(doc) == 0 {
doc = fullname
}
return commentify(doc)
}
const (
indent = " "
)
// style is what we want to prefix a string with.
// For instance, <li>Foo</li><li>Bar</li>, will generate
// * Foo
// * Bar
var style = map[string]string{
"ul": indent + "* ",
"li": indent + "* ",
"code": indent,
"pre": indent,
}
// commentify converts a string to a Go comment
func commentify(doc string) string {
if len(doc) == 0 {
return ""
}
lines := strings.Split(doc, "\n")
out := make([]string, 0, len(lines))
for i := 0; i < len(lines); i++ {
line := lines[i]
if i > 0 && line == "" && lines[i-1] == "" {
continue
}
out = append(out, line)
}
if len(out) > 0 {
out[0] = "// " + out[0]
return strings.Join(out, "\n// ")
}
return ""
}
// wrap returns a rewritten version of text to have line breaks
// at approximately length characters. Line breaks will only be
// inserted into whitespace.
func wrap(text string, length int, isIndented bool) string {
var buf bytes.Buffer
var last rune
var lastNL bool
var col int
for _, c := range text {
switch c {
case '\r': // ignore this
continue // and also don't track `last`
case '\n': // ignore this too, but reset col
if col >= length || last == '\n' {
buf.WriteString("\n")
}
buf.WriteString("\n")
col = 0
case ' ', '\t': // opportunity to split
if col >= length {
buf.WriteByte('\n')
col = 0
if isIndented {
buf.WriteString(indent)
col += 3
}
} else {
// We only want to write a leading space if the col is greater than zero.
// This will provide the proper spacing for documentation.
buf.WriteRune(c)
col++ // count column
}
default:
buf.WriteRune(c)
col++
}
lastNL = c == '\n'
_ = lastNL
last = c
}
return buf.String()
}
type tagInfo struct {
tag string
key string
val string
txt string
raw string
closingTag bool
}
// generateDoc will generate the proper doc string for html encoded or plain text doc entries.
func generateDoc(htmlSrc string) string {
tokenizer := xhtml.NewTokenizer(strings.NewReader(htmlSrc))
tokens := buildTokenArray(tokenizer)
scopes := findScopes(tokens)
return walk(scopes)
}
func buildTokenArray(tokenizer *xhtml.Tokenizer) []tagInfo {
tokens := []tagInfo{}
for tt := tokenizer.Next(); tt != xhtml.ErrorToken; tt = tokenizer.Next() {
switch tt {
case xhtml.TextToken:
txt := string(tokenizer.Text())
if len(tokens) == 0 {
info := tagInfo{
raw: txt,
}
tokens = append(tokens, info)
}
tn, _ := tokenizer.TagName()
key, val, _ := tokenizer.TagAttr()
info := tagInfo{
tag: string(tn),
key: string(key),
val: string(val),
txt: txt,
}
tokens = append(tokens, info)
case xhtml.StartTagToken:
tn, _ := tokenizer.TagName()
key, val, _ := tokenizer.TagAttr()
info := tagInfo{
tag: string(tn),
key: string(key),
val: string(val),
}
tokens = append(tokens, info)
case xhtml.SelfClosingTagToken, xhtml.EndTagToken:
tn, _ := tokenizer.TagName()
key, val, _ := tokenizer.TagAttr()
info := tagInfo{
tag: string(tn),
key: string(key),
val: string(val),
closingTag: true,
}
tokens = append(tokens, info)
}
}
return tokens
}
// walk is used to traverse each scoped block. These scoped
// blocks will act as blocked text where we do most of our
// text manipulation.
func walk(scopes [][]tagInfo) string {
doc := ""
// Documentation will be chunked by scopes.
// Meaning, for each scope will be divided by one or more newlines.
for _, scope := range scopes {
indentStr, isIndented := priorityIndentation(scope)
block := ""
href := ""
after := false
level := 0
lastTag := ""
for _, token := range scope {
if token.closingTag {
endl := closeTag(token, level)
block += endl
level--
lastTag = ""
} else if token.txt == "" {
if token.val != "" {
href, after = formatText(token, "")
}
if level == 1 && isIndented {
block += indentStr
}
level++
lastTag = token.tag
} else {
if token.txt != " " {
str, _ := formatText(token, lastTag)
block += str
if after {
block += href
after = false
}
} else {
fmt.Println(token.tag)
str, _ := formatText(tagInfo{}, lastTag)
block += str
}
}
}
if !isIndented {
block = strings.TrimPrefix(block, " ")
}
block = wrap(block, 72, isIndented)
doc += block
}
return doc
}
// closeTag will divide up the blocks of documentation to be formated properly.
func closeTag(token tagInfo, level int) string {
switch token.tag {
case "pre", "li", "div":
return "\n"
case "p", "h1", "h2", "h3", "h4", "h5", "h6":
return "\n\n"
case "code":
// indented code is only at the 0th level.
if level == 0 {
return "\n"
}
}
return ""
}
// formatText will format any sort of text based off of a tag. It will also return
// a boolean to add the string after the text token.
func formatText(token tagInfo, lastTag string) (string, bool) {
switch token.tag {
case "a":
if token.val != "" {
return fmt.Sprintf(" (%s)", token.val), true
}
}
// We don't care about a single space nor no text.
if len(token.txt) == 0 || token.txt == " " {
return "", false
}
// Here we want to indent code blocks that are newlines
if lastTag == "code" {
// Greater than one, because we don't care about newlines in the beginning
block := ""
if lines := strings.Split(token.txt, "\n"); len(lines) > 1 {
for _, line := range lines {
block += indent + line
}
block += "\n"
return block, false
}
}
return token.txt, false
}
// This is a parser to check what type of indention is needed.
func priorityIndentation(blocks []tagInfo) (string, bool) {
if len(blocks) == 0 {
return "", false
}
v, ok := style[blocks[0].tag]
return v, ok
}
// Divides into scopes based off levels.
// For instance,
// <p>Testing<code>123</code></p><ul><li>Foo</li></ul>
// This has 2 scopes, the <p> and <ul>
func findScopes(tokens []tagInfo) [][]tagInfo {
level := 0
scope := []tagInfo{}
scopes := [][]tagInfo{}
for _, token := range tokens {
// we will clear empty tagged tokens from the array
txt := strings.TrimSpace(token.txt)
tag := strings.TrimSpace(token.tag)
if len(txt) == 0 && len(tag) == 0 {
continue
}
scope = append(scope, token)
// If it is a closing tag then we check what level
// we are on. If it is 0, then that means we have found a
// scoped block.
if token.closingTag {
level--
if level == 0 {
scopes = append(scopes, scope)
scope = []tagInfo{}
}
// Check opening tags and increment the level
} else if token.txt == "" {
level++
}
}
// In this case, we did not run into a closing tag. This would mean
// we have plaintext for documentation.
if len(scopes) == 0 {
scopes = append(scopes, scope)
}
return scopes
}

View file

@ -0,0 +1,100 @@
// +build 1.6,codegen
package api
import (
"testing"
)
func TestNonHTMLDocGen(t *testing.T) {
doc := "Testing 1 2 3"
expected := "// Testing 1 2 3\n"
doc = docstring(doc)
if expected != doc {
t.Errorf("Expected %s, but received %s", expected, doc)
}
}
func TestListsHTMLDocGen(t *testing.T) {
doc := "<ul><li>Testing 1 2 3</li> <li>FooBar</li></ul>"
expected := "// * Testing 1 2 3\n// * FooBar\n"
doc = docstring(doc)
if expected != doc {
t.Errorf("Expected %s, but received %s", expected, doc)
}
doc = "<ul> <li>Testing 1 2 3</li> <li>FooBar</li> </ul>"
expected = "// * Testing 1 2 3\n// * FooBar\n"
doc = docstring(doc)
if expected != doc {
t.Errorf("Expected %s, but received %s", expected, doc)
}
// Test leading spaces
doc = " <ul> <li>Testing 1 2 3</li> <li>FooBar</li> </ul>"
doc = docstring(doc)
if expected != doc {
t.Errorf("Expected %s, but received %s", expected, doc)
}
// Paragraph check
doc = "<ul> <li> <p>Testing 1 2 3</p> </li><li> <p>FooBar</p></li></ul>"
expected = "// * Testing 1 2 3\n// \n// * FooBar\n"
doc = docstring(doc)
if expected != doc {
t.Errorf("Expected %s, but received %s", expected, doc)
}
}
func TestInlineCodeHTMLDocGen(t *testing.T) {
doc := "<ul> <li><code>Testing</code>: 1 2 3</li> <li>FooBar</li> </ul>"
expected := "// * Testing: 1 2 3\n// * FooBar\n"
doc = docstring(doc)
if expected != doc {
t.Errorf("Expected %s, but received %s", expected, doc)
}
}
func TestInlineCodeInParagraphHTMLDocGen(t *testing.T) {
doc := "<p><code>Testing</code>: 1 2 3</p>"
expected := "// Testing: 1 2 3\n"
doc = docstring(doc)
if expected != doc {
t.Errorf("Expected %s, but received %s", expected, doc)
}
}
func TestEmptyPREInlineCodeHTMLDocGen(t *testing.T) {
doc := "<pre><code>Testing</code></pre>"
expected := "// Testing\n"
doc = docstring(doc)
if expected != doc {
t.Errorf("Expected %s, but received %s", expected, doc)
}
}
func TestParagraph(t *testing.T) {
doc := "<p>Testing 1 2 3</p>"
expected := "// Testing 1 2 3\n"
doc = docstring(doc)
if expected != doc {
t.Errorf("Expected %s, but received %s", expected, doc)
}
}
func TestComplexListParagraphCode(t *testing.T) {
doc := "<ul> <li><p><code>FOO</code> Bar</p></li><li><p><code>Xyz</code> ABC</p></li></ul>"
expected := "// * FOO Bar\n// \n// * Xyz ABC\n"
doc = docstring(doc)
if expected != doc {
t.Errorf("Expected %s, but received %s", expected, doc)
}
}

View file

@ -0,0 +1,14 @@
// +build codegen
package api
import "strings"
// ExportableName a name which is exportable as a value or name in Go code
func (a *API) ExportableName(name string) string {
if name == "" {
return name
}
return strings.ToUpper(name[0:1]) + name[1:]
}

View file

@ -0,0 +1,495 @@
package api
// shamelist is used to not rename certain operation's input and output shapes.
// We need to maintain backwards compatibility with pre-existing services. Since
// not generating unique input/output shapes is not desired, we will generate
// unique input/output shapes for new operations.
var shamelist = map[string]map[string]struct {
input bool
output bool
}{
"APIGateway": {
"CreateApiKey": {
output: true,
},
"CreateAuthorizer": {
output: true,
},
"CreateBasePathMapping": {
output: true,
},
"CreateDeployment": {
output: true,
},
"CreateDocumentationPart": {
output: true,
},
"CreateDocumentationVersion": {
output: true,
},
"CreateDomainName": {
output: true,
},
"CreateModel": {
output: true,
},
"CreateResource": {
output: true,
},
"CreateRestApi": {
output: true,
},
"CreateStage": {
output: true,
},
"CreateUsagePlan": {
output: true,
},
"CreateUsagePlanKey": {
output: true,
},
"GenerateClientCertificate": {
output: true,
},
"GetAccount": {
output: true,
},
"GetApiKey": {
output: true,
},
"GetAuthorizer": {
output: true,
},
"GetBasePathMapping": {
output: true,
},
"GetClientCertificate": {
output: true,
},
"GetDeployment": {
output: true,
},
"GetDocumentationPart": {
output: true,
},
"GetDocumentationVersion": {
output: true,
},
"GetDomainName": {
output: true,
},
"GetIntegration": {
output: true,
},
"GetIntegrationResponse": {
output: true,
},
"GetMethod": {
output: true,
},
"GetMethodResponse": {
output: true,
},
"GetModel": {
output: true,
},
"GetResource": {
output: true,
},
"GetRestApi": {
output: true,
},
"GetSdkType": {
output: true,
},
"GetStage": {
output: true,
},
"GetUsage": {
output: true,
},
"GetUsagePlan": {
output: true,
},
"GetUsagePlanKey": {
output: true,
},
"ImportRestApi": {
output: true,
},
"PutIntegration": {
output: true,
},
"PutIntegrationResponse": {
output: true,
},
"PutMethod": {
output: true,
},
"PutMethodResponse": {
output: true,
},
"PutRestApi": {
output: true,
},
"UpdateAccount": {
output: true,
},
"UpdateApiKey": {
output: true,
},
"UpdateAuthorizer": {
output: true,
},
"UpdateBasePathMapping": {
output: true,
},
"UpdateClientCertificate": {
output: true,
},
"UpdateDeployment": {
output: true,
},
"UpdateDocumentationPart": {
output: true,
},
"UpdateDocumentationVersion": {
output: true,
},
"UpdateDomainName": {
output: true,
},
"UpdateIntegration": {
output: true,
},
"UpdateIntegrationResponse": {
output: true,
},
"UpdateMethod": {
output: true,
},
"UpdateMethodResponse": {
output: true,
},
"UpdateModel": {
output: true,
},
"UpdateResource": {
output: true,
},
"UpdateRestApi": {
output: true,
},
"UpdateStage": {
output: true,
},
"UpdateUsage": {
output: true,
},
"UpdateUsagePlan": {
output: true,
},
},
"AutoScaling": {
"ResumeProcesses": {
input: true,
},
"SuspendProcesses": {
input: true,
},
},
"CognitoIdentity": {
"CreateIdentityPool": {
output: true,
},
"DescribeIdentity": {
output: true,
},
"DescribeIdentityPool": {
output: true,
},
"UpdateIdentityPool": {
input: true,
output: true,
},
},
"DirectConnect": {
"AllocateConnectionOnInterconnect": {
output: true,
},
"AllocateHostedConnection": {
output: true,
},
"AllocatePrivateVirtualInterface": {
output: true,
},
"AllocatePublicVirtualInterface": {
output: true,
},
"AssociateConnectionWithLag": {
output: true,
},
"AssociateHostedConnection": {
output: true,
},
"AssociateVirtualInterface": {
output: true,
},
"CreateConnection": {
output: true,
},
"CreateInterconnect": {
output: true,
},
"CreateLag": {
output: true,
},
"CreatePrivateVirtualInterface": {
output: true,
},
"CreatePublicVirtualInterface": {
output: true,
},
"DeleteConnection": {
output: true,
},
"DeleteLag": {
output: true,
},
"DescribeConnections": {
output: true,
},
"DescribeConnectionsOnInterconnect": {
output: true,
},
"DescribeHostedConnections": {
output: true,
},
"DescribeLoa": {
output: true,
},
"DisassociateConnectionFromLag": {
output: true,
},
"UpdateLag": {
output: true,
},
},
"EC2": {
"AttachVolume": {
output: true,
},
"CreateSnapshot": {
output: true,
},
"CreateVolume": {
output: true,
},
"DetachVolume": {
output: true,
},
"RunInstances": {
output: true,
},
},
"EFS": {
"CreateFileSystem": {
output: true,
},
"CreateMountTarget": {
output: true,
},
},
"ElastiCache": {
"AddTagsToResource": {
output: true,
},
"ListTagsForResource": {
output: true,
},
"ModifyCacheParameterGroup": {
output: true,
},
"RemoveTagsFromResource": {
output: true,
},
"ResetCacheParameterGroup": {
output: true,
},
},
"ElasticBeanstalk": {
"ComposeEnvironments": {
output: true,
},
"CreateApplication": {
output: true,
},
"CreateApplicationVersion": {
output: true,
},
"CreateConfigurationTemplate": {
output: true,
},
"CreateEnvironment": {
output: true,
},
"DescribeEnvironments": {
output: true,
},
"TerminateEnvironment": {
output: true,
},
"UpdateApplication": {
output: true,
},
"UpdateApplicationVersion": {
output: true,
},
"UpdateConfigurationTemplate": {
output: true,
},
"UpdateEnvironment": {
output: true,
},
},
"Glacier": {
"DescribeJob": {
output: true,
},
"UploadArchive": {
output: true,
},
"CompleteMultipartUpload": {
output: true,
},
},
"IAM": {
"GetContextKeysForCustomPolicy": {
output: true,
},
"GetContextKeysForPrincipalPolicy": {
output: true,
},
"SimulateCustomPolicy": {
output: true,
},
"SimulatePrincipalPolicy": {
output: true,
},
},
"Kinesis": {
"DisableEnhancedMonitoring": {
output: true,
},
"EnableEnhancedMonitoring": {
output: true,
},
},
"KMS": {
"ListGrants": {
output: true,
},
"ListRetirableGrants": {
output: true,
},
},
"Lambda": {
"CreateAlias": {
output: true,
},
"CreateEventSourceMapping": {
output: true,
},
"CreateFunction": {
output: true,
},
"DeleteEventSourceMapping": {
output: true,
},
"GetAlias": {
output: true,
},
"GetEventSourceMapping": {
output: true,
},
"GetFunctionConfiguration": {
output: true,
},
"PublishVersion": {
output: true,
},
"UpdateAlias": {
output: true,
},
"UpdateEventSourceMapping": {
output: true,
},
"UpdateFunctionCode": {
output: true,
},
"UpdateFunctionConfiguration": {
output: true,
},
},
"RDS": {
"ModifyDBClusterParameterGroup": {
output: true,
},
"ModifyDBParameterGroup": {
output: true,
},
"ResetDBClusterParameterGroup": {
output: true,
},
"ResetDBParameterGroup": {
output: true,
},
},
"Redshift": {
"DescribeLoggingStatus": {
output: true,
},
"DisableLogging": {
output: true,
},
"EnableLogging": {
output: true,
},
"ModifyClusterParameterGroup": {
output: true,
},
"ResetClusterParameterGroup": {
output: true,
},
},
"S3": {
"GetBucketNotification": {
input: true,
output: true,
},
"GetBucketNotificationConfiguration": {
input: true,
output: true,
},
},
"SWF": {
"CountClosedWorkflowExecutions": {
output: true,
},
"CountOpenWorkflowExecutions": {
output: true,
},
"CountPendingActivityTasks": {
output: true,
},
"CountPendingDecisionTasks": {
output: true,
},
"ListClosedWorkflowExecutions": {
output: true,
},
"ListOpenWorkflowExecutions": {
output: true,
},
},
}

View file

@ -0,0 +1,74 @@
// +build codegen
package api
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
)
// Load takes a set of files for each filetype and returns an API pointer.
// The API will be initialized once all files have been loaded and parsed.
//
// Will panic if any failure opening the definition JSON files, or there
// are unrecognized exported names.
func Load(api, docs, paginators, waiters string) *API {
a := API{}
a.Attach(api)
a.Attach(docs)
a.Attach(paginators)
a.Attach(waiters)
a.Setup()
return &a
}
// Attach opens a file by name, and unmarshal its JSON data.
// Will proceed to setup the API if not already done so.
func (a *API) Attach(filename string) {
a.path = filepath.Dir(filename)
f, err := os.Open(filename)
defer f.Close()
if err != nil {
panic(err)
}
if err := json.NewDecoder(f).Decode(a); err != nil {
panic(fmt.Errorf("failed to decode %s, err: %v", filename, err))
}
}
// AttachString will unmarshal a raw JSON string, and setup the
// API if not already done so.
func (a *API) AttachString(str string) {
json.Unmarshal([]byte(str), a)
if !a.initialized {
a.Setup()
}
}
// Setup initializes the API.
func (a *API) Setup() {
a.setMetadataEndpointsKey()
a.writeShapeNames()
a.resolveReferences()
a.fixStutterNames()
a.renameExportable()
if !a.NoRenameToplevelShapes {
a.renameToplevelShapes()
}
a.updateTopLevelShapeReferences()
a.createInputOutputShapes()
a.customizationPasses()
if !a.NoRemoveUnusedShapes {
a.removeUnusedShapes()
}
if !a.NoValidataShapeMethods {
a.addShapeValidations()
}
a.initialized = true
}

View file

@ -0,0 +1,32 @@
// +build 1.6,codegen
package api
import (
"testing"
)
func TestResolvedReferences(t *testing.T) {
json := `{
"operations": {
"OperationName": {
"input": { "shape": "TestName" }
}
},
"shapes": {
"TestName": {
"type": "structure",
"members": {
"memberName1": { "shape": "OtherTest" },
"memberName2": { "shape": "OtherTest" }
}
},
"OtherTest": { "type": "string" }
}
}`
a := API{}
a.AttachString(json)
if len(a.Shapes["OtherTest"].refs) != 2 {
t.Errorf("Expected %d, but received %d", 2, len(a.Shapes["OtherTest"].refs))
}
}

View file

@ -0,0 +1,493 @@
// +build codegen
package api
import (
"bytes"
"fmt"
"regexp"
"sort"
"strings"
"text/template"
)
// An Operation defines a specific API Operation.
type Operation struct {
API *API `json:"-"`
ExportedName string
Name string
Documentation string
HTTP HTTPInfo
InputRef ShapeRef `json:"input"`
OutputRef ShapeRef `json:"output"`
ErrorRefs []ShapeRef `json:"errors"`
Paginator *Paginator
Deprecated bool `json:"deprecated"`
AuthType string `json:"authtype"`
imports map[string]bool
}
// A HTTPInfo defines the method of HTTP request for the Operation.
type HTTPInfo struct {
Method string
RequestURI string
ResponseCode uint
}
// HasInput returns if the Operation accepts an input paramater
func (o *Operation) HasInput() bool {
return o.InputRef.ShapeName != ""
}
// HasOutput returns if the Operation accepts an output parameter
func (o *Operation) HasOutput() bool {
return o.OutputRef.ShapeName != ""
}
func (o *Operation) GetSigner() string {
if o.AuthType == "v4-unsigned-body" {
o.API.imports["github.com/aws/aws-sdk-go/aws/signer/v4"] = true
}
buf := bytes.NewBuffer(nil)
switch o.AuthType {
case "none":
buf.WriteString("req.Config.Credentials = credentials.AnonymousCredentials")
case "v4-unsigned-body":
buf.WriteString("req.Handlers.Sign.Remove(v4.SignRequestHandler)\n")
buf.WriteString("handler := v4.BuildNamedHandler(\"v4.CustomSignerHandler\", v4.WithUnsignedPayload)\n")
buf.WriteString("req.Handlers.Sign.PushFrontNamed(handler)")
}
buf.WriteString("\n")
return buf.String()
}
// tplOperation defines a template for rendering an API Operation
var tplOperation = template.Must(template.New("operation").Funcs(template.FuncMap{
"GetCrosslinkURL": GetCrosslinkURL,
}).Parse(`
const op{{ .ExportedName }} = "{{ .Name }}"
// {{ .ExportedName }}Request generates a "aws/request.Request" representing the
// client's request for the {{ .ExportedName }} operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
//
// See {{ .ExportedName }} for usage and error information.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the {{ .ExportedName }} method directly
// instead.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the {{ .ExportedName }}Request method.
// req, resp := client.{{ .ExportedName }}Request(params)
//
// err := req.Send()
// if err == nil { // resp is now filled
// fmt.Println(resp)
// }
{{ $crosslinkURL := GetCrosslinkURL $.API.BaseCrosslinkURL $.API.APIName $.API.Metadata.UID $.ExportedName -}}
{{ if ne $crosslinkURL "" -}}
//
// Please also see {{ $crosslinkURL }}
{{ end -}}
func (c *{{ .API.StructName }}) {{ .ExportedName }}Request(` +
`input {{ .InputRef.GoType }}) (req *request.Request, output {{ .OutputRef.GoType }}) {
{{ if (or .Deprecated (or .InputRef.Deprecated .OutputRef.Deprecated)) }}if c.Client.Config.Logger != nil {
c.Client.Config.Logger.Log("This operation, {{ .ExportedName }}, has been deprecated")
}
op := &request.Operation{ {{ else }} op := &request.Operation{ {{ end }}
Name: op{{ .ExportedName }},
{{ if ne .HTTP.Method "" }}HTTPMethod: "{{ .HTTP.Method }}",
{{ end }}HTTPPath: {{ if ne .HTTP.RequestURI "" }}"{{ .HTTP.RequestURI }}"{{ else }}"/"{{ end }},
{{ if .Paginator }}Paginator: &request.Paginator{
InputTokens: {{ .Paginator.InputTokensString }},
OutputTokens: {{ .Paginator.OutputTokensString }},
LimitToken: "{{ .Paginator.LimitKey }}",
TruncationToken: "{{ .Paginator.MoreResults }}",
},
{{ end }}
}
if input == nil {
input = &{{ .InputRef.GoTypeElem }}{}
}
output = &{{ .OutputRef.GoTypeElem }}{}
req = c.newRequest(op, input, output){{ if eq .OutputRef.Shape.Placeholder true }}
req.Handlers.Unmarshal.Remove({{ .API.ProtocolPackage }}.UnmarshalHandler)
req.Handlers.Unmarshal.PushBackNamed(protocol.UnmarshalDiscardBodyHandler){{ end }}
{{ if ne .AuthType "" }}{{ .GetSigner }}{{ end -}}
return
}
// {{ .ExportedName }} API operation for {{ .API.Metadata.ServiceFullName }}.
{{ if .Documentation -}}
//
{{ .Documentation }}
{{ end -}}
//
// Returns awserr.Error for service API and SDK errors. Use runtime type assertions
// with awserr.Error's Code and Message methods to get detailed information about
// the error.
//
// See the AWS API reference guide for {{ .API.Metadata.ServiceFullName }}'s
// API operation {{ .ExportedName }} for usage and error information.
{{ if .ErrorRefs -}}
//
// Returned Error Codes:
{{ range $_, $err := .ErrorRefs -}}
// * {{ $err.Shape.ErrorCodeName }} "{{ $err.Shape.ErrorName}}"
{{ if $err.Docstring -}}
{{ $err.IndentedDocstring }}
{{ end -}}
//
{{ end -}}
{{ end -}}
{{ $crosslinkURL := GetCrosslinkURL $.API.BaseCrosslinkURL $.API.APIName $.API.Metadata.UID $.ExportedName -}}
{{ if ne $crosslinkURL "" -}}
// Please also see {{ $crosslinkURL }}
{{ end -}}
func (c *{{ .API.StructName }}) {{ .ExportedName }}(` +
`input {{ .InputRef.GoType }}) ({{ .OutputRef.GoType }}, error) {
req, out := c.{{ .ExportedName }}Request(input)
return out, req.Send()
}
// {{ .ExportedName }}WithContext is the same as {{ .ExportedName }} with the addition of
// the ability to pass a context and additional request options.
//
// See {{ .ExportedName }} for details on how to use this API operation.
//
// The context must be non-nil and will be used for request cancellation. If
// the context is nil a panic will occur. In the future the SDK may create
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
// for more information on using Contexts.
func (c *{{ .API.StructName }}) {{ .ExportedName }}WithContext(` +
`ctx aws.Context, input {{ .InputRef.GoType }}, opts ...request.Option) ` +
`({{ .OutputRef.GoType }}, error) {
req, out := c.{{ .ExportedName }}Request(input)
req.SetContext(ctx)
req.ApplyOptions(opts...)
return out, req.Send()
}
{{ if .Paginator }}
// {{ .ExportedName }}Pages iterates over the pages of a {{ .ExportedName }} operation,
// calling the "fn" function with the response data for each page. To stop
// iterating, return false from the fn function.
//
// See {{ .ExportedName }} method for more information on how to use this operation.
//
// Note: This operation can generate multiple requests to a service.
//
// // Example iterating over at most 3 pages of a {{ .ExportedName }} operation.
// pageNum := 0
// err := client.{{ .ExportedName }}Pages(params,
// func(page {{ .OutputRef.GoType }}, lastPage bool) bool {
// pageNum++
// fmt.Println(page)
// return pageNum <= 3
// })
//
func (c *{{ .API.StructName }}) {{ .ExportedName }}Pages(` +
`input {{ .InputRef.GoType }}, fn func({{ .OutputRef.GoType }}, bool) bool) error {
return c.{{ .ExportedName }}PagesWithContext(aws.BackgroundContext(), input, fn)
}
// {{ .ExportedName }}PagesWithContext same as {{ .ExportedName }}Pages except
// it takes a Context and allows setting request options on the pages.
//
// The context must be non-nil and will be used for request cancellation. If
// the context is nil a panic will occur. In the future the SDK may create
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
// for more information on using Contexts.
func (c *{{ .API.StructName }}) {{ .ExportedName }}PagesWithContext(` +
`ctx aws.Context, ` +
`input {{ .InputRef.GoType }}, ` +
`fn func({{ .OutputRef.GoType }}, bool) bool, ` +
`opts ...request.Option) error {
p := request.Pagination {
NewRequest: func() (*request.Request, error) {
var inCpy {{ .InputRef.GoType }}
if input != nil {
tmp := *input
inCpy = &tmp
}
req, _ := c.{{ .ExportedName }}Request(inCpy)
req.SetContext(ctx)
req.ApplyOptions(opts...)
return req, nil
},
}
cont := true
for p.Next() && cont {
cont = fn(p.Page().({{ .OutputRef.GoType }}), !p.HasNextPage())
}
return p.Err()
}
{{ end }}
`))
// GoCode returns a string of rendered GoCode for this Operation
func (o *Operation) GoCode() string {
var buf bytes.Buffer
err := tplOperation.Execute(&buf, o)
if err != nil {
panic(err)
}
return strings.TrimSpace(buf.String())
}
// tplInfSig defines the template for rendering an Operation's signature within an Interface definition.
var tplInfSig = template.Must(template.New("opsig").Parse(`
{{ .ExportedName }}({{ .InputRef.GoTypeWithPkgName }}) ({{ .OutputRef.GoTypeWithPkgName }}, error)
{{ .ExportedName }}WithContext(aws.Context, {{ .InputRef.GoTypeWithPkgName }}, ...request.Option) ({{ .OutputRef.GoTypeWithPkgName }}, error)
{{ .ExportedName }}Request({{ .InputRef.GoTypeWithPkgName }}) (*request.Request, {{ .OutputRef.GoTypeWithPkgName }})
{{ if .Paginator -}}
{{ .ExportedName }}Pages({{ .InputRef.GoTypeWithPkgName }}, func({{ .OutputRef.GoTypeWithPkgName }}, bool) bool) error
{{ .ExportedName }}PagesWithContext(aws.Context, {{ .InputRef.GoTypeWithPkgName }}, func({{ .OutputRef.GoTypeWithPkgName }}, bool) bool, ...request.Option) error
{{- end }}
`))
// InterfaceSignature returns a string representing the Operation's interface{}
// functional signature.
func (o *Operation) InterfaceSignature() string {
var buf bytes.Buffer
err := tplInfSig.Execute(&buf, o)
if err != nil {
panic(err)
}
return strings.TrimSpace(buf.String())
}
// tplExample defines the template for rendering an Operation example
var tplExample = template.Must(template.New("operationExample").Parse(`
func Example{{ .API.StructName }}_{{ .ExportedName }}() {
sess := session.Must(session.NewSession())
svc := {{ .API.PackageName }}.New(sess)
{{ .ExampleInput }}
resp, err := svc.{{ .ExportedName }}(params)
if err != nil {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
return
}
// Pretty-print the response data.
fmt.Println(resp)
}
`))
// Example returns a string of the rendered Go code for the Operation
func (o *Operation) Example() string {
var buf bytes.Buffer
err := tplExample.Execute(&buf, o)
if err != nil {
panic(err)
}
return strings.TrimSpace(buf.String())
}
// ExampleInput return a string of the rendered Go code for an example's input parameters
func (o *Operation) ExampleInput() string {
if len(o.InputRef.Shape.MemberRefs) == 0 {
if strings.Contains(o.InputRef.GoTypeElem(), ".") {
o.imports["github.com/aws/aws-sdk-go/service/"+strings.Split(o.InputRef.GoTypeElem(), ".")[0]] = true
return fmt.Sprintf("var params *%s", o.InputRef.GoTypeElem())
}
return fmt.Sprintf("var params *%s.%s",
o.API.PackageName(), o.InputRef.GoTypeElem())
}
e := example{o, map[string]int{}}
return "params := " + e.traverseAny(o.InputRef.Shape, false, false)
}
// A example provides
type example struct {
*Operation
visited map[string]int
}
// traverseAny returns rendered Go code for the shape.
func (e *example) traverseAny(s *Shape, required, payload bool) string {
str := ""
e.visited[s.ShapeName]++
switch s.Type {
case "structure":
str = e.traverseStruct(s, required, payload)
case "list":
str = e.traverseList(s, required, payload)
case "map":
str = e.traverseMap(s, required, payload)
case "jsonvalue":
str = "aws.JSONValue{\"key\": \"value\"}"
if required {
str += " // Required"
}
default:
str = e.traverseScalar(s, required, payload)
}
e.visited[s.ShapeName]--
return str
}
var reType = regexp.MustCompile(`\b([A-Z])`)
// traverseStruct returns rendered Go code for a structure type shape.
func (e *example) traverseStruct(s *Shape, required, payload bool) string {
var buf bytes.Buffer
if s.resolvePkg != "" {
e.imports[s.resolvePkg] = true
buf.WriteString("&" + s.GoTypeElem() + "{")
} else {
buf.WriteString("&" + s.API.PackageName() + "." + s.GoTypeElem() + "{")
}
if required {
buf.WriteString(" // Required")
}
buf.WriteString("\n")
req := make([]string, len(s.Required))
copy(req, s.Required)
sort.Strings(req)
if e.visited[s.ShapeName] < 2 {
for _, n := range req {
m := s.MemberRefs[n].Shape
p := n == s.Payload && (s.MemberRefs[n].Streaming || m.Streaming)
buf.WriteString(n + ": " + e.traverseAny(m, true, p) + ",")
if m.Type != "list" && m.Type != "structure" && m.Type != "map" {
buf.WriteString(" // Required")
}
buf.WriteString("\n")
}
for _, n := range s.MemberNames() {
if s.IsRequired(n) {
continue
}
m := s.MemberRefs[n].Shape
p := n == s.Payload && (s.MemberRefs[n].Streaming || m.Streaming)
buf.WriteString(n + ": " + e.traverseAny(m, false, p) + ",\n")
}
} else {
buf.WriteString("// Recursive values...\n")
}
buf.WriteString("}")
return buf.String()
}
// traverseMap returns rendered Go code for a map type shape.
func (e *example) traverseMap(s *Shape, required, payload bool) string {
var buf bytes.Buffer
t := ""
if s.resolvePkg != "" {
e.imports[s.resolvePkg] = true
t = s.GoTypeElem()
} else {
t = reType.ReplaceAllString(s.GoTypeElem(), s.API.PackageName()+".$1")
}
buf.WriteString(t + "{")
if required {
buf.WriteString(" // Required")
}
buf.WriteString("\n")
if e.visited[s.ShapeName] < 2 {
m := s.ValueRef.Shape
buf.WriteString("\"Key\": " + e.traverseAny(m, true, false) + ",")
if m.Type != "list" && m.Type != "structure" && m.Type != "map" {
buf.WriteString(" // Required")
}
buf.WriteString("\n// More values...\n")
} else {
buf.WriteString("// Recursive values...\n")
}
buf.WriteString("}")
return buf.String()
}
// traverseList returns rendered Go code for a list type shape.
func (e *example) traverseList(s *Shape, required, payload bool) string {
var buf bytes.Buffer
t := ""
if s.resolvePkg != "" {
e.imports[s.resolvePkg] = true
t = s.GoTypeElem()
} else {
t = reType.ReplaceAllString(s.GoTypeElem(), s.API.PackageName()+".$1")
}
buf.WriteString(t + "{")
if required {
buf.WriteString(" // Required")
}
buf.WriteString("\n")
if e.visited[s.ShapeName] < 2 {
m := s.MemberRef.Shape
buf.WriteString(e.traverseAny(m, true, false) + ",")
if m.Type != "list" && m.Type != "structure" && m.Type != "map" {
buf.WriteString(" // Required")
}
buf.WriteString("\n// More values...\n")
} else {
buf.WriteString("// Recursive values...\n")
}
buf.WriteString("}")
return buf.String()
}
// traverseScalar returns an AWS Type string representation initialized to a value.
// Will panic if s is an unsupported shape type.
func (e *example) traverseScalar(s *Shape, required, payload bool) string {
str := ""
switch s.Type {
case "integer", "long":
str = `aws.Int64(1)`
case "float", "double":
str = `aws.Float64(1.0)`
case "string", "character":
str = `aws.String("` + s.ShapeName + `")`
case "blob":
if payload {
str = `bytes.NewReader([]byte("PAYLOAD"))`
} else {
str = `[]byte("PAYLOAD")`
}
case "boolean":
str = `aws.Bool(true)`
case "timestamp":
str = `aws.Time(time.Now())`
default:
panic("unsupported shape " + s.Type)
}
return str
}

View file

@ -0,0 +1,91 @@
// +build codegen
package api
import (
"encoding/json"
"fmt"
"os"
)
// Paginator keeps track of pagination configuration for an API operation.
type Paginator struct {
InputTokens interface{} `json:"input_token"`
OutputTokens interface{} `json:"output_token"`
LimitKey string `json:"limit_key"`
MoreResults string `json:"more_results"`
}
// InputTokensString returns output tokens formatted as a list
func (p *Paginator) InputTokensString() string {
str := p.InputTokens.([]string)
return fmt.Sprintf("%#v", str)
}
// OutputTokensString returns output tokens formatted as a list
func (p *Paginator) OutputTokensString() string {
str := p.OutputTokens.([]string)
return fmt.Sprintf("%#v", str)
}
// used for unmarshaling from the paginators JSON file
type paginationDefinitions struct {
*API
Pagination map[string]Paginator
}
// AttachPaginators attaches pagination configuration from filename to the API.
func (a *API) AttachPaginators(filename string) {
p := paginationDefinitions{API: a}
f, err := os.Open(filename)
defer f.Close()
if err != nil {
panic(err)
}
err = json.NewDecoder(f).Decode(&p)
if err != nil {
panic(err)
}
p.setup()
}
// setup runs post-processing on the paginator configuration.
func (p *paginationDefinitions) setup() {
for n, e := range p.Pagination {
if e.InputTokens == nil || e.OutputTokens == nil {
continue
}
paginator := e
switch t := paginator.InputTokens.(type) {
case string:
paginator.InputTokens = []string{t}
case []interface{}:
toks := []string{}
for _, e := range t {
s := e.(string)
toks = append(toks, s)
}
paginator.InputTokens = toks
}
switch t := paginator.OutputTokens.(type) {
case string:
paginator.OutputTokens = []string{t}
case []interface{}:
toks := []string{}
for _, e := range t {
s := e.(string)
toks = append(toks, s)
}
paginator.OutputTokens = toks
}
if o, ok := p.Operations[n]; ok {
o.Paginator = &paginator
} else {
panic("unknown operation for paginator " + n)
}
}
}

View file

@ -0,0 +1,133 @@
// +build codegen
package api
import (
"fmt"
"reflect"
"strings"
"github.com/aws/aws-sdk-go/private/util"
)
// A paramFiller provides string formatting for a shape and its types.
type paramFiller struct {
prefixPackageName bool
}
// typeName returns the type name of a shape.
func (f paramFiller) typeName(shape *Shape) string {
if f.prefixPackageName && shape.Type == "structure" {
return "*" + shape.API.PackageName() + "." + shape.GoTypeElem()
}
return shape.GoType()
}
// ParamsStructFromJSON returns a JSON string representation of a structure.
func ParamsStructFromJSON(value interface{}, shape *Shape, prefixPackageName bool) string {
f := paramFiller{prefixPackageName: prefixPackageName}
return util.GoFmt(f.paramsStructAny(value, shape))
}
// paramsStructAny returns the string representation of any value.
func (f paramFiller) paramsStructAny(value interface{}, shape *Shape) string {
if value == nil {
return ""
}
switch shape.Type {
case "structure":
if value != nil {
vmap := value.(map[string]interface{})
return f.paramsStructStruct(vmap, shape)
}
case "list":
vlist := value.([]interface{})
return f.paramsStructList(vlist, shape)
case "map":
vmap := value.(map[string]interface{})
return f.paramsStructMap(vmap, shape)
case "string", "character":
v := reflect.Indirect(reflect.ValueOf(value))
if v.IsValid() {
return fmt.Sprintf("aws.String(%#v)", v.Interface())
}
case "blob":
v := reflect.Indirect(reflect.ValueOf(value))
if v.IsValid() && shape.Streaming {
return fmt.Sprintf("bytes.NewReader([]byte(%#v))", v.Interface())
} else if v.IsValid() {
return fmt.Sprintf("[]byte(%#v)", v.Interface())
}
case "boolean":
v := reflect.Indirect(reflect.ValueOf(value))
if v.IsValid() {
return fmt.Sprintf("aws.Bool(%#v)", v.Interface())
}
case "integer", "long":
v := reflect.Indirect(reflect.ValueOf(value))
if v.IsValid() {
return fmt.Sprintf("aws.Int64(%v)", v.Interface())
}
case "float", "double":
v := reflect.Indirect(reflect.ValueOf(value))
if v.IsValid() {
return fmt.Sprintf("aws.Float64(%v)", v.Interface())
}
case "timestamp":
v := reflect.Indirect(reflect.ValueOf(value))
if v.IsValid() {
return fmt.Sprintf("aws.Time(time.Unix(%d, 0))", int(v.Float()))
}
default:
panic("Unhandled type " + shape.Type)
}
return ""
}
// paramsStructStruct returns the string representation of a structure
func (f paramFiller) paramsStructStruct(value map[string]interface{}, shape *Shape) string {
out := "&" + f.typeName(shape)[1:] + "{\n"
for _, n := range shape.MemberNames() {
ref := shape.MemberRefs[n]
name := findParamMember(value, n)
if val := f.paramsStructAny(value[name], ref.Shape); val != "" {
out += fmt.Sprintf("%s: %s,\n", n, val)
}
}
out += "}"
return out
}
// paramsStructMap returns the string representation of a map of values
func (f paramFiller) paramsStructMap(value map[string]interface{}, shape *Shape) string {
out := f.typeName(shape) + "{\n"
keys := util.SortedKeys(value)
for _, k := range keys {
v := value[k]
out += fmt.Sprintf("%q: %s,\n", k, f.paramsStructAny(v, shape.ValueRef.Shape))
}
out += "}"
return out
}
// paramsStructList returns the string representation of slice of values
func (f paramFiller) paramsStructList(value []interface{}, shape *Shape) string {
out := f.typeName(shape) + "{\n"
for _, v := range value {
out += fmt.Sprintf("%s,\n", f.paramsStructAny(v, shape.MemberRef.Shape))
}
out += "}"
return out
}
// findParamMember searches a map for a key ignoring case. Returns the map key if found.
func findParamMember(value map[string]interface{}, key string) string {
for actualKey := range value {
if strings.ToLower(key) == strings.ToLower(actualKey) {
return actualKey
}
}
return ""
}

View file

@ -0,0 +1,306 @@
// +build codegen
package api
import (
"fmt"
"regexp"
"strings"
)
// updateTopLevelShapeReferences moves resultWrapper, locationName, and
// xmlNamespace traits from toplevel shape references to the toplevel
// shapes for easier code generation
func (a *API) updateTopLevelShapeReferences() {
for _, o := range a.Operations {
// these are for REST-XML services
if o.InputRef.LocationName != "" {
o.InputRef.Shape.LocationName = o.InputRef.LocationName
}
if o.InputRef.Location != "" {
o.InputRef.Shape.Location = o.InputRef.Location
}
if o.InputRef.Payload != "" {
o.InputRef.Shape.Payload = o.InputRef.Payload
}
if o.InputRef.XMLNamespace.Prefix != "" {
o.InputRef.Shape.XMLNamespace.Prefix = o.InputRef.XMLNamespace.Prefix
}
if o.InputRef.XMLNamespace.URI != "" {
o.InputRef.Shape.XMLNamespace.URI = o.InputRef.XMLNamespace.URI
}
}
}
// writeShapeNames sets each shape's API and shape name values. Binding the
// shape to its parent API.
func (a *API) writeShapeNames() {
for n, s := range a.Shapes {
s.API = a
s.ShapeName = n
}
}
func (a *API) resolveReferences() {
resolver := referenceResolver{API: a, visited: map[*ShapeRef]bool{}}
for _, s := range a.Shapes {
resolver.resolveShape(s)
}
for _, o := range a.Operations {
o.API = a // resolve parent reference
resolver.resolveReference(&o.InputRef)
resolver.resolveReference(&o.OutputRef)
// Resolve references for errors also
for i := range o.ErrorRefs {
resolver.resolveReference(&o.ErrorRefs[i])
o.ErrorRefs[i].Shape.IsError = true
}
}
}
// A referenceResolver provides a way to resolve shape references to
// shape definitions.
type referenceResolver struct {
*API
visited map[*ShapeRef]bool
}
var jsonvalueShape = &Shape{
ShapeName: "JSONValue",
Type: "jsonvalue",
ValueRef: ShapeRef{
JSONValue: true,
},
}
// resolveReference updates a shape reference to reference the API and
// its shape definition. All other nested references are also resolved.
func (r *referenceResolver) resolveReference(ref *ShapeRef) {
if ref.ShapeName == "" {
return
}
if shape, ok := r.API.Shapes[ref.ShapeName]; ok {
if ref.JSONValue {
ref.ShapeName = "JSONValue"
r.API.Shapes[ref.ShapeName] = jsonvalueShape
}
ref.API = r.API // resolve reference back to API
ref.Shape = shape // resolve shape reference
if r.visited[ref] {
return
}
r.visited[ref] = true
shape.refs = append(shape.refs, ref) // register the ref
// resolve shape's references, if it has any
r.resolveShape(shape)
}
}
// resolveShape resolves a shape's Member Key Value, and nested member
// shape references.
func (r *referenceResolver) resolveShape(shape *Shape) {
r.resolveReference(&shape.MemberRef)
r.resolveReference(&shape.KeyRef)
r.resolveReference(&shape.ValueRef)
for _, m := range shape.MemberRefs {
r.resolveReference(m)
}
}
// renameToplevelShapes renames all top level shapes of an API to their
// exportable variant. The shapes are also updated to include notations
// if they are Input or Outputs.
func (a *API) renameToplevelShapes() {
for _, v := range a.OperationList() {
if v.HasInput() {
name := v.ExportedName + "Input"
switch {
case a.Shapes[name] == nil:
if service, ok := shamelist[a.name]; ok {
if check, ok := service[v.Name]; ok && check.input {
break
}
}
v.InputRef.Shape.Rename(name)
}
}
if v.HasOutput() {
name := v.ExportedName + "Output"
switch {
case a.Shapes[name] == nil:
if service, ok := shamelist[a.name]; ok {
if check, ok := service[v.Name]; ok && check.output {
break
}
}
v.OutputRef.Shape.Rename(name)
}
}
v.InputRef.Payload = a.ExportableName(v.InputRef.Payload)
v.OutputRef.Payload = a.ExportableName(v.OutputRef.Payload)
}
}
// fixStutterNames fixes all name struttering based on Go naming conventions.
// "Stuttering" is when the prefix of a structure or function matches the
// package name (case insensitive).
func (a *API) fixStutterNames() {
str, end := a.StructName(), ""
if len(str) > 1 {
l := len(str) - 1
str, end = str[0:l], str[l:]
}
re := regexp.MustCompile(fmt.Sprintf(`\A(?i:%s)%s`, str, end))
for name, op := range a.Operations {
newName := re.ReplaceAllString(name, "")
if newName != name {
delete(a.Operations, name)
a.Operations[newName] = op
}
op.ExportedName = newName
}
for k, s := range a.Shapes {
newName := re.ReplaceAllString(k, "")
if newName != s.ShapeName {
s.Rename(newName)
}
}
}
// renameExportable renames all operation names to be exportable names.
// All nested Shape names are also updated to the exportable variant.
func (a *API) renameExportable() {
for name, op := range a.Operations {
newName := a.ExportableName(name)
if newName != name {
delete(a.Operations, name)
a.Operations[newName] = op
}
op.ExportedName = newName
}
for k, s := range a.Shapes {
// FIXME SNS has lower and uppercased shape names with the same name,
// except the lowercased variant is used exclusively for string and
// other primitive types. Renaming both would cause a collision.
// We work around this by only renaming the structure shapes.
if s.Type == "string" {
continue
}
for mName, member := range s.MemberRefs {
newName := a.ExportableName(mName)
if newName != mName {
delete(s.MemberRefs, mName)
s.MemberRefs[newName] = member
// also apply locationName trait so we keep the old one
// but only if there's no locationName trait on ref or shape
if member.LocationName == "" && member.Shape.LocationName == "" {
member.LocationName = mName
}
}
if newName == "_" {
panic("Shape " + s.ShapeName + " uses reserved member name '_'")
}
}
newName := a.ExportableName(k)
if newName != s.ShapeName {
s.Rename(newName)
}
s.Payload = a.ExportableName(s.Payload)
// fix required trait names
for i, n := range s.Required {
s.Required[i] = a.ExportableName(n)
}
}
for _, s := range a.Shapes {
// fix enum names
if s.IsEnum() {
s.EnumConsts = make([]string, len(s.Enum))
for i := range s.Enum {
shape := s.ShapeName
shape = strings.ToUpper(shape[0:1]) + shape[1:]
s.EnumConsts[i] = shape + s.EnumName(i)
}
}
}
}
// createInputOutputShapes creates toplevel input/output shapes if they
// have not been defined in the API. This normalizes all APIs to always
// have an input and output structure in the signature.
func (a *API) createInputOutputShapes() {
for _, op := range a.Operations {
if !op.HasInput() {
setAsPlacholderShape(&op.InputRef, op.ExportedName+"Input", a)
}
if !op.HasOutput() {
setAsPlacholderShape(&op.OutputRef, op.ExportedName+"Output", a)
}
}
}
func setAsPlacholderShape(tgtShapeRef *ShapeRef, name string, a *API) {
shape := a.makeIOShape(name)
shape.Placeholder = true
*tgtShapeRef = ShapeRef{API: a, ShapeName: shape.ShapeName, Shape: shape}
shape.refs = append(shape.refs, tgtShapeRef)
}
// makeIOShape returns a pointer to a new Shape initialized by the name provided.
func (a *API) makeIOShape(name string) *Shape {
shape := &Shape{
API: a, ShapeName: name, Type: "structure",
MemberRefs: map[string]*ShapeRef{},
}
a.Shapes[name] = shape
return shape
}
// removeUnusedShapes removes shapes from the API which are not referenced by any
// other shape in the API.
func (a *API) removeUnusedShapes() {
for n, s := range a.Shapes {
if len(s.refs) == 0 {
delete(a.Shapes, n)
}
}
}
// Represents the service package name to EndpointsID mapping
var custEndpointsKey = map[string]string{
"applicationautoscaling": "application-autoscaling",
}
// Sents the EndpointsID field of Metadata with the value of the
// EndpointPrefix if EndpointsID is not set. Also adds
// customizations for services if EndpointPrefix is not a valid key.
func (a *API) setMetadataEndpointsKey() {
if len(a.Metadata.EndpointsID) != 0 {
return
}
if v, ok := custEndpointsKey[a.PackageName()]; ok {
a.Metadata.EndpointsID = v
} else {
a.Metadata.EndpointsID = a.Metadata.EndpointPrefix
}
}

View file

@ -0,0 +1,169 @@
// +build 1.6,codegen
package api
import (
"testing"
)
func TestUniqueInputAndOutputs(t *testing.T) {
shamelist["FooService"] = map[string]struct {
input bool
output bool
}{}
v := shamelist["FooService"]["OpOutputNoRename"]
v.output = true
shamelist["FooService"]["OpOutputNoRename"] = v
v = shamelist["FooService"]["InputNoRename"]
v.input = true
shamelist["FooService"]["OpInputNoRename"] = v
v = shamelist["FooService"]["BothNoRename"]
v.input = true
v.output = true
shamelist["FooService"]["OpBothNoRename"] = v
testCases := [][]struct {
expectedInput string
expectedOutput string
operation string
input string
inputRef string
output string
outputRef string
}{
{
{
expectedInput: "FooOperationInput",
expectedOutput: "FooOperationOutput",
operation: "FooOperation",
input: "FooInputShape",
inputRef: "FooInputShapeRef",
output: "FooOutputShape",
outputRef: "FooOutputShapeRef",
},
{
expectedInput: "BarOperationInput",
expectedOutput: "BarOperationOutput",
operation: "BarOperation",
input: "FooInputShape",
inputRef: "FooInputShapeRef",
output: "FooOutputShape",
outputRef: "FooOutputShapeRef",
},
},
{
{
expectedInput: "FooOperationInput",
expectedOutput: "FooOperationOutput",
operation: "FooOperation",
input: "FooInputShape",
inputRef: "FooInputShapeRef",
output: "FooOutputShape",
outputRef: "FooOutputShapeRef",
},
{
expectedInput: "OpOutputNoRenameInput",
expectedOutput: "OpOutputNoRenameOutputShape",
operation: "OpOutputNoRename",
input: "OpOutputNoRenameInputShape",
inputRef: "OpOutputNoRenameInputRef",
output: "OpOutputNoRenameOutputShape",
outputRef: "OpOutputNoRenameOutputRef",
},
},
{
{
expectedInput: "FooOperationInput",
expectedOutput: "FooOperationOutput",
operation: "FooOperation",
input: "FooInputShape",
inputRef: "FooInputShapeRef",
output: "FooOutputShape",
outputRef: "FooOutputShapeRef",
},
{
expectedInput: "OpInputNoRenameInputShape",
expectedOutput: "OpInputNoRenameOutput",
operation: "OpInputNoRename",
input: "OpInputNoRenameInputShape",
inputRef: "OpInputNoRenameInputRef",
output: "OpInputNoRenameOutputShape",
outputRef: "OpInputNoRenameOutputRef",
},
},
{
{
expectedInput: "FooOperationInput",
expectedOutput: "FooOperationOutput",
operation: "FooOperation",
input: "FooInputShape",
inputRef: "FooInputShapeRef",
output: "FooOutputShape",
outputRef: "FooOutputShapeRef",
},
{
expectedInput: "OpInputNoRenameInputShape",
expectedOutput: "OpInputNoRenameOutputShape",
operation: "OpBothNoRename",
input: "OpInputNoRenameInputShape",
inputRef: "OpInputNoRenameInputRef",
output: "OpInputNoRenameOutputShape",
outputRef: "OpInputNoRenameOutputRef",
},
},
}
for _, c := range testCases {
a := &API{
name: "FooService",
Operations: map[string]*Operation{},
}
expected := map[string][]string{}
a.Shapes = map[string]*Shape{}
for _, op := range c {
a.Operations[op.operation] = &Operation{
ExportedName: op.operation,
}
a.Operations[op.operation].Name = op.operation
a.Operations[op.operation].InputRef = ShapeRef{
API: a,
ShapeName: op.inputRef,
Shape: &Shape{
API: a,
ShapeName: op.input,
},
}
a.Operations[op.operation].OutputRef = ShapeRef{
API: a,
ShapeName: op.outputRef,
Shape: &Shape{
API: a,
ShapeName: op.output,
},
}
a.Shapes[op.input] = &Shape{
ShapeName: op.input,
}
a.Shapes[op.output] = &Shape{
ShapeName: op.output,
}
expected[op.operation] = append(expected[op.operation], op.expectedInput)
expected[op.operation] = append(expected[op.operation], op.expectedOutput)
}
a.fixStutterNames()
a.renameToplevelShapes()
for k, v := range expected {
if a.Operations[k].InputRef.Shape.ShapeName != v[0] {
t.Errorf("Error %d case: Expected %q, but received %q", k, v[0], a.Operations[k].InputRef.Shape.ShapeName)
}
if a.Operations[k].OutputRef.Shape.ShapeName != v[1] {
t.Errorf("Error %d case: Expected %q, but received %q", k, v[1], a.Operations[k].OutputRef.Shape.ShapeName)
}
}
}
}

View file

@ -0,0 +1,659 @@
// +build codegen
package api
import (
"bytes"
"fmt"
"path"
"regexp"
"sort"
"strings"
"text/template"
)
// A ShapeRef defines the usage of a shape within the API.
type ShapeRef struct {
API *API `json:"-"`
Shape *Shape `json:"-"`
Documentation string
ShapeName string `json:"shape"`
Location string
LocationName string
QueryName string
Flattened bool
Streaming bool
XMLAttribute bool
// Ignore, if set, will not be sent over the wire
Ignore bool
XMLNamespace XMLInfo
Payload string
IdempotencyToken bool `json:"idempotencyToken"`
JSONValue bool `json:"jsonvalue"`
Deprecated bool `json:"deprecated"`
OrigShapeName string `json:"-"`
}
// ErrorInfo represents the error block of a shape's structure
type ErrorInfo struct {
Code string
HTTPStatusCode int
}
// A XMLInfo defines URL and prefix for Shapes when rendered as XML
type XMLInfo struct {
Prefix string
URI string
}
// A Shape defines the definition of a shape type
type Shape struct {
API *API `json:"-"`
ShapeName string
Documentation string
MemberRefs map[string]*ShapeRef `json:"members"`
MemberRef ShapeRef `json:"member"`
KeyRef ShapeRef `json:"key"`
ValueRef ShapeRef `json:"value"`
Required []string
Payload string
Type string
Exception bool
Enum []string
EnumConsts []string
Flattened bool
Streaming bool
Location string
LocationName string
IdempotencyToken bool `json:"idempotencyToken"`
XMLNamespace XMLInfo
Min float64 // optional Minimum length (string, list) or value (number)
Max float64 // optional Maximum length (string, list) or value (number)
refs []*ShapeRef // References to this shape
resolvePkg string // use this package in the goType() if present
OrigShapeName string `json:"-"`
// Defines if the shape is a placeholder and should not be used directly
Placeholder bool
Deprecated bool `json:"deprecated"`
Validations ShapeValidations
// Error information that is set if the shape is an error shape.
IsError bool
ErrorInfo ErrorInfo `json:"error"`
}
// ErrorCodeName will return the error shape's name formated for
// error code const.
func (s *Shape) ErrorCodeName() string {
return "ErrCode" + s.ShapeName
}
// ErrorName will return the shape's name or error code if available based
// on the API's protocol. This is the error code string returned by the service.
func (s *Shape) ErrorName() string {
name := s.ShapeName
switch s.API.Metadata.Protocol {
case "query", "ec2query", "rest-xml":
if len(s.ErrorInfo.Code) > 0 {
name = s.ErrorInfo.Code
}
}
return name
}
// GoTags returns the struct tags for a shape.
func (s *Shape) GoTags(root, required bool) string {
ref := &ShapeRef{ShapeName: s.ShapeName, API: s.API, Shape: s}
return ref.GoTags(root, required)
}
// Rename changes the name of the Shape to newName. Also updates
// the associated API's reference to use newName.
func (s *Shape) Rename(newName string) {
for _, r := range s.refs {
r.OrigShapeName = r.ShapeName
r.ShapeName = newName
}
delete(s.API.Shapes, s.ShapeName)
s.OrigShapeName = s.ShapeName
s.API.Shapes[newName] = s
s.ShapeName = newName
}
// MemberNames returns a slice of struct member names.
func (s *Shape) MemberNames() []string {
i, names := 0, make([]string, len(s.MemberRefs))
for n := range s.MemberRefs {
names[i] = n
i++
}
sort.Strings(names)
return names
}
// GoTypeWithPkgName returns a shape's type as a string with the package name in
// <packageName>.<type> format. Package naming only applies to structures.
func (s *Shape) GoTypeWithPkgName() string {
return goType(s, true)
}
// GenAccessors returns if the shape's reference should have setters generated.
func (s *ShapeRef) UseIndirection() bool {
switch s.Shape.Type {
case "map", "list", "blob", "structure", "jsonvalue":
return false
}
if s.Streaming || s.Shape.Streaming {
return false
}
if s.JSONValue {
return false
}
return true
}
// GoStructValueType returns the Shape's Go type value instead of a pointer
// for the type.
func (s *Shape) GoStructValueType(name string, ref *ShapeRef) string {
v := s.GoStructType(name, ref)
if ref.UseIndirection() && v[0] == '*' {
return v[1:]
}
return v
}
// GoStructType returns the type of a struct field based on the API
// model definition.
func (s *Shape) GoStructType(name string, ref *ShapeRef) string {
if (ref.Streaming || ref.Shape.Streaming) && s.Payload == name {
rtype := "io.ReadSeeker"
if strings.HasSuffix(s.ShapeName, "Output") {
rtype = "io.ReadCloser"
}
s.API.imports["io"] = true
return rtype
}
if ref.JSONValue {
s.API.imports["github.com/aws/aws-sdk-go/aws"] = true
return "aws.JSONValue"
}
for _, v := range s.Validations {
// TODO move this to shape validation resolution
if (v.Ref.Shape.Type == "map" || v.Ref.Shape.Type == "list") && v.Type == ShapeValidationNested {
s.API.imports["fmt"] = true
}
}
return ref.GoType()
}
// GoType returns a shape's Go type
func (s *Shape) GoType() string {
return goType(s, false)
}
// GoType returns a shape ref's Go type.
func (ref *ShapeRef) GoType() string {
if ref.Shape == nil {
panic(fmt.Errorf("missing shape definition on reference for %#v", ref))
}
return ref.Shape.GoType()
}
// GoTypeWithPkgName returns a shape's type as a string with the package name in
// <packageName>.<type> format. Package naming only applies to structures.
func (ref *ShapeRef) GoTypeWithPkgName() string {
if ref.Shape == nil {
panic(fmt.Errorf("missing shape definition on reference for %#v", ref))
}
return ref.Shape.GoTypeWithPkgName()
}
// Returns a string version of the Shape's type.
// If withPkgName is true, the package name will be added as a prefix
func goType(s *Shape, withPkgName bool) string {
switch s.Type {
case "structure":
if withPkgName || s.resolvePkg != "" {
pkg := s.resolvePkg
if pkg != "" {
s.API.imports[pkg] = true
pkg = path.Base(pkg)
} else {
pkg = s.API.PackageName()
}
return fmt.Sprintf("*%s.%s", pkg, s.ShapeName)
}
return "*" + s.ShapeName
case "map":
return "map[string]" + s.ValueRef.GoType()
case "jsonvalue":
return "aws.JSONValue"
case "list":
return "[]" + s.MemberRef.GoType()
case "boolean":
return "*bool"
case "string", "character":
return "*string"
case "blob":
return "[]byte"
case "integer", "long":
return "*int64"
case "float", "double":
return "*float64"
case "timestamp":
s.API.imports["time"] = true
return "*time.Time"
default:
panic("Unsupported shape type: " + s.Type)
}
}
// GoTypeElem returns the Go type for the Shape. If the shape type is a pointer just
// the type will be returned minus the pointer *.
func (s *Shape) GoTypeElem() string {
t := s.GoType()
if strings.HasPrefix(t, "*") {
return t[1:]
}
return t
}
// GoTypeElem returns the Go type for the Shape. If the shape type is a pointer just
// the type will be returned minus the pointer *.
func (ref *ShapeRef) GoTypeElem() string {
if ref.Shape == nil {
panic(fmt.Errorf("missing shape definition on reference for %#v", ref))
}
return ref.Shape.GoTypeElem()
}
// ShapeTag is a struct tag that will be applied to a shape's generated code
type ShapeTag struct {
Key, Val string
}
// String returns the string representation of the shape tag
func (s ShapeTag) String() string {
return fmt.Sprintf(`%s:"%s"`, s.Key, s.Val)
}
// ShapeTags is a collection of shape tags and provides serialization of the
// tags in an ordered list.
type ShapeTags []ShapeTag
// Join returns an ordered serialization of the shape tags with the provided
// separator.
func (s ShapeTags) Join(sep string) string {
o := &bytes.Buffer{}
for i, t := range s {
o.WriteString(t.String())
if i < len(s)-1 {
o.WriteString(sep)
}
}
return o.String()
}
// String is an alias for Join with the empty space separator.
func (s ShapeTags) String() string {
return s.Join(" ")
}
// GoTags returns the rendered tags string for the ShapeRef
func (ref *ShapeRef) GoTags(toplevel bool, isRequired bool) string {
tags := ShapeTags{}
if ref.Location != "" {
tags = append(tags, ShapeTag{"location", ref.Location})
} else if ref.Shape.Location != "" {
tags = append(tags, ShapeTag{"location", ref.Shape.Location})
}
if ref.LocationName != "" {
tags = append(tags, ShapeTag{"locationName", ref.LocationName})
} else if ref.Shape.LocationName != "" {
tags = append(tags, ShapeTag{"locationName", ref.Shape.LocationName})
}
if ref.QueryName != "" {
tags = append(tags, ShapeTag{"queryName", ref.QueryName})
}
if ref.Shape.MemberRef.LocationName != "" {
tags = append(tags, ShapeTag{"locationNameList", ref.Shape.MemberRef.LocationName})
}
if ref.Shape.KeyRef.LocationName != "" {
tags = append(tags, ShapeTag{"locationNameKey", ref.Shape.KeyRef.LocationName})
}
if ref.Shape.ValueRef.LocationName != "" {
tags = append(tags, ShapeTag{"locationNameValue", ref.Shape.ValueRef.LocationName})
}
if ref.Shape.Min > 0 {
tags = append(tags, ShapeTag{"min", fmt.Sprintf("%v", ref.Shape.Min)})
}
if ref.Deprecated || ref.Shape.Deprecated {
tags = append(tags, ShapeTag{"deprecated", "true"})
}
// All shapes have a type
tags = append(tags, ShapeTag{"type", ref.Shape.Type})
// embed the timestamp type for easier lookups
if ref.Shape.Type == "timestamp" {
t := ShapeTag{Key: "timestampFormat"}
if ref.Location == "header" {
t.Val = "rfc822"
} else {
switch ref.API.Metadata.Protocol {
case "json", "rest-json":
t.Val = "unix"
case "rest-xml", "ec2", "query":
t.Val = "iso8601"
}
}
tags = append(tags, t)
}
if ref.Shape.Flattened || ref.Flattened {
tags = append(tags, ShapeTag{"flattened", "true"})
}
if ref.XMLAttribute {
tags = append(tags, ShapeTag{"xmlAttribute", "true"})
}
if isRequired {
tags = append(tags, ShapeTag{"required", "true"})
}
if ref.Shape.IsEnum() {
tags = append(tags, ShapeTag{"enum", ref.ShapeName})
}
if toplevel {
if ref.Shape.Payload != "" {
tags = append(tags, ShapeTag{"payload", ref.Shape.Payload})
}
if ref.XMLNamespace.Prefix != "" {
tags = append(tags, ShapeTag{"xmlPrefix", ref.XMLNamespace.Prefix})
} else if ref.Shape.XMLNamespace.Prefix != "" {
tags = append(tags, ShapeTag{"xmlPrefix", ref.Shape.XMLNamespace.Prefix})
}
if ref.XMLNamespace.URI != "" {
tags = append(tags, ShapeTag{"xmlURI", ref.XMLNamespace.URI})
} else if ref.Shape.XMLNamespace.URI != "" {
tags = append(tags, ShapeTag{"xmlURI", ref.Shape.XMLNamespace.URI})
}
}
if ref.IdempotencyToken || ref.Shape.IdempotencyToken {
tags = append(tags, ShapeTag{"idempotencyToken", "true"})
}
if ref.Ignore {
tags = append(tags, ShapeTag{"ignore", "true"})
}
return fmt.Sprintf("`%s`", tags)
}
// Docstring returns the godocs formated documentation
func (ref *ShapeRef) Docstring() string {
if ref.Documentation != "" {
return strings.Trim(ref.Documentation, "\n ")
}
return ref.Shape.Docstring()
}
// Docstring returns the godocs formated documentation
func (s *Shape) Docstring() string {
return strings.Trim(s.Documentation, "\n ")
}
// IndentedDocstring is the indented form of the doc string.
func (ref *ShapeRef) IndentedDocstring() string {
doc := ref.Docstring()
return strings.Replace(doc, "// ", "// ", -1)
}
var goCodeStringerTmpl = template.Must(template.New("goCodeStringerTmpl").Parse(`
// String returns the string representation
func (s {{ .ShapeName }}) String() string {
return awsutil.Prettify(s)
}
// GoString returns the string representation
func (s {{ .ShapeName }}) GoString() string {
return s.String()
}
`))
// GoCodeStringers renders the Stringers for API input/output shapes
func (s *Shape) GoCodeStringers() string {
w := bytes.Buffer{}
if err := goCodeStringerTmpl.Execute(&w, s); err != nil {
panic(fmt.Sprintln("Unexpected error executing GoCodeStringers template", err))
}
return w.String()
}
var enumStrip = regexp.MustCompile(`[^a-zA-Z0-9_:\./-]`)
var enumDelims = regexp.MustCompile(`[-_:\./]+`)
var enumCamelCase = regexp.MustCompile(`([a-z])([A-Z])`)
// EnumName returns the Nth enum in the shapes Enum list
func (s *Shape) EnumName(n int) string {
enum := s.Enum[n]
enum = enumStrip.ReplaceAllLiteralString(enum, "")
enum = enumCamelCase.ReplaceAllString(enum, "$1-$2")
parts := enumDelims.Split(enum, -1)
for i, v := range parts {
v = strings.ToLower(v)
parts[i] = ""
if len(v) > 0 {
parts[i] = strings.ToUpper(v[0:1])
}
if len(v) > 1 {
parts[i] += v[1:]
}
}
enum = strings.Join(parts, "")
enum = strings.ToUpper(enum[0:1]) + enum[1:]
return enum
}
// NestedShape returns the shape pointer value for the shape which is nested
// under the current shape. If the shape is not nested nil will be returned.
//
// strucutures, the current shape is returned
// map: the value shape of the map is returned
// list: the element shape of the list is returned
func (s *Shape) NestedShape() *Shape {
var nestedShape *Shape
switch s.Type {
case "structure":
nestedShape = s
case "map":
nestedShape = s.ValueRef.Shape
case "list":
nestedShape = s.MemberRef.Shape
}
return nestedShape
}
var structShapeTmpl = template.Must(template.New("StructShape").Funcs(template.FuncMap{
"GetCrosslinkURL": GetCrosslinkURL,
}).Parse(`
{{ .Docstring }}
{{ if ne $.OrigShapeName "" -}}
{{ $crosslinkURL := GetCrosslinkURL $.API.BaseCrosslinkURL $.API.APIName $.API.Metadata.UID $.OrigShapeName -}}
{{ if ne $crosslinkURL "" -}}
// Please also see {{ $crosslinkURL }}
{{ end -}}
{{ else -}}
{{ $crosslinkURL := GetCrosslinkURL $.API.BaseCrosslinkURL $.API.APIName $.API.Metadata.UID $.ShapeName -}}
{{ if ne $crosslinkURL "" -}}
// Please also see {{ $crosslinkURL }}
{{ end -}}
{{ end -}}
{{ $context := . -}}
type {{ .ShapeName }} struct {
_ struct{} {{ .GoTags true false }}
{{ range $_, $name := $context.MemberNames -}}
{{ $elem := index $context.MemberRefs $name -}}
{{ $isBlob := $context.WillRefBeBase64Encoded $name -}}
{{ $isRequired := $context.IsRequired $name -}}
{{ $doc := $elem.Docstring -}}
{{ if $doc -}}
{{ $doc }}
{{ end -}}
{{ if $isBlob -}}
{{ if $doc -}}
//
{{ end -}}
// {{ $name }} is automatically base64 encoded/decoded by the SDK.
{{ end -}}
{{ if $isRequired -}}
{{ if or $doc $isBlob -}}
//
{{ end -}}
// {{ $name }} is a required field
{{ end -}}
{{ $name }} {{ $context.GoStructType $name $elem }} {{ $elem.GoTags false $isRequired }}
{{ end }}
}
{{ if not .API.NoStringerMethods }}
{{ .GoCodeStringers }}
{{ end }}
{{ if not .API.NoValidataShapeMethods }}
{{ if .Validations -}}
{{ .Validations.GoCode . }}
{{ end }}
{{ end }}
{{ if not .API.NoGenStructFieldAccessors }}
{{ $builderShapeName := print .ShapeName -}}
{{ range $_, $name := $context.MemberNames -}}
{{ $elem := index $context.MemberRefs $name -}}
// Set{{ $name }} sets the {{ $name }} field's value.
func (s *{{ $builderShapeName }}) Set{{ $name }}(v {{ $context.GoStructValueType $name $elem }}) *{{ $builderShapeName }} {
{{ if $elem.UseIndirection -}}
s.{{ $name }} = &v
{{ else -}}
s.{{ $name }} = v
{{ end -}}
return s
}
{{ end }}
{{ end }}
`))
var enumShapeTmpl = template.Must(template.New("EnumShape").Parse(`
{{ .Docstring }}
const (
{{ $context := . -}}
{{ range $index, $elem := .Enum -}}
{{ $name := index $context.EnumConsts $index -}}
// {{ $name }} is a {{ $context.ShapeName }} enum value
{{ $name }} = "{{ $elem }}"
{{ end }}
)
`))
// GoCode returns the rendered Go code for the Shape.
func (s *Shape) GoCode() string {
b := &bytes.Buffer{}
switch {
case s.Type == "structure":
if err := structShapeTmpl.Execute(b, s); err != nil {
panic(fmt.Sprintf("Failed to generate struct shape %s, %v\n", s.ShapeName, err))
}
case s.IsEnum():
if err := enumShapeTmpl.Execute(b, s); err != nil {
panic(fmt.Sprintf("Failed to generate enum shape %s, %v\n", s.ShapeName, err))
}
default:
panic(fmt.Sprintln("Cannot generate toplevel shape for", s.Type))
}
return b.String()
}
// IsEnum returns whether this shape is an enum list
func (s *Shape) IsEnum() bool {
return s.Type == "string" && len(s.Enum) > 0
}
// IsRequired returns if member is a required field.
func (s *Shape) IsRequired(member string) bool {
for _, n := range s.Required {
if n == member {
return true
}
}
return false
}
// IsInternal returns whether the shape was defined in this package
func (s *Shape) IsInternal() bool {
return s.resolvePkg == ""
}
// removeRef removes a shape reference from the list of references this
// shape is used in.
func (s *Shape) removeRef(ref *ShapeRef) {
r := s.refs
for i := 0; i < len(r); i++ {
if r[i] == ref {
j := i + 1
copy(r[i:], r[j:])
for k, n := len(r)-j+i, len(r); k < n; k++ {
r[k] = nil // free up the end of the list
} // for k
s.refs = r[:len(r)-j+i]
break
}
}
}
func (s *Shape) WillRefBeBase64Encoded(refName string) bool {
payloadRefName := s.Payload
if payloadRefName == refName {
return false
}
ref, ok := s.MemberRefs[refName]
if !ok {
panic(fmt.Sprintf("shape %s does not contain %q refName", s.ShapeName, refName))
}
return ref.Shape.Type == "blob"
}

View file

@ -0,0 +1,155 @@
// +build codegen
package api
import (
"bytes"
"fmt"
"text/template"
)
// A ShapeValidationType is the type of validation that a shape needs
type ShapeValidationType int
const (
// ShapeValidationRequired states the shape must be set
ShapeValidationRequired = iota
// ShapeValidationMinVal states the shape must have at least a number of
// elements, or for numbers a minimum value
ShapeValidationMinVal
// ShapeValidationNested states the shape has nested values that need
// to be validated
ShapeValidationNested
)
// A ShapeValidation contains information about a shape and the type of validation
// that is needed
type ShapeValidation struct {
// Name of the shape to be validated
Name string
// Reference to the shape within the context the shape is referenced
Ref *ShapeRef
// Type of validation needed
Type ShapeValidationType
}
var validationGoCodeTmpls = template.Must(template.New("validationGoCodeTmpls").Parse(`
{{ define "requiredValue" -}}
if s.{{ .Name }} == nil {
invalidParams.Add(request.NewErrParamRequired("{{ .Name }}"))
}
{{- end }}
{{ define "minLen" -}}
if s.{{ .Name }} != nil && len(s.{{ .Name }}) < {{ .Ref.Shape.Min }} {
invalidParams.Add(request.NewErrParamMinLen("{{ .Name }}", {{ .Ref.Shape.Min }}))
}
{{- end }}
{{ define "minLenString" -}}
if s.{{ .Name }} != nil && len(*s.{{ .Name }}) < {{ .Ref.Shape.Min }} {
invalidParams.Add(request.NewErrParamMinLen("{{ .Name }}", {{ .Ref.Shape.Min }}))
}
{{- end }}
{{ define "minVal" -}}
if s.{{ .Name }} != nil && *s.{{ .Name }} < {{ .Ref.Shape.Min }} {
invalidParams.Add(request.NewErrParamMinValue("{{ .Name }}", {{ .Ref.Shape.Min }}))
}
{{- end }}
{{ define "nestedMapList" -}}
if s.{{ .Name }} != nil {
for i, v := range s.{{ .Name }} {
if v == nil { continue }
if err := v.Validate(); err != nil {
invalidParams.AddNested(fmt.Sprintf("%s[%v]", "{{ .Name }}", i), err.(request.ErrInvalidParams))
}
}
}
{{- end }}
{{ define "nestedStruct" -}}
if s.{{ .Name }} != nil {
if err := s.{{ .Name }}.Validate(); err != nil {
invalidParams.AddNested("{{ .Name }}", err.(request.ErrInvalidParams))
}
}
{{- end }}
`))
// GoCode returns the generated Go code for the Shape with its validation type.
func (sv ShapeValidation) GoCode() string {
var err error
w := &bytes.Buffer{}
switch sv.Type {
case ShapeValidationRequired:
err = validationGoCodeTmpls.ExecuteTemplate(w, "requiredValue", sv)
case ShapeValidationMinVal:
switch sv.Ref.Shape.Type {
case "list", "map", "blob":
err = validationGoCodeTmpls.ExecuteTemplate(w, "minLen", sv)
case "string":
err = validationGoCodeTmpls.ExecuteTemplate(w, "minLenString", sv)
case "integer", "long", "float", "double":
err = validationGoCodeTmpls.ExecuteTemplate(w, "minVal", sv)
default:
panic(fmt.Sprintf("ShapeValidation.GoCode, %s's type %s, no min value handling",
sv.Name, sv.Ref.Shape.Type))
}
case ShapeValidationNested:
switch sv.Ref.Shape.Type {
case "map", "list":
err = validationGoCodeTmpls.ExecuteTemplate(w, "nestedMapList", sv)
default:
err = validationGoCodeTmpls.ExecuteTemplate(w, "nestedStruct", sv)
}
default:
panic(fmt.Sprintf("ShapeValidation.GoCode, %s's type %d, unknown validation type",
sv.Name, sv.Type))
}
if err != nil {
panic(fmt.Sprintf("ShapeValidation.GoCode failed, err: %v", err))
}
return w.String()
}
// A ShapeValidations is a collection of shape validations needed nested within
// a parent shape
type ShapeValidations []ShapeValidation
var validateShapeTmpl = template.Must(template.New("ValidateShape").Parse(`
// Validate inspects the fields of the type to determine if they are valid.
func (s *{{ .Shape.ShapeName }}) Validate() error {
invalidParams := request.ErrInvalidParams{Context: "{{ .Shape.ShapeName }}"}
{{ range $_, $v := .Validations -}}
{{ $v.GoCode }}
{{ end }}
if invalidParams.Len() > 0 {
return invalidParams
}
return nil
}
`))
// GoCode generates the Go code needed to perform validations for the
// shape and its nested fields.
func (vs ShapeValidations) GoCode(shape *Shape) string {
buf := &bytes.Buffer{}
validateShapeTmpl.Execute(buf, map[string]interface{}{
"Shape": shape,
"Validations": vs,
})
return buf.String()
}
// Has returns true or false if the ShapeValidations already contains the
// the reference and validation type.
func (vs ShapeValidations) Has(ref *ShapeRef, typ ShapeValidationType) bool {
for _, v := range vs {
if v.Ref == ref && v.Type == typ {
return true
}
}
return false
}

View file

@ -0,0 +1,28 @@
// +build 1.6,codegen
package api_test
import (
"testing"
"github.com/aws/aws-sdk-go/private/model/api"
)
func TestShapeTagJoin(t *testing.T) {
s := api.ShapeTags{
{Key: "location", Val: "query"},
{Key: "locationName", Val: "abc"},
{Key: "type", Val: "string"},
}
expected := `location:"query" locationName:"abc" type:"string"`
o := s.Join(" ")
o2 := s.String()
if expected != o {
t.Errorf("Expected %s, but received %s", expected, o)
}
if expected != o2 {
t.Errorf("Expected %s, but received %s", expected, o2)
}
}

View file

@ -0,0 +1,189 @@
// +build codegen
package api
import (
"bytes"
"encoding/json"
"fmt"
"os"
"sort"
"strings"
"text/template"
)
// WaiterAcceptor is the acceptors defined in the model the SDK will use
// to wait on resource states with.
type WaiterAcceptor struct {
State string
Matcher string
Argument string
Expected interface{}
}
// ExpectedString returns the string that was expected by the WaiterAcceptor
func (a *WaiterAcceptor) ExpectedString() string {
switch a.Expected.(type) {
case string:
return fmt.Sprintf("%q", a.Expected)
default:
return fmt.Sprintf("%v", a.Expected)
}
}
// A Waiter is an individual waiter definition.
type Waiter struct {
Name string
Delay int
MaxAttempts int
OperationName string `json:"operation"`
Operation *Operation
Acceptors []WaiterAcceptor
}
// WaitersGoCode generates and returns Go code for each of the waiters of
// this API.
func (a *API) WaitersGoCode() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "import (\n%q\n\n%q\n%q\n)",
"time",
"github.com/aws/aws-sdk-go/aws",
"github.com/aws/aws-sdk-go/aws/request",
)
for _, w := range a.Waiters {
buf.WriteString(w.GoCode())
}
return buf.String()
}
// used for unmarshaling from the waiter JSON file
type waiterDefinitions struct {
*API
Waiters map[string]Waiter
}
// AttachWaiters reads a file of waiter definitions, and adds those to the API.
// Will panic if an error occurs.
func (a *API) AttachWaiters(filename string) {
p := waiterDefinitions{API: a}
f, err := os.Open(filename)
defer f.Close()
if err != nil {
panic(err)
}
err = json.NewDecoder(f).Decode(&p)
if err != nil {
panic(err)
}
p.setup()
}
func (p *waiterDefinitions) setup() {
p.API.Waiters = []Waiter{}
i, keys := 0, make([]string, len(p.Waiters))
for k := range p.Waiters {
keys[i] = k
i++
}
sort.Strings(keys)
for _, n := range keys {
e := p.Waiters[n]
n = p.ExportableName(n)
e.Name = n
e.OperationName = p.ExportableName(e.OperationName)
e.Operation = p.API.Operations[e.OperationName]
if e.Operation == nil {
panic("unknown operation " + e.OperationName + " for waiter " + n)
}
p.API.Waiters = append(p.API.Waiters, e)
}
}
var waiterTmpls = template.Must(template.New("waiterTmpls").Funcs(
template.FuncMap{
"titleCase": func(v string) string {
return strings.Title(v)
},
},
).Parse(`
{{ define "waiter"}}
// WaitUntil{{ .Name }} uses the {{ .Operation.API.NiceName }} API operation
// {{ .OperationName }} to wait for a condition to be met before returning.
// If the condition is not meet within the max attempt window an error will
// be returned.
func (c *{{ .Operation.API.StructName }}) WaitUntil{{ .Name }}(input {{ .Operation.InputRef.GoType }}) error {
return c.WaitUntil{{ .Name }}WithContext(aws.BackgroundContext(), input)
}
// WaitUntil{{ .Name }}WithContext is an extended version of WaitUntil{{ .Name }}.
// With the support for passing in a context and options to configure the
// Waiter and the underlying request options.
//
// The context must be non-nil and will be used for request cancellation. If
// the context is nil a panic will occur. In the future the SDK may create
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
// for more information on using Contexts.
func (c *{{ .Operation.API.StructName }}) WaitUntil{{ .Name }}WithContext(` +
`ctx aws.Context, input {{ .Operation.InputRef.GoType }}, opts ...request.WaiterOption) error {
w := request.Waiter{
Name: "WaitUntil{{ .Name }}",
MaxAttempts: {{ .MaxAttempts }},
Delay: request.ConstantWaiterDelay({{ .Delay }} * time.Second),
Acceptors: []request.WaiterAcceptor{
{{ range $_, $a := .Acceptors }}{
State: request.{{ titleCase .State }}WaiterState,
Matcher: request.{{ titleCase .Matcher }}WaiterMatch,
{{- if .Argument }}Argument: "{{ .Argument }}",{{ end }}
Expected: {{ .ExpectedString }},
},
{{ end }}
},
Logger: c.Config.Logger,
NewRequest: func(opts []request.Option) (*request.Request, error) {
var inCpy {{ .Operation.InputRef.GoType }}
if input != nil {
tmp := *input
inCpy = &tmp
}
req, _ := c.{{ .OperationName }}Request(inCpy)
req.SetContext(ctx)
req.ApplyOptions(opts...)
return req, nil
},
}
w.ApplyOptions(opts...)
return w.WaitWithContext(ctx)
}
{{- end }}
{{ define "waiter interface" }}
WaitUntil{{ .Name }}({{ .Operation.InputRef.GoTypeWithPkgName }}) error
WaitUntil{{ .Name }}WithContext(aws.Context, {{ .Operation.InputRef.GoTypeWithPkgName }}, ...request.WaiterOption) error
{{- end }}
`))
// InterfaceSignature returns a string representing the Waiter's interface
// function signature.
func (w *Waiter) InterfaceSignature() string {
var buf bytes.Buffer
if err := waiterTmpls.ExecuteTemplate(&buf, "waiter interface", w); err != nil {
panic(err)
}
return strings.TrimSpace(buf.String())
}
// GoCode returns the generated Go code for an individual waiter.
func (w *Waiter) GoCode() string {
var buf bytes.Buffer
if err := waiterTmpls.ExecuteTemplate(&buf, "waiter", w); err != nil {
panic(err)
}
return buf.String()
}