Skip to content

Commit

Permalink
Merge branch 'release/0.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
antonio-war committed Sep 14, 2024
2 parents f8d686e + f2ad051 commit 4d2e69a
Show file tree
Hide file tree
Showing 13 changed files with 357 additions and 221 deletions.
45 changes: 30 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,22 +40,20 @@ The main steps for using SwiftyNetworking into your project are outlined below,

### Request definition
First, define a `SwiftyNetworkingRequest` which is a simple wrapper around `URLRequest` which allows you to easily set up everything you need to make an API call.
Such as the classics method, headers and query parameters, but also some parameters closely linked to the iOS ecosystem such as cache policy or timeout management.
Such as the classics method, headers and body, but also some parameters closely linked to the iOS ecosystem such as cache policy or timeout management.

```swift
let request = SwiftyNetworkingRequest(
endpoint: "https://jsonplaceholder.typicode.com",
path: "comments",
parameters: ["postId": "1"],
method: .get,
headers: [:],
body: nil,
cachePolicy: .reloadIgnoringCacheData,
timeout: 60
let request = try SwiftyNetworkingRequest(
url: URL(string: "https://jsonplaceholder.typicode.com"),
method: .get,
headers: [:],
body: nil,
cachePolicy: .reloadIgnoringCacheData,
timeout: 60
)
```

Alternatively, you can initialize the request directly with a valid URL, without manually specifying endpoints, path and parameters.
Alternatively, you can initialize the request specifying host, path and parameters without defining an URL.

### Client creation
Create a `SwiftyNetworkingClient` instance using the default or a custom URLSessionConfiguration.
Expand Down Expand Up @@ -95,8 +93,8 @@ In a context where the app makes numerous requests of different types to the sam
case users
case user(id: Int)

