Skip to content

Commit

Permalink
Merge pull request #337 from mattpolzin/feature/332/stringformats
Browse files Browse the repository at this point in the history
String Format Updates
  • Loading branch information
mattpolzin authored Nov 5, 2023
2 parents 4a691fa + ea67126 commit da5b380
Show file tree
Hide file tree
Showing 15 changed files with 404 additions and 239 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ extension Int64: OpenAPISchemaType {

extension URL: OpenAPISchemaType {
public static var openAPISchema: JSONSchema {
.string(format: .extended(.uri))
.string(format: .uri)
}
}

extension UUID: OpenAPISchemaType {
public static var openAPISchema: JSONSchema {
.string(format: .extended(.uuid))
.string(format: .uuid)
}
}
16 changes: 14 additions & 2 deletions Sources/OpenAPIKit/Schema Object/JSONSchema+Combining.swift
Original file line number Diff line number Diff line change
Expand Up @@ -473,15 +473,25 @@ extension JSONSchema.StringContext {
if let conflict = conflicting(pattern, other.pattern) {
throw JSONSchemaResolutionError(.attributeConflict(jsonType: .string, name: "pattern", original: conflict.0, new: conflict.1))
}
if let conflict = conflicting(contentMediaType, other.contentMediaType) {
throw JSONSchemaResolutionError(.attributeConflict(jsonType: .string, name: "contentMediaType", original: conflict.0.rawValue, new: conflict.1.rawValue))
}
if let conflict = conflicting(contentEncoding, other.contentEncoding) {
throw JSONSchemaResolutionError(.attributeConflict(jsonType: .string, name: "contentEncoding", original: conflict.0.rawValue, new: conflict.1.rawValue))
}
// explicitly declaring these constants one at a time
// helps the type checker a lot.
let newMaxLength = maxLength ?? other.maxLength
let newMinLength = Self._minLength(self) ?? Self._minLength(other)
let newPattern = pattern ?? other.pattern
let newContentMediaType = contentMediaType ?? other.contentMediaType
let newContentEncoding = contentEncoding ?? other.contentEncoding
return .init(
maxLength: newMaxLength,
minLength: newMinLength,
pattern: newPattern
pattern: newPattern,
contentMediaType: newContentMediaType,
contentEncoding: newContentEncoding
)
}
}
Expand Down Expand Up @@ -660,7 +670,9 @@ extension JSONSchema.StringContext {
return .init(
maxLength: maxLength,
minLength: Self._minLength(self),
pattern: pattern
pattern: pattern,
contentMediaType: contentMediaType,
contentEncoding: contentEncoding
)
}
}
Expand Down
102 changes: 102 additions & 0 deletions Sources/OpenAPIKit/Schema Object/TypesAndFormats.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,108 @@ public protocol OpenAPIFormat: SwiftTyped, Codable, Equatable, RawRepresentable,
var jsonType: JSONType { get }
}

/// These are just the OpenAPIFormats that are specific to this module; there are shared
/// formats in OpenAPIKitCore/Shared/JSONTypeFormat.swift as well.
extension JSONTypeFormat {
/// The allowed "format" properties for `.string` schemas.
public enum StringFormat: RawRepresentable, Equatable {
case generic
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 duration
case email
case hostname
case idnEmail
case idnHostname
case ipv4
case ipv6
/// International version of .uri
case iri
/// International version of .uriReference
case iriReference
case jsonPointer
case password
case regex
case relativeJsonPointer
case time
/// A string instance is valid against this attribute if it is a valid
/// URI, according to
/// https://tools.ietf.org/html/rfc3986
case 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
case uriTemplate
case uuid
case other(String)

public var rawValue: String {
switch self {
case .generic: return ""
case .date: return "date"
case .dateTime: return "date-time"
case .duration: return "duration"
case .email: return "email"
case .hostname: return "hostname"
case .idnEmail: return "idn-email"
case .idnHostname: return "idn-hostname"
case .ipv4: return "ipv4"
case .ipv6: return "ipv6"
case .iri: return "iri"
case .iriReference: return "iri-reference"
case .jsonPointer: return "json-pointer"
case .password: return "password"
case .regex: return "regex"
case .relativeJsonPointer: return "relative-json-pointer"
case .time: return "time"
case .uri: return "uri"
case .uriReference: return "uri-reference"
case .uriTemplate: return "uri-template"
case .uuid: return "uuid"
case .other(let other):
return other
}
}

public init(rawValue: String) {
switch rawValue {
case "": self = .generic
case "date": self = .date
case "date-time": self = .dateTime
case "duration": self = .duration
case "email": self = .email
case "hostname": self = .hostname
case "idn-email": self = .idnEmail
case "idn-hostname": self = .idnHostname
case "ipv4": self = .ipv4
case "ipv6": self = .ipv6
case "iri": self = .iri
case "iri-reference": self = .iriReference
case "json-pointer": self = .jsonPointer
case "password": self = .password
case "regex": self = .regex
case "relative-json-pointer": self = .relativeJsonPointer
case "time": self = .time
case "uri": self = .uri
case "uri-reference": self = .uriReference
case "uri-template": self = .uriTemplate
case "uuid": self = .uuid
default: self = .other(rawValue)
}
}

public typealias SwiftType = String

public static var unspecified: StringFormat {
return .generic
}
}
}

