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
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ OpenAPIKit leaves it to you to decide how to load external files and where to
store the results in the Components Object. It does this by requiring that you
provide an implementation of the
[`ExternalLoader`](https://mattpolzin.github.io/OpenAPIKit/documentation/openapikit/externalloader)
protocol. You provide a `load` function and a `componentKey` function, both of
protocol. You provide a `load` function and a `componentKey()` function, both of
which accept as input the `URL` to load. A simple mock example implementation
from the OpenAPIKit tests will go a long way to showing how the `ExternalLoader`
can be set up:
Expand All @@ -605,14 +605,13 @@ struct ExampleLoader: ExternalLoader {

static func load<T>(_ url: URL) async throws -> (T, [Message]) where T : Decodable {
// load data from file, perhaps. we will just mock that up for the example:
let data = try await mockData(componentKey(type: T.self, at: url))
let data = try await mockData(url)

// We use the YAML decoder purely for order-stability.
let decoded = try YAMLDecoder().decode(T.self, from: data)
let finished: T
// while unnecessary, a loader may likely want to attatch some extra info
// to keep track of where a reference was loaded from. This example
shows
// while unnecessary, a loader may likely want to attach some extra info
// to keep track of where a reference was loaded from. This example shows
// the strategy of using vendor extensions.
if var extendable = decoded as? VendorExtendable {
extendable.vendorExtensions["x-source-url"] = AnyCodable(url)
Expand All @@ -623,7 +622,7 @@ struct ExampleLoader: ExternalLoader {
return (finished, [])
}

static func componentKey<T>(type: T.Type, at url: URL) throws -> OpenAPIKit.OpenAPI.ComponentKey {
static func componentKey<T>(for object: T, at url: URL) throws -> OpenAPIKit.OpenAPI.ComponentKey {
// do anything you want here to determine what key the new component should be stored at.
//
// for the example, we will just transform the URL path into a valid components key:
Expand Down
2 changes: 1 addition & 1 deletion Sources/OpenAPIKit/ExternalLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public protocol ExternalLoader: _ExternalLoaderMetatype where Message: Sendable
/// but the same key for all equal objects. In practice, this probably means that any
/// time the same type and URL pair are passed in the same `ComponentKey` should be
/// returned.
static func componentKey<T>(type: T.Type, at url: URL) throws -> OpenAPI.ComponentKey
static func componentKey<T>(for object: T, at url: URL) throws -> OpenAPI.ComponentKey
}

public protocol ExternallyDereferenceable {
Expand Down
2 changes: 1 addition & 1 deletion Sources/OpenAPIKit/JSONReference.swift
Original file line number Diff line number Diff line change
Expand Up @@ -626,8 +626,8 @@ extension JSONReference: ExternallyDereferenceable where ReferenceType: External
case .internal(let ref):
return (.internal(ref), .init(), [])
case .external(let url):
let componentKey = try loader.componentKey(type: ReferenceType.self, at: url)
let (component, messages): (ReferenceType, [Loader.Message]) = try await loader.load(url)
let componentKey = try loader.componentKey(for: component, at: url)
var components = OpenAPI.Components()
switch ReferenceType.openAPIComponentsKeyPath {
case .a(let directPath):
Expand Down
2 changes: 1 addition & 1 deletion Sources/OpenAPIKit30/ExternalLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public protocol ExternalLoader: _ExternalLoaderMetatype where Message: Sendable
/// but the same key for all equal objects. In practice, this probably means that any
/// time the same type and URL pair are passed in the same `ComponentKey` should be
/// returned.
static func componentKey<T>(type: T.Type, at url: URL) throws -> OpenAPI.ComponentKey
static func componentKey<T>(for object: T, at url: URL) throws -> OpenAPI.ComponentKey
}

public protocol ExternallyDereferenceable {
Expand Down
2 changes: 1 addition & 1 deletion Sources/OpenAPIKit30/JSONReference.swift
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,8 @@ extension JSONReference: ExternallyDereferenceable where ReferenceType: External
case .internal(let ref):
return (.internal(ref), .init(), [])
case .external(let url):
let componentKey = try loader.componentKey(type: ReferenceType.self, at: url)
let (component, messages): (ReferenceType, [Loader.Message]) = try await loader.load(url)
let componentKey = try loader.componentKey(for: component, at: url)
var components = OpenAPI.Components()
components[keyPath: ReferenceType.openAPIComponentsKeyPath][componentKey] = component
return (try components.reference(named: componentKey.rawValue, ofType: ReferenceType.self), components, messages)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase {

static func load<T>(_ url: URL) async throws -> (T, [Message]) where T : Decodable {
// load data from file, perhaps. we will just mock that up for the test:
let data = try await mockData(componentKey(type: T.self, at: url))
let data = try await mockData(url)

// We use the YAML decoder purely for order-stability.
let decoded = try YAMLDecoder().decode(T.self, from: data)
Expand All @@ -41,7 +41,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
return (finished, [url.absoluteString])
}

static func componentKey<T>(type: T.Type, at url: URL) throws -> OpenAPIKit30.OpenAPI.ComponentKey {
static func componentKey<T>(for object: T, at url: URL) throws -> OpenAPIKit30.OpenAPI.ComponentKey {
// do anything you want here to determine what key the new component should be stored at.
// for the example, we will just transform the URL into a valid components key:
let urlString = url.pathComponents.dropFirst()
Expand All @@ -51,12 +51,12 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}

/// Mock up some data, just for the example.
static func mockData(_ key: OpenAPIKit30.OpenAPI.ComponentKey) async throws -> Data {
return try XCTUnwrap(files[key.rawValue])
static func mockData(_ url: URL) async throws -> Data {
return try XCTUnwrap(files[url.absoluteString])
}

static let files: [String: Data] = [
"params_name_json": """
"file://./params/name.json": """
{
"name": "name",
"description": "a lonely parameter",
Expand All @@ -67,20 +67,28 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}
}
""",
"schemas_string_param_json": """
"file://./schemas/string_param.json": """
{
"oneOf": [
{ "type": "string" },
{ "$ref": "file://./schemas/basic_object.json" }
]
}
""",
"schemas_basic_object_json": """
"file://./schemas/string_param.json#": """
{
"oneOf": [
{ "type": "string" },
{ "$ref": "file://./schemas/basic_object.json" }
]
}
""",
"file://./schemas/basic_object.json": """
{
"type": "object"
}
""",
"paths_webhook_json": """
"file://./paths/webhook.json": """
{
"summary": "just a webhook",
"get": {
Expand All @@ -95,7 +103,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}
}
""",
"requests_webhook_json": """
"file://./requests/webhook.json": """
{
"content": {
"application/json": {
Expand Down Expand Up @@ -128,7 +136,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}
}
""",
"responses_webhook_json": """
"file://./responses/webhook.json": """
{
"description": "webhook response",
"content": {
Expand All @@ -154,14 +162,14 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}
}
""",
"headers_webhook_json": """
"file://./headers/webhook.json": """
{
"schema": {
"$ref": "file://./schemas/string_param.json"
}
}
""",
"headers_webhook2_json": """
"file://./headers/webhook2.json": """
{
"content": {
"application/json": {
Expand All @@ -172,19 +180,19 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}
}
""",
"examples_good_json": """
"file://./examples/good.json": """
{
"value": "{\\"body\\": \\"request me\\"}"
}
""",
"callbacks_one_json": """
"file://./callbacks/one.json": """
{
"https://callback.site.com/callback": {
"summary": "just a callback"
}
}
""",
"paths_callback_json": """
"file://./paths/callback.json": """
{
"summary": "just a callback",
"get": {
Expand All @@ -208,7 +216,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}
}
""",
"links_first_json": """
"file://./links/first.json": """
{
"operationId": "helloOp"
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/OpenAPIKit30Tests/JSONReferenceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ extension JSONReferenceTests {
return (JSONSchema.string as! T, [url.absoluteString])
}

static func componentKey<T>(type: T.Type, at url: URL) throws -> OpenAPI.ComponentKey {
static func componentKey<T>(for object: T, at url: URL) throws -> OpenAPI.ComponentKey {
return try .forceInit(rawValue: url.absoluteString
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "#", with: "_")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase {

static func load<T>(_ url: URL) async throws -> (T, [Message]) where T : Decodable {
// load data from file, perhaps. we will just mock that up for the test:
let data = try await mockData(componentKey(type: T.self, at: url))
let data = try await mockData(url)

// We use the YAML decoder purely for order-stability.
let decoded = try YAMLDecoder().decode(T.self, from: data)
Expand All @@ -41,7 +41,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
return (finished, [url.absoluteString])
}

static func componentKey<T>(type: T.Type, at url: URL) throws -> OpenAPIKit.OpenAPI.ComponentKey {
static func componentKey<T>(for object: T, at url: URL) throws -> OpenAPIKit.OpenAPI.ComponentKey {
// do anything you want here to determine what key the new component should be stored at.
// for the example, we will just transform the URL into a valid components key:
let urlString = url.pathComponents.dropFirst()
Expand All @@ -51,12 +51,16 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}

/// Mock up some data, just for the example.
static func mockData(_ key: OpenAPIKit.OpenAPI.ComponentKey) async throws -> Data {
return try XCTUnwrap(files[key.rawValue])
static func mockData(_ url: URL) async throws -> Data {
return try XCTUnwrap(files[url.absoluteString])
}





static let files: [String: Data] = [
"params_name_json": """
"file://./params/name.json": """
{
"name": "name",
"description": "a lonely parameter",
Expand All @@ -67,15 +71,23 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}
}
""",
"schemas_string_param_json": """
"file://./schemas/string_param.json": """
{
"oneOf": [
{ "type": "string" },
{ "$ref": "file://./schemas/basic_object.json" }
]
}
""",
"file://./schemas/string_param.json#": """
{
"oneOf": [
{ "type": "string" },
{ "$ref": "file://./schemas/basic_object.json" }
]
}
""",
"schemas_basic_object_json": """
"file://./schemas/basic_object.json": """
{
"type": "object",
"patternProperties": {
Expand All @@ -85,12 +97,12 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}
}
""",
"schemas_pattern_property_json": """
"file://./schemas/pattern_property.json": """
{
"type": "string"
}
""",
"requests_hello_json": """
"file://./requests/hello.json": """
{
"content": {
"application/json": {
Expand Down Expand Up @@ -118,14 +130,14 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}
}
""",
"headers_hello_json": """
"file://./headers/hello.json": """
{
"schema": {
"$ref": "file://./schemas/string_param.json"
}
}
""",
"paths_webhook_json": """
"file://./paths/webhook.json": """
{
"summary": "just a webhook",
"get": {
Expand All @@ -140,7 +152,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}
}
""",
"requests_webhook_json": """
"file://./requests/webhook.json": """
{
"content": {
"application/json": {
Expand Down Expand Up @@ -173,7 +185,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}
}
""",
"responses_webhook_json": """
"file://./responses/webhook.json": """
{
"description": "webhook response",
"content": {
Expand All @@ -199,14 +211,14 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}
}
""",
"headers_webhook_json": """
"file://./headers/webhook.json": """
{
"schema": {
"$ref": "file://./schemas/string_param.json"
}
}
""",
"headers_webhook2_json": """
"file://./headers/webhook2.json": """
{
"content": {
"application/json": {
Expand All @@ -217,19 +229,19 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}
}
""",
"examples_good_json": """
"file://./examples/good.json": """
{
"value": "{\\"body\\": \\"request me\\"}"
}
""",
"callbacks_one_json": """
"file://./callbacks/one.json": """
{
"https://callback.site.com/callback": {
"$ref": "file://./paths/callback.json"
}
}
""",
"paths_callback_json": """
"file://./paths/callback.json": """
{
"summary": "just a callback",
"get": {
Expand All @@ -253,7 +265,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}
}
""",
"links_first_json": """
"file://./links/first.json": """
{
"operationId": "helloOp"
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/OpenAPIKitTests/JSONReferenceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ extension JSONReferenceTests {
return (JSONSchema.string as! T, [url.absoluteString])
}

static func componentKey<T>(type: T.Type, at url: URL) throws -> OpenAPI.ComponentKey {
static func componentKey<T>(for object: T, at url: URL) throws -> OpenAPI.ComponentKey {
return try .forceInit(rawValue: url.absoluteString
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "#", with: "_")
Expand Down
Loading