Skip to content

Commit f02de59

Browse files
authored
Create EndpointBuilderURLSession (#1)
1 parent fbfe87c commit f02de59

File tree

8 files changed

+186
-21
lines changed

8 files changed

+186
-21
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ playground.xcworkspace
2222
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
2323
Packages/
2424
Package.pins
25-
Package.resolved
2625
*.xcodeproj
2726

2827
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2023 Pixel Foundry, LLC
3+
Copyright (c) 2024 Pixel Foundry, LLC
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

Package.resolved

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
1-
// swift-tools-version:5.8
1+
// swift-tools-version:5.9
22
import PackageDescription
33

44
let package = Package(
5-
name: "SwiftPackage",
5+
name: "endpoint-builder-urlsession",
6+
platforms: [
7+
.iOS(.v13),
8+
.tvOS(.v13),
9+
.macOS(.v10_15),
10+
.watchOS(.v6),
11+
.visionOS(.v1)
12+
],
613
products: [
7-
.library(name: "SwiftPackage", targets: ["SwiftPackage"])
14+
.library(name: "EndpointBuilderURLSession", targets: ["EndpointBuilderURLSession"]),
15+
],
16+
dependencies: [
17+
.package(url: "https://github.com/apple/swift-http-types", from: "1.0.0"),
18+
.package(url: "https://github.com/pixel-foundry/endpoint-builder", from: "0.0.2")
819
],
920
targets: [
10-
.target(name: "SwiftPackage"),
11-
.testTarget(name: "Tests", dependencies: ["SwiftPackage"])
21+
.target(
22+
name: "EndpointBuilderURLSession",
23+
dependencies: [
24+
.product(name: "EndpointBuilder", package: "endpoint-builder"),
25+
.product(name: "HTTPTypes", package: "swift-http-types")
26+
]
27+
)
1228
]
1329
)

README.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1 @@
1-
# Swift Package Repository Template
2-
3-
This is a project template for Swift packages.
4-
5-
* Sensible `.gitignore`
6-
* `.swiftlint.yml` for linting
7-
* `.editorconfig` enforcing tabs with a size of 2.
1+
# Endpoint Builder URLSession Client
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import EndpointBuilder
2+
import Foundation
3+
#if canImport(FoundationNetworking)
4+
import FoundationNetworking
5+
#endif
6+
import HTTPTypes
7+
8+
/// An API endpoint client that uses `URLSession` to create requests
9+
public struct EndpointBuilderURLSession: Sendable {
10+
11+
/// The base server URL on which endpoint paths will be appended
12+
public let serverBaseURL: URL
13+
14+
/// The URLSession object that will make requests
15+
public let urlSession: @Sendable () -> URLSession
16+
17+
/// JSON encoder
18+
public let encoder: @Sendable () -> JSONEncoder
19+
20+
/// JSON decoder
21+
public let decoder: @Sendable () -> JSONDecoder
22+
23+
/// Creates a new `EndpointBuilderURLSession`.
24+
public init(
25+
serverBaseURL: URL,
26+
urlSession: @Sendable @escaping () -> URLSession = { URLSession.shared },
27+
encoder: @Sendable @escaping () -> JSONEncoder = { JSONEncoder() },
28+
decoder: @Sendable @escaping () -> JSONDecoder = { JSONDecoder() }
29+
) {
30+
self.serverBaseURL = serverBaseURL
31+
self.urlSession = urlSession
32+
self.encoder = encoder
33+
self.decoder = decoder
34+
}
35+
36+
}
37+
38+
extension EndpointBuilderURLSession {
39+
40+
/// A request with no response body
41+
@inlinable
42+
public func request<E: Endpoint>(_ endpoint: E) async throws where E.Response == Never {
43+
try await requestData(endpoint)
44+
}
45+
46+
/// A request with a response body
47+
@inlinable
48+
public func request<E: Endpoint>(_ endpoint: E) async throws -> E.Response {
49+
let data = try await requestData(endpoint)
50+
return try decoder().decode(E.responseType, from: data)
51+
}
52+
53+
@discardableResult
54+
@usableFromInline
55+
func requestData<E: Endpoint>(_ endpoint: E) async throws -> Data {
56+
// construct URL
57+
let url: URL = serverBaseURL.appendingPath(endpoint.path)
58+
59+
// construct request
60+
var request = URLRequest(url: url)
61+
request.httpMethod = E.httpMethod.rawValue
62+
63+
if E.BodyContent.self != Never.self {
64+
request.setValue("application/json", forHTTPHeaderField: HTTPField.Name.contentType.rawName)
65+
request.httpBody = try encoder().encode(endpoint.body)
66+
}
67+
68+
if let authorization = endpoint.authorization {
69+
request.setValue(authorization.headerValue, forHTTPHeaderField: HTTPField.Name.authorization.rawName)
70+
}
71+
72+
// perform request
73+
return try await urlSession().responseData(for: request)
74+
}
75+
76+
}
77+
78+
extension URL {
79+
80+
func appendingPath(_ path: String) -> URL {
81+
#if canImport(FoundationNetworking)
82+
return self.appendingPathComponent(path)
83+
#else
84+
if #available(iOS 16.0, tvOS 16.0, macOS 13.0, watchOS 9.0, *) {
85+
return self.appending(path: path)
86+
} else {
87+
return self.appendingPathComponent(path)
88+
}
89+
#endif
90+
}
91+
92+
}
93+
94+
extension URLSession {
95+
96+
func responseData(for request: URLRequest) async throws -> Data {
97+
#if canImport(FoundationNetworking)
98+
await withCheckedContinuation { continuation in
99+
self.dataTask(with: request) { data, _, _ in
100+
guard let data = data else {
101+
continuation.resume(returning: Data())
102+
return
103+
}
104+
continuation.resume(returning: data)
105+
}.resume()
106+
}
107+
#else
108+
let (data, _) = try await self.data(for: request)
109+
return data
110+
#endif
111+
}
112+
113+
}

Sources/SwiftPackage/SwiftPackage.swift

Lines changed: 0 additions & 1 deletion
This file was deleted.

Sources/Tests/Tests.swift

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)