/// A format used when no type is known or any type is allowed.
///
/// There are no built-in formats that do not have an associated
Expand Down
2 changes: 0 additions & 2 deletions Sources/OpenAPIKit/_CoreReExport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ public extension OpenAPI.Response {

public extension JSONSchema {
typealias Permissions = OpenAPIKitCore.Shared.JSONSchemaPermissions
typealias ReferenceContext = OpenAPIKitCore.Shared.ReferenceContext
}

public extension JSONTypeFormat {
Expand All @@ -54,5 +53,4 @@ public extension JSONTypeFormat {
typealias ArrayFormat = OpenAPIKitCore.Shared.ArrayFormat
typealias NumberFormat = OpenAPIKitCore.Shared.NumberFormat
typealias IntegerFormat = OpenAPIKitCore.Shared.IntegerFormat
typealias StringFormat = OpenAPIKitCore.Shared.StringFormat
}
17 changes: 17 additions & 0 deletions Sources/OpenAPIKit30/Schema Object/JSONSchemaContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,23 @@ extension JSONSchema {
return context._minLength
}
}

/// The context that only applies to `.reference` schemas.
public struct ReferenceContext: Equatable {
public let required: Bool

public init(required: Bool = true) {
self.required = required
}

public func requiredContext() -> ReferenceContext {
return .init(required: true)
}

public func optionalContext() -> ReferenceContext {
return .init(required: false)
}
}
}

// MARK: - Codable
Expand Down
75 changes: 75 additions & 0 deletions Sources/OpenAPIKit30/Schema Object/TypesAndFormats.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,81 @@ public protocol OpenAPIFormat: SwiftTyped, Codable, Equatable, RawRepresentable,
var jsonType: JSONType { get }
}

/// These are just the OpenAPIFormats that are specific to this module; there are shared
/// formats in OpenAPIKitCore/Shared/JSONTypeFormat.swift as well.
extension JSONTypeFormat {
/// The allowed "format" properties for `.string` schemas.
public enum StringFormat: RawRepresentable, Equatable {
case generic
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)

public var rawValue: String {
switch self {
case .generic: return ""
case .byte: return "byte"
case .binary: return "binary"
case .date: return "date"
case .dateTime: return "date-time"
case .password: return "password"
case .other(let other):
return other
}
}

public init(rawValue: String) {
switch rawValue {
case "": self = .generic
case "byte": self = .byte
case "binary": self = .binary
case "date": self = .date
case "date-time": self = .dateTime
case "password": self = .password
default: self = .other(rawValue)
}
}

public typealias SwiftType = String

public static var unspecified: StringFormat {
return .generic
}
}
}

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"
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 {
return .other(format.rawValue)
}
}

