Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Sources/OpenAPIKit/Validator/Validator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,10 @@ class _ReferencingValidator: _Validator {
userInfo: encoder.userInfo,
codingPath: encoder.codingPath + [key]
)
if let typedKey = key as? AnyCodingKey,
let validatableKey = typedKey.originalValue as? Validatable {
applyValidations(to: validatableKey)
}
}

init(referencing encoder: _Validator, at index: Int) {
Expand Down Expand Up @@ -573,7 +577,7 @@ extension _Validator: SingleValueEncodingContainer {
)
}

fileprivate func applyValidations(to value: Encodable, atKey key: CodingKey? = nil) {
fileprivate func applyValidations(to value: Any, atKey key: CodingKey? = nil) {
let pathTail = key.map { [$0] } ?? []
for idx in validations.indices {
errors += validations[idx].apply(to: value, at: codingPath + pathTail, in: document)
Expand Down
19 changes: 14 additions & 5 deletions Sources/OpenAPIKitCore/OrderedDictionary/OrderedDictionary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,16 @@ extension OrderedDictionary: Sendable where Key: Sendable, Value: Sendable {}
public struct AnyCodingKey: CodingKey {

public let stringValue: String
public let originalValue: Any

public init(stringValue: String) {
self.stringValue = stringValue
self.originalValue = stringValue
}

public init(stringValue: String, originalValue: Any) {
self.stringValue = stringValue
self.originalValue = originalValue
}

public let intValue: Int? = nil
Expand Down Expand Up @@ -327,7 +334,8 @@ extension OrderedDictionary: Encodable where Key: Encodable, Value: Encodable {

// try for RawRepresentable with String RawValues
if let encodableDictionary = self as? StringRawKeyEncodable {
let kvPairs = zip(encodableDictionary.orderedStringKeys, self.values)
let keyPairs = zip(self.orderedKeys, encodableDictionary.orderedStringKeys)
let kvPairs = zip(keyPairs, self.values)
try encodeKeyValuePairs(kvPairs, to: encoder)
return
}
Expand Down Expand Up @@ -365,15 +373,16 @@ internal func encodeKeyValuePairs<Value: Encodable>(
}
}

/// Encode a sequence of `String`/`Value` pairs as a hash.
internal func encodeKeyValuePairs<Value: Encodable>(
_ keyValuePairs: Zip2Sequence<[String], [Value]>,
/// Encode a sequence of `String`/`Value` pairs as a hash keeping track of the
/// original RawRepresentable values.
internal func encodeKeyValuePairs<Value: Encodable, T>(
_ keyValuePairs: Zip2Sequence<Zip2Sequence<[T], [String]>, [Value]>,
to encoder: Encoder
) throws {
var container = encoder.container(keyedBy: AnyCodingKey.self)

for (key, value) in keyValuePairs {
try container.encode(value, forKey: .init(stringValue: key))
try container.encode(value, forKey: .init(stringValue: key.1, originalValue: key.0))
}
}

Expand Down
26 changes: 26 additions & 0 deletions Tests/OpenAPIKitTests/Validator/ValidatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,32 @@ final class ValidatorTests: XCTestCase {
try document.validate(using: validator)
}

func test_keyedContainerKeysAreValidated() {
let contentMap : OpenAPI.Content.Map = [
.json: .a(.component(named: "jsonContent"))
]
let doc = OpenAPI.Document(
info: .init(title: "doc", version: "1.0"),
servers: [],
paths: [
"/hello": .init(
summary: "test",
get: .init(responses: [
200: .response(content: contentMap)
])
)
],
components: .noComponents
)
let notJson = Validation<OpenAPI.ContentType>.init(description: "JSON content not allowed", check: \.rawValue != "application/json")

XCTAssertThrowsError(try doc.validate(using: Validator.blank.validating(notJson), strict: false)) { error in
let error = error as? ValidationErrorCollection

XCTAssertEqual(error.map(OpenAPI.Error.init(from:))?.localizedDescription, "Failed to satisfy: JSON content not allowed at path: .paths[\'/hello\'].get.responses.200.content[\'application/json\']")
}
}

/// This test confirms the last example in the validation documentation file (documentation/validation.md)
/// functions as-written.
func test_requestBodySchemaValidationFails() {
Expand Down