diff --git a/LICENSE.txt b/LICENSE.txt index 9219339db..971080900 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright 2018 Mathew Polzin +Copyright 2020 Mathew Polzin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/Package.swift b/Package.swift index 71f4566f2..73ee5525a 100644 --- a/Package.swift +++ b/Package.swift @@ -1,5 +1,4 @@ // swift-tools-version:5.1 -// The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription diff --git a/README.md b/README.md index ae6b386db..fe4c29f38 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ A library containing Swift types that encode to- and decode from [OpenAPI](https - [Components Object (`OpenAPI.Components`)](#components-object-openapicomponents) - [Paths Object (`OpenAPI.PathItem.Map`)](#paths-object-openapipathitemmap) - [Path Item Object (`OpenAPI.PathItem`)](#path-item-object-openapipathitem) - - [Operation Object (`OpenAPI.PathItem.Operation`)](#operation-object-openapipathitemoperation) + - [Operation Object (`OpenAPI.Operation`)](#operation-object-openapipathitemoperation) - [External Document Object (`OpenAPI.ExternalDoc`)](#external-document-object-openapiexternaldoc) - - [Parameter Object (`OpenAPI.PathItem.Parameter`)](#parameter-object-openapipathitemparameter) + - [Parameter Object (`OpenAPI.Parameter`)](#parameter-object-openapipathitemparameter) - [Request Body Object (`OpenAPI.Request`)](#request-body-object-openapirequest) - [Media Type Object (`OpenAPI.Content`)](#media-type-object-openapicontent) - [Encoding Object (`OpenAPI.Content.Encoding`)](#encoding-object-openapicontentencoding) @@ -101,10 +101,10 @@ The types used by this library largely mirror the object definitions found in th At the root there is an `OpenAPI.Document`. In addition to some information that applies to the entire API, the document contains `OpenAPI.Components` (essentially a dictionary of reusable components that can be referenced with `JSONReferences`) and an `OpenAPI.PathItem.Map` (a dictionary of routes your API defines). #### Routes -Each route is an entry in the document's `OpenAPI.PathItem.Map`. The keys of this dictionary are the paths for each route (i.e. `/widgets`). The values of this dictionary are `OpenAPI.PathItems` which define any combination of endpoints (i.e. `GET`, `POST`, `PATCH`, etc.) that the given route supports. +Each route is an entry in the document's `OpenAPI.PathItem.Map`. The keys of this dictionary are the paths for each route (i.e. `/widgets`). The values of this dictionary are `OpenAPI.PathItems` which define any combination of endpoints (i.e. `GET`, `POST`, `PATCH`, etc.) that the given route supports. In addition to accessing endpoints on a path item under the name of the method (`.get`, `.post`, etc.), you can get an array of pairs matching endpoint methods to operations with the `.endpoints` method on `PathItem`. #### Endpoints -Each endpoint on a route is defined by an `OpenAPI.PathItem.Operation`. Among other things, this operation can specify the parameters (path, query, header, etc.), request body, and response bodies/codes supported by the given endpoint. +Each endpoint on a route is defined by an `OpenAPI.Operation`. Among other things, this operation can specify the parameters (path, query, header, etc.), request body, and response bodies/codes supported by the given endpoint. #### Request/Response Bodies Request and response bodies can be defined in great detail using OpenAPI's derivative of the JSON Schema specification. This library uses the `JSONSchema` type for such schema definitions. @@ -183,7 +183,7 @@ For example, let apiDoc: OpenAPI.Document = ... let addBooksPath = apiDoc.paths["/cloudloading/addBook"] -let addBooksParameters: [OpenAPI.PathItem.Parameter]? = addBooksPath?.parameters.compactMap(apiDoc.components.dereference) +let addBooksParameters: [OpenAPI.Parameter]? = addBooksPath?.parameters.compactMap(apiDoc.components.dereference) ``` ## Notes @@ -275,7 +275,7 @@ See [**A note on dictionary ordering**](#a-note-on-dictionary-ordering) before d - [x] trace - [x] specification extensions (`vendorExtensions`) -### Operation Object (`OpenAPI.PathItem.Operation`) +### Operation Object (`OpenAPI.Operation`) - [x] tags - [x] summary - [x] description @@ -295,7 +295,7 @@ See [**A note on dictionary ordering**](#a-note-on-dictionary-ordering) before d - [x] url - [ ] specification extensions -### Parameter Object (`OpenAPI.PathItem.Parameter`) +### Parameter Object (`OpenAPI.Parameter`) - [x] name - [x] in (`context`) - [x] description @@ -387,7 +387,7 @@ See [**A note on dictionary ordering**](#a-note-on-dictionary-ordering) before d - [x] local (same file) reference (`internal` case) - [x] encode - [x] decode - - [ ] dereference + - [x] dereference - [x] remote (different file) reference (`external` case) - [x] encode - [x] decode @@ -396,7 +396,7 @@ See [**A note on dictionary ordering**](#a-note-on-dictionary-ordering) before d ### Schema Object (`JSONSchema`) - [x] Mostly complete support for JSON Schema inherited keywords - [x] nullable -- [ ] discriminator +- [x] discriminator - [x] readOnly (`permissions` `.readOnly` case) - [x] writeOnly (`permissions` `.writeOnly` case) - [ ] xml diff --git a/Sources/OpenAPIKit/Component Object/Components+JSONReference.swift b/Sources/OpenAPIKit/Component Object/Components+JSONReference.swift index 2f2257e66..ab2c1078a 100644 --- a/Sources/OpenAPIKit/Component Object/Components+JSONReference.swift +++ b/Sources/OpenAPIKit/Component Object/Components+JSONReference.swift @@ -65,6 +65,16 @@ extension OpenAPI.Components { } } + /// Pass a reference to a component. + /// `dereference()` will return the component value if it is found + /// in the Components Object. + /// + /// - Important: Dereferencing an external reference (i.e. one that points to another file) + /// is not currently supported by OpenAPIKit and will therefore always result in `nil`. + public func dereference(_ reference: JSONReference) -> ReferenceType? { + return self[reference] + } + /// Create a `JSONReference`. /// /// - throws: If the given name does not refer to an existing component of the given type. diff --git a/Sources/OpenAPIKit/Component Object/Components+Locatable.swift b/Sources/OpenAPIKit/Component Object/Components+Locatable.swift index 785efd9a2..c9314d879 100644 --- a/Sources/OpenAPIKit/Component Object/Components+Locatable.swift +++ b/Sources/OpenAPIKit/Component Object/Components+Locatable.swift @@ -28,7 +28,7 @@ extension OpenAPI.Response: ComponentDictionaryLocatable { public static var openAPIComponentsKeyPath: KeyPath> { \.responses } } -extension OpenAPI.PathItem.Parameter: ComponentDictionaryLocatable { +extension OpenAPI.Parameter: ComponentDictionaryLocatable { public static var openAPIComponentsKey: String { "parameters" } public static var openAPIComponentsKeyPath: KeyPath> { \.parameters } } diff --git a/Sources/OpenAPIKit/Component Object/Components.swift b/Sources/OpenAPIKit/Component Object/Components.swift index f9273b126..a372091af 100644 --- a/Sources/OpenAPIKit/Component Object/Components.swift +++ b/Sources/OpenAPIKit/Component Object/Components.swift @@ -18,7 +18,7 @@ extension OpenAPI { public var schemas: ComponentDictionary public var responses: ComponentDictionary - public var parameters: ComponentDictionary + public var parameters: ComponentDictionary public var examples: ComponentDictionary public var requestBodies: ComponentDictionary public var headers: ComponentDictionary
@@ -35,7 +35,7 @@ extension OpenAPI { public init(schemas: ComponentDictionary = [:], responses: ComponentDictionary = [:], - parameters: ComponentDictionary = [:], + parameters: ComponentDictionary = [:], examples: ComponentDictionary = [:], requestBodies: ComponentDictionary = [:], headers: ComponentDictionary
= [:], @@ -163,7 +163,7 @@ extension OpenAPI.Components: Decodable { responses = try container.decodeIfPresent(OpenAPI.ComponentDictionary.self, forKey: .responses) ?? [:] - parameters = try container.decodeIfPresent(OpenAPI.ComponentDictionary.self, forKey: .parameters) + parameters = try container.decodeIfPresent(OpenAPI.ComponentDictionary.self, forKey: .parameters) ?? [:] examples = try container.decodeIfPresent(OpenAPI.ComponentDictionary.self, forKey: .examples) diff --git a/Sources/OpenAPIKit/Content.swift b/Sources/OpenAPIKit/Content/Content.swift similarity index 100% rename from Sources/OpenAPIKit/Content.swift rename to Sources/OpenAPIKit/Content/Content.swift diff --git a/Sources/OpenAPIKit/ContentEncoding.swift b/Sources/OpenAPIKit/Content/ContentEncoding.swift similarity index 97% rename from Sources/OpenAPIKit/ContentEncoding.swift rename to Sources/OpenAPIKit/Content/ContentEncoding.swift index 8a009d515..edba1df14 100644 --- a/Sources/OpenAPIKit/ContentEncoding.swift +++ b/Sources/OpenAPIKit/Content/ContentEncoding.swift @@ -10,7 +10,7 @@ extension OpenAPI.Content { /// /// See [OpenAPI Encoding Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#encoding-object). public struct Encoding: Equatable { - public typealias Style = OpenAPI.PathItem.Parameter.Schema.Style + public typealias Style = OpenAPI.Parameter.SchemaContext.Style public let contentType: OpenAPI.ContentType? public let headers: OpenAPI.Header.Map? diff --git a/Sources/OpenAPIKit/ContentType.swift b/Sources/OpenAPIKit/Content/ContentType.swift similarity index 100% rename from Sources/OpenAPIKit/ContentType.swift rename to Sources/OpenAPIKit/Content/ContentType.swift diff --git a/Sources/OpenAPIKit/Document.swift b/Sources/OpenAPIKit/Document.swift index 99106de92..c637ece40 100644 --- a/Sources/OpenAPIKit/Document.swift +++ b/Sources/OpenAPIKit/Document.swift @@ -286,12 +286,12 @@ internal func decodeSecurityRequirements(from container: internal func validateSecurityRequirements(in paths: OpenAPI.PathItem.Map, against components: OpenAPI.Components) throws { for (path, pathItem) in paths { - for (verb, operation) in pathItem.endpoints { - if let securityRequirements = operation.security { + for endpoint in pathItem.endpoints { + if let securityRequirements = endpoint.operation.security { try validate( securityRequirements: securityRequirements, at: path, - for: verb, + for: endpoint.method, against: components ) } @@ -299,7 +299,7 @@ internal func validateSecurityRequirements(in paths: OpenAPI.PathItem.Map, again } } -internal func validate(securityRequirements: [OpenAPI.SecurityRequirement], at path: OpenAPI.Path, for verb: OpenAPI.HttpVerb, against components: OpenAPI.Components) throws { +internal func validate(securityRequirements: [OpenAPI.SecurityRequirement], at path: OpenAPI.Path, for verb: OpenAPI.HttpMethod, against components: OpenAPI.Components) throws { let securitySchemes = securityRequirements.flatMap { $0.keys } for securityScheme in securitySchemes { diff --git a/Sources/OpenAPIKit/Either/Either+Convenience.swift b/Sources/OpenAPIKit/Either/Either+Convenience.swift index c552cf2b2..257ad7fbd 100644 --- a/Sources/OpenAPIKit/Either/Either+Convenience.swift +++ b/Sources/OpenAPIKit/Either/Either+Convenience.swift @@ -23,12 +23,36 @@ extension Either where A == URL { public var urlValue: A? { a } } -extension Either where A == OpenAPI.Header.Schema { - /// Retrieve the schema if that is what this property contains. - public var schemaValue: A? { a } -} - -extension Either where B == OpenAPI.PathItem.Parameter { +// This extension also covers `OpenAPI.Header.SchemaContext` +// which is a typealias of `OpenAPI.Parameter.SchemaContext`. +extension Either where A == OpenAPI.Parameter.SchemaContext { + /// Retrieve the schema context if that is what this property contains. + public var schemaContextValue: A? { a } + + /// Retrieve the schema value if this property contains a schema context. + /// + /// If the schema is a `JSONReference` this property will be `nil` + /// but the `schemaReference` property will be `non-nil`. + public var schemaValue: JSONSchema? { + guard case .a(let schemaContext) = self else { + return nil + } + return schemaContext.schema.schemaValue + } + + /// Retrieve the schema reference if this property contains a schema context. + /// + /// If the schema is a `JSONSchema` this property will be `nil` but the + /// `schemaValue` property will be `non-nil`. + public var schemaReference: JSONReference? { + guard case .a(let schemaContext) = self else { + return nil + } + return schemaContext.schema.reference + } +} + +extension Either where B == OpenAPI.Parameter { /// Retrieve the parameter if that is what this property contains. public var parameterValue: B? { b } } @@ -79,9 +103,9 @@ extension Either where A: _OpenAPIReference { public static func reference(_ reference: A) -> Self { .a(reference) } } -extension Either where A == OpenAPI.PathItem.Parameter.Schema { - /// Construct a schema value. - public static func schema(_ schema: OpenAPI.PathItem.Parameter.Schema) -> Self { .a(schema) } +extension Either where A == OpenAPI.Parameter.SchemaContext { + /// Construct a schema context value. + public static func schema(_ schema: OpenAPI.Parameter.SchemaContext) -> Self { .a(schema) } } extension Either where B == JSONSchema { @@ -89,9 +113,9 @@ extension Either where B == JSONSchema { public static func schema(_ schema: JSONSchema) -> Self { .b(schema) } } -extension Either where B == OpenAPI.PathItem.Parameter { +extension Either where B == OpenAPI.Parameter { /// Construct a parameter value. - public static func parameter(_ parameter: OpenAPI.PathItem.Parameter) -> Self { .b(parameter) } + public static func parameter(_ parameter: OpenAPI.Parameter) -> Self { .b(parameter) } } extension Either where B == OpenAPI.Content.Map { diff --git a/Sources/OpenAPIKit/Encoding and Decoding Errors/OperationDecodingError.swift b/Sources/OpenAPIKit/Encoding and Decoding Errors/OperationDecodingError.swift index 3a67ddc3f..eeda33a81 100644 --- a/Sources/OpenAPIKit/Encoding and Decoding Errors/OperationDecodingError.swift +++ b/Sources/OpenAPIKit/Encoding and Decoding Errors/OperationDecodingError.swift @@ -9,7 +9,7 @@ import Foundation extension OpenAPI.Error.Decoding { public struct Operation: OpenAPIError { - public let endpoint: OpenAPI.HttpVerb + public let endpoint: OpenAPI.HttpMethod public let context: Context internal let relativeCodingPath: [CodingKey] @@ -78,7 +78,7 @@ extension OpenAPI.Error.Decoding.Operation { internal init(_ error: OpenAPI.Error.Decoding.Request) { var codingPath = error.codingPath.dropFirst(2) // this part of the coding path is structurally guaranteed to be an HTTP verb. - let verb = OpenAPI.HttpVerb(rawValue: codingPath.removeFirst().stringValue.uppercased())! + let verb = OpenAPI.HttpMethod(rawValue: codingPath.removeFirst().stringValue.uppercased())! endpoint = verb context = .request(error) @@ -88,7 +88,7 @@ extension OpenAPI.Error.Decoding.Operation { internal init(_ error: OpenAPI.Error.Decoding.Response) { var codingPath = error.codingPath.dropFirst(2) // this part of the coding path is structurally guaranteed to be an HTTP verb. - let verb = OpenAPI.HttpVerb(rawValue: codingPath.removeFirst().stringValue.uppercased())! + let verb = OpenAPI.HttpMethod(rawValue: codingPath.removeFirst().stringValue.uppercased())! endpoint = verb context = .response(error) @@ -98,7 +98,7 @@ extension OpenAPI.Error.Decoding.Operation { internal init(_ error: InconsistencyError) { var codingPath = error.codingPath.dropFirst(2) // this part of the coding path is structurally guaranteed to be an HTTP verb. - let verb = OpenAPI.HttpVerb(rawValue: codingPath.removeFirst().stringValue.uppercased())! + let verb = OpenAPI.HttpMethod(rawValue: codingPath.removeFirst().stringValue.uppercased())! endpoint = verb context = .inconsistency(error) @@ -108,7 +108,7 @@ extension OpenAPI.Error.Decoding.Operation { internal init(_ error: Swift.DecodingError) { var codingPath = error.codingPathWithoutSubject.dropFirst(2) // this part of the coding path is structurally guaranteed to be an HTTP verb. - let verb = OpenAPI.HttpVerb(rawValue: codingPath.removeFirst().stringValue.uppercased())! + let verb = OpenAPI.HttpMethod(rawValue: codingPath.removeFirst().stringValue.uppercased())! endpoint = verb context = .other(error) @@ -123,7 +123,7 @@ extension OpenAPI.Error.Decoding.Operation { var codingPath = eitherError.codingPath.dropFirst(2) // this part of the coding path is structurally guaranteed to be an HTTP verb. - let verb = OpenAPI.HttpVerb(rawValue: codingPath.removeFirst().stringValue.uppercased())! + let verb = OpenAPI.HttpMethod(rawValue: codingPath.removeFirst().stringValue.uppercased())! endpoint = verb context = .neither(eitherError) diff --git a/Sources/OpenAPIKit/Header.swift b/Sources/OpenAPIKit/Header.swift index be2b07c44..59f7ed73b 100644 --- a/Sources/OpenAPIKit/Header.swift +++ b/Sources/OpenAPIKit/Header.swift @@ -12,17 +12,17 @@ extension OpenAPI { /// /// See [OpenAPI Header Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#header-object). public struct Header: Equatable { - public typealias Schema = PathItem.Parameter.Schema + public typealias SchemaContext = Parameter.SchemaContext public let description: String? public let required: Bool public let deprecated: Bool // default is false /// OpenAPI Spec "schema" or "content", which are mutually exclusive. - public let schemaOrContent: Either + public let schemaOrContent: Either public typealias Map = OrderedDictionary, Header>> - public init(schemaOrContent: Either, + public init(schemaOrContent: Either, description: String? = nil, required: Bool = false, deprecated: Bool = false) { @@ -32,7 +32,7 @@ extension OpenAPI { self.deprecated = deprecated } - public init(schema: Schema, + public init(schema: SchemaContext, description: String? = nil, required: Bool = false, deprecated: Bool = false) { @@ -46,7 +46,7 @@ extension OpenAPI { description: String? = nil, required: Bool = false, deprecated: Bool = false) { - self.schemaOrContent = .init(Schema(schema, style: .default(for: .header))) + self.schemaOrContent = .init(SchemaContext(schema, style: .default(for: .header))) self.description = description self.required = required self.deprecated = deprecated @@ -56,7 +56,7 @@ extension OpenAPI { description: String? = nil, required: Bool = false, deprecated: Bool = false) { - self.schemaOrContent = .init(Schema(schemaReference: schemaReference, style: .default(for: .header))) + self.schemaOrContent = .init(SchemaContext(schemaReference: schemaReference, style: .default(for: .header))) self.description = description self.required = required self.deprecated = deprecated @@ -75,7 +75,7 @@ extension OpenAPI { } // MARK: - Header Convenience -extension OpenAPI.PathItem.Parameter.Schema { +extension OpenAPI.Parameter.SchemaContext { public static func header(_ schema: JSONSchema, allowReserved: Bool = false, example: AnyCodable? = nil) -> Self { @@ -166,9 +166,9 @@ extension OpenAPI.Header: Decodable { let maybeContent = try container.decodeIfPresent(OpenAPI.Content.Map.self, forKey: .content) - let maybeSchema: Schema? + let maybeSchema: SchemaContext? if container.contains(.schema) { - maybeSchema = try Schema(from: decoder, for: .header) + maybeSchema = try SchemaContext(from: decoder, for: .header) } else { maybeSchema = nil } diff --git a/Sources/OpenAPIKit/HttpMethod.swift b/Sources/OpenAPIKit/HttpMethod.swift new file mode 100644 index 000000000..6165d72d0 --- /dev/null +++ b/Sources/OpenAPIKit/HttpMethod.swift @@ -0,0 +1,25 @@ +// +// HttpMethod.swift +// +// +// Created by Mathew Polzin on 12/29/19. +// + +extension OpenAPI { + /// Represents the HTTP methods supported by the + /// OpenAPI Specification. + /// + /// See [OpenAPI Path Item Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#path-item-object) because the supported + /// HTTP methods are enumerated as properties on that + /// object. + public enum HttpMethod: String, CaseIterable { + case get = "GET" + case post = "POST" + case patch = "PATCH" + case put = "PUT" + case delete = "DELETE" + case head = "HEAD" + case options = "OPTIONS" + case trace = "TRACE" + } +} diff --git a/Sources/OpenAPIKit/HttpVerb.swift b/Sources/OpenAPIKit/HttpVerb.swift deleted file mode 100644 index e0239a2d0..000000000 --- a/Sources/OpenAPIKit/HttpVerb.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// HttpVerb.swift -// -// -// Created by Mathew Polzin on 12/29/19. -// - -extension OpenAPI { - public enum HttpVerb: String, CaseIterable { - case get = "GET" - case post = "POST" - case patch = "PATCH" - case put = "PUT" - case delete = "DELETE" - case head = "HEAD" - case options = "OPTIONS" - case trace = "TRACE" - } -} diff --git a/Sources/OpenAPIKit/Path Item/Operation.swift b/Sources/OpenAPIKit/Operation.swift similarity index 89% rename from Sources/OpenAPIKit/Path Item/Operation.swift rename to Sources/OpenAPIKit/Operation.swift index 49f99ee69..020b5120b 100644 --- a/Sources/OpenAPIKit/Path Item/Operation.swift +++ b/Sources/OpenAPIKit/Operation.swift @@ -7,7 +7,7 @@ import Foundation -extension OpenAPI.PathItem { +extension OpenAPI { /// OpenAPI Spec "Operation Object" /// /// See [OpenAPI Operation Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#operation-object). @@ -64,7 +64,7 @@ extension OpenAPI.PathItem { description: String? = nil, externalDocs: OpenAPI.ExternalDocumentation? = nil, operationId: String? = nil, - parameters: Parameter.Array, + parameters: Parameter.Array = [], requestBody: OpenAPI.Request? = nil, responses: OpenAPI.Response.Map, deprecated: Bool = false, @@ -89,9 +89,34 @@ extension OpenAPI.PathItem { } } +extension OpenAPI.Operation { + /// A `ResponseOutcome` is the combination of a + /// status code and a response. + public struct ResponseOutcome: Equatable { + public let status: OpenAPI.Response.StatusCode + public let response: Either, OpenAPI.Response> + + public init( + status: OpenAPI.Response.StatusCode, + response: Either, OpenAPI.Response> + ) { + self.status = status + self.response = response + } + } + + /// Get all response outcomes for this operation. + /// + /// - Returns: An array of `ResponseOutcomes` with the status + /// and the response for the status. + public var responseOutcomes: [ResponseOutcome] { + return responses.map { (status, response) in .init(status: status, response: response) } + } +} + // MARK: - Codable -extension OpenAPI.PathItem.Operation: Encodable { +extension OpenAPI.Operation: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -127,7 +152,7 @@ extension OpenAPI.PathItem.Operation: Encodable { } } -extension OpenAPI.PathItem.Operation: Decodable { +extension OpenAPI.Operation: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -142,7 +167,7 @@ extension OpenAPI.PathItem.Operation: Decodable { operationId = try container.decodeIfPresent(String.self, forKey: .operationId) - parameters = try container.decodeIfPresent(OpenAPI.PathItem.Parameter.Array.self, forKey: .parameters) ?? [] + parameters = try container.decodeIfPresent(OpenAPI.Parameter.Array.self, forKey: .parameters) ?? [] requestBody = try container.decodeIfPresent(Either, OpenAPI.Request>.self, forKey: .requestBody) @@ -174,7 +199,7 @@ extension OpenAPI.PathItem.Operation: Decodable { } } -extension OpenAPI.PathItem.Operation { +extension OpenAPI.Operation { internal enum CodingKeys: ExtendableCodingKey { case tags case summary diff --git a/Sources/OpenAPIKit/Path Item/Parameter.swift b/Sources/OpenAPIKit/Parameter/Parameter.swift similarity index 93% rename from Sources/OpenAPIKit/Path Item/Parameter.swift rename to Sources/OpenAPIKit/Parameter/Parameter.swift index c54a0e949..6fe10522c 100644 --- a/Sources/OpenAPIKit/Path Item/Parameter.swift +++ b/Sources/OpenAPIKit/Parameter/Parameter.swift @@ -7,7 +7,7 @@ import Foundation -extension OpenAPI.PathItem { +extension OpenAPI { /// OpenAPI Spec "Parameter Object" /// /// See [OpenAPI Parameter Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-object). @@ -20,7 +20,7 @@ extension OpenAPI.PathItem { public var deprecated: Bool // default is false /// OpenAPI Spec "content" or "schema" properties. - public var schemaOrContent: Either + public var schemaOrContent: Either /// Dictionary of vendor extensions. /// @@ -37,7 +37,7 @@ extension OpenAPI.PathItem { public init(name: String, context: Context, - schemaOrContent: Either, + schemaOrContent: Either, description: String? = nil, deprecated: Bool = false, vendorExtensions: [String: AnyCodable] = [:]) { @@ -51,7 +51,7 @@ extension OpenAPI.PathItem { public init(name: String, context: Context, - schema: Schema, + schema: SchemaContext, description: String? = nil, deprecated: Bool = false, vendorExtensions: [String: AnyCodable] = [:]) { @@ -71,7 +71,7 @@ extension OpenAPI.PathItem { vendorExtensions: [String: AnyCodable] = [:]) { self.name = name self.context = context - self.schemaOrContent = .init(Schema(schema, style: .default(for: context))) + self.schemaOrContent = .init(SchemaContext(schema, style: .default(for: context))) self.description = description self.deprecated = deprecated self.vendorExtensions = vendorExtensions @@ -85,7 +85,7 @@ extension OpenAPI.PathItem { vendorExtensions: [String: AnyCodable] = [:]) { self.name = name self.context = context - self.schemaOrContent = .init(Schema(schemaReference: schemaReference, style: .default(for: context))) + self.schemaOrContent = .init(SchemaContext(schemaReference: schemaReference, style: .default(for: context))) self.description = description self.deprecated = deprecated self.vendorExtensions = vendorExtensions @@ -109,12 +109,12 @@ extension OpenAPI.PathItem { // MARK: `Either` convenience methods // OpenAPI.PathItem.Array.Element => -extension Either where A == JSONReference, B == OpenAPI.PathItem.Parameter { +extension Either where A == JSONReference, B == OpenAPI.Parameter { /// Construct a parameter. public static func parameter( name: String, - context: OpenAPI.PathItem.Parameter.Context, + context: OpenAPI.Parameter.Context, schema: JSONSchema, description: String? = nil, deprecated: Bool = false, @@ -135,7 +135,7 @@ extension Either where A == JSONReference, B == Open /// Construct a parameter. public static func parameter( name: String, - context: OpenAPI.PathItem.Parameter.Context, + context: OpenAPI.Parameter.Context, content: OpenAPI.Content.Map, description: String? = nil, deprecated: Bool = false, @@ -156,7 +156,7 @@ extension Either where A == JSONReference, B == Open // MARK: - Codable -extension OpenAPI.PathItem.Parameter: Encodable { +extension OpenAPI.Parameter: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -205,7 +205,7 @@ extension OpenAPI.PathItem.Parameter: Encodable { } } -extension OpenAPI.PathItem.Parameter: Decodable { +extension OpenAPI.Parameter: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -236,9 +236,9 @@ extension OpenAPI.PathItem.Parameter: Decodable { let maybeContent = try container.decodeIfPresent(OpenAPI.Content.Map.self, forKey: .content) - let maybeSchema: Schema? + let maybeSchema: SchemaContext? if container.contains(.schema) { - maybeSchema = try Schema(from: decoder, for: context) + maybeSchema = try SchemaContext(from: decoder, for: context) } else { maybeSchema = nil } @@ -264,7 +264,7 @@ extension OpenAPI.PathItem.Parameter: Decodable { } } -extension OpenAPI.PathItem.Parameter { +extension OpenAPI.Parameter { internal enum CodingKeys: ExtendableCodingKey { case name case parameterLocation diff --git a/Sources/OpenAPIKit/Path Item/ParameterContext.swift b/Sources/OpenAPIKit/Parameter/ParameterContext.swift similarity index 96% rename from Sources/OpenAPIKit/Path Item/ParameterContext.swift rename to Sources/OpenAPIKit/Parameter/ParameterContext.swift index d3f21c62a..230481a35 100644 --- a/Sources/OpenAPIKit/Path Item/ParameterContext.swift +++ b/Sources/OpenAPIKit/Parameter/ParameterContext.swift @@ -5,7 +5,7 @@ // Created by Mathew Polzin on 12/29/19. // -extension OpenAPI.PathItem.Parameter { +extension OpenAPI.Parameter { /// OpenAPI Spec "Parameter Object" location-specific configuration. /// /// See [OpenAPI Parameter Locations](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-locations). @@ -61,7 +61,7 @@ extension OpenAPI.PathItem.Parameter { } } -extension OpenAPI.PathItem.Parameter.Context { +extension OpenAPI.Parameter.Context { public enum Location: String, CaseIterable, Codable { case query case header diff --git a/Sources/OpenAPIKit/Path Item/ParameterSchema.swift b/Sources/OpenAPIKit/Parameter/ParameterSchema.swift similarity index 93% rename from Sources/OpenAPIKit/Path Item/ParameterSchema.swift rename to Sources/OpenAPIKit/Parameter/ParameterSchema.swift index c9ece3a20..63cdabc1d 100644 --- a/Sources/OpenAPIKit/Path Item/ParameterSchema.swift +++ b/Sources/OpenAPIKit/Parameter/ParameterSchema.swift @@ -5,12 +5,12 @@ // Created by Mathew Polzin on 12/29/19. // -extension OpenAPI.PathItem.Parameter { +extension OpenAPI.Parameter { /// OpenAPI Spec "Parameter Object" schema and style configuration. /// /// See [OpenAPI Parameter Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-object) /// and [OpenAPI Style Values](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#style-values). - public struct Schema: Equatable { + public struct SchemaContext: Equatable { public let style: Style public let explode: Bool public let allowReserved: Bool //defaults to false @@ -126,7 +126,7 @@ extension OpenAPI.PathItem.Parameter { } } -extension OpenAPI.PathItem.Parameter.Schema { +extension OpenAPI.Parameter.SchemaContext { public enum Style: String, CaseIterable, Codable { case form case simple @@ -141,7 +141,7 @@ extension OpenAPI.PathItem.Parameter.Schema { /// /// 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.PathItem.Parameter.Context) -> Self { + public static func `default`(for location: OpenAPI.Parameter.Context) -> Self { switch location { case .query: return .form @@ -166,7 +166,7 @@ extension OpenAPI.PathItem.Parameter.Schema { } // MARK: - Codable -extension OpenAPI.PathItem.Parameter.Schema { +extension OpenAPI.Parameter.SchemaContext { private enum CodingKeys: String, CodingKey { case style case explode @@ -179,8 +179,8 @@ extension OpenAPI.PathItem.Parameter.Schema { } } -extension OpenAPI.PathItem.Parameter.Schema { - public func encode(to encoder: Encoder, for location: OpenAPI.PathItem.Parameter.Context) throws { +extension OpenAPI.Parameter.SchemaContext { + public func encode(to encoder: Encoder, for location: OpenAPI.Parameter.Context) throws { var container = encoder.container(keyedBy: CodingKeys.self) if style != Style.default(for: location) { @@ -205,8 +205,8 @@ extension OpenAPI.PathItem.Parameter.Schema { } } -extension OpenAPI.PathItem.Parameter.Schema { - public init(from decoder: Decoder, for location: OpenAPI.PathItem.Parameter.Context) throws { +extension OpenAPI.Parameter.SchemaContext { + public init(from decoder: Decoder, for location: OpenAPI.Parameter.Context) throws { let container = try decoder.container(keyedBy: CodingKeys.self) schema = try container.decode(Either, JSONSchema>.self, forKey: .schema) diff --git a/Sources/OpenAPIKit/Path Item/PathItem.swift b/Sources/OpenAPIKit/PathItem.swift similarity index 87% rename from Sources/OpenAPIKit/Path Item/PathItem.swift rename to Sources/OpenAPIKit/PathItem.swift index 76c5268c9..7ec86eba9 100644 --- a/Sources/OpenAPIKit/Path Item/PathItem.swift +++ b/Sources/OpenAPIKit/PathItem.swift @@ -139,7 +139,7 @@ extension OpenAPI { extension OpenAPI.PathItem { /// Retrieve the operation for the given verb, if one is set for this path. - public func `for`(_ verb: OpenAPI.HttpVerb) -> Operation? { + public func `for`(_ verb: OpenAPI.HttpMethod) -> OpenAPI.Operation? { switch verb { case .delete: return self.delete @@ -161,7 +161,7 @@ extension OpenAPI.PathItem { } /// Set the operation for the given verb, overwriting any already set operation for the same verb. - public mutating func set(operation: Operation?, for verb: OpenAPI.HttpVerb) { + public mutating func set(operation: OpenAPI.Operation?, for verb: OpenAPI.HttpMethod) { switch verb { case .delete: self.delete(operation) @@ -182,7 +182,7 @@ extension OpenAPI.PathItem { } } - public subscript(verb: OpenAPI.HttpVerb) -> Operation? { + public subscript(verb: OpenAPI.HttpMethod) -> OpenAPI.Operation? { get { return `for`(verb) } @@ -191,15 +191,20 @@ extension OpenAPI.PathItem { } } - public typealias Endpoint = (verb: OpenAPI.HttpVerb, operation: OpenAPI.PathItem.Operation) + /// An `Endpoint` is the combination of an + /// HTTP method and an operation. + public struct Endpoint: Equatable { + public let method: OpenAPI.HttpMethod + public let operation: OpenAPI.Operation + } /// Get all endpoints defined at this path. /// - /// - Returns: An array of tuples with the verb (i.e. `.get`) and the operation for - /// the verb. + /// - Returns: An array of `Endpoints` with the method (i.e. `.get`) and the operation for + /// the method. public var endpoints: [Endpoint] { - return OpenAPI.HttpVerb.allCases.compactMap { verb in - self.for(verb).map { (verb, $0) } + return OpenAPI.HttpMethod.allCases.compactMap { method in + self.for(method).map { .init(method: method, operation: $0) } } } } @@ -262,16 +267,16 @@ extension OpenAPI.PathItem: Decodable { servers = try container.decodeIfPresent([OpenAPI.Server].self, forKey: .servers) - parameters = try container.decodeIfPresent(Parameter.Array.self, forKey: .parameters) ?? [] + parameters = try container.decodeIfPresent(OpenAPI.Parameter.Array.self, forKey: .parameters) ?? [] - get = try container.decodeIfPresent(Operation.self, forKey: .get) - put = try container.decodeIfPresent(Operation.self, forKey: .put) - post = try container.decodeIfPresent(Operation.self, forKey: .post) - delete = try container.decodeIfPresent(Operation.self, forKey: .delete) - options = try container.decodeIfPresent(Operation.self, forKey: .options) - head = try container.decodeIfPresent(Operation.self, forKey: .head) - patch = try container.decodeIfPresent(Operation.self, forKey: .patch) - trace = try container.decodeIfPresent(Operation.self, forKey: .trace) + get = try container.decodeIfPresent(OpenAPI.Operation.self, forKey: .get) + put = try container.decodeIfPresent(OpenAPI.Operation.self, forKey: .put) + post = try container.decodeIfPresent(OpenAPI.Operation.self, forKey: .post) + delete = try container.decodeIfPresent(OpenAPI.Operation.self, forKey: .delete) + options = try container.decodeIfPresent(OpenAPI.Operation.self, forKey: .options) + head = try container.decodeIfPresent(OpenAPI.Operation.self, forKey: .head) + patch = try container.decodeIfPresent(OpenAPI.Operation.self, forKey: .patch) + trace = try container.decodeIfPresent(OpenAPI.Operation.self, forKey: .trace) vendorExtensions = try Self.extensions(from: decoder) } catch let error as DecodingError { diff --git a/Sources/OpenAPIKit/Response.swift b/Sources/OpenAPIKit/Response.swift index 6073a8088..11f34d5f1 100644 --- a/Sources/OpenAPIKit/Response.swift +++ b/Sources/OpenAPIKit/Response.swift @@ -78,6 +78,15 @@ extension OpenAPI.Response { } } + public var isSuccess: Bool { + switch self { + case .range(.success), .status(code: 200..<300): + return true + case .range, .status, .default: + return false + } + } + public init?(rawValue: String) { if let val = Int(rawValue) { self = .status(code: val) diff --git a/Sources/OpenAPIKit/Schema Conformances/SwiftPrimitiveTypes+OpenAPI.swift b/Sources/OpenAPIKit/Schema Conformances/SwiftPrimitiveTypes+OpenAPI.swift index 2bce71a9f..9cef2f65a 100644 --- a/Sources/OpenAPIKit/Schema Conformances/SwiftPrimitiveTypes+OpenAPI.swift +++ b/Sources/OpenAPIKit/Schema Conformances/SwiftPrimitiveTypes+OpenAPI.swift @@ -9,78 +9,76 @@ import Foundation extension Optional: OpenAPISchemaType where Wrapped: OpenAPISchemaType { static public var openAPISchema: JSONSchema { - return Wrapped.openAPISchema.optionalSchemaObject() + Wrapped.openAPISchema.optionalSchemaObject() } } extension Array: OpenAPISchemaType where Element: OpenAPISchemaType { static public var openAPISchema: JSONSchema { - return .array(.init(format: .generic, - required: true), - .init(items: Element.openAPISchema)) + .array( + items: Element.openAPISchema + ) } } extension Dictionary: OpenAPISchemaType where Key == String, Value: OpenAPISchemaType { static public var openAPISchema: JSONSchema { - return .object(.init(format: .generic, - required: true), - .init(properties: [:], - additionalProperties: .init(Value.openAPISchema))) + .object( + additionalProperties: .init(Value.openAPISchema) + ) } } extension String: OpenAPISchemaType { static public var openAPISchema: JSONSchema { - return .string(.init(format: .generic, - required: true), - .init()) + .string } } extension Bool: OpenAPISchemaType { static public var openAPISchema: JSONSchema { - return .boolean(.init(format: .generic, - required: true)) + .boolean } } extension Double: OpenAPISchemaType { static public var openAPISchema: JSONSchema { - return .number(.init(format: .double, - required: true), - .init()) + .number(format: .double) } } extension Float: OpenAPISchemaType { static public var openAPISchema: JSONSchema { - return .number(.init(format: .float, - required: true), - .init()) + .number(format: .float) } } extension Int: OpenAPISchemaType { static public var openAPISchema: JSONSchema { - return .integer(.init(format: .generic, - required: true), - .init()) + .integer } } extension Int32: OpenAPISchemaType { static public var openAPISchema: JSONSchema { - return .integer(.init(format: .int32, - required: true), - .init()) + .integer(format: .int32) } } extension Int64: OpenAPISchemaType { static public var openAPISchema: JSONSchema { - return .integer(.init(format: .int64, - required: true), - .init()) + .integer(format: .int64) } } + +extension URL: OpenAPISchemaType { + public static var openAPISchema: JSONSchema { + .string(format: .extended(.uri)) + } +} + +extension UUID: OpenAPISchemaType { + public static var openAPISchema: JSONSchema { + .string(format: .extended(.uuid)) + } +} diff --git a/Sources/OpenAPIKit/Schema Object/SchemaObject.swift b/Sources/OpenAPIKit/Schema Object/SchemaObject.swift index 225b4471a..ecd3170b0 100644 --- a/Sources/OpenAPIKit/Schema Object/SchemaObject.swift +++ b/Sources/OpenAPIKit/Schema Object/SchemaObject.swift @@ -17,9 +17,9 @@ public enum JSONSchema: Equatable, JSONSchemaContext { case number(Context, NumericContext) case integer(Context, IntegerContext) case string(Context, StringContext) - indirect case all(of: [JSONSchemaFragment]) - indirect case one(of: [JSONSchema]) - indirect case any(of: [JSONSchema]) + indirect case all(of: [JSONSchemaFragment], discriminator: OpenAPI.Discriminator?) + indirect case one(of: [JSONSchema], discriminator: OpenAPI.Discriminator?) + indirect case any(of: [JSONSchema], discriminator: OpenAPI.Discriminator?) indirect case not(JSONSchema) case reference(JSONReference) /// This schema does not have a `type` specified. This is allowed @@ -173,6 +173,25 @@ public enum JSONSchema: Equatable, JSONSchemaContext { } } + /// Get the discriminator, if specified. If unspecified, returns `nil`. + public var discriminator: OpenAPI.Discriminator? { + switch self { + case .boolean(let context as JSONSchemaContext), + .object(let context as JSONSchemaContext, _), + .array(let context as JSONSchemaContext, _), + .number(let context as JSONSchemaContext, _), + .integer(let context as JSONSchemaContext, _), + .string(let context as JSONSchemaContext, _): + return context.discriminator + case .all(_, let discriminator), + .one(_, let discriminator), + .any(_, let discriminator): + return discriminator + case .undefined, .not, .reference: + return nil + } + } + /// Get the external docs, if specified. If unspecified, returns `nil`. public var externalDocs: OpenAPI.ExternalDocumentation? { switch self { @@ -372,6 +391,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, + discriminator: OpenAPI.Discriminator? = nil, externalDocs: OpenAPI.ExternalDocumentation? = nil, allowedValues: [AnyCodable]? = nil, example: AnyCodable? = nil @@ -384,6 +404,7 @@ extension JSONSchema { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, allowedValues: allowedValues, example: example @@ -400,6 +421,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, + discriminator: OpenAPI.Discriminator? = nil, externalDocs: OpenAPI.ExternalDocumentation? = nil, allowedValues: AnyCodable..., example: AnyCodable? = nil @@ -412,6 +434,7 @@ extension JSONSchema { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, allowedValues: allowedValues, example: example @@ -431,6 +454,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, + discriminator: OpenAPI.Discriminator? = nil, externalDocs: OpenAPI.ExternalDocumentation? = nil, minLength: Int = 0, maxLength: Int? = nil, @@ -446,6 +470,7 @@ extension JSONSchema { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, allowedValues: allowedValues, example: example @@ -467,6 +492,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, + discriminator: OpenAPI.Discriminator? = nil, externalDocs: OpenAPI.ExternalDocumentation? = nil, minLength: Int = 0, maxLength: Int? = nil, @@ -482,6 +508,7 @@ extension JSONSchema { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, minLength: minLength, maxLength: maxLength, @@ -504,6 +531,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, + discriminator: OpenAPI.Discriminator? = nil, externalDocs: OpenAPI.ExternalDocumentation? = nil, multipleOf: Double? = nil, maximum: (Double, exclusive: Bool)? = nil, @@ -519,6 +547,7 @@ extension JSONSchema { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, allowedValues: allowedValues, example: example @@ -540,6 +569,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, + discriminator: OpenAPI.Discriminator? = nil, externalDocs: OpenAPI.ExternalDocumentation? = nil, multipleOf: Double? = nil, maximum: (Double, exclusive: Bool)? = nil, @@ -555,6 +585,7 @@ extension JSONSchema { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, multipleOf: multipleOf, maximum: maximum, @@ -577,6 +608,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, + discriminator: OpenAPI.Discriminator? = nil, externalDocs: OpenAPI.ExternalDocumentation? = nil, multipleOf: Int? = nil, maximum: (Int, exclusive: Bool)? = nil, @@ -592,6 +624,7 @@ extension JSONSchema { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, allowedValues: allowedValues, example: example @@ -613,6 +646,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, + discriminator: OpenAPI.Discriminator? = nil, externalDocs: OpenAPI.ExternalDocumentation? = nil, multipleOf: Int? = nil, maximum: (Int, exclusive: Bool)? = nil, @@ -628,6 +662,7 @@ extension JSONSchema { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, multipleOf: multipleOf, maximum: maximum, @@ -650,6 +685,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, + discriminator: OpenAPI.Discriminator? = nil, externalDocs: OpenAPI.ExternalDocumentation? = nil, minProperties: Int = 0, maxProperties: Int? = nil, @@ -666,6 +702,7 @@ extension JSONSchema { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, allowedValues: allowedValues, example: example @@ -692,6 +729,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, + discriminator: OpenAPI.Discriminator? = nil, externalDocs: OpenAPI.ExternalDocumentation? = nil, minItems: Int = 0, maxItems: Int? = nil, @@ -708,6 +746,7 @@ extension JSONSchema { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, allowedValues: allowedValues, example: example @@ -722,16 +761,43 @@ extension JSONSchema { return .array(generalContext, arrayContext) } - public static func all(of schemas: JSONSchemaFragment...) -> JSONSchema { - return .all(of: schemas) + public static func all( + of schemas: [JSONSchemaFragment] + ) -> JSONSchema { + return .all(of: schemas, discriminator: nil) } - public static func one(of schemas: JSONSchema...) -> JSONSchema { - return .one(of: schemas) + public static func all( + of schemas: JSONSchemaFragment..., + discriminator: OpenAPI.Discriminator? = nil + ) -> JSONSchema { + return .all(of: schemas, discriminator: discriminator) + } + + public static func one( + of schemas: [JSONSchema] + ) -> JSONSchema { + return .one(of: schemas, discriminator: nil) } - public static func any(of schemas: JSONSchema...) -> JSONSchema { - return .any(of: schemas) + public static func one( + of schemas: JSONSchema..., + discriminator: OpenAPI.Discriminator? = nil + ) -> JSONSchema { + return .one(of: schemas, discriminator: discriminator) + } + + public static func any( + of schemas: [JSONSchema] + ) -> JSONSchema { + return .any(of: schemas, discriminator: nil) + } + + public static func any( + of schemas: JSONSchema..., + discriminator: OpenAPI.Discriminator? = nil + ) -> JSONSchema { + return .any(of: schemas, discriminator: discriminator) } } @@ -739,6 +805,7 @@ extension JSONSchema { extension JSONSchema { private enum SubschemaCodingKeys: String, CodingKey { + case discriminator case allOf case oneOf case anyOf @@ -761,20 +828,23 @@ extension JSONSchema: Encodable { try contextA.encode(to: encoder) try contextB.encode(to: encoder) - case .all(of: let nodes): + case .all(of: let nodes, let discriminator): var container = encoder.container(keyedBy: SubschemaCodingKeys.self) try container.encode(nodes, forKey: .allOf) + try discriminator.encodeIfNotNil(to: &container, forKey: .discriminator) - case .one(of: let nodes): + case .one(of: let nodes, let discriminator): var container = encoder.container(keyedBy: SubschemaCodingKeys.self) try container.encode(nodes, forKey: .oneOf) + try discriminator.encodeIfNotNil(to: &container, forKey: .discriminator) - case .any(of: let nodes): + case .any(of: let nodes, let discriminator): var container = encoder.container(keyedBy: SubschemaCodingKeys.self) try container.encode(nodes, forKey: .anyOf) + try discriminator.encodeIfNotNil(to: &container, forKey: .discriminator) case .not(let node): var container = encoder.container(keyedBy: SubschemaCodingKeys.self) @@ -824,17 +894,20 @@ extension JSONSchema: Decodable { let container = try decoder.container(keyedBy: SubschemaCodingKeys.self) if container.contains(.allOf) { - self = .all(of: try container.decode([JSONSchemaFragment].self, forKey: .allOf)) + let discriminator = try container.decodeIfPresent(OpenAPI.Discriminator.self, forKey: .discriminator) + self = .all(of: try container.decode([JSONSchemaFragment].self, forKey: .allOf), discriminator: discriminator) return } if container.contains(.anyOf) { - self = .any(of: try container.decode([JSONSchema].self, forKey: .anyOf)) + let discriminator = try container.decodeIfPresent(OpenAPI.Discriminator.self, forKey: .discriminator) + self = .any(of: try container.decode([JSONSchema].self, forKey: .anyOf), discriminator: discriminator) return } if container.contains(.oneOf) { - self = .one(of: try container.decode([JSONSchema].self, forKey: .oneOf)) + let discriminator = try container.decodeIfPresent(OpenAPI.Discriminator.self, forKey: .discriminator) + self = .one(of: try container.decode([JSONSchema].self, forKey: .oneOf), discriminator: discriminator) return } diff --git a/Sources/OpenAPIKit/Schema Object/SchemaObjectContext.swift b/Sources/OpenAPIKit/Schema Object/SchemaObjectContext.swift index ee018d8e3..d22b621c0 100644 --- a/Sources/OpenAPIKit/Schema Object/SchemaObjectContext.swift +++ b/Sources/OpenAPIKit/Schema Object/SchemaObjectContext.swift @@ -17,6 +17,7 @@ public protocol JSONSchemaContext { var nullable: Bool { get } var title: String? { get } var description: String? { get } + var discriminator: OpenAPI.Discriminator? { get } var externalDocs: OpenAPI.ExternalDocumentation? { get } var allowedValues: [AnyCodable]? { get } var example: AnyCodable? { get } @@ -39,6 +40,21 @@ extension JSONSchema { public let description: String? public let externalDocs: OpenAPI.ExternalDocumentation? + /// An object used to discriminate upon the options for a child object's + /// schema in a polymorphic context. + /// + /// Discriminators are only applicable when used in conjunction with + /// `allOf`, `anyOf`, or `oneOf`. + /// + /// Still, they need to be supported on the + /// `JSONSchema.Context` (which is not used with those three special + /// schema types) because the specification states that a discriminator can + /// be placed on a parent object (one level up from an `allOf`, `anyOf`, + /// or `oneOf`) as a way to reduce redundancy. + /// + /// See [OpenAPI Discriminator Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#discriminator-object). + public let discriminator: OpenAPI.Discriminator? + // NOTE: "const" is supported by the newest JSON Schema spec but not // yet by OpenAPI. Instead, will use "enum" with one possible value for now. // public let constantValue: Format.SwiftType? @@ -68,6 +84,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, + discriminator: OpenAPI.Discriminator? = nil, externalDocs: OpenAPI.ExternalDocumentation? = nil, allowedValues: [AnyCodable]? = nil, example: AnyCodable? = nil) { @@ -78,6 +95,7 @@ extension JSONSchema { self.deprecated = deprecated self.title = title self.description = description + self.discriminator = discriminator self.externalDocs = externalDocs self.allowedValues = allowedValues self.example = example @@ -90,6 +108,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, + discriminator: OpenAPI.Discriminator? = nil, externalDocs: OpenAPI.ExternalDocumentation? = nil, allowedValues: [AnyCodable]? = nil, example: String) { @@ -100,6 +119,7 @@ extension JSONSchema { self.deprecated = deprecated self.title = title self.description = description + self.discriminator = discriminator self.externalDocs = externalDocs self.allowedValues = allowedValues self.example = AnyCodable(example) @@ -125,6 +145,7 @@ extension JSONSchema.Context { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, allowedValues: allowedValues, example: example) @@ -139,6 +160,7 @@ extension JSONSchema.Context { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, allowedValues: allowedValues, example: example) @@ -153,6 +175,7 @@ extension JSONSchema.Context { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, allowedValues: allowedValues, example: example) @@ -167,6 +190,7 @@ extension JSONSchema.Context { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, allowedValues: allowedValues, example: example) @@ -181,6 +205,7 @@ extension JSONSchema.Context { deprecated: deprecated, title: title, description: description, + discriminator: discriminator, externalDocs: externalDocs, allowedValues: allowedValues, example: example) @@ -296,6 +321,17 @@ extension JSONSchema { }.keys) } + public var optionalProperties: [String] { + return Array(properties.filter { (_, schemaObject) in + !schemaObject.required + }.keys) + } + + /// The minimum number of properties allowed. + /// + /// This might constradict a value explicitly specified on initialization + /// or when decoding if the number of required properties is greater + /// than the explicitly set minimum. public var minProperties: Int { return max(_minProperties, requiredProperties.count) } @@ -321,6 +357,7 @@ extension JSONSchema { case format case title case description + case discriminator case externalDocs case allowedValues = "enum" case nullable @@ -348,6 +385,8 @@ extension JSONSchema.Context: Encodable { try description.encodeIfNotNil(to: &container, forKey: .description) + try discriminator.encodeIfNotNil(to: &container, forKey: .discriminator) + try externalDocs.encodeIfNotNil(to: &container, forKey: .externalDocs) // nullable is false if omitted @@ -386,6 +425,8 @@ extension JSONSchema.Context: Decodable { title = try container.decodeIfPresent(String.self, forKey: .title) description = try container.decodeIfPresent(String.self, forKey: .description) + discriminator = try container.decodeIfPresent(OpenAPI.Discriminator.self, forKey: .discriminator) + externalDocs = try container.decodeIfPresent(OpenAPI.ExternalDocumentation.self, forKey: .externalDocs) allowedValues = try container.decodeIfPresent([AnyCodable].self, forKey: .allowedValues) diff --git a/Sources/OpenAPIKit/SecurityScheme.swift b/Sources/OpenAPIKit/SecurityScheme.swift index 092d12911..335139551 100644 --- a/Sources/OpenAPIKit/SecurityScheme.swift +++ b/Sources/OpenAPIKit/SecurityScheme.swift @@ -52,6 +52,28 @@ extension OpenAPI { } } +extension OpenAPI.SecurityScheme.SecurityType { + public enum Name: String, Codable { + case apiKey + case http + case oauth2 + case openIdConnect + } + + public var name: Name { + switch self { + case .apiKey: + return .apiKey + case .http: + return .http + case .oauth2: + return .oauth2 + case .openIdConnect: + return .openIdConnect + } + } +} + // MARK: - Codable extension OpenAPI.SecurityScheme: Encodable { public func encode(to encoder: Encoder) throws { @@ -61,18 +83,18 @@ extension OpenAPI.SecurityScheme: Encodable { switch type { case .apiKey(name: let name, location: let location): - try container.encode(SecurityTypeName.apiKey, forKey: .type) + try container.encode(SecurityType.Name.apiKey, forKey: .type) try container.encode(name, forKey: .name) try container.encode(location, forKey: .location) case .http(scheme: let scheme, bearerFormat: let bearerFormat): - try container.encode(SecurityTypeName.http, forKey: .type) + try container.encode(SecurityType.Name.http, forKey: .type) try container.encode(scheme, forKey: .scheme) try bearerFormat.encodeIfNotNil(to: &container, forKey: .bearerFormat) case .openIdConnect(openIdConnectUrl: let url): - try container.encode(SecurityTypeName.openIdConnect, forKey: .type) + try container.encode(SecurityType.Name.openIdConnect, forKey: .type) try container.encode(url, forKey: .openIdConnectUrl) case .oauth2(flows: let flows): - try container.encode(SecurityTypeName.oauth2, forKey: .type) + try container.encode(SecurityType.Name.oauth2, forKey: .type) try container.encode(flows, forKey: .flows) } } @@ -84,7 +106,7 @@ extension OpenAPI.SecurityScheme: Decodable { description = try container.decodeIfPresent(String.self, forKey: .description) - let typeName = try container.decode(SecurityTypeName.self, forKey: .type) + let typeName = try container.decode(SecurityType.Name.self, forKey: .type) switch typeName { case .apiKey: @@ -136,11 +158,4 @@ extension OpenAPI.SecurityScheme { case flows case openIdConnectUrl } - - internal enum SecurityTypeName: String, Codable { - case apiKey - case http - case oauth2 - case openIdConnect - } } diff --git a/Tests/AnyCodableTests/AnyCodableTests.swift b/Tests/AnyCodableTests/AnyCodableTests.swift index b9d8f505c..7f9c69376 100644 --- a/Tests/AnyCodableTests/AnyCodableTests.swift +++ b/Tests/AnyCodableTests/AnyCodableTests.swift @@ -41,6 +41,10 @@ class AnyCodableTests: XCTestCase { XCTAssertNotEqual(AnyCodable(()), AnyCodable(true)) } + func testVoidDescription() { + XCTAssertEqual(String(describing: AnyCodable(Void())), "nil") + } + func testJSONDecoding() throws { let json = """ { diff --git a/Tests/OpenAPIKitCompatibilitySuite/PetStoreAPITests.swift b/Tests/OpenAPIKitCompatibilitySuite/PetStoreAPITests.swift index 8bf5831d8..8133783c9 100644 --- a/Tests/OpenAPIKitCompatibilitySuite/PetStoreAPITests.swift +++ b/Tests/OpenAPIKitCompatibilitySuite/PetStoreAPITests.swift @@ -90,7 +90,7 @@ final class PetStoreAPICampatibilityTests: XCTestCase { XCTAssertFalse(apiDoc.paths["/pet/{petId}"]?.get?.parameters.isEmpty ?? true) XCTAssertEqual(apiDoc.paths["/pet/{petId}"]?.get?.parameters.first?.parameterValue?.name, "petId") XCTAssertEqual(apiDoc.paths["/pet/{petId}"]?.get?.parameters.first?.parameterValue?.context, .path) - XCTAssertEqual(apiDoc.paths["/pet/{petId}"]?.get?.parameters.first?.parameterValue?.schemaOrContent.schemaValue?.schema.schemaValue, .integer(format: .int64, required: false)) + XCTAssertEqual(apiDoc.paths["/pet/{petId}"]?.get?.parameters.first?.parameterValue?.schemaOrContent.schemaValue, .integer(format: .int64, required: false)) } func test_successfullyParsedComponents() { diff --git a/Tests/OpenAPIKitTests/ComponentsTests.swift b/Tests/OpenAPIKitTests/ComponentsTests.swift index bcc0afc6d..6759b25b9 100644 --- a/Tests/OpenAPIKitTests/ComponentsTests.swift +++ b/Tests/OpenAPIKitTests/ComponentsTests.swift @@ -19,15 +19,16 @@ final class ComponentsTests: XCTestCase { let ref1 = JSONReference.component(named: "world") let ref2 = JSONReference.component(named: "missing") - let ref3 = JSONReference.component(named: "param") + let ref3 = JSONReference.component(named: "param") XCTAssertEqual(components[ref1], .integer(required: false)) + XCTAssertEqual(components.dereference(ref1), components[ref1]) XCTAssertNil(components[ref2]) XCTAssertNil(components[ref3]) let ref4 = JSONReference.InternalReference.component(name: "world") let ref5 = JSONReference.InternalReference.component(name: "missing") - let ref6 = JSONReference.InternalReference.component(name: "param") + let ref6 = JSONReference.InternalReference.component(name: "param") XCTAssertEqual(components[ref4], .integer(required: false)) XCTAssertNil(components[ref5]) @@ -63,7 +64,7 @@ final class ComponentsTests: XCTestCase { XCTAssertEqual(ref2, .component(named: "world")) XCTAssertThrowsError(try components.reference(named: "missing", ofType: JSONSchema.self)) - XCTAssertThrowsError(try components.reference(named: "hello", ofType: OpenAPI.PathItem.Parameter.self)) + XCTAssertThrowsError(try components.reference(named: "hello", ofType: OpenAPI.Parameter.self)) } func test_failedReferenceCreation() { @@ -100,7 +101,7 @@ final class ComponentsTests: XCTestCase { let ref1 = try components.reference(named: "one", ofType: JSONSchema.self) let ref2 = try components.reference(named: "two", ofType: OpenAPI.Response.self) - let ref3 = try components.reference(named: "three", ofType: OpenAPI.PathItem.Parameter.self) + let ref3 = try components.reference(named: "three", ofType: OpenAPI.Parameter.self) let ref4 = try components.reference(named: "four", ofType: OpenAPI.Example.self) let ref5 = try components.reference(named: "five", ofType: OpenAPI.Request.self) let ref6 = try components.reference(named: "six", ofType: OpenAPI.Header.self) diff --git a/Tests/OpenAPIKitTests/DeclarativeEaseOfUseTests.swift b/Tests/OpenAPIKitTests/EaseOfUseTests.swift similarity index 73% rename from Tests/OpenAPIKitTests/DeclarativeEaseOfUseTests.swift rename to Tests/OpenAPIKitTests/EaseOfUseTests.swift index 47a79f01b..fe3654077 100644 --- a/Tests/OpenAPIKitTests/DeclarativeEaseOfUseTests.swift +++ b/Tests/OpenAPIKitTests/EaseOfUseTests.swift @@ -160,7 +160,7 @@ final class DeclarativeEaseOfUseTests: XCTestCase { variables: [:] ) - let testSHOW_endpoint = OpenAPI.PathItem.Operation( + let testSHOW_endpoint = OpenAPI.Operation( tags: "Test", summary: "Get Test", description: "Get Test description", @@ -194,7 +194,7 @@ final class DeclarativeEaseOfUseTests: XCTestCase { ] ) - let testCREATE_endpoint = OpenAPI.PathItem.Operation( + let testCREATE_endpoint = OpenAPI.Operation( tags: "Test", summary: "Post Test", description: "Post Test description", @@ -412,7 +412,7 @@ final class DeclarativeEaseOfUseTests: XCTestCase { paths: [ "/hello": .init( summary: "Say hello", - get: OpenAPI.PathItem.Operation( + get: OpenAPI.Operation( tags: ["Greetings"], summary: "Get a greeting", description: "An endpoint that says hello to you.", @@ -425,4 +425,151 @@ final class DeclarativeEaseOfUseTests: XCTestCase { components: components ) } + + func test_getAllEndpoints() { + let document = testDocument + + // get endpoints for each path + let endpoints = document.paths.mapValues { $0.endpoints } + + // count endpoints by HTTP method + let endpointMethods = endpoints.values.flatMap { $0 }.map { $0.method } + let countByMethod = Dictionary(grouping: endpointMethods, by: { $0 }).mapValues { $0.count } + XCTAssertEqual(countByMethod[.get], 2) + XCTAssertEqual(countByMethod[.post], 1) + } + + func test_resolveSecurity() { + let document = testDocument + + let securityForAllEndpoints = document.security.first?.first + let authForAllEndpoints = securityForAllEndpoints.flatMap { document.components[$0.key] } + let scopesForAllEndpoints = securityForAllEndpoints?.value + + XCTAssertEqual(authForAllEndpoints?.type.name, .oauth2) + XCTAssertEqual(scopesForAllEndpoints, ["widget:read", "widget:write"]) + } + + func test_getResponseSchema() { + let document = testDocument + + let endpoint = document.paths["/widgets/{id}"]?.get + let response = endpoint?.responses[.status(code: 200)]?.responseValue + let responseSchemaReference = response?.content[.json]?.schema + // this response schema is a reference found in the Components Object. We dereference + // it to get at the schema. + let responseSchema = responseSchemaReference.flatMap(document.components.dereference) + + XCTAssertEqual(responseSchema, .object(properties: [ "partNumber": .integer, "description": .string ])) + } + + func test_getRequestSchema() { + let document = testDocument + + let endpoint = document.paths["/widgets/{id}"]?.post + let request = endpoint?.requestBody?.requestValue + let requestSchemaReference = request?.content[.json]?.schema + // this request schema is defined inline but dereferencing still produces the schema + // (dereferencing is just a no-op in this case). + let requestSchema = requestSchemaReference.flatMap(document.components.dereference) + + XCTAssertEqual(requestSchema, .object(properties: [ "description": .string ])) + } } + +fileprivate let testWidgetSchema = JSONSchema.object( + properties: [ + "partNumber": .integer, + "description": .string + ] +) + +fileprivate let testComponents = OpenAPI.Components( + schemas: [ + "testWidgetSchema": testWidgetSchema + ], + securitySchemes: [ + "oauth": .oauth2( + flows: .init( + clientCredentials: .init( + tokenUrl: URL(string: "http://website.com/token")!, + scopes: [ "widget:read": "", "widget:write": "" ] + ) + ) + ) + ] +) + +fileprivate let testInfo = OpenAPI.Document.Info(title: "Test API", version: "1.0") + +fileprivate let testServer = OpenAPI.Server(url: URL(string: "http://website.com")!) + +fileprivate let testDocument = OpenAPI.Document( + openAPIVersion: .v3_0_3, + info: testInfo, + servers: [testServer], + paths: [ + "/widgets/{id}": OpenAPI.PathItem( + parameters: [ + .parameter( + name: "id", + context: .path, + schema: .string + ) + ], + get: OpenAPI.Operation( + tags: "Widgets", + summary: "Get a widget", + responses: [ + 200: .response( + description: "A single widget", + content: [ + .json: .init(schemaReference: .component(named: "testWidgetSchema")) + ] + ) + ] + ), + post: OpenAPI.Operation( + tags: "Widgets", + summary: "Create a new widget", + description: "Create a new widget by adding a description. The created widget will be returned in the response body including a new part number.", + requestBody: OpenAPI.Request( + content: [ + .json: .init( + schema: JSONSchema.object( + properties: [ + "description": .string + ] + ) + ) + ] + ), + responses: [ + 201: .response( + description: "The newly created widget", + content: [ + .json: .init(schemaReference: .component(named: "testWidgetSchema")) + ] + ) + ] + ) + ), + "/docs": OpenAPI.PathItem( + get: OpenAPI.Operation( + tags: "Documentation", + responses: [ + 200: .response( + description: "Get documentation on this API.", + content: [ + .html: .init(schema: .string) + ] + ) + ] + ) + ) + ], + components: testComponents, + security: [ + [.component(named: "oauth"): ["widget:read", "widget:write"]] + ] +) diff --git a/Tests/OpenAPIKitTests/HeaderTests.swift b/Tests/OpenAPIKitTests/HeaderTests.swift index 4181919a1..8982d8ea6 100644 --- a/Tests/OpenAPIKitTests/HeaderTests.swift +++ b/Tests/OpenAPIKitTests/HeaderTests.swift @@ -41,21 +41,21 @@ final class HeaderTests: XCTestCase { let t8 = OpenAPI.Header(content: contentMap, deprecated: true) XCTAssertTrue(t8.deprecated) - let t9 = OpenAPI.Header(schema: OpenAPI.Header.Schema.header(.string)) + let t9 = OpenAPI.Header(schema: OpenAPI.Header.SchemaContext.header(.string)) XCTAssertEqual(t9, t3) } func test_headerSchemaInits() { - let t1 = OpenAPI.Header.Schema.header(.string) + let t1 = OpenAPI.Header.SchemaContext.header(.string) XCTAssertEqual(t1.style, .default(for: .header)) - let t2 = OpenAPI.Header.Schema.header(.string, examples: nil) + let t2 = OpenAPI.Header.SchemaContext.header(.string, examples: nil) XCTAssertEqual(t2.style, .default(for: .header)) - let t3 = OpenAPI.Header.Schema.header(schemaReference: .external(URL(string: "hello.yml")!)) + let t3 = OpenAPI.Header.SchemaContext.header(schemaReference: .external(URL(string: "hello.yml")!)) XCTAssertEqual(t3.style, .default(for: .header)) - let t4 = OpenAPI.Header.Schema.header(schemaReference: .external(URL(string: "hello.yml")!), examples: nil) + let t4 = OpenAPI.Header.SchemaContext.header(schemaReference: .external(URL(string: "hello.yml")!), examples: nil) XCTAssertEqual(t4.style, .default(for: .header)) } } diff --git a/Tests/OpenAPIKitTests/JSONReferenceTests.swift b/Tests/OpenAPIKitTests/JSONReferenceTests.swift index 69fdba579..a2a71555b 100644 --- a/Tests/OpenAPIKitTests/JSONReferenceTests.swift +++ b/Tests/OpenAPIKitTests/JSONReferenceTests.swift @@ -114,7 +114,7 @@ final class JSONReferenceTests: XCTestCase { func test_componentPaths() { XCTAssertEqual(JSONReference.component(named: "hello").absoluteString, "#/components/schemas/hello") XCTAssertEqual(JSONReference.component(named: "hello").absoluteString, "#/components/responses/hello") - XCTAssertEqual(JSONReference.component(named: "hello").absoluteString, "#/components/parameters/hello") + XCTAssertEqual(JSONReference.component(named: "hello").absoluteString, "#/components/parameters/hello") XCTAssertEqual(JSONReference.component(named: "hello").absoluteString, "#/components/examples/hello") XCTAssertEqual(JSONReference.component(named: "hello").absoluteString, "#/components/requestBodies/hello") XCTAssertEqual(JSONReference.component(named: "hello").absoluteString, "#/components/headers/hello") diff --git a/Tests/OpenAPIKitTests/Path Item/OperationTests.swift b/Tests/OpenAPIKitTests/OperationTests.swift similarity index 84% rename from Tests/OpenAPIKitTests/Path Item/OperationTests.swift rename to Tests/OpenAPIKitTests/OperationTests.swift index e2ee16c95..8e2aaca79 100644 --- a/Tests/OpenAPIKitTests/Path Item/OperationTests.swift +++ b/Tests/OpenAPIKitTests/OperationTests.swift @@ -13,12 +13,12 @@ import FineJSON final class OperationTests: XCTestCase { func test_init() { // minimum - let _ = OpenAPI.PathItem.Operation( + let _ = OpenAPI.Operation( responses: [:] ) // all things - let _ = OpenAPI.PathItem.Operation( + let _ = OpenAPI.Operation( tags: ["hello"], summary: "summary", description: "description", @@ -33,19 +33,36 @@ final class OperationTests: XCTestCase { ) // variadic tags - let _ = OpenAPI.PathItem.Operation( + let _ = OpenAPI.Operation( tags: "hi", "hello", parameters: [], responses: [:] ) } + + func test_responseOutcomes() { + let t1 = OpenAPI.Operation( + responses: [ + 200: .response(description: "success", content: [:]), + 404: .reference(.component(named: "notFound")) + ] + ) + + XCTAssertEqual( + t1.responseOutcomes, + [ + .init(status: 200, response: .response(description: "success", content: [:])), + .init(status: 404, response: .reference(.component(named: "notFound"))) + ] + ) + } } // MARK: - Codable Tests extension OperationTests { func test_minimal_encode() throws { - let operation = OpenAPI.PathItem.Operation( + let operation = OpenAPI.Operation( responses: [:] ) @@ -71,16 +88,16 @@ extension OperationTests { } """.data(using: .utf8)! - let operation = try testDecoder.decode(OpenAPI.PathItem.Operation.self, from: operationData) + let operation = try testDecoder.decode(OpenAPI.Operation.self, from: operationData) XCTAssertEqual( operation, - OpenAPI.PathItem.Operation(responses: [:]) + OpenAPI.Operation(responses: [:]) ) } func test_maximal_encode() throws { - let operation = OpenAPI.PathItem.Operation( + let operation = OpenAPI.Operation( tags: ["hi", "hello"], summary: "summary", description: "description", @@ -207,11 +224,11 @@ extension OperationTests { } """.data(using: .utf8)! - let operation = try testDecoder.decode(OpenAPI.PathItem.Operation.self, from: operationData) + let operation = try testDecoder.decode(OpenAPI.Operation.self, from: operationData) XCTAssertEqual( operation, - OpenAPI.PathItem.Operation( + OpenAPI.Operation( tags: ["hi", "hello"], summary: "summary", description: "description", @@ -236,7 +253,7 @@ extension OperationTests { // Note that JSONEncoder for Linux Foundation does not respect order func test_responseOrder_encode() throws { - let operation = OpenAPI.PathItem.Operation( + let operation = OpenAPI.Operation( responses: [ 404: .reference(.component(named: "404")), 200: .reference(.component(named: "200")) @@ -264,7 +281,7 @@ extension OperationTests { """ ) - let operation2 = OpenAPI.PathItem.Operation( + let operation2 = OpenAPI.Operation( responses: [ 200: .reference(.component(named: "200")), 404: .reference(.component(named: "404")) @@ -304,11 +321,11 @@ responses: $ref: '#/components/responses/200' """ - let operation = try YAMLDecoder().decode(OpenAPI.PathItem.Operation.self, from: operationString) + let operation = try YAMLDecoder().decode(OpenAPI.Operation.self, from: operationString) XCTAssertEqual( operation, - OpenAPI.PathItem.Operation( + OpenAPI.Operation( responses: [ 404: .reference(.component(named: "404")), 200: .reference(.component(named: "200")) @@ -325,11 +342,11 @@ responses: $ref: '#/components/responses/404' """ - let operation2 = try YAMLDecoder().decode(OpenAPI.PathItem.Operation.self, from: operationString2) + let operation2 = try YAMLDecoder().decode(OpenAPI.Operation.self, from: operationString2) XCTAssertEqual( operation2, - OpenAPI.PathItem.Operation( + OpenAPI.Operation( responses: [ 200: .reference(.component(named: "200")), 404: .reference(.component(named: "404")) diff --git a/Tests/OpenAPIKitTests/Path Item/ParameterContextTests.swift b/Tests/OpenAPIKitTests/Parameter/ParameterContextTests.swift similarity index 97% rename from Tests/OpenAPIKitTests/Path Item/ParameterContextTests.swift rename to Tests/OpenAPIKitTests/Parameter/ParameterContextTests.swift index a7120a74e..1e75cd68f 100644 --- a/Tests/OpenAPIKitTests/Path Item/ParameterContextTests.swift +++ b/Tests/OpenAPIKitTests/Parameter/ParameterContextTests.swift @@ -9,7 +9,7 @@ import XCTest import OpenAPIKit final class ParameterContextTests: XCTestCase { - typealias Context = OpenAPI.PathItem.Parameter.Context + typealias Context = OpenAPI.Parameter.Context func test_query() { let t1: Context = .query diff --git a/Tests/OpenAPIKitTests/Path Item/ParameterSchemaTests.swift b/Tests/OpenAPIKitTests/Parameter/ParameterSchemaTests.swift similarity index 96% rename from Tests/OpenAPIKitTests/Path Item/ParameterSchemaTests.swift rename to Tests/OpenAPIKitTests/Parameter/ParameterSchemaTests.swift index 3cb197296..3a4eb68c8 100644 --- a/Tests/OpenAPIKitTests/Path Item/ParameterSchemaTests.swift +++ b/Tests/OpenAPIKitTests/Parameter/ParameterSchemaTests.swift @@ -9,7 +9,7 @@ import XCTest import OpenAPIKit final class ParameterSchemaTests: XCTestCase { - typealias Schema = OpenAPI.PathItem.Parameter.Schema + typealias Schema = OpenAPI.Parameter.SchemaContext func test_initialize() { // init specifying opposite of default explode and allowReserved. @@ -529,9 +529,9 @@ extension ParameterSchemaTests { fileprivate struct SchemaWrapper: Codable { let location: TestLocation - let schema: OpenAPI.PathItem.Parameter.Schema + let schema: OpenAPI.Parameter.SchemaContext - init(location: OpenAPI.PathItem.Parameter.Context, schema: OpenAPI.PathItem.Parameter.Schema) { + init(location: OpenAPI.Parameter.Context, schema: OpenAPI.Parameter.SchemaContext) { self.location = .init(location) self.schema = schema } @@ -547,7 +547,7 @@ fileprivate struct SchemaWrapper: Codable { case path case cookie - var paramLoc: OpenAPI.PathItem.Parameter.Context { + var paramLoc: OpenAPI.Parameter.Context { switch self { case .query: return .query case .header: return .header @@ -556,7 +556,7 @@ fileprivate struct SchemaWrapper: Codable { } } - init(_ paramLoc: OpenAPI.PathItem.Parameter.Context) { + init(_ paramLoc: OpenAPI.Parameter.Context) { switch paramLoc { case .query: self = .query case .header: self = .header @@ -580,6 +580,6 @@ fileprivate struct SchemaWrapper: Codable { let location = try container.decode(TestLocation.self, forKey: .location) self.location = location - schema = try OpenAPI.PathItem.Parameter.Schema(from: container.superDecoder(forKey: .schema), for: location.paramLoc) + schema = try OpenAPI.Parameter.SchemaContext(from: container.superDecoder(forKey: .schema), for: location.paramLoc) } } diff --git a/Tests/OpenAPIKitTests/Path Item/ParameterTests.swift b/Tests/OpenAPIKitTests/Parameter/ParameterTests.swift similarity index 74% rename from Tests/OpenAPIKitTests/Path Item/ParameterTests.swift rename to Tests/OpenAPIKitTests/Parameter/ParameterTests.swift index fde89e56e..84b9634d0 100644 --- a/Tests/OpenAPIKitTests/Path Item/ParameterTests.swift +++ b/Tests/OpenAPIKitTests/Parameter/ParameterTests.swift @@ -10,7 +10,7 @@ import OpenAPIKit final class ParameterTests: XCTestCase { func test_initialize() { - let t1 = OpenAPI.PathItem.Parameter( + let t1 = OpenAPI.Parameter( name: "hello", context: .cookie(required: true), schemaOrContent: .init([.json: OpenAPI.Content(schema: .string)]), @@ -19,7 +19,7 @@ final class ParameterTests: XCTestCase { ) XCTAssertTrue(t1.required) - let t2 = OpenAPI.PathItem.Parameter( + let t2 = OpenAPI.Parameter( name: "hello", context: .cookie(required: true), schemaOrContent: .content([.json: OpenAPI.Content(schema: .string)]), @@ -29,7 +29,7 @@ final class ParameterTests: XCTestCase { XCTAssertTrue(t2.deprecated) XCTAssertEqual(t1, t2) - let t4 = OpenAPI.PathItem.Parameter( + let t4 = OpenAPI.Parameter( name: "hello", context: .cookie(required: false), schema: .init(.string, style: .default(for: .cookie)), @@ -38,38 +38,76 @@ final class ParameterTests: XCTestCase { ) XCTAssertFalse(t4.required) - let t5 = OpenAPI.PathItem.Parameter( + let t5 = OpenAPI.Parameter( name: "hello", context: .cookie, schema: .string ) XCTAssertFalse(t5.deprecated) - let t6 = OpenAPI.PathItem.Parameter( + let t6 = OpenAPI.Parameter( name: "hello", context: .cookie, schemaOrContent: .schema(.init(.string, style: .default(for: .cookie))) ) XCTAssertEqual(t5, t6) - let _ = OpenAPI.PathItem.Parameter( + let _ = OpenAPI.Parameter( name: "hello", context: .cookie, schemaReference: .component( named: "hello") ) - let _ = OpenAPI.PathItem.Parameter( + let _ = OpenAPI.Parameter( name: "hello", context: .cookie, content: [.json: OpenAPI.Content(schema: .string)] ) } + func test_schemaAccess() { + let t1 = OpenAPI.Parameter( + name: "hello", + context: .cookie, + schemaOrContent: .schema(.init(.string, style: .default(for: .cookie))) + ) + + XCTAssertNil(t1.schemaOrContent.contentValue) + XCTAssertNil(t1.schemaOrContent.schemaReference) + XCTAssertNil(t1.schemaOrContent.schemaContextValue?.schema.reference) + XCTAssertEqual(t1.schemaOrContent.schemaValue, .string) + XCTAssertEqual(t1.schemaOrContent.schemaContextValue, .init(.string, style: .default(for: .cookie))) + XCTAssertEqual(t1.schemaOrContent.schemaContextValue?.schema.schemaValue, t1.schemaOrContent.schemaValue) + + let t2 = OpenAPI.Parameter( + name: "hello", + context: .cookie, + schemaReference: .component( named: "hello") + ) + + XCTAssertNil(t2.schemaOrContent.contentValue) + XCTAssertNil(t2.schemaOrContent.schemaValue) + XCTAssertNil(t2.schemaOrContent.schemaContextValue?.schema.schemaValue) + XCTAssertEqual(t2.schemaOrContent.schemaReference, .component( named: "hello")) + XCTAssertEqual(t2.schemaOrContent.schemaContextValue?.schema.reference, t2.schemaOrContent.schemaReference) + + let t3 = OpenAPI.Parameter( + name: "hello", + context: .path, + content: [:] + ) + + XCTAssertNil(t3.schemaOrContent.schemaValue) + XCTAssertNil(t3.schemaOrContent.schemaReference) + XCTAssertNil(t3.schemaOrContent.schemaContextValue) + XCTAssertEqual(t3.schemaOrContent.contentValue, [:]) + } + func test_parameterArray() { - let t1: OpenAPI.PathItem.Parameter.Array = [ - .parameter(OpenAPI.PathItem.Parameter(name: "hello", context: .cookie, schema: .string)), + let t1: OpenAPI.Parameter.Array = [ + .parameter(OpenAPI.Parameter(name: "hello", context: .cookie, schema: .string)), .parameter(name: "hello", context: .cookie, schema: .string), - .parameter(OpenAPI.PathItem.Parameter(name: "hello", context: .cookie, content: [.json: OpenAPI.Content(schema: .string)])), + .parameter(OpenAPI.Parameter(name: "hello", context: .cookie, content: [.json: OpenAPI.Content(schema: .string)])), .parameter(name: "hello", context: .cookie, content: [.json: OpenAPI.Content(schema: .string)]), .reference(.component( named: "hello")) ] @@ -81,7 +119,7 @@ final class ParameterTests: XCTestCase { XCTAssertNotEqual(t1[4], t1[2]) XCTAssertNotEqual(t1[4], t1[3]) - XCTAssertEqual(t1[0].parameterValue, OpenAPI.PathItem.Parameter(name: "hello", context: .cookie, schema: .string)) + XCTAssertEqual(t1[0].parameterValue, OpenAPI.Parameter(name: "hello", context: .cookie, schema: .string)) XCTAssertEqual(t1[4].reference, .component( named: "hello")) } } @@ -89,7 +127,7 @@ final class ParameterTests: XCTestCase { // MARK: - Codable Tests extension ParameterTests { func test_minimalContent_encode() throws { - let parameter = OpenAPI.PathItem.Parameter( + let parameter = OpenAPI.Parameter( name: "hello", context: .path, content: [ .json: .init(schema: .string)] @@ -133,11 +171,11 @@ extension ParameterTests { } """.data(using: .utf8)! - let parameter = try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData) + let parameter = try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData) XCTAssertEqual( parameter, - OpenAPI.PathItem.Parameter( + OpenAPI.Parameter( name: "hello", context: .path, content: [ .json: .init(schema: .string(required: false))] @@ -147,7 +185,7 @@ extension ParameterTests { } func test_minimalSchema_encode() throws { - let parameter = OpenAPI.PathItem.Parameter( + let parameter = OpenAPI.Parameter( name: "hello", context: .path, schema: .string @@ -183,11 +221,11 @@ extension ParameterTests { } """.data(using: .utf8)! - let parameter = try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData) + let parameter = try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData) XCTAssertEqual( parameter, - OpenAPI.PathItem.Parameter( + OpenAPI.Parameter( name: "hello", context: .path, schema: .string(required: false) @@ -196,7 +234,7 @@ extension ParameterTests { } func test_queryParam_encode() throws { - let parameter = OpenAPI.PathItem.Parameter( + let parameter = OpenAPI.Parameter( name: "hello", context: .query, schema: .string @@ -230,25 +268,26 @@ extension ParameterTests { } """.data(using: .utf8)! - let parameter = try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData) + let parameter = try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData) XCTAssertEqual(parameter.location, .query) XCTAssertEqual( parameter, - OpenAPI.PathItem.Parameter( + OpenAPI.Parameter( name: "hello", context: .query, schema: .string(required: false) ) ) XCTAssertEqual( - parameter.schemaOrContent.schemaValue, - OpenAPI.PathItem.Parameter.Schema(.string(required: false), style: .default(for: .query)) + parameter.schemaOrContent.schemaContextValue, + OpenAPI.Parameter.SchemaContext(.string(required: false), style: .default(for: .query)) ) + XCTAssertEqual(parameter.schemaOrContent.schemaValue, parameter.schemaOrContent.schemaContextValue?.schema.schemaValue) } func test_queryParamAllowEmpty_encode() throws { - let parameter = OpenAPI.PathItem.Parameter( + let parameter = OpenAPI.Parameter( name: "hello", context: .query(allowEmptyValue: true), schema: .string @@ -284,11 +323,11 @@ extension ParameterTests { } """.data(using: .utf8)! - let parameter = try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData) + let parameter = try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData) XCTAssertEqual( parameter, - OpenAPI.PathItem.Parameter( + OpenAPI.Parameter( name: "hello", context: .query(allowEmptyValue: true), schema: .string(required: false) @@ -297,7 +336,7 @@ extension ParameterTests { } func test_requiredQueryParam_encode() throws { - let parameter = OpenAPI.PathItem.Parameter( + let parameter = OpenAPI.Parameter( name: "hello", context: .query(required: true), schema: .string @@ -333,11 +372,11 @@ extension ParameterTests { } """.data(using: .utf8)! - let parameter = try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData) + let parameter = try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData) XCTAssertEqual( parameter, - OpenAPI.PathItem.Parameter( + OpenAPI.Parameter( name: "hello", context: .query(required: true), schema: .string(required: false) @@ -346,7 +385,7 @@ extension ParameterTests { } func test_headerParam_encode() throws { - let parameter = OpenAPI.PathItem.Parameter( + let parameter = OpenAPI.Parameter( name: "hello", context: .header, schema: .string @@ -380,12 +419,12 @@ extension ParameterTests { } """.data(using: .utf8)! - let parameter = try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData) + let parameter = try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData) XCTAssertEqual(parameter.location, .header) XCTAssertEqual( parameter, - OpenAPI.PathItem.Parameter( + OpenAPI.Parameter( name: "hello", context: .header, schema: .string(required: false) @@ -394,7 +433,7 @@ extension ParameterTests { } func test_requiredHeaderParam_encode() throws { - let parameter = OpenAPI.PathItem.Parameter( + let parameter = OpenAPI.Parameter( name: "hello", context: .header(required: true), schema: .string @@ -430,11 +469,11 @@ extension ParameterTests { } """.data(using: .utf8)! - let parameter = try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData) + let parameter = try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData) XCTAssertEqual( parameter, - OpenAPI.PathItem.Parameter( + OpenAPI.Parameter( name: "hello", context: .header(required: true), schema: .string(required: false) @@ -443,7 +482,7 @@ extension ParameterTests { } func test_cookieParam_encode() throws { - let parameter = OpenAPI.PathItem.Parameter( + let parameter = OpenAPI.Parameter( name: "hello", context: .cookie, schema: .string @@ -477,12 +516,12 @@ extension ParameterTests { } """.data(using: .utf8)! - let parameter = try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData) + let parameter = try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData) XCTAssertEqual(parameter.location, .cookie) XCTAssertEqual( parameter, - OpenAPI.PathItem.Parameter( + OpenAPI.Parameter( name: "hello", context: .cookie, schema: .string(required: false) @@ -491,7 +530,7 @@ extension ParameterTests { } func test_requiredCookieParam_encode() throws { - let parameter = OpenAPI.PathItem.Parameter( + let parameter = OpenAPI.Parameter( name: "hello", context: .cookie(required: true), schema: .string @@ -527,11 +566,11 @@ extension ParameterTests { } """.data(using: .utf8)! - let parameter = try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData) + let parameter = try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData) XCTAssertEqual( parameter, - OpenAPI.PathItem.Parameter( + OpenAPI.Parameter( name: "hello", context: .cookie(required: true), schema: .string(required: false) @@ -540,7 +579,7 @@ extension ParameterTests { } func test_deprecated_encode() throws { - let parameter = OpenAPI.PathItem.Parameter( + let parameter = OpenAPI.Parameter( name: "hello", context: .path, schema: .string, @@ -579,11 +618,11 @@ extension ParameterTests { } """.data(using: .utf8)! - let parameter = try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData) + let parameter = try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData) XCTAssertEqual( parameter, - OpenAPI.PathItem.Parameter( + OpenAPI.Parameter( name: "hello", context: .path, schema: .string(required: false), @@ -593,7 +632,7 @@ extension ParameterTests { } func test_description_encode() throws { - let parameter = OpenAPI.PathItem.Parameter( + let parameter = OpenAPI.Parameter( name: "hello", context: .path, schema: .string, @@ -632,12 +671,12 @@ extension ParameterTests { } """.data(using: .utf8)! - let parameter = try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData) + let parameter = try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData) XCTAssertEqual(parameter.location, .path) XCTAssertEqual( parameter, - OpenAPI.PathItem.Parameter( + OpenAPI.Parameter( name: "hello", context: .path, schema: .string(required: false), @@ -647,7 +686,7 @@ extension ParameterTests { } func test_vendorExtension_encode() throws { - let parameter = OpenAPI.PathItem.Parameter( + let parameter = OpenAPI.Parameter( name: "hello", context: .path, schema: .string, @@ -695,12 +734,12 @@ extension ParameterTests { } """.data(using: .utf8)! - let parameter = try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData) + let parameter = try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData) XCTAssertEqual(parameter.location, .path) XCTAssertEqual( parameter, - OpenAPI.PathItem.Parameter( + OpenAPI.Parameter( name: "hello", context: .path, schema: .string(required: false), @@ -730,7 +769,7 @@ extension ParameterTests { } """.data(using: .utf8)! - XCTAssertThrowsError(try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData)) + XCTAssertThrowsError(try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData)) } func test_decodeNonRequiredPathParam_throws() { @@ -746,7 +785,7 @@ extension ParameterTests { } """.data(using: .utf8)! - XCTAssertThrowsError(try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData)) + XCTAssertThrowsError(try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData)) let parameterData2 = """ @@ -759,6 +798,6 @@ extension ParameterTests { } """.data(using: .utf8)! - XCTAssertThrowsError(try testDecoder.decode(OpenAPI.PathItem.Parameter.self, from: parameterData2)) + XCTAssertThrowsError(try testDecoder.decode(OpenAPI.Parameter.self, from: parameterData2)) } } diff --git a/Tests/OpenAPIKitTests/Path Item/PathItemTests.swift b/Tests/OpenAPIKitTests/PathItemTests.swift similarity index 97% rename from Tests/OpenAPIKitTests/Path Item/PathItemTests.swift rename to Tests/OpenAPIKitTests/PathItemTests.swift index 598609fc1..e4d169772 100644 --- a/Tests/OpenAPIKitTests/Path Item/PathItemTests.swift +++ b/Tests/OpenAPIKitTests/PathItemTests.swift @@ -34,7 +34,7 @@ final class PathItemTests: XCTestCase { let _ = OpenAPI.PathItem() // maximal - let op = OpenAPI.PathItem.Operation(responses: [:]) + let op = OpenAPI.Operation(responses: [:]) let _ = OpenAPI.PathItem( summary: "summary", description: "description", @@ -52,7 +52,7 @@ final class PathItemTests: XCTestCase { } func test_pathItemMutations() { - let op = OpenAPI.PathItem.Operation(responses: [:]) + let op = OpenAPI.Operation(responses: [:]) // adding/removing paths var pathItem = OpenAPI.PathItem() @@ -247,7 +247,7 @@ extension PathItemTests { } func test_operations_encode() throws { - let op = OpenAPI.PathItem.Operation(responses: [:]) + let op = OpenAPI.Operation(responses: [:]) let pathItem = OpenAPI.PathItem( get: op, @@ -360,7 +360,7 @@ extension PathItemTests { let pathItem = try testDecoder.decode(OpenAPI.PathItem.self, from: pathItemData) - let op = OpenAPI.PathItem.Operation(responses: [:]) + let op = OpenAPI.Operation(responses: [:]) XCTAssertEqual( pathItem, diff --git a/Tests/OpenAPIKitTests/ResponseTests.swift b/Tests/OpenAPIKitTests/ResponseTests.swift index 1f574f8ac..a9f49bcf9 100644 --- a/Tests/OpenAPIKitTests/ResponseTests.swift +++ b/Tests/OpenAPIKitTests/ResponseTests.swift @@ -39,6 +39,23 @@ final class ResponseTests: XCTestCase { XCTAssertNotNil(responseMap[404]?.reference) XCTAssertNil(responseMap[404]?.responseValue) } + + func test_status() { + let t1: OpenAPI.Response.StatusCode = .range(.success) + XCTAssertTrue(t1.isSuccess) + + let t2: OpenAPI.Response.StatusCode = 201 + XCTAssertTrue(t2.isSuccess) + + let t3: OpenAPI.Response.StatusCode = 200 + XCTAssertTrue(t3.isSuccess) + + let t4: OpenAPI.Response.StatusCode = 300 + XCTAssertFalse(t4.isSuccess) + + let t5: OpenAPI.Response.StatusCode = .range(.serverError) + XCTAssertFalse(t5.isSuccess) + } } // MARK: Response Status Code diff --git a/Tests/OpenAPIKitTests/Schema Conformances/SwiftPrimitiveTypes+OpenAPITests.swift b/Tests/OpenAPIKitTests/Schema Conformances/SwiftPrimitiveTypes+OpenAPITests.swift index 525a32435..1082b9ff9 100644 --- a/Tests/OpenAPIKitTests/Schema Conformances/SwiftPrimitiveTypes+OpenAPITests.swift +++ b/Tests/OpenAPIKitTests/Schema Conformances/SwiftPrimitiveTypes+OpenAPITests.swift @@ -39,6 +39,14 @@ class SwiftPrimitiveTypesTests: XCTestCase { XCTAssertEqual(Int64.openAPISchema, .integer(format: .int64)) } + func test_urlSchema() { + XCTAssertEqual(URL.openAPISchema, .string(format: .extended(.uri))) + } + + func test_uuidSchema() { + XCTAssertEqual(UUID.openAPISchema, .string(format: .extended(.uuid))) + } + func test_ArraySchema() { XCTAssertEqual([String].openAPISchema, .array(items: .string)) @@ -54,6 +62,10 @@ class SwiftPrimitiveTypesTests: XCTestCase { XCTAssertEqual([Int64].openAPISchema, .array(items: .integer(format: .int64))) + XCTAssertEqual([URL].openAPISchema, .array(items: .string(format: .extended(.uri)))) + + XCTAssertEqual([UUID].openAPISchema, .array(items: .string(format: .extended(.uuid)))) + XCTAssertEqual([String?].openAPISchema, .array(items: .string(required: false))) } @@ -72,6 +84,10 @@ class SwiftPrimitiveTypesTests: XCTestCase { XCTAssertEqual(Int64?.openAPISchema, .integer(format: .int64, required: false)) + XCTAssertEqual(URL?.openAPISchema, .string(format: .extended(.uri), required: false)) + + XCTAssertEqual(UUID?.openAPISchema, .string(format: .extended(.uuid), required: false)) + XCTAssertEqual([String]?.openAPISchema, .array(required: false, items: .string)) XCTAssertEqual([String?]?.openAPISchema, .array(required: false, items: .string(required: false))) @@ -92,6 +108,10 @@ class SwiftPrimitiveTypesTests: XCTestCase { XCTAssertEqual([String: Int64].openAPISchema, .object(additionalProperties: .schema(.integer(format: .int64)))) + XCTAssertEqual([String: URL].openAPISchema, .object(additionalProperties: .schema(.string(format: .extended(.uri))))) + + XCTAssertEqual([String: UUID].openAPISchema, .object(additionalProperties: .schema(.string(format: .extended(.uuid))))) + XCTAssertEqual([String: String?].openAPISchema, .object(additionalProperties: .schema(.string(required: false)))) } diff --git a/Tests/OpenAPIKitTests/Schema Object/SchemaObjectTests.swift b/Tests/OpenAPIKitTests/Schema Object/SchemaObjectTests.swift index 961c28190..6d203d0c8 100644 --- a/Tests/OpenAPIKitTests/Schema Object/SchemaObjectTests.swift +++ b/Tests/OpenAPIKitTests/Schema Object/SchemaObjectTests.swift @@ -373,6 +373,38 @@ final class SchemaObjectTests: XCTestCase { XCTAssertNil(undefined.description) } + func test_discriminator() { + let boolean = JSONSchema.boolean(.init(format: .unspecified, required: true, discriminator: .init(propertyName: "name"))) + let object = JSONSchema.object(.init(format: .unspecified, required: true, discriminator: .init(propertyName: "name")), .init(properties: [:])) + let array = JSONSchema.array(.init(format: .unspecified, required: true, discriminator: .init(propertyName: "name")), .init(items: .boolean(.init(format: .unspecified, required: true)))) + let number = JSONSchema.number(.init(format: .unspecified, required: true, discriminator: .init(propertyName: "name")), .init()) + let integer = JSONSchema.integer(.init(format: .unspecified, required: true, discriminator: .init(propertyName: "name")), .init()) + let string = JSONSchema.string(.init(format: .unspecified, required: true, discriminator: .init(propertyName: "name")), .init()) + + let allOf = JSONSchema.all(of: [.string(.init(), .init())], discriminator: .init(propertyName: "name")) + let anyOf = JSONSchema.any(of: [boolean], discriminator: .init(propertyName: "name")) + let oneOf = JSONSchema.one(of: [boolean], discriminator: .init(propertyName: "name")) + let not = JSONSchema.not(boolean) + let reference = JSONSchema.reference(.external(URL(string: "hello/world.json#/hello")!)) + let undefined = JSONSchema.undefined(description: nil) + let undefinedWithDescription = JSONSchema.undefined(description: "hello") + + XCTAssertEqual(boolean.discriminator?.propertyName, "name") + XCTAssertEqual(object.discriminator?.propertyName, "name") + XCTAssertEqual(array.discriminator?.propertyName, "name") + XCTAssertEqual(number.discriminator?.propertyName, "name") + XCTAssertEqual(integer.discriminator?.propertyName, "name") + XCTAssertEqual(string.discriminator?.propertyName, "name") + XCTAssertEqual(allOf.discriminator?.propertyName, "name") + XCTAssertEqual(anyOf.discriminator?.propertyName, "name") + XCTAssertEqual(oneOf.discriminator?.propertyName, "name") + + XCTAssertNil(undefinedWithDescription.discriminator) + XCTAssertNil(not.discriminator) + XCTAssertNil(reference.discriminator) + XCTAssertNil(undefined.discriminator) + } + func test_externalDocs() { let boolean = JSONSchema.boolean(.init(format: .unspecified, required: true, externalDocs: .init(url: URL(string: "http://google.com")!))) let object = JSONSchema.object(.init(format: .unspecified, required: true, externalDocs: .init(url: URL(string: "http://google.com")!)), .init(properties: [:])) @@ -631,45 +663,57 @@ final class SchemaObjectTests: XCTestCase { } func test_minObjectProperties() { - let obj1 = JSONSchema.ObjectContext(properties: [:], - additionalProperties: .init(true), - minProperties: 2) + let obj1 = JSONSchema.ObjectContext( + properties: [:], + additionalProperties: .init(true), + minProperties: 2 + ) XCTAssertEqual(obj1.minProperties, 2) - let obj2 = JSONSchema.ObjectContext(properties: [:], - additionalProperties: .init(true)) + let obj2 = JSONSchema.ObjectContext( + properties: [:], + additionalProperties: .init(true) + ) XCTAssertEqual(obj2.minProperties, 0) - let obj3 = JSONSchema.ObjectContext(properties: [ - "hello": .string + let obj3 = JSONSchema.ObjectContext( + properties: [ + "hello": .string ], - additionalProperties: .init(true)) + additionalProperties: .init(true) + ) XCTAssertEqual(obj3.minProperties, 1) - let obj4 = JSONSchema.ObjectContext(properties: [ - "hello": .string(required: false) + let obj4 = JSONSchema.ObjectContext( + properties: [ + "hello": .string(required: false) ], - additionalProperties: .init(true)) + additionalProperties: .init(true) + ) XCTAssertEqual(obj4.minProperties, 0) - let obj5 = JSONSchema.ObjectContext(properties: [ - "hello": .string + let obj5 = JSONSchema.ObjectContext( + properties: [ + "hello": .string ], - additionalProperties: .init(true), - minProperties: 3) + additionalProperties: .init(true), + minProperties: 3 + ) XCTAssertEqual(obj5.minProperties, 3) - let obj6 = JSONSchema.ObjectContext(properties: [ - "hello": .string, - "world": .boolean + let obj6 = JSONSchema.ObjectContext( + properties: [ + "hello": .string, + "world": .boolean ], - additionalProperties: .init(true), - minProperties: 1) + additionalProperties: .init(true), + minProperties: 1 + ) XCTAssertEqual(obj6.minProperties, 2) } @@ -788,32 +832,43 @@ extension SchemaObjectTests { let deprecatedBoolean = JSONSchema.boolean(.init(format: .unspecified, required: true, deprecated: true)) let allowedValueBoolean = JSONSchema.boolean(.init(format: .unspecified, required: true)) .with(allowedValues: [true]) - - testAllSharedSimpleContextEncoding(typeName: "boolean", - requiredEntity: requiredBoolean, - optionalEntity: optionalBoolean, - nullableEntity: nullableBoolean, - readOnlyEntity: readOnlyBoolean, - writeOnlyEntity: writeOnlyBoolean, - deprecatedEntity: deprecatedBoolean, - allowedValues: (entity: allowedValueBoolean, - value: "true")) + let discriminatorBoolean = JSONSchema.boolean(.init(format: .unspecified, required: true, discriminator: .init(propertyName: "hello"))) + + testAllSharedSimpleContextEncoding( + typeName: "boolean", + requiredEntity: requiredBoolean, + optionalEntity: optionalBoolean, + nullableEntity: nullableBoolean, + readOnlyEntity: readOnlyBoolean, + writeOnlyEntity: writeOnlyBoolean, + deprecatedEntity: deprecatedBoolean, + allowedValues: ( + entity: allowedValueBoolean, + value: "true" + ), + discriminator: ( + entity: discriminatorBoolean, + name: "hello" + ) + ) } - func test_decodeBoolean() { + func test_decodeBoolean() throws { let booleanData = #"{"type": "boolean"}"#.data(using: .utf8)! let nullableBooleanData = #"{"type": "boolean", "nullable": true}"#.data(using: .utf8)! let readOnlyBooleanData = #"{"type": "boolean", "readOnly": true}"#.data(using: .utf8)! let writeOnlyBooleanData = #"{"type": "boolean", "writeOnly": true}"#.data(using: .utf8)! let deprecatedBooleanData = #"{"type": "boolean", "deprecated": true}"#.data(using: .utf8)! let allowedValueBooleanData = #"{"type": "boolean", "enum": [false]}"#.data(using: .utf8)! + let discriminatorBooleanData = #"{"type": "boolean", "discriminator": { "propertyName": "hello" }}"#.data(using: .utf8)! - let boolean = try! testDecoder.decode(JSONSchema.self, from: booleanData) - let nullableBoolean = try! testDecoder.decode(JSONSchema.self, from: nullableBooleanData) - let readOnlyBoolean = try! testDecoder.decode(JSONSchema.self, from: readOnlyBooleanData) - let writeOnlyBoolean = try! testDecoder.decode(JSONSchema.self, from: writeOnlyBooleanData) - let deprecatedBoolean = try! testDecoder.decode(JSONSchema.self, from: deprecatedBooleanData) - let allowedValueBoolean = try! testDecoder.decode(JSONSchema.self, from: allowedValueBooleanData) + let boolean = try testDecoder.decode(JSONSchema.self, from: booleanData) + let nullableBoolean = try testDecoder.decode(JSONSchema.self, from: nullableBooleanData) + let readOnlyBoolean = try testDecoder.decode(JSONSchema.self, from: readOnlyBooleanData) + let writeOnlyBoolean = try testDecoder.decode(JSONSchema.self, from: writeOnlyBooleanData) + let deprecatedBoolean = try testDecoder.decode(JSONSchema.self, from: deprecatedBooleanData) + let allowedValueBoolean = try testDecoder.decode(JSONSchema.self, from: allowedValueBooleanData) + let discriminatorBoolean = try testDecoder.decode(JSONSchema.self, from: discriminatorBooleanData) XCTAssertEqual(boolean, JSONSchema.boolean(.init(format: .generic, required: false))) XCTAssertEqual(nullableBoolean, JSONSchema.boolean(.init(format: .generic, required: false, nullable: true))) @@ -821,6 +876,7 @@ extension SchemaObjectTests { XCTAssertEqual(writeOnlyBoolean, JSONSchema.boolean(.init(format: .generic, required: false, permissions: .writeOnly))) XCTAssertEqual(deprecatedBoolean, JSONSchema.boolean(.init(format: .generic, required: false, deprecated: true))) XCTAssertEqual(allowedValueBoolean, JSONSchema.boolean(.init(format: .generic, required: false, allowedValues: [false]))) + XCTAssertEqual(discriminatorBoolean, JSONSchema.boolean(.init(format: .generic, required: false, discriminator: .init(propertyName: "hello")))) } func test_encodeObject() { @@ -848,88 +904,113 @@ extension SchemaObjectTests { .with(allowedValues: [ AnyCodable(["hello": false]) ]) + let discriminatorObject = JSONSchema.object(.init(format: .unspecified, required: true, discriminator: .init(propertyName: "hello")), .init(properties: [:])) + + testEncodingPropertyLines( + entity: requiredObject, + propertyLines: [ + "\"properties\" : {", + " \"hello\" : {", + " \"type\" : \"boolean\"", + " }", + "},", + "\"type\" : \"object\"" + ] + ) - testEncodingPropertyLines(entity: requiredObject, - propertyLines: [ - "\"properties\" : {", - " \"hello\" : {", - " \"type\" : \"boolean\"", - " }", - "},", - "\"type\" : \"object\"" - ]) + testEncodingPropertyLines( + entity: optionalObject, + propertyLines: [ + "\"properties\" : {", + " \"hello\" : {", + " \"type\" : \"boolean\"", + " }", + "},", + "\"type\" : \"object\"" + ] + ) - testEncodingPropertyLines(entity: optionalObject, - propertyLines: [ - "\"properties\" : {", - " \"hello\" : {", - " \"type\" : \"boolean\"", - " }", - "},", - "\"type\" : \"object\"" - ]) + testEncodingPropertyLines( + entity: nullableObject, + propertyLines: [ + "\"nullable\" : true,", + "\"properties\" : {", + " \"hello\" : {", + " \"type\" : \"boolean\"", + " }", + "},", + "\"type\" : \"object\"" + ] + ) - testEncodingPropertyLines(entity: nullableObject, - propertyLines: [ - "\"nullable\" : true,", - "\"properties\" : {", - " \"hello\" : {", - " \"type\" : \"boolean\"", - " }", - "},", - "\"type\" : \"object\"" - ]) + testEncodingPropertyLines( + entity: readOnlyObject, + propertyLines: [ + "\"properties\" : {", + " \"hello\" : {", + " \"type\" : \"boolean\"", + " }", + "},", + "\"readOnly\" : true,", + "\"type\" : \"object\"" + ] + ) - testEncodingPropertyLines(entity: readOnlyObject, - propertyLines: [ - "\"properties\" : {", - " \"hello\" : {", - " \"type\" : \"boolean\"", - " }", - "},", - "\"readOnly\" : true,", - "\"type\" : \"object\"" - ]) + testEncodingPropertyLines( + entity: writeOnlyObject, + propertyLines: [ + "\"properties\" : {", + " \"hello\" : {", + " \"type\" : \"boolean\"", + " }", + "},", + "\"type\" : \"object\",", + "\"writeOnly\" : true" + ] + ) - testEncodingPropertyLines(entity: writeOnlyObject, - propertyLines: [ - "\"properties\" : {", - " \"hello\" : {", - " \"type\" : \"boolean\"", - " }", - "},", - "\"type\" : \"object\",", - "\"writeOnly\" : true" - ]) + testEncodingPropertyLines( + entity: deprecatedObject, + propertyLines: [ + "\"deprecated\" : true,", + "\"properties\" : {", + " \"hello\" : {", + " \"type\" : \"boolean\"", + " }", + "},", + "\"type\" : \"object\"" + ] + ) - testEncodingPropertyLines(entity: deprecatedObject, - propertyLines: [ - "\"deprecated\" : true,", - "\"properties\" : {", - " \"hello\" : {", - " \"type\" : \"boolean\"", - " }", - "},", - "\"type\" : \"object\"" - ]) + testEncodingPropertyLines( + entity: allowedValueObject, + propertyLines: [ + "\"enum\" : [", + " {", + " \"hello\" : false", + " }", + "],", + "\"properties\" : {", + " \"hello\" : {", + " \"type\" : \"boolean\"", + " }", + "},", + "\"type\" : \"object\"" + ] + ) - testEncodingPropertyLines(entity: allowedValueObject, - propertyLines: [ - "\"enum\" : [", - " {", - " \"hello\" : false", - " }", - "],", - "\"properties\" : {", - " \"hello\" : {", - " \"type\" : \"boolean\"", - " }", - "},", - "\"type\" : \"object\"" - ]) + testEncodingPropertyLines( + entity: discriminatorObject, + propertyLines: [ + "\"discriminator\" : {", + " \"propertyName\" : \"hello\"", + "},", + "\"type\" : \"object\"" + ] + ) } - func test_decodeObject() { + func test_decodeObject() throws { let objectData = """ { "type": "object" @@ -966,13 +1047,20 @@ extension SchemaObjectTests { "enum": [{"hello": false}] } """.data(using: .utf8)! + let discriminatorObjectData = """ + { + "type": "object", + "discriminator": {"propertyName": "hello"} + } + """.data(using: .utf8)! - let object = try! testDecoder.decode(JSONSchema.self, from: objectData) - let nullableObject = try! testDecoder.decode(JSONSchema.self, from: nullableObjectData) - let readOnlyObject = try! testDecoder.decode(JSONSchema.self, from: readOnlyObjectData) - let writeOnlyObject = try! testDecoder.decode(JSONSchema.self, from: writeOnlyObjectData) - let deprecatedObject = try! testDecoder.decode(JSONSchema.self, from: deprecatedObjectData) - let allowedValueObject = try! testDecoder.decode(JSONSchema.self, from: allowedValueObjectData) + let object = try testDecoder.decode(JSONSchema.self, from: objectData) + let nullableObject = try testDecoder.decode(JSONSchema.self, from: nullableObjectData) + let readOnlyObject = try testDecoder.decode(JSONSchema.self, from: readOnlyObjectData) + let writeOnlyObject = try testDecoder.decode(JSONSchema.self, from: writeOnlyObjectData) + let deprecatedObject = try testDecoder.decode(JSONSchema.self, from: deprecatedObjectData) + let allowedValueObject = try testDecoder.decode(JSONSchema.self, from: allowedValueObjectData) + let discriminatorObject = try testDecoder.decode(JSONSchema.self, from: discriminatorObjectData) XCTAssertEqual(object, JSONSchema.object(.init(format: .generic, required: false), .init(properties: [:]))) XCTAssertEqual(nullableObject, JSONSchema.object(.init(format: .generic, required: false, nullable: true), .init(properties: [:]))) @@ -981,6 +1069,7 @@ extension SchemaObjectTests { XCTAssertEqual(deprecatedObject, JSONSchema.object(.init(format: .generic, required: false, deprecated: true), .init(properties: [:]))) XCTAssertEqual(allowedValueObject.allowedValues?[0].value as! [String: Bool], ["hello": false]) XCTAssertEqual(allowedValueObject.jsonTypeFormat, .object(.generic)) + XCTAssertEqual(discriminatorObject, JSONSchema.object(required: false, discriminator: .init(propertyName: "hello"))) guard case let .object(_, contextB) = allowedValueObject else { XCTFail("expected object to be parsed as object") @@ -1801,6 +1890,11 @@ extension SchemaObjectTests { ]) .with(example: AnyCodable(["hello": true])) + if case let .object(_, objectContext) = requiredObject { + XCTAssertEqual(objectContext.requiredProperties, []) + XCTAssertEqual(objectContext.optionalProperties, ["hello"]) + } + testEncodingPropertyLines(entity: string, propertyLines: [ "\"example\" : \"hello\",", @@ -1985,6 +2079,11 @@ extension SchemaObjectTests { AnyCodable(["hello": false]) ]) + if case let .object(_, objectContext) = requiredObject { + XCTAssertEqual(objectContext.requiredProperties, ["hello"]) + XCTAssertEqual(objectContext.optionalProperties, []) + } + testEncodingPropertyLines(entity: requiredObject, propertyLines: [ "\"minProperties\" : 1,", @@ -2098,32 +2197,43 @@ extension SchemaObjectTests { let deprecatedArray = JSONSchema.array(.init(format: .unspecified, required: true, deprecated: true), .init()) let allowedValueArray = JSONSchema.array(.init(format: .unspecified, required: true), .init()) .with(allowedValues: [[10]]) - - testAllSharedSimpleContextEncoding(typeName: "array", - requiredEntity: requiredArray, - optionalEntity: optionalArray, - nullableEntity: nullableArray, - readOnlyEntity: readOnlyArray, - writeOnlyEntity: writeOnlyArray, - deprecatedEntity: deprecatedArray, - allowedValues: (entity: allowedValueArray, - value: "[\n 10\n ]")) + let discriminatorArray = JSONSchema.array(.init(format: .unspecified, required: true, discriminator: .init(propertyName: "hello")), .init()) + + testAllSharedSimpleContextEncoding( + typeName: "array", + requiredEntity: requiredArray, + optionalEntity: optionalArray, + nullableEntity: nullableArray, + readOnlyEntity: readOnlyArray, + writeOnlyEntity: writeOnlyArray, + deprecatedEntity: deprecatedArray, + allowedValues: ( + entity: allowedValueArray, + value: "[\n 10\n ]" + ), + discriminator: ( + entity: discriminatorArray, + name: "hello" + ) + ) } - func test_decodeArray() { + func test_decodeArray() throws { let arrayData = #"{"type": "array"}"#.data(using: .utf8)! let nullableArrayData = #"{"type": "array", "nullable": true}"#.data(using: .utf8)! let readOnlyArrayData = #"{"type": "array", "readOnly": true}"#.data(using: .utf8)! let writeOnlyArrayData = #"{"type": "array", "writeOnly": true}"#.data(using: .utf8)! let deprecatedArrayData = #"{"type": "array", "deprecated": true}"#.data(using: .utf8)! let allowedValueArrayData = #"{"type": "array", "items": { "type": "boolean" }, "enum": [[false]]}"#.data(using: .utf8)! + let discriminatorArrayData = #"{"type": "array", "discriminator": {"propertyName": "hello"}}"#.data(using: .utf8)! - let array = try! testDecoder.decode(JSONSchema.self, from: arrayData) - let nullableArray = try! testDecoder.decode(JSONSchema.self, from: nullableArrayData) - let readOnlyArray = try! testDecoder.decode(JSONSchema.self, from: readOnlyArrayData) - let writeOnlyArray = try! testDecoder.decode(JSONSchema.self, from: writeOnlyArrayData) - let deprecatedArray = try! testDecoder.decode(JSONSchema.self, from: deprecatedArrayData) - let allowedValueArray = try! testDecoder.decode(JSONSchema.self, from: allowedValueArrayData) + let array = try testDecoder.decode(JSONSchema.self, from: arrayData) + let nullableArray = try testDecoder.decode(JSONSchema.self, from: nullableArrayData) + let readOnlyArray = try testDecoder.decode(JSONSchema.self, from: readOnlyArrayData) + let writeOnlyArray = try testDecoder.decode(JSONSchema.self, from: writeOnlyArrayData) + let deprecatedArray = try testDecoder.decode(JSONSchema.self, from: deprecatedArrayData) + let allowedValueArray = try testDecoder.decode(JSONSchema.self, from: allowedValueArrayData) + let discriminatorArray = try testDecoder.decode(JSONSchema.self, from: discriminatorArrayData) XCTAssertEqual(array, JSONSchema.array(.init(format: .generic, required: false), .init())) XCTAssertEqual(nullableArray, JSONSchema.array(.init(format: .generic, required: false, nullable: true), .init())) @@ -2131,6 +2241,7 @@ extension SchemaObjectTests { XCTAssertEqual(writeOnlyArray, JSONSchema.array(.init(format: .generic, required: false, permissions: .writeOnly), .init())) XCTAssertEqual(deprecatedArray, JSONSchema.array(.init(format: .generic, required: false, deprecated: true), .init())) XCTAssertEqual(allowedValueArray.allowedValues?[0].value as! [Bool], [false]) + XCTAssertEqual(discriminatorArray, JSONSchema.array(required: false, discriminator: .init(propertyName: "hello"))) guard case let .array(_, contextB) = allowedValueArray else { XCTFail("expected array") @@ -2418,32 +2529,43 @@ extension SchemaObjectTests { let deprecatedNumber = JSONSchema.number(.init(format: .unspecified, required: true, deprecated: true), .init()) let allowedValueNumber = JSONSchema.number(.init(format: .unspecified, required: true), .init()) .with(allowedValues: [10.5]) - - testAllSharedSimpleContextEncoding(typeName: "number", - requiredEntity: requiredNumber, - optionalEntity: optionalNumber, - nullableEntity: nullableNumber, - readOnlyEntity: readOnlyNumber, - writeOnlyEntity: writeOnlyNumber, - deprecatedEntity: deprecatedNumber, - allowedValues: (entity: allowedValueNumber, - value: "10.5")) + let discriminatorNumber = JSONSchema.number(.init(format: .unspecified, required: true, discriminator: .init(propertyName: "hello")), .init()) + + testAllSharedSimpleContextEncoding( + typeName: "number", + requiredEntity: requiredNumber, + optionalEntity: optionalNumber, + nullableEntity: nullableNumber, + readOnlyEntity: readOnlyNumber, + writeOnlyEntity: writeOnlyNumber, + deprecatedEntity: deprecatedNumber, + allowedValues: ( + entity: allowedValueNumber, + value: "10.5" + ), + discriminator: ( + entity: discriminatorNumber, + name: "hello" + ) + ) } - func test_decodeNumber() { + func test_decodeNumber() throws { let numberData = #"{"type": "number"}"#.data(using: .utf8)! let nullableNumberData = #"{"type": "number", "nullable": true}"#.data(using: .utf8)! let readOnlyNumberData = #"{"type": "number", "readOnly": true}"#.data(using: .utf8)! let writeOnlyNumberData = #"{"type": "number", "writeOnly": true}"#.data(using: .utf8)! let deprecatedNumberData = #"{"type": "number", "deprecated": true}"#.data(using: .utf8)! let allowedValueNumberData = #"{"type": "number", "enum": [1, 2]}"#.data(using: .utf8)! + let discriminatorNumberData = #"{"type": "number", "discriminator": {"propertyName": "hello"}}"#.data(using: .utf8)! - let number = try! testDecoder.decode(JSONSchema.self, from: numberData) - let nullableNumber = try! testDecoder.decode(JSONSchema.self, from: nullableNumberData) - let readOnlyNumber = try! testDecoder.decode(JSONSchema.self, from: readOnlyNumberData) - let writeOnlyNumber = try! testDecoder.decode(JSONSchema.self, from: writeOnlyNumberData) - let deprecatedNumber = try! testDecoder.decode(JSONSchema.self, from: deprecatedNumberData) - let allowedValueNumber = try! testDecoder.decode(JSONSchema.self, from: allowedValueNumberData) + let number = try testDecoder.decode(JSONSchema.self, from: numberData) + let nullableNumber = try testDecoder.decode(JSONSchema.self, from: nullableNumberData) + let readOnlyNumber = try testDecoder.decode(JSONSchema.self, from: readOnlyNumberData) + let writeOnlyNumber = try testDecoder.decode(JSONSchema.self, from: writeOnlyNumberData) + let deprecatedNumber = try testDecoder.decode(JSONSchema.self, from: deprecatedNumberData) + let allowedValueNumber = try testDecoder.decode(JSONSchema.self, from: allowedValueNumberData) + let discriminatorNumber = try testDecoder.decode(JSONSchema.self, from: discriminatorNumberData) XCTAssertEqual(number, JSONSchema.number(.init(format: .generic, required: false), .init())) XCTAssertEqual(nullableNumber, JSONSchema.number(.init(format: .generic, required: false, nullable: true), .init())) @@ -2451,6 +2573,7 @@ extension SchemaObjectTests { XCTAssertEqual(writeOnlyNumber, JSONSchema.number(.init(format: .generic, required: false, permissions: .writeOnly), .init())) XCTAssertEqual(deprecatedNumber, JSONSchema.number(.init(format: .generic, required: false, deprecated: true), .init())) XCTAssertEqual(allowedValueNumber, JSONSchema.number(.init(format: .generic, required: false, allowedValues: [1, 2]), .init())) + XCTAssertEqual(discriminatorNumber, JSONSchema.number(required: false, discriminator: .init(propertyName: "hello"))) } func test_decodeNumberWithTypeInferred() throws { @@ -2808,32 +2931,43 @@ extension SchemaObjectTests { let deprecatedInteger = JSONSchema.integer(.init(format: .unspecified, required: true, deprecated: true), .init()) let allowedValueInteger = JSONSchema.integer(.init(format: .unspecified, required: true), .init()) .with(allowedValues: [10]) - - testAllSharedSimpleContextEncoding(typeName: "integer", - requiredEntity: requiredInteger, - optionalEntity: optionalInteger, - nullableEntity: nullableInteger, - readOnlyEntity: readOnlyInteger, - writeOnlyEntity: writeOnlyInteger, - deprecatedEntity: deprecatedInteger, - allowedValues: (entity: allowedValueInteger, - value: "10")) + let discriminatorInteger = JSONSchema.integer(.init(format: .unspecified, required: true, discriminator: .init(propertyName: "hello")), .init()) + + testAllSharedSimpleContextEncoding( + typeName: "integer", + requiredEntity: requiredInteger, + optionalEntity: optionalInteger, + nullableEntity: nullableInteger, + readOnlyEntity: readOnlyInteger, + writeOnlyEntity: writeOnlyInteger, + deprecatedEntity: deprecatedInteger, + allowedValues: ( + entity: allowedValueInteger, + value: "10" + ), + discriminator: ( + entity: discriminatorInteger, + name: "hello" + ) + ) } - func test_decodeInteger() { + func test_decodeInteger() throws { let integerData = #"{"type": "integer"}"#.data(using: .utf8)! let nullableIntegerData = #"{"type": "integer", "nullable": true}"#.data(using: .utf8)! let readOnlyIntegerData = #"{"type": "integer", "readOnly": true}"#.data(using: .utf8)! let writeOnlyIntegerData = #"{"type": "integer", "writeOnly": true}"#.data(using: .utf8)! let deprecatedIntegerData = #"{"type": "integer", "deprecated": true}"#.data(using: .utf8)! let allowedValueIntegerData = #"{"type": "integer", "enum": [1, 2]}"#.data(using: .utf8)! + let discriminatorIntegerData = #"{"type": "integer", "discriminator": {"propertyName": "hello"}}"#.data(using: .utf8)! - let integer = try! testDecoder.decode(JSONSchema.self, from: integerData) - let nullableInteger = try! testDecoder.decode(JSONSchema.self, from: nullableIntegerData) - let readOnlyInteger = try! testDecoder.decode(JSONSchema.self, from: readOnlyIntegerData) - let writeOnlyInteger = try! testDecoder.decode(JSONSchema.self, from: writeOnlyIntegerData) - let deprecatedInteger = try! testDecoder.decode(JSONSchema.self, from: deprecatedIntegerData) - let allowedValueInteger = try! testDecoder.decode(JSONSchema.self, from: allowedValueIntegerData) + let integer = try testDecoder.decode(JSONSchema.self, from: integerData) + let nullableInteger = try testDecoder.decode(JSONSchema.self, from: nullableIntegerData) + let readOnlyInteger = try testDecoder.decode(JSONSchema.self, from: readOnlyIntegerData) + let writeOnlyInteger = try testDecoder.decode(JSONSchema.self, from: writeOnlyIntegerData) + let deprecatedInteger = try testDecoder.decode(JSONSchema.self, from: deprecatedIntegerData) + let allowedValueInteger = try testDecoder.decode(JSONSchema.self, from: allowedValueIntegerData) + let discriminatorInteger = try testDecoder.decode(JSONSchema.self, from: discriminatorIntegerData) XCTAssertEqual(integer, JSONSchema.integer(.init(format: .generic, required: false), .init())) XCTAssertEqual(nullableInteger, JSONSchema.integer(.init(format: .generic, required: false, nullable: true), .init())) @@ -2841,6 +2975,7 @@ extension SchemaObjectTests { XCTAssertEqual(writeOnlyInteger, JSONSchema.integer(.init(format: .generic, required: false, permissions: .writeOnly), .init())) XCTAssertEqual(deprecatedInteger, JSONSchema.integer(.init(format: .generic, required: false, deprecated: true), .init())) XCTAssertEqual(allowedValueInteger, JSONSchema.integer(.init(format: .generic, required: false, allowedValues: [1, 2]), .init())) + XCTAssertEqual(discriminatorInteger, JSONSchema.integer(required: false, discriminator: .init(propertyName: "hello"))) } func test_encode32bitInteger() { @@ -3188,32 +3323,43 @@ extension SchemaObjectTests { let deprecatedString = JSONSchema.string(.init(format: .unspecified, required: true, deprecated: true), .init()) let allowedValueString = JSONSchema.string(.init(format: .unspecified, required: true), .init()) .with(allowedValues: ["hello"]) - - testAllSharedSimpleContextEncoding(typeName: "string", - requiredEntity: requiredString, - optionalEntity: optionalString, - nullableEntity: nullableString, - readOnlyEntity: readOnlyString, - writeOnlyEntity: writeOnlyString, - deprecatedEntity: deprecatedString, - allowedValues: (entity: allowedValueString, - value: "\"hello\"")) + let discriminatorString = JSONSchema.string(.init(format: .unspecified, required: true, discriminator: .init(propertyName: "hello")), .init()) + + testAllSharedSimpleContextEncoding( + typeName: "string", + requiredEntity: requiredString, + optionalEntity: optionalString, + nullableEntity: nullableString, + readOnlyEntity: readOnlyString, + writeOnlyEntity: writeOnlyString, + deprecatedEntity: deprecatedString, + allowedValues: ( + entity: allowedValueString, + value: "\"hello\"" + ), + discriminator: ( + entity: discriminatorString, + name: "hello" + ) + ) } - func test_decodeString() { + func test_decodeString() throws { let stringData = #"{"type": "string"}"#.data(using: .utf8)! let nullableStringData = #"{"type": "string", "nullable": true}"#.data(using: .utf8)! let readOnlyStringData = #"{"type": "string", "readOnly": true}"#.data(using: .utf8)! let writeOnlyStringData = #"{"type": "string", "writeOnly": true}"#.data(using: .utf8)! let deprecatedStringData = #"{"type": "string", "deprecated": true}"#.data(using: .utf8)! let allowedValueStringData = #"{"type": "string", "enum": ["hello"]}"#.data(using: .utf8)! + let discriminatorStringData = #"{"type": "string", "discriminator": {"propertyName": "hello"}}"#.data(using: .utf8)! - let string = try! testDecoder.decode(JSONSchema.self, from: stringData) - let nullableString = try! testDecoder.decode(JSONSchema.self, from: nullableStringData) - let readOnlyString = try! testDecoder.decode(JSONSchema.self, from: readOnlyStringData) - let writeOnlyString = try! testDecoder.decode(JSONSchema.self, from: writeOnlyStringData) - let deprecatedString = try! testDecoder.decode(JSONSchema.self, from: deprecatedStringData) - let allowedValueString = try! testDecoder.decode(JSONSchema.self, from: allowedValueStringData) + let string = try testDecoder.decode(JSONSchema.self, from: stringData) + let nullableString = try testDecoder.decode(JSONSchema.self, from: nullableStringData) + let readOnlyString = try testDecoder.decode(JSONSchema.self, from: readOnlyStringData) + let writeOnlyString = try testDecoder.decode(JSONSchema.self, from: writeOnlyStringData) + let deprecatedString = try testDecoder.decode(JSONSchema.self, from: deprecatedStringData) + let allowedValueString = try testDecoder.decode(JSONSchema.self, from: allowedValueStringData) + let discriminatorString = try testDecoder.decode(JSONSchema.self, from: discriminatorStringData) XCTAssertEqual(string, JSONSchema.string(.init(format: .generic, required: false), .init())) XCTAssertEqual(nullableString, JSONSchema.string(.init(format: .generic, required: false, nullable: true), .init())) @@ -3221,6 +3367,7 @@ extension SchemaObjectTests { XCTAssertEqual(writeOnlyString, JSONSchema.string(.init(format: .generic, required: false, permissions: .writeOnly), .init())) XCTAssertEqual(deprecatedString, JSONSchema.string(.init(format: .generic, required: false, deprecated: true), .init())) XCTAssertEqual(allowedValueString, JSONSchema.string(.init(format: .generic, required: false, allowedValues: ["hello"]), .init())) + XCTAssertEqual(discriminatorString, JSONSchema.string(required: false, discriminator: .init(propertyName: "hello"))) } func test_decodeStringWithTypeInferred() throws { @@ -3574,6 +3721,13 @@ extension SchemaObjectTests { .object(.init(), .init(properties: ["hello": .string(.init(format: .generic, required: false), .init())])), .object(.init(), .init()) ]) + let allOfWithDisciminator = JSONSchema.all( + of: [ + .object(.init(), .init(properties: ["hello": .string(.init(format: .generic, required: false), .init())])), + .object(.init(), .init()) + ], + discriminator: .init(propertyName: "hello") + ) testEncodingPropertyLines(entity: allOf, propertyLines: [ "\"allOf\" : [", @@ -3590,9 +3744,28 @@ extension SchemaObjectTests { " }", "]" ]) + + testEncodingPropertyLines(entity: allOfWithDisciminator, propertyLines: [ + "\"allOf\" : [", + " {", + " \"properties\" : {", + " \"hello\" : {", + " \"type\" : \"string\"", + " }", + " },", + " \"type\" : \"object\"", + " },", + " {", + " \"type\" : \"object\"", + " }", + "],", + "\"discriminator\" : {", + " \"propertyName\" : \"hello\"", + "}" + ]) } - func test_decodeAll() { + func test_decodeAll() throws { let allData = """ { "allOf": [ @@ -3602,43 +3775,110 @@ extension SchemaObjectTests { } """.data(using: .utf8)! - let all = try! testDecoder.decode(JSONSchema.self, from: allData) + let allWithDiscriminatorData = """ + { + "allOf": [ + { "type": "object" }, + { "properties": { "hello": { "type": "boolean" } } } + ], + "discriminator": { "propertyName": "hello" } + } + """.data(using: .utf8)! - XCTAssertEqual(all, JSONSchema.all(of: [ - .object(.init(), .init()), - .object(.init(), .init(properties: ["hello": .boolean(.init(format: .generic, required: false))])) - ])) + let all = try testDecoder.decode(JSONSchema.self, from: allData) + let allWithDiscriminator = try testDecoder.decode(JSONSchema.self, from: allWithDiscriminatorData) + + XCTAssertEqual( + all, + JSONSchema.all( + of: [ + .object(.init(), .init()), + .object(.init(), .init(properties: ["hello": .boolean(.init(format: .generic, required: false))])) + ] + ) + ) + + XCTAssertEqual( + allWithDiscriminator, + JSONSchema.all( + of: [ + .object(.init(), .init()), + .object(.init(), .init(properties: ["hello": .boolean(.init(format: .generic, required: false))])) + ], + discriminator: .init(propertyName: "hello") + ) + ) } func test_encodeOne() { - let oneOf = JSONSchema.one(of: [ - .object(.init(format: .unspecified, required: true), .init(properties: ["hello": .string(.init(format: .generic, required: false), .init())])), - .object(.init(format: .unspecified, required: true), .init(properties: ["world": .boolean(.init(format: .generic, required: false))])) - ]) + let oneOf = JSONSchema.one( + of: [ + .object(.init(format: .unspecified, required: true), .init(properties: ["hello": .string(.init(format: .generic, required: false), .init())])), + .object(.init(format: .unspecified, required: true), .init(properties: ["world": .boolean(.init(format: .generic, required: false))])) + ] + ) - testEncodingPropertyLines(entity: oneOf, propertyLines: [ - "\"oneOf\" : [", - " {", - " \"properties\" : {", - " \"hello\" : {", - " \"type\" : \"string\"", - " }", - " },", - " \"type\" : \"object\"", - " },", - " {", - " \"properties\" : {", - " \"world\" : {", - " \"type\" : \"boolean\"", - " }", - " },", - " \"type\" : \"object\"", - " }", - "]" - ]) + let oneOfWithDiscriminator = JSONSchema.one( + of: [ + .object(.init(format: .unspecified, required: true), .init(properties: ["hello": .string(.init(format: .generic, required: false), .init())])), + .object(.init(format: .unspecified, required: true), .init(properties: ["world": .boolean(.init(format: .generic, required: false))])) + ], + discriminator: .init(propertyName: "hello") + ) + + testEncodingPropertyLines( + entity: oneOf, + propertyLines: [ + "\"oneOf\" : [", + " {", + " \"properties\" : {", + " \"hello\" : {", + " \"type\" : \"string\"", + " }", + " },", + " \"type\" : \"object\"", + " },", + " {", + " \"properties\" : {", + " \"world\" : {", + " \"type\" : \"boolean\"", + " }", + " },", + " \"type\" : \"object\"", + " }", + "]" + ] + ) + + testEncodingPropertyLines( + entity: oneOfWithDiscriminator, + propertyLines: [ + "\"discriminator\" : {", + " \"propertyName\" : \"hello\"", + "},", + "\"oneOf\" : [", + " {", + " \"properties\" : {", + " \"hello\" : {", + " \"type\" : \"string\"", + " }", + " },", + " \"type\" : \"object\"", + " },", + " {", + " \"properties\" : {", + " \"world\" : {", + " \"type\" : \"boolean\"", + " }", + " },", + " \"type\" : \"object\"", + " }", + "]" + ] + ) } - func test_decodeOne() { + func test_decodeOne() throws { let oneData = """ { "oneOf": [ @@ -3648,43 +3888,110 @@ extension SchemaObjectTests { } """.data(using: .utf8)! - let one = try! testDecoder.decode(JSONSchema.self, from: oneData) + let oneWithDiscriminatorData = """ + { + "oneOf": [ + { "type": "object" }, + { "type": "object", "properties": { "hello": { "type": "boolean" } } } + ], + "discriminator": { "propertyName": "hello" } + } + """.data(using: .utf8)! - XCTAssertEqual(one, JSONSchema.one(of: [ - .object(.init(format: .generic, required: false), .init(properties: [:])), - .object(.init(format: .generic, required: false), .init(properties: ["hello": .boolean(.init(format: .generic, required: false))])) - ])) + let one = try testDecoder.decode(JSONSchema.self, from: oneData) + let oneWithDiscriminator = try testDecoder.decode(JSONSchema.self, from: oneWithDiscriminatorData) + + XCTAssertEqual( + one, + JSONSchema.one( + of: [ + .object(.init(format: .generic, required: false), .init(properties: [:])), + .object(.init(format: .generic, required: false), .init(properties: ["hello": .boolean(.init(format: .generic, required: false))])) + ] + ) + ) + + XCTAssertEqual( + oneWithDiscriminator, + JSONSchema.one( + of: [ + .object(.init(format: .generic, required: false), .init(properties: [:])), + .object(.init(format: .generic, required: false), .init(properties: ["hello": .boolean(.init(format: .generic, required: false))])) + ], + discriminator: .init(propertyName: "hello") + ) + ) } func test_encodeAny() { - let anyOf = JSONSchema.any(of: [ - .object(.init(format: .unspecified, required: true), .init(properties: ["hello": .string(.init(format: .generic, required: false), .init())])), - .object(.init(format: .unspecified, required: true), .init(properties: ["world": .boolean(.init(format: .generic, required: false))])) - ]) + let anyOf = JSONSchema.any( + of: [ + .object(.init(format: .unspecified, required: true), .init(properties: ["hello": .string(.init(format: .generic, required: false), .init())])), + .object(.init(format: .unspecified, required: true), .init(properties: ["world": .boolean(.init(format: .generic, required: false))])) + ] + ) - testEncodingPropertyLines(entity: anyOf, propertyLines: [ - "\"anyOf\" : [", - " {", - " \"properties\" : {", - " \"hello\" : {", - " \"type\" : \"string\"", - " }", - " },", - " \"type\" : \"object\"", - " },", - " {", - " \"properties\" : {", - " \"world\" : {", - " \"type\" : \"boolean\"", - " }", - " },", - " \"type\" : \"object\"", - " }", - "]" - ]) + let anyOfWithDiscriminator = JSONSchema.any( + of: [ + .object(.init(format: .unspecified, required: true), .init(properties: ["hello": .string(.init(format: .generic, required: false), .init())])), + .object(.init(format: .unspecified, required: true), .init(properties: ["world": .boolean(.init(format: .generic, required: false))])) + ], + discriminator: .init(propertyName: "hello") + ) + + testEncodingPropertyLines( + entity: anyOf, + propertyLines: [ + "\"anyOf\" : [", + " {", + " \"properties\" : {", + " \"hello\" : {", + " \"type\" : \"string\"", + " }", + " },", + " \"type\" : \"object\"", + " },", + " {", + " \"properties\" : {", + " \"world\" : {", + " \"type\" : \"boolean\"", + " }", + " },", + " \"type\" : \"object\"", + " }", + "]" + ] + ) + + testEncodingPropertyLines( + entity: anyOfWithDiscriminator, + propertyLines: [ + "\"anyOf\" : [", + " {", + " \"properties\" : {", + " \"hello\" : {", + " \"type\" : \"string\"", + " }", + " },", + " \"type\" : \"object\"", + " },", + " {", + " \"properties\" : {", + " \"world\" : {", + " \"type\" : \"boolean\"", + " }", + " },", + " \"type\" : \"object\"", + " }", + "],", + "\"discriminator\" : {", + " \"propertyName\" : \"hello\"", + "}" + ] + ) } - func test_decodeAny() { + func test_decodeAny() throws { let anyData = """ { "anyOf": [ @@ -3694,12 +4001,39 @@ extension SchemaObjectTests { } """.data(using: .utf8)! - let any = try! testDecoder.decode(JSONSchema.self, from: anyData) + let anyWithDiscriminatorData = """ + { + "anyOf": [ + { "type": "boolean" }, + { "type": "object" } + ], + "discriminator": { "propertyName": "hello" } + } + """.data(using: .utf8)! + + let any = try testDecoder.decode(JSONSchema.self, from: anyData) + let anyWithDiscriminator = try testDecoder.decode(JSONSchema.self, from: anyWithDiscriminatorData) - XCTAssertEqual(any, JSONSchema.any(of: [ - .boolean(.init(format: .generic, required: false)), - .object(.init(format: .generic, required: false), .init(properties: [:])) - ])) + XCTAssertEqual( + any, + JSONSchema.any( + of: [ + .boolean(.init(format: .generic, required: false)), + .object(.init(format: .generic, required: false), .init(properties: [:])) + ] + ) + ) + + XCTAssertEqual( + anyWithDiscriminator, + JSONSchema.any( + of: [ + .boolean(.init(format: .generic, required: false)), + .object(.init(format: .generic, required: false), .init(properties: [:])) + ], + discriminator: .init(propertyName: "hello") + ) + ) } func test_encodeNot() { @@ -3778,46 +4112,70 @@ private func testAllSharedSimpleContextEncoding( readOnlyEntity: T, writeOnlyEntity: T, deprecatedEntity: T, - allowedValues: (entity: T, value: String) + allowedValues: (entity: T, value: String), + discriminator: (entity: T, name: String) ) { - testEncodingPropertyLines(entity: requiredEntity, - propertyLines: ["\"type\" : \"\(typeName)\""]) - - testEncodingPropertyLines(entity: optionalEntity, - propertyLines: ["\"type\" : \"\(typeName)\""]) - - testEncodingPropertyLines(entity: nullableEntity, - propertyLines: [ - "\"nullable\" : true,", - "\"type\" : \"\(typeName)\"" - ]) - - testEncodingPropertyLines(entity: readOnlyEntity, - propertyLines: [ - "\"readOnly\" : true,", - "\"type\" : \"\(typeName)\"" - ]) - - testEncodingPropertyLines(entity: writeOnlyEntity, - propertyLines: [ - "\"type\" : \"\(typeName)\",", - "\"writeOnly\" : true" - ]) - - testEncodingPropertyLines(entity: deprecatedEntity, - propertyLines: [ - "\"deprecated\" : true,", - "\"type\" : \"\(typeName)\"" - ]) - - testEncodingPropertyLines(entity: allowedValues.entity, - propertyLines: [ - "\"enum\" : [", - " \(allowedValues.value)", - "],", - "\"type\" : \"\(typeName)\"" - - ]) + testEncodingPropertyLines( + entity: requiredEntity, + propertyLines: ["\"type\" : \"\(typeName)\""] + ) + + testEncodingPropertyLines( + entity: optionalEntity, + propertyLines: ["\"type\" : \"\(typeName)\""] + ) + + testEncodingPropertyLines( + entity: nullableEntity, + propertyLines: [ + "\"nullable\" : true,", + "\"type\" : \"\(typeName)\"" + ] + ) + + testEncodingPropertyLines( + entity: readOnlyEntity, + propertyLines: [ + "\"readOnly\" : true,", + "\"type\" : \"\(typeName)\"" + ] + ) + + testEncodingPropertyLines( + entity: writeOnlyEntity, + propertyLines: [ + "\"type\" : \"\(typeName)\",", + "\"writeOnly\" : true" + ] + ) + + testEncodingPropertyLines( + entity: deprecatedEntity, + propertyLines: [ + "\"deprecated\" : true,", + "\"type\" : \"\(typeName)\"" + ] + ) + + testEncodingPropertyLines( + entity: allowedValues.entity, + propertyLines: [ + "\"enum\" : [", + " \(allowedValues.value)", + "],", + "\"type\" : \"\(typeName)\"" + ] + ) + + testEncodingPropertyLines( + entity: discriminator.entity, + propertyLines: [ + "\"discriminator\" : {", + " \"propertyName\" : \"\(discriminator.name)\"", + "},", + "\"type\" : \"\(typeName)\"" + ] + ) } private func testAllSharedFormattedContextEncoding( diff --git a/Tests/OpenAPIKitTests/SecuritySchemeTests.swift b/Tests/OpenAPIKitTests/SecuritySchemeTests.swift index 52a6a633c..0cbd6f6db 100644 --- a/Tests/OpenAPIKitTests/SecuritySchemeTests.swift +++ b/Tests/OpenAPIKitTests/SecuritySchemeTests.swift @@ -33,6 +33,28 @@ final class SecuritySchemeTests: XCTestCase { OpenAPI.SecurityScheme.openIdConnect(url: URL(string: "https://google.com")!, description: "description") ) } + + func test_names() { + XCTAssertEqual( + OpenAPI.SecurityScheme(type: .apiKey(name: "hi", location: .header), description: "description").type.name, + .apiKey + ) + + XCTAssertEqual( + OpenAPI.SecurityScheme(type: .http(scheme: "hi", bearerFormat: "there"), description: "description").type.name, + .http + ) + + XCTAssertEqual( + OpenAPI.SecurityScheme(type: .oauth2(flows: .init()), description: "description").type.name, + .oauth2 + ) + + XCTAssertEqual( + OpenAPI.SecurityScheme(type: .openIdConnect(openIdConnectUrl: URL(string: "https://google.com")!), description: "description").type.name, + .openIdConnect + ) + } } // MARK: - Codable diff --git a/Tests/OpenAPIKitTests/VendorExtendableTests.swift b/Tests/OpenAPIKitTests/VendorExtendableTests.swift index aaedf8398..b061c0a99 100644 --- a/Tests/OpenAPIKitTests/VendorExtendableTests.swift +++ b/Tests/OpenAPIKitTests/VendorExtendableTests.swift @@ -61,7 +61,8 @@ final class VendorExtendableTests: XCTestCase { """.data(using: .utf8)! XCTAssertThrowsError(try JSONDecoder().decode(TestStruct.self, from: data)) { error in - XCTAssert(error as? VendorExtensionDecodingError == VendorExtensionDecodingError.selfIsArrayNotDict) + XCTAssertEqual(error as? VendorExtensionDecodingError, VendorExtensionDecodingError.selfIsArrayNotDict) + XCTAssertEqual(String(describing: error), "Tried to get vendor extensions on a list. Vendor extensions are necessarily keyed and therefore can only be retrieved from hashes.") } }