├── FlyingFox
├── Tests
│ ├── Stubs
│ │ ├── fish.json
│ │ └── subdir
│ │ │ └── vinegar.json
│ ├── HTTPMethodTests.swift
│ ├── HTTPClientTests.swift
│ ├── AsyncSocketTests.swift
│ ├── HTTPBodyPatternTests.swift
│ ├── HTTPHeaderTests.swift
│ ├── HTTPRequest+AddressTests.swift
│ ├── HTTPResponseTests.swift
│ ├── URLSession+AsyncTests.swift
│ ├── AsyncSequence+ExtensionsTests.swift
│ ├── HTTPResponse+Mock.swift
│ └── JSON
│ │ └── JSONBodyPatternTests.swift
├── XCTests
│ ├── Stubs
│ │ ├── fish.json
│ │ └── subdir
│ │ │ └── vinegar.json
│ ├── XCTest+Extension.swift
│ ├── HTTPMethodTests.swift
│ ├── SocketAddress+Glibc.swift
│ ├── HTTPResponseTests.swift
│ ├── HTTPClientTests.swift
│ ├── HTTPHeaderTests.swift
│ ├── AsyncSocketTests.swift
│ ├── HTTPBodyPatternTests.swift
│ ├── HTTPRequest+AddressTests.swift
│ ├── HTTPRequestTests.swift
│ ├── URLSession+AsyncTests.swift
│ ├── HTTPRequest+Mock.swift
│ ├── AsyncSequence+ExtensionsTests.swift
│ └── HTTPResponse+Mock.swift
└── Sources
│ ├── HTTPBodyPattern.swift
│ ├── HTTPLogging+OSLog.swift
│ ├── HTTPVersion.swift
│ ├── NonisolatedUnsafe.swift
│ ├── Handlers
│ ├── ClosureHTTPHandler.swift
│ └── DirectoryHTTPHandler.swift
│ ├── SocketAddress+Glibc.swift
│ ├── WebSocket
│ ├── WSMessage.swift
│ ├── AsyncStream+WSFrame.swift
│ └── WSCloseCode.swift
│ ├── JSON
│ ├── HTTPRoute+JSONValue.swift
│ ├── JSONBodyPattern.swift
│ └── JSONPath.swift
│ ├── HTTPRequest+RouteParameter.swift
│ ├── HTTPLogging.swift
│ ├── HTTPRequest+Address.swift
│ ├── HTTPRequest+QueryItem.swift
│ ├── JSONPredicatePattern.swift
│ ├── HTTPRequest+Target.swift
│ ├── HTTPClient.swift
│ ├── HTTPServer+Listening.swift
│ ├── URLSession+Async.swift
│ ├── HTTPServer+Configuration.swift
│ ├── HTTPChunkedEncodedSequence.swift
│ ├── HTTPHandler.swift
│ ├── HTTPHeader.swift
│ └── HTTPMethod.swift
├── CSystemLinux
├── include
│ ├── module.modulemap
│ └── CSystemLinux.h
└── shims.c
├── codecov.yml
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── container-run-tests.sh
├── FlyingSocks
├── Tests
│ ├── Resources
│ │ └── JackOfHeartsRecital.txt
│ ├── FileManager+TemporaryFile.swift
│ ├── Logging+PrintTests.swift
│ ├── Logging+OSLogTests.swift
│ ├── AsyncBufferedEmptySequenceTests.swift
│ ├── MutexTests.swift
│ ├── SocketErrorTests.swift
│ └── AsyncBufferedDataSequenceTests.swift
├── XCTests
│ ├── Resources
│ │ └── JackOfHeartsRecital.txt
│ ├── SocketAddress+Glibc.swift
│ ├── Logging+PrintTests.swift
│ ├── Logging+OSLogTests.swift
│ ├── AsyncBufferedEmptySequenceTests.swift
│ ├── SocketErrorTests.swift
│ ├── MutexXCTests.swift
│ ├── AsyncBufferedFileSequenceTests.swift
│ └── AsyncBufferedDataSequenceTests.swift
└── Sources
│ ├── SwiftSupport.swift
│ ├── Transferring.swift
│ ├── AsyncBufferedSequence+Extensions.swift
│ ├── AsyncChunkedSequence.swift
│ ├── SocketError.swift
│ ├── AsyncBufferedCollection.swift
│ ├── Logging+OSLog.swift
│ ├── AsyncBufferedPrefixSequence.swift
│ ├── AsyncBufferedSequence.swift
│ ├── AsyncBufferedEmptySequence.swift
│ └── Logging.swift
├── .github
└── actions
│ └── test-summary
│ └── action.yml
├── FlyingSocks.podspec.json
├── FlyingFox.podspec.json
├── LICENSE
├── Package.swift
├── .gitignore
└── Package@swift-5.10.swift
/FlyingFox/Tests/Stubs/fish.json:
--------------------------------------------------------------------------------
1 | {"fish": "cakes"}
--------------------------------------------------------------------------------
/FlyingFox/XCTests/Stubs/fish.json:
--------------------------------------------------------------------------------
1 | {"fish": "cakes"}
--------------------------------------------------------------------------------
/FlyingFox/Tests/Stubs/subdir/vinegar.json:
--------------------------------------------------------------------------------
1 | {"type": "malt"}
--------------------------------------------------------------------------------
/FlyingFox/XCTests/Stubs/subdir/vinegar.json:
--------------------------------------------------------------------------------
1 | {"type": "malt"}
--------------------------------------------------------------------------------
/FlyingFox/XCTests/XCTest+Extension.swift:
--------------------------------------------------------------------------------
1 | ../../FlyingSocks/XCTests/XCTest+Extension.swift
--------------------------------------------------------------------------------
/CSystemLinux/include/module.modulemap:
--------------------------------------------------------------------------------
1 | module CSystemLinux {
2 | header "CSystemLinux.h"
3 | export *
4 | }
5 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | ignore:
2 | - FlyingFox/Sources/HTTPDecoder+StandardizePath.swift
3 | - FlyingFox/Tests
4 | - FlyingSocks/Tests
5 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/container-run-tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 |
5 | container run -it \
6 | --rm \
7 | --mount src="$(pwd)",target=/flyingfox,type=bind \
8 | swift:6.2 \
9 | /usr/bin/swift test --package-path /flyingfox
10 |
--------------------------------------------------------------------------------
/FlyingSocks/Tests/Resources/JackOfHeartsRecital.txt:
--------------------------------------------------------------------------------
1 | Two doors down the boys finally made it through the wall
2 | And cleaned out the bank safe, it's said they got off with quite a haul.
3 | In the darkness by the riverbed they waited on the ground
4 | For one more member who had business back in town.
5 | But they couldn't go no further without the Jack of Hearts.
6 |
--------------------------------------------------------------------------------
/FlyingSocks/XCTests/Resources/JackOfHeartsRecital.txt:
--------------------------------------------------------------------------------
1 | Two doors down the boys finally made it through the wall
2 | And cleaned out the bank safe, it's said they got off with quite a haul.
3 | In the darkness by the riverbed they waited on the ground
4 | For one more member who had business back in town.
5 | But they couldn't go no further without the Jack of Hearts.
6 |
--------------------------------------------------------------------------------
/CSystemLinux/shims.c:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of the Swift System open source project
3 | Copyright (c) 2020 Apple Inc. and the Swift System project authors
4 | Licensed under Apache License v2.0 with Runtime Library Exception
5 | See https://swift.org/LICENSE.txt for license information
6 | */
7 |
8 | #ifdef __linux__
9 |
10 | #include
11 |
12 | #endif
13 |
--------------------------------------------------------------------------------
/.github/actions/test-summary/action.yml:
--------------------------------------------------------------------------------
1 | name: 'Test Summary'
2 | description: 'Summarise test results and coverage'
3 | inputs:
4 | junit:
5 | description: 'path to junit xml file'
6 | required: true
7 | coverage:
8 | description: 'path to lcov.json file'
9 | required: false
10 |
11 | runs:
12 | using: "composite"
13 | steps:
14 | - name: 'Summarise'
15 | run: ./.github/actions/test-summary/make-summary.swift ${{ inputs.junit }} ${{ inputs.coverage }} >> $GITHUB_STEP_SUMMARY
16 | shell: bash
17 |
--------------------------------------------------------------------------------
/CSystemLinux/include/CSystemLinux.h:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of the Swift System open source project
3 |
4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors
5 | Licensed under Apache License v2.0 with Runtime Library Exception
6 |
7 | See https://swift.org/LICENSE.txt for license information
8 | */
9 |
10 | #pragma once
11 |
12 | #ifdef __ANDROID__
13 | #include
14 | #include
15 | #endif
16 |
17 | #ifdef __linux__
18 | #include
19 | #include
20 | #endif
21 |
--------------------------------------------------------------------------------
/FlyingSocks.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "FlyingSocks",
3 | "version": "0.26.0",
4 | "summary": "Lightweight, async sockets written in Swift using async/await",
5 | "homepage": "https://github.com/swhitty/FlyingFox",
6 | "authors": "Simon Whitty",
7 | "license": {
8 | "type": "MIT",
9 | "file": "LICENSE"
10 | },
11 | "source": {
12 | "git": "https://github.com/swhitty/FlyingFox.git",
13 | "tag": "0.26.0"
14 | },
15 | "platforms": {
16 | "ios": "13.0",
17 | "osx": "10.15",
18 | "tvos": "13.0",
19 | "watchos": "7.0"
20 | },
21 | "source_files": "FlyingSocks/Sources/**/*.swift",
22 | "pod_target_xcconfig": {
23 | "OTHER_SWIFT_FLAGS": "-package-name FlyingFox"
24 | },
25 | "swift_version": "5.10"
26 | }
27 |
--------------------------------------------------------------------------------
/FlyingFox.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "FlyingFox",
3 | "version": "0.26.0",
4 | "summary": "Lightweight, HTTP server written in Swift using async/await",
5 | "homepage": "https://github.com/swhitty/FlyingFox",
6 | "authors": "Simon Whitty",
7 | "license": {
8 | "type": "MIT",
9 | "file": "LICENSE"
10 | },
11 | "source": {
12 | "git": "https://github.com/swhitty/FlyingFox.git",
13 | "tag": "0.26.0"
14 | },
15 | "platforms": {
16 | "ios": "13.0",
17 | "osx": "10.15",
18 | "tvos": "13.0",
19 | "watchos": "7.0"
20 | },
21 | "source_files": "FlyingFox/Sources/**/*.swift",
22 | "dependencies": {
23 | "FlyingSocks": "~> 0.26.0"
24 | },
25 | "pod_target_xcconfig": {
26 | "OTHER_SWIFT_FLAGS": "-package-name FlyingFox"
27 | },
28 | "swift_version": "5.10"
29 | }
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Simon Whitty
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/FlyingSocks/Sources/SwiftSupport.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftSupport.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 31/03/2023.
6 | // Copyright © 2023 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | #if compiler(<6.0)
33 | #warning("FlyingFox will soon remove support for Swift 5.10")
34 | #endif
35 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPBodyPattern.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPBodyPattern.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 5/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 |
34 | public protocol HTTPBodyPattern: Sendable {
35 | func evaluate(_ body: Data) -> Bool
36 | }
37 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPLogging+OSLog.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPLogging+OSLog.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 19/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import FlyingSocks
33 |
34 | #if canImport(OSLog)
35 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, *)
36 | public typealias OSLogHTTPLogging = FlyingSocks.OSLogLogger
37 | #endif
38 |
--------------------------------------------------------------------------------
/FlyingSocks/Sources/Transferring.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Transferring.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 09/08/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | package struct Transferring: @unchecked Sendable {
33 | package var value: Value
34 |
35 | package init(_ value: Value) {
36 | self.value = value
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/FlyingFox/Tests/HTTPMethodTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPMethodTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 11/07/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import Foundation
34 | import Testing
35 |
36 | struct HTTPMethodTests {
37 |
38 | @Test
39 | func stringValue() {
40 | #expect(
41 | Set([HTTPMethod("fish"), .DELETE, .POST]).stringValue == "POST,DELETE,FISH"
42 | )
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/FlyingSocks/XCTests/SocketAddress+Glibc.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SocketAddress+Glibc.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 15/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | #if canImport(Glibc)
33 | // swift on linux fails to import comformance for these Glibc types 🤷🏻♂️:
34 | import Glibc
35 | import FlyingSocks
36 |
37 | extension sockaddr_in: SocketAddress { }
38 |
39 | extension sockaddr_in6: SocketAddress { }
40 |
41 | extension sockaddr_un: SocketAddress { }
42 |
43 | #endif
44 |
--------------------------------------------------------------------------------
/FlyingFox/XCTests/HTTPMethodTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPMethodTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 11/07/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import Foundation
34 | import XCTest
35 |
36 | final class HTTPMethodTests: XCTestCase {
37 |
38 | func testStringValue() {
39 | XCTAssertEqual(
40 | Set([HTTPMethod("fish"), .DELETE, .POST]).stringValue,
41 | "POST,DELETE,FISH"
42 | )
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPVersion.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPVersion.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 13/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 |
34 | public struct HTTPVersion: Sendable, RawRepresentable, Hashable {
35 | public var rawValue: String
36 |
37 | public init(rawValue: String) {
38 | self.rawValue = rawValue
39 | }
40 |
41 | public init(_ rawValue: String) {
42 | self.init(rawValue: rawValue)
43 | }
44 |
45 | public static let http11 = HTTPVersion("HTTP/1.1")
46 | }
47 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/NonisolatedUnsafe.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UncheckedSendable.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 17/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 |
34 | @propertyWrapper
35 | public struct NonisolatedUnsafe: @unchecked Sendable {
36 | public var wrappedValue: Value
37 |
38 | public init(wrappedValue: Value) {
39 | self.wrappedValue = wrappedValue
40 | }
41 | }
42 |
43 | extension NonisolatedUnsafe: Equatable where Value: Equatable { }
44 |
45 | extension NonisolatedUnsafe: Hashable where Value: Hashable { }
46 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/Handlers/ClosureHTTPHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClosureHTTPHandler.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 14/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 |
33 | public struct ClosureHTTPHandler: HTTPHandler {
34 |
35 | private let closure: @Sendable (HTTPRequest) async throws -> HTTPResponse
36 |
37 | public init(_ closure: @Sendable @escaping (HTTPRequest) async throws -> HTTPResponse) {
38 | self.closure = closure
39 | }
40 |
41 | public func handleRequest(_ request: HTTPRequest) async throws -> HTTPResponse {
42 | try await closure(request)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:6.0
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "FlyingFox",
7 | platforms: [
8 | .macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v8)
9 | ],
10 | products: [
11 | .library(
12 | name: "FlyingFox",
13 | targets: ["FlyingFox"]
14 | ),
15 | .library(
16 | name: "FlyingSocks",
17 | targets: ["FlyingSocks"]
18 | )
19 | ],
20 | targets: [
21 | .target(
22 | name: "FlyingFox",
23 | dependencies: ["FlyingSocks"],
24 | path: "FlyingFox/Sources",
25 | swiftSettings: .upcomingFeatures
26 | ),
27 | .target(
28 | name: "FlyingSocks",
29 | dependencies: [.target(name: "CSystemLinux", condition: .when(platforms: [.linux, .android]))],
30 | path: "FlyingSocks/Sources",
31 | swiftSettings: .upcomingFeatures
32 | ),
33 | .target(
34 | name: "CSystemLinux",
35 | path: "CSystemLinux"
36 | ),
37 | .testTarget(
38 | name: "FlyingFoxTests",
39 | dependencies: ["FlyingFox"],
40 | path: "FlyingFox/Tests",
41 | resources: [
42 | .copy("Stubs")
43 | ],
44 | swiftSettings: .upcomingFeatures
45 | ),
46 | .testTarget(
47 | name: "FlyingSocksTests",
48 | dependencies: ["FlyingSocks"],
49 | path: "FlyingSocks/Tests",
50 | resources: [
51 | .copy("Resources")
52 | ],
53 | swiftSettings: .upcomingFeatures
54 | )
55 | ]
56 | )
57 |
58 | extension Array where Element == SwiftSetting {
59 |
60 | static var upcomingFeatures: [SwiftSetting] {
61 | [
62 | .enableUpcomingFeature("ExistentialAny"),
63 | .swiftLanguageMode(.v6)
64 | ]
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/FlyingSocks/Sources/AsyncBufferedSequence+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncBufferedSequence+Extensions.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 06/08/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 |
34 | package extension AsyncBufferedSequence where Element == UInt8 {
35 |
36 | func getAllData(suggestedBuffer count: Int = 4096) async throws -> Data {
37 | var data = Data()
38 | var iterator = makeAsyncIterator()
39 | while let buffer = try await iterator.nextBuffer(suggested: count) {
40 | data.append(contentsOf: buffer)
41 | }
42 | return data
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/FlyingSocks/Tests/FileManager+TemporaryFile.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2019 The TensorFlow Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | @testable import FlyingSocks
16 | import Foundation
17 |
18 |
19 | extension FileManager {
20 |
21 | #if canImport(WinSDK)
22 | func makeTemporaryDirectory(template: String = "FlyingSocks.XXXXXX") throws -> URL {
23 | let suffix = UUID().uuidString.replacingOccurrences(of: "-", with: "").prefix(6)
24 | let url = temporaryDirectory.appendingPathComponent("FlyingSocks.\(suffix)", isDirectory: true)
25 | try createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
26 | return url
27 | }
28 | #else
29 | func makeTemporaryDirectory(template: String = "FlyingSocks.XXXXXX") throws -> URL {
30 | let base = temporaryDirectory.path
31 | let needsSlash = base.hasSuffix("/") ? "" : "/"
32 | var tmpl = Array((base + needsSlash + template).utf8CString)
33 |
34 | let url = tmpl.withUnsafeMutableBufferPointer { buf -> URL? in
35 | guard let p = buf.baseAddress, mkdtemp(p) != nil else { return nil }
36 | let path = String(cString: p)
37 | return URL(fileURLWithPath: path, isDirectory: true)
38 | }
39 |
40 | guard let url = url else {
41 | throw SocketError.makeFailed("makeTemporaryDirectory()")
42 | }
43 | return url
44 | }
45 | #endif
46 | }
47 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/SocketAddress+Glibc.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SocketAddress+Glibc.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 15/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | #if canImport(Glibc)
33 | // swift on linux fails to import comformance for these Glibc types 🤷🏻♂️:
34 | import Glibc
35 | import FlyingSocks
36 |
37 | extension sockaddr_in: SocketAddress {
38 | public static let family = sa_family_t(AF_INET)
39 | }
40 |
41 | extension sockaddr_in6: SocketAddress {
42 | public static let family = sa_family_t(AF_INET6)
43 | }
44 |
45 | extension sockaddr_un: SocketAddress {
46 | public static let family = sa_family_t(AF_UNIX)
47 | }
48 |
49 | #endif
50 |
--------------------------------------------------------------------------------
/FlyingFox/XCTests/SocketAddress+Glibc.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SocketAddress+Glibc.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 15/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | #if canImport(Glibc)
33 | // swift 5 on linux fails to import comformance for these Glibc types 🤷🏻♂️:
34 | import Glibc
35 | import FlyingSocks
36 |
37 | extension sockaddr_in: SocketAddress {
38 | public static let family = sa_family_t(AF_INET)
39 | }
40 |
41 | extension sockaddr_in6: SocketAddress {
42 | public static let family = sa_family_t(AF_INET6)
43 | }
44 |
45 | extension sockaddr_un: SocketAddress {
46 | public static let family = sa_family_t(AF_UNIX)
47 | }
48 |
49 | #endif
50 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/WebSocket/WSMessage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WSMessage.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 19/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 |
34 | public enum WSMessage: @unchecked Sendable, Hashable {
35 | case text(String)
36 | case data(Data)
37 | case close(WSCloseCode = .normalClosure)
38 | }
39 |
40 | public protocol WSMessageHandler: Sendable {
41 | func makeMessages(for client: AsyncStream) async throws -> AsyncStream
42 | }
43 |
44 | public struct EchoWSMessageHandler: WSMessageHandler {
45 |
46 | public init() {}
47 |
48 | public func makeMessages(for client: AsyncStream) async throws -> AsyncStream {
49 | client
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/FlyingFox/XCTests/HTTPResponseTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPRequestTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 02/04/2023.
6 | // Copyright © 2023 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import XCTest
34 |
35 | final class HTTPRequestTests: XCTestCase {
36 |
37 | func testRequestBodyData_CanBeChanged() async {
38 | // when
39 | var request = HTTPRequest.make(body: Data([0x01, 0x02]))
40 |
41 | // then
42 | await AsyncAssertEqual(
43 | try await request.bodyData,
44 | Data([0x01, 0x02])
45 | )
46 |
47 | // when
48 | request.setBodyData(Data([0x05, 0x06]))
49 |
50 | // then
51 | await AsyncAssertEqual(
52 | try await request.bodyData,
53 | Data([0x05, 0x06])
54 | )
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/JSON/HTTPRoute+JSONValue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPRoute+JSONValue.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 15/08/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 |
34 | public extension HTTPRoute {
35 |
36 | /// Create a route to a request with a JSON body matching the supplued predicate.
37 | /// - Parameters:
38 | /// - string: String representing the method, path and query parameters of the route `POST /fish`
39 | /// - headers: Headers to evaluate and match
40 | /// - predicate: Predicate to evaluate body of the request via a `JSONValue`
41 | init(
42 | _ string: String,
43 | headers: [HTTPHeader: String] = [:],
44 | jsonBody predicate: @escaping @Sendable (JSONValue) throws -> Bool
45 | ) {
46 | self.init(string, headers: headers, body: .jsonValue(where: predicate))
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/JSON/JSONBodyPattern.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JSONValuePattern.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 15/08/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 |
34 | public extension HTTPBodyPattern where Self == JSONBodyPattern {
35 |
36 | static func jsonValue(where predicate: @escaping @Sendable (JSONValue) throws -> Bool) -> JSONBodyPattern {
37 | JSONBodyPattern(predicate)
38 | }
39 | }
40 |
41 | public struct JSONBodyPattern: HTTPBodyPattern {
42 |
43 | private let predicate: @Sendable (JSONValue) throws -> Bool
44 |
45 | public init(_ predicate: @escaping @Sendable (JSONValue) throws -> Bool) {
46 | self.predicate = predicate
47 | }
48 |
49 | public func evaluate(_ body: Data) -> Bool {
50 | do {
51 | return try predicate(JSONValue(data: body))
52 | } catch {
53 | return false
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/FlyingFox/Tests/HTTPClientTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPClientTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 8/06/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | #if canImport(Darwin)
33 | @_spi(Private) import struct FlyingFox._HTTPClient
34 | @testable import FlyingFox
35 | @testable import FlyingSocks
36 | import Foundation
37 | import Testing
38 |
39 | struct HTTPClientTests {
40 |
41 | @Test
42 | func client_sends_request() async throws {
43 | // given
44 | let server = HTTPServer(address: .loopback(port: 0))
45 | let task = Task { try await server.run() }
46 | defer { task.cancel() }
47 | let client = _HTTPClient()
48 |
49 | // when
50 | let port = try await server.waitForListeningPort()
51 | let response = try await client.sendHTTPRequest(HTTPRequest.make(), to: .loopback(port: port))
52 |
53 | // then
54 | #expect(response.statusCode == .notFound)
55 | }
56 | }
57 | #endif
58 |
--------------------------------------------------------------------------------
/FlyingFox/XCTests/HTTPClientTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPClientTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 8/06/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @_spi(Private) import struct FlyingFox._HTTPClient
33 | @testable import FlyingFox
34 | @testable import FlyingSocks
35 | import XCTest
36 | import Foundation
37 |
38 | final class HTTPClientTests: XCTestCase {
39 |
40 | #if canImport(Darwin)
41 | func testClient() async throws {
42 | // given
43 | let server = HTTPServer(address: .loopback(port: 0))
44 | let task = Task { try await server.run() }
45 | defer { task.cancel() }
46 | let client = _HTTPClient()
47 |
48 | // when
49 | let port = try await server.waitForListeningPort()
50 | let response = try await client.sendHTTPRequest(HTTPRequest.make(), to: .loopback(port: port))
51 |
52 | // then
53 | XCTAssertEqual(response.statusCode, .notFound)
54 | }
55 | #endif
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/FlyingSocks/Tests/Logging+PrintTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logging+PrintTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 23/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingSocks
33 | import Foundation
34 | import Testing
35 |
36 | struct LoggingTests {
37 |
38 | @Test
39 | func printLogger_SetsCategory() {
40 | let logger = PrintLogger.print(category: "Fish")
41 |
42 | #expect(
43 | logger.category == "Fish"
44 | )
45 | }
46 |
47 | @Test
48 | func printLogger_output() {
49 | // NOTE: For now this test is only used to verify the output by manual confirmation
50 | // until Swift.print can be unit-tested or we are able to inject a mock.
51 | let logger = PrintLogger.print(category: "Fox")
52 |
53 | logger.logDebug("alpha")
54 | logger.logInfo("bravo")
55 | logger.logWarning("charlie")
56 | logger.logError("delta")
57 | logger.logCritical("echo")
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/FlyingSocks/XCTests/Logging+PrintTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logging+PrintTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 23/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingSocks
33 | import Foundation
34 | import XCTest
35 |
36 | final class LoggingTests: XCTestCase {
37 |
38 | func testPrintLogger_SetsCategory() {
39 | let logger = PrintLogger.print(category: "Fish")
40 |
41 | XCTAssertEqual(
42 | logger.category,
43 | "Fish"
44 | )
45 | }
46 |
47 | func testPrintLogger_output() {
48 | // NOTE: For now this test is only used to verify the output by manual confirmation
49 | // until Swift.print can be unit-tested or we are able to inject a mock.
50 | let logger = PrintLogger.print(category: "Fox")
51 |
52 | logger.logDebug("alpha")
53 | logger.logInfo("bravo")
54 | logger.logWarning("charlie")
55 | logger.logError("delta")
56 | logger.logCritical("echo")
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/FlyingSocks/Tests/Logging+OSLogTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logging+OSLogTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Andre Jacobs on 06/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 | #if canImport(OSLog)
32 | @testable import FlyingSocks
33 | import Foundation
34 | import OSLog
35 | import Testing
36 |
37 | struct LoggingOSLogTests {
38 |
39 | @Test
40 | func info() {
41 | guard #available(macOS 11.0, iOS 14.0, tvOS 14.0, *) else { return }
42 | // NOTE: For now this test is only used to verify the output by manual confirmation (e.g. Console.app or log tool)
43 | // Run log tool in the terminal first and then run this unit-test:
44 | // log stream --level debug --predicate 'category == "FlyingFox"'
45 | let logger = OSLogLogger.oslog(category: "Fox")
46 |
47 | logger.logDebug("alpha")
48 | logger.logInfo("bravo")
49 | logger.logWarning("charlie")
50 | logger.logError("delta")
51 | logger.logCritical("echo")
52 | }
53 | }
54 |
55 | #endif // canImport(OSLog)
56 |
--------------------------------------------------------------------------------
/FlyingSocks/XCTests/Logging+OSLogTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logging+OSLogTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Andre Jacobs on 06/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 | #if canImport(OSLog)
32 | @testable import FlyingSocks
33 | import Foundation
34 | import OSLog
35 | import XCTest
36 |
37 | final class LoggingOSLogTests: XCTestCase {
38 |
39 | func testInfo() {
40 | guard #available(macOS 11.0, iOS 14.0, tvOS 14.0, *) else { return }
41 | // NOTE: For now this test is only used to verify the output by manual confirmation (e.g. Console.app or log tool)
42 | // Run log tool in the terminal first and then run this unit-test:
43 | // log stream --level debug --predicate 'category == "FlyingFox"'
44 | let logger = OSLogLogger.oslog(category: "Fox")
45 |
46 | logger.logDebug("alpha")
47 | logger.logInfo("bravo")
48 | logger.logWarning("charlie")
49 | logger.logError("delta")
50 | logger.logCritical("echo")
51 | }
52 | }
53 |
54 | #endif // canImport(OSLog)
55 |
--------------------------------------------------------------------------------
/FlyingFox/XCTests/HTTPHeaderTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPHeaderTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 11/07/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import Foundation
34 | import XCTest
35 |
36 | final class HTTPHeaderTests: XCTestCase {
37 |
38 | func testStringValue() {
39 | // given
40 | var headers = HTTPHeaders()
41 | headers[.transferEncoding] = "Identity"
42 |
43 | XCTAssertEqual(
44 | headers[.transferEncoding],
45 | "Identity"
46 | )
47 |
48 | XCTAssertEqual(
49 | headers.values(for: .transferEncoding),
50 | ["Identity"]
51 | )
52 |
53 | XCTAssertEqual(
54 | headers.values(for: .contentType),
55 | []
56 | )
57 |
58 | headers.addValue("chunked", for: .transferEncoding)
59 |
60 | XCTAssertEqual(
61 | headers[.transferEncoding],
62 | "Identity, chunked"
63 | )
64 |
65 | XCTAssertEqual(
66 | headers.values(for: .transferEncoding),
67 | ["Identity", "chunked"]
68 | )
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
--------------------------------------------------------------------------------
/FlyingFox/Tests/AsyncSocketTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncSocketTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 22/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | @testable import FlyingSocks
34 | import Foundation
35 |
36 | extension AsyncSocket {
37 |
38 | static func make() async throws -> AsyncSocket {
39 | try await make(pool: .client)
40 | }
41 |
42 | static func make(pool: some AsyncSocketPool) throws -> AsyncSocket {
43 | let socket = try Socket(domain: AF_UNIX, type: .stream)
44 | return try AsyncSocket(socket: socket, pool: pool)
45 | }
46 |
47 | static func makePair() async throws -> (AsyncSocket, AsyncSocket) {
48 | try await makePair(pool: .client)
49 | }
50 |
51 | func writeString(_ string: String) async throws {
52 | try await write(string.data(using: .utf8)!)
53 | }
54 |
55 | func readString(length: Int) async throws -> String {
56 | let bytes = try await read(bytes: length)
57 | guard let string = String(data: Data(bytes), encoding: .utf8) else {
58 | throw SocketError.makeFailed("Read")
59 | }
60 | return string
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/FlyingFox/XCTests/AsyncSocketTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncSocketTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 22/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | @testable import FlyingSocks
34 | import Foundation
35 |
36 | extension AsyncSocket {
37 |
38 | static func make() async throws -> AsyncSocket {
39 | try await make(pool: .client)
40 | }
41 |
42 | static func make(pool: some AsyncSocketPool) throws -> AsyncSocket {
43 | let socket = try Socket(domain: AF_UNIX, type: Socket.stream)
44 | return try AsyncSocket(socket: socket, pool: pool)
45 | }
46 |
47 | static func makePair() async throws -> (AsyncSocket, AsyncSocket) {
48 | try await makePair(pool: .client)
49 | }
50 |
51 | func writeString(_ string: String) async throws {
52 | try await write(string.data(using: .utf8)!)
53 | }
54 |
55 | func readString(length: Int) async throws -> String {
56 | let bytes = try await read(bytes: length)
57 | guard let string = String(data: Data(bytes), encoding: .utf8) else {
58 | throw SocketError.makeFailed("Read")
59 | }
60 | return string
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPRequest+RouteParameter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPRequest+RouteParameter.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 13/07/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 |
33 | import Foundation
34 |
35 | public extension HTTPRequest {
36 |
37 | struct RouteParameter: Sendable, Hashable {
38 | public var name: String
39 | public var value: String
40 |
41 | public init(name: String, value: String) {
42 | self.name = name
43 | self.value = value
44 | }
45 | }
46 |
47 | /// Values extracted from the matched route and request
48 | var routeParameters: [RouteParameter] { Self.matchedRoute?.extractParameters(from: self) ?? [] }
49 | }
50 |
51 | public extension Array where Element == HTTPRequest.RouteParameter {
52 |
53 | subscript(_ name: String) -> String? {
54 | get {
55 | first { $0.name == name }?.value
56 | }
57 | }
58 |
59 | subscript(_ name: String, of type: T.Type = T.self) -> T? {
60 | guard let text = first(where: { $0.name == name })?.value,
61 | let value = try? T(parameter: text) else {
62 | return nil
63 | }
64 | return value
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPLogging.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPLogging.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 19/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import FlyingSocks
33 |
34 | @available(*, unavailable, renamed: "FlyingSocks.Logging")
35 | public typealias HTTPLogging = FlyingSocks.Logging
36 |
37 | @available(*, unavailable, renamed: "FlyingSocks.PrintLogger")
38 | public typealias PrintHTTPLogger = FlyingSocks.PrintLogger
39 |
40 | public extension Logging where Self == PrintLogger {
41 |
42 | static func print(category: String = "FlyingFox") -> Self {
43 | return PrintLogger(category: category)
44 | }
45 | }
46 |
47 | extension HTTPServer {
48 |
49 | public static func defaultLogger(category: String = "FlyingFox") -> any Logging {
50 | defaultLogger(category: category, forceFallback: false)
51 | }
52 |
53 | static func defaultLogger(category: String = "FlyingFox", forceFallback: Bool) -> any Logging {
54 | guard !forceFallback, #available(macOS 11.0, iOS 14.0, tvOS 14.0, *) else {
55 | return .print(category: category)
56 | }
57 | #if canImport(OSLog)
58 | return .oslog(category: category)
59 | #else
60 | return .print(category: category)
61 | #endif
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/FlyingSocks/Sources/AsyncChunkedSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncChunkedSequence.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 20/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | /// AsyncSequence that is able to also receive elements in chunks, instead of just one-at-a-time.
33 | @available(*, unavailable, renamed: "AsyncBufferedSequence")
34 | public protocol AsyncChunkedSequence: AsyncSequence where AsyncIterator: AsyncChunkedIteratorProtocol {
35 |
36 | }
37 |
38 | @available(*, unavailable, renamed: "AsyncBufferedIteratorProtocol")
39 | public protocol AsyncChunkedIteratorProtocol: AsyncIteratorProtocol {
40 |
41 | /// Retrieves n elements from sequence in a single array.
42 | /// - Returns: Array with the number of elements that was requested. Or Nil.
43 | mutating func nextChunk(count: Int) async throws -> [Element]?
44 | }
45 |
46 | @available(*, unavailable, renamed: "AsyncBufferedSequence")
47 | public protocol ChunkedAsyncSequence: AsyncSequence where AsyncIterator: ChunkedAsyncIteratorProtocol {
48 |
49 | }
50 |
51 | @available(*, unavailable, renamed: "AsyncBufferedIteratorProtocol")
52 | public protocol ChunkedAsyncIteratorProtocol: AsyncIteratorProtocol {
53 | mutating func nextChunk(count: Int) async throws -> [Element]?
54 | }
55 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPRequest+Address.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPRequest+Adress.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 03/08/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 |
33 | import Foundation
34 | import FlyingSocks
35 |
36 | public extension HTTPRequest {
37 |
38 | enum Address: Sendable, Hashable {
39 | case ip4(String, port: UInt16)
40 | case ip6(String, port: UInt16)
41 | case unix(String)
42 | }
43 |
44 | var remoteIPAddress: String? {
45 | if let forwarded = headers[.xForwardedFor]?.split(separator: ",").first {
46 | return String(forwarded)
47 | }
48 | switch remoteAddress {
49 | case let .ip4(ip, port: _),
50 | let .ip6(ip, port: _):
51 | return ip
52 | case .unix, .none:
53 | return nil
54 | }
55 | }
56 | }
57 |
58 | public extension HTTPRequest.Address {
59 |
60 | static func make(from address: Socket.Address) -> Self {
61 | switch address {
62 | case let .ip4(ip, port: port):
63 | return .ip4(ip, port: port)
64 | case let .ip6(ip, port: port):
65 | return .ip6(ip, port: port)
66 | case let .unix(path):
67 | return .unix(path)
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/FlyingFox/Tests/HTTPBodyPatternTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPBodyPatternTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 13/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import Foundation
34 | import Testing
35 |
36 | struct HTTPBodyPatternTests {
37 |
38 | #if canImport(Darwin)
39 | @Test
40 | func bodyArray_MatchesRoute() {
41 | let pattern = JSONPredicatePattern.json(where: "animals[1].name == 'fish'")
42 |
43 | #expect(
44 | pattern.evaluate(
45 | #"""
46 | {
47 | "animals": [
48 | {"name": "dog"},
49 | {"name": "fish"}
50 | ]
51 | }
52 | """#.data(using: .utf8)!
53 | )
54 | )
55 |
56 | #expect(
57 | !pattern.evaluate(
58 | #"""
59 | {
60 | "animals": [
61 | {"name": "fish"},
62 | {"name": "dog"}
63 | ]
64 | }
65 | """#.data(using: .utf8)!
66 | )
67 | )
68 |
69 | #expect(
70 | !pattern.evaluate(
71 | Data([0x01, 0x02])
72 | )
73 | )
74 | }
75 | #endif
76 | }
77 |
--------------------------------------------------------------------------------
/FlyingFox/XCTests/HTTPBodyPatternTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPBodyPatternTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 13/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import XCTest
34 |
35 | final class HTTPBodyPatternTests: XCTestCase {
36 |
37 | #if canImport(Darwin)
38 | func testBodyArray_MatchesRoute() {
39 | let pattern = JSONPredicatePattern.json(where: "animals[1].name == 'fish'")
40 |
41 | XCTAssertTrue(
42 | pattern.evaluate(
43 | #"""
44 | {
45 | "animals": [
46 | {"name": "dog"},
47 | {"name": "fish"}
48 | ]
49 | }
50 | """#.data(using: .utf8)!
51 | )
52 | )
53 |
54 | XCTAssertFalse(
55 | pattern.evaluate(
56 | #"""
57 | {
58 | "animals": [
59 | {"name": "fish"},
60 | {"name": "dog"}
61 | ]
62 | }
63 | """#.data(using: .utf8)!
64 | )
65 | )
66 |
67 | XCTAssertFalse(
68 | pattern.evaluate(
69 | Data([0x01, 0x02])
70 | )
71 | )
72 | }
73 | #endif
74 | }
75 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPRequest+QueryItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPRequest+QueryItem.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 6/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 |
33 | import Foundation
34 |
35 | public extension HTTPRequest {
36 |
37 | struct QueryItem: Sendable, Equatable {
38 | public var name: String
39 | public var value: String
40 |
41 | public init(name: String, value: String) {
42 | self.name = name
43 | self.value = value
44 | }
45 | }
46 |
47 | }
48 |
49 | public extension Array where Element == HTTPRequest.QueryItem {
50 |
51 | subscript(_ name: String) -> String? {
52 | get {
53 | first { $0.name == name }?.value
54 | }
55 | set {
56 | let item = newValue.map {
57 | HTTPRequest.QueryItem(name: name, value: $0)
58 | }
59 | guard let idx = firstIndex(where: { $0.name == name }) else {
60 | if let item = item {
61 | append(item)
62 | }
63 | return
64 | }
65 |
66 | if let item = item {
67 | self[idx] = item
68 | } else {
69 | remove(at: idx)
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/FlyingSocks/Sources/SocketError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SocketError.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 19/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 | #if canImport(Android)
34 | import Android
35 | #endif
36 |
37 | public enum SocketError: LocalizedError, Equatable {
38 | case failed(type: String, errno: Int32, message: String)
39 | case blocked
40 | case disconnected
41 | case unsupportedAddress
42 | case timeout(message: String)
43 |
44 | public var errorDescription: String? {
45 | switch self {
46 | case .failed(let type, let errno, let message):
47 | return "SocketError. \(type)(\(errno)): \(message)"
48 | case .blocked:
49 | return "SocketError. Blocked"
50 | case .disconnected:
51 | return "SocketError. Disconnected"
52 | case .unsupportedAddress:
53 | return "SocketError. UnsupportedAddress"
54 | case .timeout(message: let message):
55 | return "SocketError. Timeout: \(message)"
56 | }
57 | }
58 |
59 | static func makeFailed(_ type: StaticString) -> Self {
60 | .failed(type: String(describing: type),
61 | errno: errno,
62 | message: String(cString: strerror(errno)))
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/JSONPredicatePattern.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPBodyPattern.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 5/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | #if canImport(Darwin)
33 |
34 | import Foundation
35 |
36 | @available(*, deprecated, message: "Use JSONBodyPattern")
37 | public struct JSONPredicatePattern: HTTPBodyPattern {
38 |
39 | #if compiler(>=6)
40 | nonisolated(unsafe) private var predicate: NSPredicate
41 | #else
42 | @NonisolatedUnsafe private var predicate: NSPredicate
43 | #endif
44 |
45 | public init(_ predicate: NSPredicate) {
46 | self.predicate = predicate
47 | }
48 |
49 | public func evaluate(_ body: Data) -> Bool {
50 | do {
51 | let object = try JSONSerialization.jsonObject(with: body, options: [])
52 | return predicate.evaluate(with: object)
53 | } catch {
54 | return false
55 | }
56 | }
57 | }
58 |
59 | @available(*, deprecated, message: "Use JSONBodyPattern")
60 | public extension HTTPBodyPattern where Self == JSONPredicatePattern {
61 |
62 | @available(*, deprecated, message: "Use JSONBodyPattern")
63 | static func json(where condition: String) -> JSONPredicatePattern {
64 | JSONPredicatePattern(NSPredicate(format: condition))
65 | }
66 | }
67 |
68 | #endif
69 |
--------------------------------------------------------------------------------
/FlyingFox/Tests/HTTPHeaderTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPHeadersTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 11/07/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import Foundation
34 | import Testing
35 |
36 | struct HTTPHeadersTests {
37 |
38 | @Test
39 | func stringValue() {
40 | // given
41 | var headers = HTTPHeaders()
42 | headers[.transferEncoding] = "Identity"
43 |
44 | #expect(
45 | headers[.transferEncoding] == "Identity"
46 | )
47 |
48 | #expect(
49 | headers.values(for: .transferEncoding) == ["Identity"]
50 | )
51 |
52 | #expect(
53 | headers.values(for: .contentType) == []
54 | )
55 |
56 | headers.addValue("chunked", for: .transferEncoding)
57 |
58 | #expect(
59 | headers[.transferEncoding] == "Identity, chunked"
60 | )
61 |
62 | #expect(
63 | headers.values(for: .transferEncoding) == ["Identity", "chunked"]
64 | )
65 | }
66 |
67 | @Test
68 | func values() {
69 | var headers = HTTPHeaders()
70 | headers.addValue("Fish", for: .setCookie)
71 | headers.addValue("Chips", for: .setCookie)
72 |
73 | #expect(headers[.setCookie] == "Fish")
74 | #expect(headers.values(for: .setCookie) == ["Fish", "Chips"])
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPRequest+Target.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPRequest+Target.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 08/11/2025.
6 | // Copyright © 2025 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 |
34 | public extension HTTPRequest {
35 |
36 | // RFC9112: e.g. /a%2Fb?q=1
37 | struct Target: Sendable, Equatable {
38 |
39 | // raw percent encoded path e.g. /fish%20chips
40 | private var _path: String
41 |
42 | // raw percent encoded query string e.g. q=fish%26chips&qty=15
43 | private var _query: String
44 |
45 | public init(path: String, query: String) {
46 | self._path = path
47 | self._query = query
48 | }
49 |
50 | public func path(percentEncoded: Bool = true) -> String {
51 | guard percentEncoded else {
52 | return _path.removingPercentEncoding ?? _path
53 | }
54 | return _path
55 | }
56 |
57 | public func query(percentEncoded: Bool = true) -> String {
58 | guard percentEncoded else {
59 | return _query.removingPercentEncoding ?? _query
60 | }
61 | return _query
62 | }
63 |
64 | public var rawValue: String {
65 | guard !_query.isEmpty else {
66 | return _path
67 | }
68 | return "\(_path)?\(_query)"
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPClient.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 8/06/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import FlyingSocks
33 |
34 | @_spi(Private)
35 | public struct _HTTPClient {
36 |
37 | public init() { }
38 |
39 | public func sendHTTPRequest(_ request: HTTPRequest, to address: some SocketAddress) async throws -> HTTPResponse {
40 | let socket = try await AsyncSocket.connected(to: address)
41 | try await socket.writeRequest(request)
42 | let response = try await socket.readResponse()
43 | // if streaming very large responses then you shouldn't close here
44 | // maybe better to close in deinit instead
45 | try? socket.close()
46 | return response
47 | }
48 | }
49 |
50 | package extension AsyncSocket {
51 | func writeRequest(_ request: HTTPRequest) async throws {
52 | try await write(HTTPEncoder.encodeRequest(request))
53 | }
54 |
55 | func readResponse() async throws -> HTTPResponse {
56 | try await HTTPDecoder(sharedRequestBufferSize: 4096, sharedRequestReplaySize: 102_400).decodeResponse(from: bytes)
57 | }
58 |
59 | func writeFrame(_ frame: WSFrame) async throws {
60 | try await write(WSFrameEncoder.encodeFrame(frame))
61 | }
62 |
63 | func readFrame() async throws -> WSFrame {
64 | try await WSFrameEncoder.decodeFrame(from: bytes)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Package@swift-5.10.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.10
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "FlyingFox",
7 | platforms: [
8 | .macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v8)
9 | ],
10 | products: [
11 | .library(
12 | name: "FlyingFox",
13 | targets: ["FlyingFox"]
14 | ),
15 | .library(
16 | name: "FlyingSocks",
17 | targets: ["FlyingSocks"]
18 | )
19 | ],
20 | targets: [
21 | .target(
22 | name: "FlyingFox",
23 | dependencies: ["FlyingSocks"],
24 | path: "FlyingFox/Sources",
25 | exclude: .excludeFiles,
26 | swiftSettings: .upcomingFeatures
27 | ),
28 | .testTarget(
29 | name: "FlyingFoxXCTests",
30 | dependencies: ["FlyingFox"],
31 | path: "FlyingFox/XCTests",
32 | resources: [
33 | .copy("Stubs")
34 | ],
35 | swiftSettings: .upcomingFeatures
36 | ),
37 | .target(
38 | name: "FlyingSocks",
39 | dependencies: [.target(name: "CSystemLinux", condition: .when(platforms: [.linux]))],
40 | path: "FlyingSocks/Sources",
41 | swiftSettings: .upcomingFeatures
42 | ),
43 | .testTarget(
44 | name: "FlyingSocksXCTests",
45 | dependencies: ["FlyingSocks"],
46 | path: "FlyingSocks/XCTests",
47 | resources: [
48 | .copy("Resources")
49 | ],
50 | swiftSettings: .upcomingFeatures
51 | ),
52 | .target(
53 | name: "CSystemLinux",
54 | path: "CSystemLinux"
55 | )
56 | ]
57 | )
58 |
59 | extension Array where Element == String {
60 | static var excludeFiles: [String] {
61 | #if os(Linux)
62 | ["JSONPredicatePattern.swift"]
63 | #else
64 | []
65 | #endif
66 | }
67 | }
68 |
69 | extension Array where Element == SwiftSetting {
70 |
71 | static var upcomingFeatures: [SwiftSetting] {
72 | [
73 | .enableUpcomingFeature("BareSlashRegexLiterals"),
74 | .enableUpcomingFeature("ConciseMagicFile"),
75 | .enableUpcomingFeature("DeprecateApplicationMain"),
76 | .enableUpcomingFeature("DisableOutwardActorInference"),
77 | .enableUpcomingFeature("ExistentialAny"),
78 | .enableUpcomingFeature("ForwardTrailingClosures"),
79 | .enableUpcomingFeature("GlobalConcurrency"),
80 | .enableUpcomingFeature("ImportObjcForwardDeclarations"),
81 | .enableUpcomingFeature("IsolatedDefaultValues"),
82 | //.enableExperimentalFeature("StrictConcurrency")
83 | ]
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/FlyingFox/XCTests/HTTPRequest+AddressTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPRequest+AddressTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 03/08/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import FlyingFox
33 | import XCTest
34 |
35 | final class HTTPRequestAddressTests: XCTestCase {
36 |
37 | typealias Address = HTTPRequest.Address
38 |
39 | func testRemoteAddress_IP4() {
40 | let request = HTTPRequest.make(remoteAddress: .ip4("fish", port: 80))
41 | XCTAssertEqual(request.remoteAddress, .ip4("fish", port: 80))
42 | XCTAssertEqual(request.remoteIPAddress, "fish")
43 | }
44 |
45 | func testRemoteAddress_IP6() {
46 | let request = HTTPRequest.make(remoteAddress: .ip6("chips", port: 8080))
47 | XCTAssertEqual(request.remoteAddress, .ip6("chips", port: 8080))
48 | XCTAssertEqual(request.remoteIPAddress, "chips")
49 | }
50 |
51 | func testRemoteAddress_Unix() {
52 | let request = HTTPRequest.make(remoteAddress: .unix("shrimp"))
53 | XCTAssertEqual(request.remoteAddress, .unix("shrimp"))
54 | XCTAssertNil(request.remoteIPAddress)
55 | }
56 |
57 | func testRemoteAddress_XForwardedFor() {
58 | let request = HTTPRequest.make(
59 | headers: [.xForwardedFor: "fish, chips"],
60 | remoteAddress: .ip4("shrimp", port: 80)
61 | )
62 | XCTAssertEqual(request.remoteAddress, .ip4("shrimp", port: 80))
63 | XCTAssertEqual(request.remoteIPAddress, "fish")
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/FlyingSocks/Tests/AsyncBufferedEmptySequenceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncBufferedEmptySequenceTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 06/08/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingSocks
33 | import Foundation
34 | import Testing
35 |
36 | struct AsyncBufferedEmptySequenceTests {
37 |
38 | @Test
39 | func completesImmediatley() async {
40 | var iterator = AsyncBufferedEmptySequence(completeImmediately: true)
41 | .makeAsyncIterator()
42 |
43 | #expect(
44 | await iterator.nextBuffer(suggested: 1) == nil
45 | )
46 | }
47 |
48 | @Test
49 | func cancels_AfterWaiting() async {
50 | let task = Task {
51 | await AsyncBufferedEmptySequence(completeImmediately: false)
52 | .first { _ in true }
53 | }
54 |
55 | try? await Task.sleep(seconds: 0.05)
56 | task.cancel()
57 | #expect(
58 | await task.value == nil
59 | )
60 | }
61 |
62 | @Test
63 | func cancels_Immediatley() async {
64 | let task = Task {
65 | try? await Task.sleep(seconds: 0.05)
66 | var iterator = AsyncBufferedEmptySequence(completeImmediately: false)
67 | .makeAsyncIterator()
68 | return await iterator.nextBuffer(suggested: 1)
69 | }
70 |
71 | task.cancel()
72 | #expect(
73 | await task.value == nil
74 | )
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/FlyingSocks/XCTests/AsyncBufferedEmptySequenceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncBufferedEmptySequenceTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 06/08/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingSocks
33 | import Foundation
34 | import XCTest
35 |
36 | final class AsyncBufferedEmptySequenceTests: XCTestCase {
37 |
38 | func testCompletesImmediatley() async {
39 | var iterator = AsyncBufferedEmptySequence(completeImmediately: true)
40 | .makeAsyncIterator()
41 |
42 | await AsyncAssertNil(
43 | await iterator.nextBuffer(suggested: 1)
44 | )
45 | }
46 |
47 | func testCancels_AfterWaiting() async {
48 | let task = Task {
49 | await AsyncBufferedEmptySequence(completeImmediately: false)
50 | .first { _ in true }
51 | }
52 |
53 | try? await Task.sleep(seconds: 0.05)
54 | task.cancel()
55 | await AsyncAssertNil(
56 | await task.value
57 | )
58 | }
59 |
60 | func testCancels_Immediatley() async {
61 | let task = Task {
62 | try? await Task.sleep(seconds: 0.05)
63 | var iterator = AsyncBufferedEmptySequence(completeImmediately: false)
64 | .makeAsyncIterator()
65 | return await iterator.nextBuffer(suggested: 1)
66 | }
67 |
68 | task.cancel()
69 | await AsyncAssertNil(
70 | await task.value
71 | )
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/FlyingSocks/XCTests/SocketErrorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SocketErrorTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Andre Jacobs on 07/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingSocks
33 | import XCTest
34 |
35 | final class SocketErrorTests: XCTestCase {
36 |
37 | func testSocketError_errorDescription() {
38 |
39 | let failedType = "failed"
40 | let failedErrno: Int32 = 42
41 | let failedMessage = "failure is an option"
42 | XCTAssertEqual(
43 | SocketError.failed(type: failedType, errno: failedErrno, message: failedMessage).errorDescription,
44 | "SocketError. \(failedType)(\(failedErrno)): \(failedMessage)"
45 | )
46 |
47 | XCTAssertEqual(SocketError.blocked.errorDescription, "SocketError. Blocked")
48 | XCTAssertEqual(SocketError.disconnected.errorDescription, "SocketError. Disconnected")
49 | XCTAssertEqual(SocketError.unsupportedAddress.errorDescription, "SocketError. UnsupportedAddress")
50 | }
51 |
52 | func testSocketError_makeFailed() {
53 | errno = EIO
54 | let socketError = SocketError.makeFailed("unit-test")
55 | switch socketError {
56 | case let .failed(type: type, errno: socketErrno, message: message):
57 | XCTAssertEqual(type, "unit-test")
58 | XCTAssertEqual(socketErrno, EIO)
59 | XCTAssertEqual(message, "Input/output error")
60 | default:
61 | XCTFail()
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/FlyingFox/Tests/HTTPRequest+AddressTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPRequest+AddressTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 03/08/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import FlyingFox
33 | import Foundation
34 | import Testing
35 |
36 | struct HTTPRequestAddressTests {
37 |
38 | typealias Address = HTTPRequest.Address
39 |
40 | @Test
41 | func remoteAddress_IP4() {
42 | let request = HTTPRequest.make(remoteAddress: .ip4("fish", port: 80))
43 | #expect(request.remoteAddress == .ip4("fish", port: 80))
44 | #expect(request.remoteIPAddress == "fish")
45 | }
46 |
47 | @Test
48 | func remoteAddress_IP6() {
49 | let request = HTTPRequest.make(remoteAddress: .ip6("chips", port: 8080))
50 | #expect(request.remoteAddress == .ip6("chips", port: 8080))
51 | #expect(request.remoteIPAddress == "chips")
52 | }
53 |
54 | @Test
55 | func remoteAddress_Unix() {
56 | let request = HTTPRequest.make(remoteAddress: .unix("shrimp"))
57 | #expect(request.remoteAddress == .unix("shrimp"))
58 | #expect(request.remoteIPAddress == nil)
59 | }
60 |
61 | @Test
62 | func remoteAddress_XForwardedFor() {
63 | let request = HTTPRequest.make(
64 | headers: [.xForwardedFor: "fish, chips"],
65 | remoteAddress: .ip4("shrimp", port: 80)
66 | )
67 | #expect(request.remoteAddress == .ip4("shrimp", port: 80))
68 | #expect(request.remoteIPAddress == "fish")
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/FlyingFox/Tests/HTTPResponseTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPRequestTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 02/04/2023.
6 | // Copyright © 2023 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import FlyingSocks
34 | import Foundation
35 | import Testing
36 |
37 | struct HTTPResponseTests {
38 |
39 | @Test
40 | func completeBodyData() async throws {
41 | // given
42 | let response = HTTPResponse.make(body: Data([0x01, 0x02]))
43 |
44 | // then
45 | #expect(
46 | try await response.bodyData == Data([0x01, 0x02])
47 | )
48 | }
49 |
50 | @Test
51 | func sequenceBodyData() async throws {
52 | // given
53 | let buffer = ConsumingAsyncSequence(
54 | bytes: [0x5, 0x6]
55 | )
56 | let sequence = HTTPBodySequence(from: buffer, count: 2, suggestedBufferSize: 2)
57 | let response = HTTPResponse.make(body: sequence)
58 |
59 | // then
60 | #expect(
61 | try await response.bodyData == Data([0x5, 0x6])
62 | )
63 | }
64 |
65 | @Test
66 | func webSocketBodyData() async throws {
67 | // given
68 | let response = HTTPResponse.make(webSocket: MessageFrameWSHandler.make())
69 |
70 | // then
71 | #expect(
72 | try await response.bodyData == Data()
73 | )
74 | }
75 |
76 | @Test
77 | func unknownRouteParameter() async {
78 | #expect(HTTPRequest.make().routeParameters["unknown"] == nil)
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/FlyingFox/Tests/URLSession+AsyncTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLSession+AsyncTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 22/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import Foundation
34 | #if canImport(FoundationNetworking)
35 | import FoundationNetworking
36 | #endif
37 | import Testing
38 |
39 | struct URLSessionAsyncTests {
40 |
41 | @Test(.disabled("pie.dev is down"))
42 | func session_MakesRequest() async throws {
43 | var request = URLRequest(url: URL(string: "https://pie.dev/status/208")!)
44 | request.timeoutInterval = 2
45 | let (_, response) = try await URLSession.shared.data(for: request)
46 |
47 | #expect(
48 | (response as! HTTPURLResponse).statusCode == 208
49 | )
50 | }
51 |
52 | @Test
53 | func session_ReturnsError() async throws {
54 | let request = URLRequest(url: URL(string: "https://flying.fox.invalid/")!)
55 | await #expect(throws: URLError.self) {
56 | try await URLSession.shared.data(for: request)
57 | }
58 | }
59 |
60 | @Test
61 | func session_CancelsRequest() async throws {
62 | let request = URLRequest(url: URL(string: "https://httpstat.us/200?sleep=10000")!)
63 |
64 | let task = Task {
65 | _ = try await URLSession.shared.data(for: request)
66 | }
67 |
68 | task.cancel()
69 |
70 | await #expect(throws: URLError.self) {
71 | try await task.value
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/FlyingFox/XCTests/HTTPRequestTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPRequestTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 02/04/2023.
6 | // Copyright © 2023 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import FlyingSocks
34 | import XCTest
35 |
36 | final class HTTPResponseTests: XCTestCase {
37 |
38 | func testCompleteBodyData() async {
39 | // given
40 | let response = HTTPResponse.make(body: Data([0x01, 0x02]))
41 |
42 | // then
43 | await AsyncAssertEqual(
44 | try await response.bodyData,
45 | Data([0x01, 0x02])
46 | )
47 | }
48 |
49 | func testSequenceBodyData() async {
50 | // given
51 | let buffer = ConsumingAsyncSequence(
52 | bytes: [0x5, 0x6]
53 | )
54 | let sequence = HTTPBodySequence(from: buffer, count: 2, suggestedBufferSize: 2)
55 | let response = HTTPResponse.make(body: sequence)
56 |
57 | // then
58 | await AsyncAssertEqual(
59 | try await response.bodyData,
60 | Data([0x5, 0x6])
61 | )
62 | }
63 |
64 | func testWebSocketBodyData() async {
65 | // given
66 | let response = HTTPResponse.make(webSocket: MessageFrameWSHandler.make())
67 |
68 | // then
69 | await AsyncAssertEqual(
70 | try await response.bodyData,
71 | Data()
72 | )
73 | }
74 |
75 | func testUnknownRouteParameter() async {
76 | XCTAssertNil(HTTPRequest.make().routeParameters["unknown"])
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/FlyingSocks/Tests/MutexTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MutexTests.swift
3 | // swift-mutex
4 | //
5 | // Created by Simon Whitty on 07/09/2024.
6 | // Copyright 2024 Simon Whitty
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/swift-mutex
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | #if canImport(Testing)
33 | @testable import FlyingSocks
34 | import Testing
35 |
36 | struct MutexTests {
37 |
38 | @Test
39 | func withLock_ReturnsValue() {
40 | let mutex = Mutex("fish")
41 | let val = mutex.withLock {
42 | $0 + " & chips"
43 | }
44 | #expect(val == "fish & chips")
45 | }
46 |
47 | @Test
48 | func withLock_ThrowsError() {
49 | let mutex = Mutex("fish")
50 | #expect(throws: CancellationError.self) {
51 | try mutex.withLock { _ -> Void in throw CancellationError() }
52 | }
53 | }
54 |
55 | @Test
56 | func lockIfAvailable_ReturnsValue() {
57 | let mutex = Mutex("fish")
58 | mutex.unsafeLock()
59 | #expect(
60 | mutex.withLockIfAvailable { _ in "chips" } == nil
61 | )
62 | mutex.unsafeUnlock()
63 | #expect(
64 | mutex.withLockIfAvailable { _ in "chips" } == "chips"
65 | )
66 | }
67 |
68 | @Test
69 | func withLockIfAvailable_ThrowsError() {
70 | let mutex = Mutex("fish")
71 | #expect(throws: CancellationError.self) {
72 | try mutex.withLockIfAvailable { _ -> Void in throw CancellationError() }
73 | }
74 | }
75 | }
76 |
77 | extension Mutex {
78 | func unsafeLock() { storage.lock() }
79 | func unsafeUnlock() { storage.unlock() }
80 | }
81 | #endif
82 |
--------------------------------------------------------------------------------
/FlyingFox/XCTests/URLSession+AsyncTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLSession+AsyncTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 22/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import XCTest
34 | import Foundation
35 | #if canImport(FoundationNetworking)
36 | import FoundationNetworking
37 | #endif
38 |
39 | final class URLSessionAsyncTests: XCTestCase {
40 |
41 | func disabled_testURLSession_MakesRequest() async throws {
42 | var request = URLRequest(url: URL(string: "https://pie.dev/status/208")!)
43 | request.timeoutInterval = 2
44 | let (_, response) = try await URLSession.shared.data(for: request)
45 |
46 | XCTAssertEqual(
47 | (response as! HTTPURLResponse).statusCode,
48 | 208
49 | )
50 | }
51 |
52 | func testURLSession_ReturnsError() async throws {
53 | let request = URLRequest(url: URL(string: "https://flying.fox.invalid/")!)
54 | await AsyncAssertThrowsError(try await URLSession.shared.data(for: request), of: URLError.self)
55 | }
56 |
57 | func testURLSession_CancelsRequest() async throws {
58 | let request = URLRequest(url: URL(string: "https://httpstat.us/200?sleep=10000")!)
59 |
60 | let task = Task {
61 | _ = try await URLSession.shared.data(for: request)
62 | }
63 |
64 | task.cancel()
65 |
66 | await AsyncAssertThrowsError(try await task.value, of: URLError.self) {
67 | XCTAssertEqual($0.code, .cancelled)
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/FlyingSocks/XCTests/MutexXCTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MutexXCTests.swift
3 | // swift-mutex
4 | //
5 | // Created by Simon Whitty on 07/09/2024.
6 | // Copyright 2024 Simon Whitty
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/swift-mutex
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | #if !canImport(Testing)
33 | @testable import FlyingSocks
34 | import XCTest
35 |
36 | final class MutexXCTests: XCTestCase {
37 |
38 | func testWithLock_ReturnsValue() {
39 | let mutex = Mutex("fish")
40 | let val = mutex.withLock {
41 | $0 + " & chips"
42 | }
43 | XCTAssertEqual(val, "fish & chips")
44 | }
45 |
46 | func testWithLock_ThrowsError() {
47 | let mutex = Mutex("fish")
48 | XCTAssertThrowsError(try mutex.withLock { _ -> Void in throw CancellationError() }) {
49 | _ = $0 is CancellationError
50 | }
51 | }
52 |
53 | func testLockIfAvailable_ReturnsValue() {
54 | let mutex = Mutex("fish")
55 | mutex.unsafeLock()
56 | XCTAssertNil(
57 | mutex.withLockIfAvailable { _ in "chips" }
58 | )
59 | mutex.unsafeUnlock()
60 | XCTAssertEqual(
61 | mutex.withLockIfAvailable { _ in "chips" },
62 | "chips"
63 | )
64 | }
65 |
66 | func testWithLockIfAvailable_ThrowsError() {
67 | let mutex = Mutex("fish")
68 | XCTAssertThrowsError(try mutex.withLockIfAvailable { _ -> Void in throw CancellationError() }) {
69 | _ = $0 is CancellationError
70 | }
71 | }
72 | }
73 |
74 | extension Mutex {
75 | func unsafeLock() { storage.lock() }
76 | func unsafeUnlock() { storage.unlock() }
77 | }
78 | #endif
79 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPServer+Listening.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPServer.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 13/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 | import FlyingSocks
34 |
35 | extension HTTPServer {
36 |
37 | public var isListening: Bool { state != nil }
38 |
39 | public func waitUntilListening(timeout: TimeInterval = 5) async throws {
40 | try await withThrowingTimeout(seconds: timeout) {
41 | try await self.doWaitUntilListening()
42 | }
43 | }
44 |
45 | private func doWaitUntilListening() async throws {
46 | guard !isListening else { return }
47 | try await withIdentifiableThrowingContinuation(isolation: self) {
48 | appendContinuation($0)
49 | } onCancel: { id in
50 | Task { await self.cancelContinuation(with: id) }
51 | }
52 | }
53 |
54 | private func appendContinuation(_ continuation: Continuation) {
55 | waiting[continuation.id] = continuation
56 | }
57 |
58 | private func cancelContinuation(with id: Continuation.ID) {
59 | if let continuation = waiting.removeValue(forKey: id) {
60 | continuation.resume(throwing: CancellationError())
61 | }
62 | }
63 |
64 | func isListeningDidUpdate(from previous: Bool) {
65 | guard isListening else { return }
66 | let waiting = self.waiting
67 | self.waiting = [:]
68 |
69 | for continuation in waiting.values {
70 | continuation.resume()
71 | }
72 | }
73 |
74 | typealias Continuation = IdentifiableContinuation
75 | }
76 |
--------------------------------------------------------------------------------
/FlyingSocks/Tests/SocketErrorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SocketErrorTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Andre Jacobs on 07/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingSocks
33 | import Foundation
34 | import Testing
35 |
36 | struct SocketErrorTests {
37 |
38 | @Test
39 | func socketError_errorDescription() {
40 |
41 | let failedType = "failed"
42 | let failedErrno: Int32 = 42
43 | let failedMessage = "failure is an option"
44 | #expect(
45 | SocketError.failed(type: failedType, errno: failedErrno, message: failedMessage).errorDescription == "SocketError. \(failedType)(\(failedErrno)): \(failedMessage)"
46 | )
47 |
48 | #expect(SocketError.blocked.errorDescription == "SocketError. Blocked")
49 | #expect(SocketError.disconnected.errorDescription == "SocketError. Disconnected")
50 | #expect(SocketError.unsupportedAddress.errorDescription == "SocketError. UnsupportedAddress")
51 | #expect(SocketError.timeout(message: "fish").errorDescription == "SocketError. Timeout: fish")
52 | }
53 |
54 | @Test
55 | func socketError_makeFailed() {
56 | #if canImport(WinSDK)
57 | WSASetLastError(EIO)
58 | #else
59 | errno = EIO
60 | #endif
61 |
62 | let socketError = SocketError.makeFailed("unit-test")
63 | switch socketError {
64 | case let .failed(type: type, errno: socketErrno, message: message):
65 | #expect(type == "unit-test")
66 | #expect(socketErrno == EIO)
67 | #expect(message == "Input/output error")
68 | default:
69 | #expect(Bool(false))
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/URLSession+Async.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLSession+Async.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 13/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 | import FlyingSocks
34 |
35 | #if compiler(<6) && canImport(FoundationNetworking)
36 | import FoundationNetworking
37 |
38 | extension URLSession {
39 |
40 | // Ports macOS Foundation method to earlier Linux versions
41 | func data(for request: URLRequest) async throws -> (Data, URLResponse) {
42 | let state = Mutex((isCancelled: false, task: URLSessionDataTask?.none))
43 | return try await withTaskCancellationHandler {
44 | try await withCheckedThrowingContinuation { continuation in
45 | let task = dataTask(with: request) { data, response, error in
46 | guard let data = data, let response = response else {
47 | continuation.resume(throwing: error!)
48 | return
49 | }
50 | continuation.resume(returning: (data, response))
51 | }
52 | let shouldCancel = state.withLock {
53 | $0.task = task
54 | return $0.isCancelled
55 | }
56 | task.resume()
57 | if shouldCancel {
58 | task.cancel()
59 | }
60 | }
61 | } onCancel: {
62 | let taskToCancel = state.withLock {
63 | $0.isCancelled = true
64 | return $0.task
65 | }
66 | if let taskToCancel {
67 | taskToCancel.cancel()
68 | }
69 | }
70 | }
71 | }
72 | #endif
73 |
--------------------------------------------------------------------------------
/FlyingSocks/Tests/AsyncBufferedDataSequenceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncBufferedDataSequenceTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 10/07/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingSocks
33 | import Foundation
34 | import Testing
35 |
36 | struct AsyncBufferedDataSequenceTests {
37 |
38 | @Test
39 | func sequenceBuffers() async {
40 | let buffer = AsyncBufferedCollection(bytes: [
41 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
42 | 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
43 | ])
44 |
45 | #expect(
46 | await buffer.collectBuffers(ofLength: 5) == [
47 | Data([0x0, 0x1, 0x2, 0x3, 0x4]),
48 | Data([0x5, 0x6, 0x7, 0x8, 0x9]),
49 | Data([0xA, 0xB, 0xC, 0xD, 0xE]),
50 | Data([0xF])
51 | ]
52 | )
53 | }
54 |
55 | @Test
56 | func sequenceCanBeIteratorMultipleTimes() async {
57 | let buffer = AsyncBufferedCollection(bytes: [
58 | 0x0, 0x1, 0x2
59 | ])
60 |
61 | #expect(
62 | await buffer.collectBuffers(ofLength: 5) == [Data([0x0, 0x1, 0x2])]
63 | )
64 |
65 | #expect(
66 | await buffer.collectBuffers(ofLength: 5) == [Data([0x0, 0x1, 0x2])]
67 | )
68 | }
69 | }
70 |
71 | private extension AsyncBufferedSequence {
72 |
73 | func collectBuffers(ofLength count: Int) async -> [AsyncIterator.Buffer] {
74 | var collected = [AsyncIterator.Buffer]()
75 | var iterator = makeAsyncIterator()
76 |
77 | while let buffer = try? await iterator.nextBuffer(suggested: count) {
78 | collected.append(buffer)
79 | }
80 | return collected
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/WebSocket/AsyncStream+WSFrame.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncStream+WSFrame.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 18/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import FlyingSocks
33 |
34 | extension AsyncThrowingStream {
35 |
36 | static func decodingFrames(from bytes: some AsyncBufferedSequence) -> Self {
37 | AsyncThrowingStream {
38 | do {
39 | return try await WSFrameEncoder.decodeFrame(from: bytes)
40 | } catch SocketError.disconnected, is SequenceTerminationError {
41 | return nil
42 | } catch {
43 | throw error
44 | }
45 | }
46 | }
47 | }
48 |
49 | extension AsyncStream {
50 |
51 | #if compiler(<6.2)
52 | typealias SendableMetatype = Any
53 | #endif
54 |
55 | static func protocolFrames(from frames: S) -> Self where S.Element == WSFrame {
56 | let iterator = Iterator(from: frames)
57 | return AsyncStream {
58 | do {
59 | return try await iterator.next()
60 | } catch {
61 | iterator.close()
62 | return .close(message: "Protocol Error")
63 | }
64 | }
65 | }
66 |
67 | private final class Iterator: @unchecked Sendable where S.Element == WSFrame {
68 | var iterator: S.AsyncIterator?
69 |
70 | init(from frames: S){
71 | self.iterator = frames.makeAsyncIterator()
72 | }
73 |
74 | func next() async throws -> WSFrame? {
75 | try await iterator?.next()
76 | }
77 |
78 | func close() {
79 | iterator = nil
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/Handlers/DirectoryHTTPHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DirectoryHTTPHandler.swift
3 | // FlyingFox
4 | //
5 | // Created by Huw Rowlands on 20/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 |
34 | public struct DirectoryHTTPHandler: HTTPHandler {
35 |
36 | private(set) var root: URL?
37 | let serverPath: String
38 |
39 | public init(root: URL, serverPath: String = "/") {
40 | self.root = root
41 | self.serverPath = serverPath
42 | }
43 |
44 | public init(bundle: Bundle, subPath: String = "", serverPath: String) {
45 | self.root = bundle.resourceURL?.appendingPathComponent(subPath)
46 | self.serverPath = serverPath
47 | }
48 |
49 | public func handleRequest(_ request: HTTPRequest) async throws -> HTTPResponse {
50 | guard
51 | let filePath = makeFileURL(for: request.path),
52 | let data = try? Data(contentsOf: filePath) else {
53 | return HTTPResponse(statusCode: .notFound)
54 | }
55 |
56 | return HTTPResponse(
57 | statusCode: .ok,
58 | headers: [.contentType: FileHTTPHandler.makeContentType(for: filePath.absoluteString)],
59 | body: data
60 | )
61 | }
62 |
63 | func makeFileURL(for requestPath: String) -> URL? {
64 | let compsA = serverPath
65 | .split(separator: "/", omittingEmptySubsequences: true)
66 | .joined(separator: "/")
67 |
68 | let compsB = requestPath
69 | .split(separator: "/", omittingEmptySubsequences: true)
70 | .joined(separator: "/")
71 |
72 | guard compsB.hasPrefix(compsA) else { return nil }
73 | let subPath = String(compsB.dropFirst(compsA.count))
74 | return root?.appendingPathComponent(subPath)
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/FlyingSocks/XCTests/AsyncBufferedFileSequenceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncBufferedFileSequenceTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 06/08/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingSocks
33 | import Foundation
34 | import XCTest
35 |
36 | final class AsyncBufferedFileSequenceTests: XCTestCase {
37 |
38 | func testFileSize() {
39 | XCTAssertEqual(
40 | try AsyncBufferedFileSequence.fileSize(at: .jackOfHeartsRecital),
41 | 299
42 | )
43 | XCTAssertThrowsError(
44 | try AsyncBufferedFileSequence.fileSize(at: URL(fileURLWithPath: "missing"))
45 | )
46 |
47 | XCTAssertThrowsError(
48 | try AsyncBufferedFileSequence.fileSize(from: [:])
49 | )
50 | }
51 |
52 | func testFileHandleRead() throws {
53 | let handle = try FileHandle(forReadingFrom: .jackOfHeartsRecital)
54 | XCTAssertEqual(
55 | try handle.read(suggestedCount: 14, forceLegacy: false),
56 | "Two doors down".data(using: .utf8)
57 | )
58 | XCTAssertEqual(
59 | try handle.read(suggestedCount: 9, forceLegacy: true),
60 | " the boys".data(using: .utf8)
61 | )
62 | }
63 |
64 | func testReadsEntireFile() async throws {
65 | let sequence = try AsyncBufferedFileSequence(contentsOf: .jackOfHeartsRecital)
66 |
67 | await AsyncAssertEqual(
68 | try await sequence.getAllData(),
69 | try Data(contentsOf: .jackOfHeartsRecital)
70 | )
71 | }
72 | }
73 |
74 | private extension URL {
75 | static var jackOfHeartsRecital: URL {
76 | Bundle.module.url(forResource: "Resources", withExtension: nil)!
77 | .appendingPathComponent("JackOfHeartsRecital.txt")
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPServer+Configuration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPServer+Configuration.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 06/08/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 | import FlyingSocks
34 |
35 | public extension HTTPServer {
36 |
37 | struct Configuration: Sendable {
38 | public var address: any SocketAddress
39 | public var timeout: TimeInterval
40 | public var sharedRequestBufferSize: Int
41 | public var sharedRequestReplaySize: Int
42 | public var pool: any AsyncSocketPool
43 | public var logger: any Logging
44 |
45 | public init(address: some SocketAddress,
46 | timeout: TimeInterval = 15,
47 | sharedRequestBufferSize: Int = 4_096,
48 | sharedRequestReplaySize: Int = 2_097_152,
49 | pool: any AsyncSocketPool = HTTPServer.defaultPool(),
50 | logger: any Logging = HTTPServer.defaultLogger()) {
51 | self.address = address
52 | self.timeout = timeout
53 | self.sharedRequestBufferSize = sharedRequestBufferSize
54 | self.sharedRequestReplaySize = sharedRequestReplaySize
55 | self.pool = pool
56 | self.logger = logger
57 | }
58 | }
59 | }
60 |
61 | extension HTTPServer.Configuration {
62 |
63 | init(port: UInt16,
64 | timeout: TimeInterval = 15,
65 | logger: any Logging = HTTPServer.defaultLogger()
66 | ) {
67 | #if canImport(WinSDK)
68 | let address = sockaddr_in.inet(port: port)
69 | #else
70 | let address = sockaddr_in6.inet6(port: port)
71 | #endif
72 | self.init(
73 | address: address,
74 | timeout: timeout,
75 | logger: logger
76 | )
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/FlyingSocks/XCTests/AsyncBufferedDataSequenceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncBufferedDataSequenceTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 10/07/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import FlyingSocks
33 | import Foundation
34 | import XCTest
35 |
36 | final class AsyncBufferedDataSequenceTests: XCTestCase {
37 |
38 | func testSequence() async {
39 | let buffer = AsyncBufferedCollection(bytes: [
40 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
41 | 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
42 | ])
43 |
44 | await AsyncAssertEqual(
45 | await buffer.collectBuffers(ofLength: 5),
46 | [
47 | Data([0x0, 0x1, 0x2, 0x3, 0x4]),
48 | Data([0x5, 0x6, 0x7, 0x8, 0x9]),
49 | Data([0xA, 0xB, 0xC, 0xD, 0xE]),
50 | Data([0xF])
51 | ]
52 | )
53 | }
54 |
55 | func testSequenceCanBeIteratorMultipleTimes() async {
56 | let buffer = AsyncBufferedCollection(bytes: [
57 | 0x0, 0x1, 0x2
58 | ])
59 |
60 | await AsyncAssertEqual(
61 | await buffer.collectBuffers(ofLength: 5),
62 | [Data([0x0, 0x1, 0x2])]
63 | )
64 |
65 | await AsyncAssertEqual(
66 | await buffer.collectBuffers(ofLength: 5),
67 | [Data([0x0, 0x1, 0x2])]
68 | )
69 | }
70 | }
71 |
72 | private extension AsyncBufferedSequence {
73 |
74 | func collectBuffers(ofLength count: Int) async -> [AsyncIterator.Buffer] {
75 | var collected = [AsyncIterator.Buffer]()
76 | var iterator = makeAsyncIterator()
77 |
78 | while let buffer = try? await iterator.nextBuffer(suggested: count) {
79 | collected.append(buffer)
80 | }
81 | return collected
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPChunkedEncodedSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPChunkedTransferEncoder.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 09/07/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 | import FlyingSocks
34 |
35 | struct HTTPChunkedTransferEncoder: AsyncBufferedSequence, Sendable
36 | where Base: AsyncBufferedSequence,
37 | Base.Element == UInt8,
38 | Base: Sendable {
39 | typealias Element = UInt8
40 |
41 | private let bytes: Base
42 |
43 | init(bytes: Base) {
44 | self.bytes = bytes
45 | }
46 |
47 | func makeAsyncIterator() -> Iterator {
48 | Iterator(bytes: bytes.makeAsyncIterator())
49 | }
50 | }
51 |
52 | extension HTTPChunkedTransferEncoder {
53 |
54 | struct Iterator: AsyncBufferedIteratorProtocol {
55 |
56 | private var bytes: Base.AsyncIterator
57 | private var isComplete: Bool = false
58 |
59 | init(bytes: Base.AsyncIterator) {
60 | self.bytes = bytes
61 | }
62 |
63 | mutating func next() async throws -> UInt8? {
64 | fatalError("call nextBuffer(suggested:)")
65 | }
66 |
67 | mutating func nextBuffer(suggested count: Int) async throws -> [UInt8]? {
68 | guard !isComplete else { return nil }
69 |
70 | if let buffer = try await bytes.nextBuffer(suggested: count) {
71 | var response = Array(String(format:"%02X", buffer.count).utf8)
72 | response.append(contentsOf: Array("\r\n".utf8))
73 | response.append(contentsOf: buffer)
74 | response.append(contentsOf: Array("\r\n".utf8))
75 | return response
76 | } else {
77 | isComplete = true
78 | return Array("0\r\n\r\n".utf8)
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/FlyingFox/XCTests/HTTPRequest+Mock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPRequest+Mock.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 18/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import Foundation
34 |
35 | extension HTTPRequest {
36 | static func make(method: HTTPMethod = .GET,
37 | version: HTTPVersion = .http11,
38 | path: String = "/",
39 | query: [QueryItem] = [],
40 | headers: HTTPHeaders = [:],
41 | body: Data = Data(),
42 | remoteAddress: Address? = nil) -> Self {
43 | HTTPRequest(method: method,
44 | version: version,
45 | path: path,
46 | query: query,
47 | headers: headers,
48 | body: HTTPBodySequence(data: body),
49 | remoteAddress: remoteAddress)
50 | }
51 |
52 | static func make(method: HTTPMethod = .GET, _ url: String, headers: HTTPHeaders = [:]) -> Self {
53 | let (path, query) = HTTPDecoder.make().readComponents(from: url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)
54 | return HTTPRequest.make(
55 | method: method,
56 | path: path,
57 | query: query,
58 | headers: headers
59 | )
60 | }
61 |
62 | var bodyString: String {
63 | get async throws {
64 | try await String(decoding: bodyData, as: UTF8.self)
65 | }
66 | }
67 | }
68 |
69 | extension HTTPDecoder {
70 | static func make(sharedRequestBufferSize: Int = 128, sharedRequestReplaySize: Int = 1024) -> HTTPDecoder {
71 | HTTPDecoder(
72 | sharedRequestBufferSize: sharedRequestBufferSize,
73 | sharedRequestReplaySize: sharedRequestReplaySize
74 | )
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/FlyingSocks/Sources/AsyncBufferedCollection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncBufferedDataSequence.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 10/07/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 |
34 | package struct AsyncBufferedCollection: AsyncBufferedSequence where C: Sendable, C.Element: Sendable {
35 | package typealias Element = C.Element
36 |
37 | private let collection: C
38 |
39 | package init(_ collection: C) {
40 | self.collection = collection
41 | }
42 |
43 | package func makeAsyncIterator() -> Iterator {
44 | Iterator(collection: collection)
45 | }
46 |
47 | package struct Iterator: AsyncBufferedIteratorProtocol {
48 |
49 | private let collection: C
50 | private var index: C.Index
51 |
52 | init(collection: C) {
53 | self.collection = collection
54 | self.index = collection.startIndex
55 | }
56 |
57 | package mutating func next() async throws -> C.Element? {
58 | guard index < collection.endIndex else { return nil }
59 | let element = collection[index]
60 | index = collection.index(after: index)
61 | return element
62 | }
63 |
64 | package mutating func nextBuffer(suggested count: Int) async -> C.SubSequence? {
65 | guard index < collection.endIndex else { return nil }
66 | let endIndex = collection.index(index, offsetBy: count, limitedBy: collection.endIndex) ?? collection.endIndex
67 | let buffer = collection[index.. {
78 | init(bytes: some Sequence) {
79 | self.init(Data(bytes))
80 | }
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/FlyingSocks/Sources/Logging+OSLog.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logging+OSLog.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 19/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | #if canImport(OSLog)
33 | import OSLog
34 |
35 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, *)
36 | public struct OSLogLogger: Logging, @unchecked Sendable {
37 |
38 | private var logger: Logger
39 |
40 | public init(logger: Logger) {
41 | self.logger = logger
42 | }
43 |
44 | public func logDebug(_ debug: @autoclosure () -> String) {
45 | withoutActuallyEscaping(debug) { debug in
46 | logger.debug("\(debug(), privacy: .public)")
47 | }
48 | }
49 |
50 | public func logInfo(_ info: @autoclosure () -> String) {
51 | withoutActuallyEscaping(info) { info in
52 | logger.info("\(info(), privacy: .public)")
53 | }
54 | }
55 |
56 | public func logWarning(_ warning: @autoclosure () -> String) {
57 | withoutActuallyEscaping(warning) { warning in
58 | logger.warning("\(warning(), privacy: .public)")
59 | }
60 | }
61 |
62 | public func logError(_ error: @autoclosure () -> String) {
63 | withoutActuallyEscaping(error) { error in
64 | logger.error("\(error(), privacy: .public)")
65 | }
66 | }
67 |
68 | public func logCritical(_ critical: @autoclosure () -> String) {
69 | withoutActuallyEscaping(critical) { critical in
70 | logger.error("\(critical(), privacy: .public)")
71 | }
72 | }
73 | }
74 |
75 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, *)
76 | public extension Logging where Self == OSLogLogger {
77 |
78 | static func oslog(bundle: Bundle = .main, category: String) -> Self {
79 | let logger = Logger(subsystem: bundle.bundleIdentifier ?? category, category: category)
80 | return OSLogLogger(logger: logger)
81 | }
82 | }
83 |
84 | #endif
85 |
--------------------------------------------------------------------------------
/FlyingSocks/Sources/AsyncBufferedPrefixSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncBufferedPrefixSequence.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 04/02/2025.
6 | // Copyright © 2025 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | package struct AsyncBufferedPrefixSequence: AsyncBufferedSequence {
33 | package typealias Element = Base.Element
34 |
35 | private let base: Base
36 | private let count: Int
37 |
38 | package init(base: Base, count: Int) {
39 | self.base = base
40 | self.count = count
41 | }
42 |
43 | package func makeAsyncIterator() -> Iterator {
44 | Iterator(iterator: base.makeAsyncIterator(), remaining: count)
45 | }
46 |
47 | package struct Iterator: AsyncBufferedIteratorProtocol {
48 | private var iterator: Base.AsyncIterator
49 | private var remaining: Int
50 |
51 | init (iterator: Base.AsyncIterator, remaining: Int) {
52 | self.iterator = iterator
53 | self.remaining = remaining
54 | }
55 |
56 | package mutating func next() async throws -> Base.Element? {
57 | guard remaining > 0 else { return nil }
58 |
59 | if let element = try await iterator.next() {
60 | remaining -= 1
61 | return element
62 | } else {
63 | remaining = 0
64 | return nil
65 | }
66 | }
67 |
68 | package mutating func nextBuffer(suggested count: Int) async throws -> Base.AsyncIterator.Buffer? {
69 | guard remaining > 0 else { return nil }
70 |
71 | let count = Swift.min(remaining, count)
72 | if let buffer = try await iterator.nextBuffer(suggested: count) {
73 | remaining -= buffer.count
74 | return buffer
75 | } else {
76 | remaining = 0
77 | return nil
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/FlyingFox/Tests/AsyncSequence+ExtensionsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncSequence+ExtensionsTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 13/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import FlyingSocks
34 | import Testing
35 |
36 | struct AsyncSequenceExtensionTests {
37 |
38 | @Test
39 | func collectStrings() async throws {
40 | var iterator = ConsumingAsyncSequence("fish,chips".data(using: .utf8)!)
41 | .collectStrings(separatedBy: ",")
42 | .makeAsyncIterator()
43 |
44 | #expect(try await iterator.next() == "fish")
45 | #expect(try await iterator.next() == "chips")
46 | #expect(try await iterator.next() == nil)
47 | }
48 |
49 | @Test
50 | func collectStringsWithTrailingSeperator() async throws {
51 | var iterator = ConsumingAsyncSequence("fish,chips,".data(using: .utf8)!)
52 | .collectStrings(separatedBy: ",")
53 | .makeAsyncIterator()
54 |
55 | #expect(try await iterator.next() == "fish")
56 | #expect(try await iterator.next() == "chips")
57 | #expect(try await iterator.next() == nil)
58 | }
59 |
60 | @Test
61 | func collectStringsWithTrailingSeperatorA() async throws {
62 | var iterator = ConsumingAsyncSequence([0x61, 0x2c, 0x62, 0x2c, 0xff])
63 | .collectStrings(separatedBy: ",")
64 | .makeAsyncIterator()
65 |
66 | #expect(try await iterator.next() == "a")
67 | #expect(try await iterator.next() == "b")
68 | await #expect(throws: AsyncSequenceError.self) {
69 | try await iterator.next()
70 | }
71 | }
72 |
73 | @Test
74 | func takeNextThrowsError_WhenSequenceEnds() async {
75 | let sequence = ConsumingAsyncSequence(bytes: [])
76 |
77 | await #expect(throws: SequenceTerminationError.self) {
78 | try await sequence.takeNext()
79 | }
80 | }
81 | }
82 |
83 |
84 |
--------------------------------------------------------------------------------
/FlyingFox/XCTests/AsyncSequence+ExtensionsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncSequence+ExtensionsTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 13/03/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import FlyingSocks
34 | import XCTest
35 |
36 | final class AsyncSequenceExtensionTests: XCTestCase {
37 |
38 | func testCollectStrings() async throws {
39 | var iterator = ConsumingAsyncSequence("fish,chips".data(using: .utf8)!)
40 | .collectStrings(separatedBy: ",")
41 | .makeAsyncIterator()
42 | //
43 | await AsyncAssertEqual(try await iterator.next(), "fish")
44 | await AsyncAssertEqual(try await iterator.next(), "chips")
45 | await AsyncAssertEqual(try await iterator.next(), nil)
46 | }
47 |
48 | func testCollectStringsWithTrailingSeperator() async throws {
49 | var iterator = ConsumingAsyncSequence("fish,chips,".data(using: .utf8)!)
50 | .collectStrings(separatedBy: ",")
51 | .makeAsyncIterator()
52 |
53 | await AsyncAssertEqual(try await iterator.next(), "fish")
54 | await AsyncAssertEqual(try await iterator.next(), "chips")
55 | await AsyncAssertEqual(try await iterator.next(), nil)
56 | }
57 |
58 | func testCollectStringsWithTrailingSeperatorA() async throws {
59 | var iterator = ConsumingAsyncSequence([0x61, 0x2c, 0x62, 0x2c, 0xff])
60 | .collectStrings(separatedBy: ",")
61 | .makeAsyncIterator()
62 |
63 | await AsyncAssertEqual(try await iterator.next(), "a")
64 | await AsyncAssertEqual(try await iterator.next(), "b")
65 | await AsyncAssertThrowsError(try await iterator.next(), of: AsyncSequenceError.self)
66 | }
67 |
68 | func testTakeNextThrowsError_WhenSequenceEnds() async {
69 | let sequence = ConsumingAsyncSequence(bytes: [])
70 |
71 | await AsyncAssertThrowsError(try await sequence.takeNext(), of: SequenceTerminationError.self)
72 | }
73 | }
74 |
75 |
76 |
--------------------------------------------------------------------------------
/FlyingSocks/Sources/AsyncBufferedSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncBufferedSequence.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 06/07/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | /// AsyncSequence that is buffered and can optionally receive contiguous elements in chunks, instead of just one-at-a-time.
33 | public protocol AsyncBufferedSequence: AsyncSequence, Sendable where AsyncIterator: AsyncBufferedIteratorProtocol, Element: Sendable {
34 |
35 | }
36 |
37 | public protocol AsyncBufferedIteratorProtocol: AsyncIteratorProtocol {
38 | // Buffered elements are returned in this collection type
39 | associatedtype Buffer: Collection where Buffer.Element == Element
40 |
41 | /// Retrieves available elements from the buffer. Suspends if 0 elements are available.
42 | /// - Parameter count: The suggested number of elements to return
43 | /// - Returns: Collection with between 1 and the number elements that was requested. Nil is returned if the sequence has ended.
44 | mutating func nextBuffer(suggested count: Int) async throws -> Buffer?
45 | }
46 |
47 | public extension AsyncBufferedIteratorProtocol {
48 |
49 | /// Retrieves n elements from sequence in a single array.
50 | /// - Parameter count: The maximum number of elements to return
51 | /// - Returns: Array with the number of elements that was requested. Nil is returned if the sequence has ended.
52 | mutating func nextBuffer(count: Int) async throws -> [Element]? {
53 | guard count > 0 else { return [] }
54 |
55 | var buffer = [Element]()
56 | while buffer.count < count {
57 | try Task.checkCancellation()
58 | let remaining = count - buffer.count
59 | if let chunk = try await nextBuffer(suggested: remaining) {
60 | buffer.append(contentsOf: chunk)
61 | } else {
62 | throw SocketError.disconnected
63 | }
64 | }
65 | return buffer.isEmpty ? nil : buffer
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/FlyingSocks/Sources/AsyncBufferedEmptySequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncBufferedEmptySequence.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 06/08/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | package struct AsyncBufferedEmptySequence: Sendable, AsyncBufferedSequence {
33 |
34 | private let completeImmediately: Bool
35 |
36 | package init(completeImmediately: Bool = false) {
37 | self.completeImmediately = completeImmediately
38 | }
39 |
40 | package func makeAsyncIterator() -> AsyncIterator {
41 | AsyncIterator(completeImmediately: completeImmediately)
42 | }
43 |
44 | package struct AsyncIterator: AsyncBufferedIteratorProtocol {
45 | let completeImmediately: Bool
46 |
47 | package mutating func next() async -> Element? {
48 | if completeImmediately {
49 | return nil
50 | }
51 | let state = Mutex(State())
52 | return await withTaskCancellationHandler {
53 | await withCheckedContinuation { (continuation: CheckedContinuation) in
54 | let shouldCancel = state.withLock {
55 | $0.continuation = continuation
56 | return $0.isCancelled
57 | }
58 |
59 | if shouldCancel {
60 | continuation.resume(returning: nil)
61 | }
62 | }
63 | } onCancel: {
64 | let continuation = state.withLock {
65 | $0.isCancelled = true
66 | return $0.continuation
67 | }
68 | continuation?.resume(returning: nil)
69 | }
70 | }
71 |
72 | package mutating func nextBuffer(suggested count: Int) async -> [Element]? {
73 | await next().map { [$0] }
74 | }
75 |
76 | private struct State {
77 | var continuation: CheckedContinuation?
78 | var isCancelled: Bool = false
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/FlyingFox/Tests/HTTPResponse+Mock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPResponse+Mock.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 17/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import FlyingSocks
34 | import Foundation
35 |
36 | extension HTTPResponse {
37 |
38 | static func make(version: HTTPVersion = .http11,
39 | statusCode: HTTPStatusCode = .ok,
40 | headers: HTTPHeaders = [:],
41 | body: Data = Data()) -> Self {
42 | HTTPResponse(version: version,
43 | statusCode: statusCode,
44 | headers: headers,
45 | body: body)
46 | }
47 |
48 | static func makeChunked(version: HTTPVersion = .http11,
49 | statusCode: HTTPStatusCode = .ok,
50 | headers: HTTPHeaders = [:],
51 | body: Data = Data(),
52 | chunkSize: Int = 5) -> Self {
53 | let consuming = ConsumingAsyncSequence(body)
54 | return HTTPResponse(
55 | version: version,
56 | statusCode: statusCode,
57 | headers: headers,
58 | body: HTTPBodySequence(from: consuming, suggestedBufferSize: chunkSize)
59 | )
60 | }
61 |
62 | static func make(version: HTTPVersion = .http11,
63 | statusCode: HTTPStatusCode = .ok,
64 | headers: HTTPHeaders = [:],
65 | body: HTTPBodySequence) -> Self {
66 | HTTPResponse(version: version,
67 | statusCode: statusCode,
68 | headers: headers,
69 | body: body)
70 | }
71 |
72 | static func make(headers: [HTTPHeader: String] = [:],
73 | webSocket handler: some WSHandler) -> Self {
74 | HTTPResponse(headers: headers,
75 | webSocket: handler)
76 | }
77 |
78 | var bodyString: String {
79 | get async throws {
80 | try await String(decoding: bodyData, as: UTF8.self)
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/WebSocket/WSCloseCode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WSCloseCode.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 04/03/2025.
6 | // Copyright © 2025 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 |
34 | public struct WSCloseCode: Sendable, Hashable {
35 | public var code: UInt16
36 | public var reason: String
37 |
38 | public init(_ code: UInt16) {
39 | self.code = code
40 | self.reason = ""
41 | }
42 | public init(_ code: UInt16, reason: String) {
43 | self.code = code
44 | self.reason = reason
45 | }
46 | }
47 |
48 | public extension WSCloseCode {
49 | // The following codes are based on:
50 | // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
51 |
52 | static let normalClosure = WSCloseCode(1000)
53 | static let goingAway = WSCloseCode(1001, reason: "Going Away")
54 | static let protocolError = WSCloseCode(1002, reason: "Protocol Error")
55 | static let unsupportedData = WSCloseCode(1003, reason: "Unsupported Data")
56 | static let noStatusReceived = WSCloseCode(1005, reason: "No Status Received")
57 | static let abnormalClosure = WSCloseCode(1006, reason: "Abnormal Closure")
58 | static let invalidFramePayload = WSCloseCode(1007, reason: "Invalid Frame Payload")
59 | static let policyViolation = WSCloseCode(1008, reason: "Policy Violation")
60 | static let messageTooBig = WSCloseCode(1009, reason: "Message Too Big")
61 | static let mandatoryExtensionMissing = WSCloseCode(1010, reason: "Mandatory Extension Missing")
62 | static let internalServerError = WSCloseCode(1011, reason: "Internal Server Error")
63 | static let serviceRestart = WSCloseCode(1012, reason: "Service Restart")
64 | static let tryAgainLater = WSCloseCode(1013, reason: "Try Again Later")
65 | static let badGateway = WSCloseCode(1014, reason: "Bad Gateway")
66 | static let tlsHandshakeFailure = WSCloseCode(1015, reason: "TLS Handshake Failure")
67 | }
68 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPHandler.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 14/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 |
34 | public protocol HTTPHandler: Sendable {
35 | func handleRequest(_ request: HTTPRequest) async throws -> HTTPResponse
36 | }
37 |
38 | public struct HTTPUnhandledError: LocalizedError {
39 | public let errorDescription: String? = "HTTPHandler can not handle the request."
40 | public init() { }
41 | }
42 |
43 | public extension HTTPHandler where Self == FileHTTPHandler {
44 | static func file(named: String, in bundle: Bundle = .main) -> FileHTTPHandler {
45 | FileHTTPHandler(named: named, in: bundle)
46 | }
47 | }
48 |
49 | public extension HTTPHandler where Self == DirectoryHTTPHandler {
50 | static func directory(for bundle: Bundle = .main, subPath: String = "", serverPath: String = "") -> DirectoryHTTPHandler {
51 | DirectoryHTTPHandler(bundle: bundle, subPath: subPath, serverPath: serverPath)
52 | }
53 | }
54 |
55 | public extension HTTPHandler where Self == RedirectHTTPHandler {
56 | static func redirect(to location: String) -> RedirectHTTPHandler {
57 | RedirectHTTPHandler(location: location)
58 | }
59 |
60 | static func redirect(via base: String, serverPath: String? = nil) -> RedirectHTTPHandler {
61 | RedirectHTTPHandler(base: base, serverPath: serverPath)
62 | }
63 | }
64 |
65 | public extension HTTPHandler where Self == ProxyHTTPHandler {
66 | static func proxy(via url: String) -> ProxyHTTPHandler {
67 | ProxyHTTPHandler(base: url)
68 | }
69 | }
70 |
71 | public extension HTTPHandler where Self == ClosureHTTPHandler {
72 | static func unhandled() -> ClosureHTTPHandler {
73 | ClosureHTTPHandler { _ in throw HTTPUnhandledError() }
74 | }
75 | }
76 |
77 | public extension HTTPHandler where Self == WebSocketHTTPHandler {
78 | static func webSocket(_ handler: some WSMessageHandler, frameSize: Int = 16384) -> WebSocketHTTPHandler {
79 | WebSocketHTTPHandler(handler: MessageFrameWSHandler(handler: handler, frameSize: frameSize))
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/FlyingFox/XCTests/HTTPResponse+Mock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPResponse+Mock.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 17/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | @testable import FlyingFox
33 | import FlyingSocks
34 | import Foundation
35 |
36 | extension HTTPResponse {
37 |
38 | static func make(version: HTTPVersion = .http11,
39 | statusCode: HTTPStatusCode = .ok,
40 | headers: [HTTPHeader: String] = [:],
41 | body: Data = Data()) -> Self {
42 | HTTPResponse(version: version,
43 | statusCode: statusCode,
44 | headers: headers,
45 | body: body)
46 | }
47 |
48 | static func makeChunked(version: HTTPVersion = .http11,
49 | statusCode: HTTPStatusCode = .ok,
50 | headers: [HTTPHeader: String] = [:],
51 | body: Data = Data(),
52 | chunkSize: Int = 5) -> Self {
53 | let consuming = ConsumingAsyncSequence(body)
54 | return HTTPResponse(
55 | version: version,
56 | statusCode: statusCode,
57 | headers: headers,
58 | body: HTTPBodySequence(from: consuming, suggestedBufferSize: chunkSize)
59 | )
60 | }
61 |
62 | static func make(version: HTTPVersion = .http11,
63 | statusCode: HTTPStatusCode = .ok,
64 | headers: [HTTPHeader: String] = [:],
65 | body: HTTPBodySequence) -> Self {
66 | HTTPResponse(version: version,
67 | statusCode: statusCode,
68 | headers: headers,
69 | body: body)
70 | }
71 |
72 | static func make(headers: [HTTPHeader: String] = [:],
73 | webSocket handler: some WSHandler) -> Self {
74 | HTTPResponse(headers: headers,
75 | webSocket: handler)
76 | }
77 |
78 | var bodyString: String? {
79 | get async throws {
80 | try await String(data: bodyData, encoding: .utf8)
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/FlyingFox/Tests/JSON/JSONBodyPatternTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JSONBodyPatternTests.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 15/08/2024.
6 | // Copyright © 2024 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import FlyingFox
33 | import Foundation
34 | import Testing
35 |
36 | struct JSONBodyPatternTests {
37 |
38 | @Test
39 | func pattern_MatchesJSONPath() async throws {
40 | // given
41 | let pattern = JSONBodyPattern { $0["$.name"] == "fish" }
42 |
43 | // when then
44 | #expect(pattern.evaluate(json: #"{"name": "fish"}"#))
45 | #expect(pattern.evaluate(json: #"{"id": 5, "name": "fish"}"#))
46 | #expect(!pattern.evaluate(json: #"{"name": "chips"}"#))
47 | #expect(!pattern.evaluate(json: #"{}"#))
48 | #expect(!pattern.evaluate(json: #""#))
49 | }
50 |
51 | @Test
52 | func route_MatchesJSONPath() async throws {
53 | // given
54 | let route = HTTPRoute(
55 | "POST /fish",
56 | jsonBody: { $0["$.food"] == "chips" }
57 | )
58 |
59 | // when
60 | var result = await route ~= .make(path: "fish", bodyJSON: #"{"food": "chips"}"#)
61 |
62 | // then
63 | #expect(result)
64 |
65 | // when
66 | result = await route ~= .make(path: "fish", bodyJSON: #"{"food": "shrimp"}"#)
67 |
68 | // then
69 | #expect(!result)
70 | }
71 | }
72 |
73 | private extension JSONBodyPattern {
74 |
75 | func evaluate(json: String) -> Bool {
76 | self.evaluate(Data(json.utf8))
77 | }
78 | }
79 |
80 | private extension HTTPRequest {
81 | static func make(method: HTTPMethod = .POST,
82 | version: HTTPVersion = .http11,
83 | path: String = "/",
84 | query: [QueryItem] = [],
85 | headers: [HTTPHeader: String] = [:],
86 | bodyJSON: String) -> Self {
87 | HTTPRequest(method: method,
88 | version: version,
89 | path: path,
90 | query: query,
91 | headers: headers,
92 | body: Data(bodyJSON.utf8))
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/FlyingSocks/Sources/Logging.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logging.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 19/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | public protocol Logging: Sendable {
33 | func logDebug(_ debug: @autoclosure () -> String)
34 | func logInfo(_ info: @autoclosure () -> String)
35 | func logWarning(_ warning: @autoclosure () -> String)
36 | func logError(_ error: @autoclosure () -> String)
37 | func logCritical(_ critical: @autoclosure () -> String)
38 | }
39 |
40 | public struct PrintLogger: Logging {
41 |
42 | let category: String
43 |
44 | public init(category: String) {
45 | self.category = category
46 | }
47 |
48 | public func logDebug(_ debug: @autoclosure () -> String) {
49 | Swift.print("[\(category)] debug: \(debug())")
50 | }
51 |
52 | public func logInfo(_ info: @autoclosure () -> String) {
53 | Swift.print("[\(category)] info: \(info())")
54 | }
55 |
56 | public func logWarning(_ warning: @autoclosure () -> String) {
57 | Swift.print("[\(category)] warning: \(warning())")
58 | }
59 |
60 | public func logError(_ error: @autoclosure () -> String) {
61 | Swift.print("[\(category)] error: \(error())")
62 | }
63 |
64 | public func logCritical(_ critical: @autoclosure () -> String) {
65 | Swift.print("[\(category)] critical: \(critical())")
66 | }
67 | }
68 |
69 | public struct DisabledLogger: Logging {
70 |
71 | public func logDebug(_ debug: @autoclosure () -> String) { }
72 |
73 | public func logInfo(_ info: @autoclosure () -> String) { }
74 |
75 | public func logWarning(_ warning: @autoclosure () -> String) { }
76 |
77 | public func logError(_ error: @autoclosure () -> String) { }
78 |
79 | public func logCritical(_ critical: @autoclosure () -> String) { }
80 | }
81 |
82 | public extension Logging where Self == PrintLogger {
83 |
84 | static func print(category: String) -> Self {
85 | PrintLogger(category: category)
86 | }
87 | }
88 |
89 | public extension Logging where Self == DisabledLogger {
90 |
91 | static var disabled: Self {
92 | DisabledLogger()
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/JSON/JSONPath.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JSONPath.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 29/05/2023.
6 | // Copyright © 2023 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Foundation
33 |
34 | public struct JSONPath {
35 |
36 | var components: [Component]
37 |
38 | enum Component: Equatable {
39 | case field(String)
40 | case array(Int)
41 | }
42 |
43 | init(components: [Component]) {
44 | self.components = components
45 | }
46 |
47 | public init(parsing path: String) throws {
48 | self.components = try Self.parseComponents(from: path)
49 | }
50 |
51 | private struct Error: LocalizedError {
52 | var errorDescription: String?
53 |
54 | init(_ description: String) {
55 | self.errorDescription = description
56 | }
57 | }
58 | }
59 |
60 | extension JSONPath {
61 |
62 | static func parseComponents(from path: String) throws -> [Component] {
63 | var scanner = Scanner(string: path)
64 | guard scanner.scanString("$") != nil else {
65 | throw Error("Expected $")
66 | }
67 |
68 | var comps = [Component]()
69 | while let comp = try scanComponent(from: &scanner) {
70 | comps.append(comp)
71 | }
72 | return comps
73 | }
74 |
75 | static func scanComponent(from scanner: inout Scanner) throws -> Component? {
76 | if scanner.scanString(".") != nil {
77 | guard let name = scanner.scanUpToCharacters(from: CharacterSet(charactersIn: ".[")) else {
78 | throw Error("Expected field name")
79 | }
80 | return .field(name)
81 | } else if scanner.scanString("[") != nil {
82 | guard let index = scanner.scanCharacters(from: CharacterSet(charactersIn: "0123456789")) else {
83 | throw Error("Expected index")
84 | }
85 | guard scanner.scanString("]") != nil else {
86 | throw Error("Expected ]")
87 | }
88 | return .array(Int(index)!)
89 | }
90 | guard scanner.isAtEnd else {
91 | throw Error("Expected end")
92 | }
93 | return nil
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPHeader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPHeader.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 13/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | public struct HTTPHeader: Sendable, RawRepresentable, Hashable {
33 | public var rawValue: String
34 |
35 | public init(rawValue: String) {
36 | self.rawValue = rawValue
37 | }
38 |
39 | public init(_ rawValue: String) {
40 | self.init(rawValue: rawValue)
41 | }
42 |
43 | public func hash(into hasher: inout Hasher) {
44 | rawValue.lowercased().hash(into: &hasher)
45 | }
46 |
47 | public static func == (lhs: HTTPHeader, rhs: HTTPHeader) -> Bool {
48 | lhs.rawValue.caseInsensitiveCompare(rhs.rawValue) == .orderedSame
49 | }
50 | }
51 |
52 | public extension HTTPHeader {
53 | static let acceptRanges = HTTPHeader("Accept-Ranges")
54 | static let authorization = HTTPHeader("Authorization")
55 | static let cookie = HTTPHeader("Cookie")
56 | static let connection = HTTPHeader("Connection")
57 | static let contentDisposition = HTTPHeader("Content-Disposition")
58 | static let contentEncoding = HTTPHeader("Content-Encoding")
59 | static let contentLength = HTTPHeader("Content-Length")
60 | static let contentRange = HTTPHeader("Content-Range")
61 | static let contentType = HTTPHeader("Content-Type")
62 | static let date = HTTPHeader("Date")
63 | static let eTag = HTTPHeader("ETag")
64 | static let host = HTTPHeader("Host")
65 | static let location = HTTPHeader("Location")
66 | static let range = HTTPHeader("Range")
67 | static let setCookie = HTTPHeader("Set-Cookie")
68 | static let transferEncoding = HTTPHeader("Transfer-Encoding")
69 | static let upgrade = HTTPHeader("Upgrade")
70 | static let webSocketAccept = HTTPHeader("Sec-WebSocket-Accept")
71 | static let webSocketKey = HTTPHeader("Sec-WebSocket-Key")
72 | static let webSocketVersion = HTTPHeader("Sec-WebSocket-Version")
73 | static let xForwardedFor = HTTPHeader("X-Forwarded-For")
74 | }
75 |
--------------------------------------------------------------------------------
/FlyingFox/Sources/HTTPMethod.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPMethod.swift
3 | // FlyingFox
4 | //
5 | // Created by Simon Whitty on 17/02/2022.
6 | // Copyright © 2022 Simon Whitty. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/swhitty/FlyingFox
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | public struct HTTPMethod: Sendable, RawRepresentable, Hashable, ExpressibleByStringLiteral {
33 | public var rawValue: String
34 |
35 | public init(rawValue: String) {
36 | self.rawValue = rawValue
37 | }
38 |
39 | public init(_ rawValue: String) {
40 | self.init(rawValue: rawValue.uppercased())
41 | }
42 |
43 | public init(stringLiteral value: String) {
44 | self.init(rawValue: value.uppercased())
45 | }
46 | }
47 |
48 | public extension HTTPMethod {
49 | func hash(into hasher: inout Hasher) {
50 | rawValue.uppercased().hash(into: &hasher)
51 | }
52 |
53 | static func == (lhs: Self, rhs: Self) -> Bool {
54 | return lhs.rawValue.uppercased() == rhs.rawValue.uppercased()
55 | }
56 | }
57 |
58 | public extension HTTPMethod {
59 | internal static let sortedMethods = [
60 | HTTPMethod.GET,
61 | .POST,
62 | .PUT,
63 | .DELETE,
64 | .PATCH,
65 | .HEAD,
66 | .OPTIONS,
67 | .CONNECT,
68 | .TRACE
69 | ]
70 |
71 | static let allMethods = Set(HTTPMethod.sortedMethods)
72 |
73 | static let GET = HTTPMethod("GET")
74 | static let POST = HTTPMethod("POST")
75 | static let PUT = HTTPMethod("PUT")
76 | static let DELETE = HTTPMethod("DELETE")
77 | static let PATCH = HTTPMethod("PATCH")
78 | static let HEAD = HTTPMethod("HEAD")
79 | static let OPTIONS = HTTPMethod("OPTIONS")
80 | static let CONNECT = HTTPMethod("CONNECT")
81 | static let TRACE = HTTPMethod("TRACE")
82 | }
83 |
84 | public extension Set {
85 |
86 | /// Comma delimited string of methods, sorted to ensure default methods appear first.
87 | var stringValue: String {
88 | var sortedMethods = HTTPMethod
89 | .sortedMethods
90 | .filter { contains($0) }
91 |
92 | sortedMethods.append(contentsOf: self.filter { !HTTPMethod.allMethods.contains($0) })
93 | return sortedMethods.map(\.rawValue).joined(separator: ",")
94 | }
95 | }
96 |
--------------------------------------------------------------------------------