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