Skip to content

Commit

Permalink
Merge pull request #56 from tdeleon/develop
Browse files Browse the repository at this point in the history
Release 2.2.0
  • Loading branch information
tdeleon authored Jan 30, 2024
2 parents 6b605db + a566c73 commit ca29367
Show file tree
Hide file tree
Showing 23 changed files with 680 additions and 155 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Relax supports the [Swift Package Manager](https://www.swift.org/package-manager
4. Click on **Add Package**

>Tip: `URLMock` is an additional framework provided to aid in testing by mocking responses to requests, and should be
added to your test targets only. For more information, see <doc:Relax#Testing> below.
added to your test targets only. For more information, see [Testing](#testing) below.

#### Import the framework

Expand Down Expand Up @@ -114,7 +114,8 @@ let request = Request(.post, url: URL(string: "https://example.com/users")!) {
}
```

See <doc:DefiningRequests> for more details.
See [Defining Requests](https://swiftpackageindex.com/tdeleon/relax/documentation/relax/definingrequests) for more
details.

### Define a Complex API Structure

Expand Down Expand Up @@ -147,7 +148,8 @@ enum UserService: Service {
let users = try await UserService.Users.getAll.send()
```

See <doc:DefiningAPIStructure> for more details.
See [Defining an API Structure](https://swiftpackageindex.com/tdeleon/relax/documentation/relax/definingapistructure) for
more details.

## Testing

Expand Down
30 changes: 26 additions & 4 deletions Sources/Relax/API Structure/APIComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +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 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 @@ -39,9 +51,11 @@ extension APIComponent {
@Request.Properties.Builder
public static var sharedProperties: Request.Properties { .empty }

public static var allProperties: Request.Properties {
sharedProperties
}
public static var allProperties: Request.Properties { sharedProperties }

public static var session: URLSession { .shared }

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

//MARK: - APISubComponent
Expand All @@ -59,5 +73,13 @@ extension APISubComponent {
public static var configuration: Request.Configuration {
Parent.configuration
}

public static var session: URLSession {
Parent.session
}

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

90 changes: 68 additions & 22 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 @@ -59,7 +61,70 @@ let request = Request(.post, url: URL(string: "https://example.com/users")!) {
}
```

### Modifying Requests
### Request Session

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: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 {
static let baseURL = URL(string: "https://example.com/")!
static let session: URLSession = mySession // use a specific URLSession already defined

// request will use session defined in MyService, mySession
static let get = Request(.get, parent: MyService.self)

// request will use URLSession.shared, overriding the parent session
static let getSharedSession = Request(.get, parent: MyService.self, session: .shared)
}
```

If a request does not have a parent set, then the session will default to `URLSession.shared`, if not otherwise
specified.

```swift
// a request using URLSession.shared
let request = Request(.get, url: URL(string: "https://example.com/")!)

// a request using a specific URLSession
let customSessionRequest = Request(.get, url: URL(string: "https://example.com/")!, session: mySession)
```
### Configuring a Request

Sometimes you need more control on a request to modify things such as the timeout interval or cache policy. This can
be done using the ``Request/Configuration-swift.struct`` structure. The following example changes the timeout interval
to 90 seconds instead of the default 60.

```swift
let request = Request(
.post,
url: URL(string: "https://example.com/users")!,
configuration: Request.Configuration(timeout: 90)
) {
Body {
// Assume that User is an Encodable type
User(name: "firstname")
}
}
```

If no configuration is specified, then the configuration will be inherited from the requests parent ``APIComponent``.
If the request is standalone (not linked to a parent), then a
[`default`](<doc:Request/Configuration-swift.struct/default>) configuration will be used.

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
the request is defined, but before it is sent. In this case, there are modifier style methods available for
Expand Down Expand Up @@ -87,25 +152,6 @@ try await request

For more information on modifying requests, see ``Request``.

### Configuring Requests

Sometimes you need more control on a request to modify things such as the timeout interval or cache policy. This can
be done using the ``Request/Configuration-swift.struct`` structure. The following example changes the timeout interval
to 90 seconds instead of the default 60.

```swift
let request = Request(
.post,
url: URL(string: "https://example.com/users")!,
configuration: Request.Configuration(timeout: 90)
) {
Body {
// Assume that User is an Encodable type
User(name: "firstname")
}
}
```

## Requests in a Complex Structure

With a large API service, there will often be many different requests (often nested at different levels) that you want
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:)``
15 changes: 9 additions & 6 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:properties:)``
- ``init(_:parent:configuration:properties:)``
- ``init(_:url:configuration:session:decoder:properties:)``
- ``init(_:parent:configuration:session:decoder:properties:)``
- ``HTTPMethod-swift.struct``
- ``Configuration-swift.struct``
- ``RequestBuilder``
Expand All @@ -17,6 +17,8 @@
- ``url``
- ``urlRequest``
- ``configuration-swift.property``
- ``session``
- ``decoder``
- ``headers``
- ``queryItems``
- ``pathComponents``
Expand All @@ -43,15 +45,16 @@
### Sending Requests Asynchronously

- <doc:SendingRequestsAsync>
- ``send(session:)-43n5v``
- ``send(decoder:session:)-3h323``
- ``send(session:)-74uav``
- ``send(decoder:session:)-667nw``
- ``AsyncResponse``

### Sending Requests with a Publisher

- <doc:SendingRequestsPublisher>
- ``send(session:)-488d1``
- ``send(decoder:session:)-6f0kg``

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ Send a Request asynchronously with Swift concurrency.

## Overview

You can send a ``Request`` which will [`await`](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html) completion
in concurrent code. Errors are thrown from the method, which you catch in a `do/catch` block.
You can send a ``Request`` which will [`await`](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html)
completion in concurrent code. Errors are thrown from the method, which you catch in a `do/catch` block.

## Sending a Request

Expand All @@ -15,7 +15,7 @@ You can optionally provide your own `URLSession` to use, otherwise `URLSession.s

### Sending the Request

Use ``Request/send(session:)-43n5v`` to receive `Data` from a request.
Use ``Request/send(session:)-74uav`` to receive `Data` from a request.

```swift
Task {
Expand All @@ -30,12 +30,38 @@ Task {
}
```

### Overriding the Request Session

Requests are sent using a `URLSession`, which is customizable through the ``Request/session`` property. If
the request is linked to a `parent` ``APIComponent``, then the session will inherit from the ``APIComponent/session`` by
default. This can be overridden when a request is sent by passing in a specific `URLSession`:

```swift
// Uses the session and configuration defined on MyService by default
let response = try await MyService.get.send()

// Uses a custom URLSession & Configuration for this send only
let response = try await MyService.get.send(session: customSession)
```

For detached requests (with no parent), `URLSession.shared` is used by default if no other session is specified:

```swift
// Uses URLSession.shared
let response = try await Request(.get, url: URL(string: "https://example.com/")!)

// Uses a specific URLSession
let response = try await Request(.get, url: URL(string: "https://example.com/")!, session: customSession)
```

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:)-3h323`` method.
You can automatically decode JSON into an expected `Decodable` instance using the
``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 All @@ -52,4 +78,8 @@ Task {

### Handling Errors

When a failure occurs, ``RequestError`` is thrown. By default, HTTP status codes (`4XX`, `5XX`, etc) returned from the server are not parsed for errors. As a convenience, you can set the ``Request/Configuration-swift.struct/parseHTTPStatusErrors`` parameter in ``Request/Configuration-swift.struct`` to `true` in order to enable this. If enabled, then any HTTP status code outside the `1XX`-`3XX` range will be treated as an error.
When a failure occurs, ``RequestError`` is thrown. By default, HTTP status codes (`4XX`, `5XX`, etc) returned from the
server are not parsed for errors. As a convenience, you can set the
``Request/Configuration-swift.struct/parseHTTPStatusErrors`` parameter in ``Request/Configuration-swift.struct`` to
`true` in order to enable this. If enabled, then any HTTP status code outside the `1XX`-`3XX` range will be treated as
an error.
Loading

0 comments on commit ca29367

Please sign in to comment.