Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9ad6a85
Initial work to resolve strict concurrency warnings
kevin-kp Jun 12, 2024
3f021e6
Merge branch 'main' into chore/strict-concurrency
kevin-kp Jun 14, 2024
2c2fbeb
Make Provider sendable and remove last warnings
kevin-kp Jul 11, 2024
077537d
Fix concurrency issues in tests
kevin-kp Jul 11, 2024
70462e4
Remove addParameter
kevin-kp Oct 4, 2024
c90bac3
Merge branch 'chore/strict-concurrency'
kevin-kp Oct 4, 2024
c086900
Add method to add an interceptor
kevin-kp Oct 4, 2024
19b8a2e
Merge branch 'chore/strict-concurrency'
kevin-kp Oct 4, 2024
9177abe
Add insert method to insert an interceptor at a specific index
kevin-kp Oct 4, 2024
1904db6
see: https://github.com/joshuawright11/papyrus/pull/64
kevin-kp Oct 7, 2024
e04597a
Merge pull request #1 from kevin-kp/chore/check-macro-compatibility
kevin-kp Oct 7, 2024
61d2804
Correct identations
kevin-kp Oct 7, 2024
92af9d4
Fix issue with swift-syntax 509.0.0
kevin-kp Oct 7, 2024
76553d3
Check if import resolves linux issue
kevin-kp Oct 7, 2024
d1f0bd3
Wrap iso8601Formatter with ResourceMutex as it is not Sendable
kevin-kp Oct 7, 2024
a517e24
Bump package swift version to Swift 6
kevin-kp Oct 9, 2024
d2b4b92
Remove coders as it is an error in Swift 6 mode
kevin-kp Oct 9, 2024
c505d17
Revert swift version back to Swift 5.9
kevin-kp Oct 9, 2024
4ea9622
Check if we can simply add swift 6 checks in CI
kevin-kp Oct 9, 2024
b9a7791
Try ubuntu 24.04 for swift 6
kevin-kp Oct 11, 2024
5c43f04
Try swift 6.0.1
kevin-kp Oct 11, 2024
ffb564d
Revert "Revert swift version back to Swift 5.9"
kevin-kp Oct 11, 2024
d158171
Add separate Package.swift for swift 6
kevin-kp Nov 15, 2024
8a6d87b
Fix missing `any` errors
kevin-kp Nov 18, 2024
65837a0
Also fix missing `any` in unit tests
kevin-kp Nov 18, 2024
9026102
Merge pull request #2 from kevin-kp/feature/swift-6
kevin-kp Nov 18, 2024
8dd4679
Update README.md
kevin-kp Nov 19, 2024
b93d2c0
Update README.md
kevin-kp Nov 19, 2024
f0b1af5
fix: change Response to PapyrusResponse to match rename
KhoraLee Nov 29, 2024
7b1f0f4
Merge pull request #3 from KhoraLee/patch-1
kevin-kp Dec 2, 2024
aec6862
Update README.md
kevin-kp May 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
branches: [main]

jobs:
test-macos:
test-macos-xcode-15:
runs-on: macos-13
env:
DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer
Expand All @@ -17,6 +17,16 @@ jobs:
run: swift build -v
- name: Test
run: swift test -v
test-macos-xcode-16:
runs-on: macos-14
env:
DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer
steps:
- uses: actions/checkout@v3
- name: Build
run: swift build -v
- name: Test
run: swift test -v
test-linux:
runs-on: ubuntu-22.04
strategy:
Expand All @@ -29,3 +39,26 @@ jobs:
run: swift build
- name: Run tests
run: swift test
test-linux-swift6:
runs-on: ubuntu-24.04
strategy:
matrix:
swift: [6.0.1]
container: swift:${{ matrix.swift }}
steps:
- uses: actions/checkout@v3
- name: Build
run: swift build
- name: Run tests
run: swift test
check-macro-compatibility:
name: Check Macro Compatibility
runs-on: macos-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run Swift Macro Compatibility Check
uses: Matejkob/swift-macro-compatibility-check@v1
with:
run-tests: false
major-versions-only: true
67 changes: 67 additions & 0 deletions Example/Example.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Foundation
import Papyrus

