mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-08 19:19:42 +00:00
ios/scripts/linux-test.sh stages HapiKit + shared/fixtures at repo depth
into a persistent tmp dir and runs swift test in swift:6.1-noble; the
manifest drops HapiUI (SwiftUI/swift-markdown/Highlightr) under
#if os(Linux). 458 tests green, including all 48 chat and 11 pagination
golden fixtures.
Fixes that fell out of the first real compile of the blind-written port:
- ChatTypes.CodexReview.wireValue + SummaryPatching legacy id fallback:
split expressions that exceeded the Swift 6 type-checker budget
- FileEndpointsTests: raw string containing "# terminated the literal
early (never compiled anywhere) — now ##-delimited
- SSEClient.backoffSleep: Task { try? ... } inferred Task<()?, Never>
- Darwin gates: Security/Keychain behind canImport(Security) (tests use
InMemoryCredentialStore via CredentialStoring), CryptoKit digest with
FNV-1a filename fallback, FoundationNetworking imports for URLSession
types, corelibs URLCache diskPath: initializer, get-only
waitsForConnectivity, delegate-based SSE transport where
URLSession.bytes(for:) does not exist
81 lines
3.3 KiB
Swift
81 lines
3.3 KiB
Swift
import Foundation
|
|
#if canImport(FoundationNetworking)
|
|
import FoundationNetworking // URLSession types live here on Linux
|
|
#endif
|
|
|
|
/// Seam between the client stack and the actual HTTP transport.
|
|
///
|
|
/// `APIClient` and `AuthManager` speak only to this protocol, so tests inject
|
|
/// a recording/scripted performer instead of resorting to `URLProtocol`
|
|
/// tricks. The production implementation wraps `URLSession`.
|
|
public protocol HTTPPerforming: Sendable {
|
|
/// Executes one HTTP exchange. Implementations must not throw on non-2xx
|
|
/// statuses — status handling (401 refresh, `APIError`) belongs to the
|
|
/// caller. Throw only for transport-level failures (`URLError`).
|
|
func perform(_ request: URLRequest) async throws -> (Data, HTTPURLResponse)
|
|
}
|
|
|
|
/// Production `HTTPPerforming` backed by `URLSession`.
|
|
public struct URLSessionHTTPPerformer: HTTPPerforming {
|
|
public let session: URLSession
|
|
|
|
public init(session: URLSession) {
|
|
self.session = session
|
|
}
|
|
|
|
/// Shared instance used by default across the client stack, so every
|
|
/// consumer benefits from one connection pool and one URL cache.
|
|
public static let shared = URLSessionHTTPPerformer(session: makeDefaultSession())
|
|
|
|
/// `URLSession` configured for the hub protocol.
|
|
///
|
|
/// The 256 MB disk cache exists chiefly for generated images: the hub
|
|
/// serves them with `ETag` + `Cache-Control: private, max-age=31536000,
|
|
/// immutable`, so `URLCache` answers repeats locally (and revalidates via
|
|
/// `If-None-Match`/304 without the hub's CLI round-trip). Streaming gzip
|
|
/// decompression of JSON responses is transparent in `URLSession`.
|
|
public static func makeDefaultSession() -> URLSession {
|
|
let configuration = URLSessionConfiguration.default
|
|
#if canImport(FoundationNetworking)
|
|
// corelibs-foundation only ships the legacy `diskPath:` initializer.
|
|
configuration.urlCache = URLCache(
|
|
memoryCapacity: 32 * 1024 * 1024,
|
|
diskCapacity: 256 * 1024 * 1024,
|
|
diskPath: nil
|
|
)
|
|
#else
|
|
configuration.urlCache = URLCache(
|
|
memoryCapacity: 32 * 1024 * 1024,
|
|
diskCapacity: 256 * 1024 * 1024,
|
|
directory: nil
|
|
)
|
|
#endif
|
|
configuration.requestCachePolicy = .useProtocolCachePolicy
|
|
return URLSession(configuration: configuration)
|
|
}
|
|
|
|
public func perform(_ request: URLRequest) async throws -> (Data, HTTPURLResponse) {
|
|
let (data, response) = try await session.data(for: request)
|
|
guard let http = response as? HTTPURLResponse else {
|
|
throw URLError(.badServerResponse)
|
|
}
|
|
return (data, http)
|
|
}
|
|
}
|
|
|
|
/// JSON coders shared by the whole transport layer so wire behavior is
|
|
/// uniform (and request bodies are byte-stable for tests).
|
|
public enum HapiJSON {
|
|
/// Wire fields are camelCase and dates are epoch-ms integers, so the
|
|
/// default strategies are exactly right.
|
|
public static let decoder = JSONDecoder()
|
|
|
|
/// `sortedKeys` makes encoded bodies deterministic;
|
|
/// `withoutEscapingSlashes` keeps embedded paths readable.
|
|
public static let encoder: JSONEncoder = {
|
|
let encoder = JSONEncoder()
|
|
encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes]
|
|
return encoder
|
|
}()
|
|
}
|