diff --git a/Package.resolved b/Package.resolved index 2eae2d9..336e804 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/jpsim/Yams", "state" : { - "revision" : "51b5127c7fb6ffac106ad6d199aaa33c5024895f", - "version" : "6.2.0" + "revision" : "deaf82e867fa2cbd3cd865978b079bfcf384ac28", + "version" : "6.2.1" } } ], diff --git a/README.md b/README.md index 4cad7b7..28f238d 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.4](https://img.shields.io/badge/Release-1%2E0%2E0--beta%2E4-F05138) + ![Release: 1.0.0-beta.5](https://img.shields.io/badge/Release-1%2E0%2E0--beta%2E5-F05138) ]( - https://github.com/feather-framework/feather-openapi/releases/tag/1.0.0-beta.4 + https://github.com/feather-framework/feather-openapi/releases/tag/1.0.0-beta.5 ) ## 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.4"), +.package(url: "https://github.com/feather-framework/feather-openapi", exact: "1.0.0-beta.5"), ``` Then add `FeatherOpenAPI` to your target dependencies: diff --git a/Sources/FeatherOpenAPI/Document/DocumentRepresentable.swift b/Sources/FeatherOpenAPI/Document/DocumentRepresentable.swift index c709973..21ad032 100644 --- a/Sources/FeatherOpenAPI/Document/DocumentRepresentable.swift +++ b/Sources/FeatherOpenAPI/Document/DocumentRepresentable.swift @@ -56,7 +56,20 @@ extension DocumentRepresentable { public var referencedSecurityRequirements: [SecurityRequirementRepresentable] { - paths.values.map { $0.referencedSecurityRequirements }.flatMap { $0 } + var seen = Set() + return paths.values + .map { $0.referencedSecurityRequirements } + .flatMap { $0 } + .filter { requirement in + let requirementID = + requirement.security.openAPIIdentifier + "::" + + requirement.requirements.sorted().joined(separator: ",") + if seen.contains(requirementID) { + return false + } + seen.insert(requirementID) + return true + } } /// Builds an OpenAPI document from the representable values. diff --git a/Tests/FeatherOpenAPITests/FeatherOpenAPITestSuite.swift b/Tests/FeatherOpenAPITests/FeatherOpenAPITestSuite.swift index 42737b7..a8b7ede 100644 --- a/Tests/FeatherOpenAPITests/FeatherOpenAPITestSuite.swift +++ b/Tests/FeatherOpenAPITests/FeatherOpenAPITestSuite.swift @@ -30,6 +30,20 @@ struct FeatherOpenAPITestSuite { #expect(tags.map(\.name) == ["Dogs"]) } + @Test + func documentDeduplicatesSecurityRequirementsBySchemeAndRequirements() { + let document = SecurityRequirementDedupDocument( + info: SecurityRequirementDedupInfo(), + paths: SecurityRequirementDedupPaths().pathMap, + components: Components() + ) + + let openAPIDoc = document.openAPIDocument() + let security = openAPIDoc.security + + #expect(security.count == 1) + } + @Test func multipleVersions() throws { diff --git a/Tests/FeatherOpenAPITests/TestObjects.swift b/Tests/FeatherOpenAPITests/TestObjects.swift index 84fdc3a..b0c17ff 100644 --- a/Tests/FeatherOpenAPITests/TestObjects.swift +++ b/Tests/FeatherOpenAPITests/TestObjects.swift @@ -247,3 +247,81 @@ struct TagDedupCreateDogOperation: OperationRepresentable { ] } } + +// MARK: - + +struct SecurityRequirementDedupInfo: InfoRepresentable { + var title: String { "Security Requirement Dedup Test" } + var version: String { "1.0.0" } +} + +struct SecurityRequirementDedupDocument: DocumentRepresentable { + var info: OpenAPIInfoRepresentable + var paths: PathMap + var components: OpenAPIComponentsRepresentable +} + +struct SecurityRequirementDedupPaths: PathCollectionRepresentable { + var pathMap: PathMap { + [ + "cats": SecurityRequirementDedupCatPathItem() + ] + } +} + +struct SecurityRequirementDedupCatPathItem: PathItemRepresentable { + var get: OperationRepresentable? { + SecurityRequirementDedupListCatsOperation() + } + var post: OperationRepresentable? { + SecurityRequirementDedupCreateCatOperation() + } +} + +struct SecurityRequirementDedupBearerTokenScheme: SecuritySchemeRepresentable { + var type: OpenAPI.SecurityScheme.SecurityType { + .http( + scheme: "bearer", + bearerFormat: "token" + ) + } +} + +struct SecurityRequirementDedupBearerTokenRequirement: + SecurityRequirementRepresentable +{ + var security: any SecuritySchemeRepresentable { + SecurityRequirementDedupBearerTokenScheme() + } +} + +struct SecurityRequirementDedupCatSchema: StringSchemaRepresentable { + var example: String? = "Milo" +} + +struct SecurityRequirementDedupCatResponse: JSONResponseRepresentable { + var description: String = "Cat response" + var schema: SecurityRequirementDedupCatSchema = .init() +} + +struct SecurityRequirementDedupListCatsOperation: OperationRepresentable { + var security: [SecurityRequirementRepresentable]? { + [SecurityRequirementDedupBearerTokenRequirement()] + } + var responseMap: ResponseMap { + [ + 200: SecurityRequirementDedupCatResponse().reference() + ] + } +} + +struct SecurityRequirementDedupCreateCatOperation: OperationRepresentable { + var security: [SecurityRequirementRepresentable]? { + [SecurityRequirementDedupBearerTokenRequirement()] + } + var responseMap: ResponseMap { + [ + 200: SecurityRequirementDedupCatResponse().reference() + ] + } +}