From 410b76f0bfe0c0d0487242a3d56a7121c04d92f6 Mon Sep 17 00:00:00 2001 From: John Hosie Date: Mon, 9 Feb 2026 18:07:40 +0000 Subject: [PATCH 1/2] add support for oneof tags Signed-off-by: John Hosie --- pkg/ffapi/openapi3.go | 161 ++++++++++++++++++- pkg/ffapi/openapi3_test.go | 316 +++++++++++++++++++++++++++++++++++++ 2 files changed, 473 insertions(+), 4 deletions(-) diff --git a/pkg/ffapi/openapi3.go b/pkg/ffapi/openapi3.go index 3e9344e..ad9af1b 100644 --- a/pkg/ffapi/openapi3.go +++ b/pkg/ffapi/openapi3.go @@ -26,6 +26,7 @@ import ( "sort" "strconv" "strings" + "sync" "time" "github.com/getkin/kin-openapi/openapi3" @@ -48,6 +49,9 @@ type SwaggerGenOptions struct { // this is useful to ensure that all fields are documented PanicOnMissingDescription bool + // if set to true, the generator will panic if a oneOf is unresolved + PanicOnUnresolvedOneOfs bool + SupportFieldRedaction bool DefaultRequestTimeout time.Duration @@ -76,6 +80,36 @@ type BaseURLVariable struct { var customRegexRemoval = regexp.MustCompile(`{(\w+)\:[^}]+}`) +var registeredSchemaTypes = map[string]interface{}{} +var registeredSchemaTypesLock sync.RWMutex + +// RegisterSchemaType registers a schema name to a Go value for oneOf resolution. +func RegisterSchemaType(name string, value interface{}) { + if name == "" || value == nil { + return + } + registeredSchemaTypesLock.Lock() + defer registeredSchemaTypesLock.Unlock() + registeredSchemaTypes[name] = value +} + +// UnregisterSchemaType removes a registered schema name. +func UnregisterSchemaType(name string) { + if name == "" { + return + } + registeredSchemaTypesLock.Lock() + defer registeredSchemaTypesLock.Unlock() + delete(registeredSchemaTypes, name) +} + +func getRegisteredSchemaType(name string) (interface{}, bool) { + registeredSchemaTypesLock.RLock() + defer registeredSchemaTypesLock.RUnlock() + v, ok := registeredSchemaTypes[name] + return v, ok +} + type SwaggerGen struct { options *SwaggerGenOptions } @@ -167,7 +201,7 @@ func (sg *SwaggerGen) ffInputTagHandler(ctx context.Context, route *Route, name if sg.isTrue(tag.Get("ffexcludeinput")) { return &openapi3gen.ExcludeSchemaSentinel{} } - if taggedRoutes, ok := tag.Lookup("ffexcludeinput"); ok { + if taggedRoutes, ok := tag.Lookup("ffexcludeinput"); ok && route != nil { for _, r := range strings.Split(taggedRoutes, ",") { if route.Name == r { return &openapi3gen.ExcludeSchemaSentinel{} @@ -191,7 +225,7 @@ func (sg *SwaggerGen) ffTagHandler(ctx context.Context, route *Route, name strin if sg.isTrue(tag.Get("ffexclude")) { return &openapi3gen.ExcludeSchemaSentinel{} } - if taggedRoutes, ok := tag.Lookup("ffexclude"); ok { + if taggedRoutes, ok := tag.Lookup("ffexclude"); ok && route != nil { for _, r := range strings.Split(taggedRoutes, ",") { if route.Name == r { return &openapi3gen.ExcludeSchemaSentinel{} @@ -203,11 +237,19 @@ func (sg *SwaggerGen) ffTagHandler(ctx context.Context, route *Route, name strin key := fmt.Sprintf("%s.%s", structName, name) description := i18n.Expand(ctx, i18n.MessageKey(key)) if description == key && sg.options.PanicOnMissingDescription { - return i18n.NewError(ctx, i18n.MsgFieldDescriptionMissing, key, route.Name) + routeName := "" + if route != nil { + routeName = route.Name + } + return i18n.NewError(ctx, i18n.MsgFieldDescriptionMissing, key, routeName) } schema.Description = description } else if sg.options.PanicOnMissingDescription { - return i18n.NewError(ctx, i18n.MsgFFStructTagMissing, name, route.Name) + routeName := "" + if route != nil { + routeName = route.Name + } + return i18n.NewError(ctx, i18n.MsgFFStructTagMissing, name, routeName) } } return nil @@ -237,11 +279,119 @@ func (sg *SwaggerGen) addCustomType(t reflect.Type, schema *openapi3.Schema) { } } +func splitCSV(value string) []string { + if value == "" { + return nil + } + parts := strings.Split(value, ",") + res := make([]string, 0, len(parts)) + for _, p := range parts { + p = strings.TrimSpace(p) + if p != "" { + res = append(res, p) + } + } + return res +} + +func componentNameFromRef(value string) string { + value = strings.TrimSpace(value) + if value == "" { + return "" + } + if strings.HasPrefix(value, "#/components/schemas/") { + return strings.TrimPrefix(value, "#/components/schemas/") + } + if strings.Contains(value, "://") || strings.Contains(value, "#/") { + // If the value contains :// (external URL) or #/ (other internal fragments like #/paths/...), it’s not a schema component, so return "" to prevent misinterpreting the ref. + return "" + } + return value +} + +func parseOneOfNames(value string) []string { + items := splitCSV(value) + out := make([]string, 0, len(items)) + for _, item := range items { + name := componentNameFromRef(item) + if name != "" { + out = append(out, name) + } + } + return out +} + +func (sg *SwaggerGen) applyOneOfTag(ctx context.Context, tag reflect.StructTag, componentSchemas openapi3.Schemas, schema *openapi3.Schema) error { + value := tag.Get("ffoneof") + if value == "" || schema == nil { + return nil + } + names := parseOneOfNames(value) + if len(names) == 0 { + return nil + } + + for _, name := range names { + registeredSchemaType, ok := getRegisteredSchemaType(name) + if !ok && sg.options.PanicOnUnresolvedOneOfs { + return fmt.Errorf("ffoneof references unregistered schema: %s", name) + } + if ok { + sg.addRegisteredSchemaType(ctx, componentSchemas, name, registeredSchemaType) + } + } + if len(schema.OneOf) == 0 { + refs := make([]*openapi3.SchemaRef, 0, len(names)) + for _, name := range names { + refs = append(refs, &openapi3.SchemaRef{Ref: "#/components/schemas/" + name}) + } + schema.OneOf = refs + } + return nil +} + +func (sg *SwaggerGen) ensureComponentAlias(components openapi3.Schemas, name string, schemaRef *openapi3.SchemaRef) { + // makes sure a schema exists in components.schemas under a given name, without overwriting anything + if name == "" || components == nil || schemaRef == nil { + return + } + if _, ok := components[name]; ok { + return + } + if schemaRef.Ref != "" { + components[name] = &openapi3.SchemaRef{Ref: schemaRef.Ref} + return + } + if schemaRef.Value != nil { + components[name] = &openapi3.SchemaRef{Value: schemaRef.Value} + } +} + +func (sg *SwaggerGen) addRegisteredSchemaType(ctx context.Context, componentSchemas openapi3.Schemas, registeredSchemaTypeName string, registeredSchemaTypeValue interface{}) { + + schemaCustomizer := func(name string, t reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error { + sg.addCustomType(t, schema) + if err := sg.applyOneOfTag(ctx, tag, componentSchemas, schema); err != nil { + return err + } + return sg.ffTagHandler(ctx, nil, name, tag, schema) + } + schemaRef, err := openapi3gen.NewSchemaRefForValue(registeredSchemaTypeValue, componentSchemas, openapi3gen.SchemaCustomizer(schemaCustomizer)) + if err != nil { + panic(fmt.Sprintf("invalid schema registration for %s: %s", registeredSchemaTypeName, err)) + } + sg.ensureComponentAlias(componentSchemas, registeredSchemaTypeName, schemaRef) + +} + func (sg *SwaggerGen) addInput(ctx context.Context, doc *openapi3.T, route *Route, op *openapi3.Operation) { var schemaRef *openapi3.SchemaRef var err error schemaCustomizer := func(name string, t reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error { sg.addCustomType(t, schema) + if err := sg.applyOneOfTag(ctx, tag, doc.Components.Schemas, schema); err != nil { + return err + } return sg.ffInputTagHandler(ctx, route, name, tag, schema) } switch { @@ -338,6 +488,9 @@ func (sg *SwaggerGen) addOutput(ctx context.Context, doc *openapi3.T, route *Rou s := i18n.Expand(ctx, i18n.APISuccessResponse) schemaCustomizer := func(name string, t reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error { sg.addCustomType(t, schema) + if err := sg.applyOneOfTag(ctx, tag, doc.Components.Schemas, schema); err != nil { + return err + } return sg.ffOutputTagHandler(ctx, route, name, tag, schema) } switch { diff --git a/pkg/ffapi/openapi3_test.go b/pkg/ffapi/openapi3_test.go index b7ecd40..8801108 100644 --- a/pkg/ffapi/openapi3_test.go +++ b/pkg/ffapi/openapi3_test.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "net/http" + "reflect" "testing" "github.com/stretchr/testify/require" @@ -64,6 +65,18 @@ type TestStruct2 struct { JSONAny1 *fftypes.JSONAny `ffstruct:"ut1" json:"jsonAny1,omitempty"` } +type TestOneOfA struct { + FieldA string `ffstruct:"TestOneOfA" json:"fieldA"` +} + +type TestOneOfB struct { + FieldB int64 `ffstruct:"TestOneOfB" json:"fieldB"` +} + +type TestOneOfHolder struct { + Value *fftypes.JSONAny `ffstruct:"TestOneOfHolder" json:"value,omitempty" ffoneof:"TestOneOfA,TestOneOfB"` +} + var ExampleDesc = i18n.FFM(language.AmericanEnglish, "TestKey", "Test Description") var example2TagName = "Example 2" @@ -578,3 +591,306 @@ func TestExcludeFromOpenAPI(t *testing.T) { err := doc.Validate(context.Background()) assert.NoError(t, err) } + +func TestPanicOnUnresolvedOneOfsInInput(t *testing.T) { + routes := []*Route{ + { + Name: "opOneOf", + Path: "oneof", + Method: http.MethodPost, + Description: ExampleDesc, + JSONInputValue: func() interface{} { return &TestOneOfHolder{} }, + JSONOutputCodes: []int{http.StatusOK}, + }, + } + + assert.PanicsWithValue(t, "invalid schema: ffoneof references unregistered schema: TestOneOfA", func() { + _ = NewSwaggerGen(&SwaggerGenOptions{ + Title: "TestPanicOnUnresolvedOneOfsInInput", + Version: "1.0", + BaseURL: "http://localhost:12345/api/v1", + PanicOnUnresolvedOneOfs: true, + }).Generate(context.Background(), routes) + }) +} + +func TestPanicOnUnresolvedOneOfsInOutput(t *testing.T) { + routes := []*Route{ + { + Name: "opOneOf", + Path: "oneof", + Method: http.MethodPost, + Description: ExampleDesc, + JSONOutputValue: func() interface{} { return &TestOneOfHolder{} }, + JSONOutputCodes: []int{http.StatusOK}, + }, + } + + assert.PanicsWithValue(t, "invalid schema: ffoneof references unregistered schema: TestOneOfA", func() { + _ = NewSwaggerGen(&SwaggerGenOptions{ + Title: "TestPanicOnUnresolvedOneOfsInOutput", + Version: "1.0", + BaseURL: "http://localhost:12345/api/v1", + PanicOnUnresolvedOneOfs: true, + }).Generate(context.Background(), routes) + }) +} + +func TestNoPanicOnUnresolvedOnOfs(t *testing.T) { + routes := []*Route{ + { + Name: "opOneOf", + Path: "oneof", + Method: http.MethodPost, + Description: ExampleDesc, + JSONOutputValue: func() interface{} { return &TestOneOfHolder{} }, + JSONInputValue: func() interface{} { return &TestOneOfHolder{} }, + JSONOutputCodes: []int{http.StatusOK}, + }, + } + doc := NewSwaggerGen(&SwaggerGenOptions{ + Title: "TestNoPanicOnUnresolvedOnOfs", + Version: "1.0", + BaseURL: "http://localhost:12345/api/v1", + PanicOnUnresolvedOneOfs: false, + }).Generate(context.Background(), routes) + assert.NotNil(t, doc) + + // however, we should get an error if we try to validate the document + loader := openapi3.NewLoader() + err := loader.ResolveRefsIn(doc, nil) + assert.Error(t, err) + +} + +func TestOneOfWithRegistrationOfStruct(t *testing.T) { + RegisterSchemaType("TestOneOfA", &TestOneOfA{}) + RegisterSchemaType("TestOneOfB", &TestOneOfB{}) + defer UnregisterSchemaType("TestOneOfA") + defer UnregisterSchemaType("TestOneOfB") + + routes := []*Route{ + { + Name: "opOneOf", + Path: "oneof", + Method: http.MethodPost, + Description: ExampleDesc, + JSONInputValue: func() interface{} { return &TestOneOfHolder{} }, + JSONOutputCodes: []int{http.StatusOK}, + }, + } + + doc := NewSwaggerGen(&SwaggerGenOptions{ + Title: "TestOneOfWithStaticRegistrationOfStruct", + Version: "1.0", + BaseURL: "http://localhost:12345/api/v1", + }).Generate(context.Background(), routes) + + b, err := yaml.Marshal(doc) + assert.NoError(t, err) + fmt.Print(string(b)) + + loader := openapi3.NewLoader() + err = loader.ResolveRefsIn(doc, nil) + assert.NoError(t, err) + + err = doc.Validate(context.Background()) + assert.NoError(t, err) + + oneOfSchema := doc.Paths.Find("/oneof").Post.RequestBody.Value.Content.Get("application/json").Schema + require.NotNil(t, oneOfSchema) + require.NotNil(t, oneOfSchema.Value) + require.NotNil(t, oneOfSchema.Value.Properties) + require.NotNil(t, oneOfSchema.Value.Properties["value"]) + require.NotNil(t, oneOfSchema.Value.Properties["value"].Value) + assert.Equal(t, 2, len(oneOfSchema.Value.Properties["value"].Value.OneOf)) + assert.Equal(t, "#/components/schemas/TestOneOfA", oneOfSchema.Value.Properties["value"].Value.OneOf[0].Ref) + assert.Equal(t, "#/components/schemas/TestOneOfB", oneOfSchema.Value.Properties["value"].Value.OneOf[1].Ref) + assert.NotNil(t, doc.Components.Schemas["TestOneOfA"]) + assert.NotNil(t, doc.Components.Schemas["TestOneOfB"]) +} + +func TestOneOfWithPostInjectionOfSchema(t *testing.T) { + + routes := []*Route{ + { + Name: "opOneOf", + Path: "oneof", + Method: http.MethodPost, + Description: ExampleDesc, + JSONInputValue: func() interface{} { return &TestOneOfHolder{} }, + JSONOutputCodes: []int{http.StatusOK}, + }, + } + + doc := NewSwaggerGen(&SwaggerGenOptions{ + Title: "TestOneOfWithDynamicRegistrationOfSchema", + Version: "1.0", + BaseURL: "http://localhost:12345/api/v1", + }).Generate(context.Background(), routes) + + // at this point the schema has unresolved oneof references to TestOneOfA and TestOneOfB + loader := openapi3.NewLoader() + err := loader.ResolveRefsIn(doc, nil) + assert.Error(t, err) + + // Create a new Schema from a JSON string + aSchemaJSON := `{ + "type": "object", + "properties": { + "type": { "type": "string" }, + "fieldA": { "type": "string" } + }, + "required": ["type", "fieldA"] + }` + var aSchema openapi3.Schema + err = aSchema.UnmarshalJSON([]byte(aSchemaJSON)) + require.NoError(t, err) + doc.Components.Schemas["TestOneOfA"] = &openapi3.SchemaRef{ + Value: &aSchema, + } + + bSchemaJSON := `{ + "type": "object", + "properties": { + "type": { "type": "string" }, + "fieldB": { "type": "string" } + }, + "required": ["type", "fieldB"] + }` + var bSchema openapi3.Schema + err = bSchema.UnmarshalJSON([]byte(bSchemaJSON)) + require.NoError(t, err) + doc.Components.Schemas["TestOneOfB"] = &openapi3.SchemaRef{ + Value: &bSchema, + } + + loader = openapi3.NewLoader() + err = loader.ResolveRefsIn(doc, nil) + assert.NoError(t, err) + + err = doc.Validate(context.Background()) + assert.NoError(t, err) + + oneOfSchema := doc.Paths.Find("/oneof").Post.RequestBody.Value.Content.Get("application/json").Schema + require.NotNil(t, oneOfSchema) + require.NotNil(t, oneOfSchema.Value) + require.NotNil(t, oneOfSchema.Value.Properties) + require.NotNil(t, oneOfSchema.Value.Properties["value"]) + require.NotNil(t, oneOfSchema.Value.Properties["value"].Value) + assert.Equal(t, 2, len(oneOfSchema.Value.Properties["value"].Value.OneOf)) + assert.Equal(t, "#/components/schemas/TestOneOfA", oneOfSchema.Value.Properties["value"].Value.OneOf[0].Ref) + assert.Equal(t, "#/components/schemas/TestOneOfB", oneOfSchema.Value.Properties["value"].Value.OneOf[1].Ref) + assert.NotNil(t, doc.Components.Schemas["TestOneOfA"]) + assert.NotNil(t, doc.Components.Schemas["TestOneOfB"]) +} + +func TestSplitCSVEmpty(t *testing.T) { + assert.Nil(t, splitCSV("")) +} + +func TestComponentNameFromRefEmpty(t *testing.T) { + assert.Equal(t, "", componentNameFromRef(" ")) +} + +func TestComponentNameFromRefNonComponentRef(t *testing.T) { + assert.Equal(t, "", componentNameFromRef("https://example.com/schemas/Foo")) + assert.Equal(t, "", componentNameFromRef("#/paths/Foo")) +} + +func TestComponentNameFromRefComponentSchema(t *testing.T) { + assert.Equal(t, "MyType", componentNameFromRef("#/components/schemas/MyType")) +} + +func TestApplyOneOfTagNoNames(t *testing.T) { + sg := &SwaggerGen{} + schema := &openapi3.Schema{} + componentsSchemas := openapi3.Schemas{} + + err := sg.applyOneOfTag(context.Background(), reflect.StructTag(`ffoneof:"#/paths/Foo"`), componentsSchemas, schema) + assert.NoError(t, err) + assert.Nil(t, schema.OneOf) +} + +func TestEnsureComponentAliasNilInputs(t *testing.T) { + sg := &SwaggerGen{} + sg.ensureComponentAlias(nil, "Any", &openapi3.SchemaRef{}) + sg.ensureComponentAlias(openapi3.Schemas{}, "", &openapi3.SchemaRef{}) + sg.ensureComponentAlias(openapi3.Schemas{}, "Any", nil) +} + +func TestEnsureComponentAliasExistingName(t *testing.T) { + sg := &SwaggerGen{} + components := openapi3.Schemas{ + "Existing": &openapi3.SchemaRef{Ref: "#/components/schemas/Existing"}, + } + + sg.ensureComponentAlias(components, "Existing", &openapi3.SchemaRef{Ref: "#/components/schemas/Other"}) + assert.Equal(t, "#/components/schemas/Existing", components["Existing"].Ref) +} + +func TestEnsureComponentAliasRef(t *testing.T) { + sg := &SwaggerGen{} + components := openapi3.Schemas{} + + sg.ensureComponentAlias(components, "Alias", &openapi3.SchemaRef{Ref: "#/components/schemas/Target"}) + + require.NotNil(t, components["Alias"]) + assert.Equal(t, "#/components/schemas/Target", components["Alias"].Ref) + assert.Nil(t, components["Alias"].Value) +} + +func TestRegisterSchemaTypeEarlyReturn(t *testing.T) { + _, ok := getRegisteredSchemaType("TestRegisterSchemaTypeEarlyReturn") + require.False(t, ok) + + RegisterSchemaType("", &TestStruct1{}) + RegisterSchemaType("TestRegisterSchemaTypeEarlyReturn", nil) + + _, ok = getRegisteredSchemaType("TestRegisterSchemaTypeEarlyReturn") + require.False(t, ok) +} + +func TestUnregisterSchemaTypeEarlyReturn(t *testing.T) { + RegisterSchemaType("TestUnregisterSchemaTypeEarlyReturn", &TestStruct1{}) + defer UnregisterSchemaType("TestUnregisterSchemaTypeEarlyReturn") + + _, ok := getRegisteredSchemaType("TestUnregisterSchemaTypeEarlyReturn") + require.True(t, ok) + + UnregisterSchemaType("") + + _, ok = getRegisteredSchemaType("TestUnregisterSchemaTypeEarlyReturn") + require.True(t, ok) +} + +func TestPanicIfRegisteredSchemaHasUnresolvedOneofs(t *testing.T) { + type ResolvableOneOf struct { + Field1 string `ffstruct:"ResolvableOneOf" json:"field1" ffoneof:"#/components/schemas/UnresolvableOneOf"` + } + RegisterSchemaType("ResolvableOneOf", &ResolvableOneOf{}) + defer UnregisterSchemaType("ResolvableOneOf") + type UnresolvableOneOf struct { + Field1 string `ffstruct:"UnresolvableOneOf" json:"field1" ffoneof:"#/components/schemas/doesNotExist"` + } + RegisterSchemaType("UnresolvableOneOf", &UnresolvableOneOf{}) + defer UnregisterSchemaType("UnresolvableOneOf") + + assert.PanicsWithValue(t, "invalid schema registration for UnresolvableOneOf: ffoneof references unregistered schema: doesNotExist", func() { + _ = NewSwaggerGen(&SwaggerGenOptions{ + Title: "TestOneOfTags", + Version: "1.0", + BaseURL: "http://localhost:12345/api/v1", + PanicOnUnresolvedOneOfs: true, + }).Generate(context.Background(), []*Route{ + { + Name: "opOneOf", + Path: "oneof", + Method: http.MethodPost, + Description: ExampleDesc, + JSONInputValue: func() interface{} { return &ResolvableOneOf{} }, + JSONOutputCodes: []int{http.StatusOK}, + }, + }) + }) +} From 0481ec3226e8c4454ff2e109199fdbe5828f0346 Mon Sep 17 00:00:00 2001 From: John Hosie Date: Wed, 11 Feb 2026 12:34:48 +0000 Subject: [PATCH 2/2] nls message Signed-off-by: John Hosie --- pkg/ffapi/openapi3.go | 2 +- pkg/ffapi/openapi3_test.go | 6 +++--- pkg/i18n/en_base_error_messages.go | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/ffapi/openapi3.go b/pkg/ffapi/openapi3.go index ad9af1b..7e01cd9 100644 --- a/pkg/ffapi/openapi3.go +++ b/pkg/ffapi/openapi3.go @@ -334,7 +334,7 @@ func (sg *SwaggerGen) applyOneOfTag(ctx context.Context, tag reflect.StructTag, for _, name := range names { registeredSchemaType, ok := getRegisteredSchemaType(name) if !ok && sg.options.PanicOnUnresolvedOneOfs { - return fmt.Errorf("ffoneof references unregistered schema: %s", name) + return i18n.NewError(ctx, i18n.MsgFFOneOfReferencesUnregisteredSchema, name) } if ok { sg.addRegisteredSchemaType(ctx, componentSchemas, name, registeredSchemaType) diff --git a/pkg/ffapi/openapi3_test.go b/pkg/ffapi/openapi3_test.go index 8801108..837bd2b 100644 --- a/pkg/ffapi/openapi3_test.go +++ b/pkg/ffapi/openapi3_test.go @@ -604,7 +604,7 @@ func TestPanicOnUnresolvedOneOfsInInput(t *testing.T) { }, } - assert.PanicsWithValue(t, "invalid schema: ffoneof references unregistered schema: TestOneOfA", func() { + assert.PanicsWithValue(t, "invalid schema: FF00258: ffoneof references unregistered schema: TestOneOfA", func() { _ = NewSwaggerGen(&SwaggerGenOptions{ Title: "TestPanicOnUnresolvedOneOfsInInput", Version: "1.0", @@ -626,7 +626,7 @@ func TestPanicOnUnresolvedOneOfsInOutput(t *testing.T) { }, } - assert.PanicsWithValue(t, "invalid schema: ffoneof references unregistered schema: TestOneOfA", func() { + assert.PanicsWithValue(t, "invalid schema: FF00258: ffoneof references unregistered schema: TestOneOfA", func() { _ = NewSwaggerGen(&SwaggerGenOptions{ Title: "TestPanicOnUnresolvedOneOfsInOutput", Version: "1.0", @@ -876,7 +876,7 @@ func TestPanicIfRegisteredSchemaHasUnresolvedOneofs(t *testing.T) { RegisterSchemaType("UnresolvableOneOf", &UnresolvableOneOf{}) defer UnregisterSchemaType("UnresolvableOneOf") - assert.PanicsWithValue(t, "invalid schema registration for UnresolvableOneOf: ffoneof references unregistered schema: doesNotExist", func() { + assert.PanicsWithValue(t, "invalid schema registration for UnresolvableOneOf: FF00258: ffoneof references unregistered schema: doesNotExist", func() { _ = NewSwaggerGen(&SwaggerGenOptions{ Title: "TestOneOfTags", Version: "1.0", diff --git a/pkg/i18n/en_base_error_messages.go b/pkg/i18n/en_base_error_messages.go index 446a199..ecf928e 100644 --- a/pkg/i18n/en_base_error_messages.go +++ b/pkg/i18n/en_base_error_messages.go @@ -192,4 +192,5 @@ var ( MsgRoutePathNotStartWithSlash = ffe("FF00255", "Route path '%s' must not start with '/'") MsgMethodNotAllowed = ffe("FF00256", "Method not allowed", http.StatusMethodNotAllowed) MsgInvalidLogLevel = ffe("FF00257", "Invalid log level: '%s'", http.StatusBadRequest) + MsgFFOneOfReferencesUnregisteredSchema = ffe("FF00258", "ffoneof references unregistered schema: %s", http.StatusBadRequest) )