Skip to content

Commit

Permalink
Release 2.1.1 (#24)
Browse files Browse the repository at this point in the history
* Completed redesign (#9)

* Complete rewrite
* Migrate documentation to DocC

* Doc organization

* Update test.yml

* Readme/doc landing page

* Add CustomStringConvertible support for PathComponents.Builder

* Move parseHTTPStatusErrors to Configuration

* Update docs

* Add configuration option to append trailing slash

* Fix issue inheriting configuration

* Remove URLSession on Service protocol

* Re-organize docs; update docs for parseHTTPStatusErrors & appendTrailingSlashToPath

* Update tests for new configuration options

* Update documentation

* Add missing Header init documentation;  add build expression for optionals on Headers, PathParameters, QueryItems

* #13 - Add support for dictionaries to Body

* #14 - Improve documentation examples

* #14 - Improve documentation examples

* #14 - Improve documentation examples

* #15 - Build block method of RequestBuilder was not marked as public

* Update documentation link to next version (2.1.0)

* Change to version independent hosted documentation link.

* Fix typo in Body.Builder doc example

* #17: fix table of default values for Request.Configuration.default

* Add tests for task cancellation; add option for delayed response in URLProtocolMock

* #18 Request.Properties properties should be public

* 19: Add URLMock library for mocking test tools (#23)

* #19: Add URLMock module

* #19: Adding documentation for URLMock

* #19: Move MockResponse out of URLMock extension; add additional method for creating URLSession with MockResponse closure

* #19: Restructuring, updating docs

* #19: Adjust URLMock.session() method signature

* #19: Restructuring

* #19: Change parameter name to avoid incorrect autocompletion

* #19: Update parameter name

* #19: Finalizing documentation

* Finalizing docs (also in main target)

* #19: Add missing FoundationNetworking imports

* #19: Implement delay using Thread.sleep for win/linux compatibility

* #19: Implement Thread.sleep() for win/linux compatibility

* #19: Disable delayed cancellation test on Windows/Linux

* #21: Remove force unwrap for decoding errors

* #22: Change incorrect reference of 'QueryParameters' to 'QueryItems' in documentation

* #19: Add URLMock to swift package index doc build targets
  • Loading branch information
tdeleon authored Dec 2, 2023
1 parent 47b23ca commit c4d2a8f
Show file tree
Hide file tree
Showing 31 changed files with 1,164 additions and 166 deletions.
2 changes: 1 addition & 1 deletion .spi.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
version: 1
builder:
configs:
- documentation_targets: [Relax]
- documentation_targets: [Relax, URLMock]

9 changes: 8 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ let package = Package(
.library(
name: "Relax",
targets: ["Relax"]),
.library(
name: "URLMock",
targets: ["URLMock"])
],
dependencies: [
// Dependencies declare other packages that this package depends on.
Expand All @@ -27,8 +30,12 @@ let package = Package(
.target(
name: "Relax",
dependencies: []),
.target(
name: "URLMock",
dependencies: ["Relax"]
),
.testTarget(
name: "RelaxTests",
dependencies: ["Relax"]),
dependencies: ["Relax", "URLMock"]),
]
)
116 changes: 112 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ lightweight built on protocols, easily allowing you to structure your requests f

### Full Reference Documentation

https://swiftpackageindex.com/tdeleon/Relax/2.1.0/documentation/relax
https://swiftpackageindex.com/tdeleon/relax/documentation

### Features

Expand Down Expand Up @@ -47,6 +47,8 @@ Available for all Swift (5.7+) platforms, including:

Relax supports the [Swift Package Manager](https://www.swift.org/package-manager/). To integrate in your project-

#### Using a Package Manifest File

1. Add the following to the **package** dependencies in the *Package.swift* manifest file:

```swift
Expand All @@ -65,14 +67,31 @@ Relax supports the [Swift Package Manager](https://www.swift.org/package-manager
]
```

#### Using an Xcode Project

1. In your project, choose **File > Add Package Dependencies...**

2. Select the desired criteria, and click **Add Package**

3. In the Package Product selection dialog, select **your target** in the *Add to Target* column for the *Package Product*
**Relax**. Leave **URLMock** set to **None**, unless you have a test target.

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.

#### Import the framework

In files where you will be using Relax, import the framework:

```swift
import Relax
```

### Make a Simple Request
### Make Requests

You can make very simple requests:
```swift
do {
let request = Request(.get, url: URL(string: "https://example.com/users")!)
Expand All @@ -81,6 +100,95 @@ do {
print(error)
}
```
Or, more complex requests with multiple properties:
```swift
let request = Request(.post, url: URL(string: "https://example.com/users")!) {
Body {
// Send an Encodable user object as JSON in the request body
User(name: "firstname")
}
Headers {
Header.authorization(.basic, value: "secret-123")
Header.contentType(.applicationJSON)
}
}
```

See <doc:DefiningRequests> for more details.

### Define a Complex API Structure

You can organize groups of requests into structures of ``Service``s and ``Endpoint``s, inheriting a common base URL and
properties:

```swift
enum UserService: Service {
static let baseURL = URL(string: "https://example.com/")!
// Define shared properties for any request/endpoint children
static var sharedProperties: Request.Properties {
Headers {
Header.authorization(.basic, value: "secretpassword")
}
}

// define a /users endpoint
enum Users: Endpoint {
// /users appended to base URL: https://example.com/users
static let path = "users"
// connect Users to UserService
typealias Parent = UserService

// GET request
static var getAll = Request(.get, parent: UserService.self)
}
}

// make a get request to https://example.com/users
let users = try await UserService.Users.getAll.send()
```

See <doc:DefiningAPIStructure> for more details.

## Testing

[URLMock](https://swiftpackageindex.com/tdeleon/relax/documentation/urlmock) is a framework for mocking responses to
`URLSession` requests, and is included in the Relax package as a second library target.

This allows you to test all ``Request``s by using a `URLSession` that returns mock content only, never making a real
request over the network. Convenience methods are provided for common responses such as HTTP status codes, JSON, and
errors.

To use, simply add `URLMock` as a dependency in your `Package.swift` file or in Xcode to your test target(s):

```swift
.testTarget(
name: "MyAppTests",
dependencies: ["Relax", "URLMock"]),
```

>Note: The `URLMock` framework is intended for testing use only. It is highly encouraged to include it as a
dependency in your test targets only.

Next, in your tests, include the `URLMock` framework along with `Relax` and the target being tested:

```swift
import XCTest
import Relax
import URLMock
@testable import MyApp
```
Finally, create a `URLSession` providing a mocked response, and use it with a ``Request``:

```swift
// Create a session which returns a URLError
let session = URLMock.session(.mock(.notConnectedToInternet))
// Make a request using the modified session. An error should be thrown
do {
try await MyAPIService.Endpoint.get.send(session: session)
XCTAssertFail("Should have failed")
} catch {
// Validate error is handled correctly
}
```

To get started using Relax, see the
[full documentation](https://swiftpackageindex.com/tdeleon/Relax/2.1.0/documentation/relax).
For further examples, see the [full documentation](https://swiftpackageindex.com/tdeleon/relax/documentation/urlmock).
117 changes: 116 additions & 1 deletion Sources/Relax/Relax.docc/Relax.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Available for all Swift (5.7+) platforms, including:

Relax supports the [Swift Package Manager](https://www.swift.org/package-manager/). To integrate in your project-

#### Using a Package Manifest File

1. Add the following to the **package** dependencies in the *Package.swift* manifest file:

```swift
Expand All @@ -54,14 +56,31 @@ Relax supports the [Swift Package Manager](https://www.swift.org/package-manager
]
```

#### Using an Xcode Project

1. In your project, choose **File > Add Package Dependencies...**

2. Select the desired criteria, and click **Add Package**

3. In the Package Product selection dialog, select **your target** in the *Add to Target* column for the *Package Product*
**Relax**. Leave **URLMock** set to **None**, unless you have a test target.

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.

#### Import the framework

In files where you will be using Relax, import the framework:

```swift
import Relax
```

### Make a Simple Request
### Make Requests

You can make very simple requests:
```swift
do {
let request = Request(.get, url: URL(string: "https://example.com/users")!)
Expand All @@ -70,6 +89,102 @@ do {
print(error)
}
```
Or, more complex requests with multiple properties:
```swift
let request = Request(.post, url: URL(string: "https://example.com/users")!) {
Body {
// Send an Encodable user object as JSON in the request body
User(name: "firstname")
}
Headers {
Header.authorization(.basic, value: "secret-123")
Header.contentType(.applicationJSON)
}
}

try await request.send()
```

See <doc:DefiningRequests> for more details.

### Define a Complex API Structure

You can organize groups of requests into structures of ``Service``s and ``Endpoint``s, inheriting a common base URL and
properties:

```swift
enum UserService: Service {
static let baseURL = URL(string: "https://example.com/")!
// Define shared properties for any request/endpoint children
static var sharedProperties: Request.Properties {
Headers {
Header.authorization(.basic, value: "secretpassword")
}
}

// define a /users endpoint
enum Users: Endpoint {
// /users appended to base URL: https://example.com/users
static let path = "users"
// connect Users to UserService
typealias Parent = UserService

// GET request
static var getAll = Request(.get, parent: UserService.self)
}
}

// make a get request to https://example.com/users
let users = try await UserService.Users.getAll.send()
```

See <doc:DefiningAPIStructure> for more details.

## Testing

`Relax` also includes a framework to aid in testing called
[URLMock](https://swiftpackageindex.com/tdeleon/relax/documentation/urlmock). This allows for easily mocking responses
to `URLSession` requests, and is a second library target in the package.

It works by using a `URLSession` that returns mock content only, never making a real request over the network.
Convenience methods are provided for common responses such as HTTP status codes, JSON, and errors, or you can construct
your own responses using `Data` and/or an `HTTPURLResponse`.

To use, simply add `URLMock` as a dependency in your `Package.swift` file or in Xcode to your test target(s):

```swift
.testTarget(
name: "MyAppTests",
dependencies: ["Relax", "URLMock"]),
```

>Note: The `URLMock` framework is intended for testing use only. It is highly encouraged to include it as a
dependency in your test targets only.

Next, in your tests, include the `URLMock` framework along with `Relax` and the target being tested:

```swift
import XCTest
import Relax
import URLMock
@testable import MyApp
```
Finally, create a `URLSession` providing a mocked response, and use it with a ``Request``:

```swift
// Create a session which returns a URLError
let session = URLMock.session(.mock(.notConnectedToInternet))
// Make a request using the modified session. An error should be thrown
do {
try await MyAPIService.Endpoint.get.send(session: session)
XCTAssertFail("Should have failed")
} catch {
// Validate that "not connected to internet" error is handled correctly
}
```

For further examples, see the [full documentation](https://swiftpackageindex.com/tdeleon/relax/documentation/urlmock)
for `URLMock`.

## Topics

Expand Down
4 changes: 2 additions & 2 deletions Sources/Relax/Relax.docc/Request/DefiningRequests.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ enum UserService: Service {
// get user with name
static func getUser(name: String) -> Request {
Request(.get, parent: Self.self) {
QueryParameters { ("name", name) }
QueryItems { ("name", name) }
}
}
}
Expand Down Expand Up @@ -173,7 +173,7 @@ enum UserService: Service {
@RequestBuilder<Users>
static func getUser(name: String) -> Request {
Request.HTTPMethod.get
QueryParameters { ("name", name) }
QueryItems { ("name", name) }
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Relax/Relax.docc/Request/Properties/BodyBuilder.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Body {

// Dictionaries serialized to Data
Body {
["key": "value]
["key": "value"]
}

// Encoding an Encodable instance
Expand Down
8 changes: 4 additions & 4 deletions Sources/Relax/Request/Properties/RequestProperty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ extension RequestProperty {
extension Request {
/// A structure that groups properties of a request
public struct Properties: Hashable {
var headers: Headers = Headers(value: [:])
var queryItems: QueryItems = QueryItems(value: [])
var pathComponents: PathComponents = PathComponents(value: [])
var body: Body = Body(value: nil)
public var headers: Headers = Headers(value: [:])
public var queryItems: QueryItems = QueryItems(value: [])
public var pathComponents: PathComponents = PathComponents(value: [])
public var body: Body = Body(value: nil)

public static func +(lhs: Properties, rhs: Properties) -> Request.Properties {
var new = rhs
Expand Down
4 changes: 2 additions & 2 deletions Sources/Relax/Request/Request+Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ extension Request {
/// ``networkServiceType`` | `URLRequest.NetworkServiceType.default`
/// ``timeoutInterval`` | `60`
/// ``httpShouldHandleCookies`` | `true`
/// ``allowsConstrainedNetworkAccess``* | `true`
/// ``allowsExpensiveNetworkAccess``* | `true`
/// ``parseHTTPStatusErrors`` | `false`
/// ``appendTraillingSlashToPath`` | `false`
/// ``allowsConstrainedNetworkAccess``* | `true`
/// ``appendTraillingSlashToPath``* | `false`
///
/// _*available on iOS, macOS, tvOS, and watchOS only._
public static var `default`: Configuration {
Expand Down
4 changes: 3 additions & 1 deletion Sources/Relax/Request/Request+SendAsync.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ extension Request {
)
do {
return try decoder.decode(ResponseModel.self, from: response.data)
} catch let error as DecodingError {
throw RequestError.decoding(request: self, error: error)
} catch {
throw RequestError.decoding(request: self, error: error as! DecodingError)
throw error
}
}
}
Loading

0 comments on commit c4d2a8f

Please sign in to comment.