1// +build codegen 2 3package api 4 5import ( 6 "encoding/json" 7 "fmt" 8 "log" 9 "os" 10 "path/filepath" 11 "sort" 12 "strings" 13) 14 15// APIs provides a set of API models loaded by API package name. 16type APIs map[string]*API 17 18// Loader provides the loading of APIs from files. 19type Loader struct { 20 // The base Go import path the loaded models will be appended to. 21 BaseImport string 22 23 // Allows ignoring API models that are unsupported by the SDK without 24 // failing the load of other supported APIs. 25 IgnoreUnsupportedAPIs bool 26} 27 28// Load loads the API model files from disk returning the map of API package. 29// Returns error if multiple API model resolve to the same package name. 30func (l Loader) Load(modelPaths []string) (APIs, error) { 31 apis := APIs{} 32 for _, modelPath := range modelPaths { 33 a, err := loadAPI(modelPath, l.BaseImport, func(a *API) { 34 a.IgnoreUnsupportedAPIs = l.IgnoreUnsupportedAPIs 35 }) 36 if err != nil { 37 return nil, fmt.Errorf("failed to load API, %v, %v", modelPath, err) 38 } 39 40 if len(a.Operations) == 0 { 41 if l.IgnoreUnsupportedAPIs { 42 fmt.Fprintf(os.Stderr, "API has no operations, ignoring model %s, %v\n", 43 modelPath, a.ImportPath()) 44 continue 45 } 46 } 47 48 importPath := a.ImportPath() 49 if _, ok := apis[importPath]; ok { 50 return nil, fmt.Errorf( 51 "package names must be unique attempted to load %v twice. Second model file: %v", 52 importPath, modelPath) 53 } 54 apis[importPath] = a 55 } 56 57 return apis, nil 58} 59 60// attempts to load a model from disk into the import specified. Additional API 61// options are invoked before to the API's Setup being called. 62func loadAPI(modelPath, baseImport string, opts ...func(*API)) (*API, error) { 63 a := &API{ 64 BaseImportPath: baseImport, 65 BaseCrosslinkURL: "https://docs.aws.amazon.com", 66 } 67 68 modelFile := filepath.Base(modelPath) 69 modelDir := filepath.Dir(modelPath) 70 err := attachModelFiles(modelDir, 71 modelLoader{modelFile, a.Attach, true}, 72 modelLoader{"docs-2.json", a.AttachDocs, false}, 73 modelLoader{"paginators-1.json", a.AttachPaginators, false}, 74 modelLoader{"waiters-2.json", a.AttachWaiters, false}, 75 modelLoader{"examples-1.json", a.AttachExamples, false}, 76 modelLoader{"smoke.json", a.AttachSmokeTests, false}, 77 ) 78 if err != nil { 79 return nil, err 80 } 81 82 for _, opt := range opts { 83 opt(a) 84 } 85 86 if err = a.Setup(); err != nil { 87 return nil, err 88 } 89 90 return a, nil 91} 92 93type modelLoader struct { 94 Filename string 95 Loader func(string) error 96 Required bool 97} 98 99func attachModelFiles(modelPath string, modelFiles ...modelLoader) error { 100 for _, m := range modelFiles { 101 filepath := filepath.Join(modelPath, m.Filename) 102 _, err := os.Stat(filepath) 103 if os.IsNotExist(err) && !m.Required { 104 continue 105 } else if err != nil { 106 return fmt.Errorf("failed to load model file %v, %v", m.Filename, err) 107 } 108 109 if err = m.Loader(filepath); err != nil { 110 return fmt.Errorf("model load failed, %s, %v", modelPath, err) 111 } 112 } 113 114 return nil 115} 116 117// ExpandModelGlobPath returns a slice of model paths expanded from the glob 118// pattern passed in. Returns the path of the model file to be loaded. Includes 119// all versions of a service model. 120// 121// e.g: 122// models/apis/*/*/api-2.json 123// 124// Or with specific model file: 125// models/apis/service/version/api-2.json 126func ExpandModelGlobPath(globs ...string) ([]string, error) { 127 modelPaths := []string{} 128 129 for _, g := range globs { 130 filepaths, err := filepath.Glob(g) 131 if err != nil { 132 return nil, err 133 } 134 for _, p := range filepaths { 135 modelPaths = append(modelPaths, p) 136 } 137 } 138 139 return modelPaths, nil 140} 141 142// TrimModelServiceVersions sorts the model paths by service version then 143// returns recent model versions, and model version excluded. 144// 145// Uses the third from last path element to determine unique service. Only one 146// service version will be included. 147// 148// models/apis/service/version/api-2.json 149func TrimModelServiceVersions(modelPaths []string) (include, exclude []string) { 150 sort.Strings(modelPaths) 151 152 // Remove old API versions from list 153 m := map[string]struct{}{} 154 for i := len(modelPaths) - 1; i >= 0; i-- { 155 // service name is 2nd-to-last component 156 parts := strings.Split(modelPaths[i], string(filepath.Separator)) 157 svc := parts[len(parts)-3] 158 159 if _, ok := m[svc]; ok { 160 // Removed unused service version 161 exclude = append(exclude, modelPaths[i]) 162 continue 163 } 164 include = append(include, modelPaths[i]) 165 m[svc] = struct{}{} 166 } 167 168 return include, exclude 169} 170 171// Attach opens a file by name, and unmarshal its JSON data. 172// Will proceed to setup the API if not already done so. 173func (a *API) Attach(filename string) error { 174 a.path = filepath.Dir(filename) 175 f, err := os.Open(filename) 176 if err != nil { 177 return err 178 } 179 defer f.Close() 180 181 if err := json.NewDecoder(f).Decode(a); err != nil { 182 return fmt.Errorf("failed to decode %s, err: %v", filename, err) 183 } 184 185 return nil 186} 187 188// AttachString will unmarshal a raw JSON string, and setup the 189// API if not already done so. 190func (a *API) AttachString(str string) error { 191 json.Unmarshal([]byte(str), a) 192 193 if a.initialized { 194 return nil 195 } 196 197 return a.Setup() 198} 199 200// Setup initializes the API. 201func (a *API) Setup() error { 202 a.setServiceAliaseName() 203 a.setMetadataEndpointsKey() 204 a.writeShapeNames() 205 a.resolveReferences() 206 a.backfillErrorMembers() 207 208 if !a.NoRemoveUnusedShapes { 209 a.removeUnusedShapes() 210 } 211 212 a.fixStutterNames() 213 if err := a.validateShapeNames(); err != nil { 214 log.Fatalf(err.Error()) 215 } 216 a.renameExportable() 217 a.applyShapeNameAliases() 218 a.createInputOutputShapes() 219 a.writeInputOutputLocationName() 220 a.renameAPIPayloadShapes() 221 a.renameCollidingFields() 222 a.updateTopLevelShapeReferences() 223 if err := a.setupEventStreams(); err != nil { 224 return err 225 } 226 227 a.findEndpointDiscoveryOp() 228 a.injectUnboundedOutputStreaming() 229 230 // Enables generated types for APIs using REST-JSON and JSONRPC protocols. 231 // Other protocols will be added as supported. 232 a.enableGeneratedTypedErrors() 233 234 if err := a.customizationPasses(); err != nil { 235 return err 236 } 237 238 a.addHeaderMapDocumentation() 239 240 if !a.NoRemoveUnusedShapes { 241 a.removeUnusedShapes() 242 } 243 244 if !a.NoValidataShapeMethods { 245 a.addShapeValidations() 246 } 247 248 a.initialized = true 249 250 return nil 251} 252 253// UnsupportedAPIModelError provides wrapping of an error causing the API to 254// fail to load because the SDK does not support the API service defined. 255type UnsupportedAPIModelError struct { 256 Err error 257} 258 259func (e UnsupportedAPIModelError) Error() string { 260 return fmt.Sprintf("service API is not supported, %v", e.Err) 261} 262