Skip to content
Draft
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
21 changes: 20 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,26 @@ let swiftSettings: [SwiftSetting] = [
let package = Package(
name: "swift-openapi-runtime",
platforms: [
.macOS(.v10_15), .macCatalyst(.v13), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .visionOS(.v1)
.macOS(.v13), .macCatalyst(.v13), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .visionOS(.v1)
],
products: [
.library(
name: "OpenAPIRuntime",
targets: ["OpenAPIRuntime"]
),
.library(
name: "OpenAPIFetchRuntime",
targets: ["OpenAPIFetchRuntime"]
)
],
traits: [
.trait(name: "FullFoundation"),
.trait(name: "JavaScriptFetch"),
.default(enabledTraits: ["FullFoundation"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-http-types", from: "1.0.0"),
.package(url: "https://github.com/swiftwasm/JavaScriptKit", .upToNextMinor(from: "0.50.2")),
],
targets: [
.target(
Expand All @@ -52,6 +58,19 @@ let package = Package(
dependencies: ["OpenAPIRuntime"],
swiftSettings: swiftSettings
),
.target(
name: "OpenAPIFetchRuntime",
dependencies: [
.product(
name: "JavaScriptKit",
package: "JavaScriptKit",
condition: .when(traits: ["JavaScriptFetch"])
),
],
swiftSettings: [
.enableExperimentalFeature("Extern"),
]
),
]
)

Expand Down
700 changes: 700 additions & 0 deletions Sources/OpenAPIFetchRuntime/BridgeJS.swift

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions Sources/OpenAPIFetchRuntime/ClientError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#if JavaScriptFetch
@_spi(BridgeJS) public import JavaScriptKit

public enum ClientError: Error {
case transportFailure(JSException)
case unexpectedStatus(statusCode: Int, operationID: String)
case deserializationFailure(String)

public func toJSException() -> JSException {
switch self {
case .transportFailure(let exception):
return exception
case .unexpectedStatus(statusCode: let code, operationID: let op):
return JSException(message: "Unexpected status \(code) for \(op)")
case .deserializationFailure(let detail):
return JSException(message: "Deserialization failure: \(detail)")
}
}
}
#endif
96 changes: 96 additions & 0 deletions Sources/OpenAPIFetchRuntime/ClientHelpers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#if JavaScriptFetch
@_spi(BridgeJS) public import JavaScriptKit

public func makeHeaders(contentType: String? = nil) throws(ClientError) -> Headers {
do {
let h = try Headers()
if let contentType { try h.set("Content-Type", contentType) }
return h
} catch {
throw .transportFailure(error)
}
}

public func checkOk(_ response: Response, operationID: String) throws(ClientError) {
let ok: Bool
let status: Double
do {
ok = try response.ok
status = try response.status
} catch {
throw .transportFailure(error)
}
if !ok {
throw .unexpectedStatus(statusCode: Int(status), operationID: operationID)
}
}

public func deserialize<T: _JSBridgedClass>(_ response: Response) async throws(ClientError) -> T {
let jsValue: JSValue
do {
jsValue = try await response.json()
} catch {
throw .transportFailure(error)
}
guard let object = jsValue.object else {
throw .deserializationFailure("Expected JS object for deserialization")
}
return T(unsafelyWrapping: object)
}

public func deserializeArray<T: _JSBridgedClass>(_ response: Response) async throws(ClientError) -> [T] {
let jsValue: JSValue
do {
jsValue = try await response.json()
} catch {
throw .transportFailure(error)
}
guard let object = jsValue.object else {
throw .deserializationFailure("Expected JS object for array deserialization")
}
guard let jsArray = JSArray(object) else {
throw .deserializationFailure("Expected JS Array, got non-array object")
}
var result: [T] = []
for element in jsArray {
guard let object = element.object else {
throw .deserializationFailure("Expected JS object in array element")
}
result.append(T(unsafelyWrapping: object))
}
return result
}

public func deserializeOneOf(
_ response: Response,
discriminatorProperty: String
) async throws(ClientError) -> (String, JSObject) {
let jsValue: JSValue
do {
jsValue = try await response.json()
} catch {
throw .transportFailure(error)
}
guard let object = jsValue.object else {
throw .deserializationFailure("Expected JS object for oneOf response")
}
let discriminatorJSValue = object[discriminatorProperty]
guard let stringValue = discriminatorJSValue.string else {
throw .deserializationFailure("Discriminator property is not a string")
}
return (stringValue, object)
}

public func appendQueryParam(_ url: String, name: String, value: String) throws(ClientError) -> String {
let encodedName: String
let encodedValue: String
do {
encodedName = try encodeURIComponent(name)
encodedValue = try encodeURIComponent(value)
} catch {
throw .transportFailure(error)
}
let separator = url.contains("?") ? "&" : "?"
return url + separator + encodedName + "=" + encodedValue
}
#endif
23 changes: 23 additions & 0 deletions Sources/OpenAPIFetchRuntime/ClientTransport.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#if JavaScriptFetch
@_spi(BridgeJS) public import JavaScriptKit

public enum HTTPMethod: String, Sendable {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
case patch = "PATCH"
case head = "HEAD"
case options = "OPTIONS"
}
extension HTTPMethod: _BridgedSwiftEnumNoPayload, _BridgedSwiftRawValueEnum {}

public protocol ClientTransport: Sendable {
func send(
method: HTTPMethod,
url: String,
headers: consuming Headers,
body: JSObject?
) async throws(ClientError) -> Response
}
#endif
21 changes: 21 additions & 0 deletions Sources/OpenAPIFetchRuntime/FetchTransport.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#if JavaScriptFetch
@_spi(BridgeJS) public import JavaScriptKit

public struct FetchTransport: ClientTransport, Sendable {
public init() {}

public func send(
method: HTTPMethod,
url: String,
headers: consuming Headers,
body: JSObject?
) async throws(ClientError) -> Response {
do {
let requestInit = try RequestInit(method, headers, body)
return try await fetch(url, requestInit)
} catch {
throw .transportFailure(error)
}
}
}
#endif
42 changes: 42 additions & 0 deletions Sources/OpenAPIFetchRuntime/WebAPIs.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#if JavaScriptFetch
@_spi(BridgeJS) public import JavaScriptKit

@JSClass(from: .global) public struct Headers {
@JSFunction public init() throws(JSException)
@JSFunction public func append(_ name: String, _ value: String) throws(JSException) -> Void
@JSFunction public func delete(_ name: String) throws(JSException) -> Void
@JSFunction public func get(_ name: String) throws(JSException) -> Optional<String>
@JSFunction public func has(_ name: String) throws(JSException) -> Bool
@JSFunction public func set(_ name: String, _ value: String) throws(JSException) -> Void
}

@JSClass(from: .global) public struct Response {
@JSGetter public var headers: Headers
@JSGetter public var ok: Bool
@JSGetter public var status: Double
@JSGetter public var statusText: String
@JSGetter public var url: String
@JSFunction public func json() async throws(JSException) -> JSValue
@JSFunction public func text() async throws(JSException) -> String
}

@JSFunction(from: .global) public func fetch(_ input: String, _ options: RequestInit) async throws(JSException) -> Response

@JSClass public struct RequestInit {
@JSFunction public init(_ method: HTTPMethod, _ headers: Headers, _ body: JSObject?) throws(JSException)
}

@JSClass(from: .global) public struct URL {
@JSFunction public init(_ path: String, _ base: String) throws(JSException)
@JSGetter public var href: String
@JSGetter public var searchParams: URLSearchParams
}

@JSClass(from: .global) public struct URLSearchParams {
@JSFunction public func set(_ name: String, _ value: String) throws(JSException) -> Void
@JSFunction public func append(_ name: String, _ value: String) throws(JSException) -> Void
@JSFunction public func delete(_ name: String) throws(JSException) -> Void
}

@JSFunction(from: .global) public func encodeURIComponent(_ string: String) throws(JSException) -> String
#endif
3 changes: 3 additions & 0 deletions Sources/OpenAPIFetchRuntime/bridge-js.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"exposeToGlobal": false
}
Loading