diff --git a/tools/importer-rest-api-specs/components/parser/flattener.go b/tools/importer-rest-api-specs/components/parser/flattener.go index 108b534f5a6..04c1723e125 100644 --- a/tools/importer-rest-api-specs/components/parser/flattener.go +++ b/tools/importer-rest-api-specs/components/parser/flattener.go @@ -27,16 +27,18 @@ func load(directory string, fileName string) (*SwaggerDefinition, error) { if err != nil { return nil, fmt.Errorf("loading swagger file %q: %+v", filePath, err) } - swaggerDocWithReferences, err = findAndMergeLocalMixins(swaggerDocWithReferences, directory, fileName) + + localMixins, err := findLocalMixins(swaggerDocWithReferences, filePath) if err != nil { return nil, fmt.Errorf("could not mixin remote swagger files referenced by %q: %+v", filePath, err) } + flattenedWithReferencesOpts := &analysis.FlattenOpts{ Minimal: true, Verbose: true, Expand: false, RemoveUnused: false, - //ContinueOnError: true, + // ContinueOnError: true, BasePath: swaggerDocWithReferences.SpecFilePath(), Spec: analysis.New(swaggerDocWithReferences.Spec()), @@ -52,17 +54,15 @@ func load(directory string, fileName string) (*SwaggerDefinition, error) { if err != nil { return nil, fmt.Errorf("loading swagger file %q: %+v", filePath, err) } - expandedSwaggerDoc, err = findAndMergeLocalMixins(expandedSwaggerDoc, directory, fileName) - if err != nil { - return nil, fmt.Errorf("could not mixin remote swagger files referenced by %q: %+v", filePath, err) - } + + _ = analysis.Mixin(expandedSwaggerDoc.Spec(), localMixins) flattenedExpandedOpts := &analysis.FlattenOpts{ Minimal: false, Verbose: true, Expand: false, RemoveUnused: false, - //ContinueOnError: true, + // ContinueOnError: true, BasePath: expandedSwaggerDoc.SpecFilePath(), Spec: analysis.New(expandedSwaggerDoc.Spec()), @@ -85,38 +85,109 @@ func load(directory string, fileName string) (*SwaggerDefinition, error) { }, nil } -func findAndMergeLocalMixins(input *loads.Document, basePath string, baseFile string) (*loads.Document, error) { - if len(strings.Split(baseFile, "/")) != 2 { // We only care about local files, not sub-folders - return input, nil +func findLocalMixins(input *loads.Document, filePath string) (*spec.Swagger, error) { + mixins := &spec.Swagger{ + SwaggerProps: spec.SwaggerProps{ + Definitions: make(map[string]spec.Schema), + }, } - pathsToMixin := make([]string, 0) + if input.Analyzer != nil { - allRefs := input.Analyzer.AllRefs() - for _, v := range allRefs { - if path := v.Ref.GetURL(); path != nil && path.Path != "" && len(strings.Split(path.Path, "/")) == 2 { // Check if we have a reference in the CWD. - pathsToMixin = append(pathsToMixin, path.Path) + for _, ref := range input.Analyzer.AllRefs() { + if err := findMixinForRef(ref, filePath, input, mixins); err != nil { + return nil, fmt.Errorf("find mixin for ref %q in %q: %+v", ref.String(), filePath, err) } } } - if len(pathsToMixin) > 0 { - uniqueFilter := make(map[string]bool) - uniquePaths := make([]string, 0) - for _, path := range pathsToMixin { - if _, ok := uniqueFilter[path]; !ok { - uniqueFilter[path] = true - uniquePaths = append(uniquePaths, path) + return mixins, nil +} + +func findMixinForRef(ref spec.Ref, filePath string, doc *loads.Document, mixins *spec.Swagger) error { + if path := ref.GetURL(); path == nil || (path.Path != "" && len(strings.Split(path.Path, "/")) != 2) { + // Check if we have a reference in the CWD (otherwise there will be issue resolving relative path in analysis.Flatten). if path.Path is empty string, it refers to the same file + return nil + } + + if strings.HasPrefix(ref.GetURL().Fragment, "/parameters/") { + return nil + } + + modelName, modelFilePath := modelNamePathFromRef(ref, filePath) + if _, ok := mixins.Definitions[modelName]; ok { + // skip if this has already been resolved + return nil + } + + refDoc := doc + if modelFilePath != filePath { + newDoc, err := loads.Spec(modelFilePath) + if err != nil { + return fmt.Errorf("load swagger %s: %+v", modelFilePath, err) + } + + refDoc = newDoc + } + resolvedModel, ok := refDoc.Spec().Definitions[modelName] + if !ok { + return fmt.Errorf("resolve ref %s from %s: model not found", ref.String(), filePath) + } + mixins.Definitions[modelName] = resolvedModel + + temp := &spec.Swagger{ + SwaggerProps: spec.SwaggerProps{ + Definitions: map[string]spec.Schema{ + modelName: resolvedModel, + }, + }, + } + + _, isVariant := resolvedModel.Extensions.GetString("x-ms-discriminator-value") + if resolvedModel.Discriminator != "" || isVariant { + for defName, def := range refDoc.Spec().Definitions { + if defName == modelName { + continue + } + for _, allOf := range def.AllOf { + if allOf.Ref.String() != "" { + allOfRefModelName, _ := modelNamePathFromRef(allOf.Ref, modelFilePath) + if modelName == allOfRefModelName { + mixins.Definitions[defName] = def + temp.Definitions[defName] = def + + break + } + } } } - mixins := make([]*spec.Swagger, 0) - for _, v := range uniquePaths { - doc, err := loads.Spec(fmt.Sprintf("%s/%s", basePath, v)) - if err != nil { - return nil, fmt.Errorf("could not load remote ref %q for mixin in %q: %+v", v, baseFile, err) + } + + if len(temp.Definitions) > 0 { + analyzer := analysis.New(temp) + for _, r := range analyzer.AllRefs() { + if err := findMixinForRef(r, modelFilePath, refDoc, mixins); err != nil { + return fmt.Errorf("find mixin for ref %q in %q: %+v", r.String(), modelFilePath, err) } - mixins = append(mixins, doc.Spec()) } - _ = analysis.Mixin(input.Spec(), mixins...) } - return input, nil + + return nil +} + +func modelNamePathFromRef(ref spec.Ref, filePath string) (modelName string, modelFilePath string) { + refUrl := ref.GetURL() + if refUrl == nil { + return "", "" + } + + modelFilePath = refUrl.Path + if modelFilePath == "" { + modelFilePath = filePath + } else { + filePath, _ := filepath.Split(filePath) + modelFilePath = filepath.Join(filePath, modelFilePath) + } + + fragments := strings.Split(refUrl.Fragment, "/") + return fragments[len(fragments)-1], modelFilePath }