Skip to content

Commit

Permalink
Merge pull request #175 from mattpolzin/misc-additions
Browse files Browse the repository at this point in the history
Misc. additions
  • Loading branch information
mattpolzin authored Jan 10, 2021
2 parents 4a5aa48 + 8c5831a commit 81995cf
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 18 deletions.
20 changes: 20 additions & 0 deletions Sources/OpenAPIKit/Document/Document.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,26 @@ extension OpenAPI {
}
}

extension OpenAPI.Document {
/// Create a new OpenAPI Document with
/// all paths not passign the given predicate
/// removed.
public func filteringPaths(with predicate: (OpenAPI.Path) -> Bool) -> OpenAPI.Document {
let filteredPaths = paths.filteringPaths(with: predicate)
return OpenAPI.Document(
openAPIVersion: openAPIVersion,
info: info,
servers: servers,
paths: filteredPaths,
components: components,
security: security,
tags: tags,
externalDocs: externalDocs,
vendorExtensions: vendorExtensions
)
}
}

extension OpenAPI.Document {
/// A `Route` is the combination of a path (where the route lives)
/// and a path item (the definition of the route).
Expand Down
10 changes: 10 additions & 0 deletions Sources/OpenAPIKit/Either/Either.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ public enum Either<A, B> {
case a(A)
case b(B)

/// Get the first of the possible values of the `Either` (if it is
/// set).
///
/// This is sometimes known as the `Left` or error case of some
/// `Either` types, but `OpenAPIKit` makes regular use of
/// this type in situations where neither of the possible values could
/// be considered an error. In fact, `OpenAPIKit` sticks to using
/// the Swift `Result` type where such semantics are needed.
public var a: A? {
guard case let .a(ret) = self else { return nil }
return ret
Expand All @@ -27,6 +35,8 @@ public enum Either<A, B> {
self = .a(a)
}

/// Get the second of the possible values of the `Either` (if
/// it is set).
public var b: B? {
guard case let .b(ret) = self else { return nil }
return ret
Expand Down
32 changes: 20 additions & 12 deletions Sources/OpenAPIKit/Header/Header.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,11 @@ extension OpenAPI.Header {

// MARK: - Header Convenience
extension OpenAPI.Parameter.SchemaContext {
public static func header(_ schema: JSONSchema,
allowReserved: Bool = false,
example: AnyCodable? = nil) -> Self {
public static func header(
_ schema: JSONSchema,
allowReserved: Bool = false,
example: AnyCodable? = nil
) -> Self {
return .init(
schema,
style: .default(for: .header),
Expand All @@ -114,9 +116,11 @@ extension OpenAPI.Parameter.SchemaContext {
)
}

public static func header(schemaReference: JSONReference<JSONSchema>,
allowReserved: Bool = false,
example: AnyCodable? = nil) -> Self {
public static func header(
schemaReference: JSONReference<JSONSchema>,
allowReserved: Bool = false,
example: AnyCodable? = nil
) -> Self {
return .init(
schemaReference: schemaReference,
style: .default(for: .header),
Expand All @@ -125,9 +129,11 @@ extension OpenAPI.Parameter.SchemaContext {
)
}

public static func header(_ schema: JSONSchema,
allowReserved: Bool = false,
examples: OpenAPI.Example.Map?) -> Self {
public static func header(
_ schema: JSONSchema,
allowReserved: Bool = false,
examples: OpenAPI.Example.Map?
) -> Self {
return .init(
schema,
style: .default(for: .header),
Expand All @@ -136,9 +142,11 @@ extension OpenAPI.Parameter.SchemaContext {
)
}

public static func header(schemaReference: JSONReference<JSONSchema>,
allowReserved: Bool = false,
examples: OpenAPI.Example.Map?) -> Self {
public static func header(
schemaReference: JSONReference<JSONSchema>,
allowReserved: Bool = false,
examples: OpenAPI.Example.Map?
) -> Self {
return .init(
schemaReference: schemaReference,
style: .default(for: .header),
Expand Down
10 changes: 8 additions & 2 deletions Sources/OpenAPIKit/OrderedDictionary/OrderedDictionary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ public struct OrderedDictionary<Key, Value> where Key: Hashable {
unorderedHash = [:]
}

public init<S>(grouping values: S, by keyForValue: (S.Element) throws -> Key) rethrows where Value == [S.Element], S : Sequence {
public init<S>(
grouping values: S,
by keyForValue: (S.Element) throws -> Key
) rethrows where Value == [S.Element], S : Sequence {
var temporaryDictionary = Self()

for value in values {
Expand All @@ -35,7 +38,10 @@ public struct OrderedDictionary<Key, Value> where Key: Hashable {
self = temporaryDictionary
}

public init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows where S : Sequence, S.Element == (Key, Value) {
public init<S>(
_ keysAndValues: S,
uniquingKeysWith combine: (Value, Value) throws -> Value
) rethrows where S : Sequence, S.Element == (Key, Value) {
var temporaryDictionary = Self()

for (key, value) in keysAndValues {
Expand Down
7 changes: 7 additions & 0 deletions Sources/OpenAPIKit/Path Item/PathItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ extension OpenAPI.PathItem {
public typealias Map = OrderedDictionary<OpenAPI.Path, OpenAPI.PathItem>
}

extension OrderedDictionary where Key == OpenAPI.Path {
public func filteringPaths(with predicate: (OpenAPI.Path) -> Bool) -> OrderedDictionary {
let filteredPaths = filter { (path, _) in predicate(path) }
return OrderedDictionary(filteredPaths, uniquingKeysWith: { fst, _ in fst })
}
}

extension OpenAPI.PathItem {
/// Retrieve the operation for the given verb, if one is set for this path.
public func `for`(_ verb: OpenAPI.HttpMethod) -> OpenAPI.Operation? {
Expand Down
97 changes: 93 additions & 4 deletions Sources/OpenAPIKit/Validator/Validator+Convenience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,10 @@ public func take<T, U>(_ path: KeyPath<ValidationContext<T>, U>, check: @escapin
/// when: \.a == "hello"
/// )
///
public func lift<T, U>(_ path: KeyPath<ValidationContext<T>, U>, into validations: Validation<U>...) -> (ValidationContext<T>) -> [ValidationError] {
public func lift<T, U>(
_ path: KeyPath<ValidationContext<T>, U>,
into validations: Validation<U>...
) -> (ValidationContext<T>) -> [ValidationError] {
return { context in
return validations.flatMap { $0.apply(to: context[keyPath: path], at: context.codingPath, in: context.document) }
}
Expand Down Expand Up @@ -184,7 +187,10 @@ public func lift<T, U>(_ path: KeyPath<ValidationContext<T>, U>, into validation
/// when: \.a == "hello"
/// )
///
public func lift<T, U>(_ path: KeyPath<T, U>, into validations: Validation<U>...) -> (ValidationContext<T>) -> [ValidationError] {
public func lift<T, U>(
_ path: KeyPath<T, U>,
into validations: Validation<U>...
) -> (ValidationContext<T>) -> [ValidationError] {
return { context in
return validations.flatMap { $0.apply(to: context.subject[keyPath: path], at: context.codingPath, in: context.document) }
}
Expand All @@ -205,7 +211,11 @@ public func lift<T, U>(_ path: KeyPath<T, U>, into validations: Validation<U>...
/// on what this function does when the value pointed to
/// is non-nil.
///
public func unwrap<T, U>(_ path: KeyPath<ValidationContext<T>, U?>, into validations: Validation<U>..., description: String? = nil) -> (ValidationContext<T>) -> [ValidationError] {
public func unwrap<T, U>(
_ path: KeyPath<ValidationContext<T>, U?>,
into validations: Validation<U>...,
description: String? = nil
) -> (ValidationContext<T>) -> [ValidationError] {
return { context in
guard let subject = context[keyPath: path] else {
let error = description.map { "Tried to unwrap but found nil: \($0)" }
Expand All @@ -231,7 +241,11 @@ public func unwrap<T, U>(_ path: KeyPath<ValidationContext<T>, U?>, into validat
/// on what this function does when the value pointed to
/// is non-nil.
///
public func unwrap<T, U>(_ path: KeyPath<T, U?>, into validations: Validation<U>..., description: String? = nil) -> (ValidationContext<T>) -> [ValidationError] {
public func unwrap<T, U>(
_ path: KeyPath<T, U?>,
into validations: Validation<U>...,
description: String? = nil
) -> (ValidationContext<T>) -> [ValidationError] {
return { context in
guard let subject = context.subject[keyPath: path] else {
let error = description.map { "Tried to unwrap but found nil: \($0)" }
Expand All @@ -242,6 +256,81 @@ public func unwrap<T, U>(_ path: KeyPath<T, U?>, into validations: Validation<U>
}
}

/// Look up the value pointed to by the KeyPath. Fail
/// with a `ValidationError` if the value is not
/// found in the `Components` for the current document.
///
/// - Parameters:
/// - path: The path to lookup.
/// - validations: One or more validations to perform on the value
/// the KeyPath points to.
///
public func lookup<T, U>(
_ path: KeyPath<T, Either<JSONReference<U>, U>>,
thenApply validations: Validation<U>...
) -> (ValidationContext<T>) -> [ValidationError] {
return { context in
return validations.flatMap { validation -> [ValidationError] in
let subject = context.subject[keyPath: path]
do {
return validation.apply(
to: try context.document.components.lookup(subject),
at: context.codingPath,
in: context.document
)
} catch {
return [
ValidationError(
reason: "Could not find component being validated: \(String(describing: subject.reference?.absoluteString))",
at: context.codingPath
)
]
}
}
}
}

/// Unwrap and look up the value pointed to by the KeyPath.
/// Fail with a `ValidationError` if the value is `nil` or
/// not found in the `Components` for the current document.
///
/// - Parameters:
/// - path: The path to lookup.
/// - validations: One or more validations to perform on the value
/// the KeyPath points to.
///
public func unwrapAndLookup<T, U>(
_ path: KeyPath<T, Either<JSONReference<U>, U>?>,
thenApply validations: Validation<U>...
) -> (ValidationContext<T>) -> [ValidationError] {
return { context in
return validations.flatMap { validation -> [ValidationError] in
guard let subject = context.subject[keyPath: path] else {
return [
ValidationError(
reason: "Tried to unwrap an optional for path \(String(describing: path)) and found `nil`",
at: context.codingPath
)
]
}
do {
return validation.apply(
to: try context.document.components.lookup(subject),
at: context.codingPath,
in: context.document
)
} catch {
return [
ValidationError(
reason: "Could not find component being validated: \(String(describing: subject.reference?.absoluteString))",
at: context.codingPath
)
]
}
}
}
}

/// Apply all of the given validations to the current context.
///
/// This is equivalent to calling `lift` with the keypath `\.self`
Expand Down
18 changes: 18 additions & 0 deletions Tests/OpenAPIKitTests/Document/DocumentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,24 @@ final class DocumentTests: XCTestCase {
XCTAssertEqual(t.allServers, [s1, s2])
}

func test_pathFiltering() {
let t = OpenAPI.Document(
info: .init(title: "test", version: "1.0"),
servers: [],
paths: [
"/test1": .init(),
"/test2": .init()
],
components: .noComponents
)

let t2 = t.filteringPaths(with: { $0 == "/test1" })

XCTAssertEqual(t.paths.count, 2)
XCTAssertEqual(t2.paths.count, 1)
XCTAssertNotNil(t2.paths["/test1"])
}

func test_existingSecuritySchemeSuccess() {
let docData =
"""
Expand Down
Loading

0 comments on commit 81995cf

Please sign in to comment.