diff --git a/JSONAPI.playground/Pages/OpenAPI Documentation.xcplaygroundpage/Contents.swift b/JSONAPI.playground/Pages/OpenAPI Documentation.xcplaygroundpage/Contents.swift index 01cff69..b9a4f1e 100644 --- a/JSONAPI.playground/Pages/OpenAPI Documentation.xcplaygroundpage/Contents.swift +++ b/JSONAPI.playground/Pages/OpenAPI Documentation.xcplaygroundpage/Contents.swift @@ -3,6 +3,7 @@ import Foundation import JSONAPI import JSONAPIOpenAPI +import Poly // print Entity Schema let encoder = JSONEncoder() @@ -28,3 +29,18 @@ print("Batch Person Document Schema") print("====") print(batchPersonSchemaData.map { String(data: $0, encoding: .utf8)! } ?? "Schema Construction Failed") print("====") + +let tmp: [String: OpenAPIComponents.SchemasDict.RefType] = [ + "BatchPerson": try! BatchPeopleDocument.openAPINodeWithExample() +] + +let components = OpenAPIComponents(schemas: tmp) + +let batchPeopleRef = JSONReference(type: \OpenAPIComponents.schemas, selector: "BatchPerson") + +let tmp2 = JSONNode.reference(batchPeopleRef) + +print("====") +print("====") +//print(String(data: try! encoder.encode(components), encoding: .utf8)!) +print(String(data: try! encoder.encode(tmp2), encoding: .utf8)!) diff --git a/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes+Codable.swift b/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes+Codable.swift index 9322035..d6357c7 100644 --- a/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes+Codable.swift +++ b/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes+Codable.swift @@ -155,6 +155,7 @@ extension JSONNode: Encodable { case oneOf case anyOf case not + case reference = "$ref" } public func encode(to encoder: Encoder) throws { @@ -189,6 +190,27 @@ extension JSONNode: Encodable { var container = encoder.container(keyedBy: SubschemaCodingKeys.self) try container.encode(node, forKey: .not) + + case .reference(let reference): + var container = encoder.container(keyedBy: SubschemaCodingKeys.self) + + try container.encode(reference, forKey: .reference) } } } + +extension JSONReference: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + try container.encode("#/\(Root.refName)/\(refName)/\(selector)") + } +} + +extension RefDict: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + try container.encode(dict) + } +} diff --git a/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes.swift b/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes.swift index c5ac131..87c67e7 100644 --- a/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes.swift +++ b/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes.swift @@ -7,6 +7,7 @@ import AnyCodable import Foundation +import Poly // MARK: Node (i.e. schema) Protocols @@ -249,6 +250,7 @@ public enum JSONNode: Equatable { indirect case one(of: [JSONNode]) indirect case any(of: [JSONNode]) indirect case not(JSONNode) + case reference(JSONReference) public struct Context: JSONNodeContext, Equatable { public let format: Format @@ -451,7 +453,7 @@ public enum JSONNode: Equatable { return .integer(context.format) case .string(let context, _): return .string(context.format) - case .all, .one, .any, .not: + case .all, .one, .any, .not, .reference: return nil } } @@ -465,7 +467,7 @@ public enum JSONNode: Equatable { .integer(let contextA as JSONNodeContext, _), .string(let contextA as JSONNodeContext, _): return contextA.required - case .all, .one, .any, .not: + case .all, .one, .any, .not, .reference: return true } } @@ -485,7 +487,7 @@ public enum JSONNode: Equatable { return .integer(context.optionalContext(), contextB) case .string(let context, let contextB): return .string(context.optionalContext(), contextB) - case .all, .one, .any, .not: + case .all, .one, .any, .not, .reference: return self } } @@ -505,7 +507,7 @@ public enum JSONNode: Equatable { return .integer(context.requiredContext(), contextB) case .string(let context, let contextB): return .string(context.requiredContext(), contextB) - case .all, .one, .any, .not: + case .all, .one, .any, .not, .reference: return self } } @@ -525,7 +527,7 @@ public enum JSONNode: Equatable { return .integer(context.nullableContext(), contextB) case .string(let context, let contextB): return .string(context.nullableContext(), contextB) - case .all, .one, .any, .not: + case .all, .one, .any, .not, .reference: return self } } @@ -545,7 +547,7 @@ public enum JSONNode: Equatable { return .integer(context.with(allowedValues: allowedValues), contextB) case .string(let context, let contextB): return .string(context.with(allowedValues: allowedValues), contextB) - case .all, .one, .any, .not: + case .all, .one, .any, .not, .reference: return self } } @@ -571,7 +573,7 @@ public enum JSONNode: Equatable { return .integer(context.with(example: example), contextB) case .string(let context, let contextB): return .string(context.with(example: example), contextB) - case .all, .one, .any, .not: + case .all, .one, .any, .not, .reference: return self } } @@ -585,3 +587,69 @@ public enum OpenAPICodableError: Swift.Error, Equatable { public enum OpenAPITypeError: Swift.Error, Equatable { case invalidNode } + +/// Anything conforming to RefName knows what to call itself +/// in the context of JSON References. +public protocol RefName { + static var refName: String { get } +} + +public protocol ReferenceRoot: RefName {} + +public protocol ReferenceDict: RefName {} + +/// A RefDict knows what to call itself (Name) and where to +/// look for itself (Root) and it stores a dictionary of +/// JSONNodes (some of which might be other references). +public struct RefDict: ReferenceDict, Equatable { + public static var refName: String { return Name.refName } + + public typealias RefType = JSONNode + + let dict: [String: RefType] + + public init(_ dict: [String: RefType]) { + self.dict = dict + } + + public subscript(_ key: String) -> RefType? { + return dict[key] + } +} + +/// A Reference is the combination of +/// a path to a reference dictionary +/// and a selector that the dictionary is keyed off of. +public struct JSONReference: Equatable { + public let path: PartialKeyPath + public let selector: String + + public var refName: String { + return (type(of: path).valueType as! ReferenceDict.Type).refName + } + + public init(type: KeyPath, + selector: String) { + self.path = type + self.selector = selector + } +} + +/// What the spec calls the "Components Object". +/// This is a place to put reusable components to +/// be referenced from other parts of the spec. +public struct OpenAPIComponents: Equatable, Encodable, ReferenceRoot { + public static var refName: String { return "components" } + + public let schemas: SchemasDict + + public init(schemas: [String: SchemasDict.RefType]) { + self.schemas = SchemasDict(schemas) + } + + public enum SchemasName: RefName { + public static var refName: String { return "schemas" } + } + + public typealias SchemasDict = RefDict +}