var endpoint: String {
"https://jsonplaceholder.typicode.com"
var host: String {
"jsonplaceholder.typicode.com"
}

var path: String {
Expand All @@ -113,19 +111,36 @@ In a context where the app makes numerous requests of different types to the sam
Making a request to one of the exposed routes will be really easy!

```swift
let request = JsonPlaceholderRouter.users
let request = try JsonPlaceholderRouter.users.request
let response = try await client.send(request)
```

### Decoding
In most cases once you make a network call you need to read the contents of the response body, obviously in the iOS environment this is achieved using the power of the Decodable protocol and its Decoder, that's why SwiftyNetworking also provides methods with integrated decoding. They are very useful when the decoding operation must be done in a simple way, without any custom behavior, SwiftyNetworking will relieve you of any responsibility.

```swift
let users = try await networkingClient.send(JsonPlaceholderRouter.users, decoding: [JsonPlaceholderUser].self, using: JSONDecoder())
let request = try JsonPlaceholderRouter.users.request
let users = try await networkingClient.send(request, decoding: [JsonPlaceholderUser].self, using: JSONDecoder())
```

By default the method uses its own instance of JSONDecoder, however, as shown it is possible to inject a custom decoder if a particular decoding configuration is necessary.

### SwiftUI integration
SwiftyNetworking was born to be a modern framework and for this reason it is oriented towards development with SwiftUI.
The `Request` property wrapper allows you to make a network request and decode the response directly within your views, without having to write any code.
It is inspired by SwiftData's @Query to provide the user with a familiar interface.

```swift
@Request(url: "https://jsonplaceholder.typicode.com/posts")
var posts: [Post]?

@Request(url: "https://jsonplaceholder.typicode.com/posts/1")
var post: Post?
```

The request is executed automatically as soon as the view is created, so you can directly access your object inside the body.
If there is an error, the object will be nil.

---
# Support
Your generous donations help sustain and improve this project. Here's why supporting us is important:
Expand Down
57 changes: 17 additions & 40 deletions Sources/SwiftyNetworking/Clients/SwiftyNetworkingClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,34 +20,27 @@ public struct SwiftyNetworkingClient {
}

public func send(_ request: SwiftyNetworkingRequest, completion: @escaping (Result<SwiftyNetworkingResponse, Error>) -> Void) {
do {
let rawRequest = try request.rawValue
let dataTask = session.dataTask(with: request.rawValue) { data, response, error in
if let error = error {
completion(.failure(error))
return
}

let dataTask = session.dataTask(with: rawRequest) { data, response, error in
if let error = error {
completion(.failure(error))
return
}

guard let body = data, let rawResponse = response as? HTTPURLResponse else {
completion(.failure(URLError(.cannotParseResponse)))
return
}

let response = SwiftyNetworkingResponse(
rawValue: rawResponse,
body: body,
fetchType: delegate.fetchType(for: rawRequest),
start: delegate.start(for: rawRequest),
end: delegate.end(for: rawRequest)
)
completion(.success(response))
guard let body = data, let rawResponse = response as? HTTPURLResponse else {
completion(.failure(URLError(.cannotParseResponse)))
return
}

dataTask.resume()
} catch {
completion(.failure(error))
let response = SwiftyNetworkingResponse(
rawValue: rawResponse,
body: body,
fetchType: delegate.fetchType(for: request.rawValue),
start: delegate.start(for: request.rawValue),
end: delegate.end(for: request.rawValue)
)
completion(.success(response))
}
dataTask.resume()
}

public func send(_ request: SwiftyNetworkingRequest) async throws -> SwiftyNetworkingResponse {
Expand Down Expand Up @@ -95,20 +88,4 @@ public struct SwiftyNetworkingClient {
}
}
}

public func send(_ router: SwiftyNetworkingRouter, completion: @escaping (Result<SwiftyNetworkingResponse, Error>) -> Void) {
send(router.rawValue, completion: completion)
}

public func send(_ router: SwiftyNetworkingRouter) async throws -> SwiftyNetworkingResponse {
try await send(router.rawValue)
}

public func send<Model: Decodable>(_ router: SwiftyNetworkingRouter, decoding model: Model.Type, using decoder: JSONDecoder = JSONDecoder(), completion: @escaping (Result<Model, Error>) -> Void) {
send(router.rawValue, decoding: model, using: decoder, completion: completion)
}

public func send<Model: Decodable>(_ router: SwiftyNetworkingRouter, decoding model: Model.Type, using decoder: JSONDecoder = JSONDecoder()) async throws -> Model {
try await send(router.rawValue, decoding: model, using: decoder)
}
}
64 changes: 2 additions & 62 deletions Sources/SwiftyNetworking/Extensions/SwiftyNetworkingRequest+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,69 +11,9 @@ extension SwiftyNetworkingRequest {

typealias RawValue = URLRequest

public init(
id: UUID = UUID(),
url: URL,
method: Method = .get,
headers: [String: String] = [:],
body: Data? = nil,
cachePolicy: CachePolicy = .returnCacheDataElseLoad,
timeout: TimeInterval = 60
) {
let components = URLComponents(string: url.absoluteString)
self.init(
id: id,
endpoint: (components?.scheme ?? "") + "://" + (components?.host ?? ""),
path: url.path,
parameters: components?.queryItems?.reduce(into: [String: String]()) { result, parameter in
result[parameter.name] = parameter.value
} ?? [:],
method: method,
headers: headers,
body: body,
cachePolicy: cachePolicy,
timeout: timeout
)
}

enum Scheme: String, Equatable, Hashable, Sendable {
case http
case https
}

var url: URL {
get throws {
guard let endpoint = URL(string: endpoint) else {
throw URLError(.badURL)
}

guard let scheme = endpoint.scheme, Scheme(rawValue: scheme) != nil else {
throw URLError(.badURL)
}

guard let url = URL(string: path, relativeTo: endpoint), var components = URLComponents(string: url.absoluteString) else {
throw URLError(.badURL)
}

guard !parameters.isEmpty else {
return url
}

components.queryItems = parameters.map { (key, value) in
URLQueryItem(name: key, value: value)
}

guard let url = components.url else {
throw URLError(.badURL)
}

return url
}
}

var rawValue: RawValue {
get throws {
var request = try URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeout)
get {
var request = URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeout)
request.httpMethod = method.rawValue.uppercased()
request.httpBody = body
request.allHTTPHeaderFields = headers
Expand Down
39 changes: 24 additions & 15 deletions Sources/SwiftyNetworking/Extensions/SwiftyNetworkingRouter+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
import Foundation

public extension SwiftyNetworkingRouter {

var scheme: Scheme {
.https
}

var path: String {
"/"
}

var parameters: [String: String] {
[:]
}
Expand All @@ -31,20 +40,20 @@ public extension SwiftyNetworkingRouter {
var timeout: TimeInterval {
60
}
}

extension SwiftyNetworkingRouter {
typealias RawValue = SwiftyNetworkingRequest

var rawValue: RawValue {
SwiftyNetworkingRequest(
endpoint: endpoint,
path: path,
parameters: parameters, method: method,
headers: headers,
body: body,
cachePolicy: cachePolicy,
timeout: timeout
)
var request: SwiftyNetworkingRequest {
get throws {
try SwiftyNetworkingRequest(
scheme: scheme,
host: host,
path: path,
parameters: parameters,
method: method,
headers: headers,
body: body,
cachePolicy: cachePolicy,
timeout: timeout
)
}
}
}
76 changes: 70 additions & 6 deletions Sources/SwiftyNetworking/Models/SwiftyNetworkingRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,87 @@ import Foundation

public struct SwiftyNetworkingRequest: Identifiable, Hashable, Sendable {
public let id: UUID
public let endpoint: String
public let url: URL
public let scheme: Scheme
public let host: String
public let path: String
public let method: Method
public let parameters: [String: String]
public let method: Method
public let headers: [String: String]
public let body: Data?
public let cachePolicy: CachePolicy
public let timeout: TimeInterval

public init(
id: UUID = UUID(),
endpoint: String,
url: URL?,
method: Method = .get,
headers: [String : String] = [:],
body: Data? = nil,
cachePolicy: CachePolicy = .returnCacheDataElseLoad,
timeout: TimeInterval = 60
) throws {
guard let url else {
throw URLError(.badURL)
}

guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
throw URLError(.badURL)
}

guard let rawScheme = components.scheme, let scheme = Scheme(rawValue: rawScheme) else {
throw URLError(.unsupportedURL)
}

guard let host = components.host else {
throw URLError(.badURL)
}

self.id = id
self.url = url
self.scheme = scheme
self.host = host
self.path = components.path
self.parameters = components.queryItems?.reduce(into: [String: String]()) { result, item in
if let value = item.value {
result[item.name] = value
}
} ?? [:]
self.method = method
self.headers = headers
self.body = body
self.cachePolicy = cachePolicy
self.timeout = timeout
}

public init(
id: UUID = UUID(),
scheme: Scheme = .https,
host: String,
path: String = "/",
parameters: [String: String] = [:],
method: Method = .get,
headers: [String: String] = [:],
headers: [String : String] = [:],
body: Data? = nil,
cachePolicy: CachePolicy = .returnCacheDataElseLoad,
timeout: TimeInterval = 60
) {
) throws {
var components = URLComponents()
components.scheme = scheme.rawValue
components.host = host
components.path = path.first == "/" ? path : "/" + path
components.queryItems = parameters.isEmpty ? nil : parameters.reduce(into: [URLQueryItem]()) { result, item in
result.append(URLQueryItem(name: item.key, value: item.value))
}

guard let url = components.url else {
throw URLError(.badURL)
}

self.id = id
self.endpoint = endpoint
self.url = url
self.scheme = scheme
self.host = host
self.path = path.first == "/" ? path : "/" + path
self.parameters = parameters
self.method = method
Expand All @@ -40,6 +99,11 @@ public struct SwiftyNetworkingRequest: Identifiable, Hashable, Sendable {
self.timeout = timeout
}

public enum Scheme: String, Equatable, Hashable, Sendable {
case http
case https
}

public enum Method: String, Equatable, Hashable, Sendable {
case connect
case delete
Expand Down
Loading

0 comments on commit 4d2e69a

Please sign in to comment.