Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let package = Package(
.library(name: "NetworkServer", targets: ["NetworkServer"]),
],
dependencies: [
.package(url: "https://github.com/luizmb/FP.git", from: "1.7.0"),
.package(url: "https://github.com/luizmb/FP.git", from: "1.8.1"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.4.0"),
],
Expand Down
2 changes: 1 addition & 1 deletion Sources/HTMLTemplating/TemplateEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public struct HTMLEnvironment {

public typealias Context = [String: TemplateValue]

public indirect enum TemplateValue {
public indirect enum TemplateValue: Sendable {
case string(String)
case list([Context])
case bool(Bool)
Expand Down
4 changes: 2 additions & 2 deletions Sources/NetworkClient/RequestPublisher+Applicative.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import Foundation

public extension RequestPublisher {
/// Lifts a pure value into `RequestPublisher`, ignoring the `URLRequest`.
static func pure(_ value: A) -> RequestPublisher<A> {
static func pure(_ value: A) -> RequestPublisher<A> where A: Sendable {
RequestPublisher { _ in
Just(value).setFailureType(to: HTTPError.self).eraseToAnyPublisher()
}
}

/// Applies a request-dependent function to a request-dependent value.
/// Both are run against the same `URLRequest` and zipped.
static func apply<B>(_ f: RequestPublisher<(A) -> B>, _ r: RequestPublisher<A>) -> RequestPublisher<B> {
static func apply<B>(_ f: RequestPublisher<@Sendable (A) -> B>, _ r: RequestPublisher<A>) -> RequestPublisher<B> {
RequestPublisher<B> { request in
f.run(request)
.zip(r.run(request))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import FP
// MARK: - RequestPublisher Applicative Operators

// (<*>) :: RequestPublisher<(a -> b)> -> RequestPublisher<a> -> RequestPublisher<b>
public func <*> <A, B>(_ f: RequestPublisher<(A) -> B>, _ r: RequestPublisher<A>) -> RequestPublisher<B> {
public func <*> <A, B>(_ f: RequestPublisher<@Sendable (A) -> B>, _ r: RequestPublisher<A>) -> RequestPublisher<B> {
RequestPublisher.apply(f, r)
}

Expand Down
10 changes: 5 additions & 5 deletions Sources/NetworkClient/RequestPublisher+Functor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@ import Foundation

public extension RequestPublisher {
/// Transforms the output value; the same `URLRequest` is threaded through unchanged.
func map<B>(_ f: @escaping (A) -> B) -> RequestPublisher<B> {
func map<B: Sendable>(_ f: @escaping @Sendable (A) -> B) -> RequestPublisher<B> {
RequestPublisher<B> { request in run(request).map(f).eraseToAnyPublisher() }
}

/// Curried fmap for point-free composition.
static func fmap<B>(_ f: @escaping (A) -> B) -> (RequestPublisher<A>) -> RequestPublisher<B> {
static func fmap<B: Sendable>(_ f: @escaping @Sendable (A) -> B) -> @Sendable (RequestPublisher<A>) -> RequestPublisher<B> {
{ $0.map(f) }
}

/// Replaces the output with a constant value.
func replace<B>(with value: B) -> RequestPublisher<B> {
map { _ in value }
func replace<B: Sendable>(with value: B) -> RequestPublisher<B> {
map { @Sendable _ in value }
}

/// Maps the failure side of the underlying publisher.
func mapError(_ f: @escaping (HTTPError) -> HTTPError) -> RequestPublisher<A> {
func mapError(_ f: @escaping @Sendable (HTTPError) -> HTTPError) -> RequestPublisher<A> {
RequestPublisher { request in run(request).mapError(f).eraseToAnyPublisher() }
}
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/NetworkClient/RequestPublisher+FunctorOperators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ import FP
// MARK: - RequestPublisher Functor Operators

// (<$>) :: (a -> b) -> RequestPublisher<a> -> RequestPublisher<b>
public func <£> <A, B>(_ f: @escaping (A) -> B, _ r: RequestPublisher<A>) -> RequestPublisher<B> {
public func <£> <A, B: Sendable>(_ f: @escaping @Sendable (A) -> B, _ r: RequestPublisher<A>) -> RequestPublisher<B> {
r.map(f)
}

// (<&>) :: RequestPublisher<a> -> (a -> b) -> RequestPublisher<b>
public func <&> <A, B>(_ r: RequestPublisher<A>, _ f: @escaping (A) -> B) -> RequestPublisher<B> {
public func <&> <A, B: Sendable>(_ r: RequestPublisher<A>, _ f: @escaping @Sendable (A) -> B) -> RequestPublisher<B> {
r.map(f)
}

// ($>) :: RequestPublisher<a> -> b -> RequestPublisher<b>
// swiftlint:disable:next identifier_name
public func £> <A, B>(_ r: RequestPublisher<A>, _ value: B) -> RequestPublisher<B> {
public func £> <A, B: Sendable>(_ r: RequestPublisher<A>, _ value: B) -> RequestPublisher<B> {
r.replace(with: value)
}

// (<$) :: b -> RequestPublisher<a> -> RequestPublisher<b>
public func <£ <A, B>(_ value: B, _ r: RequestPublisher<A>) -> RequestPublisher<B> {
public func <£ <A, B: Sendable>(_ value: B, _ r: RequestPublisher<A>) -> RequestPublisher<B> {
r £> value
}
#endif
18 changes: 9 additions & 9 deletions Sources/NetworkClient/RequestPublisher+Monad.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Foundation
public extension RequestPublisher {
/// Chains two `RequestPublisher`s, threading the same `URLRequest` through both
/// (Reader + Publisher monad stack).
func flatMap<B>(_ f: @escaping (A) -> RequestPublisher<B>) -> RequestPublisher<B> {
func flatMap<B>(_ f: @escaping @Sendable (A) -> RequestPublisher<B>) -> RequestPublisher<B> {
RequestPublisher<B> { request in
self.run(request)
.flatMap { a in f(a).run(request) }
Expand All @@ -16,7 +16,7 @@ public extension RequestPublisher {
}

/// Curried bind for point-free composition.
static func bind<B>(_ f: @escaping (A) -> RequestPublisher<B>) -> (RequestPublisher<A>) -> RequestPublisher<B> {
static func bind<B>(_ f: @escaping @Sendable (A) -> RequestPublisher<B>) -> @Sendable (RequestPublisher<A>) -> RequestPublisher<B> {
{ $0.flatMap(f) }
}

Expand All @@ -32,23 +32,23 @@ public extension RequestPublisher {

/// Kleisli composition (left-to-right): `(X -> m A) >=> (A -> m B) = X -> m B`.
static func kleisli<X, B>(
_ f: @escaping (X) -> RequestPublisher<A>,
_ g: @escaping (A) -> RequestPublisher<B>
) -> (X) -> RequestPublisher<B> {
_ f: @escaping @Sendable (X) -> RequestPublisher<A>,
_ g: @escaping @Sendable (A) -> RequestPublisher<B>
) -> @Sendable (X) -> RequestPublisher<B> {
{ x in f(x).flatMap(g) }
}

/// Kleisli composition (right-to-left).
static func kleisliBack<X, B>(
_ g: @escaping (A) -> RequestPublisher<B>,
_ f: @escaping (X) -> RequestPublisher<A>
) -> (X) -> RequestPublisher<B> {
_ g: @escaping @Sendable (A) -> RequestPublisher<B>,
_ f: @escaping @Sendable (X) -> RequestPublisher<A>
) -> @Sendable (X) -> RequestPublisher<B> {
RequestPublisher.kleisli(f, g)
}

/// Recovers from failure by running an alternative `RequestPublisher`
/// against the same `URLRequest`.
func flatMapError(_ f: @escaping (HTTPError) -> RequestPublisher<A>) -> RequestPublisher<A> {
func flatMapError(_ f: @escaping @Sendable (HTTPError) -> RequestPublisher<A>) -> RequestPublisher<A> {
RequestPublisher { request in
self.run(request)
.catch { error in f(error).run(request) }
Expand Down
16 changes: 8 additions & 8 deletions Sources/NetworkClient/RequestPublisher+MonadOperators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,28 @@ import FP
// MARK: - RequestPublisher Monad Operators

// (>>-) :: RequestPublisher<a> -> (a -> RequestPublisher<b>) -> RequestPublisher<b>
public func >>- <A, B>(_ r: RequestPublisher<A>, _ f: @escaping (A) -> RequestPublisher<B>) -> RequestPublisher<B> {
public func >>- <A, B>(_ r: RequestPublisher<A>, _ f: @escaping @Sendable (A) -> RequestPublisher<B>) -> RequestPublisher<B> {
r.flatMap(f)
}

// (-<<) :: (a -> RequestPublisher<b>) -> RequestPublisher<a> -> RequestPublisher<b>
public func -<< <A, B>(_ f: @escaping (A) -> RequestPublisher<B>, _ r: RequestPublisher<A>) -> RequestPublisher<B> {
public func -<< <A, B>(_ f: @escaping @Sendable (A) -> RequestPublisher<B>, _ r: RequestPublisher<A>) -> RequestPublisher<B> {
r.flatMap(f)
}

// (>=>) :: (x -> RequestPublisher<a>) -> (a -> RequestPublisher<b>) -> x -> RequestPublisher<b>
public func >=> <X, A, B>(
_ f: @escaping (X) -> RequestPublisher<A>,
_ g: @escaping (A) -> RequestPublisher<B>
) -> (X) -> RequestPublisher<B> {
_ f: @escaping @Sendable (X) -> RequestPublisher<A>,
_ g: @escaping @Sendable (A) -> RequestPublisher<B>
) -> @Sendable (X) -> RequestPublisher<B> {
RequestPublisher.kleisli(f, g)
}

// (<=<) :: (a -> RequestPublisher<b>) -> (x -> RequestPublisher<a>) -> x -> RequestPublisher<b>
public func <=< <X, A, B>(
_ g: @escaping (A) -> RequestPublisher<B>,
_ f: @escaping (X) -> RequestPublisher<A>
) -> (X) -> RequestPublisher<B> {
_ g: @escaping @Sendable (A) -> RequestPublisher<B>,
_ f: @escaping @Sendable (X) -> RequestPublisher<A>
) -> @Sendable (X) -> RequestPublisher<B> {
RequestPublisher.kleisliBack(g, f)
}
#endif
4 changes: 2 additions & 2 deletions Sources/NetworkClient/RequestPublisher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import FP
/// Combines the Reader monad (threading the same `URLRequest` through a chain)
/// with the Publisher monad (async response streaming with typed errors).
public struct RequestPublisher<A>: FunctionWrapper {
public let run: (URLRequest) -> AnyPublisher<A, HTTPError>
public let run: @Sendable (URLRequest) -> AnyPublisher<A, HTTPError>

public init(_ fn: @escaping (URLRequest) -> AnyPublisher<A, HTTPError>) {
public init(_ fn: @escaping @Sendable (URLRequest) -> AnyPublisher<A, HTTPError>) {
run = fn
}

Expand Down
26 changes: 13 additions & 13 deletions Tests/NetworkClientTests/NetworkClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ private func firstResult<O, E: Error>(of publisher: AnyPublisher<O, E>) -> Resul

// MARK: - RequestPublisher builders

private func just<A>(_ value: A) -> RequestPublisher<A> {
private func just<A: Sendable>(_ value: A) -> RequestPublisher<A> {
RequestPublisher { _ in Just(value).setFailureType(to: HTTPError.self).eraseToAnyPublisher() }
}

Expand Down Expand Up @@ -284,17 +284,17 @@ struct RequestPublisherFunctorTests {
@Suite("RequestPublisher — Applicative")
struct RequestPublisherApplicativeTests {
@Test func apply_combinesFunctionAndValue() {
let f = RequestPublisher<(Int) -> String>.pure(String.init)
let f = RequestPublisher<@Sendable (Int) -> String>.pure(String.init)
#expect(run(RequestPublisher.apply(f, just(42)))?.successValue == "42")
}

@Test func apply_propagatesLeftFailure() {
let f = fail(.badStatus(500, Data())) as RequestPublisher<(Int) -> Int>
let f = fail(.badStatus(500, Data())) as RequestPublisher<@Sendable (Int) -> Int>
#expect(run(RequestPublisher.apply(f, just(1)))?.isFailure == true)
}

@Test func apply_propagatesRightFailure() {
let f = just { (n: Int) in n + 1 }
let f = just({ @Sendable (n: Int) in n + 1 } as @Sendable (Int) -> Int)
let a = fail(.badStatus(500, Data())) as RequestPublisher<Int>
#expect(run(RequestPublisher.apply(f, a))?.isFailure == true)
}
Expand Down Expand Up @@ -326,14 +326,14 @@ struct RequestPublisherMonadTests {
}

@Test func kleisli_composes() {
let f: (String) -> RequestPublisher<Int> = { s in just(s.count) }
let g: (Int) -> RequestPublisher<String> = { n in just("\(n)") }
let f: @Sendable (String) -> RequestPublisher<Int> = { s in just(s.count) }
let g: @Sendable (Int) -> RequestPublisher<String> = { n in just("\(n)") }
#expect(run(RequestPublisher.kleisli(f, g)("hello"))?.successValue == "5")
}

@Test func kleisliBack_composes() {
let f: (String) -> RequestPublisher<Int> = { s in just(s.count) }
let g: (Int) -> RequestPublisher<String> = { n in just("\(n)") }
let f: @Sendable (String) -> RequestPublisher<Int> = { s in just(s.count) }
let g: @Sendable (Int) -> RequestPublisher<String> = { n in just("\(n)") }
#expect(run(RequestPublisher.kleisliBack(g, f)("hello"))?.successValue == "5")
}

Expand All @@ -356,7 +356,7 @@ struct RequestPublisherOperatorTests {
@Test func replaceLeftOp() { #expect(run("r" <£ just(5))?.successValue == "r") }

@Test func applyOp() {
let f = just { (n: Int) in n + 1 }
let f = just({ @Sendable (n: Int) in n + 1 } as @Sendable (Int) -> Int)
#expect(run(f <*> just(41))?.successValue == 42)
}
@Test func seqRightOp() { #expect(run(just(1) *> just("k"))?.successValue == "k") }
Expand All @@ -366,13 +366,13 @@ struct RequestPublisherOperatorTests {
@Test func flippedBindOp() { #expect(run({ n in just(n * n) } -<< just(3))?.successValue == 9) }

@Test func kleisliOp() {
let f: (Int) -> RequestPublisher<Int> = { n in just(n + 1) }
let g: (Int) -> RequestPublisher<String> = { n in just("\(n)") }
let f: @Sendable (Int) -> RequestPublisher<Int> = { n in just(n + 1) }
let g: @Sendable (Int) -> RequestPublisher<String> = { n in just("\(n)") }
#expect(run((f >=> g)(41))?.successValue == "42")
}
@Test func kleisliBackOp() {
let f: (Int) -> RequestPublisher<Int> = { n in just(n + 1) }
let g: (Int) -> RequestPublisher<String> = { n in just("\(n)") }
let f: @Sendable (Int) -> RequestPublisher<Int> = { n in just(n + 1) }
let g: @Sendable (Int) -> RequestPublisher<String> = { n in just("\(n)") }
#expect(run((g <=< f)(41))?.successValue == "42")
}
}
Expand Down
Loading