// MARK: 0. Define your API.

@API
@Mock
public protocol Sample: Sendable {
@GET("/todos")
func getTodos() async throws -> [Todo]

@POST("/todos")
func createTodo(name: String) async throws -> Todo

@URLForm
@POST("/todos/:id/tags")
func createTag(id: Int) async throws

@Multipart
@POST("/todo/:id/attachment")
func upload(id: Int, part1: Part, part2: Part) async throws
}

public struct Todo: Codable, Sendable {
let id: Int
let name: String
}

@main
struct Example {
static func main() async throws {
// MARK: 1. Create a Provider with any custom configuration.

let provider = Provider(baseURL: "http://127.0.0.1:3000")
.modifyRequests {
$0.addAuthorization(.bearer("<my-auth-token>"))
$0.keyMapping = .snakeCase
}
.intercept { req, next in
let start = Date()
let res = try await next(req)
let elapsedTime = String(format: "%.2fs", Date().timeIntervalSince(start))
let statusCode = res.statusCode.map { "\($0)" } ?? "N/A"
print("Got a \(statusCode) for \(req.method) \(req.url!) after \(elapsedTime)")
return res
}

// MARK: 2. Initialize an API instance & call an endpoint.

let api: any Sample = SampleAPI(provider: provider)
let todos = try await api.getTodos()
print(todos)

// MARK: 3. Easily mock endpoints for tests.

let mock = SampleMock()
mock.mockGetTodos {
return [
Todo(id: 1, name: "Foo"),
Todo(id: 2, name: "Bar"),
]
}

let mockedTodos = try await mock.getTodos()
print(mockedTodos)
}
}
60 changes: 0 additions & 60 deletions Example/main.swift

This file was deleted.

