From 5b989592ee2fb6ba0ce6650b14a20d3ae55a13cc Mon Sep 17 00:00:00 2001 From: taylorswift Date: Thu, 9 Jan 2025 03:37:56 -0600 Subject: [PATCH] fix documentation errors (#24) * add workflow for validating docs using unidoc * fix unidoc errors * fix remaining errors * purge DocC hashes --- .gitignore | 16 +- Package.resolved | 142 ++++++++++++------ Package.swift | 4 + Snippets/advanced-async-sequences.swift | 2 +- .../advanced-async-sequences.md | 10 +- .../async-http-client-by-example.md | 12 +- .../getting-started-with-hummingbird.md | 10 +- .../getting-started-with-mongokitten.md | 8 +- ...ed-with-structured-concurrency-in-swift.md | 8 +- .../getting-started-with-swiftpm-snippets.md | 2 +- ...o-build-a-proxy-server-with-hummingbird.md | 2 +- ...introduction-to-swift-service-lifecycle.md | 32 ++-- .../logging-for-server-side-swift-apps.md | 80 +++++----- ...dates-with-changestreams-and-websockets.md | 8 +- ...d-concurrency-and-shared-state-in-swift.md | 8 +- .../using-hummingbird-request-context.md | 4 +- .../using-swiftnio-channels.md | 8 +- ...ts-tutorial-using-swift-and-hummingbird.md | 28 ++-- .../whats-new-in-hummingbird-2.md | 18 +-- .../working-with-udp-in-swiftnio.md | 4 +- 20 files changed, 233 insertions(+), 173 deletions(-) diff --git a/.gitignore b/.gitignore index 72b20b0..ba7cf03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ -.DS_Store -.swiftpm -.vscode -.build -Sources/**/*.html +/.DS_Store +/.swiftpm +/.vscode +/.build +/.build.ssgc +/.ssgc +/Sources/**/*.html +/.build.ssgc +/.index-build docc -.build.ssgc -.index-build diff --git a/Package.resolved b/Package.resolved index b7b19cf..ec184aa 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "f6c66464b7de44086527b7c7cc0d835ea39ea97cad7b0a31a2ce3c82650872f4", + "originHash" : "bc7e7da478a806e0f7cbe377f7686d6279b8f12a875ce24603298eac157fefe8", "pins" : [ { "identity" : "async-http-client", "kind" : "remoteSourceControl", - "location" : "https://github.com/swift-server/async-http-client", + "location" : "https://github.com/swift-server/async-http-client.git", "state" : { - "revision" : "e8babad8226b9b3f956a252d5b80e36b0c9d62a9", - "version" : "1.22.0" + "revision" : "2119f0d9cc1b334e25447fe43d3693c0e60e6234", + "version" : "1.24.0" } }, { @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/adam-fowler/compress-nio.git", "state" : { - "revision" : "a52d3ac5f48820846d34d78d6f59316481e84968", - "version" : "1.2.1" + "revision" : "459a803a4e38718d94d64f2b31e83f01d934f324", + "version" : "1.4.0" } }, { @@ -69,8 +69,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/hummingbird-project/hummingbird.git", "state" : { - "revision" : "6c568da113a7abe712e4a00883a85ff7745d6929", - "version" : "2.0.0" + "revision" : "7a41c20c25866064f22b2bfa2c8194083e7e1595", + "version" : "2.6.1" + } + }, + { + "identity" : "hummingbird-auth", + "kind" : "remoteSourceControl", + "location" : "https://github.com/hummingbird-project/hummingbird-auth.git", + "state" : { + "revision" : "8630a49acca3b38c50e29d61ab263cb7edf0b06d", + "version" : "2.0.2" } }, { @@ -78,8 +87,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/hummingbird-project/hummingbird-websocket.git", "state" : { - "revision" : "f53e2afe403b225a674fb5d1f0c5830d3991e9dc", - "version" : "2.0.0" + "revision" : "11b4b5235e03f07511e4bb8e22204d35c5691120", + "version" : "2.3.0" } }, { @@ -87,14 +96,14 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/orlandos-nl/MongoKitten.git", "state" : { - "revision" : "6654e801833d5dab3c04af065646ba727f9542e9", - "version" : "7.9.7" + "revision" : "d9a82f57e24018cfa47a4aa359b51e684040871a", + "version" : "7.9.8" } }, { "identity" : "swift-algorithms", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-algorithms", + "location" : "https://github.com/apple/swift-algorithms.git", "state" : { "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42", "version" : "1.2.0" @@ -103,19 +112,28 @@ { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-argument-parser", + "location" : "https://github.com/apple/swift-argument-parser.git", "state" : { "revision" : "41982a3656a71c768319979febd796c6fd111d5c", "version" : "1.5.0" } }, + { + "identity" : "swift-asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-asn1.git", + "state" : { + "revision" : "7faebca1ea4f9aaf0cda1cef7c43aecd2311ddf6", + "version" : "1.3.0" + } + }, { "identity" : "swift-async-algorithms", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-async-algorithms.git", "state" : { - "revision" : "6ae9a051f76b81cc668305ceed5b0e0a7fd93d20", - "version" : "1.0.1" + "revision" : "4c3ea81f81f0a25d0470188459c6d4bf20cf2f97", + "version" : "1.0.3" } }, { @@ -132,8 +150,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections.git", "state" : { - "revision" : "9bf03ff58ce34478e66aaee630e491823326fd06", - "version" : "1.1.3" + "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", + "version" : "1.1.4" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "ff0f781cf7c6a22d52957e50b104f5768b50c779", + "version" : "3.10.0" } }, { @@ -141,8 +168,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-distributed-tracing.git", "state" : { - "revision" : "11c756c5c4d7de0eeed8595695cadd7fa107aa19", - "version" : "1.1.1" + "revision" : "6483d340853a944c96dbcc28b27dd10b6c581703", + "version" : "1.1.2" + } + }, + { + "identity" : "swift-extras-base64", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-extras/swift-extras-base64.git", + "state" : { + "revision" : "dc8121fdd2b444c97d6b0534e8ad4ddecbe0d5f4", + "version" : "1.0.0" } }, { @@ -150,17 +186,26 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-http-types.git", "state" : { - "revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd", - "version" : "1.3.0" + "revision" : "ef18d829e8b92d731ad27bb81583edd2094d1ce3", + "version" : "1.3.1" + } + }, + { + "identity" : "swift-jobs", + "kind" : "remoteSourceControl", + "location" : "https://github.com/hummingbird-project/swift-jobs.git", + "state" : { + "revision" : "6aeb0a179567b944ecf81e1bbfdd474c1ee171f1", + "version" : "1.0.0-beta.6" } }, { "identity" : "swift-log", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-log", + "location" : "https://github.com/apple/swift-log.git", "state" : { - "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", - "version" : "1.6.1" + "revision" : "96a2f8a0fa41e9e09af4585e2724c4e825410b91", + "version" : "1.6.2" } }, { @@ -175,19 +220,19 @@ { "identity" : "swift-mustache", "kind" : "remoteSourceControl", - "location" : "https://github.com/hummingbird-project/swift-mustache", + "location" : "https://github.com/hummingbird-project/swift-mustache.git", "state" : { - "revision" : "cde358e364ab26f2b72a5f70613cfb0a574de6c9", - "version" : "2.0.0-beta.3" + "revision" : "9de71fc4d10f0efc0ac758d0acf14e75d39b47b4", + "version" : "2.0.0" } }, { "identity" : "swift-nio", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio", + "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "4c4453b489cf76e6b3b0f300aba663eb78182fad", - "version" : "2.70.0" + "revision" : "dca6594f65308c761a9c409e09fbf35f48d50d34", + "version" : "2.77.0" } }, { @@ -195,8 +240,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-extras.git", "state" : { - "revision" : "d1ead62745cc3269e482f1c51f27608057174379", - "version" : "1.24.0" + "revision" : "2e9746cfc57554f70b650b021b6ae4738abef3e6", + "version" : "1.24.1" } }, { @@ -204,8 +249,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-http2.git", "state" : { - "revision" : "b5f7062b60e4add1e8c343ba4eb8da2e324b3a94", - "version" : "1.34.0" + "revision" : "170f4ca06b6a9c57b811293cebcb96e81b661310", + "version" : "1.35.0" } }, { @@ -213,8 +258,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-ssl.git", "state" : { - "revision" : "7b84abbdcef69cc3be6573ac12440220789dcd69", - "version" : "2.27.2" + "revision" : "c7e95421334b1068490b5d41314a50e70bab23d1", + "version" : "2.29.0" } }, { @@ -222,8 +267,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-transport-services.git", "state" : { - "revision" : "38ac8221dd20674682148d6451367f89c2652980", - "version" : "1.21.0" + "revision" : "bbd5e63cf949b7db0c9edaf7a21e141c52afe214", + "version" : "1.23.0" } }, { @@ -256,10 +301,10 @@ { "identity" : "swift-service-lifecycle", "kind" : "remoteSourceControl", - "location" : "https://github.com/swift-server/swift-service-lifecycle", + "location" : "https://github.com/swift-server/swift-service-lifecycle.git", "state" : { - "revision" : "24c800fb494fbee6e42bc156dc94232dc08971af", - "version" : "2.6.1" + "revision" : "c2e97cf6f81510f2d6b4a69453861db65d478560", + "version" : "2.6.3" } }, { @@ -267,8 +312,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-system.git", "state" : { - "revision" : "d2ba781702a1d8285419c15ee62fd734a9437ff5", - "version" : "1.3.2" + "revision" : "c8a44d836fe7913603e246acab7c528c2e780168", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-websocket", + "kind" : "remoteSourceControl", + "location" : "https://github.com/hummingbird-project/swift-websocket.git", + "state" : { + "revision" : "b15960a331ad39b3c1ee9e76afc34d951e07803b", + "version" : "1.2.1" } } ], diff --git a/Package.swift b/Package.swift index 18ebc2b..a5bac76 100644 --- a/Package.swift +++ b/Package.swift @@ -11,8 +11,10 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0"), + .package(url: "https://github.com/hummingbird-project/hummingbird-auth.git", from: "2.0.2"), .package(url: "https://github.com/hummingbird-project/hummingbird-websocket.git", from: "2.0.0"), .package(url: "https://github.com/hummingbird-project/swift-mustache.git", from: "2.0.0-beta.3"), + .package(url: "https://github.com/hummingbird-project/swift-jobs.git", from: "1.0.0-beta.6"), .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.21.1"), .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0"), .package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"), @@ -32,10 +34,12 @@ let package = Package( .product(name: "HummingbirdRouter", package: "hummingbird"), .product(name: "AsyncHTTPClient", package: "async-http-client"), .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "Jobs", package: "swift-jobs"), .product(name: "NIOCore", package: "swift-nio"), .product(name: "NIOPosix", package: "swift-nio"), .product(name: "Logging", package: "swift-log"), .product(name: "ServiceLifecycle", package: "swift-service-lifecycle"), + .product(name: "HummingbirdAuth", package: "hummingbird-auth"), .product(name: "HummingbirdWebSocket", package: "hummingbird-websocket"), .product(name: "HummingbirdWSCompression", package: "hummingbird-websocket"), .product(name: "Mustache", package: "swift-mustache"), diff --git a/Snippets/advanced-async-sequences.swift b/Snippets/advanced-async-sequences.swift index b600bf6..33fc122 100644 --- a/Snippets/advanced-async-sequences.swift +++ b/Snippets/advanced-async-sequences.swift @@ -207,7 +207,7 @@ extension DelayedElementEmitter { } } // snippet.end -// snipet.delayedprint +// snippet.delayedprint let delayedEmitter = DelayedElementEmitter( elements: [1, 2, 3, 4, 5], delay: .seconds(1) diff --git a/Sources/Articles/Documentation.docc/2024/advanced-async-sequences/advanced-async-sequences.md b/Sources/Articles/Documentation.docc/2024/advanced-async-sequences/advanced-async-sequences.md index 7e55d66..0e91d89 100644 --- a/Sources/Articles/Documentation.docc/2024/advanced-async-sequences/advanced-async-sequences.md +++ b/Sources/Articles/Documentation.docc/2024/advanced-async-sequences/advanced-async-sequences.md @@ -21,7 +21,7 @@ When you're using a `for .. in` loop to iterate elements, you're always leveragi ### IteratorProtocol -Sequence implementations only need to provide one function, the ``Sequence/makeIterator`` function. This function creates an ``IteratorProtocol`` that you implement as well. +Sequence implementations only need to provide one function, the ``Sequence/makeIterator [requirement]`` function. This function creates an ``IteratorProtocol`` that you implement as well. _IteratorProtocol_ should be implemented as a `struct`, and has a single function called ``IteratorProtocol/next()``. This _mutating_ function returns the next element in the sequence. @@ -113,9 +113,9 @@ The `downloadState` property is set to `downloading` before parallelising work. ## Implementing Custom AsyncSequence -You can implement your own ``AsyncSequence`` by implementing the ``AsyncSequence`` protocol. This protocol has two associated types: ``Element`` and ``AsyncIterator``. You'll need to implement the ``AsyncSequence/makeAsyncIterator()`` function and the ``AsyncIteratorProtocol``. +You can implement your own ``AsyncSequence`` by implementing the ``AsyncSequence`` protocol. This protocol has two associated types: ``AsyncSequence/Element`` and ``AsyncSequence/AsyncIterator``. You'll need to implement the ``AsyncSequence/makeAsyncIterator()`` function and the ``AsyncIteratorProtocol``. -First we'll define our custom ``DelayedElementEmitter`` struct. This struct will emit elements from an array with a delay between each element. +First we'll define our custom `DelayedElementEmitter` struct. This struct will emit elements from an array with a delay between each element. @Snippet(path: "site/Snippets/advanced-async-sequences", slice: "delayedelementemitter") @@ -129,7 +129,7 @@ You can now use this custom ``AsyncSequence`` in your code. The following exampl @Snippet(path: "site/Snippets/advanced-async-sequences", slice: "delayedprint") -Note that our custom ``DelayedElementEmitter`` cannot be iterated upon without a `try` keyword to handle errors. This is because the ``AsyncIteratorProtocol/next()`` function can throw errors due to being marked as `throws`. We can also handle the ``CancellationError``s that ``Task.sleep(for:tolerance:clock:)`` throws in the `next()` function. +Note that our custom `DelayedElementEmitter` cannot be iterated upon without a `try` keyword to handle errors. This is because the ``AsyncIteratorProtocol/next()`` function can throw errors due to being marked as `throws`. We can also handle the ``CancellationError``s that ``Task.sleep(for:tolerance:clock:)`` throws in the `next()` function. ```swift mutating func next() async -> Element? { @@ -155,7 +155,7 @@ The ``AsyncIteratorProtocol/next()`` function can be cancelled by the consumer. It is expected that Async Sequences handle cancellation gracefully as appropriate. In Networking, this could mean cancelling a network request. In a generator, this could mean stopping the generation of new elements through ``AsyncStream.Continuation.finish()``. -To detect a cancellation signal, use ``withTaskCancellationHandler(operation:onCancel:)``. Or when using Swift Service Lifecycle, use ``withTaskCancellationOrGracefulShutdownHandler(operation:onCancelOrGracefulShutdown:)``. +To detect a cancellation signal, use ``withTaskCancellationHandler(operation:onCancel:isolation:)``. Or when using Swift Service Lifecycle, use ``withTaskCancellationOrGracefulShutdownHandler(isolation:operation:onCancelOrGracefulShutdown:)``. ## Changes in Swift 6 diff --git a/Sources/Articles/Documentation.docc/2024/async-http-client-by-example/async-http-client-by-example.md b/Sources/Articles/Documentation.docc/2024/async-http-client-by-example/async-http-client-by-example.md index b49da88..f5848ed 100644 --- a/Sources/Articles/Documentation.docc/2024/async-http-client-by-example/async-http-client-by-example.md +++ b/Sources/Articles/Documentation.docc/2024/async-http-client-by-example/async-http-client-by-example.md @@ -3,7 +3,7 @@ Swift ``/AsyncHTTPClient`` is an HTTP client library built on top of SwiftNIO. It provides a solid solution for efficiently managing HTTP requests by leveraging the Swift Concurrency model, thus simplifying networking tasks for developers. The library's asynchronous and non-blocking request methods ensure that network operations do not hinder the responsiveness of the application. Additionally, the library offers TLS support, automatic HTTP/2 over HTTPS and several other convenient features. - + The AsyncHTTPClient library is a comprehensive tool for seamless HTTP communication for server-side Swift applications. Throughout this article, we'll delve into practical [examples](https://github.com/swift-on-server/async-http-client-by-example-sample) to showcase the capabilities of this library. @@ -45,9 +45,9 @@ In the `main.swift` file, import the AsyncHTTPClient library and initialize an H 2. The ``HTTPClient/Configuration`` parameter is set, defining various aspects of the ``HTTPClient``'s behavior. 3. ``HTTPClient/Configuration/RedirectConfiguration`` is specified to follow redirects up to a maximum of 3 times and disallow redirect cycles. 4. Set ``HTTPClient/Configuration/Timeout``s for different phases of the HTTP request process, such as connection establishment, reading, and writing. -5. Cleanup by calling the ``HTTPClient/shutdown() [96AYW]`` method on the HTTPClient instance. +5. Cleanup by calling the ``HTTPClient/shutdown() -> ()`` method on the HTTPClient instance. -Please be aware that it is essential to properly terminate the HTTP client after executing requests. Forgetting to invoke the ``HTTPClient/shutdown() [96AYW]`` method may cause the library to issue a warning about a potential memory leak when compiling the application in debug mode. +Please be aware that it is essential to properly terminate the HTTP client after executing requests. Forgetting to invoke the ``HTTPClient/shutdown() -> ()`` method may cause the library to issue a warning about a potential memory leak when compiling the application in debug mode. ## Performing HTTP requests @@ -85,7 +85,7 @@ The following code snippet demonstrates how to encode request bodies and decode 3. The `Input` data is encoded into JSON data using a ``ByteBuffer`` and set as the request body. 4. If the response status is ok and the content type is JSON, the response body is processed. 5. The response body chunks are collected asynchronously using ``AsyncSequence.collect(upTo:)`` -6. The buffer containing the JSON data response is decoded as an `Output` structure using ``JSONDecoder.decode(_:from:) [7481W]``. +6. The buffer containing the JSON data response is decoded as an `Output` structure using ``JSONDecoder.decode(_:from:) (_, ByteBuffer)``. The code snippet above demonstrates how to use Swift's Codable protocol to handle JSON data in HTTP communication. It defines structures for input and output data, sends a POST request with JSON payload, and processes the response by decoding JSON into a designated output structure. @@ -95,12 +95,12 @@ The AsyncHTTPClient library provides support for file downloads using the ``File @Snippet(path: "site/Snippets/ahc_download") -1. A ``FileDownloadDelegate`` is created to manage file downloads. +1. A ``FileDownloadDelegate`` is created to manage file downloads. 2. Specify the download destination path. 3. A progress reporting function is provided to monitor the download progress. 4. The file download request is executed using the request URL and the delegate. -Running this example will display the download progress, indicating the received bytes and the total bytes, with the same information also available within the `fileDownloadResponse` object. +Running this example will display the download progress, indicating the received bytes and the total bytes, with the same information also available within the `fileDownloadResponse` object. There are many more configuration options available for the Swift AsyncHTTPClient library. It is also possible to create custom delegate objects; additional useful examples and code snippets are provided in the project's [README on GitHub](https://github.com/swift-server/async-http-client). diff --git a/Sources/Articles/Documentation.docc/2024/getting-started-with-hummingbird/getting-started-with-hummingbird.md b/Sources/Articles/Documentation.docc/2024/getting-started-with-hummingbird/getting-started-with-hummingbird.md index 4f9bfcc..4e070ee 100644 --- a/Sources/Articles/Documentation.docc/2024/getting-started-with-hummingbird/getting-started-with-hummingbird.md +++ b/Sources/Articles/Documentation.docc/2024/getting-started-with-hummingbird/getting-started-with-hummingbird.md @@ -41,13 +41,13 @@ Finally, you can use Visual Studio Code to develop and run apps as well. More on ## Project Structure -The Hummingbird project template includes only two files: +The Hummingbird project template includes only two files: -`App.swift` containing your Command Line arguments, and ``AsyncParsableCommand/run()`` function. The type is `@main` annotated, making this is the entrypoint for your app. +`App.swift` containing your Command Line arguments, and ``AsyncParsableCommand/run()`` function. The type is `@main` annotated, making this is the entrypoint for your app. -From here, the app calls into `buildApplication`, which is located in `Application+build.swift`. +From here, the app calls into `buildApplication`, which is located in `Application+build.swift`. -Once the app is configured in `buildApplication`, the server is started by calling ``Application.runService()``. +Once the app is configured in `buildApplication`, the server is started by calling ``Application.runService(gracefulShutdownSignals:)``. Running services starts Hummingbird and all registered dependencies (``Service``s). This kicks off ``ServiceLifecycle``, which manages your app's lifecycle. For now, services are not important to understand in detail, but you can learn about them in detail here: @@ -91,7 +91,7 @@ A route handler has two input parameters: a ``Request`` first, and the Context s Let's add a new GET route at the `/` path. This means that visiting your server at `http://localhost:8080` you'll see the response. -@Snippet(path: "site/Snippets/HummingbirdApp". slice: basic_route) +@Snippet(path: "site/Snippets/HummingbirdApp", slice: basic_route) Rebuild and re-run your app, using Xcode, `swift run` or your other preferred method. Note that we've changed the return type to ``String`` to return a body. diff --git a/Sources/Articles/Documentation.docc/2024/getting-started-with-mongokitten/getting-started-with-mongokitten.md b/Sources/Articles/Documentation.docc/2024/getting-started-with-mongokitten/getting-started-with-mongokitten.md index 7a2cad3..62945fe 100644 --- a/Sources/Articles/Documentation.docc/2024/getting-started-with-mongokitten/getting-started-with-mongokitten.md +++ b/Sources/Articles/Documentation.docc/2024/getting-started-with-mongokitten/getting-started-with-mongokitten.md @@ -46,9 +46,9 @@ Let's create our first connection: For our social network, we'll create a Post model using the ``Codable`` protocol: -@Snippet(path: "site/Snippets/mongokitten-models", slice: "models") +@Snippet(path: "site/Snippets/mongokitten-basics", slice: "models") -The ``ObjectId`` type is MongoDB's native unique identifier, similar to a ``UUID``. Every document in MongoDB has an `_id` field, which must be unique within a collection. +The ``ObjectId`` type is MongoDB's native unique identifier, similar to a ``UUID``. Every document in MongoDB has an `_id` field, which must be unique within a collection. ### Creating Posts @@ -82,7 +82,7 @@ MongoKitten supports filtering and sorting on most queries. @Snippet(path: "site/Snippets/mongokitten-basics", slice: "find-by-author") -The ``MongoCollection.find(_:) [7HX5V]`` method returns a ``FindQueryBuilder``, a type of ``MongoCursor`` that allows you to chain more methods. +The ``MongoCollection.find(_:) (Query)`` method returns a ``FindQueryBuilder``, a type of ``MongoCursor`` that allows you to chain more methods. ### Sorting and Limiting @@ -92,7 +92,7 @@ The **find** method accepts one argument, a filter. By providing the find filter Then, chain the following methods: -- ``FindQueryBuilder.sort(_:) [5UP4H]`` allows you to sort the results by one or more fields. +- ``FindQueryBuilder.sort(_:) (Sorting)`` allows you to sort the results by one or more fields. - ``FindQueryBuilder.limit(_:)`` allows you to limit the number of results returned. ### Understanding BSON diff --git a/Sources/Articles/Documentation.docc/2024/getting-started-with-structured-concurrency-in-swift/getting-started-with-structured-concurrency-in-swift.md b/Sources/Articles/Documentation.docc/2024/getting-started-with-structured-concurrency-in-swift/getting-started-with-structured-concurrency-in-swift.md index 651bdbb..0d49479 100644 --- a/Sources/Articles/Documentation.docc/2024/getting-started-with-structured-concurrency-in-swift/getting-started-with-structured-concurrency-in-swift.md +++ b/Sources/Articles/Documentation.docc/2024/getting-started-with-structured-concurrency-in-swift/getting-started-with-structured-concurrency-in-swift.md @@ -134,7 +134,7 @@ Now, a common request; "How can I await the delivery of these books concurrently A task is a concurrent unit of work. In concurrency, many tasks can run in parallel. -The _easiest_ way to create a task is using the **unstructured** `Task` type. It's used to run a piece of code concurrently in the background, similar to `DispatchQueue.global().async {}`. In addition, you can manage it's lifecycle by `cancel()`ing it. Finally, you can also `await` its `value` for it to finish. +The _easiest_ way to create a task is using the **unstructured** `Task` type. It's used to run a piece of code concurrently in the background, similar to `DispatchQueue.global().async {}`. In addition, you can manage it's lifecycle by `cancel()`ing it. Finally, you can also `await` its `value` for it to finish. @Snippet(path: "site/Snippets/getting-started-concurrency-buy-books", slice: "buyBooksInBackground") @@ -227,7 +227,7 @@ When finding yourself in a situation where you need to delay a task, you can use try await Task.sleep(for: .seconds(10)) ``` -An extra feature of `Task.sleep` is that it can be cancelled. If the task is +An extra feature of `Task.sleep` is that it can be cancelled. If the task is cancelled while it's sleeping, the sleep will be interrupted and throw a ``CancellationError``. ### Cancellation Handlers @@ -273,7 +273,7 @@ func buyBooks() async throws { This is a structured way to run multiple pieces of work concurrently. It's clear when the tasks start and when they end. You can run many pieces of work in parallel. And you can await all tasks being completed, and get an error if any one of them fails. -The above task group can throw errors, but not all task groups need to throw. If you use ``withTaskGroup(of:returning:body:)``, you'll be able to run tasks that don't throw, and you won't need to handle errors. +The above task group can throw errors, but not all task groups need to throw. If you use ``withTaskGroup(of:returning:isolation:body:)``, you'll be able to run tasks that don't throw, and you won't need to handle errors. In the above example, `withThrowingTaskGroup(of: Book.self)` specifies that each task _must_ produce a `Book` result if successful. In some cases, the result of the task is not necessary. In this case however, the results are helpful to collect the books that were bought. @@ -298,7 +298,7 @@ func buyBooks() async throws -> [Book] { ### Discarding Task Groups -In some cases, you might not be interested in the result of the task group. For example, you might want to run a number of tasks concurrently, but these tasks don't return results. In that case, you can use ``withDiscardingTaskGroup(returning:body:)`` and ``withThrowingDiscardingTaskGroup(returning:body:)`` from iOS 17 and macOS 14. This is a structured way to run multiple pieces of work concurrently, without needing to retain results. +In some cases, you might not be interested in the result of the task group. For example, you might want to run a number of tasks concurrently, but these tasks don't return results. In that case, you can use ``withDiscardingTaskGroup(returning:isolation:body:)`` and ``withThrowingDiscardingTaskGroup(returning:isolation:body:)`` from iOS 17 and macOS 14. This is a structured way to run multiple pieces of work concurrently, without needing to retain results. The regular task groups create a collection of results, which you can then iterate over. In some cases, such as a TCP server, this collection of results is not needed and grow indefinitely. In that case, you'll want to use a discarding task group to prevent an ever-growing collection of results. Note that `Void` results are still stored and occupy a small amount of memory! diff --git a/Sources/Articles/Documentation.docc/2024/getting-started-with-swiftpm-snippets/getting-started-with-swiftpm-snippets.md b/Sources/Articles/Documentation.docc/2024/getting-started-with-swiftpm-snippets/getting-started-with-swiftpm-snippets.md index 2015653..240c3ec 100644 --- a/Sources/Articles/Documentation.docc/2024/getting-started-with-swiftpm-snippets/getting-started-with-swiftpm-snippets.md +++ b/Sources/Articles/Documentation.docc/2024/getting-started-with-swiftpm-snippets/getting-started-with-swiftpm-snippets.md @@ -146,7 +146,7 @@ Create a markdown article named `My article.md` in the `docs.docc` directory. In the example above, we have specified the Snippet to include by `path` identity. -Despite its naming, the `path` syntax is not a file path. The first component is the name of the package **as specified by the ``PackageDescription/Package/name`` field in the manifest**. The second component is always the string `Snippets`. The third component is the Snippet ID, which is the name of the Snippet file without the `.swift` extension. +Despite its naming, the `path` syntax is not a file path. The first component is the name of the package **as specified by the `name` field in the manifest**. The second component is always the string `Snippets`. The third component is the Snippet ID, which is the name of the Snippet file without the `.swift` extension. If the Snippet ID contains special characters, you should pass the ID as-is, without replacing any characters. diff --git a/Sources/Articles/Documentation.docc/2024/how-to-build-a-proxy-server-with-hummingbird/how-to-build-a-proxy-server-with-hummingbird.md b/Sources/Articles/Documentation.docc/2024/how-to-build-a-proxy-server-with-hummingbird/how-to-build-a-proxy-server-with-hummingbird.md index 48d6cfa..5cc6e37 100644 --- a/Sources/Articles/Documentation.docc/2024/how-to-build-a-proxy-server-with-hummingbird/how-to-build-a-proxy-server-with-hummingbird.md +++ b/Sources/Articles/Documentation.docc/2024/how-to-build-a-proxy-server-with-hummingbird/how-to-build-a-proxy-server-with-hummingbird.md @@ -35,7 +35,7 @@ As you see, there are four steps to forward a request: 3. Execute the request using ``HTTPClient``. 4. Forward the ``Response`` back to the end user. -In both step 2 and step 4, the HTTP body is passed along as a stream, or specifically an ``AsyncSequence``. This allows SwiftNIO to pass along the data efficiently, whilst applying backpressure in both directions (client and remote). +In both step 2 and step 4, the HTTP body is passed along as a stream, or specifically an ``AsyncSequence`` of ``ByteBuffer``. This allows SwiftNIO to pass along the data efficiently, whilst applying backpressure in both directions (client and remote). Due to the design of Hummingbird, where bodies are always streamed, this is not just an efficient solution but also the easiest to implement. diff --git a/Sources/Articles/Documentation.docc/2024/introduction-to-swift-service-lifecycle/introduction-to-swift-service-lifecycle.md b/Sources/Articles/Documentation.docc/2024/introduction-to-swift-service-lifecycle/introduction-to-swift-service-lifecycle.md index 5e76323..4567d59 100644 --- a/Sources/Articles/Documentation.docc/2024/introduction-to-swift-service-lifecycle/introduction-to-swift-service-lifecycle.md +++ b/Sources/Articles/Documentation.docc/2024/introduction-to-swift-service-lifecycle/introduction-to-swift-service-lifecycle.md @@ -32,13 +32,13 @@ let package = Package( Before the actual implementation, it is important to understand the basic building blocks of the Service Lifecycle library. -Long-running work should be modeled as services that implement the ``Service`` protocol. This protocol requires only one function: a simple asynchronous throwing ``run()`` method. +Long-running work should be modeled as services that implement the ``Service`` protocol. This protocol requires only one function: a simple asynchronous throwing ``Service/run()`` method. The ``ServiceGroup`` is used to orchestrate multiple services. A child task is spawned for each service, and the respective run method is called in the child task. Additionally, signal listeners are set up for the configured signals, triggering a graceful shutdown for each service. -A ``ServiceGroup`` manages the execution of multiple services, handles signal processing, and signals graceful shutdowns to the services. +A ``ServiceGroup`` manages the execution of multiple services, handles signal processing, and signals graceful shutdowns to the services. -While a service is typically a long-running task, it can also be a simple task that returns immediately. By default, if one service returns or throws an error, the entire group is canceled. This behavior can be customized by providing a custom ``successTerminationBehavior`` parameter for the service group, as detailed [here](https://swiftpackageindex.com/swift-server/swift-service-lifecycle/main/documentation/servicelifecycle/how-to-adopt-servicelifecycle-in-applications#Customizing-the-behavior-when-a-service-returns-or-throws). +While a service is typically a long-running task, it can also be a simple task that returns immediately. By default, if one service returns or throws an error, the entire group is canceled. This behavior can be customized by providing a custom ``ServiceGroupConfiguration/ServiceConfiguration/successTerminationBehavior`` parameter for the service group, as detailed [here](https://swiftpackageindex.com/swift-server/swift-service-lifecycle/main/documentation/servicelifecycle/how-to-adopt-servicelifecycle-in-applications#Customizing-the-behavior-when-a-service-returns-or-throws). The following example illustrates a simple service and a service group with customized termination behavior: @@ -88,7 +88,7 @@ struct Application { ## Graceful shutdown and cancellation signals -Rather than setting a custom termination behavior, it is also possible to wait for a cancellation signal and then shut down service resources when that event occurs. The ``gracefulShutdown()`` function is designed to suspend the caller until a graceful shutdown is initiated. +Rather than setting a custom termination behavior, it is also possible to wait for a cancellation signal and then shut down service resources when that event occurs. The ``gracefulShutdown()`` function is designed to suspend the caller until a graceful shutdown is initiated. A graceful shutdown is when an application closes down in an orderly way. Instead of stopping suddenly, it finishes its current tasks and cleans up resources like open files or network connections. This matters because it prevents data loss, avoids corruption, and ensures that the system remains stable and reliable. By shutting down gracefully, applications can stop safely without causing problems for users or other systems they interact with. @@ -101,10 +101,10 @@ struct BasicService: Service { func run() async throws { print("Start - basic service!") - - // 1. + + // 1. try? await gracefulShutdown() - + print("Shutdown - basic service!") } } @@ -155,9 +155,9 @@ import AsyncHTTPClient // 1. actor AsyncHTTPClientService: Service { - + var httpClient: HTTPClient - + init() { self.httpClient = .init( eventLoopGroupProvider: .singleton @@ -166,21 +166,21 @@ actor AsyncHTTPClientService: Service { func run() async throws { try? await gracefulShutdown() - + print("Shutting down http client service...") try await httpClient.shutdown() } } actor PingService: Service { - + let httpClientService: AsyncHTTPClientService - + // 2. init(httpClientService: AsyncHTTPClientService) { self.httpClientService = httpClientService } - + func run() async throws { // 3. try await withGracefulShutdownHandler { @@ -188,7 +188,7 @@ actor PingService: Service { // 4. repeat { print("Ping #\(i) - apple.com") - + let request = HTTPClientRequest( url: "https://apple.com/" ) @@ -243,7 +243,7 @@ struct Application { logger: .init(label: "service-group") ) ) - + try await serviceGroup.run() } } @@ -251,7 +251,7 @@ struct Application { 1. Defines an `AsyncHTTPClientService` actor that initializes an HTTP client and implements the run method to handle graceful shutdown by shutting down the HTTP client. 2. Defines a `PingService` actor that relies on the `AsyncHTTPClientService` for making HTTP requests. -3. Uses ``withGracefulShutdownHandler`` to handle graceful shutdown within the `PingService` actor's run method. +3. Uses ``withGracefulShutdownHandler(isolation:operation:onGracefulShutdown:)`` to handle graceful shutdown within the `PingService` actor's run method. 4. Initiates a loop for sending HTTP requests to apple.com and prints the result. 5. Executes an HTTP request asynchronously using the AsyncHTTPClientService and handles the response. 6. Delays execution for one second before the next iteration of the loop. diff --git a/Sources/Articles/Documentation.docc/2024/logging-for-server-side-swift-apps/logging-for-server-side-swift-apps.md b/Sources/Articles/Documentation.docc/2024/logging-for-server-side-swift-apps/logging-for-server-side-swift-apps.md index cc538bd..e7a3718 100644 --- a/Sources/Articles/Documentation.docc/2024/logging-for-server-side-swift-apps/logging-for-server-side-swift-apps.md +++ b/Sources/Articles/Documentation.docc/2024/logging-for-server-side-swift-apps/logging-for-server-side-swift-apps.md @@ -47,7 +47,7 @@ This article offers a [sample project](https://github.com/swift-on-server/loggin In this tutorial, we are going to create a basic library and an executable target. These simulate a virtual meeting room including participants and their ability to join and leave the room. We're going to use the `MyLibrary` target, which will take advantage of the logging framework. -Inside the `MyApp` target , we'll add more log messages as well. The starter sample project has no logs at all, but relies on the `print` function to display the desired output. We're going to improve the project to provide debug messages for developers through the Logging API. +Inside the `MyApp` target , we'll add more log messages as well. The starter sample project has no logs at all, but relies on the `print` function to display the desired output. We're going to improve the project to provide debug messages for developers through the Logging API. ### The basics @@ -62,8 +62,8 @@ Through extra metadata, developers can get even more details about the circumsta The following snippet demonstrates how to use a basic `Logger` instance: ```swift -// 1. -import Logging +// 1. +import Logging // 2. var logger: Logger = .init(label: "my-app") @@ -98,8 +98,8 @@ The default Logger output contains these sections: - The date of the log generation event. - The log level. -- The custom label of the logger instance. -- All the provided metadata key-value pairs combined together. +- The custom label of the logger instance. +- All the provided metadata key-value pairs combined together. - The name of the application, which triggered the log message. The output format of the logger can be customized and it is also possible to write logs into files. @@ -127,7 +127,7 @@ The `warning`, `error` and `critical` levels shouldn't be overused. Those are al Additional information can be attached to log messages, called Metadata. Metadata can include contextual information such as identifiers, keys, names, and any other relevant information. -Providing extra metadata for the log messages can be helpful for debugging, monitoring, and analyzing the behavior of an application. +Providing extra metadata for the log messages can be helpful for debugging, monitoring, and analyzing the behavior of an application. The Swift Logging library, has built-in metadata support. All the log message functions feature a metadata parameter. A Logger instance can also have associated metadata objects through subscripts. It's also possible to create a custom metadata provider during bootstrapping. @@ -202,10 +202,10 @@ import Foundation import Logging public struct Meeting { - + // 1. public init( - id: UUID, + id: UUID, logger: Logger = .init(label: "meeting-logger") ) { self.id = id @@ -214,7 +214,7 @@ public struct Meeting { self.logger = logger // 2. self.logger[metadataKey: "meeting.id"] = "\(id)" - + // 3. self.logger.trace("meeting room is ready") } @@ -225,11 +225,11 @@ public struct Meeting { 2. Set the current meeting identifier as a metadata value for the `meeting.id` key. 3. Log a trace message to inform others about the status of the meeting room. -A default logger instance as an init parameter helps to avoid interface changes. +A default logger instance as an init parameter helps to avoid interface changes. The default log level is always set to _info_, meaning _trace_ and _debug_ log messages won't be visible by default. -Integrating swift-log won't significantly affect the performance of the project. +Integrating swift-log won't significantly affect the performance of the project. Library consumers can override the logger and provide a custom instance during the instantiation process: @@ -240,7 +240,7 @@ import Foundation @main struct MyApp { - + static func main() async throws { var libLogger = Logger(label: "my-library") libLogger.logLevel = .trace @@ -258,28 +258,28 @@ struct MyApp { } ``` -This is an extremely powerful debugging feature, since users can filter the console output based on the log levels. +This is an extremely powerful debugging feature, since users can filter the console output based on the log levels. ``` 2024-01-24T11:31:19+0100 trace my-library : meeting.id=B6176BC5-39A0-4141-B50B-B86141CCE4C8 [MyLibrary] meeting room is ready ``` -The next step is to add some useful debug & trace information message to the `add`, `remove`, `start` and `end` functions. +The next step is to add some useful debug & trace information message to the `add`, `remove`, `start` and `end` functions. ```swift public mutating func add(_ participant: Participant) { - // 1. + // 1. logger.debug( "trying to add participant", metadata: participant.loggerMetadata ) - + if isInProgress { greet(participant) // 2. logger.trace("meeting is in progress") } - + if participants.contains(participant) { // 3. logger.trace( @@ -325,7 +325,7 @@ public mutating func remove(_ participant: Participant) { } participants.remove(participant) - + logger.debug("participant removed", metadata: [ "participants": "\(participants.count)" ]) @@ -339,7 +339,7 @@ The start function will look very similar: ```swift public mutating func start() throws { logger.debug("trying to start the meeting") - + if isInProgress { logger.trace("already in progress") return @@ -367,7 +367,7 @@ We should also update the end function using the same technique: ```swift public mutating func end() { logger.debug("trying to end the meeting") - + guard isInProgress else { logger.trace("meeting is not in progress yet") return @@ -388,9 +388,9 @@ public mutating func end() { The debug log level is used to get a brief overview of the internal behavior of the library functions. In addition, the trace log level's purpose is to enable tracking of the entire workflow, by providing more detailed information. -Try to run the application using different log levels. +Try to run the application using different log levels. -Set the log level to `.debug`, using the `libLogger.logLevel` property inside the `MyApp.swift` file to hide trace messages. +Set the log level to `.debug`, using the `libLogger.logLevel` property inside the `MyApp.swift` file to hide trace messages. ### Logging in executables @@ -405,7 +405,7 @@ import Foundation @main struct MyApp { - + static func main() { // 1. @@ -414,37 +414,37 @@ struct MyApp { var libLogger = Logger(label: "my-library") libLogger.logLevel = .info - + // 2. appLogger.info("Start a meeting") let bob = Participant(name: "Bob") let john = Participant(name: "John") let kate = Participant(name: "Kate") let mike = Participant(name: "Mike") - + // 3. appLogger.notice("Preparing the meeting") var meeting = Meeting( id: .init(), logger: libLogger ) - + appLogger.notice("Add the participants, except Mike...") - + meeting.add(bob) meeting.add(john) meeting.add(kate) - + // 4. appLogger.warning("Trying to remove Mike from the list, but he is not on the list.") meeting.remove(mike) - + appLogger.info("Start the meeting") if !meeting.hasEnoughParticipants { appLogger.warning("the meeting has not enough participants just yet") } - + do { try meeting.start() } @@ -452,16 +452,16 @@ struct MyApp { // 5. appLogger.error("\(error)") } - + appLogger.notice("Add Mike to the list") meeting.add(mike) - + appLogger.notice("Remove Bob to the list") meeting.remove(bob) - + appLogger.info("End the meeting") meeting.end() - + appLogger.info("Meeting finished") } } @@ -473,7 +473,7 @@ struct MyApp { 4. Warnings can be used to inform users about potential issues or errors. 5. Error log messages can indicate that something has gone wrong. -Try to set different log levels for each Logger instance and run the application. +Try to set different log levels for each Logger instance and run the application. ### Environment-based logs @@ -541,7 +541,7 @@ import Foundation @main struct MyApp { - + static func main() { // setenv("MY_APP_LOG_LEVEL", "trace", 1) let appLogger = Logger.subsystem("my-app", .trace) @@ -562,9 +562,9 @@ Define a custom environment variable based on your identifier: e.g.: `my-library` -> `MY_LIBRARY_LOG_LEVEL` -The ``setenv`` function can be used to define environmental variables from Swift code. +The ``setenv(_:_:_:)`` function can be used to define environmental variables from Swift code. -Important: Avoid utilizing the ``setenv`` function. It is intended solely for demonstration purposes. +Important: Avoid utilizing the ``setenv(_:_:_:)`` function. It is intended solely for demonstration purposes. Run the project from the command line, using the following command to explicitly set environment variables: @@ -580,11 +580,11 @@ swift run MyApp Provide the environmental variables using a single command before the `swift run MyApp` action. -The `export` command can be used to export variables, making them available in the environment of subsequently executed commands. +The `export` command can be used to export variables, making them available in the environment of subsequently executed commands. ## Summary -That's how you can integrate the Swift Logging library into a framework or application. +That's how you can integrate the Swift Logging library into a framework or application. You've learned a lot about logging in this article, including log levels, metadata and custom logging subsystems via environment variables. diff --git a/Sources/Articles/Documentation.docc/2024/realtime-mongodb-updates-with-changestreams-and-websockets/realtime-mongodb-updates-with-changestreams-and-websockets.md b/Sources/Articles/Documentation.docc/2024/realtime-mongodb-updates-with-changestreams-and-websockets/realtime-mongodb-updates-with-changestreams-and-websockets.md index ca042ed..3e0a597 100644 --- a/Sources/Articles/Documentation.docc/2024/realtime-mongodb-updates-with-changestreams-and-websockets/realtime-mongodb-updates-with-changestreams-and-websockets.md +++ b/Sources/Articles/Documentation.docc/2024/realtime-mongodb-updates-with-changestreams-and-websockets/realtime-mongodb-updates-with-changestreams-and-websockets.md @@ -21,7 +21,7 @@ Make sure you have MongoDB running locally before starting. ## The Connection Manager -The ``ConnectionManager`` handles WebSocket connections and MongoDB change notifications: +The `ConnectionManager` handles WebSocket connections and MongoDB change notifications: @Snippet(path: "site/Snippets/realtime-mongodb-app", slice: "connection-manager") @@ -36,7 +36,7 @@ The use of `withRegisteredClient` ensures that the WebSocket connection is prope ### Watching for Changes -Now that the ``ConnectionManager`` is implemented, we can watch for changes in the MongoDB database. For this, we'll tie the ``ConnectionManager`` to the application lifecycle using the ``Service`` protocol. +Now that the `ConnectionManager` is implemented, we can watch for changes in the MongoDB database. For this, we'll tie the `ConnectionManager` to the application lifecycle using the ``Service`` protocol. @Snippet(path: "site/Snippets/realtime-mongodb-app", slice: "watch-changes") @@ -112,10 +112,10 @@ You've learned how to create a real-time feed of MongoDB changes using ChangeStr @Comment { Primary Keyword: mongodb changestream swift - Secondary Keywords: + Secondary Keywords: - swift websocket mongodb - real-time mongodb updates - mongodb swift streaming - swift websocket server - mongodb changestream tutorial -} \ No newline at end of file +} \ No newline at end of file diff --git a/Sources/Articles/Documentation.docc/2024/structured-concurrency-and-shared-state-in-swift/structured-concurrency-and-shared-state-in-swift.md b/Sources/Articles/Documentation.docc/2024/structured-concurrency-and-shared-state-in-swift/structured-concurrency-and-shared-state-in-swift.md index 95e99cc..48123c9 100644 --- a/Sources/Articles/Documentation.docc/2024/structured-concurrency-and-shared-state-in-swift/structured-concurrency-and-shared-state-in-swift.md +++ b/Sources/Articles/Documentation.docc/2024/structured-concurrency-and-shared-state-in-swift/structured-concurrency-and-shared-state-in-swift.md @@ -36,7 +36,7 @@ An actor's isolation is inherited by its properties and methods. Actor Isolation @Snippet(path: "site/Snippets/shared-state", slice: "unownedExecutor") -You can create your own ``SerialExecutor`` for use with your actors. SwiftNIO's EventLoop already has a ``EventLoop/executor [59PH6]`` property that you can use. ``/Dispatch``'s ``DispatchQueue`` can be adapted easily as well. +You can create your own ``SerialExecutor`` for use with your actors. SwiftNIO's EventLoop already has a ``EventLoop/executor [requirement: false]`` property that you can use. ``/Dispatch``'s ``DispatchQueue`` can be adapted easily as well. Since ``Actor/unownedExecutor`` is not a static member of an actor, an actor's static properties can _not be isolated_ by the actor. @@ -162,7 +162,7 @@ We can restructure the `loadImage` function to use a continuation: @Snippet(path: "site/Snippets/shared-state", slice: "efficientImageCache") -**Note:** When creating a continuation, you're starting a new workload that does not (yet) adopt structured concurrency. When this happens, this code is also responsible for ensuring that Task Cancellation is handled propertly. For that, please refer back to ``withTaskCancellationHandler`` earlier in this article. +**Note:** When creating a continuation, you're starting a new workload that does not (yet) adopt structured concurrency. When this happens, this code is also responsible for ensuring that Task Cancellation is handled propertly. For that, please refer back to ``withTaskCancellationHandler(operation:onCancel:isolation:)`` earlier in this article. ## Global Actors @@ -327,7 +327,7 @@ However, heavy workload _can_ be run on a custom executor. Using the pattern sho ### Inheriting Actor Isolation -Starting with Swift 6, a variant of ``AsyncSequence.next()`` is available. +Starting with Swift 6, a variant of ``AsyncIteratorProtocol.next()`` is available. ```swift mutating func next(isolation actor: isolated (any Actor)? = #isolation) async throws -> Element? @@ -337,6 +337,6 @@ The `isolated (any Actor)?` argument allows callees to tell an `async` function In addition, Swift 6' AsyncSequences can specify an `associatedtype Failure: Error`. Using typed throws, you can specify the type of error that the iterator can throw. -```swift +```swift mutating func next(isolation actor: isolated (any Actor)? = #isolation) async throws(MyCustomIteratorError) -> Element? ``` \ No newline at end of file diff --git a/Sources/Articles/Documentation.docc/2024/using-hummingbird-request-context/using-hummingbird-request-context.md b/Sources/Articles/Documentation.docc/2024/using-hummingbird-request-context/using-hummingbird-request-context.md index b8eeba2..65547f7 100644 --- a/Sources/Articles/Documentation.docc/2024/using-hummingbird-request-context/using-hummingbird-request-context.md +++ b/Sources/Articles/Documentation.docc/2024/using-hummingbird-request-context/using-hummingbird-request-context.md @@ -1,6 +1,6 @@ # Using Hummingbird's Request Context -[Hummingbird](https://hummingbird.codes) introduces a new feature that is essential in how it integrates with other libraries: the ``RequestContext``. +[Hummingbird](https://hummingbird.codes) introduces a new feature that is essential in how it integrates with other libraries: the ``RequestContext``. This article explains what they're used for, and how they can help you build statically checked and performant applications. @@ -16,7 +16,7 @@ In addition, a context can specify a ``RequestDecoder`` and ``ResponseEncoder``. While the en- and decoder usually focus on one content type such as JSON, it's also possible for a context to support multiple content types. -Finally, a request specifies ``RequestContext/maxUploadSize``, which has a sensible default value. This specifies the maximum amount of data that Hummingbird may send to a ``RequestDecoder`` before rejecting the request. +Finally, a request specifies ``RequestContext/maxUploadSize [requirement]``, which has a sensible default value. This specifies the maximum amount of data that Hummingbird may send to a ``RequestDecoder`` before rejecting the request. ## Custom Contexts diff --git a/Sources/Articles/Documentation.docc/2024/using-swiftnio-channels/using-swiftnio-channels.md b/Sources/Articles/Documentation.docc/2024/using-swiftnio-channels/using-swiftnio-channels.md index 600a70b..b02efbf 100644 --- a/Sources/Articles/Documentation.docc/2024/using-swiftnio-channels/using-swiftnio-channels.md +++ b/Sources/Articles/Documentation.docc/2024/using-swiftnio-channels/using-swiftnio-channels.md @@ -77,7 +77,7 @@ Let's implement that: This code is an implementation of the server bootstrap that was created in the previous snippet. Let's go over the code step-by-step: 1. Create a task group to manage the lifetime of our server -2. By calling ``NIOAsyncChannel/executeThenClose(_:) [2G196]``, receive a sequence of incoming clients. Once this sequence ends, the end of the function is reached and the server is closed. +2. By calling ``NIOAsyncChannel/executeThenClose(_:) ((NIOAsyncChannelInboundStream, NIOAsyncChannelOutboundWriter) -> Result)``, receive a sequence of incoming clients. Once this sequence ends, the end of the function is reached and the server is closed. 3. A for-loop is used to iterate over each new client, allowing us to handle their traffic. 4. By adding a task to the task group, this Swift code can handle many clients in parallel 5. Call `handleClient` to handle the client. This will be a separate function that will be implemented in a moment. @@ -90,11 +90,11 @@ The server is not able to accept client, but can not yet communicate with them. This code receives messages from a client, and echoes it back. It's functional, efficient and easy to understand. Let's go over the code step-by-step: -1. Call ``NIOAsyncChannel/executeThenClose(_:) [2G196]`` on the client. This allows us to receive a sequence of inbound messages, and a handle to write messages back. +1. Call ``NIOAsyncChannel/executeThenClose(_:) ((NIOAsyncChannelInboundStream, NIOAsyncChannelOutboundWriter) -> Result)`` on the client. This allows us to receive a sequence of inbound messages, and a handle to write messages back. 2. Iterate over each inbound message, using a for-loop. 3. Write the inbound message back to the client. -When the client closes the connection, the sequence of inbound messages will end. This causes the ``NIOAsyncChannel/executeThenClose(_:) [2G196]`` function will return, and the client will be cleaned up. +When the client closes the connection, the sequence of inbound messages will end. This causes the ``NIOAsyncChannel/executeThenClose(_:) ((NIOAsyncChannelInboundStream, NIOAsyncChannelOutboundWriter) -> Result)`` function will return, and the client will be cleaned up. You can try connecting yourself by running the following in your terminal. If a connection is successful, you'll get prompt where you can type a message. When you press enter, the message will be echoed back to you. @@ -103,7 +103,7 @@ nc localhost 2048 ``` If you want, close the connection from our side as well. I've placed a marker where you can close the connection from our side. -Because ``NIOAsyncChannel/executeThenClose(_:) [2G196]`` will close the connection when the function ends, simply place a `return` statement here. +Because ``NIOAsyncChannel/executeThenClose(_:) ((NIOAsyncChannelInboundStream, NIOAsyncChannelOutboundWriter) -> Result)`` will close the connection when the function ends, simply place a `return` statement here. ## Conclusion diff --git a/Sources/Articles/Documentation.docc/2024/websockets-tutorial-using-swift-and-hummingbird/websockets-tutorial-using-swift-and-hummingbird.md b/Sources/Articles/Documentation.docc/2024/websockets-tutorial-using-swift-and-hummingbird/websockets-tutorial-using-swift-and-hummingbird.md index b9221dd..a83a076 100644 --- a/Sources/Articles/Documentation.docc/2024/websockets-tutorial-using-swift-and-hummingbird/websockets-tutorial-using-swift-and-hummingbird.md +++ b/Sources/Articles/Documentation.docc/2024/websockets-tutorial-using-swift-and-hummingbird/websockets-tutorial-using-swift-and-hummingbird.md @@ -30,7 +30,7 @@ The above methods are less efficient on a protocol level and often seem like wor ### Security (WSS) -WebSocket (WS) uses a plain-text TCP connection. A WebSocket connection is created by upgrading an HTTP/1 connection. WebSocket Secure (WSS), which is upgraded from HTTPS, uses TLS to protect the TCP connection. WSS protects against man-in-the-middle attacks but does not offer cross-origin or application-level security. Developers should add URL origin checks and strong authentication. +WebSocket (WS) uses a plain-text TCP connection. A WebSocket connection is created by upgrading an HTTP/1 connection. WebSocket Secure (WSS), which is upgraded from HTTPS, uses TLS to protect the TCP connection. WSS protects against man-in-the-middle attacks but does not offer cross-origin or application-level security. Developers should add URL origin checks and strong authentication. ## Building a real-time WebSocket chat @@ -56,7 +56,7 @@ The [Hummingbird WebSocket chat application](https://github.com/hummingbird-proj └── chat.html ``` -The `App.swift` file contains the standard entry point for a Hummingbird application. The `Application+build.swift` file includes the Hummingbird app configuration using the WebSocket connection manager. The ``ConnectionManager`` is responsible for managing WebSocket connections. The `public/chat.html` file contains client-side JavaScript code to demonstrate a WebSocket connection. +The `App.swift` file contains the standard entry point for a Hummingbird application. The `Application+build.swift` file includes the Hummingbird app configuration using the WebSocket connection manager. The `ConnectionManager` is responsible for managing WebSocket connections. The `public/chat.html` file contains client-side JavaScript code to demonstrate a WebSocket connection. To add WebSocket support to a Hummingbird-based Swift package, simply include the Hummingbird Websocket library as a dependency in your `Package.swift` file.. @@ -91,20 +91,20 @@ let package = Package( ) ``` -The `App.swift` file is the main entry point for a Hummingbird application using the ``ArgumentParser`` library. +The `App.swift` file is the main entry point for a Hummingbird application using the ``ArgumentParser`` library. @Snippet(path: "site/Snippets/websockets", slice: "entrypoint") -1. The ``AppArguments`` protocol defines hostname and port properties. +1. The `AppArguments` protocol defines hostname and port properties. 2. The `HummingbirdArguments` structure is the main entry point, using ``AsyncParsableCommand``, and sets command-line options. 3. The run function builds the Hummingbird application and starts the server as a service. -The code inside the `Application+build.swift` file sets up a Hummingbird application configured for WebSocket communication. It defines a function buildApplication that takes command-line arguments for hostname and port, initializes a logger, and sets up routers with middlewares for logging and file handling. It creates a ``ConnectionManager`` for managing WebSocket connections and configures the WebSocket router to handle chat connections, upgrading the connection if a username is provided: +The code inside the `Application+build.swift` file sets up a Hummingbird application configured for WebSocket communication. It defines a function buildApplication that takes command-line arguments for hostname and port, initializes a logger, and sets up routers with middlewares for logging and file handling. It creates a `ConnectionManager` for managing WebSocket connections and configures the WebSocket router to handle chat connections, upgrading the connection if a username is provided: @Snippet(path: "site/Snippets/websockets", slice: "buildApplication") 1. A ``Router`` instance is created, and middlewares for logging requests and serving files are added to it. -2. A ``ConnectionManager`` instance is created with a logger for managing WebSocket connections. +2. A `ConnectionManager` instance is created with a logger for managing WebSocket connections. 3. A separate ``Router`` instance is created specifically for handling and logging WebSocket requests. 4. A WebSocket route is set up for the `/chat` path, checking for a username query parameter for WebSocket upgrades. 5. On upgrade, the connection manager handles WebSocket users and writes the output stream to the outbound channel. @@ -116,24 +116,24 @@ The application is configured to use HTTP with WebSocket upgrades and includes W 1. An `Application` instance is created with the previously configured routers for both HTTP and WebSocket requests. 2. The `ConnectionManager` is added as a service to the application before returning it. -The ``ConnectionManager`` struct manages WebSocket connections, allowing users to join, send messages, and leave the chat, using an `AsyncStream` for connection handling and Actor for managing outbound connections: +The `ConnectionManager` struct manages WebSocket connections, allowing users to join, send messages, and leave the chat, using an `AsyncStream` for connection handling and Actor for managing outbound connections: @Snippet(path: "site/Snippets/websockets-connection-manager-types") -1. The ``ConnectionManager`` implements the ``Service`` protocol to manage WebSocket connections and ensure graceful shutdown. +1. The `ConnectionManager` implements the ``Service`` protocol to manage WebSocket connections and ensure graceful shutdown. 2. ``OutputStream`` is defined as an ``AsyncChannel`` for sending WebSocket outbound frames. -3. The ``Connection`` struct contains details about each WebSocket connection, including the username, inbound stream, and outbound stream. -4. The ``OutboundConnections`` actor manages a dictionary of outbound writers to broadcast messages to all connections. +3. The `Connection` struct contains details about each WebSocket connection, including the username, inbound stream, and outbound stream. +4. The `OutboundConnections` actor manages a dictionary of outbound writers to broadcast messages to all connections. -The ``addUser`` function creates a ``Connection`` object with a given name and WebSocket streams, yields this connection, and returns a new ``OutputStream``: +The `addUser` function creates a `Connection` object with a given name and WebSocket streams, yields this connection, and returns a new ``OutputStream``: @Snippet(path: "site/Snippets/websockets-connection-manager-add-user") -The ``init(logger:)`` method creates an asynchronous stream for ``Connection`` objects along with a logger, and the ``run`` function asynchronously handles connections by logging their addition, processing inbound messages, sending outbound messages, and logging their removal, with graceful shutdown support: +The `init(logger:)` method creates an asynchronous stream for `Connection` objects along with a logger, and the `run` function asynchronously handles connections by logging their addition, processing inbound messages, sending outbound messages, and logging their removal, with graceful shutdown support: @Snippet(path: "site/Snippets/websockets", slice: "ConnectionManager") -1. The ``run`` function iterates through the `connectionStream` asynchronously to handle incoming connections and messages. +1. The `run` function iterates through the `connectionStream` asynchronously to handle incoming connections and messages. 2. For each connection, a task is added to the group to manage the connection and broadcast the "joined" message. 3. The task listens for incoming messages, processing text messages to broadcast to all connections. 4. Each received text message is broadcast to all outbound connections by calling `send`. @@ -147,7 +147,7 @@ Execute the app and open a web browser to navigate to the [WebSocket Tester Tool ws://localhost:8080/chat?username=Tib ``` -The `username` parameter can be modified as needed. Press the _Connect_ button to add a new user to the chat room. Upon connection, messages can be sent as the user by entering a custom message and clicking the _Send Message_ button. Multiple windows of the WebSocket Tester Tool can be opened to add additional users. +The `username` parameter can be modified as needed. Press the _Connect_ button to add a new user to the chat room. Upon connection, messages can be sent as the user by entering a custom message and clicking the _Send Message_ button. Multiple windows of the WebSocket Tester Tool can be opened to add additional users. ## Conclusion diff --git a/Sources/Articles/Documentation.docc/2024/whats-new-in-hummingbird-2/whats-new-in-hummingbird-2.md b/Sources/Articles/Documentation.docc/2024/whats-new-in-hummingbird-2/whats-new-in-hummingbird-2.md index 5c39ee4..863e216 100644 --- a/Sources/Articles/Documentation.docc/2024/whats-new-in-hummingbird-2/whats-new-in-hummingbird-2.md +++ b/Sources/Articles/Documentation.docc/2024/whats-new-in-hummingbird-2/whats-new-in-hummingbird-2.md @@ -4,11 +4,11 @@ Hummingbird is a lightweight, flexible HTTP server framework written in Swift. T ## Swift concurrency -Hummingbird 2 was built using the modern Swift concurrency APIs. Most of the NIO event loop references are replaced with async / await functions and calls. Structured concurrency is present all around the codebase and the project components, such as ``Request``, are thread safe thanks to the ``Sendable`` conformance. +Hummingbird 2 was built using the modern Swift concurrency APIs. Most of the NIO event loop references are replaced with async / await functions and calls. Structured concurrency is present all around the codebase and the project components, such as ``Request``, are thread safe thanks to the ``Sendable`` conformance. Before the async / await feature adoption, some components had a `HBAsync` prefix. Those are now removed from the v2 library. For example `HBAsyncMiddleware` is now ``MiddlewareProtocol`` or `HBAsyncResponder` is simply called ``HTTPResponder``. -It is worth to mention that HB2 is prepared for Swift 6, the project also compiles against the experimental `StrictConcurrency=complete` feature flag. +It is worth to mention that HB2 is prepared for Swift 6, the project also compiles against the experimental `StrictConcurrency=complete` feature flag. ## Swift service lifecycle v2 @@ -20,11 +20,11 @@ The [HummingbirdCore](https://github.com/hummingbird-project/hummingbird-core) r ## Jobs framework updates -The HummingbirdJobs framework can be used to push work onto a queue, so that is processed outside of a request. Job handlers were restructured to use ``TaskGroup`` and conform to the ``Service`` protocol from the Swift service lifecycle framework. A ``JobQueue`` can also define it's own ``JobID`` type, which helps when integrating with various database/driver implementations. +The HummingbirdJobs framework can be used to push work onto a queue, so that is processed outside of a request. Job handlers were restructured to use ``TaskGroup`` and conform to the ``Service`` protocol from the Swift service lifecycle framework. A ``JobQueue`` can also define it's own ``JobQueueDriver/JobID`` type, which helps when integrating with various database/driver implementations. ## Connection pools -The custom connection pool implementation was removed from the framework. Previously, this component offered connection pooling for PostgreSQL. Since [PostgresNIO](https://github.com/vapor/postgres-nio) has built-in support, there's no need for it anymore inside the HB framework. +The custom connection pool implementation was removed from the framework. Previously, this component offered connection pooling for PostgreSQL. Since [PostgresNIO](https://github.com/vapor/postgres-nio) has built-in support, there's no need for it anymore inside the HB framework. ## HTTP improvements @@ -40,7 +40,7 @@ Here's a little peak into the usage of the new ``RouterBuilder`` object: ## Generic request context -The biggest change to the framework is definitely the introduction of the generic request context. Hummingbird 2.0 separates contextual objects from the `Request` type and users can define custom properties as custom `RequestContext` protocol implementations. +The biggest change to the framework is definitely the introduction of the generic request context. Hummingbird 2.0 separates contextual objects from the `Request` type and users can define custom properties as custom `RequestContext` protocol implementations. The request context is associated with the reworked ``Router``, which a generic class, featuring a _Context_ type. The `BasicRequestContext` type is the default _Context_ implementation for the _Router_. The request decoder and encoder defaults to a JSON-based solution when using the base context. You can provide a custom decoder through a custom router context. @@ -118,7 +118,7 @@ Create the application instance using the `buildApplication` function. 4. Add routes using the custom controller to the `api` route group 5. Build the _Application_ instance using the router and the configuration -Inside the main entrypoint you can start the server by calling the ``Application.runService()`` method: +Inside the main entrypoint you can start the server by calling the ``Application.runService(gracefulShutdownSignals:)`` method: @Snippet(path: "site/Snippets/hummingbird-2", slice: "run") @@ -127,10 +127,10 @@ The route handlers in the `MyController` struct can access of the custom context @Snippet(path: "site/Snippets/hummingbird-2", slice: "controller") 1. Register route handlers using the router group -2. Hummingbird is thread-safe, so every route handler should be marked with `@Sendable` to propagate these thread-safety checks. -3. It is possible to access both the request and the context in each route handler. +2. Hummingbird is thread-safe, so every route handler should be marked with `@Sendable` to propagate these thread-safety checks. +3. It is possible to access both the request and the context in each route handler. Beyond these changes, there are many more in the new Hummingbird 2 release. We're excited for you to try it out! -If have questions about Hummingbird, feel free to join the following [Discord server](https://discord.gg/fkN7FC7QJk). You can also get some inspiration from the official [Hummingbird examples](https://hummingbird.codes/examples). +If have questions about Hummingbird, feel free to join the following [Discord server](https://discord.gg/fkN7FC7QJk). You can also get some inspiration from the official [Hummingbird examples](https://hummingbird.codes/examples). diff --git a/Sources/Articles/Documentation.docc/2024/working-with-udp-in-swiftnio/working-with-udp-in-swiftnio.md b/Sources/Articles/Documentation.docc/2024/working-with-udp-in-swiftnio/working-with-udp-in-swiftnio.md index f641594..3e5abec 100644 --- a/Sources/Articles/Documentation.docc/2024/working-with-udp-in-swiftnio/working-with-udp-in-swiftnio.md +++ b/Sources/Articles/Documentation.docc/2024/working-with-udp-in-swiftnio/working-with-udp-in-swiftnio.md @@ -21,13 +21,13 @@ In order to start accepting packets, bind a UDP socket to a port: 1. First, create a ``DatagramBootstrap`` to open a UDP socket. 2. Next, bind the socket to a port using the `bind` method. 3. Before completing the setup, transform the created ``Channel`` by wrapping it in a ``NIOAsyncChannel`` -4. Unlike TCP, a UDP server does not receive _connections_. It receives an ``AddressedEnvelope`` containing a ``ByteBuffer`` and the sender's ``SocketAddress``. +4. Unlike TCP, a UDP server does not receive _connections_. It receives an ``AddressedEnvelope`` containing a ``ByteBuffer`` and the sender's ``NIOCore/SocketAddress``. Now that you've bound the socket, you can start receiving packets. ### Receiving and Sending Packets -First, start observing the socket using ``NIOAsyncChannel/executeThenClose(_:) [2G196]``. This method will provide an `inbound` and `outbound` argument. +First, start observing the socket using ``NIOAsyncChannel/executeThenClose(_:) ((NIOAsyncChannelInboundStream, NIOAsyncChannelOutboundWriter) -> Result)``. This method will provide an `inbound` and `outbound` argument. Inbound is a stream of incoming packets, whereas outbound is a writer that you can write packets to.