From 2746032ae58a058e2961bc7030b94ee358e2fd97 Mon Sep 17 00:00:00 2001 From: dankinsoid <30962149+dankinsoid@users.noreply.github.com> Date: Sat, 2 Dec 2023 01:35:24 +0400 Subject: [PATCH] 2.19.4 --- README.md | 2 +- .../SwiftOpenAPI/Encoders/SchemeEncoder.swift | 59 +++++++++++++++---- .../TypeRevision/TypeRevisionDecoder.swift | 7 ++- .../TypeRevision/TypeRevisionEncoder.swift | 2 +- .../ArrayDecodingTests.swift | 10 +++- 5 files changed, 63 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index b3c8f9d..d2fe91a 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ import PackageDescription let package = Package( name: "SomeProject", dependencies: [ - .package(url: "https://github.com/dankinsoid/SwiftOpenAPI.git", from: "2.19.3") + .package(url: "https://github.com/dankinsoid/SwiftOpenAPI.git", from: "2.19.4") ], targets: [ .target(name: "SomeProject", dependencies: ["SwiftOpenAPI"]) diff --git a/Sources/SwiftOpenAPI/Encoders/SchemeEncoder.swift b/Sources/SwiftOpenAPI/Encoders/SchemeEncoder.swift index e4ef26d..6372260 100644 --- a/Sources/SwiftOpenAPI/Encoders/SchemeEncoder.swift +++ b/Sources/SwiftOpenAPI/Encoders/SchemeEncoder.swift @@ -11,11 +11,10 @@ struct SchemeEncoder { _ value: Encodable, into schemas: inout ComponentsMap ) throws -> ReferenceOr { - let type = Swift.type(of: value) - return try parse( + try parse( value: TypeRevision().describeType(of: value), - type: type, - into: &schemas + type: Swift.type(of: value), + into: &schemas ) } @@ -24,21 +23,50 @@ struct SchemeEncoder { _ type: Decodable.Type, into schemas: inout ComponentsMap ) throws -> ReferenceOr { - try parse( + try parse( value: TypeRevision().describe(type: type), type: type, into: &schemas ) } + func parse( + value: TypeInfo, + type: Any.Type, + into schemas: inout ComponentsMap + ) throws -> ReferenceOr { + var needReparse = false + var cache: [ObjectIdentifier: ReferenceOr] = [:] + var result = try parse( + value: value, + type: type, + into: &schemas, + cache: &cache, + needReparse: &needReparse + ) + if needReparse { + result = try parse( + value: value, + type: type, + into: &schemas, + cache: &cache, + needReparse: &needReparse + ) + } + return result + } + func parse( - value: @autoclosure () throws -> TypeInfo, + value: TypeInfo, type: Any.Type, - into schemas: inout ComponentsMap + into schemas: inout ComponentsMap, + cache: inout [ObjectIdentifier: ReferenceOr], + needReparse: inout Bool ) throws -> ReferenceOr { let name = String.typeName(type) var result: ReferenceOr - let typeInfo = try value() + let typeInfo = value + let typeID = ObjectIdentifier(typeInfo.type) switch type { case is Date.Type: @@ -67,7 +95,7 @@ struct SchemeEncoder { properties: keyedInfo.fields.mapKeys { keyEncodingStrategy.encode($0) }.mapValues { - try parse(value: $0, type: $0.type, into: &schemas) + try parse(value: $0, type: $0.type, into: &schemas, cache: &cache, needReparse: &needReparse) }, required: Set(keyedInfo.fields.unorderedHash.filter { !$0.value.isOptional }.keys) ) @@ -76,7 +104,7 @@ struct SchemeEncoder { case false: let schema = try SchemaObject.dictionary( of: (keyedInfo.fields.first?.value).map { - try parse(value: $0, type: $0.type, into: &schemas) + try parse(value: $0, type: $0.type, into: &schemas, cache: &cache, needReparse: &needReparse) } ?? .any ) result = .value(schema) @@ -84,12 +112,17 @@ struct SchemeEncoder { case let .unkeyed(itemInfo): let schema = try SchemaObject.array( - of: parse(value: itemInfo, type: itemInfo.type, into: &schemas) + of: parse(value: itemInfo, type: itemInfo.type, into: &schemas, cache: &cache, needReparse: &needReparse) ) result = .value(schema) case .recursive: - result = .ref(components: \.schemas, name) + needReparse = true + if let cached = cache[typeID] { + result = cached + } else { + result = .ref(components: \.schemas, name) + } } } @@ -100,11 +133,13 @@ struct SchemeEncoder { if extractReferences, result.isReferenceable { result.object?.nullable = nil schemas[name] = result + cache[typeID] = .ref(components: \.schemas, name) return .ref(components: \.schemas, name) } else { if typeInfo.isOptional, result.object?.enum == nil { result.object?.nullable = true } + cache[typeID] = result return result } } diff --git a/Sources/SwiftOpenAPI/Encoders/TypeRevision/TypeRevisionDecoder.swift b/Sources/SwiftOpenAPI/Encoders/TypeRevision/TypeRevisionDecoder.swift index 2a79ee5..26af2c7 100644 --- a/Sources/SwiftOpenAPI/Encoders/TypeRevision/TypeRevisionDecoder.swift +++ b/Sources/SwiftOpenAPI/Encoders/TypeRevision/TypeRevisionDecoder.swift @@ -309,7 +309,7 @@ private struct TypeRevisionSingleValueDecodingContainer: SingleValueDecodingCont private func _decodeIfPresent(_ type: T.Type, optional: Bool) -> T? where T : Decodable { let decoder = TypeRevisionDecoder( - path: nestedPath(for: type), + path: nestedPath(for: optional ? Optional.self : type), context: decoder.context ) let decodable = try? decoder.decode(type) @@ -535,7 +535,10 @@ private struct TypeRevisionKeyedDecodingContainer: KeyedDecoding } private func decode(_ type: T.Type, forKey key: Key, optional: Bool) throws -> T { - let decoder = TypeRevisionDecoder(path: nestedPath(for: key, type), context: decoder.context) + let decoder = TypeRevisionDecoder( + path: nestedPath(for: key, optional ? Optional.self : type), + context: decoder.context + ) let decodeResult = Result { try decoder.decode(type) } diff --git a/Sources/SwiftOpenAPI/Encoders/TypeRevision/TypeRevisionEncoder.swift b/Sources/SwiftOpenAPI/Encoders/TypeRevision/TypeRevisionEncoder.swift index 9868a4e..4d2e205 100644 --- a/Sources/SwiftOpenAPI/Encoders/TypeRevision/TypeRevisionEncoder.swift +++ b/Sources/SwiftOpenAPI/Encoders/TypeRevision/TypeRevisionEncoder.swift @@ -337,7 +337,7 @@ private struct TypeRevisionKeyedEncodingContainer: KeyedEncoding private mutating func encode(_ value: T?, forKey key: Key, optional: Bool) throws where T: Encodable { let encoder = TypeRevisionEncoder( - path: nestedPath(for: key, T.self), + path: nestedPath(for: key, optional ? Optional.self : T.self), context: encoder.context ) var info = try encoder.encode(value, type: T.self) diff --git a/Tests/SwiftOpenAPITests/ArrayDecodingTests.swift b/Tests/SwiftOpenAPITests/ArrayDecodingTests.swift index 6ca3427..1189b66 100644 --- a/Tests/SwiftOpenAPITests/ArrayDecodingTests.swift +++ b/Tests/SwiftOpenAPITests/ArrayDecodingTests.swift @@ -46,7 +46,14 @@ class ArrayDecodingTests: XCTestCase { func testDecodeEmbeddedRecursiveOptionalArray() throws { var schemas: ComponentsMap = [:] let _ = try ReferenceOr.encodeSchema(EmbeddedOptionalRecursiveTypeInArray.example, into: &schemas) - XCTAssertNoDifference(schemas, ["ProductDependency": .value(ProductDependency.scheme)]) + XCTAssertNoDifference( + schemas["EmbeddedOptionalRecursiveTypeInArray"], + .value(EmbeddedOptionalRecursiveTypeInArray.scheme) + ) + XCTAssertNoDifference( + schemas["ProductDependency"], + .value(ProductDependency.scheme) + ) } } @@ -76,6 +83,7 @@ public struct EmbeddedOptionalRecursiveTypeInArray: Codable, Equatable { properties: [ "name": .string, "dependencies": .array(of: .ref(components: \.schemas, "ProductDependency")) + .with(\.nullable, true) ], required: ["name"] )