18 changes: 16 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let package = Package(
.library(name: "Papyrus", targets: ["Papyrus"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-syntax", "509.0.0"..<"601.0.0-prerelease"),
.package(url: "https://github.com/apple/swift-syntax", "509.0.0"..<"601.0.0"),
.package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.1.0"),
],
targets: [
Expand All @@ -36,7 +36,10 @@ let package = Package(
dependencies: [
"PapyrusPlugin"
],
path: "Papyrus/Sources"
path: "Papyrus/Sources",
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")
]
),
.testTarget(
name: "PapyrusTests",
Expand Down Expand Up @@ -70,3 +73,14 @@ let package = Package(
),
]
)

#if compiler(>=6)
for target in package.targets where target.type != .system && target.type != .test {
target.swiftSettings = target.swiftSettings ?? []
target.swiftSettings?.append(contentsOf: [
.enableExperimentalFeature("StrictConcurrency"),
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("InferSendableFromCaptures"),
])
}
#endif
90 changes: 90 additions & 0 deletions [email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// swift-tools-version:6.0
import CompilerPluginSupport
import PackageDescription

let package = Package(
name: "papyrus",
platforms: [
.iOS("13.0"),
.macOS("10.15"),
.tvOS("13.0")
],
products: [
.executable(name: "Example", targets: ["Example"]),
.library(name: "Papyrus", targets: ["Papyrus"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-syntax", "509.0.0"..<"601.0.0"),
.package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.1.0"),
],
targets: [

// MARK: Demo

.executableTarget(
name: "Example",
dependencies: [
"Papyrus"
],
path: "Example"
),

// MARK: Library

.target(
name: "Papyrus",
dependencies: [
"PapyrusPlugin"
],
path: "Papyrus/Sources",
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")
]
),
.testTarget(
name: "PapyrusTests",
dependencies: [
"Papyrus"
],
path: "Papyrus/Tests"
),

// MARK: Plugin

.macro(
name: "PapyrusPlugin",
dependencies: [
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftOperators", package: "swift-syntax"),
.product(name: "SwiftParser", package: "swift-syntax"),
.product(name: "SwiftParserDiagnostics", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
],
path: "PapyrusPlugin/Sources"
),
.testTarget(
name: "PapyrusPluginTests",
dependencies: [
"PapyrusPlugin",
.product(name: "MacroTesting", package: "swift-macro-testing"),
],
path: "PapyrusPlugin/Tests"
),
]
)

for target in package.targets {
target.swiftSettings = target.swiftSettings ?? []
target.swiftSettings?.append(contentsOf: [
.enableUpcomingFeature("ExistentialAny")
])
}

for target in package.targets where target.type == .system || target.type == .test {
target.swiftSettings?.append(contentsOf: [
.swiftLanguageMode(.v5),
.enableExperimentalFeature("StrictConcurrency"),
.enableUpcomingFeature("InferSendableFromCaptures"),
])
}
26 changes: 26 additions & 0 deletions Papyrus/Sources/CoderProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
public protocol CoderProvider: Sendable {
func provideHttpBodyEncoder() -> any HTTPBodyEncoder
func provideHttpBodyDecoder() -> any HTTPBodyDecoder
func provideQueryEncoder() -> URLEncodedFormEncoder
func provideQueryDecoder() -> URLEncodedFormDecoder
}

public struct DefaultProvider: CoderProvider {
public init() {}

public func provideHttpBodyEncoder() -> any HTTPBodyEncoder {
return .json()
}

public func provideHttpBodyDecoder() -> any HTTPBodyDecoder {
return .json()
}

public func provideQueryEncoder() -> URLEncodedFormEncoder {
return URLEncodedFormEncoder()
}

public func provideQueryDecoder() -> URLEncodedFormDecoder {
return URLEncodedFormDecoder()
}
}
12 changes: 0 additions & 12 deletions Papyrus/Sources/Coders.swift

This file was deleted.

14 changes: 7 additions & 7 deletions Papyrus/Sources/Extensions/URLSession+Papyrus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@ import FoundationNetworking
extension Provider {
public convenience init(baseURL: String,
urlSession: URLSession = .shared,
modifiers: [RequestModifier] = [],
interceptors: [Interceptor] = []) {
modifiers: [any RequestModifier] = [],
interceptors: [any Interceptor] = []) {
self.init(baseURL: baseURL, http: urlSession, modifiers: modifiers, interceptors: interceptors)
}
}

// MARK: `HTTPService` Conformance

extension URLSession: HTTPService {
public func build(method: String, url: URL, headers: [String: String], body: Data?) -> PapyrusRequest {
public func build(method: String, url: URL, headers: [String: String], body: Data?) -> any PapyrusRequest {
var request = URLRequest(url: url)
request.httpMethod = method
request.httpBody = body
request.allHTTPHeaderFields = headers
return request
}

public func request(_ req: PapyrusRequest) async -> PapyrusResponse {
public func request(_ req: any PapyrusRequest) async -> any PapyrusResponse {
#if os(Linux) // Linux doesn't have access to async URLSession APIs
await withCheckedContinuation { continuation in
let urlRequest = req.urlRequest
Expand Down Expand Up @@ -55,13 +55,13 @@ private struct _Response: PapyrusResponse {
let urlRequest: URLRequest
let urlResponse: URLResponse?

var request: PapyrusRequest? { urlRequest }
let error: Error?
var request: (any PapyrusRequest)? { urlRequest }
let error: (any Error)?
let body: Data?
let headers: [String: String]?
var statusCode: Int? { (urlResponse as? HTTPURLResponse)?.statusCode }

init(request: URLRequest, response: URLResponse?, error: Error?, body: Data?) {
init(request: URLRequest, response: URLResponse?, error: (any Error)?, body: Data?) {
self.urlRequest = request
self.urlResponse = response
self.error = error
Expand Down
Loading