Skip to content

Commit

Permalink
#29: Add inherited decoder property; documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
tdeleon committed Jan 26, 2024
1 parent 913c58d commit 32b9468
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 56 deletions.
19 changes: 17 additions & 2 deletions Sources/Relax/API Structure/APIComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,20 @@ public protocol APIComponent {
/// - Important: You should not override this property, doing so will not allow properties to be properly inherited by child components.
static var allProperties: Request.Properties { get }

/// The configuration to use for any Requests provided by this component or its children
/// The configuration to use for any Requests provided by this component or its children.
///
/// The default value is ``Request/Configuration-swift.struct/default``
static var configuration: Request.Configuration { get }

/// The URLSession to use for any Requests defined in this component or its children. The default is `URLSession.shared`.
/// The URLSession to use for any Requests defined in this component or its children.
///
/// The default value is `URLSession.shared`.
static var session: URLSession { get }

/// The decoder to use for any Requests provided by this component or its childern.
///
/// The default value is `JSONDecoder()`.
static var decoder: JSONDecoder { get }
}

//MARK: Default Implementation
Expand All @@ -45,6 +54,8 @@ extension APIComponent {
public static var allProperties: Request.Properties { sharedProperties }

public static var session: URLSession { .shared }

public static var decoder: JSONDecoder { JSONDecoder() }
}

//MARK: - APISubComponent
Expand All @@ -66,5 +77,9 @@ extension APISubComponent {
public static var session: URLSession {
Parent.session
}

public static var decoder: JSONDecoder {
Parent.decoder
}
}

18 changes: 14 additions & 4 deletions Sources/Relax/Relax.docc/Request/DefiningRequests.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ You can create Requests in several different ways, ranging from as simple as a o
definition of an entire REST API service. They can be either pre-defined and static, or accept dynamic parameters at
runtime, depending on your needs.

Once you've created your request, see <doc:SendingRequestsAsync>, <doc:SendingRequestsPublisher>, or <doc:SendingRequestsHandler> on how to send them and receive a response.
Once you've created your request, see <doc:SendingRequestsAsync>, <doc:SendingRequestsPublisher>, or
<doc:SendingRequestsHandler> on how to send them and receive a response.

## Request Basics

Expand All @@ -30,7 +31,8 @@ Usually, you will need to customize at least some other properties on the reques
- ``PathComponents``

