Files
JSONAPI/Sources/JSONAPIOpenAPI/OpenAPITypes.swift
T

397 lines
10 KiB
Swift
Raw Normal View History

//
// OpenAPITypes.swift
// JSONAPIOpenAPI
//
// Created by Mathew Polzin on 1/13/19.
//
import AnyCodable
public protocol OpenAPINodeType {
static var openAPINode: JSONNode { get }
}
public protocol SwiftTyped {
2019-01-14 23:14:25 -08:00
associatedtype SwiftType: Codable, Equatable
}
public protocol OpenAPIFormat: SwiftTyped, Codable, Equatable {
static var unspecified: Self { get }
var jsonType: JSONType { get }
}
public protocol JSONNodeContext {
var required: Bool { get }
}
public enum JSONType: String, Codable {
case boolean = "boolean"
case object = "object"
case array = "array"
case number = "number"
case integer = "integer"
case string = "string"
}
public enum JSONTypeFormat: Equatable {
case boolean(BooleanFormat)
case object(ObjectFormat)
case array(ArrayFormat)
case number(NumberFormat)
case integer(IntegerFormat)
case string(StringFormat)
public enum BooleanFormat: String, Equatable, OpenAPIFormat {
case generic = ""
public typealias SwiftType = Bool
public static var unspecified: BooleanFormat {
return .generic
}
public var jsonType: 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: 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: 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: 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: 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: JSONType {
return .string
}
}
public var jsonType: 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
}
}
}
/// A JSON Node is what OpenAPI calls a
/// "Schema Object"
public enum JSONNode {
case boolean(Context<JSONTypeFormat.BooleanFormat>)
indirect case object(Context<JSONTypeFormat.ObjectFormat>, ObjectContext)
indirect case array(Context<JSONTypeFormat.ArrayFormat>, ArrayContext)
case number(Context<JSONTypeFormat.NumberFormat>, NumericContext)
case integer(Context<JSONTypeFormat.IntegerFormat>, NumericContext)
case string(Context<JSONTypeFormat.StringFormat>, StringContext)
indirect case allOf([JSONNode])
indirect case oneOf([JSONNode])
indirect case anyOf([JSONNode])
indirect case not(JSONNode)
2019-01-14 23:14:25 -08:00
public struct Context<Format: OpenAPIFormat>: 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,
2019-01-14 23:14:25 -08:00
nullable: nullable,
allowedValues: allowedValues)
}
/// Return the required version of this context
public func requiredContext() -> Context {
return .init(format: format,
required: true,
2019-01-14 23:14:25 -08:00
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: 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: 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: JSONNode]
public let additionalProperties: [String: 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: JSONNode],
additionalProperties: [String: JSONNode]? = nil,
maxProperties: Int? = nil,
minProperties: Int = 0) {
self.properties = properties
self.additionalProperties = additionalProperties
self.maxProperties = maxProperties
self.minProperties = minProperties
}
}
public var jsonTypeFormat: 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() -> 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() -> 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
}
}
2019-01-14 23:14:25 -08:00
/// Return the nullable version of this JSONNode
public func nullableNode() -> JSONNode {
2019-01-14 23:14:25 -08:00
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
}
}
}