diff --git a/README.md b/README.md index 1f4e30d..c2a2004 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ The FeatherOpenAPI library makes it easy to define OpenAPI specifications using Swift in a type-safe way. [ - ![Release: 1.0.0-beta.2](https://img.shields.io/badge/Release-1%2E0%2E0--beta%2E2-F05138) + ![Release: 1.0.0-beta.3](https://img.shields.io/badge/Release-1%2E0%2E0--beta%2E3-F05138) ]( - https://github.com/feather-framework/feather-openapi/releases/tag/1.0.0-beta.2 + https://github.com/feather-framework/feather-openapi/releases/tag/1.0.0-beta.3 ) ## Features @@ -34,7 +34,7 @@ The FeatherOpenAPI library makes it easy to define OpenAPI specifications using Use Swift Package Manager; add the dependency to your `Package.swift` file: ```swift -.package(url: "https://github.com/feather-framework/feather-openapi", exact: "1.0.0-beta.2"), +.package(url: "https://github.com/feather-framework/feather-openapi", exact: "1.0.0-beta.3"), ``` Then add `FeatherOpenAPI` to your target dependencies: diff --git a/Sources/FeatherOpenAPI/Document/DocumentRepresentable.swift b/Sources/FeatherOpenAPI/Document/DocumentRepresentable.swift index c75bd71..c709973 100644 --- a/Sources/FeatherOpenAPI/Document/DocumentRepresentable.swift +++ b/Sources/FeatherOpenAPI/Document/DocumentRepresentable.swift @@ -38,7 +38,18 @@ extension DocumentRepresentable { /// Collects all tags referenced by the document. public var referencedTags: [OpenAPITagRepresentable] { - paths.values.map { $0.referencedTags }.flatMap { $0 } + var seen = Set() + return paths.values + .map { $0.referencedTags } + .flatMap { $0 } + .filter { tag in + let name = tag.openAPITag().name + if seen.contains(name) { + return false + } + seen.insert(name) + return true + } } /// Collects all security requirements referenced by the document. diff --git a/Tests/FeatherOpenAPITests/Example/Example.swift b/Tests/FeatherOpenAPITests/Example/Components/Example.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/Example.swift rename to Tests/FeatherOpenAPITests/Example/Components/Example.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleDocument.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleDocument.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleDocument.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleDocument.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleDuplicatedItem.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleDuplicatedItem.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleDuplicatedItem.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleDuplicatedItem.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleDuplicatedItemDocument.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleDuplicatedItemDocument.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleDuplicatedItemDocument.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleDuplicatedItemDocument.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleDuplicatedItemModel+Schemas.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleDuplicatedItemModel+Schemas.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleDuplicatedItemModel+Schemas.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleDuplicatedItemModel+Schemas.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleDuplicatedItemModel.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleDuplicatedItemModel.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleDuplicatedItemModel.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleDuplicatedItemModel.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleMissingParentItem.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleMissingParentItem.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleMissingParentItem.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleMissingParentItem.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleMissingParentItemDocument.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleMissingParentItemDocument.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleMissingParentItemDocument.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleMissingParentItemDocument.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleMissingParentItemModel+Schemas.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleMissingParentItemModel+Schemas.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleMissingParentItemModel+Schemas.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleMissingParentItemModel+Schemas.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleMissingParentItemModel.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleMissingParentItemModel.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleMissingParentItemModel.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleMissingParentItemModel.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleModel+Headers.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleModel+Headers.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleModel+Headers.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleModel+Headers.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleModel+Operations.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleModel+Operations.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleModel+Operations.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleModel+Operations.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleModel+Parameters.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleModel+Parameters.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleModel+Parameters.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleModel+Parameters.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleModel+PathItems.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleModel+PathItems.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleModel+PathItems.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleModel+PathItems.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleModel+RequestBodies.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleModel+RequestBodies.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleModel+RequestBodies.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleModel+RequestBodies.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleModel+Responses.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleModel+Responses.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleModel+Responses.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleModel+Responses.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleModel+Schemas.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleModel+Schemas.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleModel+Schemas.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleModel+Schemas.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleModel+SecuritySchemes.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleModel+SecuritySchemes.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleModel+SecuritySchemes.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleModel+SecuritySchemes.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleModel+Tags.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleModel+Tags.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleModel+Tags.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleModel+Tags.swift diff --git a/Tests/FeatherOpenAPITests/Example/ExampleModel.swift b/Tests/FeatherOpenAPITests/Example/Components/ExampleModel.swift similarity index 100% rename from Tests/FeatherOpenAPITests/Example/ExampleModel.swift rename to Tests/FeatherOpenAPITests/Example/Components/ExampleModel.swift diff --git a/Tests/FeatherOpenAPITests/ExampleTestSuite.swift b/Tests/FeatherOpenAPITests/Example/ExampleTestSuite.swift similarity index 100% rename from Tests/FeatherOpenAPITests/ExampleTestSuite.swift rename to Tests/FeatherOpenAPITests/Example/ExampleTestSuite.swift diff --git a/Tests/FeatherOpenAPITests/TodoTestSuite.swift b/Tests/FeatherOpenAPITests/FeatherOpenAPITestSuite.swift similarity index 68% rename from Tests/FeatherOpenAPITests/TodoTestSuite.swift rename to Tests/FeatherOpenAPITests/FeatherOpenAPITestSuite.swift index 82d679a..42737b7 100644 --- a/Tests/FeatherOpenAPITests/TodoTestSuite.swift +++ b/Tests/FeatherOpenAPITests/FeatherOpenAPITestSuite.swift @@ -1,8 +1,9 @@ // -// TodoTestSuite.swift +// FeatherOpenAPITestSuite.swift // feather-openapi // -// Created by Tibor Bödecs on 2026. 01. 25.. +// Created by Tibor Bödecs on 2026. 02. 05.. +// import OpenAPIKit import OpenAPIKit30 @@ -13,10 +14,24 @@ import Yams @testable import FeatherOpenAPI @Suite -struct TodoTestSuite { +struct FeatherOpenAPITestSuite { + + @Test + func documentDeduplicatesTagsByName() { + let document = TagDedupDocument( + info: TagDedupInfo(), + paths: TagDedupPaths().pathMap, + components: Components() + ) + + let openAPIDoc = document.openAPIDocument() + let tags = openAPIDoc.tags ?? [] + + #expect(tags.map(\.name) == ["Dogs"]) + } @Test - func example() throws { + func multipleVersions() throws { let collection = MyPathCollection() // collection.components.schemas.register(id: "", TodoFieldId()) diff --git a/Tests/FeatherOpenAPITests/PathComponentTests.swift b/Tests/FeatherOpenAPITests/PathComponentTestSuite.swift similarity index 97% rename from Tests/FeatherOpenAPITests/PathComponentTests.swift rename to Tests/FeatherOpenAPITests/PathComponentTestSuite.swift index cb96c28..3aaa289 100644 --- a/Tests/FeatherOpenAPITests/PathComponentTests.swift +++ b/Tests/FeatherOpenAPITests/PathComponentTestSuite.swift @@ -1,5 +1,5 @@ // -// PathComponentTests.swift +// PathComponentTestSuite.swift // feather-openapi // // Created by Tibor Bödecs on 2026. 01. 22.. @@ -32,7 +32,7 @@ extension Path { } @Suite -struct PathComponentTests { +struct PathComponentTestSuite { @Test func doesNotCompile() { diff --git a/Tests/FeatherOpenAPITests/PetstoreTestSuite.swift b/Tests/FeatherOpenAPITests/Petstore/PetstoreTestSuite.swift similarity index 100% rename from Tests/FeatherOpenAPITests/PetstoreTestSuite.swift rename to Tests/FeatherOpenAPITests/Petstore/PetstoreTestSuite.swift diff --git a/Tests/FeatherOpenAPITests/Todo/TestObjects.swift b/Tests/FeatherOpenAPITests/TestObjects.swift similarity index 73% rename from Tests/FeatherOpenAPITests/Todo/TestObjects.swift rename to Tests/FeatherOpenAPITests/TestObjects.swift index 6eaf712..79ca84d 100644 --- a/Tests/FeatherOpenAPITests/Todo/TestObjects.swift +++ b/Tests/FeatherOpenAPITests/TestObjects.swift @@ -189,3 +189,61 @@ struct APIKeySecurityRequirement: SecurityRequirementRepresentable { var security: any SecuritySchemeRepresentable = APIKeySecurityScheme() } + +// MARK: - + +struct TagDedupInfo: InfoRepresentable { + var title: String { "Tag Dedup Test" } + var version: String { "1.0.0" } +} + +struct TagDedupDocument: DocumentRepresentable { + var info: OpenAPIInfoRepresentable + var paths: PathMap + var components: OpenAPIComponentsRepresentable +} + +struct TagDedupPaths: PathCollectionRepresentable { + var pathMap: PathMap { + [ + "dogs": TagDedupDogPathItem() + ] + } +} + +struct TagDedupDogPathItem: PathItemRepresentable { + var get: OperationRepresentable? { TagDedupListDogsOperation() } + var post: OperationRepresentable? { TagDedupCreateDogOperation() } +} + +struct TagDedupDogTag: TagRepresentable { + var name: String = "Dogs" + var description: String? = "Manage dogs." +} + +struct TagDedupDogSchema: StringSchemaRepresentable { + var example: String? = "Hachi" +} + +struct TagDedupDogResponse: JSONResponseRepresentable { + var description: String = "Dog response" + var schema: TagDedupDogSchema = TagDedupDogSchema() +} + +struct TagDedupListDogsOperation: OperationRepresentable { + var tags: [TagRepresentable] { [TagDedupDogTag()] } + var responseMap: ResponseMap { + [ + 200: TagDedupDogResponse().reference() + ] + } +} + +struct TagDedupCreateDogOperation: OperationRepresentable { + var tags: [TagRepresentable] { [TagDedupDogTag()] } + var responseMap: ResponseMap { + [ + 200: TagDedupDogResponse().reference() + ] + } +}