These can be set using the `properties` parameter on the init method, which takes a ``Request/Properties/Builder``
closure where you set any number of properties to be used in the request using a [result builder](https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html#ID630).
closure where you set any number of properties to be used in the request using a
[result builder](https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html#ID630).

The following builds on the previous example by adding a `Content-Type: application/json` header and appending a query
parameter to the URL, so the final URL will be `https://example.com/users?name=firstname`.
Expand Down Expand Up @@ -63,8 +65,8 @@ let request = Request(.post, url: URL(string: "https://example.com/users")!) {

When sending requests, a `URLSession` is used, which can be configured through the ``Request/session`` property. If not
specified, this property will inherit from the
[`parent`](<doc:Request/init(_:parent:configuration:session:properties:)>) if defined, otherwise it will be set to
`URLSession.shared` by default. See <doc:DefiningAPIStructure> for more on inheritance.
[`parent`](<doc:Request/init(_:parent:configuration:session:decoder:properties:)>) if defined, otherwise it will be set
to `URLSession.shared` by default. See <doc:DefiningAPIStructure> for more on inheritance.

```swift
enum MyService: Service {
Expand Down Expand Up @@ -114,6 +116,14 @@ If the request is standalone (not linked to a parent), then a

See <doc:DefiningAPIStructure> for more on inheritance.

### Decoding Data

When decoding received data from the request, the ``Request/decoder`` property on the request will be used. This
property defaults to either the ``APIComponent/decoder-74ja3`` of the parent if linked, or `JSONDecoder()` if not. You
can override this for a particular request by specifying a different value for the ``Request/decoder`` property.

See <doc:DefiningAPIStructure> for more on inheritance.

### Modifying a Request

While many properties can be pre-defined on requests, there may be cases where values need to be changed after
Expand Down
1 change: 0 additions & 1 deletion Sources/Relax/Relax.docc/Request/Properties/Body.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,4 @@ Body {

- ``init(_:)``
- ``init(_:encoder:)``
- ``init(_:options:)``
- ``init(value:)``
9 changes: 5 additions & 4 deletions Sources/Relax/Relax.docc/Request/Request.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
### Creating a Request

- <doc:DefiningRequests>
- ``init(_:url:configuration:session:properties:)``
- ``init(_:parent:configuration:session:properties:)``
- ``init(_:url:configuration:session:decoder:properties:)``
- ``init(_:parent:configuration:session:decoder:properties:)``
- ``HTTPMethod-swift.struct``
- ``Configuration-swift.struct``
- ``RequestBuilder``
Expand All @@ -18,6 +18,7 @@
- ``urlRequest``
- ``configuration-swift.property``
- ``session``
- ``decoder``
- ``headers``
- ``queryItems``
- ``pathComponents``
Expand Down Expand Up @@ -45,15 +46,15 @@

- <doc:SendingRequestsAsync>
- ``send(session:)-74uav``
- ``send(decoder:session:)-2kid8``
- ``send(decoder:session:)-667nw``
- ``AsyncResponse``

### Sending Requests with a Publisher

- <doc:SendingRequestsPublisher>

- ``send(session:)-8vwky``
- ``send(decoder:session:)-9jzsp``
- ``send(decoder:session:)-3j2hs``
- ``PublisherResponse``
- ``PublisherModelResponse``

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,9 @@ See <doc:DefiningAPIStructure> for more on inheritance.
### Decoding JSON

You can automatically decode JSON into an expected `Decodable` instance using the
``Request/send(decoder:session:)-2kid8`` method.
``Request/send(decoder:session:)-667nw`` method.

> Tip: By default, `JSONDecoder()` is used, but you
can also pass in your own to the `decoder` parameter.
> Tip: The ``Request/decoder`` defined in the request is used by default, but you can pass in your own to override this.
```swift
Task {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ You can automatically decode received data into a `Decodable` instance with the
``Request/send(decoder:session:completion:)`` method. This method also uses a completion handler, but instead of `Data`,
returns the data decoded to a `Decodable` type.

> Tip: By default, `JSONDecoder()` is used, but you can also pass in your own to the `decoder` parameter.
> Tip: The ``Request/decoder`` defined in the request is used by default, but you can pass in your own to override this.
```swift
let request = Request(.get, url: URL(string: "https://example.com")!)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ See <doc:DefiningAPIStructure> for more on inheritance.

### Decoding JSON

You can automatically decode received data into a `Decodable` instance with the``Request/send(decoder:session:)-9jzsp``,
You can automatically decode received data into a `Decodable` instance with the``Request/send(decoder:session:)-3j2hs``,
which will publish a ``Request/PublisherModelResponse`` with the decoded data.

> Tip: By default, `JSONDecoder()` is used, but you can also pass in your own to the `decoder` parameter.
> Tip: The ``Request/decoder`` defined in the request is used by default, but you can pass in your own to override this.
```swift
let request = Request(.get, url: URL(string: "https://example.com")!)
Expand Down
9 changes: 4 additions & 5 deletions Sources/Relax/Request/Request+Send.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,11 @@ extension Request {

/// Send a request with a completion handler, decoding the received data to a Decodable instance
/// - Parameters:
/// - decoder: The decoder to decode received data with. Default is `JSONDecoder()`.
/// - session: When provided, overrides the ``Request/session`` defined in the Request.
/// - parseHTTPStatusErrors: Whether to parse HTTP status codes returned for errors. The default is `false`.
/// - decoder: When set, overrides the ``Request/decoder`` used to decode received data.
/// - session: When set, overrides the ``Request/session`` used to send the request.
/// - completion: A completion handler with the response from the server, including the decoded data as the Decodable type.
public func send<ResponseModel: Decodable>(
decoder: JSONDecoder = JSONDecoder(),
decoder: JSONDecoder? = nil,
session: URLSession? = nil,
completion: @escaping Request.ModelCompletion<ResponseModel>
) {
Expand All @@ -101,7 +100,7 @@ extension Request {
) { result in
do {
let success = try result.get()
let decoded = try decoder.decode(ResponseModel.self, from: success.data)
let decoded = try (decoder ?? self.decoder).decode(ResponseModel.self, from: success.data)
completion(.success((self, success.urlResponse, decoded)))
} catch let error as DecodingError {
completion(.failure(.decoding(request: self, error: error)))
Expand Down
16 changes: 7 additions & 9 deletions Sources/Relax/Request/Request+SendAsync.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ extension Request {
/// Send a request asynchronously
///
/// - Parameters:
/// - session: When provided, overrides the ``Request/session`` defined in the Request.
/// - session: When set, overrides the ``Request/session`` used to send the request.
/// - Returns: A response containing the request sent, url response, and data.
/// - Throws: A `RequestError` on error.
@discardableResult
Expand All @@ -38,7 +38,7 @@ extension Request {

return try await withCheckedThrowingContinuation { continuation in
task = send(
session: session,
session: session ?? self.session,
autoResumeTask: true
) { result in
switch result {
Expand All @@ -56,19 +56,17 @@ extension Request {

/// Send a request asynchronously, decoding data received to a Decodable instance.
/// - Parameters:
/// - decoder: The decoder to decode received data with. Default is `JSONDecoder()`.
/// - session: When provided, overrides the ``Request/session`` defined in the Request.
/// - decoder: When set, overrides the ``Request/decoder`` used to decode received data.
/// - session: When set, overrides the ``Request/session`` used to send the request.
/// - Returns: The model, decoded from received data.
/// - Throws: A `RequestError` on error.
public func send<ResponseModel: Decodable>(
decoder: JSONDecoder = JSONDecoder(),
decoder: JSONDecoder? = nil,
session: URLSession? = nil
) async throws -> ResponseModel {
let response: AsyncResponse = try await send(
session: session
)
let response: AsyncResponse = try await send(session: session)
do {
return try decoder.decode(ResponseModel.self, from: response.data)
return try (decoder ?? self.decoder).decode(ResponseModel.self, from: response.data)
} catch let error as DecodingError {
throw RequestError.decoding(request: self, error: error)
} catch {
Expand Down
25 changes: 12 additions & 13 deletions Sources/Relax/Request/Request+SendPublisher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,19 @@ extension Request {
/// - request: The request made
/// - urlResponse: The response received
/// - responseModel: The model decoded from received data
public typealias PublisherModelResponse<Model: Decodable> = (request: Request, urlResponse: HTTPURLResponse, responseModel: Model)
public typealias PublisherModelResponse<Model: Decodable> = (
request: Request,
urlResponse: HTTPURLResponse,
responseModel: Model
)

/// Send a request, returning a Combine publisher
/// - Parameters:
/// - session: When provided, overrides the ``Request/session`` defined in the Request.
/// - session: When set, overrides the ``Request/session`` used to send the request.
/// - Returns: A Publisher which returns the received data, or a ``RequestError`` on failure.
public func send(
session: URLSession? = nil
) -> AnyPublisher<PublisherResponse, RequestError> {
public func send(session: URLSession? = nil) -> AnyPublisher<PublisherResponse, RequestError> {
Future<PublisherResponse, RequestError> { promise in
send(
session: session,
autoResumeTask: true
) { result in
send(session: session ?? self.session,autoResumeTask: true) { result in
switch result {
case .success(let successResponse):
promise(.success(successResponse))
Expand All @@ -51,16 +50,16 @@ extension Request {

/// Send a request and decode received data to a Decodable instance, returning a Combine publisher
/// - Parameters:
/// - decoder: The decoder to decode received data with. Default is `JSONDecoder()`.
/// - session: When provided, overrides the ``Request/session`` defined in the Request.
/// - decoder: When set, overrides the ``Request/decoder`` used to decode received data.
/// - session: When set, overrides the ``Request/session`` used to send the request.
/// - Returns: A Pubisher which returns the received data, or a ``RequestError`` on failure.
public func send<ResponseModel: Decodable>(
decoder: JSONDecoder = JSONDecoder(),
decoder: JSONDecoder? = nil,
session: URLSession? = nil
) -> AnyPublisher<ResponseModel, RequestError> {
send(session: session)
.map(\.data)
.decode(type: ResponseModel.self, decoder: decoder)
.decode(type: ResponseModel.self, decoder: decoder ?? self.decoder)
.mapError {
switch $0 {
case let error as DecodingError:
Expand Down
Loading

0 comments on commit 32b9468

Please sign in to comment.