diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 92dbab2..104f2a0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: xcode-version: latest - uses: actions/checkout@v4 - name: Darwin build & test - run: swift test --skip IntegrationTests + run: swift test --enable-all-traits --skip IntegrationTests linux-test: runs-on: ubuntu-latest container: @@ -20,4 +20,4 @@ jobs: steps: - uses: actions/checkout@v4 - name: Linux build & test - run: swift test --skip IntegrationTests + run: swift test --traits ClientNIO,ServerVapor --skip IntegrationTests diff --git a/.spi.yml b/.spi.yml index fbf5631..7ea7490 100644 --- a/.spi.yml +++ b/.spi.yml @@ -4,7 +4,4 @@ builder: - documentation_targets: - "Haystack" - "HaystackClient" - - "HaystackClientDarwin" - - "HaystackClientNIO" - "HaystackServer" - - "HaystackServerVapor" diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..2ff2e98 --- /dev/null +++ b/.swiftformat @@ -0,0 +1 @@ +--ifdef no-indent diff --git a/Package.swift b/Package.swift index e58614a..77fc3c6 100644 --- a/Package.swift +++ b/Package.swift @@ -1,217 +1,80 @@ -// swift-tools-version: 5.7 +// swift-tools-version: 6.1 import PackageDescription -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) - let package = Package( - name: "Haystack", - platforms: [ - .macOS(.v12), - .iOS(.v15), - .tvOS(.v15), - .watchOS(.v8), - ], - products: [ - .library( - name: "Haystack", - targets: ["Haystack"] - ), - .library( - name: "HaystackClientDarwin", - targets: [ - "HaystackClient", - "HaystackClientDarwin", - ] - ), - .library( - name: "HaystackClientNIO", - targets: [ - "HaystackClient", - "HaystackClientNIO", - ] - ), - .library( - name: "HaystackServer", - targets: [ - "HaystackServer", - ] - ), - .library( - name: "HaystackServerVapor", - targets: [ - "HaystackServerVapor", - ] - ), - ], - dependencies: [ - .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0"), - .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0"), - .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), - ], - targets: [ - .target( - name: "Haystack", - dependencies: [] - ), - .target( - name: "HaystackClient", - dependencies: [ - "Haystack", - .product(name: "Crypto", package: "swift-crypto"), - ] - ), - .target( - name: "HaystackClientDarwin", - dependencies: [ - "Haystack", - "HaystackClient", - ] - ), - .target( - name: "HaystackClientNIO", - dependencies: [ - "Haystack", - "HaystackClient", - .product(name: "AsyncHTTPClient", package: "async-http-client"), - ] - ), - .target( - name: "HaystackServer", - dependencies: [ - "Haystack", - ] - ), - .target( - name: "HaystackServerVapor", - dependencies: [ - "Haystack", - "HaystackServer", - .product(name: "Vapor", package: "vapor"), - ] - ), +let package = Package( + name: "Haystack", + platforms: [ + .macOS(.v12), + .iOS(.v15), + .tvOS(.v15), + .watchOS(.v8), + ], + products: [ + .library( + name: "Haystack", + targets: ["Haystack"] + ), + .library( + name: "HaystackClient", + targets: [ + "HaystackClient", + ] + ), + .library( + name: "HaystackServer", + targets: [ + "HaystackServer", + ] + ), + ], + traits: [ + "ServerVapor", + "ClientNIO", + "ClientDarwin", + .default(enabledTraits: []), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0"), + .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0"), + .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), + ], + targets: [ + .target( + name: "Haystack", + dependencies: [] + ), + .target( + name: "HaystackClient", + dependencies: [ + "Haystack", + .product(name: "Crypto", package: "swift-crypto"), + .product(name: "AsyncHTTPClient", package: "async-http-client", condition: .when(traits: ["ClientNIO"])), + ] + ), + .target( + name: "HaystackServer", + dependencies: [ + "Haystack", + .product(name: "Vapor", package: "vapor", condition: .when(traits: ["ServerVapor"])), + ] + ), - // Tests - .testTarget( - name: "HaystackTests", - dependencies: ["Haystack"] - ), - .testTarget( - name: "HaystackClientTests", - dependencies: ["HaystackClient"] - ), - .testTarget( - name: "HaystackClientNIOIntegrationTests", - dependencies: ["HaystackClientNIO"] - ), - .testTarget( - name: "HaystackClientDarwinIntegrationTests", - dependencies: ["HaystackClientDarwin"] - ), - .testTarget( - name: "HaystackServerTests", - dependencies: ["HaystackServer"] - ), - .testTarget( - name: "HaystackServerVaporTests", - dependencies: [ - "HaystackServerVapor", - .product(name: "VaporTesting", package: "vapor"), - ] - ), - ] - ) -#else - let package = Package( - name: "Haystack", - products: [ - .library( - name: "Haystack", - targets: ["Haystack"] - ), - .library( - name: "HaystackClientNIO", - targets: [ - "HaystackClient", - "HaystackClientNIO", - ] - ), - .library( - name: "HaystackServer", - targets: [ - "HaystackServer", - ] - ), - .library( - name: "HaystackServerVapor", - targets: [ - "HaystackServerVapor", - ] - ), - ], - dependencies: [ - .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0"), - .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0"), - .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), - ], - targets: [ - .target( - name: "Haystack", - dependencies: [] - ), - .target( - name: "HaystackClient", - dependencies: [ - "Haystack", - .product(name: "Crypto", package: "swift-crypto"), - ] - ), - .target( - name: "HaystackClientNIO", - dependencies: [ - "Haystack", - "HaystackClient", - .product(name: "AsyncHTTPClient", package: "async-http-client"), - ] - ), - .target( - name: "HaystackServer", - dependencies: [ - "Haystack", - ] - ), - .target( - name: "HaystackServerVapor", - dependencies: [ - "Haystack", - "HaystackServer", - .product(name: "Vapor", package: "vapor"), - ] - ), - - // Tests - .testTarget( - name: "HaystackTests", - dependencies: ["Haystack"] - ), - .testTarget( - name: "HaystackClientTests", - dependencies: ["HaystackClient"] - ), - .testTarget( - name: "HaystackClientNIOIntegrationTests", - dependencies: ["HaystackClientNIO"] - ), - .testTarget( - name: "HaystackServerTests", - dependencies: ["HaystackServer"] - ), - .testTarget( - name: "HaystackServerVaporTests", - dependencies: [ - "HaystackServerVapor", - .product(name: "VaporTesting", package: "vapor"), - ] - ), - ] - ) -#endif + // Tests + .testTarget( + name: "HaystackTests", + dependencies: ["Haystack"] + ), + .testTarget( + name: "HaystackClientTests", + dependencies: ["HaystackClient"] + ), + .testTarget( + name: "HaystackServerTests", + dependencies: [ + "HaystackServer", + .product(name: "VaporTesting", package: "vapor", condition: .when(traits: ["ServerVapor"])), + ] + ), + ] +) diff --git a/README.md b/README.md index a8d7477..065bd69 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,51 @@ This contains the [Haystack type-system primitives](https://project-haystack.org/doc/docHaystack/Kinds) and utilities to interact with them. -### HaystackClientDarwin +### HaystackClient + +This defines the main functionality of Haystack API clients. It should not be imported directly; +its assets are imported automatically by `HaystackClientDarwin` or `HaystackClientNIO`. + +Once you create a client, you can use it to make requests: + +```swift +func yesterdaysValues() async throws -> Grid { + let client = ... + + // Open and authenticate. This must be called before requests can be made + try await client.open() + + // Request the historical values for @28e7fb7d-e20316e0 + let grid = try await client.hisRead(id: Ref("28e7fb7d-e20316e0"), range: .yesterday) + + // Close the client session and log out + try await client.close() + + return grid +} +``` + +### HaystackServer + +This defines the standard functionality and data processing of Haystack API servers, based on generic backing data +stores. In most cases, Haystack servers should use the `HaystackServer` class and customize storage behavior by +implementing the `RecordStore`, `HistoryStore`, and `WatchStore` protocols. + +```swift +struct InfluxHistoryStore: HistoryStore { + // Define storage behavior here + ... +} + +let server = HaystackServer( + historyStore: InfluxHistoryStore(), + ... +) +``` + +## Available Traits + +### ClientDarwin A Darwin-only client driver for the [Haystack HTTP API](https://project-haystack.org/doc/docHaystack/HttpApi) that @@ -60,7 +104,7 @@ to reduce dependencies. Here's an example of how to use it: ```swift -import HaystackClientDarwin +import HaystackClient func client() throws -> Client { return try Client( @@ -71,7 +115,7 @@ func client() throws -> Client { } ``` -### HaystackClientNIO +### ClientNIO A cross-platform client driver for the [Haystack HTTP API](https://project-haystack.org/doc/docHaystack/HttpApi) that @@ -81,7 +125,7 @@ are deploying to Darwin platforms and are willing to accept more dependencies. Here's an example of how to use it: ```swift -import HaystackClientNIO +import HaystackClient func client() throws -> Client { let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) @@ -94,31 +138,7 @@ func client() throws -> Client { } ``` -### HaystackClient - -This defines the main functionality of Haystack API clients. It should not be imported directly; -its assets are imported automatically by `HaystackClientDarwin` or `HaystackClientNIO`. - -Once you create a client, you can use it to make requests: - -```swift -func yesterdaysValues() async throws -> Grid { - let client = ... - - // Open and authenticate. This must be called before requests can be made - try await client.open() - - // Request the historical values for @28e7fb7d-e20316e0 - let grid = try await client.hisRead(id: Ref("28e7fb7d-e20316e0"), range: .yesterday) - - // Close the client session and log out - try await client.close() - - return grid -} -``` - -### HaystackServerVapor +### ServerVapor A server for the [Haystack HTTP API](https://project-haystack.org/doc/docHaystack/HttpApi) that uses [Vapor](https://github.com/vapor/vapor). It's separated from the `HaystackServer` package so that clients may use @@ -135,22 +155,14 @@ try app.register(collection: HaystackRouteCollection(delegate: ...)) The delegate is a protocol that can be implemented however the user sees fit, although the standard Haystack implementation is defined in `HaystackServer`. -### HaystackServer +## Contributing -This defines the standard functionality and data processing of Haystack API servers, based on generic backing data -stores. In most cases, Haystack servers should use the `HaystackServer` class and customize storage behavior by -implementing the `RecordStore`, `HistoryStore`, and `WatchStore` protocols. +### Integration tests -```swift -struct InfluxHistoryStore: HistoryStore { - // Define storage behavior here - ... -} +To run the integration tests, run a local Haxall server: -let server = HaystackServer( - historyStore: InfluxHistoryStore(), - ... -) +```sh +docker run -e 'SU_PASS=su' -p '8080:8080' needleinajaystack/haxall:latest ``` ## License diff --git a/Sources/HaystackClientDarwin/Client+URLSession.swift b/Sources/HaystackClient/ClientDarwin/Client+URLSession.swift similarity index 97% rename from Sources/HaystackClientDarwin/Client+URLSession.swift rename to Sources/HaystackClient/ClientDarwin/Client+URLSession.swift index 0d375d4..9e7d154 100644 --- a/Sources/HaystackClientDarwin/Client+URLSession.swift +++ b/Sources/HaystackClient/ClientDarwin/Client+URLSession.swift @@ -1,5 +1,5 @@ +#if ClientDarwin import Foundation -import HaystackClient public extension Client { /// Create a Haystack API Client that uses a `URLSession` from `Foundation` that @@ -26,3 +26,4 @@ public extension Client { ) } } +#endif diff --git a/Sources/HaystackClientDarwin/URLSessionFetcher.swift b/Sources/HaystackClient/ClientDarwin/URLSessionFetcher.swift similarity index 98% rename from Sources/HaystackClientDarwin/URLSessionFetcher.swift rename to Sources/HaystackClient/ClientDarwin/URLSessionFetcher.swift index 004360c..c54058e 100644 --- a/Sources/HaystackClientDarwin/URLSessionFetcher.swift +++ b/Sources/HaystackClient/ClientDarwin/URLSessionFetcher.swift @@ -1,5 +1,5 @@ +#if ClientDarwin import Foundation -import HaystackClient /// A Haystack API Client fetcher based on `URLSession`. This is only available on Darwin platforms. struct URLSessionFetcher: Fetcher { @@ -50,3 +50,4 @@ struct URLSessionFetcher: Fetcher { public enum URLSessionFetcherError: Error { case invalidUrl(String) } +#endif diff --git a/Sources/HaystackClientNIO/Client+HTTPClient.swift b/Sources/HaystackClient/ClientNIO/Client+HTTPClient.swift similarity index 97% rename from Sources/HaystackClientNIO/Client+HTTPClient.swift rename to Sources/HaystackClient/ClientNIO/Client+HTTPClient.swift index 2048218..62d3e74 100644 --- a/Sources/HaystackClientNIO/Client+HTTPClient.swift +++ b/Sources/HaystackClient/ClientNIO/Client+HTTPClient.swift @@ -1,6 +1,6 @@ +#if ClientNIO import AsyncHTTPClient import Foundation -import HaystackClient import NIO public extension Client { @@ -29,3 +29,4 @@ public extension Client { ) } } +#endif diff --git a/Sources/HaystackClientNIO/HTTPClientFetcher.swift b/Sources/HaystackClient/ClientNIO/HTTPClientFetcher.swift similarity index 98% rename from Sources/HaystackClientNIO/HTTPClientFetcher.swift rename to Sources/HaystackClient/ClientNIO/HTTPClientFetcher.swift index 2513c5c..663d084 100644 --- a/Sources/HaystackClientNIO/HTTPClientFetcher.swift +++ b/Sources/HaystackClient/ClientNIO/HTTPClientFetcher.swift @@ -1,6 +1,6 @@ +#if ClientNIO import AsyncHTTPClient import Foundation -import HaystackClient import NIO import NIOFoundationCompat @@ -51,3 +51,4 @@ struct HTTPClientFetcher: Fetcher { ) } } +#endif diff --git a/Sources/HaystackClientDarwin/Exports.swift b/Sources/HaystackClientDarwin/Exports.swift deleted file mode 100644 index 4b7fe47..0000000 --- a/Sources/HaystackClientDarwin/Exports.swift +++ /dev/null @@ -1 +0,0 @@ -@_exported import HaystackClient diff --git a/Sources/HaystackClientNIO/Exports.swift b/Sources/HaystackClientNIO/Exports.swift deleted file mode 100644 index 4b7fe47..0000000 --- a/Sources/HaystackClientNIO/Exports.swift +++ /dev/null @@ -1 +0,0 @@ -@_exported import HaystackClient diff --git a/Sources/HaystackServerVapor/Application+Haystack.swift b/Sources/HaystackServer/ServerVapor/Application+Haystack.swift similarity index 96% rename from Sources/HaystackServerVapor/Application+Haystack.swift rename to Sources/HaystackServer/ServerVapor/Application+Haystack.swift index 18a2777..5582047 100644 --- a/Sources/HaystackServerVapor/Application+Haystack.swift +++ b/Sources/HaystackServer/ServerVapor/Application+Haystack.swift @@ -1,3 +1,4 @@ +#if ServerVapor import Haystack import Vapor @@ -24,3 +25,4 @@ extension Request { return haystack } } +#endif diff --git a/Sources/HaystackServerVapor/HTTPMediaType+zinc.swift b/Sources/HaystackServer/ServerVapor/HTTPMediaType+zinc.swift similarity index 91% rename from Sources/HaystackServerVapor/HTTPMediaType+zinc.swift rename to Sources/HaystackServer/ServerVapor/HTTPMediaType+zinc.swift index 6938557..06c975f 100644 --- a/Sources/HaystackServerVapor/HTTPMediaType+zinc.swift +++ b/Sources/HaystackServer/ServerVapor/HTTPMediaType+zinc.swift @@ -1,6 +1,8 @@ +#if ServerVapor import Vapor public extension HTTPMediaType { /// The `application/zinc` media type: https://project-haystack.org/doc/docHaystack/Zinc static let zinc = HTTPMediaType(type: "text", subType: "zinc", parameters: ["charset": "utf-8"]) } +#endif diff --git a/Sources/HaystackServerVapor/HaystackRouteCollection.swift b/Sources/HaystackServer/ServerVapor/HaystackRouteCollection.swift similarity index 99% rename from Sources/HaystackServerVapor/HaystackRouteCollection.swift rename to Sources/HaystackServer/ServerVapor/HaystackRouteCollection.swift index 2f52068..9024cc1 100644 --- a/Sources/HaystackServerVapor/HaystackRouteCollection.swift +++ b/Sources/HaystackServer/ServerVapor/HaystackRouteCollection.swift @@ -1,3 +1,4 @@ +#if ServerVapor import Haystack import Vapor @@ -546,3 +547,4 @@ public struct HaystackRouteCollection: RouteCollection { } } } +#endif diff --git a/Sources/HaystackServerVapor/Request+Grid.swift b/Sources/HaystackServer/ServerVapor/Request+Grid.swift similarity index 98% rename from Sources/HaystackServerVapor/Request+Grid.swift rename to Sources/HaystackServer/ServerVapor/Request+Grid.swift index 94ca1e2..30ace28 100644 --- a/Sources/HaystackServerVapor/Request+Grid.swift +++ b/Sources/HaystackServer/ServerVapor/Request+Grid.swift @@ -1,3 +1,4 @@ +#if ServerVapor import Haystack import Vapor @@ -44,3 +45,4 @@ extension Request { return Dict(dictMap) } } +#endif diff --git a/Tests/HaystackClientDarwinIntegrationTests/HaystackClientDarwinIntegrationTests.swift b/Tests/HaystackClientTests/HaystackClientDarwinIntegrationTests.swift similarity index 98% rename from Tests/HaystackClientDarwinIntegrationTests/HaystackClientDarwinIntegrationTests.swift rename to Tests/HaystackClientTests/HaystackClientDarwinIntegrationTests.swift index 93955eb..d259795 100644 --- a/Tests/HaystackClientDarwinIntegrationTests/HaystackClientDarwinIntegrationTests.swift +++ b/Tests/HaystackClientTests/HaystackClientDarwinIntegrationTests.swift @@ -1,6 +1,7 @@ +#if ClientDarwin import Foundation import Haystack -import HaystackClientDarwin +import HaystackClient import Testing /// To use these tests, run a [Haxall](https://github.com/haxall/haxall) server and set the username and password @@ -83,3 +84,4 @@ struct HaystackClientDarwinIntegration { try print(await client.watchUnsubRemove(watchId: "id", ids: [Ref("28e7fb47-d67ab19a")])) } } +#endif diff --git a/Tests/HaystackClientNIOIntegrationTests/HaystackClientNIOIntegrationTests.swift b/Tests/HaystackClientTests/HaystackClientNIOIntegrationTests.swift similarity index 99% rename from Tests/HaystackClientNIOIntegrationTests/HaystackClientNIOIntegrationTests.swift rename to Tests/HaystackClientTests/HaystackClientNIOIntegrationTests.swift index 1ece6c4..38ca745 100644 --- a/Tests/HaystackClientNIOIntegrationTests/HaystackClientNIOIntegrationTests.swift +++ b/Tests/HaystackClientTests/HaystackClientNIOIntegrationTests.swift @@ -1,7 +1,8 @@ +#if ClientNIO import AsyncHTTPClient import Foundation import Haystack -import HaystackClientNIO +import HaystackClient import NIO import Testing @@ -120,3 +121,4 @@ struct HaystackClientNIOIntegrationTests { } } } +#endif diff --git a/Tests/HaystackServerVaporTests/HaystackServerVaporTests.swift b/Tests/HaystackServerTests/HaystackServerVaporTests.swift similarity index 99% rename from Tests/HaystackServerVaporTests/HaystackServerVaporTests.swift rename to Tests/HaystackServerTests/HaystackServerVaporTests.swift index a6423e7..c82637e 100644 --- a/Tests/HaystackServerVaporTests/HaystackServerVaporTests.swift +++ b/Tests/HaystackServerTests/HaystackServerVaporTests.swift @@ -1,5 +1,6 @@ +#if ServerVapor import Haystack -import HaystackServerVapor +import HaystackServer import Testing import VaporTesting @@ -234,3 +235,4 @@ struct HaystackAPIMock: Haystack.API, Sendable { return GridBuilder().toGrid() } } +#endif