diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 2b9461a74..564bedc70 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -5,7 +5,7 @@ on: [pull_request] jobs: codecov: container: - image: swift:5.5 + image: swift:5.7 runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 27ee0bdfe..7b83d1b24 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,10 +62,16 @@ jobs: - swift:5.5-focal - swift:5.5-centos8 - swift:5.5-amazonlinux2 - - swiftlang/swift:nightly-xenial + - swift:5.6-bionic + - swift:5.6-focal + - swift:5.6-amazonlinux2 + - swift:5.7-bionic + - swift:5.7-focal + - swift:5.7-jammy + - swift:5.7-amazonlinux2 - swiftlang/swift:nightly-bionic - swiftlang/swift:nightly-focal - - swiftlang/swift:nightly-centos8 + - swiftlang/swift:nightly-jammy - swiftlang/swift:nightly-amazonlinux2 container: ${{ matrix.image }} steps: @@ -74,10 +80,10 @@ jobs: - name: Run tests run: swift test osx: - runs-on: macOS-11 + runs-on: macOS-12 steps: - name: Select latest available Xcode - uses: maxim-lobanov/setup-xcode@v1.2.3 + uses: maxim-lobanov/setup-xcode@v1.5.1 with: xcode-version: latest - name: Checkout code diff --git a/Package.swift b/Package.swift index 9becff554..d93c76bb4 100644 --- a/Package.swift +++ b/Package.swift @@ -15,6 +15,9 @@ let package = Package( .library( name: "OpenAPIKit", targets: ["OpenAPIKit"]), + .library( + name: "OpenAPIKitCompat", + targets: ["OpenAPIKitCompat"]), ], dependencies: [ .package(url: "https://github.com/jpsim/Yams.git", "4.0.0"..<"6.0.0") // just for tests @@ -60,7 +63,14 @@ let package = Package( dependencies: ["OpenAPIKit", "Yams"]), .testTarget( name: "OpenAPIKitErrorReportingTests", - dependencies: ["OpenAPIKit", "Yams"]) + dependencies: ["OpenAPIKit", "Yams"]), + + .target( + name: "OpenAPIKitCompat", + dependencies: ["OpenAPIKit30", "OpenAPIKit"]), + .testTarget( + name: "OpenAPIKitCompatTests", + dependencies: ["OpenAPIKitCompat"]) ], swiftLanguageVersions: [ .v5 ] ) diff --git a/Sources/OpenAPIKit/Callbacks.swift b/Sources/OpenAPIKit/Callbacks.swift index 676bd9dfd..3b0e61dc4 100644 --- a/Sources/OpenAPIKit/Callbacks.swift +++ b/Sources/OpenAPIKit/Callbacks.swift @@ -10,46 +10,6 @@ import Foundation extension OpenAPI { - /// A URL template where the placeholders are OpenAPI **Runtime Expressions** instead - /// of named variables. - /// - /// See [OpenAPI Callback Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#callback-object) and [OpenAPI Runtime Expression](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#runtime-expressions) for more. - /// - public struct CallbackURL: Hashable, RawRepresentable { - public let template: URLTemplate - - /// The string value of the URL without variable replacement. - /// - /// Variables cannot be replaced based on other information in the - /// OpenAPI document; they are only available at "runtime" which is - /// where the name of the OpenAPI structure `CallbackURL` - /// represents comes from. - public var rawValue: String { - template.rawValue - } - - /// Get a URL from the runtime expression if it is a valid URL without - /// variable replacement. - /// - /// Callback URLs with variables in them will not be valid URLs - /// and are therefore guaranteed to return `nil`. - public var url: URL? { - template.url - } - - /// Create a CallbackURL from the string if possible. - public init?(rawValue: String) { - guard let template = URLTemplate(rawValue: rawValue) else { - return nil - } - self.template = template - } - - public init(url: URL) { - template = .init(url: url) - } - } - /// A map from runtime expressions to path items to be used as /// callbacks for the API. The OpenAPI Spec "Callback Object." /// @@ -60,19 +20,3 @@ extension OpenAPI { /// A map of named collections of Callback Objects (`OpenAPI.Callbacks`). public typealias CallbacksMap = OrderedDictionary, Callbacks>> } - -extension OpenAPI.CallbackURL: Encodable { - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - - try container.encode(rawValue) - } -} - -extension OpenAPI.CallbackURL: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - - template = try container.decode(URLTemplate.self) - } -} diff --git a/Sources/OpenAPIKit/Components Object/Components.swift b/Sources/OpenAPIKit/Components Object/Components.swift index b818eb338..599e5788b 100644 --- a/Sources/OpenAPIKit/Components Object/Components.swift +++ b/Sources/OpenAPIKit/Components Object/Components.swift @@ -72,64 +72,6 @@ extension OpenAPI { } extension OpenAPI { - /// A key for one of the component dictionaries. - /// - /// These keys must match the regex - /// `^[a-zA-Z0-9\.\-_]+$`. - public struct ComponentKey: RawRepresentable, ExpressibleByStringLiteral, Codable, Equatable, Hashable, StringConvertibleHintProvider { - public let rawValue: String - - public init(stringLiteral value: StringLiteralType) { - self.rawValue = value - } - - public init?(rawValue: String) { - guard !rawValue.isEmpty else { - return nil - } - var allowedCharacters = CharacterSet.alphanumerics - allowedCharacters.insert(charactersIn: "-_.") - guard CharacterSet(charactersIn: rawValue).isSubset(of: allowedCharacters) else { - return nil - } - self.rawValue = rawValue - } - - public static func problem(with proposedString: String) -> String? { - if Self(rawValue: proposedString) == nil { - return "Keys for components in the Components Object must conform to the regex `^[a-zA-Z0-9\\.\\-_]+$`. '\(proposedString)' does not.." - } - return nil - } - - public init(from decoder: Decoder) throws { - let rawValue = try decoder.singleValueContainer().decode(String.self) - guard let key = Self(rawValue: rawValue) else { - throw InconsistencyError( - subjectName: "Component Key", - details: "Keys for components in the Components Object must conform to the regex `^[a-zA-Z0-9\\.\\-_]+$`. '\(rawValue)' does not..", - codingPath: decoder.codingPath - ) - } - self = key - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - - // we check for consistency on encode because a string literal - // may result in an invalid component key being constructed. - guard Self(rawValue: rawValue) != nil else { - throw InconsistencyError( - subjectName: "Component Key", - details: "Keys for components in the Components Object must conform to the regex `^[a-zA-Z0-9\\.\\-_]+$`. '\(rawValue)' does not..", - codingPath: container.codingPath - ) - } - - try container.encode(rawValue) - } - } public typealias ComponentDictionary = OrderedDictionary } diff --git a/Sources/OpenAPIKitCore/OpenAPI.swift b/Sources/OpenAPIKit/OpenAPI.swift similarity index 100% rename from Sources/OpenAPIKitCore/OpenAPI.swift rename to Sources/OpenAPIKit/OpenAPI.swift diff --git a/Sources/OpenAPIKit/Parameter/ParameterContext.swift b/Sources/OpenAPIKit/Parameter/ParameterContext.swift index 8d148f466..13119e83b 100644 --- a/Sources/OpenAPIKit/Parameter/ParameterContext.swift +++ b/Sources/OpenAPIKit/Parameter/ParameterContext.swift @@ -73,13 +73,6 @@ extension OpenAPI.Parameter { } extension OpenAPI.Parameter.Context { - public enum Location: String, CaseIterable, Codable { - case query - case header - case path - case cookie - } - public var location: Location { switch self { case .query: @@ -93,5 +86,3 @@ extension OpenAPI.Parameter.Context { } } } - -extension OpenAPI.Parameter.Context.Location: Validatable {} diff --git a/Sources/OpenAPIKit/Parameter/ParameterSchemaContext.swift b/Sources/OpenAPIKit/Parameter/ParameterSchemaContext.swift index 583396bee..3af3e8ff2 100644 --- a/Sources/OpenAPIKit/Parameter/ParameterSchemaContext.swift +++ b/Sources/OpenAPIKit/Parameter/ParameterSchemaContext.swift @@ -127,41 +127,31 @@ extension OpenAPI.Parameter { } } -extension OpenAPI.Parameter.SchemaContext { - public enum Style: String, CaseIterable, Codable { - case form - case simple - case matrix - case label - case spaceDelimited - case pipeDelimited - case deepObject - - /// Get the default `Style` for the given location - /// per the OpenAPI Specification. - /// - /// See the `style` fixed field under - /// [OpenAPI Parameter Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-object). - public static func `default`(for location: OpenAPI.Parameter.Context) -> Self { - switch location { - case .query: - return .form - case .cookie: - return .form - case .path: - return .simple - case .header: - return .simple - } +extension OpenAPI.Parameter.SchemaContext.Style { + /// Get the default `Style` for the given location + /// per the OpenAPI Specification. + /// + /// See the `style` fixed field under + /// [OpenAPI Parameter Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-object). + public static func `default`(for location: OpenAPI.Parameter.Context) -> Self { + switch location { + case .query: + return .form + case .cookie: + return .form + case .path: + return .simple + case .header: + return .simple } + } - internal var defaultExplode: Bool { - switch self { - case .form: - return true - default: - return false - } + internal var defaultExplode: Bool { + switch self { + case .form: + return true + default: + return false } } } diff --git a/Sources/OpenAPIKit/Path Item/PathItem.swift b/Sources/OpenAPIKit/Path Item/PathItem.swift index 2ea0b5bdc..326d1dea0 100644 --- a/Sources/OpenAPIKit/Path Item/PathItem.swift +++ b/Sources/OpenAPIKit/Path Item/PathItem.swift @@ -7,37 +7,6 @@ import OpenAPIKitCore -extension OpenAPI { - /// OpenAPI Spec "Paths Object" path field pattern support. - /// - /// See [OpenAPI Paths Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#paths-object) - /// and [OpenAPI Patterned Fields](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#patterned-fields). - public struct Path: RawRepresentable, Equatable, Hashable { - public let components: [String] - - public init(_ components: [String]) { - self.components = components - } - - public init(rawValue: String) { - let pathComponents = rawValue.split(separator: "/").map(String.init) - components = pathComponents.count > 0 && pathComponents[0].isEmpty - ? Array(pathComponents.dropFirst()) - : pathComponents - } - - public var rawValue: String { - return "/\(components.joined(separator: "/"))" - } - } -} - -extension OpenAPI.Path: ExpressibleByStringLiteral { - public init(stringLiteral value: String) { - self.init(rawValue: value) - } -} - extension OpenAPI { /// OpenAPI Spec "Path Item Object" /// diff --git a/Sources/OpenAPIKit/Response/Response.swift b/Sources/OpenAPIKit/Response/Response.swift index bd940397f..f85a83e2f 100644 --- a/Sources/OpenAPIKit/Response/Response.swift +++ b/Sources/OpenAPIKit/Response/Response.swift @@ -68,109 +68,6 @@ extension OrderedDictionary where Key == OpenAPI.Response.StatusCode { } } -// MARK: - Status Code -extension OpenAPI.Response { - /// An HTTP Status code or status code range. - /// - /// OpenAPI supports one of the following as a key in the [Responses Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#responses-object): - /// - A `default` entry. - /// - A specific status code. - /// - A status code range. - /// - /// The `.default` case is used for a default entry. - /// - /// You can use integer literals to specify an exact status code. - /// - /// Status code ranges are named in the `StatusCode.Range` enum. For example, the "1XX" range (100-199) can be written as either `.range(.information)` or as `.range(.init(rawValue: "1XX"))`. - public struct StatusCode: RawRepresentable, Equatable, Hashable, HasWarnings { - public typealias RawValue = String - - public let warnings: [OpenAPI.Warning] - - public var value: Code - - internal init(value: Code) { - self.value = value - warnings = [] - } - - public static let `default`: Self = .init(value: .default) - public static func range(_ range: Range) -> Self { .init(value: .range(range)) } - public static func status(code: Int) -> Self { .init(value: .status(code: code)) } - - public enum Code: Equatable, Hashable { - case `default` - case range(Range) - case status(code: Int) - } - - public enum Range: String { - /// Status Code `100-199` - case information = "1XX" - /// Status Code `200-299` - case success = "2XX" - /// Status Code `300-399` - case redirect = "3XX" - /// Status Code `400-499` - case clientError = "4XX" - /// Status Code `500-599` - case serverError = "5XX" - } - - public var rawValue: String { - switch value { - case .default: - return "default" - - case .range(let range): - return range.rawValue - - case .status(code: let code): - return String(code) - } - } - - public var isSuccess: Bool { - switch value { - case .range(.success), .status(code: 200..<300): - return true - case .range, .status, .default: - return false - } - } - - public init?(rawValue: String) { - if let val = Int(rawValue) { - value = .status(code: val) - warnings = [] - } else if rawValue == "default" { - value = .default - warnings = [] - } else if let range = Range(rawValue: rawValue.uppercased()) { - value = .range(range) - warnings = [] - } else if rawValue.contains("/"), - let first = (rawValue.split(separator: "/")).first, - let fallback = Self(rawValue: String(first)) { - value = fallback.value - warnings = [ - .message("Found non-compliant Status Code '\(rawValue)' but was able to parse as \(first)") - ] - } else { - return nil - } - } - } -} - -extension OpenAPI.Response.StatusCode: ExpressibleByIntegerLiteral { - - public init(integerLiteral value: Int) { - self.value = .status(code: value) - warnings = [] - } -} - // MARK: `Either` convenience methods extension Either where A == OpenAPI.Reference, B == OpenAPI.Response { diff --git a/Sources/OpenAPIKit/Schema Object/JSONSchema+Combining.swift b/Sources/OpenAPIKit/Schema Object/JSONSchema+Combining.swift index 752f99c49..288a01392 100644 --- a/Sources/OpenAPIKit/Schema Object/JSONSchema+Combining.swift +++ b/Sources/OpenAPIKit/Schema Object/JSONSchema+Combining.swift @@ -286,7 +286,7 @@ extension JSONSchema.CoreContext where Format == JSONTypeFormat.AnyFormat { format: newFormat, required: required, nullable: nullable, - permissions: _permissions.map(OtherContext.Permissions.init), + permissions: _permissions, deprecated: _deprecated, title: title, description: description, @@ -312,7 +312,7 @@ extension JSONSchema.CoreContext { if let conflict = conflicting(_permissions, other._permissions) { throw JSONSchemaResolutionError(.inconsistency("A schema cannot be both \(conflict.0.rawValue) and \(conflict.1.rawValue).")) } - let newPermissions: JSONSchema.CoreContext.Permissions? + let newPermissions: JSONSchema.Permissions? if _permissions == nil && other._permissions == nil { newPermissions = nil } else { @@ -467,7 +467,7 @@ extension JSONSchema.StringContext { if let conflict = conflicting(maxLength, other.maxLength) { throw JSONSchemaResolutionError(.attributeConflict(jsonType: .string, name: "maxLength", original: String(conflict.0), new: String(conflict.1))) } - if let conflict = conflicting(_minLength, other._minLength) { + if let conflict = conflicting(Self._minLength(self), Self._minLength(other)) { throw JSONSchemaResolutionError(.attributeConflict(jsonType: .string, name: "minLength", original: String(conflict.0), new: String(conflict.1))) } if let conflict = conflicting(pattern, other.pattern) { @@ -476,7 +476,7 @@ extension JSONSchema.StringContext { // explicitly declaring these constants one at a time // helps the type checker a lot. let newMaxLength = maxLength ?? other.maxLength - let newMinLength = _minLength ?? other._minLength + let newMinLength = Self._minLength(self) ?? Self._minLength(other) let newPattern = pattern ?? other.pattern return .init( maxLength: newMaxLength, @@ -577,13 +577,12 @@ extension JSONSchema.CoreContext { guard let newFormat = NewFormat(rawValue: format.rawValue) else { throw JSONSchemaResolutionError(.inconsistency("Tried to create a \(NewFormat.self) from the incompatible format value: \(format.rawValue)")) } - let newPermissions = _permissions.map(JSONSchema.CoreContext.Permissions.init) return .init( format: newFormat, required: required, nullable: nullable, - permissions: newPermissions, + permissions: _permissions, deprecated: _deprecated, title: title, description: description, @@ -648,7 +647,7 @@ extension JSONSchema.NumericContext { extension JSONSchema.StringContext { internal func validatedContext() throws -> JSONSchema.StringContext { - if let minimum = _minLength { + if let minimum = Self._minLength(self) { guard minimum >= 0 else { throw JSONSchemaResolutionError(.inconsistency("String minimum length (\(minimum) cannot be less than 0")) } @@ -660,7 +659,7 @@ extension JSONSchema.StringContext { } return .init( maxLength: maxLength, - minLength: _minLength, + minLength: Self._minLength(self), pattern: pattern ) } diff --git a/Sources/OpenAPIKit/Schema Object/JSONSchema.swift b/Sources/OpenAPIKit/Schema Object/JSONSchema.swift index cebab9ae1..266f3995b 100644 --- a/Sources/OpenAPIKit/Schema Object/JSONSchema.swift +++ b/Sources/OpenAPIKit/Schema Object/JSONSchema.swift @@ -1003,7 +1003,7 @@ extension JSONSchema { format: JSONTypeFormat.BooleanFormat = .unspecified, required: Bool = true, nullable: Bool = false, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1036,7 +1036,7 @@ extension JSONSchema { format: JSONTypeFormat.BooleanFormat = .unspecified, required: Bool = true, nullable: Bool = false, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1073,7 +1073,7 @@ extension JSONSchema { format: JSONTypeFormat.AnyFormat = .unspecified, required: Bool = true, nullable: Bool = false, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1106,7 +1106,7 @@ extension JSONSchema { format: JSONTypeFormat.AnyFormat = .unspecified, required: Bool = true, nullable: Bool = false, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1146,7 +1146,7 @@ extension JSONSchema { format: JSONTypeFormat.StringFormat = .unspecified, required: Bool = true, nullable: Bool = false, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1187,7 +1187,7 @@ extension JSONSchema { format: JSONTypeFormat.StringFormat = .unspecified, required: Bool = true, nullable: Bool = false, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1230,7 +1230,7 @@ extension JSONSchema { format: JSONTypeFormat.NumberFormat = .unspecified, required: Bool = true, nullable: Bool = false, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1271,7 +1271,7 @@ extension JSONSchema { format: JSONTypeFormat.NumberFormat = .unspecified, required: Bool = true, nullable: Bool = false, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1314,7 +1314,7 @@ extension JSONSchema { format: JSONTypeFormat.IntegerFormat = .unspecified, required: Bool = true, nullable: Bool = false, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1355,7 +1355,7 @@ extension JSONSchema { format: JSONTypeFormat.IntegerFormat = .unspecified, required: Bool = true, nullable: Bool = false, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1398,7 +1398,7 @@ extension JSONSchema { format: JSONTypeFormat.ObjectFormat = .unspecified, required: Bool = true, nullable: Bool = false, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1446,7 +1446,7 @@ extension JSONSchema { format: JSONTypeFormat.ArrayFormat = .unspecified, required: Bool = true, nullable: Bool = false, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, diff --git a/Sources/OpenAPIKit/Schema Object/JSONSchemaContext.swift b/Sources/OpenAPIKit/Schema Object/JSONSchemaContext.swift index c19d7a094..ede3a4d65 100644 --- a/Sources/OpenAPIKit/Schema Object/JSONSchemaContext.swift +++ b/Sources/OpenAPIKit/Schema Object/JSONSchemaContext.swift @@ -217,25 +217,6 @@ extension JSONSchema { self.defaultValue = defaultValue self.examples = examples.map(AnyCodable.init) } - - public enum Permissions: String, Codable { - case readOnly - case writeOnly - case readWrite - - public init( - _ permissions: CoreContext.Permissions - ) { - switch permissions { - case .readOnly: - self = .readOnly - case .writeOnly: - self = .writeOnly - case .readWrite: - self = .readWrite - } - } - } } } @@ -536,29 +517,6 @@ extension JSONSchema { } } - /// The context that only applies to `.string` schemas. - public struct StringContext: Equatable { - public let maxLength: Int? - let _minLength: Int? - - public var minLength: Int { - return _minLength ?? 0 - } - - /// Regular expression - public let pattern: String? - - public init( - maxLength: Int? = nil, - minLength: Int? = nil, - pattern: String? = nil - ) { - self.maxLength = maxLength - self._minLength = minLength - self.pattern = pattern - } - } - /// The context that only applies to `.array` schemas. public struct ArrayContext: Equatable { /// A JSON Type Node that describes @@ -658,23 +616,6 @@ extension JSONSchema { self._minProperties = minProperties } } - - /// The context that only applies to `.reference` schemas. - public struct ReferenceContext: Equatable { - public let required: Bool - - public init(required: Bool = true) { - self.required = required - } - - public func requiredContext() -> ReferenceContext { - return .init(required: true) - } - - public func optionalContext() -> ReferenceContext { - return .init(required: false) - } - } } // MARK: - Codable @@ -928,9 +869,9 @@ extension JSONSchema.IntegerContext: Decodable { multipleOf = try container.decodeIfPresent(Int.self, forKey: .multipleOf) - // the following acrobatics thanks to some libraries (namely Yams) not - // being willing to decode floating point representations of whole numbers - // as integer values. + // the following acrobatics thanks to some libraries (namely Yams) not + // being willing to decode floating point representations of whole numbers + // as integer values. let exclusiveMaximumAttempt = try container.decodeIfPresent(Double.self, forKey: .exclusiveMaximum) let exclusiveMinimumAttempt = try container.decodeIfPresent(Double.self, forKey: .exclusiveMinimum) @@ -951,38 +892,10 @@ extension JSONSchema.IntegerContext: Decodable { } maximum = try boundFromAttempt(exclusiveMaximumAttempt, max: true, exclusive: true) - ?? boundFromAttempt(maximumAttempt, max: true, exclusive: false) + ?? boundFromAttempt(maximumAttempt, max: true, exclusive: false) minimum = try boundFromAttempt(exclusiveMinimumAttempt, max: false, exclusive: true) - ?? boundFromAttempt(minimumAttempt, max: false, exclusive: false) - } -} - -extension JSONSchema.StringContext { - internal enum CodingKeys: String, CodingKey { - case maxLength - case minLength - case pattern - } -} - -extension JSONSchema.StringContext: Encodable { - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encodeIfPresent(maxLength, forKey: .maxLength) - try container.encodeIfPresent(_minLength, forKey: .minLength) - try container.encodeIfPresent(pattern, forKey: .pattern) - } -} - -extension JSONSchema.StringContext: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - maxLength = try container.decodeIfPresent(Int.self, forKey: .maxLength) - _minLength = try container.decodeIfPresent(Int.self, forKey: .minLength) - pattern = try container.decodeIfPresent(String.self, forKey: .pattern) + ?? boundFromAttempt(minimumAttempt, max: false, exclusive: false) } } diff --git a/Sources/OpenAPIKit/Schema Object/TypesAndFormats.swift b/Sources/OpenAPIKit/Schema Object/TypesAndFormats.swift index 91e2a02c0..34b95e95f 100644 --- a/Sources/OpenAPIKit/Schema Object/TypesAndFormats.swift +++ b/Sources/OpenAPIKit/Schema Object/TypesAndFormats.swift @@ -121,296 +121,57 @@ public protocol OpenAPIFormat: SwiftTyped, Codable, Equatable, RawRepresentable, var jsonType: JSONType { get } } -extension JSONTypeFormat { - /// A format used when no type is known or any type is allowed. - /// - /// There are no built-in formats that do not have an associated - /// type, but it is still important to be able to specify a format without - /// a type. This can come into play when writing fragments of schemas - /// to be combined later. - public enum AnyFormat: RawRepresentable, Equatable, OpenAPIFormat { - case generic - case other(String) - - public var rawValue: String { - switch self { - case .generic: return "" - case .other(let other): - return other - } - } - - public init(rawValue: String) { - switch rawValue { - case "": self = .generic - default: self = .other(rawValue) - } - } - - public typealias SwiftType = AnyCodable - - public static var unspecified: AnyFormat { - return .generic - } - - public var jsonType: JSONType { - return .object - } - } - - /// The allowed "format" properties for `.boolean` schemas. - public enum BooleanFormat: RawRepresentable, Equatable, OpenAPIFormat { - case generic - case other(String) - - public var rawValue: String { - switch self { - case .generic: return "" - case .other(let other): - return other - } - } - - public init(rawValue: String) { - switch rawValue { - case "": self = .generic - default: self = .other(rawValue) - } - } - - public typealias SwiftType = Bool - - public static var unspecified: BooleanFormat { - return .generic - } - - public var jsonType: JSONType { - return .boolean - } - } - - /// The allowed "format" properties for `.object` schemas. - public enum ObjectFormat: RawRepresentable, Equatable, OpenAPIFormat { - case generic - case other(String) - - public var rawValue: String { - switch self { - case .generic: return "" - case .other(let other): - return other - } - } - - public init(rawValue: String) { - switch rawValue { - case "": self = .generic - default: self = .other(rawValue) - } - } - - public typealias SwiftType = AnyCodable - - public static var unspecified: ObjectFormat { - return .generic - } - - public var jsonType: JSONType { - return .object - } - } - - /// The allowed "format" properties for `.array` schemas. - public enum ArrayFormat: RawRepresentable, Equatable, OpenAPIFormat { - case generic - case other(String) - - public var rawValue: String { - switch self { - case .generic: return "" - case .other(let other): - return other - } - } - - public init(rawValue: String) { - switch rawValue { - case "": self = .generic - default: self = .other(rawValue) - } - } - - public typealias SwiftType = [AnyCodable] - - public static var unspecified: ArrayFormat { - return .generic - } - - public var jsonType: JSONType { - return .array - } - } - - /// The allowed "format" properties for `.number` schemas. - public enum NumberFormat: RawRepresentable, Equatable, OpenAPIFormat { - case generic - case float - case double - case other(String) - - public var rawValue: String { - switch self { - case .generic: return "" - case .float: return "float" - case .double: return "double" - case .other(let other): - return other - } - } - - public init(rawValue: String) { - switch rawValue { - case "": self = .generic - case "float": self = .float - case "double": self = .double - default: self = .other(rawValue) - } - } - - public typealias SwiftType = Double - - public static var unspecified: NumberFormat { - return .generic - } - - public var jsonType: JSONType { - return .number - } +/// A format used when no type is known or any type is allowed. +/// +/// There are no built-in formats that do not have an associated +/// type, but it is still important to be able to specify a format without +/// a type. This can come into play when writing fragments of schemas +/// to be combined later. +extension JSONTypeFormat.AnyFormat: OpenAPIFormat { + public var jsonType: JSONType { + return .object } +} - /// The allowed "format" properties for `.integer` schemas. - public enum IntegerFormat: RawRepresentable, Equatable, OpenAPIFormat { - case generic - case int32 - case int64 - case other(String) - - public var rawValue: String { - switch self { - case .generic: return "" - case .int32: return "int32" - case .int64: return "int64" - case .other(let other): - return other - } - } - - public init(rawValue: String) { - switch rawValue { - case "": self = .generic - case "int32": self = .int32 - case "int64": self = .int64 - default: self = .other(rawValue) - } - } - - public typealias SwiftType = Int - - public static var unspecified: IntegerFormat { - return .generic - } - - public var jsonType: JSONType { - return .integer - } +/// The allowed "format" properties for `.boolean` schemas. +extension JSONTypeFormat.BooleanFormat: OpenAPIFormat { + public var jsonType: JSONType { + return .boolean } +} - /// The allowed "format" properties for `.string` schemas. - public enum StringFormat: RawRepresentable, Equatable, OpenAPIFormat { - case generic - case byte - case binary - case date - /// A string instance is valid against this attribute if it is a valid - /// date representation as defined by - /// https://tools.ietf.org/html/rfc3339#section-5.6 - case dateTime - case password - case other(String) - - public var rawValue: String { - switch self { - case .generic: return "" - case .byte: return "byte" - case .binary: return "binary" - case .date: return "date" - case .dateTime: return "date-time" - case .password: return "password" - case .other(let other): - return other - } - } - - public init(rawValue: String) { - switch rawValue { - case "": self = .generic - case "byte": self = .byte - case "binary": self = .binary - case "date": self = .date - case "date-time": self = .dateTime - case "password": self = .password - default: self = .other(rawValue) - } - } - - public typealias SwiftType = String - - public static var unspecified: StringFormat { - return .generic - } - - public var jsonType: JSONType { - return .string - } +/// The allowed "format" properties for `.object` schemas. +extension JSONTypeFormat.ObjectFormat: OpenAPIFormat { + public var jsonType: JSONType { + return .object } } -extension JSONTypeFormat.StringFormat { - /// Popular non-standard "format" properties for `.string` schemas. - /// - /// Specify with e.g. `.string(format: .extended(.uuid))` - public enum Extended: String, Equatable { - case uuid = "uuid" - case email = "email" - case hostname = "hostname" - case ipv4 = "ipv4" - case ipv6 = "ipv6" - /// A string instance is valid against this attribute if it is a valid - /// URI, according to - /// https://tools.ietf.org/html/rfc3986 - case uri = "uri" - /// A string instance is valid against this attribute if it is a valid - /// URI, according to - /// https://tools.ietf.org/html/rfc3986 - case uriReference = "uriref" +/// The allowed "format" properties for `.array` schemas. +extension JSONTypeFormat.ArrayFormat: OpenAPIFormat { + public var jsonType: JSONType { + return .array } +} - public static func extended(_ format: Extended) -> Self { - return .other(format.rawValue) +/// The allowed "format" properties for `.number` schemas. +extension JSONTypeFormat.NumberFormat: OpenAPIFormat { + public var jsonType: JSONType { + return .number } } -extension JSONTypeFormat.IntegerFormat { - /// Popular non-standard "format" properties for `.integer` schemas. - /// - /// Specify with e.g. `.integer(format: .extended(.uint32))` - public enum Extended: String, Equatable { - case uint32 = "uint32" - case uint64 = "uint64" +/// The allowed "format" properties for `.integer` schemas. +extension JSONTypeFormat.IntegerFormat: OpenAPIFormat { + public var jsonType: JSONType { + return .integer } +} - public static func extended(_ format: Extended) -> Self { - return .other(format.rawValue) +/// The allowed "format" properties for `.string` schemas. +extension JSONTypeFormat.StringFormat: OpenAPIFormat { + public var jsonType: JSONType { + return .string } } diff --git a/Sources/OpenAPIKit/Security/SecurityScheme.swift b/Sources/OpenAPIKit/Security/SecurityScheme.swift index 0003db2b1..20e3b0391 100644 --- a/Sources/OpenAPIKit/Security/SecurityScheme.swift +++ b/Sources/OpenAPIKit/Security/SecurityScheme.swift @@ -52,7 +52,7 @@ extension OpenAPI { public static func mutualTLS(description: String? = nil) -> SecurityScheme { return .init(type: .mutualTLS, description: description) } - + public enum SecurityType: Equatable { case apiKey(name: String, location: Location) case http(scheme: String, bearerFormat: String?) @@ -60,12 +60,6 @@ extension OpenAPI { case openIdConnect(openIdConnectUrl: URL) case mutualTLS } - - public enum Location: String, Codable, Equatable { - case query - case header - case cookie - } } } @@ -89,7 +83,7 @@ extension OpenAPI.SecurityScheme.SecurityType { case .openIdConnect: return .openIdConnect case .mutualTLS: - return .mutualTLS + return .mutualTLS } } } @@ -271,6 +265,3 @@ extension OpenAPI.SecurityScheme: LocallyDereferenceable { return self } } - -extension OpenAPI.SecurityScheme.Location: Validatable {} -extension OpenAPI.SecurityScheme.SecurityType.Name: Validatable {} diff --git a/Sources/OpenAPIKit/_CoreReExport.swift b/Sources/OpenAPIKit/_CoreReExport.swift index c342da20f..00447c26e 100644 --- a/Sources/OpenAPIKit/_CoreReExport.swift +++ b/Sources/OpenAPIKit/_CoreReExport.swift @@ -8,7 +8,52 @@ @_exported import struct OpenAPIKitCore.AnyCodable @_exported import struct OpenAPIKitCore.CodingPathError @_exported import enum OpenAPIKitCore.Either -@_exported import enum OpenAPIKitCore.OpenAPI @_exported import protocol OpenAPIKitCore.OpenAPIError @_exported import struct OpenAPIKitCore.OrderedDictionary @_exported import struct OpenAPIKitCore.URLTemplate + +import OpenAPIKitCore + +public extension OpenAPI { + typealias HttpMethod = OpenAPIKitCore.Shared.HttpMethod + typealias ContentType = OpenAPIKitCore.Shared.ContentType + typealias Error = OpenAPIKitCore.Error + typealias Warning = OpenAPIKitCore.Warning + typealias Path = OpenAPIKitCore.Shared.Path + typealias ComponentKey = OpenAPIKitCore.Shared.ComponentKey + typealias Discriminator = OpenAPIKitCore.Shared.Discriminator + typealias OAuthFlows = OpenAPIKitCore.Shared.OAuthFlows + typealias CallbackURL = OpenAPIKitCore.Shared.CallbackURL +} + +public extension OpenAPI.SecurityScheme { + typealias Location = OpenAPIKitCore.Shared.SecuritySchemeLocation +} + +public extension OpenAPI.Parameter.Context { + typealias Location = OpenAPIKitCore.Shared.ParameterContextLocation +} + +public extension OpenAPI.Parameter.SchemaContext { + typealias Style = OpenAPIKitCore.Shared.ParameterSchemaContextStyle +} + +public extension OpenAPI.Response { + typealias StatusCode = OpenAPIKitCore.Shared.ResponseStatusCode +} + +public extension JSONSchema { + typealias Permissions = OpenAPIKitCore.Shared.JSONSchemaPermissions + typealias StringContext = OpenAPIKitCore.Shared.StringContext + typealias ReferenceContext = OpenAPIKitCore.Shared.ReferenceContext +} + +public extension JSONTypeFormat { + typealias AnyFormat = OpenAPIKitCore.Shared.AnyFormat + typealias BooleanFormat = OpenAPIKitCore.Shared.BooleanFormat + typealias ObjectFormat = OpenAPIKitCore.Shared.ObjectFormat + typealias ArrayFormat = OpenAPIKitCore.Shared.ArrayFormat + typealias NumberFormat = OpenAPIKitCore.Shared.NumberFormat + typealias IntegerFormat = OpenAPIKitCore.Shared.IntegerFormat + typealias StringFormat = OpenAPIKitCore.Shared.StringFormat +} diff --git a/Sources/OpenAPIKit30/Callbacks.swift b/Sources/OpenAPIKit30/Callbacks.swift index beff994bc..6309e7713 100644 --- a/Sources/OpenAPIKit30/Callbacks.swift +++ b/Sources/OpenAPIKit30/Callbacks.swift @@ -9,47 +9,6 @@ import OpenAPIKitCore import Foundation extension OpenAPI { - - /// A URL template where the placeholders are OpenAPI **Runtime Expressions** instead - /// of named variables. - /// - /// See [OpenAPI Callback Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#callback-object) and [OpenAPI Runtime Expression](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#runtime-expressions) for more. - /// - public struct CallbackURL: Hashable, RawRepresentable { - public let template: URLTemplate - - /// The string value of the URL without variable replacement. - /// - /// Variables cannot be replaced based on other information in the - /// OpenAPI document; they are only available at "runtime" which is - /// where the name of the OpenAPI structure `CallbackURL` - /// represents comes from. - public var rawValue: String { - template.rawValue - } - - /// Get a URL from the runtime expression if it is a valid URL without - /// variable replacement. - /// - /// Callback URLs with variables in them will not be valid URLs - /// and are therefore guaranteed to return `nil`. - public var url: URL? { - template.url - } - - /// Create a CallbackURL from the string if possible. - public init?(rawValue: String) { - guard let template = URLTemplate(rawValue: rawValue) else { - return nil - } - self.template = template - } - - public init(url: URL) { - template = .init(url: url) - } - } - /// A map from runtime expressions to path items to be used as /// callbacks for the API. /// @@ -60,19 +19,3 @@ extension OpenAPI { /// A map of named collections of Callback Objects (`OpenAPI.Callbacks`). public typealias CallbacksMap = OrderedDictionary, Callbacks>> } - -extension OpenAPI.CallbackURL: Encodable { - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - - try container.encode(rawValue) - } -} - -extension OpenAPI.CallbackURL: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - - template = try container.decode(URLTemplate.self) - } -} diff --git a/Sources/OpenAPIKit30/Components Object/Components.swift b/Sources/OpenAPIKit30/Components Object/Components.swift index b90f8b053..54d8d701f 100644 --- a/Sources/OpenAPIKit30/Components Object/Components.swift +++ b/Sources/OpenAPIKit30/Components Object/Components.swift @@ -68,64 +68,6 @@ extension OpenAPI { } extension OpenAPI { - /// A key for one of the component dictionaries. - /// - /// These keys must match the regex - /// `^[a-zA-Z0-9\.\-_]+$`. - public struct ComponentKey: RawRepresentable, ExpressibleByStringLiteral, Codable, Equatable, Hashable, StringConvertibleHintProvider { - public let rawValue: String - - public init(stringLiteral value: StringLiteralType) { - self.rawValue = value - } - - public init?(rawValue: String) { - guard !rawValue.isEmpty else { - return nil - } - var allowedCharacters = CharacterSet.alphanumerics - allowedCharacters.insert(charactersIn: "-_.") - guard CharacterSet(charactersIn: rawValue).isSubset(of: allowedCharacters) else { - return nil - } - self.rawValue = rawValue - } - - public static func problem(with proposedString: String) -> String? { - if Self(rawValue: proposedString) == nil { - return "Keys for components in the Components Object must conform to the regex `^[a-zA-Z0-9\\.\\-_]+$`. '\(proposedString)' does not.." - } - return nil - } - - public init(from decoder: Decoder) throws { - let rawValue = try decoder.singleValueContainer().decode(String.self) - guard let key = Self(rawValue: rawValue) else { - throw InconsistencyError( - subjectName: "Component Key", - details: "Keys for components in the Components Object must conform to the regex `^[a-zA-Z0-9\\.\\-_]+$`. '\(rawValue)' does not..", - codingPath: decoder.codingPath - ) - } - self = key - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - - // we check for consistency on encode because a string literal - // may result in an invalid component key being constructed. - guard Self(rawValue: rawValue) != nil else { - throw InconsistencyError( - subjectName: "Component Key", - details: "Keys for components in the Components Object must conform to the regex `^[a-zA-Z0-9\\.\\-_]+$`. '\(rawValue)' does not..", - codingPath: container.codingPath - ) - } - - try container.encode(rawValue) - } - } public typealias ComponentDictionary = OrderedDictionary } diff --git a/Sources/OpenAPIKit30/Discriminator.swift b/Sources/OpenAPIKit30/Discriminator.swift deleted file mode 100644 index e157f0266..000000000 --- a/Sources/OpenAPIKit30/Discriminator.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// Discriminator.swift -// -// -// Created by Mathew Polzin on 10/6/19. -// - -import OpenAPIKitCore - -extension OpenAPI { - /// OpenAPI Spec "Disciminator Object" - /// - /// See [OpenAPI Discriminator Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#discriminator-object). - public struct Discriminator: Equatable { - public let propertyName: String - public let mapping: [String: String]? - - public init(propertyName: String, - mapping: [String: String]? = nil) { - self.propertyName = propertyName - self.mapping = mapping - } - } -} - -// MARK: - Codable - -extension OpenAPI.Discriminator: Encodable { - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(propertyName, forKey: .propertyName) - try container.encodeIfPresent(mapping, forKey: .mapping) - } -} - -extension OpenAPI.Discriminator: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - propertyName = try container.decode(String.self, forKey: .propertyName) - mapping = try container.decodeIfPresent([String: String].self, forKey: .mapping) - } -} - -extension OpenAPI.Discriminator { - private enum CodingKeys: String, CodingKey { - case propertyName - case mapping - } -} - -extension OpenAPI.Discriminator: Validatable {} diff --git a/Sources/OpenAPIKit30/OpenAPI.swift b/Sources/OpenAPIKit30/OpenAPI.swift new file mode 100644 index 000000000..f56c419b7 --- /dev/null +++ b/Sources/OpenAPIKit30/OpenAPI.swift @@ -0,0 +1,9 @@ +// +// OpenAPI.swift +// +// +// Created by Mathew Polzin on 6/22/19. +// + +/// The OpenAPI namespace +public enum OpenAPI {} diff --git a/Sources/OpenAPIKit30/Parameter/ParameterContext.swift b/Sources/OpenAPIKit30/Parameter/ParameterContext.swift index 8d148f466..abb134563 100644 --- a/Sources/OpenAPIKit30/Parameter/ParameterContext.swift +++ b/Sources/OpenAPIKit30/Parameter/ParameterContext.swift @@ -73,13 +73,6 @@ extension OpenAPI.Parameter { } extension OpenAPI.Parameter.Context { - public enum Location: String, CaseIterable, Codable { - case query - case header - case path - case cookie - } - public var location: Location { switch self { case .query: @@ -94,4 +87,3 @@ extension OpenAPI.Parameter.Context { } } -extension OpenAPI.Parameter.Context.Location: Validatable {} diff --git a/Sources/OpenAPIKit30/Parameter/ParameterSchemaContext.swift b/Sources/OpenAPIKit30/Parameter/ParameterSchemaContext.swift index a330cb896..e44df7313 100644 --- a/Sources/OpenAPIKit30/Parameter/ParameterSchemaContext.swift +++ b/Sources/OpenAPIKit30/Parameter/ParameterSchemaContext.swift @@ -128,41 +128,31 @@ extension OpenAPI.Parameter { } } -extension OpenAPI.Parameter.SchemaContext { - public enum Style: String, CaseIterable, Codable { - case form - case simple - case matrix - case label - case spaceDelimited - case pipeDelimited - case deepObject - - /// Get the default `Style` for the given location - /// per the OpenAPI Specification. - /// - /// See the `style` fixed field under - /// [OpenAPI Parameter Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-object). - public static func `default`(for location: OpenAPI.Parameter.Context) -> Self { - switch location { - case .query: - return .form - case .cookie: - return .form - case .path: - return .simple - case .header: - return .simple - } +extension OpenAPI.Parameter.SchemaContext.Style { + /// Get the default `Style` for the given location + /// per the OpenAPI Specification. + /// + /// See the `style` fixed field under + /// [OpenAPI Parameter Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-object). + public static func `default`(for location: OpenAPI.Parameter.Context) -> Self { + switch location { + case .query: + return .form + case .cookie: + return .form + case .path: + return .simple + case .header: + return .simple } - - internal var defaultExplode: Bool { - switch self { - case .form: - return true - default: - return false - } + } + + internal var defaultExplode: Bool { + switch self { + case .form: + return true + default: + return false } } } diff --git a/Sources/OpenAPIKit30/Path Item/PathItem.swift b/Sources/OpenAPIKit30/Path Item/PathItem.swift index 15d3fc3ec..c9c5e9c89 100644 --- a/Sources/OpenAPIKit30/Path Item/PathItem.swift +++ b/Sources/OpenAPIKit30/Path Item/PathItem.swift @@ -7,37 +7,6 @@ import OpenAPIKitCore -extension OpenAPI { - /// OpenAPI Spec "Paths Object" path field pattern support. - /// - /// See [OpenAPI Paths Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#paths-object) - /// and [OpenAPI Patterned Fields](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#patterned-fields). - public struct Path: RawRepresentable, Equatable, Hashable { - public let components: [String] - - public init(_ components: [String]) { - self.components = components - } - - public init(rawValue: String) { - let pathComponents = rawValue.split(separator: "/").map(String.init) - components = pathComponents.count > 0 && pathComponents[0].isEmpty - ? Array(pathComponents.dropFirst()) - : pathComponents - } - - public var rawValue: String { - return "/\(components.joined(separator: "/"))" - } - } -} - -extension OpenAPI.Path: ExpressibleByStringLiteral { - public init(stringLiteral value: String) { - self.init(rawValue: value) - } -} - extension OpenAPI { /// OpenAPI Spec "Path Item Object" /// diff --git a/Sources/OpenAPIKit30/Response/Response.swift b/Sources/OpenAPIKit30/Response/Response.swift index 02edee5da..56e057ddc 100644 --- a/Sources/OpenAPIKit30/Response/Response.swift +++ b/Sources/OpenAPIKit30/Response/Response.swift @@ -68,109 +68,6 @@ extension OrderedDictionary where Key == OpenAPI.Response.StatusCode { } } -// MARK: - Status Code -extension OpenAPI.Response { - /// An HTTP Status code or status code range. - /// - /// OpenAPI supports one of the following as a key in the [Responses Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#responses-object): - /// - A `default` entry. - /// - A specific status code. - /// - A status code range. - /// - /// The `.default` case is used for a default entry. - /// - /// You can use integer literals to specify an exact status code. - /// - /// Status code ranges are named in the `StatusCode.Range` enum. For example, the "1XX" range (100-199) can be written as either `.range(.information)` or as `.range(.init(rawValue: "1XX"))`. - public struct StatusCode: RawRepresentable, Equatable, Hashable, HasWarnings { - public typealias RawValue = String - - public let warnings: [OpenAPI.Warning] - - public var value: Code - - internal init(value: Code) { - self.value = value - warnings = [] - } - - public static let `default`: Self = .init(value: .default) - public static func range(_ range: Range) -> Self { .init(value: .range(range)) } - public static func status(code: Int) -> Self { .init(value: .status(code: code)) } - - public enum Code: Equatable, Hashable { - case `default` - case range(Range) - case status(code: Int) - } - - public enum Range: String { - /// Status Code `100-199` - case information = "1XX" - /// Status Code `200-299` - case success = "2XX" - /// Status Code `300-399` - case redirect = "3XX" - /// Status Code `400-499` - case clientError = "4XX" - /// Status Code `500-599` - case serverError = "5XX" - } - - public var rawValue: String { - switch value { - case .default: - return "default" - - case .range(let range): - return range.rawValue - - case .status(code: let code): - return String(code) - } - } - - public var isSuccess: Bool { - switch value { - case .range(.success), .status(code: 200..<300): - return true - case .range, .status, .default: - return false - } - } - - public init?(rawValue: String) { - if let val = Int(rawValue) { - value = .status(code: val) - warnings = [] - } else if rawValue == "default" { - value = .default - warnings = [] - } else if let range = Range(rawValue: rawValue.uppercased()) { - value = .range(range) - warnings = [] - } else if rawValue.contains("/"), - let first = (rawValue.split(separator: "/")).first, - let fallback = Self(rawValue: String(first)) { - value = fallback.value - warnings = [ - .message("Found non-compliant Status Code '\(rawValue)' but was able to parse as \(first)") - ] - } else { - return nil - } - } - } -} - -extension OpenAPI.Response.StatusCode: ExpressibleByIntegerLiteral { - - public init(integerLiteral value: Int) { - self.value = .status(code: value) - warnings = [] - } -} - // MARK: `Either` convenience methods extension Either where A == JSONReference, B == OpenAPI.Response { diff --git a/Sources/OpenAPIKit30/Schema Object/JSONSchema+Combining.swift b/Sources/OpenAPIKit30/Schema Object/JSONSchema+Combining.swift index 9d61dd767..79adb97c1 100644 --- a/Sources/OpenAPIKit30/Schema Object/JSONSchema+Combining.swift +++ b/Sources/OpenAPIKit30/Schema Object/JSONSchema+Combining.swift @@ -260,7 +260,7 @@ extension JSONSchema.CoreContext where Format == JSONTypeFormat.AnyFormat { format: newFormat, required: required, nullable: _nullable, - permissions: _permissions.map(OtherContext.Permissions.init), + permissions: _permissions, deprecated: _deprecated, title: title, description: description, @@ -286,7 +286,7 @@ extension JSONSchema.CoreContext { if let conflict = conflicting(_permissions, other._permissions) { throw JSONSchemaResolutionError(.inconsistency("A schema cannot be both \(conflict.0.rawValue) and \(conflict.1.rawValue).")) } - let newPermissions: JSONSchema.CoreContext.Permissions? + let newPermissions: JSONSchema.Permissions? if _permissions == nil && other._permissions == nil { newPermissions = nil } else { @@ -447,7 +447,7 @@ extension JSONSchema.StringContext { if let conflict = conflicting(maxLength, other.maxLength) { throw JSONSchemaResolutionError(.attributeConflict(jsonType: .string, name: "maxLength", original: String(conflict.0), new: String(conflict.1))) } - if let conflict = conflicting(_minLength, other._minLength) { + if let conflict = conflicting(Self._minLength(self), Self._minLength(other)) { throw JSONSchemaResolutionError(.attributeConflict(jsonType: .string, name: "minLength", original: String(conflict.0), new: String(conflict.1))) } if let conflict = conflicting(pattern, other.pattern) { @@ -456,7 +456,7 @@ extension JSONSchema.StringContext { // explicitly declaring these constants one at a time // helps the type checker a lot. let newMaxLength = maxLength ?? other.maxLength - let newMinLength = _minLength ?? other._minLength + let newMinLength = Self._minLength(self) ?? Self._minLength(other) let newPattern = pattern ?? other.pattern return .init( maxLength: newMaxLength, @@ -557,13 +557,12 @@ extension JSONSchema.CoreContext { guard let newFormat = NewFormat(rawValue: format.rawValue) else { throw JSONSchemaResolutionError(.inconsistency("Tried to create a \(NewFormat.self) from the incompatible format value: \(format.rawValue)")) } - let newPermissions = _permissions.map(JSONSchema.CoreContext.Permissions.init) return .init( format: newFormat, required: required, nullable: _nullable, - permissions: newPermissions, + permissions: _permissions, deprecated: _deprecated, title: title, description: description, @@ -628,7 +627,7 @@ extension JSONSchema.NumericContext { extension JSONSchema.StringContext { internal func validatedContext() throws -> JSONSchema.StringContext { - if let minimum = _minLength { + if let minimum = Self._minLength(self) { guard minimum >= 0 else { throw JSONSchemaResolutionError(.inconsistency("String minimum length (\(minimum) cannot be less than 0")) } @@ -640,7 +639,7 @@ extension JSONSchema.StringContext { } return .init( maxLength: maxLength, - minLength: _minLength, + minLength: Self._minLength(self), pattern: pattern ) } diff --git a/Sources/OpenAPIKit30/Schema Object/JSONSchema.swift b/Sources/OpenAPIKit30/Schema Object/JSONSchema.swift index 25a9d98fe..b1f1b03b6 100644 --- a/Sources/OpenAPIKit30/Schema Object/JSONSchema.swift +++ b/Sources/OpenAPIKit30/Schema Object/JSONSchema.swift @@ -911,7 +911,7 @@ extension JSONSchema { format: JSONTypeFormat.BooleanFormat = .unspecified, required: Bool = true, nullable: Bool? = nil, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -944,7 +944,7 @@ extension JSONSchema { format: JSONTypeFormat.BooleanFormat = .unspecified, required: Bool = true, nullable: Bool? = nil, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -981,7 +981,7 @@ extension JSONSchema { format: JSONTypeFormat.AnyFormat = .unspecified, required: Bool = true, nullable: Bool? = nil, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1014,7 +1014,7 @@ extension JSONSchema { format: JSONTypeFormat.AnyFormat = .unspecified, required: Bool = true, nullable: Bool? = nil, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1054,7 +1054,7 @@ extension JSONSchema { format: JSONTypeFormat.StringFormat = .unspecified, required: Bool = true, nullable: Bool? = nil, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1095,7 +1095,7 @@ extension JSONSchema { format: JSONTypeFormat.StringFormat = .unspecified, required: Bool = true, nullable: Bool? = nil, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1138,7 +1138,7 @@ extension JSONSchema { format: JSONTypeFormat.NumberFormat = .unspecified, required: Bool = true, nullable: Bool? = nil, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1179,7 +1179,7 @@ extension JSONSchema { format: JSONTypeFormat.NumberFormat = .unspecified, required: Bool = true, nullable: Bool? = nil, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1222,7 +1222,7 @@ extension JSONSchema { format: JSONTypeFormat.IntegerFormat = .unspecified, required: Bool = true, nullable: Bool? = nil, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1263,7 +1263,7 @@ extension JSONSchema { format: JSONTypeFormat.IntegerFormat = .unspecified, required: Bool = true, nullable: Bool? = nil, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1306,7 +1306,7 @@ extension JSONSchema { format: JSONTypeFormat.ObjectFormat = .unspecified, required: Bool = true, nullable: Bool? = nil, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, @@ -1354,7 +1354,7 @@ extension JSONSchema { format: JSONTypeFormat.ArrayFormat = .unspecified, required: Bool = true, nullable: Bool? = nil, - permissions: JSONSchema.CoreContext.Permissions? = nil, + permissions: Permissions? = nil, deprecated: Bool? = nil, title: String? = nil, description: String? = nil, diff --git a/Sources/OpenAPIKit30/Schema Object/JSONSchemaContext.swift b/Sources/OpenAPIKit30/Schema Object/JSONSchemaContext.swift index 35dd54702..9c7a99d0a 100644 --- a/Sources/OpenAPIKit30/Schema Object/JSONSchemaContext.swift +++ b/Sources/OpenAPIKit30/Schema Object/JSONSchemaContext.swift @@ -210,25 +210,6 @@ extension JSONSchema { self.defaultValue = defaultValue self.example = AnyCodable(example) } - - public enum Permissions: String, Codable { - case readOnly - case writeOnly - case readWrite - - public init( - _ permissions: CoreContext.Permissions - ) { - switch permissions { - case .readOnly: - self = .readOnly - case .writeOnly: - self = .writeOnly - case .readWrite: - self = .readWrite - } - } - } } } @@ -493,29 +474,6 @@ extension JSONSchema { } } - /// The context that only applies to `.string` schemas. - public struct StringContext: Equatable { - public let maxLength: Int? - let _minLength: Int? - - public var minLength: Int { - return _minLength ?? 0 - } - - /// Regular expression - public let pattern: String? - - public init( - maxLength: Int? = nil, - minLength: Int? = nil, - pattern: String? = nil - ) { - self.maxLength = maxLength - self._minLength = minLength - self.pattern = pattern - } - } - /// The context that only applies to `.array` schemas. public struct ArrayContext: Equatable { /// A JSON Type Node that describes @@ -615,23 +573,6 @@ extension JSONSchema { self._minProperties = minProperties } } - - /// The context that only applies to `.reference` schemas. - public struct ReferenceContext: Equatable { - public let required: Bool - - public init(required: Bool = true) { - self.required = required - } - - public func requiredContext() -> ReferenceContext { - return .init(required: true) - } - - public func optionalContext() -> ReferenceContext { - return .init(required: false) - } - } } // MARK: - Codable @@ -759,7 +700,7 @@ extension JSONSchema.CoreContext: Decodable { } extension JSONSchema.NumericContext { - internal enum CodingKeys: String, CodingKey { + public enum CodingKeys: String, CodingKey { case multipleOf case maximum case exclusiveMaximum @@ -807,7 +748,7 @@ extension JSONSchema.NumericContext: Decodable { } extension JSONSchema.IntegerContext { - internal enum CodingKeys: String, CodingKey { + public enum CodingKeys: String, CodingKey { case multipleOf case maximum case exclusiveMaximum @@ -877,34 +818,6 @@ extension JSONSchema.IntegerContext: Decodable { } } -extension JSONSchema.StringContext { - internal enum CodingKeys: String, CodingKey { - case maxLength - case minLength - case pattern - } -} - -extension JSONSchema.StringContext: Encodable { - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encodeIfPresent(maxLength, forKey: .maxLength) - try container.encodeIfPresent(_minLength, forKey: .minLength) - try container.encodeIfPresent(pattern, forKey: .pattern) - } -} - -extension JSONSchema.StringContext: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - maxLength = try container.decodeIfPresent(Int.self, forKey: .maxLength) - _minLength = try container.decodeIfPresent(Int.self, forKey: .minLength) - pattern = try container.decodeIfPresent(String.self, forKey: .pattern) - } -} - extension JSONSchema.ArrayContext { internal enum CodingKeys: String, CodingKey { case items diff --git a/Sources/OpenAPIKit30/Schema Object/TypesAndFormats.swift b/Sources/OpenAPIKit30/Schema Object/TypesAndFormats.swift index 6416ce5c7..ba71ad631 100644 --- a/Sources/OpenAPIKit30/Schema Object/TypesAndFormats.swift +++ b/Sources/OpenAPIKit30/Schema Object/TypesAndFormats.swift @@ -115,296 +115,57 @@ public protocol OpenAPIFormat: SwiftTyped, Codable, Equatable, RawRepresentable, var jsonType: JSONType { get } } -extension JSONTypeFormat { - /// A format used when no type is known or any type is allowed. - /// - /// There are no built-in formats that do not have an associated - /// type, but it is still important to be able to specify a format without - /// a type. This can come into play when writing fragments of schemas - /// to be combined later. - public enum AnyFormat: RawRepresentable, Equatable, OpenAPIFormat { - case generic - case other(String) - - public var rawValue: String { - switch self { - case .generic: return "" - case .other(let other): - return other - } - } - - public init(rawValue: String) { - switch rawValue { - case "": self = .generic - default: self = .other(rawValue) - } - } - - public typealias SwiftType = AnyCodable - - public static var unspecified: AnyFormat { - return .generic - } - - public var jsonType: JSONType { - return .object - } - } - - /// The allowed "format" properties for `.boolean` schemas. - public enum BooleanFormat: RawRepresentable, Equatable, OpenAPIFormat { - case generic - case other(String) - - public var rawValue: String { - switch self { - case .generic: return "" - case .other(let other): - return other - } - } - - public init(rawValue: String) { - switch rawValue { - case "": self = .generic - default: self = .other(rawValue) - } - } - - public typealias SwiftType = Bool - - public static var unspecified: BooleanFormat { - return .generic - } - - public var jsonType: JSONType { - return .boolean - } - } - - /// The allowed "format" properties for `.object` schemas. - public enum ObjectFormat: RawRepresentable, Equatable, OpenAPIFormat { - case generic - case other(String) - - public var rawValue: String { - switch self { - case .generic: return "" - case .other(let other): - return other - } - } - - public init(rawValue: String) { - switch rawValue { - case "": self = .generic - default: self = .other(rawValue) - } - } - - public typealias SwiftType = AnyCodable - - public static var unspecified: ObjectFormat { - return .generic - } - - public var jsonType: JSONType { - return .object - } - } - - /// The allowed "format" properties for `.array` schemas. - public enum ArrayFormat: RawRepresentable, Equatable, OpenAPIFormat { - case generic - case other(String) - - public var rawValue: String { - switch self { - case .generic: return "" - case .other(let other): - return other - } - } - - public init(rawValue: String) { - switch rawValue { - case "": self = .generic - default: self = .other(rawValue) - } - } - - public typealias SwiftType = [AnyCodable] - - public static var unspecified: ArrayFormat { - return .generic - } - - public var jsonType: JSONType { - return .array - } - } - - /// The allowed "format" properties for `.number` schemas. - public enum NumberFormat: RawRepresentable, Equatable, OpenAPIFormat { - case generic - case float - case double - case other(String) - - public var rawValue: String { - switch self { - case .generic: return "" - case .float: return "float" - case .double: return "double" - case .other(let other): - return other - } - } - - public init(rawValue: String) { - switch rawValue { - case "": self = .generic - case "float": self = .float - case "double": self = .double - default: self = .other(rawValue) - } - } - - public typealias SwiftType = Double - - public static var unspecified: NumberFormat { - return .generic - } - - public var jsonType: JSONType { - return .number - } +/// A format used when no type is known or any type is allowed. +/// +/// There are no built-in formats that do not have an associated +/// type, but it is still important to be able to specify a format without +/// a type. This can come into play when writing fragments of schemas +/// to be combined later. +extension JSONTypeFormat.AnyFormat: OpenAPIFormat { + public var jsonType: JSONType { + return .object } +} - /// The allowed "format" properties for `.integer` schemas. - public enum IntegerFormat: RawRepresentable, Equatable, OpenAPIFormat { - case generic - case int32 - case int64 - case other(String) - - public var rawValue: String { - switch self { - case .generic: return "" - case .int32: return "int32" - case .int64: return "int64" - case .other(let other): - return other - } - } - - public init(rawValue: String) { - switch rawValue { - case "": self = .generic - case "int32": self = .int32 - case "int64": self = .int64 - default: self = .other(rawValue) - } - } - - public typealias SwiftType = Int - - public static var unspecified: IntegerFormat { - return .generic - } - - public var jsonType: JSONType { - return .integer - } +/// The allowed "format" properties for `.boolean` schemas. +extension JSONTypeFormat.BooleanFormat: OpenAPIFormat { + public var jsonType: JSONType { + return .boolean } +} - /// The allowed "format" properties for `.string` schemas. - public enum StringFormat: RawRepresentable, Equatable, OpenAPIFormat { - case generic - case byte - case binary - case date - /// A string instance is valid against this attribute if it is a valid - /// date representation as defined by - /// https://tools.ietf.org/html/rfc3339#section-5.6 - case dateTime - case password - case other(String) - - public var rawValue: String { - switch self { - case .generic: return "" - case .byte: return "byte" - case .binary: return "binary" - case .date: return "date" - case .dateTime: return "date-time" - case .password: return "password" - case .other(let other): - return other - } - } - - public init(rawValue: String) { - switch rawValue { - case "": self = .generic - case "byte": self = .byte - case "binary": self = .binary - case "date": self = .date - case "date-time": self = .dateTime - case "password": self = .password - default: self = .other(rawValue) - } - } - - public typealias SwiftType = String - - public static var unspecified: StringFormat { - return .generic - } - - public var jsonType: JSONType { - return .string - } +/// The allowed "format" properties for `.object` schemas. +extension JSONTypeFormat.ObjectFormat: OpenAPIFormat { + public var jsonType: JSONType { + return .object } } -extension JSONTypeFormat.StringFormat { - /// Popular non-standard "format" properties for `.string` schemas. - /// - /// Specify with e.g. `.string(format: .extended(.uuid))` - public enum Extended: String, Equatable { - case uuid = "uuid" - case email = "email" - case hostname = "hostname" - case ipv4 = "ipv4" - case ipv6 = "ipv6" - /// A string instance is valid against this attribute if it is a valid - /// URI, according to - /// https://tools.ietf.org/html/rfc3986 - case uri = "uri" - /// A string instance is valid against this attribute if it is a valid - /// URI, according to - /// https://tools.ietf.org/html/rfc3986 - case uriReference = "uriref" +/// The allowed "format" properties for `.array` schemas. +extension JSONTypeFormat.ArrayFormat: OpenAPIFormat { + public var jsonType: JSONType { + return .array } +} - public static func extended(_ format: Extended) -> Self { - return .other(format.rawValue) +/// The allowed "format" properties for `.number` schemas. +extension JSONTypeFormat.NumberFormat: OpenAPIFormat { + public var jsonType: JSONType { + return .number } } -extension JSONTypeFormat.IntegerFormat { - /// Popular non-standard "format" properties for `.integer` schemas. - /// - /// Specify with e.g. `.integer(format: .extended(.uint32))` - public enum Extended: String, Equatable { - case uint32 = "uint32" - case uint64 = "uint64" +/// The allowed "format" properties for `.integer` schemas. +extension JSONTypeFormat.IntegerFormat: OpenAPIFormat { + public var jsonType: JSONType { + return .integer } +} - public static func extended(_ format: Extended) -> Self { - return .other(format.rawValue) +/// The allowed "format" properties for `.string` schemas. +extension JSONTypeFormat.StringFormat: OpenAPIFormat { + public var jsonType: JSONType { + return .string } } diff --git a/Sources/OpenAPIKit30/Security/OAuthFlows.swift b/Sources/OpenAPIKit30/Security/OAuthFlows.swift deleted file mode 100644 index 8d24ed999..000000000 --- a/Sources/OpenAPIKit30/Security/OAuthFlows.swift +++ /dev/null @@ -1,275 +0,0 @@ -// -// OAuthFlows.swift -// -// -// Created by Mathew Polzin on 1/23/20. -// - -import OpenAPIKitCore -import Foundation - -extension OpenAPI { - /// OpenAPI Spec "Oauth Flows Object" - /// - /// See [OpenAPI Oauth Flows Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#oauth-flows-object). - public struct OAuthFlows: Equatable { - public let implicit: Implicit? - public let password: Password? - public let clientCredentials: ClientCredentials? - public let authorizationCode: AuthorizationCode? - - public init( - implicit: Implicit? = nil, - password: Password? = nil, - clientCredentials: ClientCredentials? = nil, - authorizationCode: AuthorizationCode? = nil - ) { - self.implicit = implicit - self.password = password - self.clientCredentials = clientCredentials - self.authorizationCode = authorizationCode - } - } -} - -extension OpenAPI.OAuthFlows { - public typealias Scope = String - public typealias ScopeDescription = String - - public struct CommonFields: Equatable { - public let refreshUrl: URL? - public let scopes: OrderedDictionary - } - - @dynamicMemberLookup - public struct Implicit: Equatable { - private let common: CommonFields - public let authorizationUrl: URL - - public init(authorizationUrl: URL, refreshUrl: URL? = nil, scopes: OrderedDictionary) { - self.authorizationUrl = authorizationUrl - common = .init(refreshUrl: refreshUrl, scopes: scopes) - } - - public subscript(dynamicMember path: KeyPath) -> T { - return common[keyPath: path] - } - } - - @dynamicMemberLookup - public struct Password: Equatable { - private let common: CommonFields - public let tokenUrl: URL - - public init(tokenUrl: URL, refreshUrl: URL? = nil, scopes: OrderedDictionary) { - self.tokenUrl = tokenUrl - common = .init(refreshUrl: refreshUrl, scopes: scopes) - } - - public subscript(dynamicMember path: KeyPath) -> T { - return common[keyPath: path] - } - } - - @dynamicMemberLookup - public struct ClientCredentials: Equatable { - private let common: CommonFields - public let tokenUrl: URL - - public init(tokenUrl: URL, refreshUrl: URL? = nil, scopes: OrderedDictionary) { - self.tokenUrl = tokenUrl - common = .init(refreshUrl: refreshUrl, scopes: scopes) - } - - public subscript(dynamicMember path: KeyPath) -> T { - return common[keyPath: path] - } - } - - @dynamicMemberLookup - public struct AuthorizationCode: Equatable { - private let common: CommonFields - public let authorizationUrl: URL - public let tokenUrl: URL - - public init(authorizationUrl: URL, tokenUrl: URL, refreshUrl: URL? = nil, scopes: OrderedDictionary) { - self.authorizationUrl = authorizationUrl - self.tokenUrl = tokenUrl - common = .init(refreshUrl: refreshUrl, scopes: scopes) - } - - public subscript(dynamicMember path: KeyPath) -> T { - return common[keyPath: path] - } - } -} - -// MARK: - Codable -extension OpenAPI.OAuthFlows { - private enum CodingKeys: String, CodingKey { - case implicit - case password - case clientCredentials - case authorizationCode - } -} - -extension OpenAPI.OAuthFlows.CommonFields { - private enum CodingKeys: String, CodingKey { - case refreshUrl - case scopes - } -} - -extension OpenAPI.OAuthFlows.Implicit { - private enum CodingKeys: String, CodingKey { - case authorizationUrl - } -} - -extension OpenAPI.OAuthFlows.Password { - private enum CodingKeys: String, CodingKey { - case tokenUrl - } -} - -extension OpenAPI.OAuthFlows.ClientCredentials { - private enum CodingKeys: String, CodingKey { - case tokenUrl - } -} - -extension OpenAPI.OAuthFlows.AuthorizationCode { - private enum CodingKeys: String, CodingKey { - case authorizationUrl - case tokenUrl - } -} - -extension OpenAPI.OAuthFlows: Encodable { - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encodeIfPresent(implicit, forKey: .implicit) - try container.encodeIfPresent(password, forKey: .password) - try container.encodeIfPresent(clientCredentials, forKey: .clientCredentials) - try container.encodeIfPresent(authorizationCode, forKey: .authorizationCode) - } -} - -extension OpenAPI.OAuthFlows: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - implicit = try container.decodeIfPresent(OpenAPI.OAuthFlows.Implicit.self, forKey: .implicit) - password = try container.decodeIfPresent(OpenAPI.OAuthFlows.Password.self, forKey: .password) - clientCredentials = try container.decodeIfPresent(OpenAPI.OAuthFlows.ClientCredentials.self, forKey: .clientCredentials) - authorizationCode = try container.decodeIfPresent(OpenAPI.OAuthFlows.AuthorizationCode.self, forKey: .authorizationCode) - } -} - -extension OpenAPI.OAuthFlows.CommonFields: Encodable { - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encodeIfPresent(refreshUrl?.absoluteString, forKey: .refreshUrl) - try container.encode(scopes, forKey: .scopes) - } -} - -extension OpenAPI.OAuthFlows.CommonFields: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - refreshUrl = try container.decodeURLAsStringIfPresent(forKey: .refreshUrl) - scopes = try container.decode(OrderedDictionary.self, forKey: .scopes) - } -} - -extension OpenAPI.OAuthFlows.Implicit: Encodable { - public func encode(to encoder: Encoder) throws { - try common.encode(to: encoder) - - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(authorizationUrl.absoluteString, forKey: .authorizationUrl) - } -} - -extension OpenAPI.OAuthFlows.Implicit: Decodable { - public init(from decoder: Decoder) throws { - common = try OpenAPI.OAuthFlows.CommonFields(from: decoder) - - let container = try decoder.container(keyedBy: CodingKeys.self) - - authorizationUrl = try container.decodeURLAsString(forKey: .authorizationUrl) - } -} - -extension OpenAPI.OAuthFlows.Password: Encodable { - public func encode(to encoder: Encoder) throws { - try common.encode(to: encoder) - - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(tokenUrl.absoluteString, forKey: .tokenUrl) - } -} - -extension OpenAPI.OAuthFlows.Password: Decodable { - public init(from decoder: Decoder) throws { - common = try OpenAPI.OAuthFlows.CommonFields(from: decoder) - - let container = try decoder.container(keyedBy: CodingKeys.self) - - tokenUrl = try container.decodeURLAsString(forKey: .tokenUrl) - } -} - -extension OpenAPI.OAuthFlows.ClientCredentials: Encodable { - public func encode(to encoder: Encoder) throws { - try common.encode(to: encoder) - - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(tokenUrl.absoluteString, forKey: .tokenUrl) - } -} - -extension OpenAPI.OAuthFlows.ClientCredentials: Decodable { - public init(from decoder: Decoder) throws { - common = try OpenAPI.OAuthFlows.CommonFields(from: decoder) - - let container = try decoder.container(keyedBy: CodingKeys.self) - - tokenUrl = try container.decodeURLAsString(forKey: .tokenUrl) - } -} - -extension OpenAPI.OAuthFlows.AuthorizationCode: Encodable { - public func encode(to encoder: Encoder) throws { - try common.encode(to: encoder) - - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(tokenUrl.absoluteString, forKey: .tokenUrl) - try container.encode(authorizationUrl.absoluteString, forKey: .authorizationUrl) - } -} - -extension OpenAPI.OAuthFlows.AuthorizationCode: Decodable { - public init(from decoder: Decoder) throws { - common = try OpenAPI.OAuthFlows.CommonFields(from: decoder) - - let container = try decoder.container(keyedBy: CodingKeys.self) - - tokenUrl = try container.decodeURLAsString(forKey: .tokenUrl) - authorizationUrl = try container.decodeURLAsString(forKey: .authorizationUrl) - } -} - -extension OpenAPI.OAuthFlows: Validatable {} -extension OpenAPI.OAuthFlows.Implicit: Validatable {} -extension OpenAPI.OAuthFlows.Password: Validatable {} -extension OpenAPI.OAuthFlows.ClientCredentials: Validatable {} -extension OpenAPI.OAuthFlows.AuthorizationCode: Validatable {} diff --git a/Sources/OpenAPIKit30/Security/SecurityScheme.swift b/Sources/OpenAPIKit30/Security/SecurityScheme.swift index 86e2ac496..9d9cc7a62 100644 --- a/Sources/OpenAPIKit30/Security/SecurityScheme.swift +++ b/Sources/OpenAPIKit30/Security/SecurityScheme.swift @@ -55,12 +55,6 @@ extension OpenAPI { case oauth2(flows: OAuthFlows) case openIdConnect(openIdConnectUrl: URL) } - - public enum Location: String, Codable, Equatable { - case query - case header - case cookie - } } } @@ -249,5 +243,4 @@ extension OpenAPI.SecurityScheme: LocallyDereferenceable { } } -extension OpenAPI.SecurityScheme.Location: Validatable {} extension OpenAPI.SecurityScheme.SecurityType.Name: Validatable {} diff --git a/Sources/OpenAPIKit30/_CoreReExport.swift b/Sources/OpenAPIKit30/_CoreReExport.swift index c342da20f..00447c26e 100644 --- a/Sources/OpenAPIKit30/_CoreReExport.swift +++ b/Sources/OpenAPIKit30/_CoreReExport.swift @@ -8,7 +8,52 @@ @_exported import struct OpenAPIKitCore.AnyCodable @_exported import struct OpenAPIKitCore.CodingPathError @_exported import enum OpenAPIKitCore.Either -@_exported import enum OpenAPIKitCore.OpenAPI @_exported import protocol OpenAPIKitCore.OpenAPIError @_exported import struct OpenAPIKitCore.OrderedDictionary @_exported import struct OpenAPIKitCore.URLTemplate + +import OpenAPIKitCore + +public extension OpenAPI { + typealias HttpMethod = OpenAPIKitCore.Shared.HttpMethod + typealias ContentType = OpenAPIKitCore.Shared.ContentType + typealias Error = OpenAPIKitCore.Error + typealias Warning = OpenAPIKitCore.Warning + typealias Path = OpenAPIKitCore.Shared.Path + typealias ComponentKey = OpenAPIKitCore.Shared.ComponentKey + typealias Discriminator = OpenAPIKitCore.Shared.Discriminator + typealias OAuthFlows = OpenAPIKitCore.Shared.OAuthFlows + typealias CallbackURL = OpenAPIKitCore.Shared.CallbackURL +} + +public extension OpenAPI.SecurityScheme { + typealias Location = OpenAPIKitCore.Shared.SecuritySchemeLocation +} + +public extension OpenAPI.Parameter.Context { + typealias Location = OpenAPIKitCore.Shared.ParameterContextLocation +} + +public extension OpenAPI.Parameter.SchemaContext { + typealias Style = OpenAPIKitCore.Shared.ParameterSchemaContextStyle +} + +public extension OpenAPI.Response { + typealias StatusCode = OpenAPIKitCore.Shared.ResponseStatusCode +} + +public extension JSONSchema { + typealias Permissions = OpenAPIKitCore.Shared.JSONSchemaPermissions + typealias StringContext = OpenAPIKitCore.Shared.StringContext + typealias ReferenceContext = OpenAPIKitCore.Shared.ReferenceContext +} + +public extension JSONTypeFormat { + typealias AnyFormat = OpenAPIKitCore.Shared.AnyFormat + typealias BooleanFormat = OpenAPIKitCore.Shared.BooleanFormat + typealias ObjectFormat = OpenAPIKitCore.Shared.ObjectFormat + typealias ArrayFormat = OpenAPIKitCore.Shared.ArrayFormat + typealias NumberFormat = OpenAPIKitCore.Shared.NumberFormat + typealias IntegerFormat = OpenAPIKitCore.Shared.IntegerFormat + typealias StringFormat = OpenAPIKitCore.Shared.StringFormat +} diff --git a/Sources/OpenAPIKitCompat/Compat30To31.swift b/Sources/OpenAPIKitCompat/Compat30To31.swift new file mode 100644 index 000000000..aff05338d --- /dev/null +++ b/Sources/OpenAPIKitCompat/Compat30To31.swift @@ -0,0 +1,602 @@ +// +// Compat30To31.swift +// +// +// Created by Mathew Polzin on 12/17/22. +// + +import OpenAPIKit +import OpenAPIKit30 + +private typealias OpenAPI31 = OpenAPIKit.OpenAPI +private typealias OpenAPI30 = OpenAPIKit30.OpenAPI + +public extension OpenAPIKit30.OpenAPI.Document { + func `convert`(to version: OpenAPIKit.OpenAPI.Document.Version) -> OpenAPIKit.OpenAPI.Document { + switch version { + case .v3_1_0: + return self.to31() + } + } +} + +private protocol To31 { + associatedtype Destination + func to31() -> Destination +} + +extension OpenAPIKit30.OpenAPI.Document: To31 { + fileprivate func to31() -> OpenAPI31.Document { + OpenAPI31.Document( + openAPIVersion: .v3_1_0, + info: info.to31(), + servers: servers.map { $0.to31() }, + paths: paths.mapValues { $0.to31() }, + components: components.to31(), + security: security.map { $0.to31() }, + tags: tags?.map { $0.to31() }, + externalDocs: externalDocs?.to31(), + vendorExtensions: vendorExtensions + ) + } +} + +extension OpenAPIKit30.OpenAPI.Document.Info: To31 { + fileprivate func to31() -> OpenAPI31.Document.Info { + OpenAPI31.Document.Info( + title: title, + description: description, + termsOfService: termsOfService, + contact: contact?.to31(), + license: license?.to31(), + version: version, + vendorExtensions: vendorExtensions + ) + } +} + +extension OpenAPIKit30.OpenAPI.Document.Info.License: To31 { + fileprivate func to31() -> OpenAPI31.Document.Info.License { + OpenAPI31.Document.Info.License( + name: name, + url: url, + vendorExtensions: vendorExtensions + ) + } +} + +extension OpenAPIKit30.OpenAPI.Document.Info.Contact: To31 { + fileprivate func to31() -> OpenAPI31.Document.Info.Contact { + OpenAPI31.Document.Info.Contact( + name: name, + url: url, + email: email, + vendorExtensions: vendorExtensions + ) + } +} + +extension OpenAPIKit30.OpenAPI.Server: To31 { + fileprivate func to31() -> OpenAPI31.Server { + + let newVariables = variables.mapValues { variable in + OpenAPI31.Server.Variable( + enum: variable.enum, + default: variable.default, + description: variable.description, + vendorExtensions: variable.vendorExtensions + ) + } + + return OpenAPI31.Server( + urlTemplate: urlTemplate, + description: description, + variables: newVariables, + vendorExtensions: vendorExtensions + ) + } +} + +extension OpenAPIKit30.OpenAPI.Header: To31 { + fileprivate func to31() -> OpenAPI31.Header { + let newSchemaOrContent: Either + switch schemaOrContent { + case .a(let context): + newSchemaOrContent = .a(context.to31()) + case .b(let contentMap): + newSchemaOrContent = .b(contentMap.mapValues { $0.to31() }) + } + + return OpenAPI31.Header( + schemaOrContent: newSchemaOrContent, + description: description, + required: `required`, + deprecated: deprecated, + vendorExtensions: vendorExtensions + ) + } +} + +extension OpenAPIKit30.OpenAPI.Parameter.Context: To31 { + fileprivate func to31() -> OpenAPI31.Parameter.Context { + switch self { + case .query(required: let required, allowEmptyValue: let allowEmptyValue): + return .query(required: required, allowEmptyValue: allowEmptyValue) + case .header(required: let required): + return .header(required: required) + case .path: + return .path + case .cookie(required: let required): + return .cookie(required: required) + } + } +} + +extension OpenAPIKit30.OpenAPI.Example: To31 { + fileprivate func to31() -> OpenAPI31.Example { + OpenAPI31.Example( + summary: summary, + description: description, + value: value, + vendorExtensions: vendorExtensions + ) + } +} + +extension Either: To31 where A: To31, B: To31 { + fileprivate func to31() -> Either { + switch self { + case .a(let a): + return .a(a.to31()) + case .b(let b): + return .b(b.to31()) + } + } +} + +fileprivate func eitherRefTo31(_ either: Either, T>) -> Either, U> where T: To31, T.Destination == U { + switch either { + case .a(let a): + return .a(.init(a.to31())) + case .b(let b): + return .b(b.to31()) + } +} + +extension OpenAPIKit30.OpenAPI.Parameter.SchemaContext: To31 { + fileprivate func to31() -> OpenAPI31.Parameter.SchemaContext { + let newExamples = examples?.mapValues(eitherRefTo31) + switch schema { + case .a(let ref): + if let newExamples = newExamples { + return OpenAPI31.Parameter.SchemaContext( + schemaReference: .init(ref.to31()), + style: style, + allowReserved: allowReserved, + examples: newExamples + ) + } else { + return OpenAPI31.Parameter.SchemaContext( + schemaReference: .init(ref.to31()), + style: style, + allowReserved: allowReserved, + example: example + ) + } + case .b(let schema): + if let newExamples = newExamples { + return OpenAPI31.Parameter.SchemaContext( + schema.to31(), + style: style, + allowReserved: allowReserved, + examples: newExamples + ) + } else { + return OpenAPI31.Parameter.SchemaContext( + schema.to31(), + style: style, + allowReserved: allowReserved, + example: example + ) + } + } + } +} + +extension OpenAPIKit30.OpenAPI.Content.Encoding: To31 { + fileprivate func to31() -> OpenAPI31.Content.Encoding { + OpenAPI31.Content.Encoding( + contentType: contentType, + headers: headers?.mapValues(eitherRefTo31), + style: style, + explode: explode, + allowReserved: allowReserved + ) + } +} + +extension OpenAPIKit30.OpenAPI.Content: To31 { + fileprivate func to31() -> OpenAPI31.Content { + if let newExamples = examples?.mapValues(eitherRefTo31) { + return OpenAPI31.Content( + schema: schema.map(eitherRefTo31), + examples: newExamples, + encoding: encoding?.mapValues { $0.to31() }, + vendorExtensions: vendorExtensions + ) + } else { + return OpenAPI31.Content( + schema: schema.map(eitherRefTo31), + example: example, + encoding: encoding?.mapValues { $0.to31() }, + vendorExtensions: vendorExtensions + ) + } + } +} + +extension OpenAPIKit30.OpenAPI.Parameter: To31 { + fileprivate func to31() -> OpenAPI31.Parameter { + let newSchemaOrContent: Either + switch schemaOrContent { + case .a(let context): + newSchemaOrContent = .a(context.to31()) + case .b(let contentMap): + newSchemaOrContent = .b(contentMap.mapValues { $0.to31() }) + } + + return OpenAPI31.Parameter( + name: name, + context: context.to31(), + schemaOrContent: newSchemaOrContent, + description: description, + deprecated: deprecated, + vendorExtensions: vendorExtensions + ) + } +} + +extension OpenAPIKit30.OpenAPI.RuntimeExpression.Source: To31 { + fileprivate func to31() -> OpenAPI31.RuntimeExpression.Source { + switch self { + case .header(name: let name): + return .header(name: name) + case .query(name: let name): + return .query(name: name) + case .path(name: let name): + return .path(name: name) + case .body(let ref): + return .body(ref?.to31()) + } + } +} + +extension OpenAPIKit30.OpenAPI.RuntimeExpression: To31 { + fileprivate func to31() -> OpenAPI31.RuntimeExpression { + switch self { + case .url: + return .url + case .method: + return .method + case .statusCode: + return .statusCode + case .request(let source): + return .request(source.to31()) + case .response(let source): + return .response(source.to31()) + } + } +} + +extension OpenAPIKit30.OpenAPI.Link: To31 { + fileprivate func to31() -> OpenAPI31.Link { + OpenAPI31.Link( + operation: operation, + parameters: parameters.mapValues { parameter in parameter.mapFirst { $0.to31() }}, + requestBody: requestBody?.mapFirst { $0.to31() }, + description: description, + server: server?.to31(), + vendorExtensions: vendorExtensions + ) + } +} + +extension OpenAPIKit30.OpenAPI.Response: To31 { + fileprivate func to31() -> OpenAPI31.Response { + OpenAPI31.Response( + description: description, + headers: headers?.mapValues(eitherRefTo31), + content: content.mapValues { $0.to31() }, + links: links.mapValues(eitherRefTo31), + vendorExtensions: vendorExtensions + ) + } +} + +extension OpenAPIKit30.OpenAPI.Request: To31 { + fileprivate func to31() -> OpenAPI31.Request { + OpenAPI31.Request( + description: description, + content: content.mapValues { $0.to31() }, + required: `required`, + vendorExtensions: vendorExtensions + ) + } +} + +extension OpenAPIKit30.OpenAPI.Callbacks: To31 { + fileprivate func to31() -> OpenAPI31.Callbacks { + self.mapValues { (pathItem: OpenAPI30.PathItem) in + .b(pathItem.to31()) + } + } +} + +extension OpenAPIKit30.OpenAPI.Operation: To31 { + fileprivate func to31() -> OpenAPI31.Operation { + if let newRequestBody = requestBody { + return OpenAPI31.Operation( + tags: tags, + summary: summary, + description: description, + externalDocs: externalDocs?.to31(), + operationId: operationId, + parameters: parameters.map(eitherRefTo31), + requestBody: eitherRefTo31(newRequestBody), + responses: responses.mapValues(eitherRefTo31), + callbacks: callbacks.mapValues(eitherRefTo31), + deprecated: deprecated, + security: security?.map { $0.to31() }, + servers: servers?.map { $0.to31() }, + vendorExtensions: vendorExtensions + ) + } else { + return OpenAPI31.Operation( + tags: tags, + summary: summary, + description: description, + externalDocs: externalDocs?.to31(), + operationId: operationId, + parameters: parameters.map(eitherRefTo31), + responses: responses.mapValues(eitherRefTo31), + callbacks: callbacks.mapValues(eitherRefTo31), + deprecated: deprecated, + security: security?.map { $0.to31() }, + servers: servers?.map { $0.to31() }, + vendorExtensions: vendorExtensions + ) + } + } +} + +extension OpenAPIKit30.OpenAPI.PathItem: To31 { + fileprivate func to31() -> OpenAPI31.PathItem { + OpenAPI31.PathItem( + summary: summary, + description: description, + servers: servers?.map { $0.to31() }, + parameters: parameters.map(eitherRefTo31), + get: `get`?.to31(), + put: put?.to31(), + post: post?.to31(), + delete: delete?.to31(), + options: options?.to31(), + head: head?.to31(), + patch: patch?.to31(), + trace: trace?.to31(), + vendorExtensions: vendorExtensions + ) + } +} + +extension OpenAPIKit30.OpenAPI.SecurityRequirement: To31 { + fileprivate func to31() -> OpenAPI31.SecurityRequirement { + var result = [OpenAPIKit.JSONReference: [String]]() + for (key, value) in self { + result[key.to31()] = value + } + return result + } +} + +private extension OpenAPIKit30.JSONReference.InternalReference { + func to31() -> OpenAPIKit.JSONReference.InternalReference { + switch self { + case .component(name: let name): + return .component(name: name) + case .path(let path): + return .path(.init(rawValue: path.rawValue)) + } + } +} + +private extension OpenAPIKit30.JSONReference { + func to31() -> OpenAPIKit.JSONReference { + switch self { + case .internal(let ref): + return .internal(ref.to31()) + case .external(let url): + return OpenAPIKit.JSONReference.external(url) + } + } +} + +extension OpenAPIKit30.OpenAPI.Tag: To31 { + fileprivate func to31() -> OpenAPI31.Tag { + OpenAPI31.Tag( + name: name, + description: description, + externalDocs: externalDocs?.to31(), + vendorExtensions: vendorExtensions + ) + } +} + +extension OpenAPIKit30.OpenAPI.ExternalDocumentation: To31 { + fileprivate func to31() -> OpenAPI31.ExternalDocumentation { + OpenAPI31.ExternalDocumentation( + description: description, + url: url, + vendorExtensions: vendorExtensions + ) + } +} + +extension OpenAPIKit30.OpenAPI.SecurityScheme.SecurityType: To31 { + fileprivate func to31() -> OpenAPI31.SecurityScheme.SecurityType { + switch self { + case .apiKey(name: let name, location: let location): + return .apiKey(name: name, location: location) + case .http(scheme: let scheme, bearerFormat: let bearerFormat): + return .http(scheme: scheme, bearerFormat: bearerFormat) + case .oauth2(flows: let flows): + return .oauth2(flows: flows) + case .openIdConnect(openIdConnectUrl: let openIdConnectUrl): + return .openIdConnect(openIdConnectUrl: openIdConnectUrl) + } + } +} + +extension OpenAPIKit30.OpenAPI.SecurityScheme: To31 { + fileprivate func to31() -> OpenAPI31.SecurityScheme { + OpenAPI31.SecurityScheme( + type: type.to31(), + description: description, + vendorExtensions: vendorExtensions + ) + } +} + +extension OpenAPIKit30.JSONTypeFormat: To31 { + fileprivate func to31() -> OpenAPIKit.JSONTypeFormat { + switch self { + case .boolean(let f): + return .boolean(f) + case .object(let f): + return .object(f) + case .array(let f): + return .array(f) + case .number(let f): + return .number(f) + case .integer(let f): + return .integer(f) + case .string(let f): + return .string(f) + } + } +} + +extension OpenAPIKit30.JSONSchema.CoreContext: To31 where Format: OpenAPIKit.OpenAPIFormat { + fileprivate func to31() -> OpenAPIKit.JSONSchema.CoreContext { + OpenAPIKit.JSONSchema.CoreContext( + format: format, + required: `required`, + nullable: nullable, + permissions: permissions, + deprecated: deprecated, + title: title, + description: description, + discriminator: discriminator, + externalDocs: externalDocs?.to31(), + allowedValues: allowedValues, + defaultValue: defaultValue, + examples: [example].compactMap { $0 } + ) + } +} + +extension OpenAPIKit30.JSONSchema.NumericContext: To31 { + fileprivate func to31() -> OpenAPIKit.JSONSchema.NumericContext { + OpenAPIKit.JSONSchema.NumericContext( + multipleOf: multipleOf, + maximum: maximum.map { ($0.value, $0.exclusive) }, + minimum: minimum.map { ($0.value, $0.exclusive) } + ) + } +} + +extension OpenAPIKit30.JSONSchema.IntegerContext: To31 { + fileprivate func to31() -> OpenAPIKit.JSONSchema.IntegerContext { + OpenAPIKit.JSONSchema.IntegerContext( + multipleOf: multipleOf, + maximum: maximum.map { ($0.value, $0.exclusive) }, + minimum: minimum.map { ($0.value, $0.exclusive) } + ) + } +} + +extension OpenAPIKit30.JSONSchema.ArrayContext: To31 { + fileprivate func to31() -> OpenAPIKit.JSONSchema.ArrayContext { + OpenAPIKit.JSONSchema.ArrayContext( + items: items.map { $0.to31() }, + maxItems: maxItems, + minItems: minItems, + uniqueItems: uniqueItems + ) + } +} + +extension OpenAPIKit30.JSONSchema.ObjectContext: To31 { + fileprivate func to31() -> OpenAPIKit.JSONSchema.ObjectContext { + OpenAPIKit.JSONSchema.ObjectContext( + properties: properties.mapValues { $0.to31() }, + additionalProperties: additionalProperties?.mapSecond { $0.to31() }, + maxProperties: maxProperties, + minProperties: minProperties + ) + } +} + +extension OpenAPIKit30.JSONSchema: To31 { + fileprivate func to31() -> OpenAPIKit.JSONSchema { + let schema: OpenAPIKit.JSONSchema.Schema + + switch value { + case .boolean(let core): + schema = .boolean(core.to31()) + case .number(let core, let numeric): + schema = .number(core.to31(), numeric.to31()) + case .integer(let core, let integral): + schema = .integer(core.to31(), integral.to31()) + case .string(let core, let stringy): + schema = .string(core.to31(), stringy) + case .object(let core, let objective): + schema = .object(core.to31(), objective.to31()) + case .array(let core, let listy): + schema = .array(core.to31(), listy.to31()) + case .all(of: let of, core: let core): + schema = .all(of: of.map { $0.to31() }, core: core.to31()) + case .one(of: let of, core: let core): + schema = .one(of: of.map { $0.to31() }, core: core.to31()) + case .any(of: let of, core: let core): + schema = .any(of: of.map { $0.to31() }, core: core.to31()) + case .not(let not, core: let core): + schema = .not(not.to31(), core: core.to31()) + case .reference(let ref, let context): + schema = .reference(ref.to31(), context) + case .fragment(let core): + schema = .fragment(core.to31()) + } + + return OpenAPIKit.JSONSchema( + schema: schema + ) + } +} + +extension OpenAPIKit30.OpenAPI.Components: To31 { + fileprivate func to31() -> OpenAPI31.Components { + OpenAPI31.Components( + schemas: schemas.mapValues { $0.to31() }, + responses: responses.mapValues { $0.to31() }, + parameters: parameters.mapValues { $0.to31() }, + examples: examples.mapValues { $0.to31() }, + requestBodies: requestBodies.mapValues { $0.to31() }, + headers: headers.mapValues { $0.to31() }, + securitySchemes: securitySchemes.mapValues { $0.to31() }, + links: links.mapValues { $0.to31() }, + callbacks: callbacks.mapValues { $0.to31() }, + vendorExtensions: vendorExtensions + ) + } +} diff --git a/Sources/OpenAPIKitCompat/Either+Map.swift b/Sources/OpenAPIKitCompat/Either+Map.swift new file mode 100644 index 000000000..2ec2efc22 --- /dev/null +++ b/Sources/OpenAPIKitCompat/Either+Map.swift @@ -0,0 +1,28 @@ +// +// Either+Map.swift +// +// +// Created by Mathew Polzin on 12/19/22. +// + +import OpenAPIKitCore + +extension Either { + internal func mapFirst(_ transform: (A) -> T) -> Either { + switch self { + case .a(let a): + return .a(transform(a)) + case .b(let b): + return .b(b) + } + } + + internal func mapSecond(_ transform: (B) -> T) -> Either { + switch self { + case .a(let a): + return .a(a) + case .b(let b): + return .b(transform(b)) + } + } +} diff --git a/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/EitherDecodeNoTypesMatchedErrorExtensions.swift b/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/EitherDecodeNoTypesMatchedErrorExtensions.swift index 71f3f9950..4cb250295 100644 --- a/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/EitherDecodeNoTypesMatchedErrorExtensions.swift +++ b/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/EitherDecodeNoTypesMatchedErrorExtensions.swift @@ -39,10 +39,10 @@ public extension EitherDecodeNoTypesMatchedError { // If the intention was not to use a reference, this error will be superfluous. let error1 = isRefKeyNotFoundError(failure1) ? nil - : OpenAPI.Error(from: failure1.error.replacingPath(with: failure1.codingPath(relativeTo: codingPath))).localizedDescription + : Error(from: failure1.error.replacingPath(with: failure1.codingPath(relativeTo: codingPath))).localizedDescription let error2 = isRefKeyNotFoundError(failure2) ? nil - : OpenAPI.Error(from: failure2.error.replacingPath(with: failure2.codingPath(relativeTo: codingPath))).localizedDescription + : Error(from: failure2.error.replacingPath(with: failure2.codingPath(relativeTo: codingPath))).localizedDescription let details1 = error1 .map { "\(String(describing: failure1.type)) could not be decoded because:\n\($0)" } diff --git a/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/OpenAPIDecodingErrors.swift b/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/OpenAPIDecodingErrors.swift index 86273409b..781569ef0 100644 --- a/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/OpenAPIDecodingErrors.swift +++ b/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/OpenAPIDecodingErrors.swift @@ -5,7 +5,7 @@ // Created by Mathew Polzin on 2/23/20. // -extension OpenAPI.Error { +extension Error { // Just creating a namespace public enum Decoding {} } diff --git a/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/OpenAPIError.swift b/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/OpenAPIError.swift index 5ab249203..544be57e3 100644 --- a/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/OpenAPIError.swift +++ b/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/OpenAPIError.swift @@ -5,52 +5,50 @@ // Created by Mathew Polzin on 3/30/20. // -extension OpenAPI { - /// An `OpenAPI.Error` can be constructed from any error thrown while decoding - /// an OpenAPI document. This wrapper provides a superior human-readable error - /// and a human readable coding path. - /// - /// Example: - /// - /// do { - /// document = try JSONDecoder().decode(OpenAPI.Document.self, from: ...) - /// } catch let error { - /// let prettyError = OpenAPI.Error(from: error) - /// print(prettyError.localizedDescription) - /// print(prettyError.codingPathString) - /// } - /// - public struct Error: Swift.Error, CustomStringConvertible { - - public let localizedDescription: String - public let codingPath: [CodingKey] - public let underlyingError: Swift.Error - - public var codingPathString: String { codingPath.stringValue } - - public init(from underlyingError: Swift.Error) { - self.underlyingError = underlyingError - if let openAPIError = underlyingError as? OpenAPIError { +/// An `OpenAPI.Error` can be constructed from any error thrown while decoding +/// an OpenAPI document. This wrapper provides a superior human-readable error +/// and a human readable coding path. +/// +/// Example: +/// +/// do { +/// document = try JSONDecoder().decode(OpenAPI.Document.self, from: ...) +/// } catch let error { +/// let prettyError = OpenAPI.Error(from: error) +/// print(prettyError.localizedDescription) +/// print(prettyError.codingPathString) +/// } +/// +public struct Error: Swift.Error, CustomStringConvertible { + + public let localizedDescription: String + public let codingPath: [CodingKey] + public let underlyingError: Swift.Error + + public var codingPathString: String { codingPath.stringValue } + + public init(from underlyingError: Swift.Error) { + self.underlyingError = underlyingError + if let openAPIError = underlyingError as? OpenAPIError { + localizedDescription = openAPIError.localizedDescription + codingPath = openAPIError.codingPath + + } else if let decodingError = underlyingError as? Swift.DecodingError { + + if let openAPIError = decodingError.underlyingError as? OpenAPIError { localizedDescription = openAPIError.localizedDescription codingPath = openAPIError.codingPath - - } else if let decodingError = underlyingError as? Swift.DecodingError { - - if let openAPIError = decodingError.underlyingError as? OpenAPIError { - localizedDescription = openAPIError.localizedDescription - codingPath = openAPIError.codingPath - } else { - let wrappedError = DecodingErrorWrapper(decodingError: decodingError) - localizedDescription = wrappedError.localizedDescription - codingPath = wrappedError.codingPath - } - } else { - localizedDescription = underlyingError.localizedDescription - codingPath = [] + let wrappedError = DecodingErrorWrapper(decodingError: decodingError) + localizedDescription = wrappedError.localizedDescription + codingPath = wrappedError.codingPath } - } - public var description: String { localizedDescription } + } else { + localizedDescription = underlyingError.localizedDescription + codingPath = [] + } } + + public var description: String { localizedDescription } } diff --git a/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/OpenAPIWarning.swift b/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/OpenAPIWarning.swift index cb4862c79..21aa92406 100644 --- a/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/OpenAPIWarning.swift +++ b/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/OpenAPIWarning.swift @@ -5,54 +5,52 @@ // Created by Mathew Polzin on 12/19/21. // -extension OpenAPI { - public enum Warning: Swift.Error { - case underlyingError(OpenAPIError) - case message(String) +public enum Warning: Swift.Error { + case underlyingError(OpenAPIError) + case message(String) - public var underlyingError: OpenAPIError? { - switch self { - case .underlyingError(let error): return error - case .message: return nil - } + public var underlyingError: OpenAPIError? { + switch self { + case .underlyingError(let error): return error + case .message: return nil } + } - public var subjectName: String? { - switch self { - case .underlyingError(let err): return err.subjectName - default: return nil - } + public var subjectName: String? { + switch self { + case .underlyingError(let err): return err.subjectName + default: return nil } + } - public var contextString: String? { - switch self { - case .underlyingError(let err): return err.contextString - default: return nil - } + public var contextString: String? { + switch self { + case .underlyingError(let err): return err.contextString + default: return nil } + } - public var errorCategory: ErrorCategory? { - switch self { - case .underlyingError(let err): return err.errorCategory - default: return nil - } + public var errorCategory: ErrorCategory? { + switch self { + case .underlyingError(let err): return err.errorCategory + default: return nil } + } - public var codingPath: [CodingKey]? { - switch self { - case .underlyingError(let err): return err.codingPath - default: return nil - } + public var codingPath: [CodingKey]? { + switch self { + case .underlyingError(let err): return err.codingPath + default: return nil } + } - /// Get a human readable string value of the coding path. - public var codingPathString : String? { - return codingPath?.stringValue - } + /// Get a human readable string value of the coding path. + public var codingPathString : String? { + return codingPath?.stringValue } } -extension OpenAPI.Warning: CustomStringConvertible { +extension Warning: CustomStringConvertible { /// Description of warning. public var localizedDescription: String { switch self { @@ -67,5 +65,5 @@ extension OpenAPI.Warning: CustomStringConvertible { } public protocol HasWarnings { - var warnings: [OpenAPI.Warning] { get } + var warnings: [Warning] { get } } diff --git a/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/RequestDecodingError.swift b/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/RequestDecodingError.swift index 623e53edc..af8d921ba 100644 --- a/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/RequestDecodingError.swift +++ b/Sources/OpenAPIKitCore/Encoding and Decoding Errors And Warnings/RequestDecodingError.swift @@ -5,7 +5,7 @@ // Created by Mathew Polzin on 2/28/20. // -extension OpenAPI.Error.Decoding { +extension Error.Decoding { public struct Request: OpenAPIError { public let context: Context internal let relativeCodingPath: [CodingKey] @@ -23,7 +23,7 @@ extension OpenAPI.Error.Decoding { } } -extension OpenAPI.Error.Decoding.Request { +extension Error.Decoding.Request { public var subjectName: String { switch context { case .inconsistency(let error): @@ -91,7 +91,7 @@ extension OpenAPI.Error.Decoding.Request { } } -extension OpenAPI.Error.Decoding.Request: DiggingError { +extension Error.Decoding.Request: DiggingError { public init(unwrapping error: Swift.DecodingError) { if let decodingError = error.underlyingError as? Swift.DecodingError { self = Self(unwrapping: decodingError) diff --git a/Sources/OpenAPIKitCore/OrderedDictionary/OrderedDictionary.swift b/Sources/OpenAPIKitCore/OrderedDictionary/OrderedDictionary.swift index defcfa43c..8779f30ec 100644 --- a/Sources/OpenAPIKitCore/OrderedDictionary/OrderedDictionary.swift +++ b/Sources/OpenAPIKitCore/OrderedDictionary/OrderedDictionary.swift @@ -21,9 +21,9 @@ public struct OrderedDictionary: HasWarnings where Key: Hashable { private var orderedKeys: [Key] private var unorderedHash: [Key: Value] - private var _warnings: [OpenAPI.Warning] + private var _warnings: [Warning] - public var warnings: [OpenAPI.Warning] { _warnings } + public var warnings: [Warning] { _warnings } public init() { orderedKeys = [] diff --git a/Sources/OpenAPIKitCore/Shared.swift b/Sources/OpenAPIKitCore/Shared.swift new file mode 100644 index 000000000..5976827a3 --- /dev/null +++ b/Sources/OpenAPIKitCore/Shared.swift @@ -0,0 +1,11 @@ +// +// Shared.swift +// +// +// Created by Mathew Polzin on 12/24/22. +// + +/// A Core namespace for OpenAPI types that are shared by multiple OpenAPI standard versions. +public enum Shared { + +} diff --git a/Sources/OpenAPIKitCore/Shared/CallbackURL.swift b/Sources/OpenAPIKitCore/Shared/CallbackURL.swift new file mode 100644 index 000000000..5e2d6295d --- /dev/null +++ b/Sources/OpenAPIKitCore/Shared/CallbackURL.swift @@ -0,0 +1,66 @@ +// +// CallbackURL.swift +// +// +// Created by Mathew Polzin on 12/19/22. +// + +import Foundation + +extension Shared { + /// A URL template where the placeholders are OpenAPI **Runtime Expressions** instead + /// of named variables. + /// + /// See [OpenAPI Callback Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#callback-object) and [OpenAPI Runtime Expression](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#runtime-expressions) for more. + /// + public struct CallbackURL: Hashable, RawRepresentable { + public let template: URLTemplate + + /// The string value of the URL without variable replacement. + /// + /// Variables cannot be replaced based on other information in the + /// OpenAPI document; they are only available at "runtime" which is + /// where the name of the OpenAPI structure `CallbackURL` + /// represents comes from. + public var rawValue: String { + template.rawValue + } + + /// Get a URL from the runtime expression if it is a valid URL without + /// variable replacement. + /// + /// Callback URLs with variables in them will not be valid URLs + /// and are therefore guaranteed to return `nil`. + public var url: URL? { + template.url + } + + /// Create a CallbackURL from the string if possible. + public init?(rawValue: String) { + guard let template = URLTemplate(rawValue: rawValue) else { + return nil + } + self.template = template + } + + public init(url: URL) { + template = .init(url: url) + } + } +} + +extension Shared.CallbackURL: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + try container.encode(rawValue) + } +} + +extension Shared.CallbackURL: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + + template = try container.decode(URLTemplate.self) + } +} diff --git a/Sources/OpenAPIKitCore/Shared/ComponentKey.swift b/Sources/OpenAPIKitCore/Shared/ComponentKey.swift new file mode 100644 index 000000000..f8ff7f48e --- /dev/null +++ b/Sources/OpenAPIKitCore/Shared/ComponentKey.swift @@ -0,0 +1,69 @@ +// +// ComponentKey.swift +// +// +// Created by Mathew Polzin on 12/17/22. +// + +import Foundation + +extension Shared { + /// A key for one of the component dictionaries. + /// + /// These keys must match the regex + /// `^[a-zA-Z0-9\.\-_]+$`. + public struct ComponentKey: RawRepresentable, ExpressibleByStringLiteral, Codable, Equatable, Hashable, StringConvertibleHintProvider { + public let rawValue: String + + public init(stringLiteral value: StringLiteralType) { + self.rawValue = value + } + + public init?(rawValue: String) { + guard !rawValue.isEmpty else { + return nil + } + var allowedCharacters = CharacterSet.alphanumerics + allowedCharacters.insert(charactersIn: "-_.") + guard CharacterSet(charactersIn: rawValue).isSubset(of: allowedCharacters) else { + return nil + } + self.rawValue = rawValue + } + + public static func problem(with proposedString: String) -> String? { + if Self(rawValue: proposedString) == nil { + return "Keys for components in the Components Object must conform to the regex `^[a-zA-Z0-9\\.\\-_]+$`. '\(proposedString)' does not.." + } + return nil + } + + public init(from decoder: Decoder) throws { + let rawValue = try decoder.singleValueContainer().decode(String.self) + guard let key = Self(rawValue: rawValue) else { + throw InconsistencyError( + subjectName: "Component Key", + details: "Keys for components in the Components Object must conform to the regex `^[a-zA-Z0-9\\.\\-_]+$`. '\(rawValue)' does not..", + codingPath: decoder.codingPath + ) + } + self = key + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + // we check for consistency on encode because a string literal + // may result in an invalid component key being constructed. + guard Self(rawValue: rawValue) != nil else { + throw InconsistencyError( + subjectName: "Component Key", + details: "Keys for components in the Components Object must conform to the regex `^[a-zA-Z0-9\\.\\-_]+$`. '\(rawValue)' does not..", + codingPath: container.codingPath + ) + } + + try container.encode(rawValue) + } + } +} diff --git a/Sources/OpenAPIKitCore/ContentType.swift b/Sources/OpenAPIKitCore/Shared/ContentType.swift similarity index 95% rename from Sources/OpenAPIKitCore/ContentType.swift rename to Sources/OpenAPIKitCore/Shared/ContentType.swift index 24a547ebb..bf81f267a 100644 --- a/Sources/OpenAPIKitCore/ContentType.swift +++ b/Sources/OpenAPIKitCore/Shared/ContentType.swift @@ -5,11 +5,11 @@ // Created by Mathew Polzin on 12/29/19. // -extension OpenAPI { +extension Shared { /// The Content Type of an API request or response body. public struct ContentType: Codable, Equatable, Hashable, RawRepresentable, HasWarnings { internal let underlyingType: Builtin - public let warnings: [OpenAPI.Warning] + public let warnings: [Warning] /// The type and subtype of the content type. This is everything except for /// any parameters that are also attached. @@ -43,7 +43,7 @@ extension OpenAPI { let parts = rawValue.split(separator: ";") let type = parts.first.map(String.init) ?? rawValue - var warnings = [OpenAPI.Warning]() + var warnings = [Warning]() var params = [String:String]() for part in parts.dropFirst() { switch Self.parseParameter(part) { @@ -69,14 +69,14 @@ extension OpenAPI { self.warnings = warnings } - private static func parseParameter(_ param: String.SubSequence) -> Result<(String, String), OpenAPI.Warning> { + private static func parseParameter(_ param: String.SubSequence) -> Result<(String, String), Warning> { let parts = param.split(separator: "=") guard parts.count == 2, let name = parts.first, let value = parts.last else { - return .failure(.message("Could not parse a Content Type parameter from '\(param)'")) - } + return .failure(.message("Could not parse a Content Type parameter from '\(param)'")) + } return .success( ( @@ -95,7 +95,7 @@ extension OpenAPI { } // convenience constructors -public extension OpenAPI.ContentType { +public extension Shared.ContentType { /// Bitmap image static let bmp: Self = .init(.bmp) static let css: Self = .init(.css) @@ -157,7 +157,7 @@ public extension OpenAPI.ContentType { static let any: Self = .init(.any) } -extension OpenAPI.ContentType { +extension Shared.ContentType { // This internal representation makes it easier to ensure that the popular // builtin types supported are fully covered in their rawValue implementation. internal enum Builtin: Codable, Equatable, Hashable { @@ -223,7 +223,7 @@ extension OpenAPI.ContentType { } } -extension OpenAPI.ContentType.Builtin: RawRepresentable { +extension Shared.ContentType.Builtin: RawRepresentable { public var rawValue: String { switch self { case .bmp: return "image/bmp" @@ -316,4 +316,4 @@ extension OpenAPI.ContentType.Builtin: RawRepresentable { } } -extension OpenAPI.ContentType: Validatable {} +extension Shared.ContentType: Validatable {} diff --git a/Sources/OpenAPIKit/Discriminator.swift b/Sources/OpenAPIKitCore/Shared/Discriminator.swift similarity index 85% rename from Sources/OpenAPIKit/Discriminator.swift rename to Sources/OpenAPIKitCore/Shared/Discriminator.swift index e157f0266..fe66f71ab 100644 --- a/Sources/OpenAPIKit/Discriminator.swift +++ b/Sources/OpenAPIKitCore/Shared/Discriminator.swift @@ -5,11 +5,9 @@ // Created by Mathew Polzin on 10/6/19. // -import OpenAPIKitCore - -extension OpenAPI { +extension Shared { /// OpenAPI Spec "Disciminator Object" - /// + /// /// See [OpenAPI Discriminator Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#discriminator-object). public struct Discriminator: Equatable { public let propertyName: String @@ -25,7 +23,7 @@ extension OpenAPI { // MARK: - Codable -extension OpenAPI.Discriminator: Encodable { +extension Shared.Discriminator: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -34,7 +32,7 @@ extension OpenAPI.Discriminator: Encodable { } } -extension OpenAPI.Discriminator: Decodable { +extension Shared.Discriminator: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -43,11 +41,11 @@ extension OpenAPI.Discriminator: Decodable { } } -extension OpenAPI.Discriminator { +extension Shared.Discriminator { private enum CodingKeys: String, CodingKey { case propertyName case mapping } } -extension OpenAPI.Discriminator: Validatable {} +extension Shared.Discriminator: Validatable {} diff --git a/Sources/OpenAPIKitCore/HttpMethod.swift b/Sources/OpenAPIKitCore/Shared/HttpMethod.swift similarity index 97% rename from Sources/OpenAPIKitCore/HttpMethod.swift rename to Sources/OpenAPIKitCore/Shared/HttpMethod.swift index 6165d72d0..316a15ddd 100644 --- a/Sources/OpenAPIKitCore/HttpMethod.swift +++ b/Sources/OpenAPIKitCore/Shared/HttpMethod.swift @@ -5,7 +5,7 @@ // Created by Mathew Polzin on 12/29/19. // -extension OpenAPI { +extension Shared { /// Represents the HTTP methods supported by the /// OpenAPI Specification. /// diff --git a/Sources/OpenAPIKitCore/Shared/JSONSchemaPermissions.swift b/Sources/OpenAPIKitCore/Shared/JSONSchemaPermissions.swift new file mode 100644 index 000000000..c6363d646 --- /dev/null +++ b/Sources/OpenAPIKitCore/Shared/JSONSchemaPermissions.swift @@ -0,0 +1,14 @@ +// +// JSONSchemaPermissions.swift +// +// +// Created by Mathew Polzin on 12/17/22. +// + +extension Shared { + public enum JSONSchemaPermissions: String, Codable { + case readOnly + case writeOnly + case readWrite + } +} diff --git a/Sources/OpenAPIKitCore/Shared/JSONSchemaSimpleContexts.swift b/Sources/OpenAPIKitCore/Shared/JSONSchemaSimpleContexts.swift new file mode 100644 index 000000000..0e65dd39f --- /dev/null +++ b/Sources/OpenAPIKitCore/Shared/JSONSchemaSimpleContexts.swift @@ -0,0 +1,82 @@ +// +// JSONSchemaSimpleContexts.swift +// +// +// Created by Mathew Polzin on 12/19/22. +// + +extension Shared { + /// The context that only applies to `.string` schemas. + public struct StringContext: Equatable { + public let maxLength: Int? + let _minLength: Int? + + public var minLength: Int { + return _minLength ?? 0 + } + + /// Regular expression + public let pattern: String? + + public init( + maxLength: Int? = nil, + minLength: Int? = nil, + pattern: String? = nil + ) { + self.maxLength = maxLength + self._minLength = minLength + self.pattern = pattern + } + + // we make the following a static function so it doesn't muddy the namespace while auto-completing on a value. + public static func _minLength(_ context: StringContext) -> Int? { + return context._minLength + } + } + + + /// The context that only applies to `.reference` schemas. + public struct ReferenceContext: Equatable { + public let required: Bool + + public init(required: Bool = true) { + self.required = required + } + + public func requiredContext() -> ReferenceContext { + return .init(required: true) + } + + public func optionalContext() -> ReferenceContext { + return .init(required: false) + } + } +} + +extension Shared.StringContext { + public enum CodingKeys: String, CodingKey { + case maxLength + case minLength + case pattern + } +} + +extension Shared.StringContext: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encodeIfPresent(maxLength, forKey: .maxLength) + try container.encodeIfPresent(_minLength, forKey: .minLength) + try container.encodeIfPresent(pattern, forKey: .pattern) + } +} + +extension Shared.StringContext: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + maxLength = try container.decodeIfPresent(Int.self, forKey: .maxLength) + _minLength = try container.decodeIfPresent(Int.self, forKey: .minLength) + pattern = try container.decodeIfPresent(String.self, forKey: .pattern) + } +} diff --git a/Sources/OpenAPIKitCore/Shared/JSONTypeFormat.swift b/Sources/OpenAPIKitCore/Shared/JSONTypeFormat.swift new file mode 100644 index 000000000..15bca0aa8 --- /dev/null +++ b/Sources/OpenAPIKitCore/Shared/JSONTypeFormat.swift @@ -0,0 +1,272 @@ +// +// JSONTypeFormat.swift +// +// +// Created by Mathew Polzin on 12/17/22. +// + + +extension Shared { + /// A format used when no type is known or any type is allowed. + /// + /// There are no built-in formats that do not have an associated + /// type, but it is still important to be able to specify a format without + /// a type. This can come into play when writing fragments of schemas + /// to be combined later. + public enum AnyFormat: RawRepresentable, Equatable { + case generic + case other(String) + + public var rawValue: String { + switch self { + case .generic: return "" + case .other(let other): + return other + } + } + + public init(rawValue: String) { + switch rawValue { + case "": self = .generic + default: self = .other(rawValue) + } + } + + public typealias SwiftType = AnyCodable + + public static var unspecified: AnyFormat { + return .generic + } + } + + /// The allowed "format" properties for `.boolean` schemas. + public enum BooleanFormat: RawRepresentable, Equatable { + case generic + case other(String) + + public var rawValue: String { + switch self { + case .generic: return "" + case .other(let other): + return other + } + } + + public init(rawValue: String) { + switch rawValue { + case "": self = .generic + default: self = .other(rawValue) + } + } + + public typealias SwiftType = Bool + + public static var unspecified: BooleanFormat { + return .generic + } + } + + /// The allowed "format" properties for `.object` schemas. + public enum ObjectFormat: RawRepresentable, Equatable { + case generic + case other(String) + + public var rawValue: String { + switch self { + case .generic: return "" + case .other(let other): + return other + } + } + + public init(rawValue: String) { + switch rawValue { + case "": self = .generic + default: self = .other(rawValue) + } + } + + public typealias SwiftType = AnyCodable + + public static var unspecified: ObjectFormat { + return .generic + } + } + + /// The allowed "format" properties for `.array` schemas. + public enum ArrayFormat: RawRepresentable, Equatable { + case generic + case other(String) + + public var rawValue: String { + switch self { + case .generic: return "" + case .other(let other): + return other + } + } + + public init(rawValue: String) { + switch rawValue { + case "": self = .generic + default: self = .other(rawValue) + } + } + + public typealias SwiftType = [AnyCodable] + + public static var unspecified: ArrayFormat { + return .generic + } + } + + /// The allowed "format" properties for `.number` schemas. + public enum NumberFormat: RawRepresentable, Equatable { + case generic + case float + case double + case other(String) + + public var rawValue: String { + switch self { + case .generic: return "" + case .float: return "float" + case .double: return "double" + case .other(let other): + return other + } + } + + public init(rawValue: String) { + switch rawValue { + case "": self = .generic + case "float": self = .float + case "double": self = .double + default: self = .other(rawValue) + } + } + + public typealias SwiftType = Double + + public static var unspecified: NumberFormat { + return .generic + } + } + + /// The allowed "format" properties for `.integer` schemas. + public enum IntegerFormat: RawRepresentable, Equatable { + case generic + case int32 + case int64 + case other(String) + + public var rawValue: String { + switch self { + case .generic: return "" + case .int32: return "int32" + case .int64: return "int64" + case .other(let other): + return other + } + } + + public init(rawValue: String) { + switch rawValue { + case "": self = .generic + case "int32": self = .int32 + case "int64": self = .int64 + default: self = .other(rawValue) + } + } + + public typealias SwiftType = Int + + public static var unspecified: IntegerFormat { + return .generic + } + } + + /// The allowed "format" properties for `.string` schemas. + public enum StringFormat: RawRepresentable, Equatable { + case generic + case byte + case binary + case date + /// A string instance is valid against this attribute if it is a valid + /// date representation as defined by + /// https://tools.ietf.org/html/rfc3339#section-5.6 + case dateTime + case password + case other(String) + + public var rawValue: String { + switch self { + case .generic: return "" + case .byte: return "byte" + case .binary: return "binary" + case .date: return "date" + case .dateTime: return "date-time" + case .password: return "password" + case .other(let other): + return other + } + } + + public init(rawValue: String) { + switch rawValue { + case "": self = .generic + case "byte": self = .byte + case "binary": self = .binary + case "date": self = .date + case "date-time": self = .dateTime + case "password": self = .password + default: self = .other(rawValue) + } + } + + public typealias SwiftType = String + + public static var unspecified: StringFormat { + return .generic + } + } +} + +extension Shared.StringFormat { + /// Popular non-standard "format" properties for `.string` schemas. + /// + /// Specify with e.g. `.string(format: .extended(.uuid))` + public enum Extended: String, Equatable { + case uuid = "uuid" + case email = "email" + case hostname = "hostname" + case ipv4 = "ipv4" + case ipv6 = "ipv6" + /// A string instance is valid against this attribute if it is a valid + /// URI, according to + /// https://tools.ietf.org/html/rfc3986 + case uri = "uri" + /// A string instance is valid against this attribute if it is a valid + /// URI, according to + /// https://tools.ietf.org/html/rfc3986 + case uriReference = "uriref" + } + + public static func extended(_ format: Extended) -> Self { + return .other(format.rawValue) + } +} + +extension Shared.IntegerFormat { + /// Popular non-standard "format" properties for `.integer` schemas. + /// + /// Specify with e.g. `.integer(format: .extended(.uint32))` + public enum Extended: String, Equatable { + case uint32 = "uint32" + case uint64 = "uint64" + } + + public static func extended(_ format: Extended) -> Self { + return .other(format.rawValue) + } +} diff --git a/Sources/OpenAPIKit/Security/OAuthFlows.swift b/Sources/OpenAPIKitCore/Shared/OAuthFlows.swift similarity index 76% rename from Sources/OpenAPIKit/Security/OAuthFlows.swift rename to Sources/OpenAPIKitCore/Shared/OAuthFlows.swift index 8d24ed999..1a8253abe 100644 --- a/Sources/OpenAPIKit/Security/OAuthFlows.swift +++ b/Sources/OpenAPIKitCore/Shared/OAuthFlows.swift @@ -5,10 +5,9 @@ // Created by Mathew Polzin on 1/23/20. // -import OpenAPIKitCore import Foundation -extension OpenAPI { +extension Shared { /// OpenAPI Spec "Oauth Flows Object" /// /// See [OpenAPI Oauth Flows Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#oauth-flows-object). @@ -32,7 +31,7 @@ extension OpenAPI { } } -extension OpenAPI.OAuthFlows { +extension Shared.OAuthFlows { public typealias Scope = String public typealias ScopeDescription = String @@ -105,7 +104,7 @@ extension OpenAPI.OAuthFlows { } // MARK: - Codable -extension OpenAPI.OAuthFlows { +extension Shared.OAuthFlows { private enum CodingKeys: String, CodingKey { case implicit case password @@ -114,39 +113,39 @@ extension OpenAPI.OAuthFlows { } } -extension OpenAPI.OAuthFlows.CommonFields { +extension Shared.OAuthFlows.CommonFields { private enum CodingKeys: String, CodingKey { case refreshUrl case scopes } } -extension OpenAPI.OAuthFlows.Implicit { +extension Shared.OAuthFlows.Implicit { private enum CodingKeys: String, CodingKey { case authorizationUrl } } -extension OpenAPI.OAuthFlows.Password { +extension Shared.OAuthFlows.Password { private enum CodingKeys: String, CodingKey { case tokenUrl } } -extension OpenAPI.OAuthFlows.ClientCredentials { +extension Shared.OAuthFlows.ClientCredentials { private enum CodingKeys: String, CodingKey { case tokenUrl } } -extension OpenAPI.OAuthFlows.AuthorizationCode { +extension Shared.OAuthFlows.AuthorizationCode { private enum CodingKeys: String, CodingKey { case authorizationUrl case tokenUrl } } -extension OpenAPI.OAuthFlows: Encodable { +extension Shared.OAuthFlows: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -157,18 +156,18 @@ extension OpenAPI.OAuthFlows: Encodable { } } -extension OpenAPI.OAuthFlows: Decodable { +extension Shared.OAuthFlows: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - implicit = try container.decodeIfPresent(OpenAPI.OAuthFlows.Implicit.self, forKey: .implicit) - password = try container.decodeIfPresent(OpenAPI.OAuthFlows.Password.self, forKey: .password) - clientCredentials = try container.decodeIfPresent(OpenAPI.OAuthFlows.ClientCredentials.self, forKey: .clientCredentials) - authorizationCode = try container.decodeIfPresent(OpenAPI.OAuthFlows.AuthorizationCode.self, forKey: .authorizationCode) + implicit = try container.decodeIfPresent(Shared.OAuthFlows.Implicit.self, forKey: .implicit) + password = try container.decodeIfPresent(Shared.OAuthFlows.Password.self, forKey: .password) + clientCredentials = try container.decodeIfPresent(Shared.OAuthFlows.ClientCredentials.self, forKey: .clientCredentials) + authorizationCode = try container.decodeIfPresent(Shared.OAuthFlows.AuthorizationCode.self, forKey: .authorizationCode) } } -extension OpenAPI.OAuthFlows.CommonFields: Encodable { +extension Shared.OAuthFlows.CommonFields: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -177,16 +176,16 @@ extension OpenAPI.OAuthFlows.CommonFields: Encodable { } } -extension OpenAPI.OAuthFlows.CommonFields: Decodable { +extension Shared.OAuthFlows.CommonFields: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) refreshUrl = try container.decodeURLAsStringIfPresent(forKey: .refreshUrl) - scopes = try container.decode(OrderedDictionary.self, forKey: .scopes) + scopes = try container.decode(OrderedDictionary.self, forKey: .scopes) } } -extension OpenAPI.OAuthFlows.Implicit: Encodable { +extension Shared.OAuthFlows.Implicit: Encodable { public func encode(to encoder: Encoder) throws { try common.encode(to: encoder) @@ -196,9 +195,9 @@ extension OpenAPI.OAuthFlows.Implicit: Encodable { } } -extension OpenAPI.OAuthFlows.Implicit: Decodable { +extension Shared.OAuthFlows.Implicit: Decodable { public init(from decoder: Decoder) throws { - common = try OpenAPI.OAuthFlows.CommonFields(from: decoder) + common = try Shared.OAuthFlows.CommonFields(from: decoder) let container = try decoder.container(keyedBy: CodingKeys.self) @@ -206,7 +205,7 @@ extension OpenAPI.OAuthFlows.Implicit: Decodable { } } -extension OpenAPI.OAuthFlows.Password: Encodable { +extension Shared.OAuthFlows.Password: Encodable { public func encode(to encoder: Encoder) throws { try common.encode(to: encoder) @@ -216,9 +215,9 @@ extension OpenAPI.OAuthFlows.Password: Encodable { } } -extension OpenAPI.OAuthFlows.Password: Decodable { +extension Shared.OAuthFlows.Password: Decodable { public init(from decoder: Decoder) throws { - common = try OpenAPI.OAuthFlows.CommonFields(from: decoder) + common = try Shared.OAuthFlows.CommonFields(from: decoder) let container = try decoder.container(keyedBy: CodingKeys.self) @@ -226,7 +225,7 @@ extension OpenAPI.OAuthFlows.Password: Decodable { } } -extension OpenAPI.OAuthFlows.ClientCredentials: Encodable { +extension Shared.OAuthFlows.ClientCredentials: Encodable { public func encode(to encoder: Encoder) throws { try common.encode(to: encoder) @@ -236,9 +235,9 @@ extension OpenAPI.OAuthFlows.ClientCredentials: Encodable { } } -extension OpenAPI.OAuthFlows.ClientCredentials: Decodable { +extension Shared.OAuthFlows.ClientCredentials: Decodable { public init(from decoder: Decoder) throws { - common = try OpenAPI.OAuthFlows.CommonFields(from: decoder) + common = try Shared.OAuthFlows.CommonFields(from: decoder) let container = try decoder.container(keyedBy: CodingKeys.self) @@ -246,7 +245,7 @@ extension OpenAPI.OAuthFlows.ClientCredentials: Decodable { } } -extension OpenAPI.OAuthFlows.AuthorizationCode: Encodable { +extension Shared.OAuthFlows.AuthorizationCode: Encodable { public func encode(to encoder: Encoder) throws { try common.encode(to: encoder) @@ -257,9 +256,9 @@ extension OpenAPI.OAuthFlows.AuthorizationCode: Encodable { } } -extension OpenAPI.OAuthFlows.AuthorizationCode: Decodable { +extension Shared.OAuthFlows.AuthorizationCode: Decodable { public init(from decoder: Decoder) throws { - common = try OpenAPI.OAuthFlows.CommonFields(from: decoder) + common = try Shared.OAuthFlows.CommonFields(from: decoder) let container = try decoder.container(keyedBy: CodingKeys.self) @@ -268,8 +267,8 @@ extension OpenAPI.OAuthFlows.AuthorizationCode: Decodable { } } -extension OpenAPI.OAuthFlows: Validatable {} -extension OpenAPI.OAuthFlows.Implicit: Validatable {} -extension OpenAPI.OAuthFlows.Password: Validatable {} -extension OpenAPI.OAuthFlows.ClientCredentials: Validatable {} -extension OpenAPI.OAuthFlows.AuthorizationCode: Validatable {} +extension Shared.OAuthFlows: Validatable {} +extension Shared.OAuthFlows.Implicit: Validatable {} +extension Shared.OAuthFlows.Password: Validatable {} +extension Shared.OAuthFlows.ClientCredentials: Validatable {} +extension Shared.OAuthFlows.AuthorizationCode: Validatable {} diff --git a/Sources/OpenAPIKitCore/Shared/ParameterContextLocation.swift b/Sources/OpenAPIKitCore/Shared/ParameterContextLocation.swift new file mode 100644 index 000000000..1a36cc8e0 --- /dev/null +++ b/Sources/OpenAPIKitCore/Shared/ParameterContextLocation.swift @@ -0,0 +1,17 @@ +// +// ParameterContextLocation.swift +// +// +// Created by Mathew Polzin on 12/24/22. +// + +extension Shared { + public enum ParameterContextLocation: String, CaseIterable, Codable { + case query + case header + case path + case cookie + } +} + +extension Shared.ParameterContextLocation: Validatable {} diff --git a/Sources/OpenAPIKitCore/Shared/ParameterSchemaContextStyle.swift b/Sources/OpenAPIKitCore/Shared/ParameterSchemaContextStyle.swift new file mode 100644 index 000000000..e28e9c5be --- /dev/null +++ b/Sources/OpenAPIKitCore/Shared/ParameterSchemaContextStyle.swift @@ -0,0 +1,18 @@ +// +// ParameterSchemaContextStyle.swift +// +// +// Created by Mathew Polzin on 12/18/22. +// + +extension Shared { + public enum ParameterSchemaContextStyle: String, CaseIterable, Codable { + case form + case simple + case matrix + case label + case spaceDelimited + case pipeDelimited + case deepObject + } +} diff --git a/Sources/OpenAPIKitCore/Shared/Path.swift b/Sources/OpenAPIKitCore/Shared/Path.swift new file mode 100644 index 000000000..f951b481f --- /dev/null +++ b/Sources/OpenAPIKitCore/Shared/Path.swift @@ -0,0 +1,37 @@ +// +// Path.swift +// +// +// Created by Mathew Polzin on 12/17/22. +// + +extension Shared { + /// OpenAPI Spec "Paths Object" path field pattern support. + /// + /// See [OpenAPI Paths Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#paths-object) + /// and [OpenAPI Patterned Fields](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#patterned-fields). + public struct Path: RawRepresentable, Equatable, Hashable { + public let components: [String] + + public init(_ components: [String]) { + self.components = components + } + + public init(rawValue: String) { + let pathComponents = rawValue.split(separator: "/").map(String.init) + components = pathComponents.count > 0 && pathComponents[0].isEmpty + ? Array(pathComponents.dropFirst()) + : pathComponents + } + + public var rawValue: String { + return "/\(components.joined(separator: "/"))" + } + } +} + +extension Shared.Path: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + self.init(rawValue: value) + } +} diff --git a/Sources/OpenAPIKitCore/Shared/ResponseStatusCode.swift b/Sources/OpenAPIKitCore/Shared/ResponseStatusCode.swift new file mode 100644 index 000000000..9c69aeb9e --- /dev/null +++ b/Sources/OpenAPIKitCore/Shared/ResponseStatusCode.swift @@ -0,0 +1,108 @@ +// +// ResponseStatusCode.swift +// +// +// Created by Mathew Polzin on 12/19/22. +// + +extension Shared { + /// An HTTP Status code or status code range. + /// + /// OpenAPI supports one of the following as a key in the [Responses Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#responses-object): + /// - A `default` entry. + /// - A specific status code. + /// - A status code range. + /// + /// The `.default` case is used for a default entry. + /// + /// You can use integer literals to specify an exact status code. + /// + /// Status code ranges are named in the `StatusCode.Range` enum. For example, the "1XX" range (100-199) can be written as either `.range(.information)` or as `.range(.init(rawValue: "1XX"))`. + public struct ResponseStatusCode: RawRepresentable, Equatable, Hashable, HasWarnings { + public typealias RawValue = String + + public let warnings: [Warning] + + public var value: Code + + internal init(value: Code) { + self.value = value + warnings = [] + } + + public static let `default`: Self = .init(value: .default) + public static func range(_ range: Range) -> Self { .init(value: .range(range)) } + public static func status(code: Int) -> Self { .init(value: .status(code: code)) } + + public enum Code: Equatable, Hashable { + case `default` + case range(Range) + case status(code: Int) + } + + public enum Range: String { + /// Status Code `100-199` + case information = "1XX" + /// Status Code `200-299` + case success = "2XX" + /// Status Code `300-399` + case redirect = "3XX" + /// Status Code `400-499` + case clientError = "4XX" + /// Status Code `500-599` + case serverError = "5XX" + } + + public var rawValue: String { + switch value { + case .default: + return "default" + + case .range(let range): + return range.rawValue + + case .status(code: let code): + return String(code) + } + } + + public var isSuccess: Bool { + switch value { + case .range(.success), .status(code: 200..<300): + return true + case .range, .status, .default: + return false + } + } + + public init?(rawValue: String) { + if let val = Int(rawValue) { + value = .status(code: val) + warnings = [] + } else if rawValue == "default" { + value = .default + warnings = [] + } else if let range = Range(rawValue: rawValue.uppercased()) { + value = .range(range) + warnings = [] + } else if rawValue.contains("/"), + let first = (rawValue.split(separator: "/")).first, + let fallback = Self(rawValue: String(first)) { + value = fallback.value + warnings = [ + .message("Found non-compliant Status Code '\(rawValue)' but was able to parse as \(first)") + ] + } else { + return nil + } + } + } +} + +extension Shared.ResponseStatusCode: ExpressibleByIntegerLiteral { + + public init(integerLiteral value: Int) { + self.value = .status(code: value) + warnings = [] + } +} diff --git a/Sources/OpenAPIKitCore/Shared/SecurityScheme.swift b/Sources/OpenAPIKitCore/Shared/SecurityScheme.swift new file mode 100644 index 000000000..4c24567b5 --- /dev/null +++ b/Sources/OpenAPIKitCore/Shared/SecurityScheme.swift @@ -0,0 +1,16 @@ +// +// SecurityScheme.swift +// +// +// Created by Mathew Polzin on 12/18/22. +// + +extension Shared { + public enum SecuritySchemeLocation: String, Codable, Equatable { + case query + case header + case cookie + } +} + +extension Shared.SecuritySchemeLocation: Validatable {} diff --git a/Sources/OpenAPIKitCore/Utility/Container+DecodeURLAsString.swift b/Sources/OpenAPIKitCore/Utility/Container+DecodeURLAsString.swift new file mode 100644 index 000000000..787a05f60 --- /dev/null +++ b/Sources/OpenAPIKitCore/Utility/Container+DecodeURLAsString.swift @@ -0,0 +1,37 @@ +// +// Container+DecodeURLAsString.swift +// +// +// Created by Mathew Polzin on 7/5/20. +// + +import Foundation + +extension KeyedDecodingContainerProtocol { + internal func decodeURLAsString(forKey key: Self.Key) throws -> URL { + let string = try decode(String.self, forKey: key) + guard let url = URL(string: string) else { + throw InconsistencyError( + subjectName: key.stringValue, + details: "If specified, must be a valid URL", + codingPath: codingPath + ) + } + return url + } + + internal func decodeURLAsStringIfPresent(forKey key: Self.Key) throws -> URL? { + guard let string = try decodeIfPresent(String.self, forKey: key) else { + return nil + } + + guard let url = URL(string: string) else { + throw InconsistencyError( + subjectName: key.stringValue, + details: "If specified, must be a valid URL", + codingPath: codingPath + ) + } + return url + } +} diff --git a/Tests/OpenAPIKit30Tests/Schema Object/SchemaFragmentCombiningTests.swift b/Tests/OpenAPIKit30Tests/Schema Object/SchemaFragmentCombiningTests.swift index d8f2a8366..264f9c2a2 100644 --- a/Tests/OpenAPIKit30Tests/Schema Object/SchemaFragmentCombiningTests.swift +++ b/Tests/OpenAPIKit30Tests/Schema Object/SchemaFragmentCombiningTests.swift @@ -1244,7 +1244,7 @@ extension JSONSchema.CoreContext { format: NewFormat(rawValue: format.rawValue)!, required: required, nullable: nullable, - permissions: JSONSchema.CoreContext.Permissions(permissions), + permissions: permissions, deprecated: deprecated, title: title, description: description, diff --git a/Tests/OpenAPIKitCompatTests/DocumentConversionTests.swift b/Tests/OpenAPIKitCompatTests/DocumentConversionTests.swift new file mode 100644 index 000000000..76f67ddf9 --- /dev/null +++ b/Tests/OpenAPIKitCompatTests/DocumentConversionTests.swift @@ -0,0 +1,537 @@ +// +// DocumentConversionTests.swift +// +// +// Created by Mathew Polzin on 12/19/22. +// + +import OpenAPIKit30 +import OpenAPIKit +import OpenAPIKitCompat +import XCTest + +final class DocumentConversionTests: XCTestCase { + func test_barebonesDocument() throws { + let oldDoc = OpenAPIKit30.OpenAPI.Document( + openAPIVersion: .v3_0_3, + info: .init(title: "Hello World", version: "1.0.1"), + servers: [], + paths: [:], + components: .noComponents + ) + + let newDoc = oldDoc.convert(to: .v3_1_0) + + try assertEqualOldToNew(newDoc, oldDoc) + XCTAssertEqual(newDoc.openAPIVersion, .v3_1_0) + } + + func test_vendorExtensionsOnDoc() throws { + let oldDoc = OpenAPIKit30.OpenAPI.Document( + openAPIVersion: .v3_0_3, + info: .init(title: "Hello World", version: "1.0.1"), + servers: [], + paths: [:], + components: .noComponents, + vendorExtensions: ["x-doc": "document"] + ) + + let newDoc = oldDoc.convert(to: .v3_1_0) + + try assertEqualOldToNew(newDoc, oldDoc) + } + + func test_fullInfo() throws { + let oldDoc = OpenAPIKit30.OpenAPI.Document( + info: .init( + title: "Hello World", + description: "described", + termsOfService: URL(string: "https://website.com"), + contact: .init(name: "Me", url: URL(string: "https://website.com"), email: "me@website.com", vendorExtensions: ["x-test": 1]), + license: .init(name: "MIT-not", url: URL(string: "https://website.com"), vendorExtensions: ["x-two": 2.0]), + version: "1.0.1", + vendorExtensions: ["x-good" : "yeah"] + ), + servers: [], + paths: [:], + components: .noComponents + ) + + let newDoc = oldDoc.convert(to: .v3_1_0) + + try assertEqualOldToNew(newDoc, oldDoc) + } + + func test_servers() throws { + let oldDoc = OpenAPIKit30.OpenAPI.Document( + info: .init(title: "Hello", version: "1.0.0"), + servers: [ + .init( + url: URL(string: "https://website.com")!, + description: "ok", + variables: ["x-ok": .init(default: "1.0")], + vendorExtensions: ["x-cool": 1.5] + ), + .init( + urlTemplate: .init(url: URL(string: "https://website.com")!), + description: "hi", + variables: ["hello": .init(enum: ["1"], default: "1", description: "described", vendorExtensions: ["x-hi": "hello"])], + vendorExtensions: ["x-test": 2] + ) + ], + paths: [:], + components: .noComponents + ) + + let newDoc = oldDoc.convert(to: .v3_1_0) + + try assertEqualOldToNew(newDoc, oldDoc) + } + + func test_paths() throws { + let params: OpenAPIKit30.OpenAPI.Parameter.Array = [ + .a(.external(URL(string: "https://welcome.com")!)), + .a(.component(named: "test")), + .parameter(.init(name: "test", context: .query, schema: .string)) + ] + + let externalDocs = OpenAPIKit30.OpenAPI.ExternalDocumentation( + description: "hello", + url: URL(string: "https://website.com")!, + vendorExtensions: ["x-hi": 3] + ) + + let request = OpenAPIKit30.OpenAPI.Request( + description: "describble", + content: [ + .json: .init(schema: .string, example: "{\"hi\": 1}", encoding: ["utf8": .init()]) + ], + required: true, + vendorExtensions: ["x-tend": "ed"] + ) + + let response = OpenAPIKit30.OpenAPI.Response( + description: "hello", + headers: ["Content-Type": .header(.init(schema: .string))], + content: [.json: .init(schema: .string)], + links: ["link1": .link(operationId: "link1")] + ) + + let callbacks: OpenAPIKit30.OpenAPI.Callbacks = [ + .init(url: URL(string: "https://website.com")!): .init(summary: "hello") + ] + + let server = OpenAPIKit30.OpenAPI.Server( + url: URL(string: "https://website.com")!, + description: "ok", + variables: ["x-ok": .init(default: "1.0")], + vendorExtensions: ["x-cool": 1.5] + ) + + let securityRequirement: OpenAPIKit30.OpenAPI.SecurityRequirement = [ + .component(named: "security"): ["hello"] + ] + + let operation = OpenAPIKit30.OpenAPI.Operation( + tags: ["hello"], + summary: "sum", + description: "described", + externalDocs: externalDocs, + operationId: "ident", + parameters: params, + requestBody: .request(request), + responses: [200: .b(response)], + callbacks: ["callback": .b(callbacks)], + deprecated: true, + security: [securityRequirement], + servers: [server], + vendorExtensions: ["x-hello": 101] + ) + + let oldDoc = OpenAPIKit30.OpenAPI.Document( + info: .init(title: "Hello", version: "1.0.0"), + servers: [], + paths: [ + "/hello/world": .init( + summary: "sum", + description: "described", + servers: [ + .init( + url: URL(string: "https://website.com")!, + description: "ok", + variables: ["x-ok": .init(default: "1.0")], + vendorExtensions: ["x-cool": 1.5] + ) + ], + parameters: [ + .a(.external(URL(string: "https://welcome.com")!)), + .a(.internal(.component(name: "test"))), + .parameter(.init(name: "test", context: .query, schema: .string)) + ], + get: operation, + put: operation, + post: operation, + delete: operation, + options: operation, + head: operation, + patch: operation, + trace: operation, + vendorExtensions: ["x-test": 123] + ) + ], + components: .noComponents + ) + + let newDoc = oldDoc.convert(to: .v3_1_0) + + try assertEqualOldToNew(newDoc, oldDoc) + } + + // TODO: more tests +} + +fileprivate func assertEqualOldToNew(_ newDoc: OpenAPIKit.OpenAPI.Document, _ oldDoc: OpenAPIKit30.OpenAPI.Document) throws { + // INFO + XCTAssertEqual(newDoc.info.title, oldDoc.info.title) + XCTAssertEqual(newDoc.info.version, oldDoc.info.version) + XCTAssertEqual(newDoc.info.vendorExtensions, oldDoc.info.vendorExtensions) + XCTAssertEqual(newDoc.info.description, oldDoc.info.description) + XCTAssertEqual(newDoc.info.contact?.name, oldDoc.info.contact?.name) + XCTAssertEqual(newDoc.info.contact?.url, oldDoc.info.contact?.url) + XCTAssertEqual(newDoc.info.contact?.email, oldDoc.info.contact?.email) + XCTAssertEqual(newDoc.info.contact?.vendorExtensions, oldDoc.info.contact?.vendorExtensions) + XCTAssertEqual(newDoc.info.termsOfService, oldDoc.info.termsOfService) + XCTAssertEqual(newDoc.info.license?.name, oldDoc.info.license?.name) + XCTAssertEqual(newDoc.info.license?.identifier, oldDoc.info.license?.url.map(OpenAPIKit.OpenAPI.Document.Info.License.Identifier.url)) + XCTAssertEqual(newDoc.info.license?.vendorExtensions, oldDoc.info.license?.vendorExtensions) + XCTAssertNil(newDoc.info.summary) + + // SERVERS + XCTAssertEqual(newDoc.servers.count, oldDoc.servers.count) + for (newServer, oldServer) in zip(newDoc.servers, oldDoc.servers) { + assertEqualOldToNew(newServer, oldServer) + } + + // PATHS + XCTAssertEqual(newDoc.paths.count, oldDoc.paths.count) + for (path, newPathItem) in newDoc.paths { + let oldPathItem = try XCTUnwrap(oldDoc.paths[path]) + try assertEqualOldToNew(newPathItem, oldPathItem) + } + + // COMPONENTS + + // SECURITY + + // TAGS + + // EXTERNAL DOCS + + // VENDOR EXTENSIONS + XCTAssertEqual(newDoc.vendorExtensions, oldDoc.vendorExtensions) + + // TODO: test the rest of equality. +} + +fileprivate func assertEqualOldToNew(_ newServer: OpenAPIKit.OpenAPI.Server, _ oldServer: OpenAPIKit30.OpenAPI.Server) { + XCTAssertEqual(newServer.urlTemplate, oldServer.urlTemplate) + XCTAssertEqual(newServer.description, oldServer.description) + XCTAssertEqual(newServer.vendorExtensions, oldServer.vendorExtensions) + for (key, newVariable) in newServer.variables { + let oldVariable = oldServer.variables[key] + XCTAssertEqual(newVariable.description, oldVariable?.description) + XCTAssertEqual(newVariable.`enum`, oldVariable?.`enum`) + XCTAssertEqual(newVariable.`default`, oldVariable?.`default`) + XCTAssertEqual(newVariable.vendorExtensions, oldVariable?.vendorExtensions) + } +} + +fileprivate func assertEqualOldToNew(_ newParamArray: OpenAPIKit.OpenAPI.Parameter.Array, _ oldParamArray: OpenAPIKit30.OpenAPI.Parameter.Array) { + for (newParameter, oldParameter) in zip(newParamArray, oldParamArray) { + switch (newParameter, oldParameter) { + case (.a(let ref), .a(let ref2)): + XCTAssertNil(ref.summary) + XCTAssertNil(ref.description) + XCTAssertEqual(ref.jsonReference.absoluteString, ref2.absoluteString) + case (.b(let param), .b(let param2)): + XCTAssertEqual(param.name, param2.name) + assertEqualOldToNew(param.context, param2.context) + XCTAssertEqual(param.description, param2.description) + XCTAssertEqual(param.deprecated, param2.deprecated) + XCTAssertEqual(param.vendorExtensions, param2.vendorExtensions) + XCTAssertEqual(param.required, param2.required) + default: + XCTFail("Parameters are not equal because one is a reference and the other is not: \(newParameter) / \(oldParameter)") + } + } +} + +fileprivate func assertEqualOldToNew(_ newPathItem: OpenAPIKit.OpenAPI.PathItem, _ oldPathItem: OpenAPIKit30.OpenAPI.PathItem) throws { + XCTAssertEqual(newPathItem.summary, oldPathItem.summary) + XCTAssertEqual(newPathItem.description, oldPathItem.description) + if let newServers = newPathItem.servers { + let oldServers = try XCTUnwrap(oldPathItem.servers) + for (newServer, oldServer) in zip(newServers, oldServers) { + assertEqualOldToNew(newServer, oldServer) + } + } + assertEqualOldToNew(newPathItem.parameters, oldPathItem.parameters) + try assertEqualOldToNew(newPathItem.get, oldPathItem.get) + try assertEqualOldToNew(newPathItem.put, oldPathItem.put) + try assertEqualOldToNew(newPathItem.post, oldPathItem.post) + try assertEqualOldToNew(newPathItem.delete, oldPathItem.delete) + try assertEqualOldToNew(newPathItem.options, oldPathItem.options) + try assertEqualOldToNew(newPathItem.head, oldPathItem.head) + try assertEqualOldToNew(newPathItem.patch, oldPathItem.patch) + try assertEqualOldToNew(newPathItem.trace, oldPathItem.trace) + + XCTAssertEqual(newPathItem.vendorExtensions, oldPathItem.vendorExtensions) +} + +fileprivate func assertEqualOldToNew(_ newParamContext: OpenAPIKit.OpenAPI.Parameter.Context, _ oldParamContext: OpenAPIKit30.OpenAPI.Parameter.Context) { + switch (newParamContext, oldParamContext) { + case (.query(required: let req, allowEmptyValue: let empty), .query(required: let req2, allowEmptyValue: let empty2)): + XCTAssertEqual(req, req2) + XCTAssertEqual(empty, empty2) + case (.header(required: let req), .header(required: let req2)): + XCTAssertEqual(req, req2) + case (.path, .path): + break + case (.cookie(required: let req), .cookie(required: let req2)): + XCTAssertEqual(req, req2) + default: + XCTFail("Parameter contexts are not equal. \(newParamContext) / \(oldParamContext)") + } +} + +fileprivate func assertEqualOldToNew(_ newOperation: OpenAPIKit.OpenAPI.Operation?, _ oldOperation: OpenAPIKit30.OpenAPI.Operation?) throws { + if let newOp = newOperation { + let oldOp = try XCTUnwrap(oldOperation) + + XCTAssertEqual(newOp.tags, oldOp.tags) + XCTAssertEqual(newOp.summary, oldOp.summary) + XCTAssertEqual(newOp.description, oldOp.description) + try assertEqualOldToNew(newOp.externalDocs, oldOp.externalDocs) + XCTAssertEqual(newOp.operationId, oldOp.operationId) + assertEqualOldToNew(newOp.parameters, oldOp.parameters) + if let newRequest = newOp.requestBody { + let oldRequest = try XCTUnwrap(oldOp.requestBody) + switch (newRequest, oldRequest) { + case (.a(let ref1), .a(let ref2)): + XCTAssertEqual(ref1.absoluteString, ref2.absoluteString) + case (.b(let req1), .b(let req2)): + try assertEqualOldToNew(req1, req2) + default: + XCTFail("One request was a reference and the other was not. \(newRequest) / \(oldRequest)") + } + } else { + XCTAssertNil(oldOp.requestBody) + } + assertEqualOldToNew(newOp.responses, oldOp.responses) + try assertEqualOldToNew(newOp.callbacks, oldOp.callbacks) + XCTAssertEqual(newOp.deprecated, oldOp.deprecated) + if let newSecurity = newOp.security { + let oldSecurity = try XCTUnwrap(oldOp.security) + + for (newSecurityReq, oldSecurityReq) in zip(newSecurity, oldSecurity) { + try assertEqualOldToNew(newSecurityReq, oldSecurityReq) + } + } else { + XCTAssertNil(oldOp.security) + } + if let newServers = newOp.servers { + let oldServers = try XCTUnwrap(oldOp.servers) + + for (newServer, oldServer) in zip(newServers, oldServers) { + assertEqualOldToNew(newServer, oldServer) + } + } else { + XCTAssertNil(oldOp.servers) + } + XCTAssertEqual(newOp.vendorExtensions, oldOp.vendorExtensions) + } else { + XCTAssertNil(oldOperation) + } +} + +fileprivate func assertEqualOldToNew(_ newSecurityReq: OpenAPIKit.OpenAPI.SecurityRequirement, _ oldSecurityReq: OpenAPIKit30.OpenAPI.SecurityRequirement) throws { + for (ref, strs) in newSecurityReq { + switch ref { + case .internal(let internalRef): + let maybeOldRefInternal = OpenAPIKit30.JSONReference.InternalReference(rawValue: internalRef.rawValue) + let oldRefInternal = try XCTUnwrap(maybeOldRefInternal) + let oldRef = OpenAPIKit30.JSONReference.internal(oldRefInternal) + let oldStrs = oldSecurityReq[oldRef] + XCTAssertEqual(strs, oldStrs) + case .external(let external): + let oldStrs = oldSecurityReq[.external(external)] + XCTAssertEqual(strs, oldStrs) + } + } +} + +fileprivate func assertEqualOldToNew(_ newRequest: OpenAPIKit.OpenAPI.Request, _ oldRequest: OpenAPIKit30.OpenAPI.Request) throws { + XCTAssertEqual(newRequest.description, oldRequest.description) + try assertEqualOldToNew(newRequest.content, oldRequest.content) + XCTAssertEqual(newRequest.required, oldRequest.required) + XCTAssertEqual(newRequest.vendorExtensions, oldRequest.vendorExtensions) +} + +fileprivate func assertEqualOldToNew(_ newContentMap: OpenAPIKit.OpenAPI.Content.Map, _ oldContentMap: OpenAPIKit30.OpenAPI.Content.Map) throws { + for ((newCt, newContent), (oldCt, oldContent)) in zip(newContentMap, oldContentMap) { + XCTAssertEqual(newCt, oldCt) + switch (newContent.schema, oldContent.schema) { + case (.a(let ref1), .a(let ref2)): + XCTAssertEqual(ref1.absoluteString, ref2.absoluteString) + case (.b(let schema1), .b(let schema2)): + assertEqualOldToNew(schema1, schema2) + default: + XCTFail("Found one reference and one schema. \(String(describing: newContent.schema)) / \(String(describing: oldContent.schema))") + } + XCTAssertEqual(newContent.example, oldContent.example) + if let newContentExamplesRef = newContent.examples { + let oldContentExamplesRef = try XCTUnwrap(oldContent.examples) + for ((newKey, newExampleRef), (oldKey, oldExampleRef)) in zip(newContentExamplesRef, oldContentExamplesRef) { + XCTAssertEqual(newKey, oldKey) + switch (newExampleRef, oldExampleRef) { + case (.a(let ref1), .a(let ref2)): + XCTAssertEqual(ref1.absoluteString, ref2.absoluteString) + case (.b(let example1), .b(let example2)): + assertEqualOldToNew(example1, example2) + default: + XCTFail("Found one reference and one example. \(newExampleRef) / \(oldExampleRef)") + } + } + } else { + XCTAssertNil(oldContent.examples) + } + if let newEncodingRef = newContent.encoding { + let oldEncodingRef = try XCTUnwrap(oldContent.encoding) + for ((newKey, newEncoding), (oldKey, oldEncoding)) in zip(newEncodingRef, oldEncodingRef) { + XCTAssertEqual(newKey, oldKey) + try assertEqualOldToNew(newEncoding, oldEncoding) + } + } else { + XCTAssertNil(oldContent.encoding) + } + XCTAssertEqual(newContent.vendorExtensions, oldContent.vendorExtensions) + } +} + +fileprivate func assertEqualOldToNew(_ newSchema: OpenAPIKit.JSONSchema, _ oldSchema: OpenAPIKit30.JSONSchema) { + // TODO +} + +fileprivate func assertEqualOldToNew(_ newExample: OpenAPIKit.OpenAPI.Example, _ oldExample: OpenAPIKit30.OpenAPI.Example) { + XCTAssertEqual(newExample.summary, oldExample.summary) + XCTAssertEqual(newExample.description, oldExample.description) + XCTAssertEqual(newExample.value, oldExample.value) + XCTAssertEqual(newExample.vendorExtensions, oldExample.vendorExtensions) +} + +fileprivate func assertEqualOldToNew(_ newEncoding: OpenAPIKit.OpenAPI.Content.Encoding, _ oldEncoding: OpenAPIKit30.OpenAPI.Content.Encoding) throws { + XCTAssertEqual(newEncoding.contentType, oldEncoding.contentType) + if let newEncodingHeaders = newEncoding.headers { + let oldEncodingHeaders = try XCTUnwrap(oldEncoding.headers) + for ((newKey, newHeader), (oldKey, oldHeader)) in zip(newEncodingHeaders, oldEncodingHeaders) { + XCTAssertEqual(newKey, oldKey) + switch (newHeader, oldHeader) { + case (.a(let ref1), .a(let ref2)): + XCTAssertEqual(ref1.absoluteString, ref2.absoluteString) + case (.b(let header1), .b(let header2)): + try assertEqualOldToNew(header1, header2) + default: + XCTFail("Found one reference and one header. \(newHeader) / \(oldHeader)") + } + } + } else { + XCTAssertNil(oldEncoding.headers) + } + XCTAssertEqual(newEncoding.style, oldEncoding.style) + XCTAssertEqual(newEncoding.explode, oldEncoding.explode) + XCTAssertEqual(newEncoding.allowReserved, oldEncoding.allowReserved) +} + +fileprivate func assertEqualOldToNew(_ newHeader: OpenAPIKit.OpenAPI.Header, _ oldHeader: OpenAPIKit30.OpenAPI.Header) throws { + XCTAssertEqual(newHeader.description, oldHeader.description) + XCTAssertEqual(newHeader.required, oldHeader.required) + XCTAssertEqual(newHeader.deprecated, oldHeader.deprecated) + switch (newHeader.schemaOrContent, oldHeader.schemaOrContent) { + case (.a(let schema1), .a(let schema2)): + try assertEqualOldToNew(schema1, schema2) + case (.b(let content1), .b(let content2)): + try assertEqualOldToNew(content1, content2) + default: + XCTFail("Found one schema and one content map. \(newHeader.schemaOrContent) / \(oldHeader.schemaOrContent)") + } + XCTAssertEqual(newHeader.vendorExtensions, oldHeader.vendorExtensions) +} + +fileprivate func assertEqualOldToNew(_ newSchemaContext: OpenAPIKit.OpenAPI.Parameter.SchemaContext, _ oldSchemaContext: OpenAPIKit30.OpenAPI.Parameter.SchemaContext) throws { + XCTAssertEqual(newSchemaContext.style, oldSchemaContext.style) + XCTAssertEqual(newSchemaContext.explode, oldSchemaContext.explode) + XCTAssertEqual(newSchemaContext.allowReserved, oldSchemaContext.allowReserved) + switch (newSchemaContext.schema, oldSchemaContext.schema) { + case (.a(let ref1), .a(let ref2)): + XCTAssertEqual(ref1.absoluteString, ref2.absoluteString) + case (.b(let schema1), .b(let schema2)): + assertEqualOldToNew(schema1, schema2) + default: + XCTFail("Found one reference and one schema. \(newSchemaContext.schema) / \(oldSchemaContext.schema)") + } + XCTAssertEqual(newSchemaContext.example, oldSchemaContext.example) + if let newExamplesRef = newSchemaContext.examples { + let oldExamplesRef = try XCTUnwrap(oldSchemaContext.examples) + for ((newKey, newExampleRef), (oldKey, oldExampleRef)) in zip(newExamplesRef, oldExamplesRef) { + XCTAssertEqual(newKey, oldKey) + switch (newExampleRef, oldExampleRef) { + case (.a(let ref1), .a(let ref2)): + XCTAssertEqual(ref1.absoluteString, ref2.absoluteString) + case (.b(let example1), .b(let example2)): + assertEqualOldToNew(example1, example2) + default: + XCTFail("Found one reference and one example. \(newExampleRef) / \(oldExampleRef)") + } + } + } else { + XCTAssertNil(oldSchemaContext.examples) + } +} + +fileprivate func assertEqualOldToNew(_ newResponseMap: OpenAPIKit.OpenAPI.Response.Map, _ oldResponseMap: OpenAPIKit30.OpenAPI.Response.Map) { + // TODO +} + +fileprivate func assertEqualOldToNew(_ newCallbacksMap: OpenAPIKit.OpenAPI.CallbacksMap, _ oldCallbacksMap: OpenAPIKit30.OpenAPI.CallbacksMap) throws { + XCTAssertEqual(newCallbacksMap.count, oldCallbacksMap.count) + for (key, ref) in newCallbacksMap { + let oldRef = try XCTUnwrap(oldCallbacksMap[key]) + switch (ref, oldRef) { + case (.a(let ref1), .a(let ref2)): + XCTAssertEqual(ref1.absoluteString, ref2.absoluteString) + case (.b(let callback1), .b(let callback2)): + for (url, pathItemRef) in callback1 { + let pathItem2 = try XCTUnwrap(callback2[url]) + switch pathItemRef { + case .a(_): + XCTFail("Found a reference in OpenAPI 3.1 document where OpenAPI 3.0 does not support references!") + case .b(let pathItem): + try assertEqualOldToNew(pathItem, pathItem2) + } + } + default: + XCTFail("Found reference to a callbacks object in one document and actual callbacks object in the other. \(ref) / \(oldRef)") + } + } +} + +fileprivate func assertEqualOldToNew(_ newExternalDocs: OpenAPIKit.OpenAPI.ExternalDocumentation?, _ oldExternalDocs: OpenAPIKit30.OpenAPI.ExternalDocumentation?) throws { + if let newDocs = newExternalDocs { + let oldDocs = try XCTUnwrap(oldExternalDocs) + XCTAssertEqual(newDocs.description, oldDocs.description) + XCTAssertEqual(newDocs.url, oldDocs.url) + XCTAssertEqual(newDocs.vendorExtensions, oldDocs.vendorExtensions) + } else { + XCTAssertNil(oldExternalDocs) + } +} diff --git a/Tests/OpenAPIKitCoreTests/ContentTypeTests.swift b/Tests/OpenAPIKitCoreTests/ContentTypeTests.swift index 515c863ab..12f5b900e 100644 --- a/Tests/OpenAPIKitCoreTests/ContentTypeTests.swift +++ b/Tests/OpenAPIKitCoreTests/ContentTypeTests.swift @@ -10,7 +10,7 @@ import XCTest final class ContentTypeTests: XCTestCase { func test_badContentType() { - let bad = OpenAPI.ContentType(rawValue: "not a content type") + let bad = Shared.ContentType(rawValue: "not a content type") XCTAssertEqual( bad?.warnings[0].localizedDescription, "\'not a content type\' could not be parsed as a Content Type. Content Types should have the format \'/\'" @@ -18,7 +18,7 @@ final class ContentTypeTests: XCTestCase { } func test_contentTypeStringReflexivity() { - let types: [OpenAPI.ContentType] = [ + let types: [Shared.ContentType] = [ .bmp, .css, .csv, @@ -57,24 +57,24 @@ final class ContentTypeTests: XCTestCase { ] for type in types { - XCTAssertEqual(type, OpenAPI.ContentType(rawValue: type.rawValue)) + XCTAssertEqual(type, Shared.ContentType(rawValue: type.rawValue)) } } func test_goodParam() { - let type = OpenAPI.ContentType.init(rawValue: "text/html; charset=utf8") + let type = Shared.ContentType.init(rawValue: "text/html; charset=utf8") XCTAssertEqual(type?.warnings.count, 0) XCTAssertEqual(type?.rawValue, "text/html; charset=utf8") } func test_multipleParams() { - let type = OpenAPI.ContentType.init(rawValue: "my/type; some=thing; another=else") + let type = Shared.ContentType.init(rawValue: "my/type; some=thing; another=else") XCTAssertEqual(type?.warnings.count, 0) XCTAssert(type?.rawValue == "my/type; another=else; some=thing") } func test_oneBadParam() { - let type = OpenAPI.ContentType.init(rawValue: "my/type; some: thing; another=else") + let type = Shared.ContentType.init(rawValue: "my/type; some: thing; another=else") XCTAssertEqual(type?.warnings.count, 1) XCTAssertEqual(type?.warnings.first?.localizedDescription, "Could not parse a Content Type parameter from \' some: thing\'") XCTAssert(type?.rawValue == "my/type; another=else") diff --git a/Tests/OpenAPIKitTests/Schema Object/SchemaFragmentCombiningTests.swift b/Tests/OpenAPIKitTests/Schema Object/SchemaFragmentCombiningTests.swift index 1adc1ad8d..47cd1af52 100644 --- a/Tests/OpenAPIKitTests/Schema Object/SchemaFragmentCombiningTests.swift +++ b/Tests/OpenAPIKitTests/Schema Object/SchemaFragmentCombiningTests.swift @@ -1313,7 +1313,7 @@ extension JSONSchema.CoreContext { format: NewFormat(rawValue: format.rawValue)!, required: required, nullable: nullable, - permissions: JSONSchema.CoreContext.Permissions(permissions), + permissions: permissions, deprecated: deprecated, title: title, description: description,