Skip to content

Commit

Permalink
Merge pull request #343 from mattpolzin/differentiate-propertyless-re…
Browse files Browse the repository at this point in the history
…quirements

Differentiate propertyless requirements
  • Loading branch information
mattpolzin authored Nov 13, 2023
2 parents f93e9bd + 74ef7bb commit cdcdb22
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 8 deletions.
3 changes: 3 additions & 0 deletions Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ public enum DereferencedJSONSchema: Equatable, JSONSchemaContext {
// See `JSONSchemaContext`
public var examples: [AnyCodable] { jsonSchema.examples }

// See `JSONSchemaContext`
public var inferred: Bool { jsonSchema.inferred }

// See `JSONSchemaContext`
public var readOnly: Bool { jsonSchema.readOnly }

Expand Down
5 changes: 5 additions & 0 deletions Sources/OpenAPIKit/Schema Object/JSONSchema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ public struct JSONSchema: JSONSchemaContext, HasWarnings {
public var examples: [AnyCodable] {
return coreContext.examples
}

// See `JSONSchemaContext`
public var inferred: Bool {
return coreContext.inferred
}
}

extension JSONSchema: Equatable {
Expand Down
48 changes: 46 additions & 2 deletions Sources/OpenAPIKit/Schema Object/JSONSchemaContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ public protocol JSONSchemaContext {
/// Get examples of values fitting the schema.
var examples: [AnyCodable] { get }

/// A schema is "inferred" if it was not actually parsed as a JSON Schema but rather
/// inferred to exist based on surroundings.
///
/// The only currently known case of this is when we parse a `requried` entry in an
/// object and that object has no property with the same name as the requirement.
/// We _infer_ that there is a property by that name (even if only when combined with
/// another schema elsewhere via e.g. `allOf`). This inferred schema has no properties
/// except for being required; it can be differentiated from a schema that was explicitly
/// given in the parsed JSON Schema to have no properties via this internal `_inferred`
/// boolean.
///
/// This is a non-breaking way to tracking such properties, but a breaking change in the
/// future might very well represent this more elegantly. For example, maybe a requirement
/// without a property definition is not a .fragment schema but rather a new case in that
/// enum.
var inferred: Bool { get }

/// `true` if this schema can only be read from and is therefore
/// unsupported for request data.
var readOnly: Bool { get }
Expand Down Expand Up @@ -146,6 +163,23 @@ extension JSONSchema {
/// where the values are anything codable.
public var vendorExtensions: [String : AnyCodable]

/// A schema is "inferred" if it was not actually parsed as a JSON Schema but rather
/// inferred to exist based on surroundings.
///
/// The only currently known case of this is when we parse a `requried` entry in an
/// object and that object has no property with the same name as the requirement.
/// We _infer_ that there is a property by that name (even if only when combined with
/// another schema elsewhere via e.g. `allOf`). This inferred schema has no properties
/// except for being required; it can be differentiated from a schema that was explicitly
/// given in the parsed JSON Schema to have no properties via this internal `_inferred`
/// boolean.
///
/// This is a non-breaking way to tracking such properties, but a breaking change in the
/// future might very well represent this more elegantly. For example, maybe a requirement
/// without a property definition is not a .fragment schema but rather a new case in that
/// enum.
public let inferred: Bool

public var permissions: Permissions { _permissions ?? .readWrite}
public var deprecated: Bool { _deprecated ?? false }

Expand All @@ -167,6 +201,12 @@ extension JSONSchema {
&& _permissions == nil
}

/// Create a schema core context.
///
/// NOTE that the `_inferred` parameter has semantics specific to
/// decoding schemas and you almost certaintly do not want
/// to set it unless you are carrying forward the `inferred`
/// property of another core context.
public init(
format: Format = .unspecified,
required: Bool = true,
Expand All @@ -180,7 +220,8 @@ extension JSONSchema {
allowedValues: [AnyCodable]? = nil,
defaultValue: AnyCodable? = nil,
examples: [AnyCodable] = [],
vendorExtensions: [String: AnyCodable] = [:]
vendorExtensions: [String: AnyCodable] = [:],
_inferred: Bool = false
) {
self.format = format
self.required = required
Expand All @@ -195,6 +236,7 @@ extension JSONSchema {
self.defaultValue = defaultValue
self.examples = examples
self.vendorExtensions = vendorExtensions
self.inferred = _inferred
}

public init(
Expand Down Expand Up @@ -225,6 +267,7 @@ extension JSONSchema {
self.defaultValue = defaultValue
self.examples = examples.map(AnyCodable.init)
self.vendorExtensions = vendorExtensions
self.inferred = false
}
}
}
Expand Down Expand Up @@ -853,6 +896,7 @@ extension JSONSchema.CoreContext: Decodable {
// apply to all schemas (core context) they are more accurately in the context of the
// full JSON Schema.
vendorExtensions = [:]
inferred = false
}

/// Support both `enum` and `const` when decoding allowed values for the schema.
Expand Down Expand Up @@ -1117,7 +1161,7 @@ extension JSONSchema.ObjectContext: Decodable {
required
.filter { !properties.keys.contains($0) }
.forEach { propertyName in
properties[propertyName] = .fragment(.init(required: true))
properties[propertyName] = .fragment(.init(required: true, _inferred: true))
}

return properties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ public enum DereferencedJSONSchema: Equatable, JSONSchemaContext {
// See `JSONSchemaContext`
public var example: AnyCodable? { jsonSchema.example }

// See `JSONSchemaContext`
public var inferred: Bool { jsonSchema.inferred }

// See `JSONSchemaContext`
public var readOnly: Bool { jsonSchema.readOnly }

Expand Down
5 changes: 5 additions & 0 deletions Sources/OpenAPIKit30/Schema Object/JSONSchema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,11 @@ public struct JSONSchema: JSONSchemaContext, HasWarnings, VendorExtendable {
public var example: AnyCodable? {
return coreContext?.example
}

// See `JSONSchemaContext`
public var inferred: Bool {
return coreContext?.inferred ?? false
}
}

extension JSONSchema: Equatable {
Expand Down
42 changes: 40 additions & 2 deletions Sources/OpenAPIKit30/Schema Object/JSONSchemaContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ public protocol JSONSchemaContext {
/// Get an example, if specified. If unspecified, returns `nil`.
var example: AnyCodable? { get }

/// A schema is "inferred" if it was not actually parsed as a JSON Schema but rather
/// inferred to exist based on surroundings.
///
/// The only currently known case of this is when we parse a `requried` entry in an
/// object and that object has no property with the same name as the requirement.
/// We _infer_ that there is a property by that name (even if only when combined with
/// another schema elsewhere via e.g. `allOf`). This inferred schema has no properties
/// except for being required; it can be differentiated from a schema that was explicitly
/// given in the parsed JSON Schema to have no properties via this internal `_inferred`
/// boolean.
///
/// This is a non-breaking way to tracking such properties, but a breaking change in the
/// future might very well represent this more elegantly. For example, maybe a requirement
/// without a property definition is not a .fragment schema but rather a new case in that
/// enum.
var inferred: Bool { get }

/// `true` if this schema can only be read from and is therefore
/// unsupported for request data.
var readOnly: Bool { get }
Expand Down Expand Up @@ -132,6 +149,23 @@ extension JSONSchema {

public let example: AnyCodable?

/// A schema is "inferred" if it was not actually parsed as a JSON Schema but rather
/// inferred to exist based on surroundings.
///
/// The only currently known case of this is when we parse a `requried` entry in an
/// object and that object has no property with the same name as the requirement.
/// We _infer_ that there is a property by that name (even if only when combined with
/// another schema elsewhere via e.g. `allOf`). This inferred schema has no properties
/// except for being required; it can be differentiated from a schema that was explicitly
/// given in the parsed JSON Schema to have no properties via this internal `_inferred`
/// boolean.
///
/// This is a non-breaking way to tracking such properties, but a breaking change in the
/// future might very well represent this more elegantly. For example, maybe a requirement
/// without a property definition is not a .fragment schema but rather a new case in that
/// enum.
public let inferred: Bool

public var nullable: Bool { _nullable ?? false }
public var permissions: Permissions { _permissions ?? .readWrite}
public var deprecated: Bool { _deprecated ?? false }
Expand Down Expand Up @@ -167,7 +201,8 @@ extension JSONSchema {
externalDocs: OpenAPI.ExternalDocumentation? = nil,
allowedValues: [AnyCodable]? = nil,
defaultValue: AnyCodable? = nil,
example: AnyCodable? = nil
example: AnyCodable? = nil,
_inferred: Bool = false
) {
self.format = format
self.required = required
Expand All @@ -181,6 +216,7 @@ extension JSONSchema {
self.allowedValues = allowedValues
self.defaultValue = defaultValue
self.example = example
self.inferred = _inferred
}

public init(
Expand Down Expand Up @@ -209,6 +245,7 @@ extension JSONSchema {
self.allowedValues = allowedValues
self.defaultValue = defaultValue
self.example = AnyCodable(example)
self.inferred = false
}
}
}
Expand Down Expand Up @@ -741,6 +778,7 @@ extension JSONSchema.CoreContext: Decodable {

_deprecated = try container.decodeIfPresent(Bool.self, forKey: .deprecated)
example = try container.decodeIfPresent(AnyCodable.self, forKey: .example)
inferred = false
}
}

Expand Down Expand Up @@ -979,7 +1017,7 @@ extension JSONSchema.ObjectContext: Decodable {
required
.filter { !properties.keys.contains($0) }
.forEach { propertyName in
properties[propertyName] = .fragment(.init(required: true))
properties[propertyName] = .fragment(.init(required: true, _inferred: true))
}

return properties
Expand Down
6 changes: 4 additions & 2 deletions Sources/OpenAPIKitCompat/Compat30To31.swift
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,8 @@ extension OpenAPIKit30.JSONSchema.CoreContext: To31 where Format: OpenAPIKit.Ope
externalDocs: externalDocs?.to31(),
allowedValues: allowedValues,
defaultValue: defaultValue,
examples: [example].compactMap { $0 }
examples: [example].compactMap { $0 },
_inferred: inferred
)
}
}
Expand All @@ -529,7 +530,8 @@ extension OpenAPIKit30.JSONSchema.CoreContext where Format == OpenAPIKit30.JSONT
externalDocs: externalDocs?.to31(),
allowedValues: allowedValues,
defaultValue: defaultValue,
examples: [example].compactMap { $0 }
examples: [example].compactMap { $0 },
_inferred: inferred
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -836,7 +836,8 @@ extension SchemaFragmentTests {

let decoded3 = try orderUnstableDecode(JSONSchema.self, from: t3)

XCTAssertEqual(decoded3, JSONSchema.object(.init(), .init(properties: ["hello": .fragment(.init(required: true))])))
XCTAssertEqual(decoded3, JSONSchema.object(.init(), .init(properties: ["hello": .fragment(.init(required: true, _inferred: true))])))
XCTAssertEqual(decoded3.objectContext?.properties["hello"]?.inferred, true)

let t4 =
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,8 @@ extension SchemaFragmentTests {

let decoded3 = try orderUnstableDecode(JSONSchema.self, from: t3)

XCTAssertEqual(decoded3, JSONSchema.object(.init(), .init(properties: ["hello": .fragment(.init(required: true))])))
XCTAssertEqual(decoded3, JSONSchema.object(.init(), .init(properties: ["hello": .fragment(.init(required: true, _inferred: true))])))
XCTAssertEqual(decoded3.objectContext?.properties["hello"]?.inferred, true)

let t4 =
"""
Expand Down

0 comments on commit cdcdb22

Please sign in to comment.