diff --git a/bundler/bundler_test.go b/bundler/bundler_test.go index b58b4c05..004c0efc 100644 --- a/bundler/bundler_test.go +++ b/bundler/bundler_test.go @@ -20,18 +20,19 @@ import ( "sync" "testing" - "github.com/pb33f/libopenapi/datamodel/low" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v4" "github.com/pb33f/libopenapi" + "github.com/pb33f/libopenapi/bundler/test/specs/schemawithrefs" "github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel/high/base" v3high "github.com/pb33f/libopenapi/datamodel/high/v3" + "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/utils" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.yaml.in/yaml/v4" ) // Test helper functions to reduce duplication across DigitalOcean tests @@ -2209,8 +2210,8 @@ func TestCopySchemaToComponents_NameCollision(t *testing.T) { func TestCalculateCollisionNameInline_NumericSuffix(t *testing.T) { // Test: When filename-based name also collides, use numeric suffix existingNames := map[string]bool{ - "Cat": true, - "Cat__external": true, // Filename-based collision also exists + "Cat": true, + "Cat__external": true, // Filename-based collision also exists "Cat__external__1": true, // First numeric suffix also taken (format: name__basename__N) } @@ -2284,3 +2285,44 @@ components: assert.Equal(t, "#node", itemsSchema.DynamicRef, "DynamicRef should be '#node'") } +func TestBundleDocument_Embedded(t *testing.T) { + expected, err := os.ReadFile("test/specs/schemawithrefs_expected.yaml") + require.NoError(t, err) + + tests := []struct { + name string + config *datamodel.DocumentConfiguration + }{ + { + name: "directory", + config: &datamodel.DocumentConfiguration{ + BasePath: "test/specs/schemawithrefs", + AllowFileReferences: true, + }, + }, + { + name: "embed", + config: &datamodel.DocumentConfiguration{ + LocalFS: schemawithrefs.Files, + AllowFileReferences: true, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doc, err := libopenapi.NewDocumentWithConfiguration(schemawithrefs.Schema, tt.config) + require.NoError(t, err) + + t.Run("v3", func(t *testing.T) { + v3, err := doc.BuildV3Model() + require.NoError(t, err) + + b, err := BundleDocument(&v3.Model) + require.NoError(t, err) + + assert.Equal(t, string(b), string(expected)) + }) + }) + } +} diff --git a/bundler/test/specs/schemawithrefs/description.yaml b/bundler/test/specs/schemawithrefs/description.yaml new file mode 100644 index 00000000..4bb93d9a --- /dev/null +++ b/bundler/test/specs/schemawithrefs/description.yaml @@ -0,0 +1,4 @@ +introduction: | + # Title + + Description diff --git a/bundler/test/specs/schemawithrefs/openapi.yaml b/bundler/test/specs/schemawithrefs/openapi.yaml new file mode 100644 index 00000000..bb3b5f7a --- /dev/null +++ b/bundler/test/specs/schemawithrefs/openapi.yaml @@ -0,0 +1,11 @@ +openapi: "3.0.0" + +info: + title: Test API + version: "2.0" + description: + $ref: "description.yaml#/introduction" +paths: + /v2/actions/{action_id}: + get: + $ref: "resources/actions/actions_get.yaml" diff --git a/bundler/test/specs/schemawithrefs/resources/actions/actions_get.yaml b/bundler/test/specs/schemawithrefs/resources/actions/actions_get.yaml new file mode 100644 index 00000000..54d5d784 --- /dev/null +++ b/bundler/test/specs/schemawithrefs/resources/actions/actions_get.yaml @@ -0,0 +1,16 @@ +operationId: actions_get + +summary: Retrieve an action + +description: The description for this endpoint + +tags: + - Actions + +parameters: + - $ref: 'parameters.yaml#/action_id' + +responses: + '200': + $ref: 'responses/action.yaml' + diff --git a/bundler/test/specs/schemawithrefs/resources/actions/models/action.yaml b/bundler/test/specs/schemawithrefs/resources/actions/models/action.yaml new file mode 100644 index 00000000..f69e0095 --- /dev/null +++ b/bundler/test/specs/schemawithrefs/resources/actions/models/action.yaml @@ -0,0 +1,17 @@ +type: object + +properties: + id: + type: integer + description: A description of id + example: 36804636 + + status: + type: string + description: A description of status + enum: + - started + - completed + - errored + example: completed + default: started diff --git a/bundler/test/specs/schemawithrefs/resources/actions/parameters.yaml b/bundler/test/specs/schemawithrefs/resources/actions/parameters.yaml new file mode 100644 index 00000000..19c5ea36 --- /dev/null +++ b/bundler/test/specs/schemawithrefs/resources/actions/parameters.yaml @@ -0,0 +1,9 @@ +action_id: + in: path + name: action_id + description: A unique numeric ID that can be used to identify and reference an action. + required: true + schema: + type: integer + minimum: 1 + example: 36804636 \ No newline at end of file diff --git a/bundler/test/specs/schemawithrefs/resources/actions/responses/action.yaml b/bundler/test/specs/schemawithrefs/resources/actions/responses/action.yaml new file mode 100644 index 00000000..b6757b2f --- /dev/null +++ b/bundler/test/specs/schemawithrefs/resources/actions/responses/action.yaml @@ -0,0 +1,10 @@ +description: >- + The result will be a JSON object with an action key. + This will be set to an action object containing the standard action attributes. + +content: + application/json: + schema: + properties: + action: + $ref: '../models/action.yaml' diff --git a/bundler/test/specs/schemawithrefs/schema.go b/bundler/test/specs/schemawithrefs/schema.go new file mode 100644 index 00000000..517970d5 --- /dev/null +++ b/bundler/test/specs/schemawithrefs/schema.go @@ -0,0 +1,11 @@ +package schemawithrefs + +import ( + "embed" +) + +//go:embed openapi.yaml +var Schema []byte + +//go:embed description.yaml resources +var Files embed.FS diff --git a/bundler/test/specs/schemawithrefs_expected.yaml b/bundler/test/specs/schemawithrefs_expected.yaml new file mode 100644 index 00000000..52876d0c --- /dev/null +++ b/bundler/test/specs/schemawithrefs_expected.yaml @@ -0,0 +1,46 @@ +openapi: "3.0.0" +info: + title: Test API + version: "2.0" + description: "" +paths: + /v2/actions/{action_id}: + get: + operationId: actions_get + summary: Retrieve an action + description: The description for this endpoint + tags: + - Actions + parameters: + - in: path + name: action_id + description: A unique numeric ID that can be used to identify and reference an action. + required: true + schema: + type: integer + minimum: 1 + example: 36804636 + responses: + '200': + description: >- + The result will be a JSON object with an action key. This will be set to an action object containing the standard action attributes. + content: + application/json: + schema: + properties: + action: + type: object + properties: + id: + type: integer + description: A description of id + example: 36804636 + status: + type: string + description: A description of status + enum: + - started + - completed + - errored + example: completed + default: started diff --git a/datamodel/low/v3/create_document.go b/datamodel/low/v3/create_document.go index c4bac1b0..2a62a7ac 100644 --- a/datamodel/low/v3/create_document.go +++ b/datamodel/low/v3/create_document.go @@ -106,7 +106,23 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur cwd, _ = filepath.Abs(config.BasePath) // if a supplied local filesystem is provided, add it to the rolodex. if config.LocalFS != nil { - rolodex.AddLocalFS(cwd, config.LocalFS) + var localFS index.RolodexFS + if fs, ok := config.LocalFS.(index.RolodexFS); ok { + localFS = fs + } else { + // create a local filesystem + localFSConf := index.LocalFSConfig{ + BaseDirectory: cwd, + IndexConfig: idxConfig, + FileFilters: config.FileFilter, + DirFS: config.LocalFS, + } + + localFS, _ = index.NewLocalFSWithConfig(&localFSConf) + idxConfig.AllowFileLookup = true + } + + rolodex.AddLocalFS(cwd, localFS) } else { // create a local filesystem