// +build codegen package api import ( "encoding/json" "fmt" "os" "path/filepath" "sort" "strings" ) // APIs provides a set of API models loaded by API package name. type APIs map[string]*API // LoadAPIs loads the API model files from disk returning the map of API // package. Returns error if multiple API model resolve to the same package // name. func LoadAPIs(modelPaths []string, baseImport string) (APIs, error) { apis := APIs{} for _, modelPath := range modelPaths { a, err := loadAPI(modelPath, baseImport) if err != nil { return nil, fmt.Errorf("failed to load API, %v, %v", modelPath, err) } importPath := a.ImportPath() if _, ok := apis[importPath]; ok { return nil, fmt.Errorf( "package names must be unique attempted to load %v twice. Second model file: %v", importPath, modelPath) } apis[importPath] = a } return apis, nil } func loadAPI(modelPath, baseImport string) (*API, error) { a := &API{ BaseImportPath: baseImport, BaseCrosslinkURL: "https://docs.aws.amazon.com", } modelFile := filepath.Base(modelPath) modelDir := filepath.Dir(modelPath) err := attachModelFiles(modelDir, modelLoader{modelFile, a.Attach, true}, modelLoader{"docs-2.json", a.AttachDocs, false}, modelLoader{"paginators-1.json", a.AttachPaginators, false}, modelLoader{"waiters-2.json", a.AttachWaiters, false}, modelLoader{"examples-1.json", a.AttachExamples, false}, modelLoader{"smoke.json", a.AttachSmokeTests, false}, ) if err != nil { return nil, err } a.Setup() return a, nil } type modelLoader struct { Filename string Loader func(string) Required bool } func attachModelFiles(modelPath string, modelFiles ...modelLoader) error { for _, m := range modelFiles { filepath := filepath.Join(modelPath, m.Filename) _, err := os.Stat(filepath) if os.IsNotExist(err) && !m.Required { continue } else if err != nil { return fmt.Errorf("failed to load model file %v, %v", m.Filename, err) } m.Loader(filepath) } return nil } // ExpandModelGlobPath returns a slice of model paths expanded from the glob // pattern passed in. Returns the path of the model file to be loaded. Includes // all versions of a service model. // // e.g: // models/apis/*/*/api-2.json // // Or with specific model file: // models/apis/service/version/api-2.json func ExpandModelGlobPath(globs ...string) ([]string, error) { modelPaths := []string{} for _, g := range globs { filepaths, err := filepath.Glob(g) if err != nil { return nil, err } for _, p := range filepaths { modelPaths = append(modelPaths, p) } } return modelPaths, nil } // TrimModelServiceVersions sorts the model paths by service version then // returns recent model versions, and model version excluded. // // Uses the third from last path element to determine unique service. Only one // service version will be included. // // models/apis/service/version/api-2.json func TrimModelServiceVersions(modelPaths []string) (include, exclude []string) { sort.Strings(modelPaths) // Remove old API versions from list m := map[string]struct{}{} for i := len(modelPaths) - 1; i >= 0; i-- { // service name is 2nd-to-last component parts := strings.Split(modelPaths[i], string(filepath.Separator)) svc := parts[len(parts)-3] if _, ok := m[svc]; ok { // Removed unused service version exclude = append(exclude, modelPaths[i]) continue } include = append(include, modelPaths[i]) m[svc] = struct{}{} } return include, exclude } // 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.setServiceAliaseName() a.setMetadataEndpointsKey() a.writeShapeNames() a.resolveReferences() if !a.NoRemoveUnusedShapes { a.removeUnusedShapes() } a.fixStutterNames() a.renameExportable() a.applyShapeNameAliases() a.createInputOutputShapes() a.renameAPIPayloadShapes() a.renameCollidingFields() a.updateTopLevelShapeReferences() a.suppressHTTP2EventStreams() a.setupEventStreams() a.findEndpointDiscoveryOp() a.customizationPasses() if !a.NoRemoveUnusedShapes { a.removeUnusedShapes() } if !a.NoValidataShapeMethods { a.addShapeValidations() } a.initialized = true }