From dfdbd72ade44a4bfc0e484c888967164b50cc19a Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Thu, 26 Oct 2023 17:33:05 +0200 Subject: [PATCH] [Generator] Generate server variables (#348) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Generator] Generate server variables ### Motivation The generator side of https://github.com/apple/swift-openapi-generator/issues/24. Depends on https://github.com/apple/swift-openapi-runtime/pull/64 landing and getting released. ### Modifications See the file-based reference tests first to get a sense of how server variables work in templates. Most of this PR is test changes, plus slightly changed formatting of arrays, to put each item on new line for non-empty ones. ### Result Server variables are supported, allowing e.g. services that have enterprise versions to use the feature instead of having to hardcode the remote URL. ### Test Plan Adapted tests. Reviewed by: glbrntt Builds: ✔︎ pull request validation (5.10) - Build finished. ✔︎ pull request validation (5.8) - Build finished. ✔︎ pull request validation (5.9) - Build finished. ✔︎ pull request validation (compatibility test) - Build finished. ✔︎ pull request validation (docc test) - Build finished. ✔︎ pull request validation (integration test) - Build finished. ✔︎ pull request validation (nightly) - Build finished. ✔︎ pull request validation (soundness) - Build finished. https://github.com/apple/swift-openapi-generator/pull/348 --- Package.swift | 2 +- .../Renderer/TextBasedRenderer.swift | 16 ++- .../translateStructBlueprint.swift | 22 ++-- .../CommonTypes/CommentExtensions.swift | 25 ++++- .../TypesTranslator/translateServers.swift | 27 ++++- .../Renderer/Test_TextBasedRenderer.swift | 9 +- .../Resources/Docs/petstore.yaml | 14 +++ .../ReferenceSources/Petstore/Client.swift | 8 +- .../ReferenceSources/Petstore/Types.swift | 106 +++++++++++++++--- .../SnippetBasedReferenceTests.swift | 36 ++++-- Tests/PetstoreConsumerTests/Test_Types.swift | 51 ++++----- 11 files changed, 234 insertions(+), 82 deletions(-) diff --git a/Package.swift b/Package.swift index 7448c689..d9909727 100644 --- a/Package.swift +++ b/Package.swift @@ -64,7 +64,7 @@ let package = Package( // Tests-only: Runtime library linked by generated code, and also // helps keep the runtime library new enough to work with the generated // code. - .package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.3.3")), + .package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.3.5")), // Build and preview docs .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), diff --git a/Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift b/Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift index c1345195..f2ec5aa1 100644 --- a/Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift +++ b/Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift @@ -456,13 +456,17 @@ struct TextBasedRenderer: RendererProtocol { case .nil: write("nil") case .array(let items): writer.writeLine("[") - writer.nextLineAppendsToLastLine() - for (item, isLast) in items.enumeratedWithLastMarker() { - renderExpression(item) - if !isLast { - writer.nextLineAppendsToLastLine() - writer.writeLine(", ") + if !items.isEmpty { + writer.withNestedLevel { + for (item, isLast) in items.enumeratedWithLastMarker() { + renderExpression(item) + if !isLast { + writer.nextLineAppendsToLastLine() + writer.writeLine(",") + } + } } + } else { writer.nextLineAppendsToLastLine() } writer.writeLine("]") diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStructBlueprint.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStructBlueprint.swift index 5c948f0c..4513058f 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStructBlueprint.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStructBlueprint.swift @@ -67,7 +67,7 @@ extension FileTranslator { /// - Returns: A `Declaration` representing the translated struct. func translateStructBlueprintInitializer(typeName: TypeName, properties: [PropertyBlueprint]) -> Declaration { - let comment: Comment = .doc(properties.initializerComment(typeName: typeName.shortSwiftName)) + let comment: Comment = properties.initializerComment(typeName: typeName.shortSwiftName) let decls: [(ParameterDescription, String)] = properties.map { property in ( @@ -145,19 +145,11 @@ fileprivate extension Array where Element == PropertyBlueprint { /// the properties contained in the current array. /// - Parameter typeName: The name of the structure type. /// - Returns: A comment string describing the initializer. - func initializerComment(typeName: String) -> String { - var components: [String] = ["Creates a new `\(typeName)`."] - if !isEmpty { - var parameterComponents: [String] = [] - parameterComponents.append("- Parameters:") - for parameter in self { - parameterComponents.append( - " - \(parameter.swiftSafeName):\(parameter.comment?.firstLineOfContent.map { " \($0)" } ?? "")" - ) - } - components.append("") - components.append(parameterComponents.joined(separator: "\n")) - } - return components.joined(separator: "\n") + func initializerComment(typeName: String) -> Comment { + Comment.functionComment( + abstract: "Creates a new `\(typeName)`.", + parameters: map { ($0.swiftSafeName, $0.comment?.firstLineOfContent) } + )! // This force-unwrap is safe as the method never returns nil when + // a non-nil abstract is provided. } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/CommentExtensions.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/CommentExtensions.swift index 739b74fb..3093aa6a 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/CommentExtensions.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/CommentExtensions.swift @@ -238,7 +238,30 @@ extension Comment { } ) } - + /// Returns a documentation comment for a function with the provided + /// parameters. + /// - Parameters: + /// - abstract: The documentation of the function. + /// - parameters: The parameters. + /// - Returns: A documentation comment for the function. + static func functionComment(abstract: String?, parameters: [(name: String, comment: String?)]) -> Comment? { + guard !parameters.isEmpty else { return abstract.map { .doc($0) } } + var components: [String] = abstract.map { [$0] } ?? [] + var parameterComponents: [String] = [] + parameterComponents.append("- Parameters:") + for (name, comment) in parameters { + let parameterComment: String + if let comment { + parameterComment = Comment.doc(comment).firstLineOfContent.map { " \($0)" } ?? "" + } else { + parameterComment = "" + } + parameterComponents.append(" - \(name):\(parameterComment)") + } + components.append("") + components.append(parameterComponents.joined(separator: "\n")) + return .doc(components.joined(separator: "\n")) + } } extension ComponentDictionaryLocatable { diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServers.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServers.swift index 6979e1ea..4ead49bc 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServers.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServers.swift @@ -25,12 +25,33 @@ extension TypesFileTranslator { /// declare the method under. func translateServer(index: Int, server: OpenAPI.Server) -> Declaration { let methodName = "\(Constants.ServerURL.propertyPrefix)\(index+1)" + let parameters: [ParameterDescription] = server.variables.map { (key, value) in + .init(label: key, type: .init(TypeName.string), defaultValue: .literal(value.default)) + } + let variableInitializers: [Expression] = server.variables.map { (key, value) in + let allowedValuesArg: FunctionArgumentDescription? + if let allowedValues = value.enum { + allowedValuesArg = .init( + label: "allowedValues", + expression: .literal(.array(allowedValues.map { .literal($0) })) + ) + } else { + allowedValuesArg = nil + } + return .dot("init") + .call( + [ + .init(label: "name", expression: .literal(key)), + .init(label: "value", expression: .identifierPattern(key)), + ] + (allowedValuesArg.flatMap { [$0] } ?? []) + ) + } let methodDecl = Declaration.commentable( - server.description.flatMap { .doc($0) }, + .functionComment(abstract: server.description, parameters: server.variables.map { ($0, $1.description) }), .function( accessModifier: config.access, kind: .function(name: methodName, isStatic: true), - parameters: [], + parameters: parameters, keywords: [.throws], returnType: .identifierType(TypeName.url), body: [ @@ -41,7 +62,7 @@ extension TypesFileTranslator { .init( label: "validatingOpenAPIServerURL", expression: .literal(.string(server.urlTemplate.absoluteString)) - ) + ), .init(label: "variables", expression: .literal(.array(variableInitializers))), ]) ) ) diff --git a/Tests/OpenAPIGeneratorCoreTests/Renderer/Test_TextBasedRenderer.swift b/Tests/OpenAPIGeneratorCoreTests/Renderer/Test_TextBasedRenderer.swift index dbb53e7f..c50ce589 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Renderer/Test_TextBasedRenderer.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Renderer/Test_TextBasedRenderer.swift @@ -180,14 +180,19 @@ final class Test_TextBasedRenderer: XCTestCase { .array([.literal(.nil)]), renderedBy: TextBasedRenderer.renderLiteral, rendersAs: #""" - [nil] + [ + nil + ] """# ) try _test( .array([.literal(.nil), .literal(.nil)]), renderedBy: TextBasedRenderer.renderLiteral, rendersAs: #""" - [nil, nil] + [ + nil, + nil + ] """# ) } diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/Docs/petstore.yaml b/Tests/OpenAPIGeneratorReferenceTests/Resources/Docs/petstore.yaml index ec86e169..727604af 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/Docs/petstore.yaml +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/Docs/petstore.yaml @@ -10,6 +10,20 @@ servers: - url: https://example.com/api description: Example Petstore implementation service - url: /api + - url: https://{subdomain}.example.com:{port}/{basePath} + description: A custom domain. + variables: + subdomain: + default: test + description: A subdomain name. + port: + enum: + - '443' + - 8443 + default: '443' + basePath: + default: v1 + description: The base API path. paths: /pets: summary: Work with pets diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift index 1928b30c..7c698711 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift @@ -473,7 +473,9 @@ public struct Client: APIProtocol { serializer: { input in let path = try converter.renderedPath( template: "/pets/{}", - parameters: [input.path.petId] + parameters: [ + input.path.petId + ] ) var request: HTTPTypes.HTTPRequest = .init( soar_path: path, @@ -539,7 +541,9 @@ public struct Client: APIProtocol { serializer: { input in let path = try converter.renderedPath( template: "/pets/{}/avatar", - parameters: [input.path.petId] + parameters: [ + input.path.petId + ] ) var request: HTTPTypes.HTTPRequest = .init( soar_path: path, diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift index dabd6078..92ab82f4 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift @@ -139,10 +139,49 @@ extension APIProtocol { public enum Servers { /// Example Petstore implementation service public static func server1() throws -> Foundation.URL { - try Foundation.URL(validatingOpenAPIServerURL: "https://example.com/api") + try Foundation.URL( + validatingOpenAPIServerURL: "https://example.com/api", + variables: [] + ) } public static func server2() throws -> Foundation.URL { - try Foundation.URL(validatingOpenAPIServerURL: "/api") + try Foundation.URL( + validatingOpenAPIServerURL: "/api", + variables: [] + ) + } + /// A custom domain. + /// + /// - Parameters: + /// - subdomain: A subdomain name. + /// - port: + /// - basePath: The base API path. + public static func server3( + subdomain: Swift.String = "test", + port: Swift.String = "443", + basePath: Swift.String = "v1" + ) throws -> Foundation.URL { + try Foundation.URL( + validatingOpenAPIServerURL: "https://{subdomain}.example.com:{port}/{basePath}", + variables: [ + .init( + name: "subdomain", + value: subdomain + ), + .init( + name: "port", + value: port, + allowedValues: [ + "443", + "8443" + ] + ), + .init( + name: "basePath", + value: basePath + ) + ] + ) } } @@ -233,13 +272,22 @@ public enum Components { value3 = try? .init(from: decoder) value4 = try? decoder.decodeFromSingleValueContainer() try Swift.DecodingError.verifyAtLeastOneSchemaIsNotNil( - [value1, value2, value3, value4], + [ + value1, + value2, + value3, + value4 + ], type: Self.self, codingPath: decoder.codingPath ) } public func encode(to encoder: any Encoder) throws { - try encoder.encodeFirstNonNilValueToSingleValueContainer([value1, value2, value4]) + try encoder.encodeFirstNonNilValueToSingleValueContainer([ + value1, + value2, + value4 + ]) try value3?.encode(to: encoder) } } @@ -438,7 +486,9 @@ public enum Components { Swift.String.self, forKey: .foo ) - try decoder.ensureNoAdditionalProperties(knownKeys: ["foo"]) + try decoder.ensureNoAdditionalProperties(knownKeys: [ + "foo" + ]) } } /// - Remark: Generated from `#/components/schemas/AnyAdditionalProperties`. @@ -468,7 +518,9 @@ public enum Components { Swift.String.self, forKey: .foo ) - additionalProperties = try decoder.decodeAdditionalProperties(knownKeys: ["foo"]) + additionalProperties = try decoder.decodeAdditionalProperties(knownKeys: [ + "foo" + ]) } public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -506,7 +558,9 @@ public enum Components { Swift.String.self, forKey: .foo ) - additionalProperties = try decoder.decodeAdditionalProperties(knownKeys: ["foo"]) + additionalProperties = try decoder.decodeAdditionalProperties(knownKeys: [ + "foo" + ]) } public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -611,7 +665,10 @@ public enum Components { value1 = try? .init(from: decoder) value2 = try? .init(from: decoder) try Swift.DecodingError.verifyAtLeastOneSchemaIsNotNil( - [value1, value2], + [ + value1, + value2 + ], type: Self.self, codingPath: decoder.codingPath ) @@ -1194,13 +1251,18 @@ public enum Components { value1 = try? .init(from: decoder) value2 = try? decoder.decodeFromSingleValueContainer() try Swift.DecodingError.verifyAtLeastOneSchemaIsNotNil( - [value1, value2], + [ + value1, + value2 + ], type: Self.self, codingPath: decoder.codingPath ) } func encode(to encoder: any Encoder) throws { - try encoder.encodeFirstNonNilValueToSingleValueContainer([value2]) + try encoder.encodeFirstNonNilValueToSingleValueContainer([ + value2 + ]) try value1?.encode(to: encoder) } } @@ -1651,7 +1713,9 @@ public enum Operations { } } public static var allCases: [Self] { - [.json] + [ + .json + ] } } } @@ -1823,7 +1887,9 @@ public enum Operations { } } public static var allCases: [Self] { - [.json] + [ + .json + ] } } } @@ -2037,7 +2103,11 @@ public enum Operations { } } public static var allCases: [Self] { - [.json, .plainText, .binary] + [ + .json, + .plainText, + .binary + ] } } } @@ -2310,7 +2380,9 @@ public enum Operations { } } public static var allCases: [Self] { - [.json] + [ + .json + ] } } } @@ -2559,7 +2631,11 @@ public enum Operations { } } public static var allCases: [Self] { - [.binary, .json, .plainText] + [ + .binary, + .json, + .plainText + ] } } } diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index 285fa3f6..476ed3f8 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -418,13 +418,21 @@ final class SnippetBasedReferenceTests: XCTestCase { value3 = try? decoder.decodeFromSingleValueContainer() value4 = try? decoder.decodeFromSingleValueContainer() try Swift.DecodingError.verifyAtLeastOneSchemaIsNotNil( - [value1, value2, value3, value4], + [ + value1, + value2, + value3, + value4 + ], type: Self.self, codingPath: decoder.codingPath ) } public func encode(to encoder: any Encoder) throws { - try encoder.encodeFirstNonNilValueToSingleValueContainer([value3, value4]) + try encoder.encodeFirstNonNilValueToSingleValueContainer([ + value3, + value4 + ]) try value1?.encode(to: encoder) try value2?.encode(to: encoder) } @@ -748,7 +756,10 @@ final class SnippetBasedReferenceTests: XCTestCase { value1 = try? .init(from: decoder) value2 = try? .init(from: decoder) try Swift.DecodingError.verifyAtLeastOneSchemaIsNotNil( - [value1, value2], + [ + value1, + value2 + ], type: Self.self, codingPath: decoder.codingPath ) @@ -960,13 +971,19 @@ final class SnippetBasedReferenceTests: XCTestCase { value1 = try? decoder.decodeFromSingleValueContainer() value2 = try? decoder.decodeFromSingleValueContainer() try Swift.DecodingError.verifyAtLeastOneSchemaIsNotNil( - [value1, value2], + [ + value1, + value2 + ], type: Self.self, codingPath: decoder.codingPath ) } public func encode(to encoder: any Encoder) throws { - try encoder.encodeFirstNonNilValueToSingleValueContainer([value1, value2]) + try encoder.encodeFirstNonNilValueToSingleValueContainer([ + value1, + value2 + ]) } } } @@ -1352,13 +1369,18 @@ final class SnippetBasedReferenceTests: XCTestCase { value1 = try? .init(from: decoder) value2 = try? decoder.decodeFromSingleValueContainer() try Swift.DecodingError.verifyAtLeastOneSchemaIsNotNil( - [value1, value2], + [ + value1, + value2 + ], type: Self.self, codingPath: decoder.codingPath ) } func encode(to encoder: any Encoder) throws { - try encoder.encodeFirstNonNilValueToSingleValueContainer([value2]) + try encoder.encodeFirstNonNilValueToSingleValueContainer([ + value2 + ]) try value1?.encode(to: encoder) } } diff --git a/Tests/PetstoreConsumerTests/Test_Types.swift b/Tests/PetstoreConsumerTests/Test_Types.swift index 580e58ff..2e06767b 100644 --- a/Tests/PetstoreConsumerTests/Test_Types.swift +++ b/Tests/PetstoreConsumerTests/Test_Types.swift @@ -16,51 +16,42 @@ import XCTest import PetstoreConsumerTestCore final class Test_Types: XCTestCase { - /// Setup method called before the invocation of each test method in the class. override func setUp() async throws { try await super.setUp() continueAfterFailure = false } - func testStructCodingKeys() throws { let cases: [(Components.Schemas._Error.CodingKeys, String)] = [(.code, "code"), (.me_dollar_sage, "me$sage")] for (value, rawValue) in cases { XCTAssertEqual(value.rawValue, rawValue) } } - func testEnumCoding() throws { let cases: [(Components.Schemas.PetKind, String)] = [(.cat, "cat"), (._dollar_nake, "$nake")] for (value, rawValue) in cases { XCTAssertEqual(value.rawValue, rawValue) } } - var testEncoder: JSONEncoder { let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 encoder.outputFormatting = [.sortedKeys] return encoder } - var testDecoder: JSONDecoder { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 return decoder } - func roundtrip(_ value: T, verifyingJSON: String? = nil) throws -> T { let data = try testEncoder.encode(value) if let verifyingJSON { XCTAssertEqual(String(decoding: data, as: UTF8.self), verifyingJSON) } return try testDecoder.decode(T.self, from: data) } - func _testRoundtrip(_ value: T, verifyingJSON: String? = nil) throws { let decodedValue = try roundtrip(value, verifyingJSON: verifyingJSON) XCTAssertEqual(decodedValue, value) } - func testNoAdditionalPropertiesCoding_roundtrip() throws { try _testRoundtrip(Components.Schemas.NoAdditionalProperties(foo: "hi")) } - func testNoAdditionalPropertiesCoding_extraProperty() throws { XCTAssertThrowsError( try testDecoder.decode( @@ -69,11 +60,9 @@ final class Test_Types: XCTestCase { ) ) } - func testAnyAdditionalPropertiesCoding_roundtrip_noExtraProperty() throws { try _testRoundtrip(Components.Schemas.AnyAdditionalProperties(foo: "hi", additionalProperties: .init())) } - func testAnyAdditionalPropertiesCoding_roundtrip_withExtraProperty() throws { try _testRoundtrip( Components.Schemas.AnyAdditionalProperties( @@ -82,19 +71,15 @@ final class Test_Types: XCTestCase { ) ) } - func testTypedAdditionalPropertiesCoding_roundtrip_noExtraProperty() throws { try _testRoundtrip(Components.Schemas.TypedAdditionalProperties(foo: "hi", additionalProperties: [:])) } - func testTypedAdditionalPropertiesCoding_roundtrip_withExtraProperty() throws { try _testRoundtrip(Components.Schemas.TypedAdditionalProperties(foo: "hi", additionalProperties: ["hello": 1])) } - func testAllOf_roundtrip() throws { try _testRoundtrip(Components.Schemas.AllOfObjects(value1: .init(message: "hi"), value2: .init(code: 1))) } - func testAllAnyOneOf_withDate_roundtrip() throws { func testJSON( _ value: T, @@ -107,7 +92,6 @@ final class Test_Types: XCTestCase { let decodedValue = try testDecoder.decode(T.self, from: data) XCTAssertEqual(decodedValue, value, file: file, line: line) } - try testJSON( Components.Schemas.MixedAnyOf( value1: Date(timeIntervalSince1970: 1_674_036_251), @@ -123,7 +107,6 @@ final class Test_Types: XCTestCase { Components.Schemas.MixedAnyOf(value3: .init(id: 1, name: "Fluffz")), expectedJSON: #"{"id":1,"name":"Fluffz"}"# ) - try testJSON( Components.Schemas.MixedOneOf.case1(Date(timeIntervalSince1970: 1_674_036_251)), expectedJSON: #""2023-01-18T10:04:11Z""# @@ -133,7 +116,6 @@ final class Test_Types: XCTestCase { Components.Schemas.MixedOneOf.Pet(.init(id: 1, name: "Fluffz")), expectedJSON: #"{"id":1,"name":"Fluffz"}"# ) - try testJSON( Components.Schemas.MixedAllOfPrimitive( value1: Date(timeIntervalSince1970: 1_674_036_251), @@ -142,7 +124,6 @@ final class Test_Types: XCTestCase { expectedJSON: #""2023-01-18T10:04:11Z""# ) } - func testAllOf_missingProperty() throws { XCTAssertThrowsError(try testDecoder.decode(Components.Schemas.AllOfObjects.self, from: Data(#"{}"#.utf8))) XCTAssertThrowsError( @@ -152,24 +133,20 @@ final class Test_Types: XCTestCase { try testDecoder.decode(Components.Schemas.AllOfObjects.self, from: Data(#"{"code":1}"#.utf8)) ) } - func testAnyOf_roundtrip() throws { try _testRoundtrip(Components.Schemas.AnyOfObjects(value1: .init(message: "hi"), value2: .init(code: 1))) try _testRoundtrip(Components.Schemas.AnyOfObjects(value1: .init(message: "hi"), value2: nil)) try _testRoundtrip(Components.Schemas.AnyOfObjects(value1: nil, value2: .init(code: 1))) } - func testAnyOf_allFailedToDecode() throws { XCTAssertThrowsError(try testDecoder.decode(Components.Schemas.AnyOfObjects.self, from: Data(#"{}"#.utf8))) } - func testOneOfAny_roundtrip() throws { try _testRoundtrip(Components.Schemas.OneOfAny.case1("hi")) try _testRoundtrip(Components.Schemas.OneOfAny.case2(1)) try _testRoundtrip(Components.Schemas.OneOfAny.CodeError(.init(code: 2))) try _testRoundtrip(Components.Schemas.OneOfAny.case4(.init(message: "hello"))) } - func testOneOfWithDiscriminator_roundtrip() throws { try _testRoundtrip(Components.Schemas.OneOfObjectsWithDiscriminator.Walk(.init(kind: "Walk", length: 1))) try _testRoundtrip( @@ -178,13 +155,11 @@ final class Test_Types: XCTestCase { ) ) } - func testOneOfWithDiscriminator_invalidDiscriminator() throws { XCTAssertThrowsError( try testDecoder.decode(Components.Schemas.OneOfObjectsWithDiscriminator.self, from: Data(#"{}"#.utf8)) ) } - func testThrowingShorthandAPIs() throws { let created = Operations.createPet.Output.Created(body: .json(.init(id: 42, name: "Scruffy"))) let output = Operations.createPet.Output.created(created) @@ -197,7 +172,6 @@ final class Test_Types: XCTestCase { return } } - let stats = Components.Schemas.PetStats(count: 42) let ok = Operations.getStats.Output.Ok(body: .json(stats)) XCTAssertEqual(try ok.body.json, stats) @@ -210,14 +184,12 @@ final class Test_Types: XCTestCase { } } } - func testRecursiveType_roundtrip() throws { try _testRoundtrip( Components.Schemas.RecursivePet(name: "C", parent: .init(name: "B", parent: .init(name: "A"))), verifyingJSON: #"{"name":"C","parent":{"name":"B","parent":{"name":"A"}}}"# ) } - func testRecursiveType_accessors_3levels() throws { var c = Components.Schemas.RecursivePet(name: "C", parent: .init(name: "B")) c.name = "C2" @@ -228,7 +200,6 @@ final class Test_Types: XCTestCase { Components.Schemas.RecursivePet(name: "C2", parent: .init(name: "B", parent: .init(name: "A"))) ) } - func testRecursiveType_accessors_2levels() throws { var b = Components.Schemas.RecursivePet(name: "B") b.name = "B2" @@ -236,7 +207,6 @@ final class Test_Types: XCTestCase { XCTAssertEqual(b.parent, .init(name: "A")) XCTAssertEqual(b, .init(name: "B2", parent: .init(name: "A"))) } - func testRecursiveNestedType_roundtrip() throws { try _testRoundtrip( Components.Schemas.RecursivePetNested( @@ -246,4 +216,25 @@ final class Test_Types: XCTestCase { verifyingJSON: #"{"name":"C","parent":{"nested":{"name":"B","parent":{"nested":{"name":"A"}}}}}"# ) } + func testServers_1() throws { XCTAssertEqual(try Servers.server1(), URL(string: "https://example.com/api")) } + func testServers_2() throws { XCTAssertEqual(try Servers.server2(), URL(string: "/api")) } + func testServers_3() throws { + XCTAssertEqual(try Servers.server3(), URL(string: "https://test.example.com:443/v1")) + XCTAssertEqual( + try Servers.server3(subdomain: "bar", port: "8443", basePath: "v2/staging"), + URL(string: "https://bar.example.com:8443/v2/staging") + ) + XCTAssertThrowsError(try Servers.server3(port: "foo")) { error in + guard + case let .invalidServerVariableValue(name: name, value: value, allowedValues: allowedValues) = error + as? RuntimeError + else { + XCTFail("Expected error, but not this: \(error)") + return + } + XCTAssertEqual(name, "port") + XCTAssertEqual(value, "foo") + XCTAssertEqual(allowedValues, ["443", "8443"]) + } + } }