Skip to content

Commit

Permalink
RequestDLClientTransport (#3)
Browse files Browse the repository at this point in the history
## Description

<!--- 
Please include a summary of the change and which issue is fixed. Please
also include relevant motivation and context.
-->

Implemented the `RequestDLClientTransport` to use the power of RequestDL
with OpenAPI format.

Fixes **None**

## Type of change

<!--- Please delete options that are not relevant. -->

- [x] New feature (non-breaking change which adds functionality)

## Checklist

<!--- Please delete options that are not relevant. -->

- [x] My code follows the code style of this project.
- [x] I have updated the documentation accordingly.
- [x] I have added tests to cover my changes.
  • Loading branch information
o-nnerb committed Jun 11, 2023
1 parent d08f019 commit b222156
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 11 deletions.
6 changes: 5 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ let package = Package(
),
.testTarget(
name: "OpenAPIRequestDLTests",
dependencies: ["OpenAPIRequestDL"]
dependencies: [
"OpenAPIRequestDL",
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
.product(name: "RequestDL", package: "request-dl")
]
)
]
)
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

# OpenAPIRequestDL

The `RequestDLClientTransport` enables the use of the `OpenAPI` format along with all the available configuration properties provided by `RequestDL`.

To generate code for the objects, you should use the [Swift OpenAPI Generator](https://github.com/apple/swift-openapi-generator).

Additionally, Apple has provided all the details in the WWDC23 session [Meet Swift OpenAPI Generator](https://developer.apple.com/wwdc23/10171).

## Installation

OpenAPIRequestDL can be installed using Swift Package Manager. To include it in your project,
Expand Down
30 changes: 30 additions & 0 deletions Sources/OpenAPIRequestDL/OpenAPIRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
See LICENSE for this package's licensing information.
*/

import OpenAPIRuntime
import RequestDL
import Foundation

struct OpenAPIRequest: Property {

let request: OpenAPIRuntime.Request

var body: some Property {
if let data = request.body {
Payload(data: data)
}

if let query = request.query {
Path(request.path + (query.hasPrefix("?") ? query : "?\(query)"))
} else {
Path(request.path)
}

PropertyForEach(request.headerFields, id: \.self) { header in
CustomHeader(name: header.name, value: header.value)
}

RequestMethod(.init(request.method.name))
}
}
7 changes: 0 additions & 7 deletions Sources/OpenAPIRequestDL/OpenAPIRequestDL.swift

This file was deleted.

125 changes: 125 additions & 0 deletions Sources/OpenAPIRequestDL/RequestDLClientTransport.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
See LICENSE for this package's licensing information.
*/

import OpenAPIRuntime
import RequestDL
import Foundation

/**
A `ClientTransport` that utilizes `RequestDL` to execute HTTP requests.
When creating the `Client` for `OpenAPIRuntime`, the instantiated transport should be provided as
shown in the example:
```swift
Client(
serverURL: try! Servers.server1(),
transport: RequestDLClientTransport()
)
```
You can add extra configurations if necessary to maintain communication security with the server:
```swift
RequestDLClientTransport {
SecureConnection {
AdditionalTrusts("apple.com")
// Add more properties as needed
}
}
```
Furthermore, you can combine any other necessary `Property` objects to customize your request, just
specify them as shown in the example above.
*/
public struct RequestDLClientTransport: ClientTransport {

// MARK: - Private properties

private let content: AnyProperty
private let task: @Sendable (AnyProperty) -> any RequestTask<TaskResult<Data>>

// MARK: - Inits

/// Instancia o `RequestDLClientTransport`.
public init() {
self.init(content: EmptyProperty.init)
}

/**
Initializes the `RequestDLClientTransport` with a content closure that returns a `Property`
object.
- Parameter content: A closure that returns a `Property` object, which specifies the properties
of the request.
Example usage:
```swift
let transport = RequestDLClientTransport {
BaseURL("https://api.example.com")
RequestMethod(.post)
// Add more properties as needed
}
let client = Client(
serverURL: try! Servers.server1(),
transport: transport
)
```
*/
public init<Content: Property>(@PropertyBuilder content: () -> Content) {
self.init(
content: content(),
task: { request in DataTask { request }}
)
}

init<Content: Property>(
content: Content,
task: @escaping @Sendable (AnyProperty) -> any RequestTask<TaskResult<Data>>
) {
self.content = .init(content)
self.task = task
}

// MARK: - Public methods

public func send(
_ request: OpenAPIRuntime.Request,
baseURL: URL,
operationID: String
) async throws -> OpenAPIRuntime.Response {

let scheme = baseURL.scheme ?? "http"
let url = baseURL.absoluteString.replacingOccurrences(
of: scheme + "://",
with: ""
)
var components = url.split(separator: "/")
let baseURL = components.removeFirst()

let response = try await task(AnyProperty(
PropertyGroup {
content

BaseURL(.init(scheme), host: String(baseURL))
PropertyForEach(components, id: \.self) {
Path($0)
}

OpenAPIRequest(request: request)
}
))
.result()

return .init(
statusCode: Int(response.head.status.code),
headerFields: response.head.headers.map {
.init(name: $0, value: $1)
},
body: response.payload
)
}
}
96 changes: 93 additions & 3 deletions Tests/OpenAPIRequestDLTests/OpenAPIRequestDLTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,101 @@
*/

import XCTest
import OpenAPIRuntime
import RequestDL
@testable import OpenAPIRequestDL

final class OpenAPIRequestDLTests: XCTestCase {
final class RequestDLClientTransportTests: XCTestCase {

func testPackage() throws {
XCTAssertEqual(String(describing: OpenAPIRequestDL.self), "OpenAPIRequestDL")
var transport: RequestDLClientTransport!

override func setUp() async throws {
try await super.setUp()
transport = .init(content: EmptyProperty()) { request in
MockedTask(
status: .init(code: 200, reason: "Ok"),
content: { request }
)
.ignoresProgress()
}
}

override func tearDown() async throws {
try await super.tearDown()
transport = nil
}

func testClient_whenSend() async throws {
// Given
let data = Data("hello world!".utf8)

let request = OpenAPIRuntime.Request(
path: "/path/to/some/content",
query: "id=102",
method: .post,
headerFields: [
.init(name: "content-type", value: "application/json")
],
body: data
)

// When
let response = try await transport.send(
request,
baseURL: try XCTUnwrap(URL(string: "https://api.example.org/v1/")),
operationID: "100"
)

// Then
XCTAssertEqual(response.body, data)
XCTAssertEqual(response.statusCode, 200)
XCTAssertEqual(response.headerFields, [
HeaderField(name: "Content-Type", value: "application/json"),
HeaderField(name: "Content-Length", value: String(data.count))
])
}

func testClient_whenSendWithCustomConfiguration() async throws {
// Given
let transport = RequestDLClientTransport(
content: PropertyGroup {
AcceptHeader(.text)
},
task: { request in
MockedTask(
status: .init(code: 202, reason: "Ok"),
content: { request }
)
.ignoresProgress()
}
)

let data = Data("hello world!".utf8)

let request = OpenAPIRuntime.Request(
path: "/path/to/some/content",
query: "id=102",
method: .post,
headerFields: [
.init(name: "content-type", value: "application/json")
],
body: data
)

// When
let response = try await transport.send(
request,
baseURL: try XCTUnwrap(URL(string: "https://api.example.org/v1/")),
operationID: "100"
)

// Then
XCTAssertEqual(response.body, data)
XCTAssertEqual(response.statusCode, 202)
XCTAssertEqual(response.headerFields, [
HeaderField(name: "Accept", value: "text/plain"),
HeaderField(name: "Content-Type", value: "application/json"),
HeaderField(name: "Content-Length", value: String(data.count))
])
}
}

0 comments on commit b222156

Please sign in to comment.