From 30775be3eb8b1af9c07f225ed48e4e3ca8f1dfb4 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Sat, 14 Mar 2020 21:37:34 -0700 Subject: [PATCH 1/4] audit decoding errors folder for unnecessary abbreviations. --- .../DecodingErrorExtensions.swift | 32 ++++++++--------- .../DocumentDecodingError.swift | 12 +++---- .../Decoding Errors/InconsistencyError.swift | 3 ++ .../OpenAPIDecodingErrors.swift | 25 ++++++------- .../OperationDecodingError.swift | 25 +++++++++---- .../Decoding Errors/PathDecodingError.swift | 20 ++++++----- ...yDecodeNoTypesMatchedErrorExtensions.swift | 36 +++++++++---------- .../RequestDecodingError.swift | 24 ++++++++----- .../ResponseDecodingError.swift | 27 +++++++++----- 9 files changed, 118 insertions(+), 86 deletions(-) diff --git a/Sources/OpenAPIKit/Decoding Errors/DecodingErrorExtensions.swift b/Sources/OpenAPIKit/Decoding Errors/DecodingErrorExtensions.swift index 5c51906c0..9f721b721 100644 --- a/Sources/OpenAPIKit/Decoding Errors/DecodingErrorExtensions.swift +++ b/Sources/OpenAPIKit/Decoding Errors/DecodingErrorExtensions.swift @@ -13,8 +13,8 @@ internal extension Swift.DecodingError { switch self { case .keyNotFound(let key, _): return "\(key.stringValue)" - case .typeMismatch(_, let ctx), .valueNotFound(_, let ctx), .dataCorrupted(let ctx): - return ctx.codingPath.last?.stringValue + case .typeMismatch(_, let context), .valueNotFound(_, let context), .dataCorrupted(let context): + return context.codingPath.last?.stringValue @unknown default: return nil } @@ -25,8 +25,8 @@ internal extension Swift.DecodingError { var codingPathWithoutSubject: [CodingKey] { switch self { - case .keyNotFound(_, let ctx): - return ctx.codingPath + case .keyNotFound(_, let context): + return context.codingPath case .typeMismatch(_, let ctx), .valueNotFound(_, let ctx), .dataCorrupted(let ctx): return ctx.codingPath.count > 0 ? ctx.codingPath.dropLast() : [] @unknown default: @@ -36,8 +36,8 @@ internal extension Swift.DecodingError { var codingPath: [CodingKey] { switch self { - case .keyNotFound(_, let ctx), .typeMismatch(_, let ctx), .valueNotFound(_, let ctx), .dataCorrupted(let ctx): - return ctx.codingPath + case .keyNotFound(_, let context), .typeMismatch(_, let context), .valueNotFound(_, let context), .dataCorrupted(let context): + return context.codingPath @unknown default: return [] } @@ -64,8 +64,8 @@ internal extension Swift.DecodingError { var underlyingError: Swift.Error? { switch self { - case .typeMismatch(_, let ctx), .valueNotFound(_, let ctx), .keyNotFound(_, let ctx), .dataCorrupted(let ctx): - return ctx.underlyingError + case .typeMismatch(_, let context), .valueNotFound(_, let context), .keyNotFound(_, let context), .dataCorrupted(let context): + return context.underlyingError @unknown default: return nil } @@ -73,14 +73,14 @@ internal extension Swift.DecodingError { func replacingPath(with codingPath: [CodingKey]) -> Self { switch self { - case .typeMismatch(let type, let ctx): - return .typeMismatch(type, ctx.replacingPath(with: codingPath)) - case .valueNotFound(let type, let ctx): - return .valueNotFound(type, ctx.replacingPath(with: codingPath)) - case .keyNotFound(let key, let ctx): - return .keyNotFound(key, ctx.replacingPath(with: codingPath)) - case .dataCorrupted(let ctx): - return .dataCorrupted(ctx.replacingPath(with: codingPath)) + case .typeMismatch(let type, let context): + return .typeMismatch(type, context.replacingPath(with: codingPath)) + case .valueNotFound(let type, let context): + return .valueNotFound(type, context.replacingPath(with: codingPath)) + case .keyNotFound(let key, let context): + return .keyNotFound(key, context.replacingPath(with: codingPath)) + case .dataCorrupted(let context): + return .dataCorrupted(context.replacingPath(with: codingPath)) @unknown default: return .dataCorrupted(.init(codingPath: codingPath, debugDescription: "unknown error")) } diff --git a/Sources/OpenAPIKit/Decoding Errors/DocumentDecodingError.swift b/Sources/OpenAPIKit/Decoding Errors/DocumentDecodingError.swift index 5376ee8f7..be9f7e8db 100644 --- a/Sources/OpenAPIKit/Decoding Errors/DocumentDecodingError.swift +++ b/Sources/OpenAPIKit/Decoding Errors/DocumentDecodingError.swift @@ -15,7 +15,7 @@ extension OpenAPI.Error.Decoding { public enum Context { case path(Path) case inconsistency(InconsistencyError) - case generic(Swift.DecodingError) + case other(Swift.DecodingError) } } } @@ -26,7 +26,7 @@ extension OpenAPI.Error.Decoding.Document { case .path(let pathError): return pathError.subjectName - case .generic(let decodingError): + case .other(let decodingError): return decodingError.subjectName case .inconsistency(let error): @@ -38,7 +38,7 @@ extension OpenAPI.Error.Decoding.Document { switch context { case .path(let pathError): return pathError.contextString - case .generic, .inconsistency: + case .other, .inconsistency: return relativeCodingPathString.isEmpty ? "in the root Document object" : "in Document\(relativeCodingPathString)" @@ -49,7 +49,7 @@ extension OpenAPI.Error.Decoding.Document { switch context { case .path(let pathError): return pathError.errorCategory - case .generic(let error): + case .other(let error): return error.errorCategory case .inconsistency(let error): return .inconsistency(details: error.details) @@ -58,7 +58,7 @@ extension OpenAPI.Error.Decoding.Document { internal var relativeCodingPathString: String { switch context { - case .generic(let decodingError): + case .other(let decodingError): return decodingError.relativeCodingPathString case .inconsistency: return "" @@ -68,7 +68,7 @@ extension OpenAPI.Error.Decoding.Document { } internal init(_ error: DecodingError) { - context = .generic(error) + context = .other(error) codingPath = error.codingPath } diff --git a/Sources/OpenAPIKit/Decoding Errors/InconsistencyError.swift b/Sources/OpenAPIKit/Decoding Errors/InconsistencyError.swift index 5a04c600d..07cac0338 100644 --- a/Sources/OpenAPIKit/Decoding Errors/InconsistencyError.swift +++ b/Sources/OpenAPIKit/Decoding Errors/InconsistencyError.swift @@ -7,6 +7,9 @@ import Foundation +/// This error type is thrown when a problem _during_ decoding but the +/// problem is not inherent to the types or structures but rather specific +/// to the OpenAPI specification rules. public struct InconsistencyError: Swift.Error, OpenAPIError { public let subjectName: String public let details: String diff --git a/Sources/OpenAPIKit/Decoding Errors/OpenAPIDecodingErrors.swift b/Sources/OpenAPIKit/Decoding Errors/OpenAPIDecodingErrors.swift index 42cd106dd..c25a0eae9 100644 --- a/Sources/OpenAPIKit/Decoding Errors/OpenAPIDecodingErrors.swift +++ b/Sources/OpenAPIKit/Decoding Errors/OpenAPIDecodingErrors.swift @@ -9,6 +9,7 @@ import Foundation import Poly extension OpenAPI.Error { + // Just creating a namespace public enum Decoding {} } @@ -41,8 +42,8 @@ public extension OpenAPIError { var localizedDescription: String { let subjectString: String = { switch errorCategory { - case .missing(let kv): - switch kv { + case .missing(let keyOrValue): + switch keyOrValue { case .key: return "Expected to find `\(subjectName)` key" case .value: @@ -54,8 +55,8 @@ public extension OpenAPIError { } else { return "Expected `\(subjectName)` value" } - case .typeMismatch2(possibleTypeName1: let t1, possibleTypeName2: let t2, details: _): - return "Found neither a \(t1) nor a \(t2)" + case .typeMismatch2(possibleTypeName1: let type1, possibleTypeName2: let type2, details: _): + return "Found neither a \(type1) nor a \(type2)" case .dataCorrupted: return "Could not parse `\(subjectName)`" case .inconsistency(details: _): @@ -71,8 +72,8 @@ public extension OpenAPIError { return " to be parsable as \(typeName) but it was not" case .typeMismatch2(possibleTypeName1: _, possibleTypeName2: _, details: let details): return ". \(details)" - case .missing(let kv): - switch kv { + case .missing(let keyOrValue): + switch keyOrValue { case .key: return " but it is missing" case .value: @@ -92,14 +93,14 @@ public extension OpenAPIError { internal extension Swift.Array where Element == CodingKey { var stringValue: String { return self.map { key in - if let intVal = key.intValue { - return "[\(intVal)]" + if let intValue = key.intValue { + return "[\(intValue)]" } - let strVal = key.stringValue - if strVal.contains("/") { - return "['\(strVal)']" + let strValue = key.stringValue + if strValue.contains("/") { + return "['\(strValue)']" } - return ".\(strVal)" + return ".\(strValue)" }.joined() } diff --git a/Sources/OpenAPIKit/Decoding Errors/OperationDecodingError.swift b/Sources/OpenAPIKit/Decoding Errors/OperationDecodingError.swift index 7626a87e9..96d8f6bbc 100644 --- a/Sources/OpenAPIKit/Decoding Errors/OperationDecodingError.swift +++ b/Sources/OpenAPIKit/Decoding Errors/OperationDecodingError.swift @@ -18,7 +18,7 @@ extension OpenAPI.Error.Decoding { case request(Request) case response(Response) case inconsistency(InconsistencyError) - case generic(Swift.DecodingError) + case other(Swift.DecodingError) case neither(PolyDecodeNoTypesMatchedError) } } @@ -33,7 +33,7 @@ extension OpenAPI.Error.Decoding.Operation { return error.subjectName case .inconsistency(let error): return error.subjectName - case .generic(let decodingError): + case .other(let decodingError): return decodingError.subjectName case .neither(let polyError): return polyError.subjectName @@ -50,7 +50,7 @@ extension OpenAPI.Error.Decoding.Operation { return error.errorCategory case .inconsistency(let error): return .inconsistency(details: error.details) - case .generic(let decodingError): + case .other(let decodingError): return decodingError.errorCategory case .neither(let polyError): return polyError.errorCategory @@ -65,7 +65,7 @@ extension OpenAPI.Error.Decoding.Operation { return error.codingPath case .inconsistency(let error): return error.codingPath - case .generic(let decodingError): + case .other(let decodingError): return decodingError.codingPath case .neither(let polyError): return polyError.codingPath @@ -78,6 +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())! endpoint = verb @@ -87,6 +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())! endpoint = verb @@ -96,6 +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())! endpoint = verb @@ -105,10 +108,11 @@ 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())! endpoint = verb - context = .generic(error) + context = .other(error) relativeCodingPath = Array(codingPath) } @@ -130,16 +134,23 @@ extension OpenAPI.Error.Decoding.Operation { internal init(_ polyError: PolyDecodeNoTypesMatchedError) { if polyError.individualTypeFailures.count == 2 { - if polyError.individualTypeFailures[0].typeString == "$ref" && polyError.individualTypeFailures[1].codingPath(relativeTo: polyError.codingPath).count > 1 { + let firstFailureIsReference = polyError.individualTypeFailures[0].typeString == "$ref" + let secondFailureIsReference = polyError.individualTypeFailures[1].typeString == "$ref" + + let firstFailureIsDeeper = polyError.individualTypeFailures[0].codingPath(relativeTo: polyError.codingPath).count > 1 + let secondFailureIsDeeper = polyError.individualTypeFailures[1].codingPath(relativeTo: polyError.codingPath).count > 1 + + if firstFailureIsReference && secondFailureIsDeeper { self = Self(unwrapping: polyError.individualTypeFailures[1].error) return - } else if polyError.individualTypeFailures[1].typeString == "$ref" && polyError.individualTypeFailures[0].codingPath(relativeTo: polyError.codingPath).count > 0 { + } else if secondFailureIsReference && firstFailureIsDeeper { self = Self(unwrapping: polyError.individualTypeFailures[0].error) return } } var codingPath = polyError.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())! endpoint = verb diff --git a/Sources/OpenAPIKit/Decoding Errors/PathDecodingError.swift b/Sources/OpenAPIKit/Decoding Errors/PathDecodingError.swift index 0c08717d3..36c32a653 100644 --- a/Sources/OpenAPIKit/Decoding Errors/PathDecodingError.swift +++ b/Sources/OpenAPIKit/Decoding Errors/PathDecodingError.swift @@ -16,7 +16,7 @@ extension OpenAPI.Error.Decoding { public enum Context { case endpoint(Operation) - case generic(Swift.DecodingError) + case other(Swift.DecodingError) case neither(PolyDecodeNoTypesMatchedError) } } @@ -27,7 +27,7 @@ extension OpenAPI.Error.Decoding.Path { switch context { case .endpoint(let endpointError): return endpointError.subjectName - case .generic(let decodingError): + case .other(let decodingError): return decodingError.subjectName case .neither(let polyError): return polyError.subjectName @@ -38,6 +38,7 @@ extension OpenAPI.Error.Decoding.Path { let relativeCodingPath = relativeCodingPathString.isEmpty ? "" : "in \(relativeCodingPathString) " + switch context { case .endpoint(let endpointError): switch endpointError.context { @@ -45,13 +46,14 @@ extension OpenAPI.Error.Decoding.Path { let responseContext = responseError.statusCode.rawValue == "default" ? "default" : "status code '\(responseError.statusCode.rawValue)'" + return "\(relativeCodingPath)for the \(responseContext) response of the **\(endpointError.endpoint.rawValue)** endpoint under `\(path.rawValue)`" case .request: return "\(relativeCodingPath)for the request body of the **\(endpointError.endpoint.rawValue)** endpoint under `\(path.rawValue)`" - case .generic, .inconsistency, .neither: + case .other, .inconsistency, .neither: return "\(relativeCodingPath)for the **\(endpointError.endpoint.rawValue)** endpoint under `\(path.rawValue)`" } - case .generic, .neither: + case .other, .neither: return "\(relativeCodingPath)under the `\(path.rawValue)` path" } } @@ -60,7 +62,7 @@ extension OpenAPI.Error.Decoding.Path { switch context { case .endpoint(let endpointError): return endpointError.errorCategory - case .generic(let decodingError): + case .other(let decodingError): return decodingError.errorCategory case .neither(let polyError): return polyError.errorCategory @@ -71,7 +73,7 @@ extension OpenAPI.Error.Decoding.Path { switch context { case .endpoint(let endpointError): return endpointError.codingPath - case .generic(let decodingError): + case .other(let decodingError): return decodingError.codingPath case .neither(let polyError): return polyError.codingPath @@ -86,10 +88,10 @@ extension OpenAPI.Error.Decoding.Path { return responseError.relativeCodingPathString case .request(let requestError): return requestError.relativeCodingPathString - case .generic, .inconsistency, .neither: + case .other, .inconsistency, .neither: return endpointError.relativeCodingPathString } - case .generic, .neither: + case .other, .neither: return relativeCodingPath.stringValue } } @@ -99,7 +101,7 @@ extension OpenAPI.Error.Decoding.Path { let route = OpenAPI.PathComponents(rawValue: codingPath.removeFirst().stringValue) path = route - context = .generic(error) + context = .other(error) relativeCodingPath = Array(codingPath) } diff --git a/Sources/OpenAPIKit/Decoding Errors/PolyDecodeNoTypesMatchedErrorExtensions.swift b/Sources/OpenAPIKit/Decoding Errors/PolyDecodeNoTypesMatchedErrorExtensions.swift index 5767fe944..2f224053c 100644 --- a/Sources/OpenAPIKit/Decoding Errors/PolyDecodeNoTypesMatchedErrorExtensions.swift +++ b/Sources/OpenAPIKit/Decoding Errors/PolyDecodeNoTypesMatchedErrorExtensions.swift @@ -30,8 +30,8 @@ internal extension PolyDecodeNoTypesMatchedError { // guard individualTypeFailures.count == 2, - let f1 = individualTypeFailures.first, - let f2 = individualTypeFailures.dropFirst().first + let failure1 = individualTypeFailures.first, + let failure2 = individualTypeFailures.dropFirst().first else { return .dataCorrupted(underlying: self) } @@ -46,27 +46,27 @@ internal extension PolyDecodeNoTypesMatchedError { // We want to omit details if the problem is a missing '$ref' key. // If the intention was to write a reference, this error will be obvious. // If the intention was not to use a reference, this error will be superfluous. - let error1 = isRefKeyNotFoundError(f1) + let error1 = isRefKeyNotFoundError(failure1) ? nil - : OpenAPI.Error(from: f1.error.replacingPath(with: f1.codingPath(relativeTo: codingPath))).localizedDescription - let error2 = isRefKeyNotFoundError(f2) + : OpenAPI.Error(from: failure1.error.replacingPath(with: failure1.codingPath(relativeTo: codingPath))).localizedDescription + let error2 = isRefKeyNotFoundError(failure2) ? nil - : OpenAPI.Error(from: f2.error.replacingPath(with: f2.codingPath(relativeTo: codingPath))).localizedDescription + : OpenAPI.Error(from: failure2.error.replacingPath(with: failure2.codingPath(relativeTo: codingPath))).localizedDescription let details1 = error1 - .map { "\(String(describing: f1.type)) could not be decoded because:\n\($0)" } + .map { "\(String(describing: failure1.type)) could not be decoded because:\n\($0)" } .map { "\n\n" + $0 } ?? "" let details2 = error2 - .map { "\(String(describing: f2.type)) could not be decoded because:\n\($0)" } + .map { "\(String(describing: failure2.type)) could not be decoded because:\n\($0)" } .map { "\n\n" + $0 } ?? "" let details = details1 + details2 return .typeMismatch2( - possibleTypeName1: f1.typeString, - possibleTypeName2: f2.typeString, + possibleTypeName1: failure1.typeString, + possibleTypeName2: failure2.typeString, details: details ) } @@ -88,17 +88,17 @@ internal extension PolyDecodeNoTypesMatchedError.IndividualFailure { /// which will be at least as long if not longer than that of /// the `IndividualFailure` var fullCodingPath: [CodingKey] { - if let err = error.underlyingError as? DecodingError { - return err.codingPath + if let decodingError = error.underlyingError as? DecodingError { + return decodingError.codingPath } - if let err = error.underlyingError as? InconsistencyError { - return err.codingPath + if let inconsistencyError = error.underlyingError as? InconsistencyError { + return inconsistencyError.codingPath } - if let err = error.underlyingError as? PolyDecodeNoTypesMatchedError { - return err.codingPath + if let polyError = error.underlyingError as? PolyDecodeNoTypesMatchedError { + return polyError.codingPath } - if let err = error.underlyingError as? OpenAPIError { - return err.codingPath + if let openApiError = error.underlyingError as? OpenAPIError { + return openApiError.codingPath } return error.codingPath } diff --git a/Sources/OpenAPIKit/Decoding Errors/RequestDecodingError.swift b/Sources/OpenAPIKit/Decoding Errors/RequestDecodingError.swift index 1f51f3fca..46b8eed4a 100644 --- a/Sources/OpenAPIKit/Decoding Errors/RequestDecodingError.swift +++ b/Sources/OpenAPIKit/Decoding Errors/RequestDecodingError.swift @@ -15,7 +15,7 @@ extension OpenAPI.Error.Decoding { public enum Context { case inconsistency(InconsistencyError) - case generic(Swift.DecodingError) + case other(Swift.DecodingError) case neither(PolyDecodeNoTypesMatchedError) } } @@ -26,7 +26,7 @@ extension OpenAPI.Error.Decoding.Request { switch context { case .inconsistency(let error): return error.subjectName - case .generic(let decodingError): + case .other(let decodingError): return decodingError.subjectName case .neither(let polyError): return polyError.subjectName @@ -39,7 +39,7 @@ extension OpenAPI.Error.Decoding.Request { switch context { case .inconsistency(let error): return .inconsistency(details: error.details) - case .generic(let decodingError): + case .other(let decodingError): return decodingError.errorCategory case .neither(let polyError): return polyError.errorCategory @@ -50,7 +50,7 @@ extension OpenAPI.Error.Decoding.Request { switch context { case .inconsistency(let error): return error.codingPath - case .generic(let decodingError): + case .other(let decodingError): return decodingError.codingPath case .neither(let polyError): return polyError.codingPath @@ -62,10 +62,10 @@ extension OpenAPI.Error.Decoding.Request { } internal static func relativePath(from path: [CodingKey]) -> [CodingKey] { - guard let responsesIdx = path.firstIndex(where: { $0.stringValue == "requestBody" }) else { + guard let responsesIndex = path.firstIndex(where: { $0.stringValue == "requestBody" }) else { return path } - return Array(path.dropFirst(responsesIdx.advanced(by: 1))) + return Array(path.dropFirst(responsesIndex.advanced(by: 1))) } internal init(_ error: InconsistencyError) { @@ -74,7 +74,7 @@ extension OpenAPI.Error.Decoding.Request { } internal init(_ error: Swift.DecodingError) { - context = .generic(error) + context = .other(error) relativeCodingPath = Self.relativePath(from: error.codingPathWithoutSubject) } @@ -92,10 +92,16 @@ extension OpenAPI.Error.Decoding.Request { internal init(_ polyError: PolyDecodeNoTypesMatchedError) { if polyError.individualTypeFailures.count == 2 { - if polyError.individualTypeFailures[0].typeString == "$ref" && polyError.individualTypeFailures[1].codingPath(relativeTo: polyError.codingPath).count > 1 { + let firstFailureIsReference = polyError.individualTypeFailures[0].typeString == "$ref" + let secondFailureIsReference = polyError.individualTypeFailures[1].typeString == "$ref" + + let firstFailureIsDeeper = polyError.individualTypeFailures[0].codingPath(relativeTo: polyError.codingPath).count > 1 + let secondFailureIsDeeper = polyError.individualTypeFailures[1].codingPath(relativeTo: polyError.codingPath).count > 1 + + if firstFailureIsReference && secondFailureIsDeeper { self = Self(unwrapping: polyError.individualTypeFailures[1].error) return - } else if polyError.individualTypeFailures[1].typeString == "$ref" && polyError.individualTypeFailures[0].codingPath(relativeTo: polyError.codingPath).count > 1 { + } else if secondFailureIsReference && firstFailureIsDeeper { self = Self(unwrapping: polyError.individualTypeFailures[0].error) return } diff --git a/Sources/OpenAPIKit/Decoding Errors/ResponseDecodingError.swift b/Sources/OpenAPIKit/Decoding Errors/ResponseDecodingError.swift index 60ed7743d..bcb41eff2 100644 --- a/Sources/OpenAPIKit/Decoding Errors/ResponseDecodingError.swift +++ b/Sources/OpenAPIKit/Decoding Errors/ResponseDecodingError.swift @@ -16,7 +16,7 @@ extension OpenAPI.Error.Decoding { public enum Context { case inconsistency(InconsistencyError) - case generic(Swift.DecodingError) + case other(Swift.DecodingError) case neither(PolyDecodeNoTypesMatchedError) } } @@ -27,7 +27,7 @@ extension OpenAPI.Error.Decoding.Response { switch context { case .inconsistency(let error): return error.subjectName - case .generic(let decodingError): + case .other(let decodingError): return decodingError.subjectName case .neither(let polyError): return polyError.subjectName @@ -40,7 +40,7 @@ extension OpenAPI.Error.Decoding.Response { switch context { case .inconsistency(let error): return .inconsistency(details: error.details) - case .generic(let decodingError): + case .other(let decodingError): return decodingError.errorCategory case .neither(let polyError): return polyError.errorCategory @@ -51,7 +51,7 @@ extension OpenAPI.Error.Decoding.Response { switch context { case .inconsistency(let error): return error.codingPath - case .generic(let decodingError): + case .other(let decodingError): return decodingError.codingPath case .neither(let polyError): return polyError.codingPath @@ -63,16 +63,17 @@ extension OpenAPI.Error.Decoding.Response { } internal static func relativePath(from path: [CodingKey]) -> [CodingKey] { - guard let responsesIdx = path.firstIndex(where: { $0.stringValue == "responses" }) else { + guard let responsesIndex = path.firstIndex(where: { $0.stringValue == "responses" }) else { return path } - return Array(path.dropFirst(responsesIdx.advanced(by: 1))) + return Array(path.dropFirst(responsesIndex.advanced(by: 1))) } internal init(_ error: InconsistencyError) { var codingPath = Self.relativePath(from: error.codingPath) let code = codingPath.removeFirst().stringValue.lowercased() + // this part of the coding path is structurally guaranteed to be a status code. statusCode = OpenAPI.Response.StatusCode(rawValue: code)! context = .inconsistency(error) relativeCodingPath = Array(codingPath) @@ -82,8 +83,9 @@ extension OpenAPI.Error.Decoding.Response { var codingPath = Self.relativePath(from: error.codingPathWithoutSubject) let code = codingPath.removeFirst().stringValue.lowercased() + // this part of the coding path is structurally guaranteed to be a status code. statusCode = OpenAPI.Response.StatusCode(rawValue: code)! - context = .generic(error) + context = .other(error) relativeCodingPath = Array(codingPath) } @@ -101,10 +103,16 @@ extension OpenAPI.Error.Decoding.Response { internal init(_ polyError: PolyDecodeNoTypesMatchedError) { if polyError.individualTypeFailures.count == 2 { - if polyError.individualTypeFailures[0].typeString == "$ref" && polyError.individualTypeFailures[1].codingPath(relativeTo: polyError.codingPath).count > 1 { + let firstFailureIsReference = polyError.individualTypeFailures[0].typeString == "$ref" + let secondFailureIsReference = polyError.individualTypeFailures[1].typeString == "$ref" + + let firstFailureIsDeeper = polyError.individualTypeFailures[0].codingPath(relativeTo: polyError.codingPath).count > 1 + let secondFailureIsDeeper = polyError.individualTypeFailures[1].codingPath(relativeTo: polyError.codingPath).count > 1 + + if firstFailureIsReference && secondFailureIsDeeper { self = Self(unwrapping: polyError.individualTypeFailures[1].error) return - } else if polyError.individualTypeFailures[1].typeString == "$ref" && polyError.individualTypeFailures[0].codingPath(relativeTo: polyError.codingPath).count > 1 { + } else if secondFailureIsReference && firstFailureIsDeeper { self = Self(unwrapping: polyError.individualTypeFailures[0].error) return } @@ -113,6 +121,7 @@ extension OpenAPI.Error.Decoding.Response { var codingPath = Self.relativePath(from: polyError.codingPath) let code = codingPath.removeFirst().stringValue.lowercased() + // this part of the coding path is structurally guaranteed to be a status code. statusCode = OpenAPI.Response.StatusCode(rawValue: code)! context = .neither(polyError) relativeCodingPath = Array(codingPath) From 6026710daa25d0bfa99cee22e2b9fc71b76417d0 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Sat, 14 Mar 2020 22:00:36 -0700 Subject: [PATCH 2/4] audit path items folder for unnecessary abbreviations and poorly chosen type names. add open api spec links. --- .../Decoding Errors/PathDecodingError.swift | 8 +++---- Sources/OpenAPIKit/Document.swift | 6 ++--- Sources/OpenAPIKit/ExternalDoc.swift | 10 +++++---- Sources/OpenAPIKit/Path Item/Operation.swift | 10 +++++---- Sources/OpenAPIKit/Path Item/Parameter.swift | 6 +++++ .../Path Item/ParameterLocation.swift | 2 ++ .../Path Item/ParameterSchema.swift | 3 +++ Sources/OpenAPIKit/Path Item/PathItem.swift | 15 ++++++++----- .../Schema Object/SchemaObject.swift | 22 +++++++++---------- .../Schema Object/SchemaObjectContext.swift | 12 +++++----- Sources/OpenAPIKit/Tag.swift | 6 ++--- Tests/OpenAPIKitTests/ExternalDocTests.swift | 16 +++++++------- .../Path Item/PathItemTests.swift | 14 ++++++------ 13 files changed, 75 insertions(+), 55 deletions(-) diff --git a/Sources/OpenAPIKit/Decoding Errors/PathDecodingError.swift b/Sources/OpenAPIKit/Decoding Errors/PathDecodingError.swift index 36c32a653..1271c54b1 100644 --- a/Sources/OpenAPIKit/Decoding Errors/PathDecodingError.swift +++ b/Sources/OpenAPIKit/Decoding Errors/PathDecodingError.swift @@ -10,7 +10,7 @@ import Poly extension OpenAPI.Error.Decoding { public struct Path: OpenAPIError { - public let path: OpenAPI.PathComponents + public let path: OpenAPI.Path public let context: Context internal let relativeCodingPath: [CodingKey] @@ -98,7 +98,7 @@ extension OpenAPI.Error.Decoding.Path { internal init(_ error: DecodingError) { var codingPath = error.codingPathWithoutSubject.dropFirst() - let route = OpenAPI.PathComponents(rawValue: codingPath.removeFirst().stringValue) + let route = OpenAPI.Path(rawValue: codingPath.removeFirst().stringValue) path = route context = .other(error) @@ -107,7 +107,7 @@ extension OpenAPI.Error.Decoding.Path { internal init(_ polyError: PolyDecodeNoTypesMatchedError) { var codingPath = polyError.codingPath.dropFirst() - let route = OpenAPI.PathComponents(rawValue: codingPath.removeFirst().stringValue) + let route = OpenAPI.Path(rawValue: codingPath.removeFirst().stringValue) path = route context = .neither(polyError) @@ -116,7 +116,7 @@ extension OpenAPI.Error.Decoding.Path { internal init(_ error: OpenAPI.Error.Decoding.Operation) { var codingPath = error.codingPath.dropFirst() - let route = OpenAPI.PathComponents(rawValue: codingPath.removeFirst().stringValue) + let route = OpenAPI.Path(rawValue: codingPath.removeFirst().stringValue) path = route context = .endpoint(error) diff --git a/Sources/OpenAPIKit/Document.swift b/Sources/OpenAPIKit/Document.swift index d6b202c9d..c62d9e385 100644 --- a/Sources/OpenAPIKit/Document.swift +++ b/Sources/OpenAPIKit/Document.swift @@ -19,7 +19,7 @@ extension OpenAPI { public var components: Components public var security: [SecurityRequirement] public var tags: [Tag]? - public var externalDocs: ExternalDoc? + public var externalDocs: ExternalDocumentation? public init(openAPIVersion: Version = .v3_0_0, info: Info, @@ -28,7 +28,7 @@ extension OpenAPI { components: Components, security: [SecurityRequirement] = [], tags: [Tag]? = nil, - externalDocs: ExternalDoc? = nil) { + externalDocs: ExternalDocumentation? = nil) { self.openAPIVersion = openAPIVersion self.info = info self.servers = servers @@ -124,7 +124,7 @@ extension OpenAPI.Document: Decodable { tags = try container.decodeIfPresent([OpenAPI.Tag].self, forKey: .tags) - externalDocs = try container.decodeIfPresent(OpenAPI.ExternalDoc.self, forKey: .externalDocs) + externalDocs = try container.decodeIfPresent(OpenAPI.ExternalDocumentation.self, forKey: .externalDocs) } catch let error as OpenAPI.Error.Decoding.Path { throw OpenAPI.Error.Decoding.Document(error) diff --git a/Sources/OpenAPIKit/ExternalDoc.swift b/Sources/OpenAPIKit/ExternalDoc.swift index 8628e3dfe..8d783ff6e 100644 --- a/Sources/OpenAPIKit/ExternalDoc.swift +++ b/Sources/OpenAPIKit/ExternalDoc.swift @@ -8,7 +8,9 @@ import Foundation extension OpenAPI { - public struct ExternalDoc: Equatable { + /// OpenAPI Spec "External Documentation Object" + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#external-documentation-object + public struct ExternalDocumentation: Equatable { public let description: String? public let url: URL @@ -22,7 +24,7 @@ extension OpenAPI { // MARK: - Codable -extension OpenAPI.ExternalDoc: Encodable { +extension OpenAPI.ExternalDocumentation: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -32,7 +34,7 @@ extension OpenAPI.ExternalDoc: Encodable { } } -extension OpenAPI.ExternalDoc: Decodable { +extension OpenAPI.ExternalDocumentation: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -42,7 +44,7 @@ extension OpenAPI.ExternalDoc: Decodable { } } -extension OpenAPI.ExternalDoc { +extension OpenAPI.ExternalDocumentation { private enum CodingKeys: String, CodingKey { case description case url diff --git a/Sources/OpenAPIKit/Path Item/Operation.swift b/Sources/OpenAPIKit/Path Item/Operation.swift index 0d6e8e624..f9086aa50 100644 --- a/Sources/OpenAPIKit/Path Item/Operation.swift +++ b/Sources/OpenAPIKit/Path Item/Operation.swift @@ -9,11 +9,13 @@ import Foundation import Poly extension OpenAPI.PathItem { + /// OpenAPI Spec "Operation Object" + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#operation-object public struct Operation: Equatable { public var tags: [String]? public var summary: String? public var description: String? - public var externalDocs: OpenAPI.ExternalDoc? + public var externalDocs: OpenAPI.ExternalDocumentation? public var operationId: String? public var parameters: Parameter.Array public var requestBody: Either, OpenAPI.Request>? @@ -26,7 +28,7 @@ extension OpenAPI.PathItem { public init(tags: [String]? = nil, summary: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, operationId: String? = nil, parameters: Parameter.Array = [], requestBody: OpenAPI.Request? = nil, @@ -51,7 +53,7 @@ extension OpenAPI.PathItem { public init(tags: String..., summary: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, operationId: String? = nil, parameters: Parameter.Array, requestBody: OpenAPI.Request? = nil, @@ -140,7 +142,7 @@ extension OpenAPI.PathItem.Operation: Decodable { description = try container.decodeIfPresent(String.self, forKey: .description) - externalDocs = try container.decodeIfPresent(OpenAPI.ExternalDoc.self, forKey: .externalDocs) + externalDocs = try container.decodeIfPresent(OpenAPI.ExternalDocumentation.self, forKey: .externalDocs) operationId = try container.decodeIfPresent(String.self, forKey: .operationId) diff --git a/Sources/OpenAPIKit/Path Item/Parameter.swift b/Sources/OpenAPIKit/Path Item/Parameter.swift index 1ac29c7e3..5684ea604 100644 --- a/Sources/OpenAPIKit/Path Item/Parameter.swift +++ b/Sources/OpenAPIKit/Path Item/Parameter.swift @@ -9,11 +9,17 @@ import Foundation import Poly extension OpenAPI.PathItem { + /// OpenAPI Spec "Parameter Object" + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-object public struct Parameter: Equatable { public var name: String + + /// OpenAPI Spec "in" property. public var parameterLocation: Location public var description: String? public var deprecated: Bool // default is false + + /// OpenAPI Spec "content" or "schema" properties. public var schemaOrContent: Either public typealias Array = [Either, Parameter>] diff --git a/Sources/OpenAPIKit/Path Item/ParameterLocation.swift b/Sources/OpenAPIKit/Path Item/ParameterLocation.swift index 3c2d27fff..a2eebddfa 100644 --- a/Sources/OpenAPIKit/Path Item/ParameterLocation.swift +++ b/Sources/OpenAPIKit/Path Item/ParameterLocation.swift @@ -6,6 +6,8 @@ // extension OpenAPI.PathItem.Parameter { + /// OpenAPI Spec "Parameter Object" location-specific configuration. + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-locations public enum Location: Equatable { case query(required: Bool, allowEmptyValue: Bool) case header(required: Bool) diff --git a/Sources/OpenAPIKit/Path Item/ParameterSchema.swift b/Sources/OpenAPIKit/Path Item/ParameterSchema.swift index fdd8df885..6f77023b0 100644 --- a/Sources/OpenAPIKit/Path Item/ParameterSchema.swift +++ b/Sources/OpenAPIKit/Path Item/ParameterSchema.swift @@ -9,6 +9,9 @@ import Poly import AnyCodable extension OpenAPI.PathItem.Parameter { + /// OpenAPI Spec "Parameter Object" schema and style configuration. + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-object + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#style-values public struct Schema: Equatable { public let style: Style public let explode: Bool diff --git a/Sources/OpenAPIKit/Path Item/PathItem.swift b/Sources/OpenAPIKit/Path Item/PathItem.swift index 389b4fe85..85d898e95 100644 --- a/Sources/OpenAPIKit/Path Item/PathItem.swift +++ b/Sources/OpenAPIKit/Path Item/PathItem.swift @@ -10,7 +10,10 @@ import Poly import OrderedDictionary extension OpenAPI { - public struct PathComponents: RawRepresentable, Equatable, Hashable { + /// OpenAPI Spec "Paths Object" path field pattern support. + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#paths-object + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#patterned-fields + public struct Path: RawRepresentable, Equatable, Hashable { public let components: [String] public init(_ components: [String]) { @@ -30,13 +33,15 @@ extension OpenAPI { } } -extension OpenAPI.PathComponents: ExpressibleByStringLiteral { +extension OpenAPI.Path: ExpressibleByStringLiteral { public init(stringLiteral value: String) { self.init(rawValue: value) } } extension OpenAPI { + /// OpenAPI Spec "Path Item Object" + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#path-item-object public struct PathItem: Equatable { public var summary: String? public var description: String? @@ -111,7 +116,7 @@ extension OpenAPI { trace = op } - public typealias Map = OrderedDictionary + public typealias Map = OrderedDictionary } } @@ -170,7 +175,7 @@ extension OpenAPI.PathItem { // MARK: - Codable -extension OpenAPI.PathComponents: Encodable { +extension OpenAPI.Path: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -178,7 +183,7 @@ extension OpenAPI.PathComponents: Encodable { } } -extension OpenAPI.PathComponents: Decodable { +extension OpenAPI.Path: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() diff --git a/Sources/OpenAPIKit/Schema Object/SchemaObject.swift b/Sources/OpenAPIKit/Schema Object/SchemaObject.swift index d00fb4940..92df25c7f 100644 --- a/Sources/OpenAPIKit/Schema Object/SchemaObject.swift +++ b/Sources/OpenAPIKit/Schema Object/SchemaObject.swift @@ -144,7 +144,7 @@ public enum JSONSchema: Equatable, JSONSchemaContext { } } - public var externalDocs: OpenAPI.ExternalDoc? { + public var externalDocs: OpenAPI.ExternalDocumentation? { switch self { case .boolean(let context as JSONSchemaContext), .object(let context as JSONSchemaContext, _), @@ -328,7 +328,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, allowedValues: [AnyCodable]? = nil, example: (codable: AnyCodable, encoder: JSONEncoder)? = nil ) -> JSONSchema { @@ -356,7 +356,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, allowedValues: AnyCodable..., example: (codable: AnyCodable, encoder: JSONEncoder)? = nil ) -> JSONSchema { @@ -387,7 +387,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, minLength: Int = 0, maxLength: Int? = nil, pattern: String? = nil, @@ -423,7 +423,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, minLength: Int = 0, maxLength: Int? = nil, pattern: String? = nil, @@ -460,7 +460,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, multipleOf: Double? = nil, maximum: (Double, exclusive: Bool)? = nil, minimum: (Double, exclusive: Bool)? = nil, @@ -496,7 +496,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, multipleOf: Double? = nil, maximum: (Double, exclusive: Bool)? = nil, minimum: (Double, exclusive: Bool)? = nil, @@ -533,7 +533,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, multipleOf: Int? = nil, maximum: (Int, exclusive: Bool)? = nil, minimum: (Int, exclusive: Bool)? = nil, @@ -569,7 +569,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, multipleOf: Int? = nil, maximum: (Int, exclusive: Bool)? = nil, minimum: (Int, exclusive: Bool)? = nil, @@ -606,7 +606,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, minProperties: Int = 0, maxProperties: Int? = nil, properties: [String: JSONSchema] = [:], @@ -648,7 +648,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, minItems: Int = 0, maxItems: Int? = nil, uniqueItems: Bool = false, diff --git a/Sources/OpenAPIKit/Schema Object/SchemaObjectContext.swift b/Sources/OpenAPIKit/Schema Object/SchemaObjectContext.swift index c367e967f..0420cf1ee 100644 --- a/Sources/OpenAPIKit/Schema Object/SchemaObjectContext.swift +++ b/Sources/OpenAPIKit/Schema Object/SchemaObjectContext.swift @@ -16,7 +16,7 @@ public protocol JSONSchemaContext { var nullable: Bool { get } var title: String? { get } var description: String? { get } - var externalDocs: OpenAPI.ExternalDoc? { get } + var externalDocs: OpenAPI.ExternalDocumentation? { get } var allowedValues: [AnyCodable]? { get } var example: String? { get } var readOnly: Bool { get } @@ -35,7 +35,7 @@ extension JSONSchema { public let title: String? public let description: String? - public let externalDocs: OpenAPI.ExternalDoc? + public let externalDocs: OpenAPI.ExternalDocumentation? // 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. @@ -70,7 +70,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, allowedValues: [AnyCodable]? = nil, example: (codable: T, encoder: JSONEncoder)) { self.format = format @@ -94,7 +94,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, allowedValues: [AnyCodable]? = nil, example: (codable: AnyCodable, encoder: JSONEncoder)? = nil) { self.format = format @@ -118,7 +118,7 @@ extension JSONSchema { deprecated: Bool = false, title: String? = nil, description: String? = nil, - externalDocs: OpenAPI.ExternalDoc? = nil, + externalDocs: OpenAPI.ExternalDocumentation? = nil, allowedValues: [AnyCodable]? = nil, example: String?) { self.format = format @@ -408,7 +408,7 @@ extension JSONSchema.Context: Decodable { title = try container.decodeIfPresent(String.self, forKey: .title) description = try container.decodeIfPresent(String.self, forKey: .description) - externalDocs = try container.decodeIfPresent(OpenAPI.ExternalDoc.self, forKey: .externalDocs) + externalDocs = try container.decodeIfPresent(OpenAPI.ExternalDocumentation.self, forKey: .externalDocs) allowedValues = try container.decodeIfPresent([AnyCodable].self, forKey: .allowedValues) diff --git a/Sources/OpenAPIKit/Tag.swift b/Sources/OpenAPIKit/Tag.swift index 5ffeb795c..33edfeed2 100644 --- a/Sources/OpenAPIKit/Tag.swift +++ b/Sources/OpenAPIKit/Tag.swift @@ -11,11 +11,11 @@ extension OpenAPI { public struct Tag: Equatable { public let name: String public let description: String? - public let externalDocs: ExternalDoc? + public let externalDocs: ExternalDocumentation? public init(name: String, description: String? = nil, - externalDocs: ExternalDoc? = nil) { + externalDocs: ExternalDocumentation? = nil) { self.name = name self.description = description self.externalDocs = externalDocs @@ -51,7 +51,7 @@ extension OpenAPI.Tag: Decodable { description = try container.decodeIfPresent(String.self, forKey: .description) - externalDocs = try container.decodeIfPresent(OpenAPI.ExternalDoc.self, forKey: .externalDocs) + externalDocs = try container.decodeIfPresent(OpenAPI.ExternalDocumentation.self, forKey: .externalDocs) } } diff --git a/Tests/OpenAPIKitTests/ExternalDocTests.swift b/Tests/OpenAPIKitTests/ExternalDocTests.swift index 20e2244de..6d3a56d28 100644 --- a/Tests/OpenAPIKitTests/ExternalDocTests.swift +++ b/Tests/OpenAPIKitTests/ExternalDocTests.swift @@ -10,10 +10,10 @@ import OpenAPIKit final class ExternalDocTests: XCTestCase { func test_init() { - let t1 = OpenAPI.ExternalDoc(url: URL(string: "http://google.com")!) + let t1 = OpenAPI.ExternalDocumentation(url: URL(string: "http://google.com")!) XCTAssertNil(t1.description) - let t2 = OpenAPI.ExternalDoc(description: "hello world", + let t2 = OpenAPI.ExternalDocumentation(description: "hello world", url: URL(string: "http://google.com")!) XCTAssertEqual(t2.description, "hello world") } @@ -22,7 +22,7 @@ final class ExternalDocTests: XCTestCase { // MARK: - Codable extension ExternalDocTests { func test_descriptionAndUrl_encode() { - let externalDoc = OpenAPI.ExternalDoc(description: "hello world", + let externalDoc = OpenAPI.ExternalDocumentation(description: "hello world", url: URL(string: "http://google.com")!) let encodedExternalDoc = try! testStringFromEncoding(of: externalDoc) @@ -45,14 +45,14 @@ extension ExternalDocTests { "url" : "http:\\/\\/google.com" } """.data(using: .utf8)! - let externalDocs = try! testDecoder.decode(OpenAPI.ExternalDoc.self, from: externalDocsData) + let externalDocs = try! testDecoder.decode(OpenAPI.ExternalDocumentation.self, from: externalDocsData) - XCTAssertEqual(externalDocs, OpenAPI.ExternalDoc(description: "hello world", + XCTAssertEqual(externalDocs, OpenAPI.ExternalDocumentation(description: "hello world", url: URL(string: "http://google.com")!)) } func test_onlyUrl_encode() { - let externalDoc = OpenAPI.ExternalDoc(url: URL(string: "http://google.com")!) + let externalDoc = OpenAPI.ExternalDocumentation(url: URL(string: "http://google.com")!) let encodedExternalDoc = try! testStringFromEncoding(of: externalDoc) @@ -72,8 +72,8 @@ extension ExternalDocTests { "url" : "http:\\/\\/google.com" } """.data(using: .utf8)! - let externalDocs = try! testDecoder.decode(OpenAPI.ExternalDoc.self, from: externalDocsData) + let externalDocs = try! testDecoder.decode(OpenAPI.ExternalDocumentation.self, from: externalDocsData) - XCTAssertEqual(externalDocs, OpenAPI.ExternalDoc(url: URL(string: "http://google.com")!)) + XCTAssertEqual(externalDocs, OpenAPI.ExternalDocumentation(url: URL(string: "http://google.com")!)) } } diff --git a/Tests/OpenAPIKitTests/Path Item/PathItemTests.swift b/Tests/OpenAPIKitTests/Path Item/PathItemTests.swift index 402cf4e00..9669e941c 100644 --- a/Tests/OpenAPIKitTests/Path Item/PathItemTests.swift +++ b/Tests/OpenAPIKitTests/Path Item/PathItemTests.swift @@ -11,11 +11,11 @@ import FineJSON final class PathItemTests: XCTestCase { func test_initializePathComponents() { - let t1 = OpenAPI.PathComponents(["hello", "world"]) - let t2 = OpenAPI.PathComponents(rawValue: "/hello/world") - let t3 = OpenAPI.PathComponents(rawValue: "hello/world") - let t4: OpenAPI.PathComponents = "/hello/world" - let t5: OpenAPI.PathComponents = "hello/world" + let t1 = OpenAPI.Path(["hello", "world"]) + let t2 = OpenAPI.Path(rawValue: "/hello/world") + let t3 = OpenAPI.Path(rawValue: "hello/world") + let t4: OpenAPI.Path = "/hello/world" + let t5: OpenAPI.Path = "hello/world" XCTAssertEqual(t1, t2) XCTAssertEqual(t2, t3) @@ -368,7 +368,7 @@ extension PathItemTests { } func test_pathComponents_encode() throws { - let test: [OpenAPI.PathComponents] = ["/hello/world", "hi/there"] + let test: [OpenAPI.Path] = ["/hello/world", "hi/there"] let encodedTest = try testStringFromEncoding(of: test) @@ -392,7 +392,7 @@ extension PathItemTests { ] """.data(using: .utf8)! - let test = try testDecoder.decode([OpenAPI.PathComponents].self, from: testData) + let test = try testDecoder.decode([OpenAPI.Path].self, from: testData) XCTAssertEqual( test, From 8f8254e25b10e6f9bff52cf9b76057f0b30d9bcd Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Sat, 14 Mar 2020 22:25:10 -0700 Subject: [PATCH 3/4] audit schema object folder for unnecessary abbreviations and poorly chosen type names. add documentation. --- .../Schema Conformances/SchemaProtocols.swift | 3 +- .../Schema Object/SchemaObject.swift | 37 ++++++++++++++++++- .../Schema Object/SchemaObjectContext.swift | 9 +++++ .../Schema Object/TypesAndFormats.swift | 12 ++++++ 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/Sources/OpenAPIKit/Schema Conformances/SchemaProtocols.swift b/Sources/OpenAPIKit/Schema Conformances/SchemaProtocols.swift index 0b1fee5c6..d421dd678 100644 --- a/Sources/OpenAPIKit/Schema Conformances/SchemaProtocols.swift +++ b/Sources/OpenAPIKit/Schema Conformances/SchemaProtocols.swift @@ -11,8 +11,9 @@ import Foundation import AnyCodable -/// Anything conforming to `OpenAPINodeType` can provide an +/// Anything conforming to `OpenAPISchemaType` can provide an /// OpenAPI schema representing itself. public protocol OpenAPISchemaType { + /// The best `JSONSchema` representation for this type. static func openAPISchema() throws -> JSONSchema } diff --git a/Sources/OpenAPIKit/Schema Object/SchemaObject.swift b/Sources/OpenAPIKit/Schema Object/SchemaObject.swift index 92df25c7f..e534c702e 100644 --- a/Sources/OpenAPIKit/Schema Object/SchemaObject.swift +++ b/Sources/OpenAPIKit/Schema Object/SchemaObject.swift @@ -9,6 +9,8 @@ import Foundation import AnyCodable import Poly +/// OpenAPI "Schema Object" +/// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#schema-object public enum JSONSchema: Equatable, JSONSchemaContext { case boolean(Context) indirect case object(Context, ObjectContext) @@ -21,6 +23,10 @@ public enum JSONSchema: Equatable, JSONSchemaContext { indirect case any(of: [JSONSchema]) indirect case not(JSONSchema) case reference(JSONReference) + /// This schema does not have a `type` specified. This is allowed + /// but does not offer much in the way of documenting the schema + /// so it is represented here as "undefined" with an optional + /// description. case undefined(description: String?) // This is the "{}" case where not even a type constraint is given. If a 'description' property is found, it is used as the associated value. public var jsonTypeFormat: JSONTypeFormat? { @@ -42,6 +48,15 @@ public enum JSONSchema: Equatable, JSONSchemaContext { } } + /// `true` if values for this schema are required, `false` if they + /// are optional (and can therefore be omitted from request/response data). + /// + /// - Important: This is distinct from the concept of nullability. + /// + /// **Nullability:** Whether or not a value can be `null`. + /// + /// **Optionality:** Whether or not a key/value can be entirely + /// omitted from request/response data. public var required: Bool { switch self { case .boolean(let context as JSONSchemaContext), @@ -58,6 +73,14 @@ public enum JSONSchema: Equatable, JSONSchemaContext { } } + /// `true` if values for this schema can be `null`. + /// + /// - Important: This is distinct from the concept of optionality. + /// + /// **Nullability:** Whether or not a value can be `null`. + /// + /// **Optionality:** Whether or not a key/value can be entirely + /// omitted from request/response data. public var nullable: Bool { switch self { case .boolean(let context as JSONSchemaContext), @@ -72,6 +95,8 @@ public enum JSONSchema: Equatable, JSONSchemaContext { } } + /// `true` if this schema can only be read from and is therefore + /// unsupported for request data. public var readOnly: Bool { switch self { case .boolean(let context as JSONSchemaContext), @@ -86,6 +111,8 @@ public enum JSONSchema: Equatable, JSONSchemaContext { } } + /// `true` if this schema can only be written to and is therefore + /// unavailable in response data. public var writeOnly: Bool { switch self { case .boolean(let context as JSONSchemaContext), @@ -100,6 +127,7 @@ public enum JSONSchema: Equatable, JSONSchemaContext { } } + /// `true` if this schema is deprecated, `false` otherwise. public var deprecated: Bool { switch self { case .boolean(let context as JSONSchemaContext), @@ -114,6 +142,7 @@ public enum JSONSchema: Equatable, JSONSchemaContext { } } + /// Get the title, if specified. If unspecified, returns `nil`. public var title: String? { switch self { case .boolean(let context as JSONSchemaContext), @@ -128,6 +157,7 @@ public enum JSONSchema: Equatable, JSONSchemaContext { } } + /// Get the description, if specified. If unspecified, returns `nil`. public var description: String? { switch self { case .boolean(let context as JSONSchemaContext), @@ -144,6 +174,7 @@ public enum JSONSchema: Equatable, JSONSchemaContext { } } + /// Get the external docs, if specified. If unspecified, returns `nil`. public var externalDocs: OpenAPI.ExternalDocumentation? { switch self { case .boolean(let context as JSONSchemaContext), @@ -158,7 +189,7 @@ public enum JSONSchema: Equatable, JSONSchemaContext { } } - /// Allowed values, if specified. If unspecified, returns `nil`. + /// Get the allowed values, if specified. If unspecified, returns `nil`. public var allowedValues: [AnyCodable]? { switch self { case .boolean(let context as JSONSchemaContext), @@ -173,7 +204,7 @@ public enum JSONSchema: Equatable, JSONSchemaContext { } } - /// An example, encoded as a `String`, if specified. If unspecified, returns `nil`. + /// Get an example, encoded as a `String`, if specified. If unspecified, returns `nil`. public var example: String? { switch self { case .boolean(let context as JSONSchemaContext), @@ -273,6 +304,8 @@ extension JSONSchema { } } + /// Returns a version of this `JSONSchema` that has the given example + /// attached. public func with(example codableExample: T, using encoder: JSONEncoder) throws -> JSONSchema { switch self { diff --git a/Sources/OpenAPIKit/Schema Object/SchemaObjectContext.swift b/Sources/OpenAPIKit/Schema Object/SchemaObjectContext.swift index 0420cf1ee..0b60587ce 100644 --- a/Sources/OpenAPIKit/Schema Object/SchemaObjectContext.swift +++ b/Sources/OpenAPIKit/Schema Object/SchemaObjectContext.swift @@ -11,6 +11,9 @@ import Poly // MARK: - Generic Context +/// A schema context stores information about a schema. +/// All schemas can have the contextual information in +/// this protocol. public protocol JSONSchemaContext { var required: Bool { get } var nullable: Bool { get } @@ -25,6 +28,7 @@ public protocol JSONSchemaContext { } extension JSONSchema { + /// The context that applies to all schemas. public struct Context: JSONSchemaContext, Equatable { public let format: Format public let required: Bool // default true (except on decode, where required depends on whether there is a parent schema scope to contain a 'required' property) @@ -218,6 +222,7 @@ extension JSONSchema.Context { // MARK: - Specific Contexts extension JSONSchema { + /// The context that only applies to `.number` schemas. public struct NumericContext: Equatable { public struct Bound: Equatable { public let value: Double @@ -239,6 +244,7 @@ extension JSONSchema { } } + /// The context that only applies to `.integer` schemas. public struct IntegerContext: Equatable { public struct Bound: Equatable { public let value: Int @@ -260,6 +266,7 @@ extension JSONSchema { } } + /// The context that only applies to `.string` schemas. public struct StringContext: Equatable { public let maxLength: Int? public let minLength: Int @@ -276,6 +283,7 @@ extension JSONSchema { } } + /// The context that only applies to `.array` schemas. public struct ArrayContext: Equatable { /// A JSON Type Node that describes /// the type of each element in the array. @@ -304,6 +312,7 @@ extension JSONSchema { } } + /// The context that only applies to `.object` schemas. public struct ObjectContext: Equatable { public let maxProperties: Int? let _minProperties: Int diff --git a/Sources/OpenAPIKit/Schema Object/TypesAndFormats.swift b/Sources/OpenAPIKit/Schema Object/TypesAndFormats.swift index a06f5ba03..91d6a7419 100644 --- a/Sources/OpenAPIKit/Schema Object/TypesAndFormats.swift +++ b/Sources/OpenAPIKit/Schema Object/TypesAndFormats.swift @@ -73,6 +73,7 @@ public protocol OpenAPIFormat: SwiftTyped, Codable, Equatable { } extension JSONTypeFormat { + /// The allowed "format" properties for `.boolean` schemas. public enum BooleanFormat: RawRepresentable, Equatable, OpenAPIFormat { case generic case other(String) @@ -103,6 +104,7 @@ extension JSONTypeFormat { } } + /// The allowed "format" properties for `.object` schemas. public enum ObjectFormat: RawRepresentable, Equatable, OpenAPIFormat { case generic case other(String) @@ -133,6 +135,7 @@ extension JSONTypeFormat { } } + /// The allowed "format" properties for `.array` schemas. public enum ArrayFormat: RawRepresentable, Equatable, OpenAPIFormat { case generic case other(String) @@ -163,6 +166,7 @@ extension JSONTypeFormat { } } + /// The allowed "format" properties for `.number` schemas. public enum NumberFormat: RawRepresentable, Equatable, OpenAPIFormat { case generic case float @@ -199,6 +203,7 @@ extension JSONTypeFormat { } } + /// The allowed "format" properties for `.integer` schemas. public enum IntegerFormat: RawRepresentable, Equatable, OpenAPIFormat { case generic case int32 @@ -235,6 +240,7 @@ extension JSONTypeFormat { } } + /// The allowed "format" properties for `.string` schemas. public enum StringFormat: RawRepresentable, Equatable, OpenAPIFormat { case generic case byte @@ -282,6 +288,9 @@ extension JSONTypeFormat { } extension JSONTypeFormat.StringFormat { + /// Popular non-standard "format" properties for `.string` schemas. + /// + /// Specify with e.g. `.string(format: .extended(.uuid))` public enum Extended: String, Equatable { case uuid = "uuid" case email = "email" @@ -293,6 +302,9 @@ extension JSONTypeFormat.StringFormat { } extension JSONTypeFormat.IntegerFormat { + /// Popular non-standard "format" properties for `.integer` schemas. + /// + /// Specify with e.g. `.integer(format: .extended(.uint32))` public enum Extended: String, Equatable { case uint32 = "uint32" case uint64 = "uint64" From 63ee8a716c61697101c199cc9ceaa32cab6991c0 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Sun, 15 Mar 2020 15:05:12 -0700 Subject: [PATCH 4/4] finish audit for abbreviation usage, add some documentation, make minor tweaks for readability or consistency. --- .../OpenAPIKit/CodableVendorExtendable.swift | 46 ++++++++++++------- Sources/OpenAPIKit/Components.swift | 9 ++-- Sources/OpenAPIKit/Content.swift | 7 ++- Sources/OpenAPIKit/ContentEncoding.swift | 3 ++ Sources/OpenAPIKit/Discriminator.swift | 3 ++ Sources/OpenAPIKit/Document.swift | 6 +++ Sources/OpenAPIKit/DocumentInfo.swift | 13 ++++-- Sources/OpenAPIKit/Example.swift | 15 +++--- Sources/OpenAPIKit/Exports.swift | 8 ---- Sources/OpenAPIKit/ExternalDoc.swift | 1 + Sources/OpenAPIKit/Header.swift | 4 ++ Sources/OpenAPIKit/JSONReference.swift | 5 +- Sources/OpenAPIKit/OAuthFlows.swift | 3 ++ Sources/OpenAPIKit/OpenAPI.swift | 14 ++++++ Sources/OpenAPIKit/Path Item/Operation.swift | 1 + Sources/OpenAPIKit/Path Item/Parameter.swift | 1 + .../Path Item/ParameterLocation.swift | 1 + .../Path Item/ParameterSchema.swift | 2 + Sources/OpenAPIKit/Path Item/PathItem.swift | 3 ++ Sources/OpenAPIKit/Request.swift | 7 ++- Sources/OpenAPIKit/Response.swift | 18 ++++++-- .../Schema Object/SchemaObject.swift | 1 + .../Schema Object/TypesAndFormats.swift | 14 ++++++ Sources/OpenAPIKit/SecurityScheme.swift | 11 +++-- Sources/OpenAPIKit/Server.swift | 24 +++++++--- Sources/OpenAPIKit/Tag.swift | 3 ++ .../OpenAPIKit/Utility/Optional+ZipWith.swift | 2 +- Sources/OpenAPIKit/XML.swift | 3 ++ Tests/OpenAPIKitTests/ComponentsTests.swift | 42 +++++++++++++++++ Tests/OpenAPIKitTests/DocumentInfoTests.swift | 6 ++- .../OpenAPIKitTests/JSONReferenceTests.swift | 10 ++++ 31 files changed, 224 insertions(+), 62 deletions(-) delete mode 100644 Sources/OpenAPIKit/Exports.swift diff --git a/Sources/OpenAPIKit/CodableVendorExtendable.swift b/Sources/OpenAPIKit/CodableVendorExtendable.swift index 61a2bcfbb..1cfd59f4b 100644 --- a/Sources/OpenAPIKit/CodableVendorExtendable.swift +++ b/Sources/OpenAPIKit/CodableVendorExtendable.swift @@ -8,7 +8,21 @@ import Foundation import AnyCodable -protocol ExtendableCodingKey: CodingKey, Equatable { +/// A `VendorExtendable` type is a type that supports arbitrary +/// additions as long as those additions are keyed by strings starting +/// with "x-" (e.g. "x-customThing"). +public protocol VendorExtendable { + typealias VendorExtensions = [String: AnyCodable] + + /// Dictionary of vendor extensions. + /// + /// These should be of the form: + /// `[ "x-extensionKey": ]` + /// where the values are anything codable. + var vendorExtensions: VendorExtensions { get } +} + +internal protocol ExtendableCodingKey: CodingKey, Equatable { /// An array of all keys that are not vendor extensions. static var allBuiltinKeys: [Self] { get } @@ -24,34 +38,32 @@ protocol ExtendableCodingKey: CodingKey, Equatable { extension ExtendableCodingKey { /// Returns a builtin key if possible, but assumes any other /// key is an extended key. - public static func key(for value: String) -> Self { + internal static func key(for value: String) -> Self { return Self(stringValue: value) ?? .extendedKey(for: value) } } -public protocol VendorExtendable { - typealias VendorExtensions = [String: AnyCodable] - - /// Dictionary of vendor extensions. - /// - /// These should be of the form: - /// `[ "x-extensionKey": ]` - /// where the values are anything codable. - var vendorExtensions: VendorExtensions { get } -} - -protocol CodableVendorExtendable: VendorExtendable { +internal protocol CodableVendorExtendable: VendorExtendable { associatedtype CodingKeys: ExtendableCodingKey } -enum VendorExtensionDecodingError: Swift.Error { +internal enum VendorExtensionDecodingError: Swift.Error, CustomStringConvertible { case selfIsArrayNotDict case foundNonStringKeys + + var description: String { + switch self { + case .selfIsArrayNotDict: + return "Tried to get vendor extensions on a list. Vendor extensions are necessarily keyed and therefore can only be retrieved from hashes." + case .foundNonStringKeys: + return "Vendor extension keys must be string values." + } + } } extension CodableVendorExtendable { - public static func extensions(from decoder: Decoder) throws -> VendorExtensions { + internal static func extensions(from decoder: Decoder) throws -> VendorExtensions { let decoded = try AnyCodable(from: decoder).value @@ -80,7 +92,7 @@ extension CodableVendorExtendable { return extensions.mapValues(AnyCodable.init) } - public func encodeExtensions(to container: inout T) throws where T.Key == Self.CodingKeys { + internal func encodeExtensions(to container: inout T) throws where T.Key == Self.CodingKeys { for (key, value) in vendorExtensions { let xKey = key.starts(with: "x-") ? key : "x-\(key)" try container.encode(value, forKey: .extendedKey(for: xKey)) diff --git a/Sources/OpenAPIKit/Components.swift b/Sources/OpenAPIKit/Components.swift index efb2358c5..11a312970 100644 --- a/Sources/OpenAPIKit/Components.swift +++ b/Sources/OpenAPIKit/Components.swift @@ -9,7 +9,10 @@ import Foundation import OrderedDictionary extension OpenAPI { - /// What the spec calls the "Components Object". + /// OpenAPI Spec "Components Object". + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#components-object + /// /// This is a place to put reusable components to /// be referenced from other parts of the spec. public struct Components: Equatable { @@ -50,7 +53,7 @@ extension OpenAPI { securitySchemes: [:] ) - var isEmpty: Bool { + public var isEmpty: Bool { return self == .noComponents } } @@ -237,7 +240,7 @@ extension OpenAPI.Components: Decodable { } extension OpenAPI.Components { - enum CodingKeys: String, CodingKey { + internal enum CodingKeys: String, CodingKey { case schemas case responses case parameters diff --git a/Sources/OpenAPIKit/Content.swift b/Sources/OpenAPIKit/Content.swift index 6f0c6b078..5af0a5976 100644 --- a/Sources/OpenAPIKit/Content.swift +++ b/Sources/OpenAPIKit/Content.swift @@ -11,6 +11,9 @@ import OrderedDictionary import AnyCodable extension OpenAPI { + /// OpenAPI Spec "Media Type Object" + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#media-type-object public struct Content: Equatable, CodableVendorExtendable { public var schema: Either, JSONSchema> public var example: AnyCodable? @@ -157,9 +160,9 @@ extension OpenAPI.Content: Decodable { } extension OpenAPI.Content { - enum CodingKeys: ExtendableCodingKey { + internal enum CodingKeys: ExtendableCodingKey { case schema - case example + case example // `example` and `examples` are mutually exclusive case examples // `example` and `examples` are mutually exclusive case encoding case extended(String) diff --git a/Sources/OpenAPIKit/ContentEncoding.swift b/Sources/OpenAPIKit/ContentEncoding.swift index e43d7968c..6417b45e3 100644 --- a/Sources/OpenAPIKit/ContentEncoding.swift +++ b/Sources/OpenAPIKit/ContentEncoding.swift @@ -6,6 +6,9 @@ // extension OpenAPI.Content { + /// OpenAPI Spec "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 diff --git a/Sources/OpenAPIKit/Discriminator.swift b/Sources/OpenAPIKit/Discriminator.swift index 263f148cc..fc96ccbe6 100644 --- a/Sources/OpenAPIKit/Discriminator.swift +++ b/Sources/OpenAPIKit/Discriminator.swift @@ -8,6 +8,9 @@ import Foundation extension OpenAPI { + /// OpenAPI Spec "Disciminator Object" + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#discriminator-object public struct Discriminator: Equatable { public let propertyName: String public let mapping: [String: String]? diff --git a/Sources/OpenAPIKit/Document.swift b/Sources/OpenAPIKit/Document.swift index c62d9e385..b3eb6642d 100644 --- a/Sources/OpenAPIKit/Document.swift +++ b/Sources/OpenAPIKit/Document.swift @@ -11,6 +11,8 @@ import Poly extension OpenAPI { /// The root of an OpenAPI 3.0 document. + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md public struct Document: Equatable { public var openAPIVersion: Version public var info: Info @@ -45,6 +47,10 @@ extension OpenAPI { /// If the security scheme is of type "oauth2" or "openIdConnect", /// then the value is a list of scope names required for the execution. /// For other security scheme types, the array MUST be empty. + /// + /// OpenAPI Spec "Security Requirement Object" + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#security-requirement-object public typealias SecurityRequirement = [JSONReference: [String]] } diff --git a/Sources/OpenAPIKit/DocumentInfo.swift b/Sources/OpenAPIKit/DocumentInfo.swift index fda9cff39..e3d5430ab 100644 --- a/Sources/OpenAPIKit/DocumentInfo.swift +++ b/Sources/OpenAPIKit/DocumentInfo.swift @@ -8,6 +8,9 @@ import Foundation extension OpenAPI.Document { + /// OpenAPI Spec "Info Object" + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#info-object public struct Info: Equatable { public let title: String public let description: String? @@ -64,7 +67,7 @@ extension OpenAPI.Document.Info.License { } public static var MIT: Self { - return .init(name: "MIT") + return .init(name: "MIT", url: URL(string: "https://www.mit.edu/~amini/LICENSE.md")!) } // MARK: Apache License @@ -73,7 +76,7 @@ extension OpenAPI.Document.Info.License { } public static var apache2: Self { - return .init(name: "Apache 2.0") + return .init(name: "Apache 2.0", url: URL(string: "https://www.apache.org/licenses/LICENSE-2.0.txt")!) } } @@ -99,7 +102,7 @@ extension OpenAPI.Document.Info.License: Decodable { } extension OpenAPI.Document.Info.License { - enum CodingKeys: String, CodingKey { + internal enum CodingKeys: String, CodingKey { case name case url } @@ -130,7 +133,7 @@ extension OpenAPI.Document.Info.Contact: Decodable { } extension OpenAPI.Document.Info.Contact { - enum CodingKeys: String, CodingKey { + internal enum CodingKeys: String, CodingKey { case name case url case email @@ -174,7 +177,7 @@ extension OpenAPI.Document.Info: Decodable { } extension OpenAPI.Document.Info { - enum CodingKeys: String, CodingKey { + internal enum CodingKeys: String, CodingKey { case title case description case termsOfService diff --git a/Sources/OpenAPIKit/Example.swift b/Sources/OpenAPIKit/Example.swift index ae3cf3135..17ee9be07 100644 --- a/Sources/OpenAPIKit/Example.swift +++ b/Sources/OpenAPIKit/Example.swift @@ -11,6 +11,9 @@ import OrderedDictionary import AnyCodable extension OpenAPI { + /// OpenAPI Spec "Example Object" + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#example-object public struct Example: Equatable, CodableVendorExtendable { public let summary: String? public let description: String? @@ -89,7 +92,11 @@ extension OpenAPI.Example: Decodable { let container = try decoder.container(keyedBy: CodingKeys.self) guard !(container.contains(.externalValue) && container.contains(.value)) else { - throw Error.foundBothInternalAndExternalExamples + throw InconsistencyError( + subjectName: "example value", + details: "Found both `value` and `externalValue` keys in an Example. You must specify one or the other.", + codingPath: container.codingPath + ) } let externalValue = try container.decodeIfPresent(URL.self, forKey: .externalValue) @@ -103,14 +110,10 @@ extension OpenAPI.Example: Decodable { vendorExtensions = try Self.extensions(from: decoder) } - - public enum Error: Swift.Error { - case foundBothInternalAndExternalExamples - } } extension OpenAPI.Example { - enum CodingKeys: ExtendableCodingKey { + internal enum CodingKeys: ExtendableCodingKey { case summary case description case value diff --git a/Sources/OpenAPIKit/Exports.swift b/Sources/OpenAPIKit/Exports.swift deleted file mode 100644 index 0e2557e42..000000000 --- a/Sources/OpenAPIKit/Exports.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// Exports.swift -// OpenAPIKit -// -// Created by Mathew Polzin on 12/15/19. -// - -@_exported import AnyCodable diff --git a/Sources/OpenAPIKit/ExternalDoc.swift b/Sources/OpenAPIKit/ExternalDoc.swift index 8d783ff6e..65d7c744a 100644 --- a/Sources/OpenAPIKit/ExternalDoc.swift +++ b/Sources/OpenAPIKit/ExternalDoc.swift @@ -9,6 +9,7 @@ import Foundation extension OpenAPI { /// OpenAPI Spec "External Documentation Object" + /// /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#external-documentation-object public struct ExternalDocumentation: Equatable { public let description: String? diff --git a/Sources/OpenAPIKit/Header.swift b/Sources/OpenAPIKit/Header.swift index 888595550..4d08c1196 100644 --- a/Sources/OpenAPIKit/Header.swift +++ b/Sources/OpenAPIKit/Header.swift @@ -11,12 +11,16 @@ import OrderedDictionary import AnyCodable extension OpenAPI { + /// OpenAPI Spec "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 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 typealias Map = OrderedDictionary, Header>> diff --git a/Sources/OpenAPIKit/JSONReference.swift b/Sources/OpenAPIKit/JSONReference.swift index 4dfe2ca01..ccba9c6f7 100644 --- a/Sources/OpenAPIKit/JSONReference.swift +++ b/Sources/OpenAPIKit/JSONReference.swift @@ -132,8 +132,11 @@ public enum JSONReference: Equatabl } } - /// A JSON Reference path, as described by the JSON pointer specification + /// A JSON Reference path. + /// + /// As described by the JSON pointer specification /// at https://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-04 + /// /// and following the URI specification for a "fragment" found here: /// https://tools.ietf.org/html/rfc3986 public struct Path: ExpressibleByArrayLiteral, ExpressibleByStringLiteral, LosslessStringConvertible, RawRepresentable, Equatable, Hashable { diff --git a/Sources/OpenAPIKit/OAuthFlows.swift b/Sources/OpenAPIKit/OAuthFlows.swift index 3f0ad0840..193697b92 100644 --- a/Sources/OpenAPIKit/OAuthFlows.swift +++ b/Sources/OpenAPIKit/OAuthFlows.swift @@ -9,6 +9,9 @@ import Foundation import OrderedDictionary extension OpenAPI { + /// OpenAPI Spec "Oauth Flows Object" + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#oauth-flows-object public struct OAuthFlows: Equatable { public let implicit: Implicit? public let password: Password? diff --git a/Sources/OpenAPIKit/OpenAPI.swift b/Sources/OpenAPIKit/OpenAPI.swift index 9fd761058..c1d8145aa 100644 --- a/Sources/OpenAPIKit/OpenAPI.swift +++ b/Sources/OpenAPIKit/OpenAPI.swift @@ -32,6 +32,20 @@ extension OpenAPI { } } + /// An `OpenAPI.Error` can be constructed from any error thrown while decoding + /// an OpenAPI document. This wrapper provides a superior human-readable error + /// and a human readable coding path. + /// + /// Example: + /// + /// do { + /// document = try JSONDecoder().decode(OpenAPI.Document.self, from: ...) + /// } catch let error { + /// let prettyError = OpenAPI.Error(from: error) + /// print(prettyError.localizedDescription) + /// print(prettyError.codingPathString) + /// } + /// public struct Error: Swift.Error { public let localizedDescription: String diff --git a/Sources/OpenAPIKit/Path Item/Operation.swift b/Sources/OpenAPIKit/Path Item/Operation.swift index f9086aa50..c5405f291 100644 --- a/Sources/OpenAPIKit/Path Item/Operation.swift +++ b/Sources/OpenAPIKit/Path Item/Operation.swift @@ -10,6 +10,7 @@ import Poly extension OpenAPI.PathItem { /// OpenAPI Spec "Operation Object" + /// /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#operation-object public struct Operation: Equatable { public var tags: [String]? diff --git a/Sources/OpenAPIKit/Path Item/Parameter.swift b/Sources/OpenAPIKit/Path Item/Parameter.swift index 5684ea604..bdb210325 100644 --- a/Sources/OpenAPIKit/Path Item/Parameter.swift +++ b/Sources/OpenAPIKit/Path Item/Parameter.swift @@ -10,6 +10,7 @@ import Poly extension OpenAPI.PathItem { /// OpenAPI Spec "Parameter Object" + /// /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-object public struct Parameter: Equatable { public var name: String diff --git a/Sources/OpenAPIKit/Path Item/ParameterLocation.swift b/Sources/OpenAPIKit/Path Item/ParameterLocation.swift index a2eebddfa..f50e9b137 100644 --- a/Sources/OpenAPIKit/Path Item/ParameterLocation.swift +++ b/Sources/OpenAPIKit/Path Item/ParameterLocation.swift @@ -7,6 +7,7 @@ extension OpenAPI.PathItem.Parameter { /// OpenAPI Spec "Parameter Object" location-specific configuration. + /// /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-locations public enum Location: Equatable { case query(required: Bool, allowEmptyValue: Bool) diff --git a/Sources/OpenAPIKit/Path Item/ParameterSchema.swift b/Sources/OpenAPIKit/Path Item/ParameterSchema.swift index 6f77023b0..13b8a2e9f 100644 --- a/Sources/OpenAPIKit/Path Item/ParameterSchema.swift +++ b/Sources/OpenAPIKit/Path Item/ParameterSchema.swift @@ -10,7 +10,9 @@ import AnyCodable extension OpenAPI.PathItem.Parameter { /// OpenAPI Spec "Parameter Object" schema and style configuration. + /// /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-object + /// /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#style-values public struct Schema: Equatable { public let style: Style diff --git a/Sources/OpenAPIKit/Path Item/PathItem.swift b/Sources/OpenAPIKit/Path Item/PathItem.swift index 85d898e95..9c5956126 100644 --- a/Sources/OpenAPIKit/Path Item/PathItem.swift +++ b/Sources/OpenAPIKit/Path Item/PathItem.swift @@ -11,7 +11,9 @@ import OrderedDictionary extension OpenAPI { /// OpenAPI Spec "Paths Object" path field pattern support. + /// /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#paths-object + /// /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#patterned-fields public struct Path: RawRepresentable, Equatable, Hashable { public let components: [String] @@ -41,6 +43,7 @@ extension OpenAPI.Path: ExpressibleByStringLiteral { extension OpenAPI { /// OpenAPI Spec "Path Item Object" + /// /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#path-item-object public struct PathItem: Equatable { public var summary: String? diff --git a/Sources/OpenAPIKit/Request.swift b/Sources/OpenAPIKit/Request.swift index 102b7f64f..17dfa6add 100644 --- a/Sources/OpenAPIKit/Request.swift +++ b/Sources/OpenAPIKit/Request.swift @@ -9,6 +9,9 @@ import Foundation import Poly extension OpenAPI { + /// OpenAPI Spec "Request Body Object" + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#request-body-object public struct Request: Equatable { public let description: String? public let content: Content.Map @@ -26,13 +29,15 @@ extension OpenAPI { // MARK: - Codable -extension OpenAPI.Request: Encodable { +extension OpenAPI.Request { private enum CodingKeys: String, CodingKey { case description case content case required } +} +extension OpenAPI.Request: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) diff --git a/Sources/OpenAPIKit/Response.swift b/Sources/OpenAPIKit/Response.swift index b4ad2e991..3c174776c 100644 --- a/Sources/OpenAPIKit/Response.swift +++ b/Sources/OpenAPIKit/Response.swift @@ -10,6 +10,9 @@ import Poly import OrderedDictionary extension OpenAPI { + /// OpenAPI Spec "Response Object" + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#response-object public struct Response: Equatable { public let description: String public let headers: Header.Map? @@ -40,11 +43,16 @@ extension OpenAPI.Response { case status(code: Int) public enum Range: String { - case _100 = "1XX" - case _200 = "2XX" - case _300 = "3XX" - case _400 = "4XX" - case _500 = "5XX" + /// Status Code `100-199` + case information = "1XX" + /// Status Code `200-299` + case success = "2XX" + /// Status Code `300-399` + case redirect = "3XX" + /// Status Code `400-499` + case clientError = "4XX" + /// Status Code `500-599` + case serverError = "5XX" } public var rawValue: String { diff --git a/Sources/OpenAPIKit/Schema Object/SchemaObject.swift b/Sources/OpenAPIKit/Schema Object/SchemaObject.swift index e534c702e..a0ca55f9d 100644 --- a/Sources/OpenAPIKit/Schema Object/SchemaObject.swift +++ b/Sources/OpenAPIKit/Schema Object/SchemaObject.swift @@ -10,6 +10,7 @@ import AnyCodable import Poly /// OpenAPI "Schema Object" +/// /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#schema-object public enum JSONSchema: Equatable, JSONSchemaContext { case boolean(Context) diff --git a/Sources/OpenAPIKit/Schema Object/TypesAndFormats.swift b/Sources/OpenAPIKit/Schema Object/TypesAndFormats.swift index 91d6a7419..ce92e0f58 100644 --- a/Sources/OpenAPIKit/Schema Object/TypesAndFormats.swift +++ b/Sources/OpenAPIKit/Schema Object/TypesAndFormats.swift @@ -246,6 +246,9 @@ extension JSONTypeFormat { case byte case binary case date + /// A string instance is valid against this attribute if it is a valid + /// date representation as defined by + /// https://tools.ietf.org/html/rfc3339#section-5.6 case dateTime case password case other(String) @@ -294,6 +297,17 @@ extension JSONTypeFormat.StringFormat { public enum Extended: String, Equatable { case uuid = "uuid" case email = "email" + case hostname = "hostname" + case ipv4 = "ipv4" + case ipv6 = "ipv6" + /// A string instance is valid against this attribute if it is a valid + /// URI, according to + /// https://tools.ietf.org/html/rfc3986 + case uri = "uri" + /// A string instance is valid against this attribute if it is a valid + /// URI, according to + /// https://tools.ietf.org/html/rfc3986 + case uriReference = "uriref" } public static func extended(_ format: Extended) -> Self { diff --git a/Sources/OpenAPIKit/SecurityScheme.swift b/Sources/OpenAPIKit/SecurityScheme.swift index 8b9071fcc..39e935b4b 100644 --- a/Sources/OpenAPIKit/SecurityScheme.swift +++ b/Sources/OpenAPIKit/SecurityScheme.swift @@ -8,6 +8,9 @@ import Foundation extension OpenAPI { + /// OpenAPI Spec "Security Scheme Object" + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#security-scheme-object public struct SecurityScheme: Equatable { public let type: SecurityType public let description: String? @@ -107,14 +110,14 @@ extension OpenAPI.SecurityScheme: Decodable { } } - static func decodeAPIKey(from container: KeyedDecodingContainer) throws -> (name: String, location: Location) { + internal static func decodeAPIKey(from container: KeyedDecodingContainer) throws -> (name: String, location: Location) { return try ( name: container.decode(String.self, forKey: .name), location: container.decode(Location.self, forKey: .location) ) } - static func decodeHTTP(from container: KeyedDecodingContainer) throws -> (scheme: String, bearerFormat: String?) { + internal static func decodeHTTP(from container: KeyedDecodingContainer) throws -> (scheme: String, bearerFormat: String?) { return try ( scheme: container.decode(String.self, forKey: .scheme), bearerFormat: container.decodeIfPresent(String.self, forKey: .bearerFormat) @@ -123,7 +126,7 @@ extension OpenAPI.SecurityScheme: Decodable { } extension OpenAPI.SecurityScheme { - enum CodingKeys: String, CodingKey { + internal enum CodingKeys: String, CodingKey { case type case description case name @@ -134,7 +137,7 @@ extension OpenAPI.SecurityScheme { case openIdConnectUrl } - enum SecurityTypeName: String, Codable { + internal enum SecurityTypeName: String, Codable { case apiKey case http case oauth2 diff --git a/Sources/OpenAPIKit/Server.swift b/Sources/OpenAPIKit/Server.swift index b5c018fc1..7c0b73dab 100644 --- a/Sources/OpenAPIKit/Server.swift +++ b/Sources/OpenAPIKit/Server.swift @@ -9,6 +9,9 @@ import Foundation import OrderedDictionary extension OpenAPI { + /// OpenAPI Spec "Server Object" + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#server-object public struct Server: Equatable { public let url: URL public let description: String? @@ -25,6 +28,9 @@ extension OpenAPI { } extension OpenAPI.Server { + /// OpenAPI Spec "Server Variable Object" + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#server-variable-object public struct Variable: Equatable { public let `enum`: [String] public let `default`: String @@ -41,13 +47,7 @@ extension OpenAPI.Server { } // MARK: - Codable -extension OpenAPI.Server: Codable { - private enum CodingKeys: String, CodingKey { - case url - case description - case variables - } - +extension OpenAPI.Server: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -55,7 +55,9 @@ extension OpenAPI.Server: Codable { description = try container.decodeIfPresent(String.self, forKey: .description) variables = try container.decodeIfPresent(OrderedDictionary.self, forKey: .variables) ?? [:] } +} +extension OpenAPI.Server: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -69,6 +71,14 @@ extension OpenAPI.Server: Codable { } } +extension OpenAPI.Server { + private enum CodingKeys: String, CodingKey { + case url + case description + case variables + } +} + extension OpenAPI.Server.Variable: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) diff --git a/Sources/OpenAPIKit/Tag.swift b/Sources/OpenAPIKit/Tag.swift index 33edfeed2..21685d630 100644 --- a/Sources/OpenAPIKit/Tag.swift +++ b/Sources/OpenAPIKit/Tag.swift @@ -8,6 +8,9 @@ import Foundation extension OpenAPI { + /// OpenAPI Spec "Tag Object" + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#tag-object public struct Tag: Equatable { public let name: String public let description: String? diff --git a/Sources/OpenAPIKit/Utility/Optional+ZipWith.swift b/Sources/OpenAPIKit/Utility/Optional+ZipWith.swift index 36b6a69c9..597eef171 100644 --- a/Sources/OpenAPIKit/Utility/Optional+ZipWith.swift +++ b/Sources/OpenAPIKit/Utility/Optional+ZipWith.swift @@ -8,6 +8,6 @@ /// Zip two optionals together with the given operation performed on /// the unwrapped contents. If either optional is nil, the zip /// yields nil. -func zip(_ left: X?, _ right: Y?, with fn: (X, Y) -> Z) -> Z? { +internal func zip(_ left: X?, _ right: Y?, with fn: (X, Y) -> Z) -> Z? { return left.flatMap { lft in right.map { rght in fn(lft, rght) }} } diff --git a/Sources/OpenAPIKit/XML.swift b/Sources/OpenAPIKit/XML.swift index b796440e3..5a5520577 100644 --- a/Sources/OpenAPIKit/XML.swift +++ b/Sources/OpenAPIKit/XML.swift @@ -8,6 +8,9 @@ import Foundation extension OpenAPI { + /// OpenAPI Spec "XML Object" + /// + /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#xml-object public struct XML: Equatable { public let name: String? public let namespace: URL? diff --git a/Tests/OpenAPIKitTests/ComponentsTests.swift b/Tests/OpenAPIKitTests/ComponentsTests.swift index 2ae84767c..76c1c56dd 100644 --- a/Tests/OpenAPIKitTests/ComponentsTests.swift +++ b/Tests/OpenAPIKitTests/ComponentsTests.swift @@ -57,6 +57,48 @@ final class ComponentsTests: XCTestCase { XCTAssertThrowsError(try components.reference(named: "hello", ofType: OpenAPI.PathItem.Parameter.self)) } + func test_lookupEachType() throws { + let components = OpenAPI.Components( + schemas: [ + "one": .string + ], + responses: [ + "two": .init(description: "hello", content: [:]) + ], + parameters: [ + "three": .init(name: "hello", parameterLocation: .query, schema: .string) + ], + examples: [ + "four": .init(value: .init(URL(string: "hello.com/hello")!)) + ], + requestBodies: [ + "five": .init(content: [:]) + ], + headers: [ + "six": .init(schema: .string) + ], + securitySchemes: [ + "seven": .apiKey(name: "hello", location: .cookie) + ] + ) + + 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 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) + let ref7 = try components.reference(named: "seven", ofType: OpenAPI.SecurityScheme.self) + + XCTAssertEqual(components[ref1], .string) + XCTAssertEqual(components[ref2], .init(description: "hello", content: [:])) + XCTAssertEqual(components[ref3], .init(name: "hello", parameterLocation: .query, schema: .string)) + XCTAssertEqual(components[ref4], .init(value: .init(URL(string: "hello.com/hello")!))) + XCTAssertEqual(components[ref5], .init(content: [:])) + XCTAssertEqual(components[ref6], .init(schema: .string)) + XCTAssertEqual(components[ref7], .apiKey(name: "hello", location: .cookie)) + } + // TODO: write tests } diff --git a/Tests/OpenAPIKitTests/DocumentInfoTests.swift b/Tests/OpenAPIKitTests/DocumentInfoTests.swift index 22331aa2c..94974ee69 100644 --- a/Tests/OpenAPIKitTests/DocumentInfoTests.swift +++ b/Tests/OpenAPIKitTests/DocumentInfoTests.swift @@ -43,7 +43,8 @@ extension DocumentInfoTests { assertJSONEquivalent(encodedLicense, """ { - "name" : "MIT" + "name" : "MIT", + "url" : "https:\\/\\/www.mit.edu\\/~amini\\/LICENSE.md" } """ ) @@ -53,7 +54,8 @@ extension DocumentInfoTests { let licenseData = """ { - "name" : "MIT" + "name" : "MIT", + "url" : "https:\\/\\/www.mit.edu\\/~amini\\/LICENSE.md" } """.data(using: .utf8)! let license = try testDecoder.decode(OpenAPI.Document.Info.License.self, from: licenseData) diff --git a/Tests/OpenAPIKitTests/JSONReferenceTests.swift b/Tests/OpenAPIKitTests/JSONReferenceTests.swift index 906a20265..69fdba579 100644 --- a/Tests/OpenAPIKitTests/JSONReferenceTests.swift +++ b/Tests/OpenAPIKitTests/JSONReferenceTests.swift @@ -110,6 +110,16 @@ final class JSONReferenceTests: XCTestCase { XCTAssertEqual(t1.stringValue, "~hello/world") XCTAssertEqual(t1.rawValue, "~0hello~1world") } + + 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/examples/hello") + XCTAssertEqual(JSONReference.component(named: "hello").absoluteString, "#/components/requestBodies/hello") + XCTAssertEqual(JSONReference.component(named: "hello").absoluteString, "#/components/headers/hello") + XCTAssertEqual(JSONReference.component(named: "hello").absoluteString, "#/components/securitySchemes/hello") + } } // MARK: Codable