From 8a0125603e6bccca0a73cecf10274505bb2daca1 Mon Sep 17 00:00:00 2001 From: John Gresty Date: Wed, 7 Aug 2024 16:07:41 +0100 Subject: [PATCH 1/6] refactor: move to recursive call --- internal/simplebuild/refs.go | 74 +++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/internal/simplebuild/refs.go b/internal/simplebuild/refs.go index 7fd7b331..3dc9d5f6 100644 --- a/internal/simplebuild/refs.go +++ b/internal/simplebuild/refs.go @@ -90,38 +90,60 @@ func (rr *refResolver) deref(ref string, value reflect.Value) error { // correctly. return fmt.Errorf("external ref %s", ref) } + field := reflect.ValueOf(rr.doc) - // Need to forward declare err so field is not shadowed in the loop - var err error - for _, segment := range path[1:] { - // Maps are a special case since the key also needs to be created. - if field.Kind() == reflect.Map { - newValue := reflect.New(field.Type().Elem().Elem()) - field.SetMapIndex(reflect.ValueOf(segment), newValue) - field = newValue.Elem() - continue - } - // else we assume we are working on a struct - field, err = getField(segment, field) - if err != nil { - return fmt.Errorf("invalid ref %s: %w", ref, err) - } + return deref(path[1:], field, value) +} + +func deref(path []string, field, value reflect.Value) error { + if len(path) == 0 { + field.Set(value.Elem()) + return nil + } + if len(path) == 1 { + fmt.Println("setting last value", path[0]) + //return trySetField(path[0], field, value) + } - // A lot of the openapi3.T fields are pointers so if this is the first - // time we have encountered an object of this type we need to create - // the container. - if field.Kind() == reflect.Map && field.IsZero() { - newValue := reflect.MakeMap(field.Type()) - field.Set(newValue) - } else if field.IsNil() { - newValue := reflect.New(field.Type().Elem()) - field.Set(newValue) + // Maps are a special case since the key also needs to be created. + if field.Kind() == reflect.Map { + newValue := reflect.New(field.Type().Elem().Elem()) + fieldName := reflect.ValueOf(path[0]) + oldVal := field.MapIndex(fieldName) + fmt.Println("setting map key", oldVal, oldVal.IsValid()) + if oldVal.IsValid() { + // Value already exists + return deref(path[1:], oldVal.Elem(), value) } + field.SetMapIndex(fieldName, newValue) + return deref(path[1:], newValue.Elem(), value) } - field.Set(value.Elem()) - return nil + // else we assume we are working on a struct + field, err := getField(path[0], field) + if err != nil { + return fmt.Errorf("invalid ref: %w", err) + } + + // A lot of the openapi3.T fields are pointers so if this is the first + // time we have encountered an object of this type we need to create + // the container. + if field.Kind() == reflect.Map && field.IsZero() { + newValue := reflect.MakeMap(field.Type()) + field.Set(newValue) + } else if field.IsNil() { + newValue := reflect.New(field.Type().Elem()) + field.Set(newValue) + } + + return deref(path[1:], field, value) } +/* + *func trySetField(name string, field, value reflect.Value) error { + * return nil + *} + */ + func getField(tag string, object reflect.Value) (reflect.Value, error) { reflectedObject := object.Type().Elem() if reflectedObject.Kind() != reflect.Struct { From 3f7b3dfc930eb5bf9112411d55d5f531c1d46208 Mon Sep 17 00:00:00 2001 From: John Gresty Date: Tue, 13 Aug 2024 13:13:07 +0100 Subject: [PATCH 2/6] fix: rename conflicting components in simplebuild If there are refs from two documents that have the same name but different definitions then we would previously overwrite the oldest one discovered with the latter one. This obviously produces wrong results. This patch renames any refs and the components they point to when it detects a conflict, however doing so mangles any previously generated documents as the components are not copied but share references between the documents. This needs to be addressed in a future version. This behaviour differs from the older build command as that detects conflicts but will error out the process and tell the user to fix the conflicts before running. --- collator.go | 20 ++--- internal/simplebuild/refs.go | 135 ++++++++++++++++++++---------- internal/simplebuild/refs_test.go | 78 +++++++++++++++++ merge.go | 2 +- 4 files changed, 182 insertions(+), 53 deletions(-) diff --git a/collator.go b/collator.go index 162f7976..29550410 100644 --- a/collator.go +++ b/collator.go @@ -150,7 +150,7 @@ func (c *Collator) mergeComponents(rv *ResourceVersion) error { inliner := NewInliner() for k, v := range rv.T.Components.Schemas { ref := "#/components/schemas/" + k - if current, ok := c.result.Components.Schemas[k]; ok && !componentsEqual(current, v) { + if current, ok := c.result.Components.Schemas[k]; ok && !ComponentsEqual(current, v) { inliner.AddRef(ref) } else { c.result.Components.Schemas[k] = v @@ -159,7 +159,7 @@ func (c *Collator) mergeComponents(rv *ResourceVersion) error { } for k, v := range rv.T.Components.Parameters { ref := "#/components/parameters/" + k - if current, ok := c.result.Components.Parameters[k]; ok && !componentsEqual(current, v) { + if current, ok := c.result.Components.Parameters[k]; ok && !ComponentsEqual(current, v) { inliner.AddRef(ref) } else { c.result.Components.Parameters[k] = v @@ -168,7 +168,7 @@ func (c *Collator) mergeComponents(rv *ResourceVersion) error { } for k, v := range rv.T.Components.Headers { ref := "#/components/headers/" + k - if current, ok := c.result.Components.Headers[k]; ok && !componentsEqual(current, v) { + if current, ok := c.result.Components.Headers[k]; ok && !ComponentsEqual(current, v) { inliner.AddRef(ref) } else { c.result.Components.Headers[k] = v @@ -177,7 +177,7 @@ func (c *Collator) mergeComponents(rv *ResourceVersion) error { } for k, v := range rv.T.Components.RequestBodies { ref := "#/components/requestBodies/" + k - if current, ok := c.result.Components.RequestBodies[k]; ok && !componentsEqual(current, v) { + if current, ok := c.result.Components.RequestBodies[k]; ok && !ComponentsEqual(current, v) { inliner.AddRef(ref) } else { c.result.Components.RequestBodies[k] = v @@ -186,7 +186,7 @@ func (c *Collator) mergeComponents(rv *ResourceVersion) error { } for k, v := range rv.T.Components.Responses { ref := "#/components/responses/" + k - if current, ok := c.result.Components.Responses[k]; ok && !componentsEqual(current, v) { + if current, ok := c.result.Components.Responses[k]; ok && !ComponentsEqual(current, v) { inliner.AddRef(ref) } else { c.result.Components.Responses[k] = v @@ -195,7 +195,7 @@ func (c *Collator) mergeComponents(rv *ResourceVersion) error { } for k, v := range rv.T.Components.SecuritySchemes { ref := "#/components/securitySchemas/" + k - if current, ok := c.result.Components.SecuritySchemes[k]; ok && !componentsEqual(current, v) { + if current, ok := c.result.Components.SecuritySchemes[k]; ok && !ComponentsEqual(current, v) { inliner.AddRef(ref) } else { c.result.Components.SecuritySchemes[k] = v @@ -204,7 +204,7 @@ func (c *Collator) mergeComponents(rv *ResourceVersion) error { } for k, v := range rv.T.Components.Examples { ref := "#/components/examples/" + k - if current, ok := c.result.Components.Examples[k]; ok && !componentsEqual(current, v) { + if current, ok := c.result.Components.Examples[k]; ok && !ComponentsEqual(current, v) { inliner.AddRef(ref) } else { c.result.Components.Examples[k] = v @@ -213,7 +213,7 @@ func (c *Collator) mergeComponents(rv *ResourceVersion) error { } for k, v := range rv.T.Components.Links { ref := "#/components/links/" + k - if current, ok := c.result.Components.Links[k]; ok && !componentsEqual(current, v) { + if current, ok := c.result.Components.Links[k]; ok && !ComponentsEqual(current, v) { inliner.AddRef(ref) } else { c.result.Components.Links[k] = v @@ -222,7 +222,7 @@ func (c *Collator) mergeComponents(rv *ResourceVersion) error { } for k, v := range rv.T.Components.Callbacks { ref := "#/components/callbacks/" + k - if current, ok := c.result.Components.Callbacks[k]; ok && !componentsEqual(current, v) { + if current, ok := c.result.Components.Callbacks[k]; ok && !ComponentsEqual(current, v) { inliner.AddRef(ref) } else { c.result.Components.Callbacks[k] = v @@ -258,7 +258,7 @@ var cmpComponents = cmp.Options{ }, cmp.Ignore()), } -func componentsEqual(x, y interface{}) bool { +func ComponentsEqual(x, y interface{}) bool { return cmp.Equal(x, y, cmpComponents) } diff --git a/internal/simplebuild/refs.go b/internal/simplebuild/refs.go index 3dc9d5f6..241cce91 100644 --- a/internal/simplebuild/refs.go +++ b/internal/simplebuild/refs.go @@ -3,10 +3,14 @@ package simplebuild import ( "fmt" "reflect" + "slices" "strings" + "unicode" "github.com/getkin/kin-openapi/openapi3" "github.com/mitchellh/reflectwalk" + + "github.com/snyk/vervet/v7" ) // Refs are an OpenAPI concept where you can define part of a spec then use a @@ -70,10 +74,20 @@ func (rr *refResolver) Struct(v reflect.Value) error { return nil } // Create a new *Ref object to avoid mutating the original - derefed := reflect.New(v.Type()) - reflect.Indirect(derefed).FieldByName("Value").Set(value) + component := reflect.New(v.Type()) + reflect.Indirect(component).FieldByName("Value").Set(value) + newRef, err := rr.deref(refLoc, component) + if err != nil { + return err + } - return rr.deref(refLoc, derefed) + if newRef != refLoc { + // TODO: other documents have references to this same object which we + // are mutating. Any previously generated document is now wrong. + ref.Set(reflect.ValueOf(newRef)) + } + + return nil } // Implements reflectwalk.StructWalker. We work on whole structs so there is @@ -82,69 +96,106 @@ func (rr *refResolver) StructField(sf reflect.StructField, v reflect.Value) erro return nil } -func (rr *refResolver) deref(ref string, value reflect.Value) error { +func (rr *refResolver) deref(ref string, value reflect.Value) (string, error) { path := strings.Split(ref, "/") if path[0] != "#" { // All refs should have been resolved to the local document when // loading so if we hit this case then we have not loaded the document // correctly. - return fmt.Errorf("external ref %s", ref) + return "", fmt.Errorf("external ref %s", ref) } field := reflect.ValueOf(rr.doc) - return deref(path[1:], field, value) + newRef, err := deref(path[1:], field, value) + if err != nil { + return "", err + } + slices.Reverse(newRef) + newRefStr := fmt.Sprintf("#/%s", strings.Join(newRef, "/")) + return newRefStr, nil } -func deref(path []string, field, value reflect.Value) error { +func deref(path []string, field, value reflect.Value) ([]string, error) { if len(path) == 0 { field.Set(value.Elem()) - return nil + return []string{}, nil } - if len(path) == 1 { - fmt.Println("setting last value", path[0]) - //return trySetField(path[0], field, value) + + newName := path[0] + nextField, err := getField(newName, field) + if err != nil { + return nil, fmt.Errorf("invalid ref: %w", err) } - // Maps are a special case since the key also needs to be created. - if field.Kind() == reflect.Map { - newValue := reflect.New(field.Type().Elem().Elem()) - fieldName := reflect.ValueOf(path[0]) - oldVal := field.MapIndex(fieldName) - fmt.Println("setting map key", oldVal, oldVal.IsValid()) - if oldVal.IsValid() { - // Value already exists - return deref(path[1:], oldVal.Elem(), value) + // Lookup if we already have a component in the same document with the same + // name, if they conflict then we need to rename the current component + if len(path) == 1 { + // Name might have changed on previous documents but previous + // collisions are no longer present. Always start from 0 to make sure + // we aren't leaving unessisary gaps. Some components are already + // numbers, eg "400" responses, in which case assume they don't + // conflict. TODO: fix that + if !unicode.IsDigit(rune(newName[0])) { + newName = strings.TrimRightFunc(newName, unicode.IsDigit) + nextField, err = getField(newName, field) + if err != nil { + return nil, fmt.Errorf("invalid ref: %w", err) + } + } + suffix := 0 + prevName := newName + // If the component is the same as the one we have already then it + // isn't a problem, we can merge them. + for !isZero(nextField) && !vervet.ComponentsEqual(nextField.Interface(), value.Interface()) { + newName = fmt.Sprintf("%s%d", prevName, suffix) + nextField, err = getField(newName, field) + if err != nil { + return nil, fmt.Errorf("renaming ref: %w", err) + } + suffix += 1 } - field.SetMapIndex(fieldName, newValue) - return deref(path[1:], newValue.Elem(), value) - } - // else we assume we are working on a struct - field, err := getField(path[0], field) - if err != nil { - return fmt.Errorf("invalid ref: %w", err) } - // A lot of the openapi3.T fields are pointers so if this is the first - // time we have encountered an object of this type we need to create - // the container. - if field.Kind() == reflect.Map && field.IsZero() { - newValue := reflect.MakeMap(field.Type()) - field.Set(newValue) - } else if field.IsNil() { - newValue := reflect.New(field.Type().Elem()) - field.Set(newValue) + // If the container for the next layer doesn't exist then we have to create + // it. + if isZero(nextField) { + if field.Kind() == reflect.Map { + nextField = reflect.New(field.Type().Elem().Elem()) + field.SetMapIndex(reflect.ValueOf(newName), nextField) + } else { + var newValue reflect.Value + if nextField.Kind() == reflect.Map { + newValue = reflect.MakeMap(nextField.Type()) + } else { + newValue = reflect.New(nextField.Type().Elem()) + } + nextField.Set(newValue) + } + } + if field.Kind() == reflect.Map { + nextField = nextField.Elem() } - return deref(path[1:], field, value) + newRef, err := deref(path[1:], nextField, value) + return append(newRef, newName), err } -/* - *func trySetField(name string, field, value reflect.Value) error { - * return nil - *} - */ +func isZero(field reflect.Value) bool { + if !field.IsValid() { + return true + } + if field.Kind() == reflect.Pointer { + return field.IsNil() + } + return field.IsZero() +} func getField(tag string, object reflect.Value) (reflect.Value, error) { + if object.Kind() == reflect.Map { + fieldName := reflect.ValueOf(tag) + return object.MapIndex(fieldName), nil + } + reflectedObject := object.Type().Elem() if reflectedObject.Kind() != reflect.Struct { return reflect.Value{}, fmt.Errorf("object is not a struct") diff --git a/internal/simplebuild/refs_test.go b/internal/simplebuild/refs_test.go index 2ebade08..fd46689f 100644 --- a/internal/simplebuild/refs_test.go +++ b/internal/simplebuild/refs_test.go @@ -1,6 +1,7 @@ package simplebuild_test import ( + "fmt" "testing" qt "github.com/frankban/quicktest" @@ -141,4 +142,81 @@ func TestResolveRefs(t *testing.T) { c.Assert(doc.Components.Parameters["foo"], qt.IsNil) }) + + c.Run("conflicting components get renamed", func(c *qt.C) { + paramA := &openapi3.Parameter{ + Name: "fooname", + } + pathA := openapi3.PathItem{ + Parameters: []*openapi3.ParameterRef{{ + Ref: "#/components/parameters/fooo", + Value: paramA, + }}, + } + paramB := &openapi3.Parameter{ + Name: "barname", + } + pathB := openapi3.PathItem{ + Parameters: []*openapi3.ParameterRef{{ + Ref: "#/components/parameters/fooo", + Value: paramB, + }}, + } + doc := openapi3.T{ + Paths: openapi3.Paths{ + "/foo": &pathA, + "/bar": &pathB, + }, + } + + rr := simplebuild.NewRefResolver(&doc) + err := rr.Resolve(pathA) + c.Assert(err, qt.IsNil) + err = rr.Resolve(pathB) + c.Assert(err, qt.IsNil) + + c.Assert(doc.Paths["/foo"].Parameters[0].Ref, qt.Not(qt.Equals), doc.Paths["/bar"].Parameters[0].Ref) + c.Assert(doc.Components.Parameters, qt.HasLen, 2) + }) + + c.Run("comparable components get merged", func(c *qt.C) { + paramA := &openapi3.Parameter{ + Name: "fooname", + } + pathA := openapi3.PathItem{ + Parameters: []*openapi3.ParameterRef{{ + Ref: "#/components/parameters/fooo", + Value: paramA, + }}, + } + paramB := &openapi3.Parameter{ + Name: "fooname", + } + pathB := openapi3.PathItem{ + Parameters: []*openapi3.ParameterRef{{ + Ref: "#/components/parameters/fooo", + Value: paramB, + }}, + } + doc := openapi3.T{ + Paths: openapi3.Paths{ + "/foo": &pathA, + "/bar": &pathB, + }, + } + + rr := simplebuild.NewRefResolver(&doc) + err := rr.Resolve(pathA) + c.Assert(err, qt.IsNil) + err = rr.Resolve(pathB) + c.Assert(err, qt.IsNil) + + out, _ := doc.MarshalJSON() + fmt.Println() + fmt.Println(string(out)) + fmt.Println() + + c.Assert(doc.Paths["/foo"].Parameters[0].Ref, qt.Equals, doc.Paths["/bar"].Parameters[0].Ref) + c.Assert(doc.Components.Parameters, qt.HasLen, 1) + }) } diff --git a/merge.go b/merge.go index 1c3e238e..20088586 100644 --- a/merge.go +++ b/merge.go @@ -142,7 +142,7 @@ func mergeComponents(dst, src *openapi3.T, replace bool) error { func mergeMap[T any](dst, src map[string]T, replace bool) error { for k, v := range src { existing, exists := dst[k] - if exists && !replace && !componentsEqual(v, existing) { + if exists && !replace && !ComponentsEqual(v, existing) { return errors.New("conflicting component: " + k) } dst[k] = v From 3181452effa0ebad5c8d4d6bc3a306faa82ac511 Mon Sep 17 00:00:00 2001 From: John Gresty Date: Thu, 15 Aug 2024 09:14:20 +0100 Subject: [PATCH 3/6] fix: prevent mangling documents when renaming components We had a problem when renaming conflicting components. Since the component object is a reference inside each document, multiple documents share the same reference so will get the changes propagated back whenever a later generated document has a component name change - which will make the older document invalid. To avoid massive memory expansion we did not want to deep copy all the component chains, which is both an expensive and complex operation. Instead we are outputting documents as they are generated instead of after they are all written. This way it does not matter that they are mangled after they are generated since the correct version is already written to disk. This is not 100% ideal as the documents in memory are mangled but as long as we don't use them after writing we should be fine. --- internal/simplebuild/build.go | 40 ++++--- internal/simplebuild/build_test.go | 21 ++-- internal/simplebuild/output.go | 175 +++++++++++++++------------- internal/simplebuild/output_test.go | 29 ++--- internal/simplebuild/overlays.go | 15 ++- internal/simplebuild/refs.go | 18 ++- 6 files changed, 165 insertions(+), 133 deletions(-) diff --git a/internal/simplebuild/build.go b/internal/simplebuild/build.go index f058b497..aee690d3 100644 --- a/internal/simplebuild/build.go +++ b/internal/simplebuild/build.go @@ -21,6 +21,11 @@ func Build(ctx context.Context, project *config.Project, startDate vervet.Versio return nil } for _, apiConfig := range project.APIs { + if apiConfig.Output == nil { + fmt.Printf("No output specified for %s, skipping\n", apiConfig.Name) + continue + } + operations, err := LoadPaths(ctx, apiConfig) if err != nil { return err @@ -28,22 +33,32 @@ func Build(ctx context.Context, project *config.Project, startDate vervet.Versio for _, op := range operations { op.Annotate() } - - docs, err := operations.Build(startDate) + docs := operations.Build(startDate) + writer, err := NewWriter(*apiConfig.Output, appendOutputFiles) if err != nil { return err } - err = docs.ApplyOverlays(ctx, apiConfig.Overlays) - if err != nil { - return err - } + for _, doc := range docs { + err := doc.ApplyOverlays(ctx, apiConfig.Overlays) + if err != nil { + return err + } - if apiConfig.Output != nil { - err = docs.WriteOutputs(*apiConfig.Output, appendOutputFiles) + refResolver := NewRefResolver(doc.Doc) + err = doc.ResolveRefs(refResolver) if err != nil { return err } + + err = writer.Write(doc) + if err != nil { + return err + } + } + err = writer.Finalize() + if err != nil { + return err } } return nil @@ -70,7 +85,7 @@ type VersionedDoc struct { } type DocSet []VersionedDoc -func (ops Operations) Build(startVersion vervet.Version) (DocSet, error) { +func (ops Operations) Build(startVersion vervet.Version) DocSet { versionDates := ops.VersionDates() versionDates = filterVersionByStartDate(versionDates, startVersion.Date) output := make(DocSet, len(versionDates)) @@ -79,20 +94,15 @@ func (ops Operations) Build(startVersion vervet.Version) (DocSet, error) { Doc: &openapi3.T{}, VersionDate: versionDate, } - refResolver := NewRefResolver(output[idx].Doc) for path, spec := range ops { op := spec.GetLatest(versionDate) if op == nil { continue } output[idx].Doc.AddOperation(path.Path, path.Method, op) - err := refResolver.Resolve(op) - if err != nil { - return nil, err - } } } - return output, nil + return output } func filterVersionByStartDate(dates []time.Time, startDate time.Time) []time.Time { diff --git a/internal/simplebuild/build_test.go b/internal/simplebuild/build_test.go index 6f68031f..cb579ecc 100644 --- a/internal/simplebuild/build_test.go +++ b/internal/simplebuild/build_test.go @@ -104,8 +104,7 @@ func TestBuild(t *testing.T) { ResourceName: "foo", }}, } - output, err := ops.Build(vervet.MustParseVersion("2024-01-01")) - c.Assert(err, qt.IsNil) + output := ops.Build(vervet.MustParseVersion("2024-01-01")) c.Assert(output[0].VersionDate, qt.Equals, time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)) c.Assert(output[0].Doc.Paths["/foo"].Get, qt.IsNotNil) }) @@ -143,8 +142,7 @@ func TestBuild(t *testing.T) { ResourceName: "bar", }}, } - output, err := ops.Build(vervet.MustParseVersion("2024-01-01")) - c.Assert(err, qt.IsNil) + output := ops.Build(vervet.MustParseVersion("2024-01-01")) c.Assert(output[0].VersionDate, qt.Equals, version.Date) c.Assert(output[0].Doc.Paths["/foo"].Get, qt.Equals, getFoo) c.Assert(output[0].Doc.Paths["/foo"].Post, qt.Equals, postFoo) @@ -179,8 +177,7 @@ func TestBuild(t *testing.T) { ResourceName: "bar", }}, } - output, err := ops.Build(vervet.MustParseVersion("2024-01-01")) - c.Assert(err, qt.IsNil) + output := ops.Build(vervet.MustParseVersion("2024-01-01")) inputVersions := make([]time.Time, len(versions)) for idx, in := range versions { @@ -232,8 +229,7 @@ func TestBuild(t *testing.T) { ResourceName: "bar", }}, } - output, err := ops.Build(vervet.MustParseVersion("2024-01-01")) - c.Assert(err, qt.IsNil) + output := ops.Build(vervet.MustParseVersion("2024-01-01")) slices.SortFunc(output, compareDocs) @@ -283,8 +279,7 @@ func TestBuild(t *testing.T) { ResourceName: "bar", }}, } - output, err := ops.Build(vervet.MustParseVersion("2024-01-01")) - c.Assert(err, qt.IsNil) + output := ops.Build(vervet.MustParseVersion("2024-01-01")) slices.SortFunc(output, compareDocs) @@ -331,8 +326,7 @@ func TestBuild(t *testing.T) { ResourceName: "bar", }}, } - output, err := ops.Build(vervet.MustParseVersion("2024-01-02")) - c.Assert(err, qt.IsNil) + output := ops.Build(vervet.MustParseVersion("2024-01-02")) slices.SortFunc(output, compareDocs) @@ -382,8 +376,7 @@ func TestBuild(t *testing.T) { ResourceName: "bar", }}, } - output, err := ops.Build(vervet.MustParseVersion("2024-01-01")) - c.Assert(err, qt.IsNil) + output := ops.Build(vervet.MustParseVersion("2024-01-01")) slices.SortFunc(output, compareDocs) diff --git a/internal/simplebuild/output.go b/internal/simplebuild/output.go index 00bb2e8e..fa622306 100644 --- a/internal/simplebuild/output.go +++ b/internal/simplebuild/output.go @@ -17,110 +17,125 @@ import ( "github.com/snyk/vervet/v7/internal/files" ) -// Some services have a need to write specs to multiple destinations. This -// tends to happen in Typescript services in which we want to write specs to -// two places: -// - src/** for committing into git and ingesting into Backstage -// - dist/** for runtime module access to compiled specs. -// -// To maintain backwards compatibility we still allow a single path in the -// config file then normalise that here to an array. -func getOutputPaths(cfg config.Output) []string { - paths := cfg.Paths - if len(paths) == 0 && cfg.Path != "" { - paths = []string{cfg.Path} - } - return paths +type DocWriter struct { + cfg config.Output + paths []string + versionSpecFiles []string } -// WriteOutputs writes compiled specs to all directories specified by the given -// api config. Removes any existing builds if they are present. -func (docs DocSet) WriteOutputs(cfg config.Output, appendOutputFiles bool) error { +// NewWriter initialises any output paths, removing existing files and +// directories if they are present. +func NewWriter(cfg config.Output, appendOutputFiles bool) (*DocWriter, error) { paths := getOutputPaths(cfg) + toClear := paths + if appendOutputFiles { + // We treat the first path as the source of truth and copy the whole + // directory to the other paths in Finalize. + toClear = toClear[1:] + } - if !appendOutputFiles { - for _, dir := range paths { - err := os.RemoveAll(dir) - if err != nil { - return fmt.Errorf("clear output directory: %w", err) - } + for _, dir := range toClear { + err := os.RemoveAll(dir) + if err != nil { + return nil, fmt.Errorf("clear output directory: %w", err) } } - - err := docs.Write(paths[0], appendOutputFiles) + err := os.MkdirAll(paths[0], 0777) if err != nil { - return fmt.Errorf("write output files: %w", err) + return nil, fmt.Errorf("make output directory: %w", err) } - for _, dir := range paths[1:] { - err := files.CopyDir(dir, paths[0], true) - if err != nil { - return fmt.Errorf("copy outputs: %w", err) - } + versionSpecFiles, err := getExisingSpecFiles(paths[0]) + if err != nil { + return nil, fmt.Errorf("list existing files: %w", err) } - return nil + return &DocWriter{ + cfg: cfg, + paths: paths, + versionSpecFiles: versionSpecFiles, + }, nil } // Write writes compiled specs to a single directory in YAML and JSON formats. -// Unlike WriteOutputs this function assumes the destination directory does not -// already exist. -func (docs DocSet) Write(dir string, appendOutputFiles bool) error { - err := os.MkdirAll(dir, 0777) +// Call Finalize after to populate other directories. +func (out *DocWriter) Write(doc VersionedDoc) error { + // We write to the first directory then copy the entire directory + // afterwards + dir := out.paths[0] + + versionDir := path.Join(dir, doc.VersionDate.Format(time.DateOnly)) + err := os.MkdirAll(versionDir, 0755) if err != nil { - return err + return fmt.Errorf("make output directory: %w", err) + } + + jsonBuf, err := vervet.ToSpecJSON(doc.Doc) + if err != nil { + return fmt.Errorf("serialise spec to json: %w", err) + } + jsonSpecPath := path.Join(versionDir, "spec.json") + jsonEmbedPath, err := filepath.Rel(dir, jsonSpecPath) + if err != nil { + return fmt.Errorf("get relative output path: %w", err) } - existingFiles, err := getExisingSpecFiles(dir) + out.versionSpecFiles = append(out.versionSpecFiles, jsonEmbedPath) + err = os.WriteFile(jsonSpecPath, jsonBuf, 0644) if err != nil { - return fmt.Errorf("list existing files: %w", err) + return fmt.Errorf("write json file: %w", err) } + fmt.Println(jsonSpecPath) - versionSpecFiles := make([]string, 0, len(existingFiles)+len(docs)*2) - versionSpecFiles = append(versionSpecFiles, existingFiles...) - for _, doc := range docs { - versionDir := path.Join(dir, doc.VersionDate.Format(time.DateOnly)) - err = os.MkdirAll(versionDir, 0755) - if err != nil { - return fmt.Errorf("make output directory: %w", err) - } + yamlBuf, err := yaml.JSONToYAML(jsonBuf) + if err != nil { + return fmt.Errorf("convert spec to yaml: %w", err) + } + yamlBuf, err = vervet.WithGeneratedComment(yamlBuf) + if err != nil { + return fmt.Errorf("prepend yaml comment: %w", err) + } + yamlSpecPath := path.Join(versionDir, "spec.yaml") + yamlEmbedPath, err := filepath.Rel(dir, yamlSpecPath) + if err != nil { + return fmt.Errorf("get relative output path: %w", err) + } + out.versionSpecFiles = append(out.versionSpecFiles, yamlEmbedPath) + err = os.WriteFile(yamlSpecPath, yamlBuf, 0644) + if err != nil { + return fmt.Errorf("write yaml file: %w", err) + } + fmt.Println(yamlSpecPath) + return nil +} - jsonBuf, err := vervet.ToSpecJSON(doc.Doc) - if err != nil { - return fmt.Errorf("serialise spec to json: %w", err) - } - jsonSpecPath := path.Join(versionDir, "spec.json") - jsonEmbedPath, err := filepath.Rel(dir, jsonSpecPath) - if err != nil { - return fmt.Errorf("get relative output path: %w", err) - } - versionSpecFiles = append(versionSpecFiles, jsonEmbedPath) - err = os.WriteFile(jsonSpecPath, jsonBuf, 0644) +func (out *DocWriter) Finalize() error { + err := writeEmbedGo(out.paths[0], out.versionSpecFiles) + if err != nil { + return err + } + for _, dir := range out.paths[1:] { + err := files.CopyDir(dir, out.paths[0], true) if err != nil { - return fmt.Errorf("write json file: %w", err) + return fmt.Errorf("copy outputs: %w", err) } - fmt.Println(jsonSpecPath) + } + return nil +} - yamlBuf, err := yaml.JSONToYAML(jsonBuf) - if err != nil { - return fmt.Errorf("convert spec to yaml: %w", err) - } - yamlBuf, err = vervet.WithGeneratedComment(yamlBuf) - if err != nil { - return fmt.Errorf("prepend yaml comment: %w", err) - } - yamlSpecPath := path.Join(versionDir, "spec.yaml") - yamlEmbedPath, err := filepath.Rel(dir, yamlSpecPath) - if err != nil { - return fmt.Errorf("get relative output path: %w", err) - } - versionSpecFiles = append(versionSpecFiles, yamlEmbedPath) - err = os.WriteFile(yamlSpecPath, yamlBuf, 0644) - if err != nil { - return fmt.Errorf("write yaml file: %w", err) - } - fmt.Println(yamlSpecPath) +// Some services have a need to write specs to multiple destinations. This +// tends to happen in Typescript services in which we want to write specs to +// two places: +// - src/** for committing into git and ingesting into Backstage +// - dist/** for runtime module access to compiled specs. +// +// To maintain backwards compatibility we still allow a single path in the +// config file then normalise that here to an array. +func getOutputPaths(cfg config.Output) []string { + paths := cfg.Paths + if len(paths) == 0 && cfg.Path != "" { + paths = []string{cfg.Path} } - return writeEmbedGo(dir, versionSpecFiles) + return paths } func getExisingSpecFiles(dir string) ([]string, error) { diff --git a/internal/simplebuild/output_test.go b/internal/simplebuild/output_test.go index 9afef9b8..faa39ffd 100644 --- a/internal/simplebuild/output_test.go +++ b/internal/simplebuild/output_test.go @@ -25,12 +25,11 @@ func TestDocSet_WriteOutputs(t *testing.T) { appendOutputFiles bool } tests := []struct { - name string - docs DocSet - args args - wantErr bool - assert func(*testing.T, args) - setup func(*testing.T, args) + name string + docs DocSet + args args + assert func(*testing.T, args) + setup func(*testing.T, args) }{ { name: "write the doc sets to outputs", @@ -45,7 +44,6 @@ func TestDocSet_WriteOutputs(t *testing.T) { Doc: testDoc, }, }, - wantErr: false, assert: func(t *testing.T, args args) { t.Helper() files, err := filepath.Glob(filepath.Join(args.cfg.Path, "*")) @@ -70,7 +68,6 @@ func TestDocSet_WriteOutputs(t *testing.T) { Doc: testDoc, }, }, - wantErr: false, setup: func(t *testing.T, args args) { t.Helper() err = os.WriteFile(path.Join(args.cfg.Path, "existing-file"), []byte("existing"), 0644) @@ -101,7 +98,6 @@ func TestDocSet_WriteOutputs(t *testing.T) { Doc: testDoc, }, }, - wantErr: false, setup: func(t *testing.T, args args) { t.Helper() err = os.WriteFile(path.Join(args.cfg.Path, "2024-02-01"), []byte("existing"), 0644) @@ -130,12 +126,17 @@ func TestDocSet_WriteOutputs(t *testing.T) { if tt.setup != nil { tt.setup(t, tt.args) } - if err := tt.docs.WriteOutputs(tt.args.cfg, tt.args.appendOutputFiles); (err != nil) != tt.wantErr { - t.Errorf("WriteOutputs() error = %v, wantErr %v", err, tt.wantErr) - } - if tt.assert != nil { - tt.assert(t, tt.args) + + writer, err := NewWriter(tt.args.cfg, tt.args.appendOutputFiles) + c.Assert(err, qt.IsNil) + for _, doc := range tt.docs { + err = writer.Write(doc) + c.Assert(err, qt.IsNil) } + err = writer.Finalize() + c.Assert(err, qt.IsNil) + + tt.assert(t, tt.args) }) } } diff --git a/internal/simplebuild/overlays.go b/internal/simplebuild/overlays.go index 23d6d51d..b9e13ee4 100644 --- a/internal/simplebuild/overlays.go +++ b/internal/simplebuild/overlays.go @@ -11,18 +11,17 @@ import ( "github.com/snyk/vervet/v7/config" ) -func (docs DocSet) ApplyOverlays(ctx context.Context, cfgs []*config.Overlay) error { +func (doc VersionedDoc) ApplyOverlays(ctx context.Context, cfgs []*config.Overlay) error { + // TODO: cache overlays, err := loadOverlays(ctx, cfgs) if err != nil { return fmt.Errorf("load overlays: %w", err) } - for _, doc := range docs { - for _, overlay := range overlays { - // NB: Will overwrite any existing definitions without warning. - err := vervet.Merge(doc.Doc, overlay, true) - if err != nil { - return fmt.Errorf("apply overlay: %w", err) - } + for _, overlay := range overlays { + // NB: Will overwrite any existing definitions without warning. + err := vervet.Merge(doc.Doc, overlay, true) + if err != nil { + return fmt.Errorf("apply overlay: %w", err) } } diff --git a/internal/simplebuild/refs.go b/internal/simplebuild/refs.go index 241cce91..53e47e26 100644 --- a/internal/simplebuild/refs.go +++ b/internal/simplebuild/refs.go @@ -51,6 +51,7 @@ type refResolver struct { doc *openapi3.T } +// TODO: Clean up public API. func NewRefResolver(doc *openapi3.T) refResolver { return refResolver{doc: doc} } @@ -59,6 +60,17 @@ func (rr *refResolver) Resolve(from any) error { return reflectwalk.Walk(from, rr) } +// ResolveRefs recursively finds all ref objects in the current documents paths +// and makes sure they are valid by copying the referenced component to the +// documents components section. +// +// WARNING: this will mutate references so if references are shared between +// documents make sure that any other documents are serialised before resolving +// refs. This method only ensures the current document is correct. +func (doc VersionedDoc) ResolveRefs(rr refResolver) error { + return rr.Resolve(doc.Doc.Paths) +} + // Implements reflectwalk.StructWalker. This function is called for every // struct found when walking. func (rr *refResolver) Struct(v reflect.Value) error { @@ -82,8 +94,10 @@ func (rr *refResolver) Struct(v reflect.Value) error { } if newRef != refLoc { - // TODO: other documents have references to this same object which we - // are mutating. Any previously generated document is now wrong. + // WARNING: this mutates other documents that have references to this + // component. Any existing generated document is now invalid. Ensure a + // document is serialised before resolving references on a different + // document which could share references. ref.Set(reflect.ValueOf(newRef)) } From ecb349d215d964da20cfd5a6900e75d22702d681 Mon Sep 17 00:00:00 2001 From: John Gresty Date: Thu, 15 Aug 2024 10:52:47 +0100 Subject: [PATCH 4/6] chore: simplify refresolver public api The ref resolver now can only work on an entire document which makes using it a bit easier. In addition keeping a map of renamed components makes the reset name logic less brittle. --- internal/simplebuild/build.go | 4 +- internal/simplebuild/refs.go | 39 +++++++------- internal/simplebuild/refs_test.go | 84 ++++--------------------------- 3 files changed, 31 insertions(+), 96 deletions(-) diff --git a/internal/simplebuild/build.go b/internal/simplebuild/build.go index aee690d3..8b94e1d0 100644 --- a/internal/simplebuild/build.go +++ b/internal/simplebuild/build.go @@ -45,8 +45,8 @@ func Build(ctx context.Context, project *config.Project, startDate vervet.Versio return err } - refResolver := NewRefResolver(doc.Doc) - err = doc.ResolveRefs(refResolver) + refResolver := NewRefResolver() + err = refResolver.ResolveRefs(doc.Doc) if err != nil { return err } diff --git a/internal/simplebuild/refs.go b/internal/simplebuild/refs.go index 53e47e26..92d50e93 100644 --- a/internal/simplebuild/refs.go +++ b/internal/simplebuild/refs.go @@ -5,7 +5,6 @@ import ( "reflect" "slices" "strings" - "unicode" "github.com/getkin/kin-openapi/openapi3" "github.com/mitchellh/reflectwalk" @@ -48,16 +47,12 @@ import ( // This class walks a given object and recursively copy any refs it finds back // into the document at the path they are referenced from. type refResolver struct { - doc *openapi3.T + doc *openapi3.T + renames map[string]string } -// TODO: Clean up public API. -func NewRefResolver(doc *openapi3.T) refResolver { - return refResolver{doc: doc} -} - -func (rr *refResolver) Resolve(from any) error { - return reflectwalk.Walk(from, rr) +func NewRefResolver() refResolver { + return refResolver{renames: make(map[string]string)} } // ResolveRefs recursively finds all ref objects in the current documents paths @@ -67,8 +62,12 @@ func (rr *refResolver) Resolve(from any) error { // WARNING: this will mutate references so if references are shared between // documents make sure that any other documents are serialised before resolving // refs. This method only ensures the current document is correct. -func (doc VersionedDoc) ResolveRefs(rr refResolver) error { - return rr.Resolve(doc.Doc.Paths) +func (rr *refResolver) ResolveRefs(doc *openapi3.T) error { + // Refs use a full path eg #/components/schemas/..., to avoid having a + // special case at the top level we pass the entire document and trust the + // refs to not reference parts of the document they shouldn't. + rr.doc = doc + return reflectwalk.Walk(doc.Paths, rr) } // Implements reflectwalk.StructWalker. This function is called for every @@ -99,6 +98,7 @@ func (rr *refResolver) Struct(v reflect.Value) error { // document is serialised before resolving references on a different // document which could share references. ref.Set(reflect.ValueOf(newRef)) + rr.renames[newRef] = refLoc } return nil @@ -120,7 +120,7 @@ func (rr *refResolver) deref(ref string, value reflect.Value) (string, error) { } field := reflect.ValueOf(rr.doc) - newRef, err := deref(path[1:], field, value) + newRef, err := deref(path[1:], field, value, rr.renames) if err != nil { return "", err } @@ -129,7 +129,7 @@ func (rr *refResolver) deref(ref string, value reflect.Value) (string, error) { return newRefStr, nil } -func deref(path []string, field, value reflect.Value) ([]string, error) { +func deref(path []string, field, value reflect.Value, renames map[string]string) ([]string, error) { if len(path) == 0 { field.Set(value.Elem()) return []string{}, nil @@ -145,12 +145,11 @@ func deref(path []string, field, value reflect.Value) ([]string, error) { // name, if they conflict then we need to rename the current component if len(path) == 1 { // Name might have changed on previous documents but previous - // collisions are no longer present. Always start from 0 to make sure - // we aren't leaving unessisary gaps. Some components are already - // numbers, eg "400" responses, in which case assume they don't - // conflict. TODO: fix that - if !unicode.IsDigit(rune(newName[0])) { - newName = strings.TrimRightFunc(newName, unicode.IsDigit) + // collisions are no longer present. Always start from the original + // name to make sure we aren't leaving unessisary gaps. + originalName, ok := renames[newName] + if ok { + newName = originalName nextField, err = getField(newName, field) if err != nil { return nil, fmt.Errorf("invalid ref: %w", err) @@ -190,7 +189,7 @@ func deref(path []string, field, value reflect.Value) ([]string, error) { nextField = nextField.Elem() } - newRef, err := deref(path[1:], nextField, value) + newRef, err := deref(path[1:], nextField, value, renames) return append(newRef, newName), err } diff --git a/internal/simplebuild/refs_test.go b/internal/simplebuild/refs_test.go index fd46689f..75bde2af 100644 --- a/internal/simplebuild/refs_test.go +++ b/internal/simplebuild/refs_test.go @@ -27,73 +27,13 @@ func TestResolveRefs(t *testing.T) { }, } - rr := simplebuild.NewRefResolver(&doc) - err := rr.Resolve(path) + rr := simplebuild.NewRefResolver() + err := rr.ResolveRefs(&doc) c.Assert(err, qt.IsNil) c.Assert(doc.Components.Parameters["foo"].Value, qt.Equals, param) }) - c.Run("ignores refs on other parts of the doc", func(c *qt.C) { - param := &openapi3.Parameter{} - pathA := openapi3.PathItem{ - Parameters: []*openapi3.ParameterRef{{ - Ref: "#/components/parameters/foo", - Value: param, - }}, - } - pathB := openapi3.PathItem{ - Parameters: []*openapi3.ParameterRef{{ - Ref: "#/components/parameters/bar", - Value: param, - }}, - } - doc := openapi3.T{ - Paths: openapi3.Paths{ - "/foo": &pathA, - "/bar": &pathB, - }, - } - - rr := simplebuild.NewRefResolver(&doc) - err := rr.Resolve(pathA) - c.Assert(err, qt.IsNil) - - c.Assert(doc.Components.Parameters["bar"], qt.IsNil) - }) - - c.Run("merges refs from successive calls", func(c *qt.C) { - paramA := &openapi3.Parameter{} - pathA := openapi3.PathItem{ - Parameters: []*openapi3.ParameterRef{{ - Ref: "#/components/parameters/foo", - Value: paramA, - }}, - } - paramB := &openapi3.Parameter{} - pathB := openapi3.PathItem{ - Parameters: []*openapi3.ParameterRef{{ - Ref: "#/components/parameters/bar", - Value: paramB, - }}, - } - doc := openapi3.T{ - Paths: openapi3.Paths{ - "/foo": &pathA, - "/bar": &pathB, - }, - } - - rr := simplebuild.NewRefResolver(&doc) - err := rr.Resolve(pathA) - c.Assert(err, qt.IsNil) - err = rr.Resolve(pathB) - c.Assert(err, qt.IsNil) - - c.Assert(doc.Components.Parameters["foo"].Value, qt.Equals, paramA) - c.Assert(doc.Components.Parameters["bar"].Value, qt.Equals, paramB) - }) - c.Run("recursively resolves components", func(c *qt.C) { schema := &openapi3.Schema{} param := &openapi3.Parameter{ @@ -114,8 +54,8 @@ func TestResolveRefs(t *testing.T) { }, } - rr := simplebuild.NewRefResolver(&doc) - err := rr.Resolve(path) + rr := simplebuild.NewRefResolver() + err := rr.ResolveRefs(&doc) c.Assert(err, qt.IsNil) c.Assert(doc.Components.Parameters["foo"].Value, qt.Equals, param) @@ -136,8 +76,8 @@ func TestResolveRefs(t *testing.T) { }, } - rr := simplebuild.NewRefResolver(&doc) - err := rr.Resolve(path) + rr := simplebuild.NewRefResolver() + err := rr.ResolveRefs(&doc) c.Assert(err, qt.IsNil) c.Assert(doc.Components.Parameters["foo"], qt.IsNil) @@ -169,10 +109,8 @@ func TestResolveRefs(t *testing.T) { }, } - rr := simplebuild.NewRefResolver(&doc) - err := rr.Resolve(pathA) - c.Assert(err, qt.IsNil) - err = rr.Resolve(pathB) + rr := simplebuild.NewRefResolver() + err := rr.ResolveRefs(&doc) c.Assert(err, qt.IsNil) c.Assert(doc.Paths["/foo"].Parameters[0].Ref, qt.Not(qt.Equals), doc.Paths["/bar"].Parameters[0].Ref) @@ -205,10 +143,8 @@ func TestResolveRefs(t *testing.T) { }, } - rr := simplebuild.NewRefResolver(&doc) - err := rr.Resolve(pathA) - c.Assert(err, qt.IsNil) - err = rr.Resolve(pathB) + rr := simplebuild.NewRefResolver() + err := rr.ResolveRefs(&doc) c.Assert(err, qt.IsNil) out, _ := doc.MarshalJSON() From 1acd209fa61c03667d2471a23c8d39e7f5f4c8ce Mon Sep 17 00:00:00 2001 From: John Gresty Date: Thu, 15 Aug 2024 14:11:55 +0100 Subject: [PATCH 5/6] chore: add tilde to renamed components Some components are just named after numbers, eg "400" for a HTTP status 400 response. These look weird if we just append another number so adding a delimiter makes it a bit more clear it is a variant. --- internal/simplebuild/refs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/simplebuild/refs.go b/internal/simplebuild/refs.go index 92d50e93..4c1302b3 100644 --- a/internal/simplebuild/refs.go +++ b/internal/simplebuild/refs.go @@ -160,7 +160,7 @@ func deref(path []string, field, value reflect.Value, renames map[string]string) // If the component is the same as the one we have already then it // isn't a problem, we can merge them. for !isZero(nextField) && !vervet.ComponentsEqual(nextField.Interface(), value.Interface()) { - newName = fmt.Sprintf("%s%d", prevName, suffix) + newName = fmt.Sprintf("%s~%d", prevName, suffix) nextField, err = getField(newName, field) if err != nil { return nil, fmt.Errorf("renaming ref: %w", err) From ab191513495f0ed7109b58cc5f781c604262ad31 Mon Sep 17 00:00:00 2001 From: Jatin Naik Date: Fri, 16 Aug 2024 17:11:16 +0100 Subject: [PATCH 6/6] fix: rename deref in ref resolver to be intention revealing --- internal/simplebuild/refs.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/simplebuild/refs.go b/internal/simplebuild/refs.go index 35ff7cd9..63fcfe12 100644 --- a/internal/simplebuild/refs.go +++ b/internal/simplebuild/refs.go @@ -55,7 +55,7 @@ func NewRefResolver() refResolver { return refResolver{renames: make(map[string]string)} } -func (rr *refResolver) deRef(orignalRef string, component any) (string, error) { +func (rr *refResolver) copyToComponents(orignalRef string, component any) (string, error) { newRef, err := rr.deref(orignalRef, reflect.ValueOf(component)) if err != nil { return "", err @@ -74,7 +74,7 @@ func (rr *refResolver) ProcessCallbackRef(ref *openapi3.CallbackRef) error { Value: ref.Value, } var err error - ref.Ref, err = rr.deRef(ref.Ref, component) + ref.Ref, err = rr.copyToComponents(ref.Ref, component) return err } @@ -86,7 +86,7 @@ func (rr *refResolver) ProcessExampleRef(ref *openapi3.ExampleRef) error { Value: ref.Value, } var err error - ref.Ref, err = rr.deRef(ref.Ref, component) + ref.Ref, err = rr.copyToComponents(ref.Ref, component) return err } @@ -98,7 +98,7 @@ func (rr *refResolver) ProcessHeaderRef(ref *openapi3.HeaderRef) error { Value: ref.Value, } var err error - ref.Ref, err = rr.deRef(ref.Ref, component) + ref.Ref, err = rr.copyToComponents(ref.Ref, component) return err } @@ -110,7 +110,7 @@ func (rr *refResolver) ProcessLinkRef(ref *openapi3.LinkRef) error { Value: ref.Value, } var err error - ref.Ref, err = rr.deRef(ref.Ref, component) + ref.Ref, err = rr.copyToComponents(ref.Ref, component) return err } @@ -122,7 +122,7 @@ func (rr *refResolver) ProcessParameterRef(ref *openapi3.ParameterRef) error { Value: ref.Value, } var err error - ref.Ref, err = rr.deRef(ref.Ref, component) + ref.Ref, err = rr.copyToComponents(ref.Ref, component) return err } @@ -134,7 +134,7 @@ func (rr *refResolver) ProcessRequestBodyRef(ref *openapi3.RequestBodyRef) error Value: ref.Value, } var err error - ref.Ref, err = rr.deRef(ref.Ref, component) + ref.Ref, err = rr.copyToComponents(ref.Ref, component) return err } @@ -146,7 +146,7 @@ func (rr *refResolver) ProcessResponseRef(ref *openapi3.ResponseRef) error { Value: ref.Value, } var err error - ref.Ref, err = rr.deRef(ref.Ref, component) + ref.Ref, err = rr.copyToComponents(ref.Ref, component) return err } @@ -158,7 +158,7 @@ func (rr *refResolver) ProcessSchemaRef(ref *openapi3.SchemaRef) error { Value: ref.Value, } var err error - ref.Ref, err = rr.deRef(ref.Ref, component) + ref.Ref, err = rr.copyToComponents(ref.Ref, component) return err } @@ -170,7 +170,7 @@ func (rr *refResolver) ProcessSecuritySchemeRef(ref *openapi3.SecuritySchemeRef) Value: ref.Value, } var err error - ref.Ref, err = rr.deRef(ref.Ref, component) + ref.Ref, err = rr.copyToComponents(ref.Ref, component) return err }