// // OpenAPITypes.swift // JSONAPIOpenAPI // // Created by Mathew Polzin on 1/13/19. // import AnyCodable public protocol OpenAPINodeType { static var openAPINode: OpenAPI.JSONNode { get } } public protocol SwiftTyped { associatedtype SwiftType: Codable, Equatable } public protocol OpenAPIFormat: SwiftTyped, Codable, Equatable { static var unspecified: Self { get } var jsonType: OpenAPI.JSONType { get } } public protocol JSONNodeContext { var required: Bool { get } } public extension OpenAPI { enum JSONType: String, Codable { case boolean = "boolean" case object = "object" case array = "array" case number = "number" case integer = "integer" case string = "string" } enum JSONTypeFormat: Equatable { case boolean(BooleanFormat) case object(ObjectFormat) case array(ArrayFormat) case number(NumberFormat) case integer(IntegerFormat) case string(StringFormat) } /// A JSON Node is what OpenAPI calls a /// "Schema Object" enum JSONNode { case boolean(Context) indirect case object(Context, ObjectContext) indirect case array(Context, ArrayContext) case number(Context, NumericContext) case integer(Context, NumericContext) case string(Context, StringContext) indirect case allOf([JSONNode]) indirect case oneOf([JSONNode]) indirect case anyOf([JSONNode]) indirect case not(JSONNode) } } public extension OpenAPI.JSONTypeFormat { public enum BooleanFormat: String, Equatable, OpenAPIFormat { case generic = "" public typealias SwiftType = Bool public static var unspecified: BooleanFormat { return .generic } public var jsonType: OpenAPI.JSONType { return .boolean } } public enum ObjectFormat: String, Equatable, OpenAPIFormat { case generic = "" public typealias SwiftType = AnyCodable public static var unspecified: ObjectFormat { return .generic } public var jsonType: OpenAPI.JSONType { return .object } } public enum ArrayFormat: String, Equatable, OpenAPIFormat { case generic = "" public typealias SwiftType = [AnyCodable] public static var unspecified: ArrayFormat { return .generic } public var jsonType: OpenAPI.JSONType { return .array } } public enum NumberFormat: String, Equatable, OpenAPIFormat { case generic = "" case float = "float" case double = "double" public typealias SwiftType = Double public static var unspecified: NumberFormat { return .generic } public var jsonType: OpenAPI.JSONType { return .number } } public enum IntegerFormat: String, Equatable, OpenAPIFormat { case generic = "" case int32 = "int32" case int64 = "int64" public typealias SwiftType = Int public static var unspecified: IntegerFormat { return .generic } public var jsonType: OpenAPI.JSONType { return .integer } } public enum StringFormat: String, Equatable, OpenAPIFormat { case generic = "" case byte = "byte" case binary = "binary" case date = "date" case dateTime = "date-time" case password = "password" public typealias SwiftType = String public static var unspecified: StringFormat { return .generic } public var jsonType: OpenAPI.JSONType { return .string } } public var jsonType: OpenAPI.JSONType { switch self { case .boolean: return .boolean case .object: return .object case .array: return .array case .number: return .number case .integer: return .integer case .string: return .string } } } extension OpenAPI.JSONNode { public struct Context: JSONNodeContext, Equatable { public let format: Format public let required: Bool public let nullable: Bool /// The OpenAPI spec calls this "enum" /// If not specified, it is assumed that any /// value of the given format is allowed. public let allowedValues: [Format.SwiftType]? public init(format: Format, required: Bool, nullable: Bool = false, allowedValues: [Format.SwiftType]? = nil) { self.format = format self.required = required self.nullable = nullable self.allowedValues = allowedValues } /// Return the optional version of this Context public func optionalContext() -> Context { return .init(format: format, required: false, nullable: nullable, allowedValues: allowedValues) } /// Return the required version of this context public func requiredContext() -> Context { return .init(format: format, required: true, nullable: nullable, allowedValues: allowedValues) } /// Return the nullable version of this context public func nullableContext() -> Context { return .init(format: format, required: required, nullable: true, allowedValues: allowedValues) } } public struct NumericContext { /// A numeric instance is valid only if division by this keyword's value results in an integer. Defaults to nil. public let multipleOf: Double? public let maximum: Double? public let exclusiveMaximum: Double? public let minimum: Double? public let exclusiveMinimum: Double? public init(multipleOf: Double? = nil, maximum: Double? = nil, exclusiveMaximum: Double? = nil, minimum: Double? = nil, exclusiveMinimum: Double? = nil) { self.multipleOf = multipleOf self.maximum = maximum self.exclusiveMaximum = exclusiveMaximum self.minimum = minimum self.exclusiveMinimum = exclusiveMinimum } } public struct StringContext { public let maxLength: Int? public let minLength: Int /// Regular expression public let pattern: String? public init(maxLength: Int? = nil, minLength: Int = 0, pattern: String? = nil) { self.maxLength = maxLength self.minLength = minLength self.pattern = pattern } } public struct ArrayContext { /// A JSON Type Node that describes /// the type of each element in the array. public let items: OpenAPI.JSONNode /// Maximum number of items in array. public let maxItems: Int? /// Minimum number of items in array. /// Defaults to 0. public let minItems: Int /// Setting to true indicates all /// elements of the array are expected /// to be unique. Defaults to false. public let uniqueItems: Bool public init(items: OpenAPI.JSONNode, maxItems: Int? = nil, minItems: Int = 0, uniqueItems: Bool = false) { self.items = items self.maxItems = maxItems self.minItems = minItems self.uniqueItems = uniqueItems } } public struct ObjectContext { public let maxProperties: Int? public let minProperties: Int public let properties: [String: OpenAPI.JSONNode] public let additionalProperties: [String: OpenAPI.JSONNode]? /* // NOTE that an object's required properties // array is determined by looking at its properties' // required Bool. public let required: [String] */ public init(properties: [String: OpenAPI.JSONNode], additionalProperties: [String: OpenAPI.JSONNode]? = nil, maxProperties: Int? = nil, minProperties: Int = 0) { self.properties = properties self.additionalProperties = additionalProperties self.maxProperties = maxProperties self.minProperties = minProperties } } public var jsonTypeFormat: OpenAPI.JSONTypeFormat? { switch self { case .boolean(let context): return .boolean(context.format) case .object(let context, _): return .object(context.format) case .array(let context, _): return .array(context.format) case .number(let context, _): return .number(context.format) case .integer(let context, _): return .integer(context.format) case .string(let context, _): return .string(context.format) case .allOf, .oneOf, .anyOf, .not: return nil } } public var required: Bool { switch self { case .boolean(let contextA as JSONNodeContext), .object(let contextA as JSONNodeContext, _), .array(let contextA as JSONNodeContext, _), .number(let contextA as JSONNodeContext, _), .integer(let contextA as JSONNodeContext, _), .string(let contextA as JSONNodeContext, _): return contextA.required case .allOf, .oneOf, .anyOf, .not: return true } } /// Return the optional version of this JSONNode public func optionalNode() -> OpenAPI.JSONNode { switch self { case .boolean(let context): return .boolean(context.optionalContext()) case .object(let contextA, let contextB): return .object(contextA.optionalContext(), contextB) case .array(let contextA, let contextB): return .array(contextA.optionalContext(), contextB) case .number(let context, let contextB): return .number(context.optionalContext(), contextB) case .integer(let context, let contextB): return .integer(context.optionalContext(), contextB) case .string(let context, let contextB): return .string(context.optionalContext(), contextB) case .allOf, .oneOf, .anyOf, .not: return self } } /// Return the required version of this JSONNode public func requiredNode() -> OpenAPI.JSONNode { switch self { case .boolean(let context): return .boolean(context.requiredContext()) case .object(let contextA, let contextB): return .object(contextA.requiredContext(), contextB) case .array(let contextA, let contextB): return .array(contextA.requiredContext(), contextB) case .number(let context, let contextB): return .number(context.requiredContext(), contextB) case .integer(let context, let contextB): return .integer(context.requiredContext(), contextB) case .string(let context, let contextB): return .string(context.requiredContext(), contextB) case .allOf, .oneOf, .anyOf, .not: return self } } /// Return the nullable version of this JSONNode public func nullableNode() -> OpenAPI.JSONNode { switch self { case .boolean(let context): return .boolean(context.nullableContext()) case .object(let contextA, let contextB): return .object(contextA.nullableContext(), contextB) case .array(let contextA, let contextB): return .array(contextA.nullableContext(), contextB) case .number(let context, let contextB): return .number(context.nullableContext(), contextB) case .integer(let context, let contextB): return .integer(context.nullableContext(), contextB) case .string(let context, let contextB): return .string(context.nullableContext(), contextB) case .allOf, .oneOf, .anyOf, .not: return self } } }