/// A format used when no type is known or any type is allowed.
///
/// There are no built-in formats that do not have an associated
Expand Down
2 changes: 0 additions & 2 deletions Sources/OpenAPIKit30/_CoreReExport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ public extension OpenAPI.Response {

public extension JSONSchema {
typealias Permissions = OpenAPIKitCore.Shared.JSONSchemaPermissions
typealias ReferenceContext = OpenAPIKitCore.Shared.ReferenceContext
}

public extension JSONTypeFormat {
Expand All @@ -54,5 +53,4 @@ public extension JSONTypeFormat {
typealias ArrayFormat = OpenAPIKitCore.Shared.ArrayFormat
typealias NumberFormat = OpenAPIKitCore.Shared.NumberFormat
typealias IntegerFormat = OpenAPIKitCore.Shared.IntegerFormat
typealias StringFormat = OpenAPIKitCore.Shared.StringFormat
}
45 changes: 39 additions & 6 deletions Sources/OpenAPIKitCompat/Compat30To31.swift
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,12 @@ extension OpenAPIKit30.OpenAPI.SecurityScheme: To31 {
}
}

extension OpenAPIKit30.JSONTypeFormat.StringFormat: To31 {
fileprivate func to31() -> OpenAPIKit.JSONTypeFormat.StringFormat {
.init(rawValue: rawValue)
}
}

extension OpenAPIKit30.JSONTypeFormat: To31 {
fileprivate func to31() -> OpenAPIKit.JSONTypeFormat {
switch self {
Expand All @@ -485,7 +491,7 @@ extension OpenAPIKit30.JSONTypeFormat: To31 {
case .integer(let f):
return .integer(f)
case .string(let f):
return .string(f)
return .string(f.to31())
}
}
}
Expand All @@ -509,6 +515,25 @@ extension OpenAPIKit30.JSONSchema.CoreContext: To31 where Format: OpenAPIKit.Ope
}
}

extension OpenAPIKit30.JSONSchema.CoreContext where Format == OpenAPIKit30.JSONTypeFormat.StringFormat {
fileprivate func to31() -> OpenAPIKit.JSONSchema.CoreContext<OpenAPIKit.JSONTypeFormat.StringFormat> {
OpenAPIKit.JSONSchema.CoreContext<OpenAPIKit.JSONTypeFormat.StringFormat>(
format: format.to31(),
required: `required`,
nullable: nullable,
permissions: permissions,
deprecated: deprecated,
title: title,
description: description,
discriminator: discriminator,
externalDocs: externalDocs?.to31(),
allowedValues: allowedValues,
defaultValue: defaultValue,
examples: [example].compactMap { $0 }
)
}
}

extension OpenAPIKit30.JSONSchema.NumericContext: To31 {
fileprivate func to31() -> OpenAPIKit.JSONSchema.NumericContext {
OpenAPIKit.JSONSchema.NumericContext(
Expand Down Expand Up @@ -551,12 +576,20 @@ extension OpenAPIKit30.JSONSchema.ObjectContext: To31 {
}
}

extension OpenAPIKit30.JSONSchema.StringContext: To31 {
fileprivate func to31() -> OpenAPIKit.JSONSchema.StringContext {
OpenAPIKit.JSONSchema.StringContext(
extension OpenAPIKit30.JSONSchema.StringContext {
fileprivate func to31(format: OpenAPIKit30.JSONTypeFormat.StringFormat) -> OpenAPIKit.JSONSchema.StringContext {
let contentEncoding: OpenAPIKit.OpenAPI.ContentEncoding?
switch format {
case .byte: contentEncoding = .base64
case .binary: contentEncoding = .binary
default: contentEncoding = nil
}

return OpenAPIKit.JSONSchema.StringContext(
maxLength: maxLength,
minLength: OpenAPIKit30.JSONSchema.StringContext._minLength(self),
pattern: pattern
pattern: pattern,
contentEncoding: contentEncoding
)
}
}
Expand All @@ -573,7 +606,7 @@ extension OpenAPIKit30.JSONSchema: To31 {
case .integer(let core, let integral):
schema = .integer(core.to31(), integral.to31())
case .string(let core, let stringy):
schema = .string(core.to31(), stringy.to31())
schema = .string(core.to31(), stringy.to31(format: core.format))
case .object(let core, let objective):
schema = .object(core.to31(), objective.to31())
case .array(let core, let listy):
Expand Down
Loading

0 comments on commit da5b380

Please sign in to comment.