├── .github └── FUNDING.yml ├── .gitignore ├── .swift-format ├── .swift-version ├── Benchmarks ├── .gitignore ├── Benchmarks │ ├── Benchmarks │ │ └── Benchmarks.swift │ ├── Destiny │ │ └── Destiny.swift │ ├── Hummingbird │ │ └── Hummingbird.swift │ ├── Latency │ │ └── main.swift │ ├── Run │ │ └── main.swift │ ├── UnitTests │ │ └── UnitTests.swift │ ├── Utilities │ │ └── Utilities.swift │ └── Vapor │ │ └── Vapor.swift ├── Package.swift ├── results_destiny.txt ├── results_hummingbird.txt └── results_vapor.txt ├── LICENSE ├── Package.swift ├── README.md ├── SECURITY.md ├── Sources ├── Destiny │ ├── Destiny.swift │ ├── Epoll.swift │ ├── Server.swift │ ├── Socket.swift │ ├── SocketAcceptor.swift │ └── commands │ │ └── StopCommand.swift ├── DestinyBlueprint │ ├── ApplicationProtocol.swift │ ├── DestinyBlueprint.swift │ ├── HTTPCookieProtocol.swift │ ├── HTTPDateFormat.swift │ ├── HTTPHeadersProtocol.swift │ ├── HTTPMediaTypeProtocol.swift │ ├── HTTPMessageProtocol.swift │ ├── HTTPRequestHeadersProtocol.swift │ ├── HTTPRequestMethodProtocol.swift │ ├── HTTPRequestProtocol.swift │ ├── HTTPResponseHeadersProtocol.swift │ ├── HTTPResponseStatus.swift │ ├── HTTPRouterProtocol.swift │ ├── HTTPServerProtocol.swift │ ├── HTTPSocketProtocol.swift │ ├── HTTPStartLineProtocol.swift │ ├── HTTPVersion.swift │ ├── InlineArrayProtocol.swift │ ├── InlineCollectionProtocol.swift │ ├── InlineSequenceProtocol.swift │ ├── InlineVLArray.swift │ ├── PathComponent.swift │ ├── ResponseBodyProtocol.swift │ ├── SocketProtocol.swift │ ├── errors │ │ ├── DestinyErrorProtocol.swift │ │ ├── EpollError.swift │ │ ├── MiddlewareError.swift │ │ ├── ServerError.swift │ │ └── SocketError.swift │ ├── extensions │ │ ├── InlineArrayExtensions.swift │ │ ├── IntExtensions.swift │ │ ├── StringExtensions.swift │ │ └── simd │ │ │ ├── SIMDExtensions+DropTrailing.swift │ │ │ ├── SIMDExtensions+HasPrefix.swift │ │ │ ├── SIMDExtensions+KeepLeading.swift │ │ │ ├── SIMDExtensions+KeepTrailing.swift │ │ │ ├── SIMDExtensions+LeadingNonByteCount.swift │ │ │ ├── SIMDExtensions+LeadingNonzeroByteCount.swift │ │ │ ├── SIMDExtensions+LeadingString.swift │ │ │ ├── SIMDExtensions+Lowercased.swift │ │ │ ├── SIMDExtensions+Split.swift │ │ │ ├── SIMDExtensions+TrailingNonzeroByteCount.swift │ │ │ ├── SIMDExtensions+TrailingString.swift │ │ │ ├── SIMDExtensions+TrailingZeroByteCount.swift │ │ │ └── StackStrings.swift │ ├── middleware │ │ ├── CORSMiddlewareAllowedOrigin.swift │ │ ├── CORSMiddlewareProtocol.swift │ │ ├── DynamicMetricMiddlewareProtocol.swift │ │ ├── DynamicMiddlewareProtocol.swift │ │ ├── FileMiddlewareProtocol.swift │ │ ├── MetricMiddlewareProtocol.swift │ │ ├── MiddlewareProtocol.swift │ │ ├── RateLimitMiddlewareProtocol.swift │ │ └── storage │ │ │ └── StaticMiddlewareStorageProtocol.swift │ ├── responders │ │ ├── ConditionalRouteResponderProtocol.swift │ │ ├── DynamicRouteResponderProtocol.swift │ │ ├── ErrorResponderProtocol.swift │ │ ├── RouteResponderProtocol.swift │ │ └── StaticRouteResponderProtocol.swift │ ├── routes │ │ ├── DynamicRequestTimestamps.swift │ │ ├── DynamicResponseProtocol.swift │ │ ├── RedirectionRouteProtocol.swift │ │ └── RouteProtocol.swift │ └── storage │ │ ├── DynamicResponderStorageProtocol.swift │ │ ├── RouterResponderStorageProtocol.swift │ │ └── StaticResponderStorageProtocol.swift ├── DestinyDefaults │ ├── Application.swift │ ├── Charset.swift │ ├── DestinyDefaults.swift │ ├── HTTPRequestHeaders.swift │ ├── HTTPRequestMethod.swift │ ├── HTTPResponseHeaders.swift │ ├── HTTPResponseMessage.swift │ ├── HTTPRouter.swift │ ├── HTTPStartLine.swift │ ├── Request.swift │ ├── RouteGroup.swift │ ├── RouteGroupProtocol.swift │ ├── commands │ │ └── BootCommands.swift │ ├── cookies │ │ ├── HTTPCookie.swift │ │ ├── HTTPCookieFlag.swift │ │ └── HTTPCookieStorageProtocol.swift │ ├── extensions │ │ ├── SequenceExtensions.swift │ │ ├── SwiftCompressionExtensions.swift │ │ └── UnsafePointerExtensions.swift │ ├── headers │ │ ├── HTTPRequestHeader.swift │ │ └── HTTPResponseHeader.swift │ ├── mediaTypes │ │ ├── HTTPMediaTypes+Application.swift │ │ ├── HTTPMediaTypes+Audio.swift │ │ ├── HTTPMediaTypes+Font.swift │ │ ├── HTTPMediaTypes+Haptics.swift │ │ ├── HTTPMediaTypes+Image.swift │ │ ├── HTTPMediaTypes+Message.swift │ │ ├── HTTPMediaTypes+Model.swift │ │ ├── HTTPMediaTypes+Multipart.swift │ │ ├── HTTPMediaTypes+Text.swift │ │ ├── HTTPMediaTypes+Video.swift │ │ └── HTTPMediaTypes.swift │ ├── middleware │ │ ├── DynamicCORSMiddleware.swift │ │ ├── DynamicDateMiddleware.swift │ │ ├── DynamicMiddleware.swift │ │ ├── DynamicRateLimitMiddleware.swift │ │ ├── StaticFileMiddleware.swift │ │ ├── StaticMiddleware.swift │ │ ├── StaticMiddlewareProtocol.swift │ │ └── storage │ │ │ └── CompiledStaticMiddlewareStorage.swift │ ├── routes │ │ ├── ConditionalRouteResponder.swift │ │ ├── DynamicResponse.swift │ │ ├── DynamicRoute.swift │ │ ├── DynamicRouteProtocol.swift │ │ ├── DynamicRouteResponder.swift │ │ ├── StaticErrorResponder.swift │ │ ├── StaticRedirectionRoute.swift │ │ ├── StaticRoute.swift │ │ ├── StaticRouteProtocol.swift │ │ ├── responses │ │ │ ├── RouteResponses+FoundationData.swift │ │ │ ├── RouteResponses+MacroExpansion.swift │ │ │ ├── RouteResponses+MacroExpansionWithDateHeader.swift │ │ │ ├── RouteResponses+StaticString.swift │ │ │ ├── RouteResponses+StaticStringWithDateHeader.swift │ │ │ ├── RouteResponses+String.swift │ │ │ ├── RouteResponses+StringWithDateHeader.swift │ │ │ ├── RouteResponses+UInt16Array.swift │ │ │ ├── RouteResponses+UInt8Array.swift │ │ │ └── RouteResponses.swift │ │ └── results │ │ │ ├── ResponseBody+Bytes.swift │ │ │ ├── ResponseBody+Data.swift │ │ │ ├── ResponseBody+MacroExpansion.swift │ │ │ ├── ResponseBody+MacroExpansionWithDateHeader.swift │ │ │ ├── ResponseBody+StaticString.swift │ │ │ ├── ResponseBody+StaticStringWithDateHeader.swift │ │ │ ├── ResponseBody+String.swift │ │ │ ├── ResponseBody+StringWithDateHeader.swift │ │ │ ├── ResponseBody.swift │ │ │ └── ResponseBodyValueProtocol.swift │ └── storage │ │ ├── DynamicResponderStorage.swift │ │ ├── RouterResponderStorage.swift │ │ ├── StaticResponderStorage.swift │ │ └── compiled │ │ ├── CompiledDynamicResponderStorage.swift │ │ └── CompiledStaticResponderStorage.swift ├── DestinyMacros │ ├── DestinyMacros.swift │ ├── HTTPMessage.swift │ └── Router.swift ├── DestinyUtilityMacros │ ├── DestinyUtilityMacros.swift │ ├── HTTPFieldContentType.swift │ ├── HTTPMediaTypeMacro.swift │ ├── HTTPRequestMethods.swift │ ├── HTTPResponseStatusMacro.swift │ ├── HTTPResponseStatusesMacro.swift │ └── InlineArrayMacro.swift └── Run │ └── main.swift ├── Tests └── DestinyTests │ ├── DestinyTests.swift │ ├── HTTPDateFormatTests.swift │ └── SIMDTests.swift └── script.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: RandomHashTags 2 | thanks_dev: d/gh/randomhashtags 3 | buy_me_a_coffee: randomhashtags -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .build 3 | Packages 4 | xcuserdata 5 | DerivedData 6 | .swiftpm 7 | .netrc 8 | .vscode 9 | Package.resolved -------------------------------------------------------------------------------- /.swift-format: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "lineLength": 200, 4 | "indentation" : { 5 | "spaces": 4 6 | }, 7 | "indentBlankLines": false, 8 | "indentSwitchCaseLabels": false, 9 | "lineBreakBeforeControlFlowKeywords": false, 10 | "lineBreakBeforeEachArgument": false, 11 | "respectsExistingLineBreaks": true, 12 | "multiElementCollectionTrailingCommas": true, 13 | "spacesAroundRangeFormationOperators": false, 14 | "rules" : { 15 | "AllPublicDeclarationsHaveDocumentation" : true, 16 | "AlwaysUseLowerCamelCase" : true, 17 | "DoNotUseSemicolons": true, 18 | "NeverUseForceTry" : true, 19 | "NoAccessLevelOnExtensionDeclaration": true, 20 | "NoBlockComments": true, 21 | "OneCasePerLine" : true, 22 | "OneVariableDeclarationPerLine" : false, 23 | "OrderedImports" : true, 24 | "ReturnVoidInsteadOfEmptyTuple": true, 25 | "TypeNamesShouldBeCapitalized" : true, 26 | "UseEarlyExits" : true, 27 | "UseShorthandTypeNames" : true, 28 | "UseTripleSlashForDocumentationComments" : true 29 | } 30 | } -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | main-snapshot-2025-06-09 -------------------------------------------------------------------------------- /Benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm 7 | .swiftpm 8 | .netrc 9 | .vscode 10 | Package.resolved 11 | __BenchmarkBoilerplate.d 12 | __BenchmarkBoilerplate.o 13 | __BenchmarkBoilerplate.swiftdeps 14 | Current_run.jmh.json -------------------------------------------------------------------------------- /Benchmarks/Benchmarks/Benchmarks/Benchmarks.swift: -------------------------------------------------------------------------------- 1 | 2 | import Benchmark 3 | import Destiny 4 | import Utilities 5 | 6 | import HTTPTypes 7 | 8 | import TestDestiny 9 | import TestHummingbird 10 | import TestVapor 11 | 12 | let benchmarks = { 13 | Benchmark.defaultConfiguration = .init(metrics: .all) 14 | 15 | /*Benchmark("HTTPFieldName") { 16 | for _ in $0.scaledIterations { 17 | blackHole(HTTPField.Name.contentType.rawNameString) 18 | } 19 | }*/ 20 | 21 | Benchmark("HTTPRequestHeader") { 22 | for _ in $0.scaledIterations { 23 | blackHole(HTTPRequestHeader.contentType.rawNameString) 24 | } 25 | } 26 | 27 | /*let libraries:[String:UInt16] = [ 28 | "Destiny" : 8080, 29 | //"Hummingbird" : 8081, 30 | //"Vapor" : 8082 31 | ] 32 | for (library, port) in libraries { 33 | //let request:HTTPClientRequest = HTTPClientRequest(url: "http://192.168.1.96:\(port)/test") 34 | Benchmark(library) { 35 | for _ in $0.scaledIterations { 36 | blackHole(make_request(port: port)) 37 | } 38 | } 39 | }*/ 40 | /*Benchmark("SIMD8") { 41 | for _ in $0.scaledIterations { 42 | blackHole(stackString()) 43 | } 44 | } 45 | Benchmark("StaticString") { 46 | for _ in $0.scaledIterations { 47 | blackHole(StaticString("HlloWRLD")) 48 | } 49 | } 50 | Benchmark("String") { 51 | for _ in $0.scaledIterations { 52 | blackHole(string()) 53 | } 54 | } 55 | func stackString() -> SIMD8 { 56 | return SIMD8(buffer: ( 57 | Int8(Character("H").asciiValue!), 58 | Int8(Character("l").asciiValue!), 59 | Int8(Character("l").asciiValue!), 60 | Int8(Character("o").asciiValue!), 61 | Int8(Character("W").asciiValue!), 62 | Int8(Character("R").asciiValue!), 63 | Int8(Character("L").asciiValue!), 64 | Int8(Character("D").asciiValue!) 65 | )) 66 | } 67 | func string() -> String { 68 | var string:String = "Hllo" 69 | string += "WRLD" 70 | return string 71 | }*/ 72 | } -------------------------------------------------------------------------------- /Benchmarks/Benchmarks/Destiny/Destiny.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Destiny.swift 3 | // 4 | // 5 | // Created by Evan Anderson on 10/18/24. 6 | // -------------------------------------------------------------------------------- /Benchmarks/Benchmarks/Hummingbird/Hummingbird.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Hummingbird.swift 3 | // 4 | // 5 | // Created by Evan Anderson on 10/18/24. 6 | // -------------------------------------------------------------------------------- /Benchmarks/Benchmarks/Latency/main.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | let libraries:[String:UInt16] = [ 5 | "Destiny" : 8080, 6 | //"Hummingbird" : 8081, 7 | //"Vapor" : 8082 8 | ] 9 | 10 | /* 11 | let clock:ContinuousClock = ContinuousClock() 12 | for (library, port) in libraries.shuffled() { 13 | var request:URLRequest = URLRequest(url: URL(string: "http://192.168.1.96:\(port)/test")!) 14 | request.httpMethod = "GET" 15 | request.timeoutInterval = 60*/ 16 | //request.addValue("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", forHTTPHeaderField: "Accept") 17 | /*request.addValue("en-US,en;q=0.9", forHTTPHeaderField: "Accept-Language") 18 | request.addValue("max-age=0", forHTTPHeaderField: "Cache-Control") 19 | request.addValue("http://192.168.1.96:\(port)", forHTTPHeaderField: "Host") 20 | request.addValue("gzip, deflate", forHTTPHeaderField: "Accept-Encoding") 21 | request.addValue("keep-alive", forHTTPHeaderField: "Connection") 22 | request.addValue("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", forHTTPHeaderField: "User-Agent") 23 | let amount:Int = 5_000 24 | var latencies:[ContinuousClock.Duration] = [] 25 | latencies.reserveCapacity(amount) 26 | try await withThrowingTaskGroup(of: ContinuousClock.Duration.self) { group in 27 | for _ in 0...size)) 20 | if connection == 0 { 21 | var request:String = "GET /test HTTP/1.1\r\n" 22 | request.withUTF8 { p in 23 | send(socket, p.baseAddress!, p.count, 0) 24 | } 25 | var response:UnsafeMutableRawPointer = .allocate(byteCount: 1024, alignment: MemoryLayout.alignment) 26 | let read:Int = recv(socket, &response, 1024, 0) 27 | response.deallocate() 28 | } 29 | close(socket) 30 | }) 31 | }*/ -------------------------------------------------------------------------------- /Benchmarks/Benchmarks/Vapor/Vapor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Vapor.swift 3 | // 4 | // 5 | // Created by Evan Anderson on 10/18/24. 6 | // -------------------------------------------------------------------------------- /Benchmarks/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.10 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Benchmarks", 8 | platforms: [ 9 | .macOS(.v14) 10 | ], 11 | dependencies: [ 12 | .package(url: "https://github.com/ordo-one/package-benchmark", from: "1.29.2"), 13 | 14 | // networking 15 | .package(url: "https://github.com/swift-server/swift-service-lifecycle", from: "2.7.0"), 16 | .package(url: "https://github.com/apple/swift-nio", from: "2.82.0"), 17 | .package(url: "https://github.com/apple/swift-log", from: "1.6.3"), 18 | 19 | .package(url: "https://github.com/swift-server/async-http-client", from: "1.23.1"), 20 | 21 | //.package(name: "destiny", path: "../"), 22 | .package(url: "https://github.com/vapor/vapor", exact: "4.114.1"), 23 | .package(url: "https://github.com/hummingbird-project/hummingbird", exact: "2.11.1") 24 | ], 25 | targets: [ 26 | .target( 27 | name: "Utilities", 28 | dependencies: [ 29 | .product(name: "ServiceLifecycle", package: "swift-service-lifecycle"), 30 | .product(name: "NIOCore", package: "swift-nio"), 31 | .product(name: "Logging", package: "swift-log"), 32 | .product(name: "AsyncHTTPClient", package: "async-http-client") 33 | ], 34 | path: "Benchmarks/Utilities" 35 | ), 36 | 37 | .testTarget( 38 | name: "UnitTests", 39 | dependencies: [ 40 | "Utilities" 41 | ], 42 | path: "Benchmarks/UnitTests" 43 | ), 44 | 45 | .target( 46 | name: "TestDestiny", 47 | dependencies: [ 48 | //.product(name: "Destiny", package: "destiny") 49 | ], 50 | path: "Benchmarks/Destiny" 51 | ), 52 | .target( 53 | name: "TestHummingbird", 54 | dependencies: [ 55 | .product(name: "Hummingbird", package: "hummingbird") 56 | ], 57 | path: "Benchmarks/Hummingbird" 58 | ), 59 | .target( 60 | name: "TestVapor", 61 | dependencies: [ 62 | .product(name: "Vapor", package: "vapor") 63 | ], 64 | path: "Benchmarks/Vapor" 65 | ), 66 | 67 | .executableTarget( 68 | name: "Run", 69 | dependencies: [ 70 | "Utilities", 71 | "TestDestiny", 72 | "TestHummingbird", 73 | "TestVapor" 74 | ], 75 | path: "Benchmarks/Run" 76 | ), 77 | /*.executableTarget( 78 | name: "Latency", 79 | dependencies: [ 80 | "Utilities" 81 | ], 82 | path: "Benchmarks/Latency" 83 | ),*/ 84 | 85 | /*.executableTarget( 86 | name: "Benchmarks", 87 | dependencies: [ 88 | "Utilities", 89 | 90 | "TestDestiny", 91 | "TestHummingbird", 92 | "TestVapor", 93 | .product(name: "Benchmark", package: "package-benchmark") 94 | ], 95 | path: "Benchmarks/Benchmarks", 96 | plugins: [ 97 | .plugin(name: "BenchmarkPlugin", package: "package-benchmark") 98 | ] 99 | )*/ 100 | ] 101 | ) 102 | -------------------------------------------------------------------------------- /Benchmarks/results_destiny.txt: -------------------------------------------------------------------------------- 1 | 2 | /\ Grafana /‾‾/ 3 | /\ / \ |\ __ / / 4 | / \/ \ | |/ / / ‾‾\ 5 | / \ | ( | (‾) | 6 | / __________ \ |_|\_\ \_____/ 7 | 8 | execution: local 9 | script: script.js 10 | output: - 11 | 12 | scenarios: (100.00%) 1 scenario, 10000 max VUs, 40s max duration (incl. graceful stop): 13 | * default: 10000 looping VUs for 10s (gracefulStop: 30s) 14 | 15 | 16 | running (00.8s), 10000/10000 VUs, 0 complete and 0 interrupted iterations 17 | default [ 8% ] 10000 VUs 00.8s/10s 18 | 19 | running (01.8s), 10000/10000 VUs, 10000 complete and 0 interrupted iterations 20 | default [ 18% ] 10000 VUs 01.8s/10s 21 | 22 | running (02.8s), 10000/10000 VUs, 20000 complete and 0 interrupted iterations 23 | default [ 28% ] 10000 VUs 02.8s/10s 24 | 25 | running (03.8s), 10000/10000 VUs, 30000 complete and 0 interrupted iterations 26 | default [ 38% ] 10000 VUs 03.8s/10s 27 | 28 | running (04.8s), 10000/10000 VUs, 40000 complete and 0 interrupted iterations 29 | default [ 48% ] 10000 VUs 04.8s/10s 30 | 31 | running (05.8s), 10000/10000 VUs, 50000 complete and 0 interrupted iterations 32 | default [ 58% ] 10000 VUs 05.8s/10s 33 | 34 | running (06.8s), 10000/10000 VUs, 60000 complete and 0 interrupted iterations 35 | default [ 68% ] 10000 VUs 06.8s/10s 36 | 37 | running (07.8s), 10000/10000 VUs, 70000 complete and 0 interrupted iterations 38 | default [ 78% ] 10000 VUs 07.8s/10s 39 | 40 | running (08.8s), 10000/10000 VUs, 80000 complete and 0 interrupted iterations 41 | default [ 88% ] 10000 VUs 08.8s/10s 42 | 43 | running (09.8s), 10000/10000 VUs, 90000 complete and 0 interrupted iterations 44 | default [ 98% ] 10000 VUs 09.8s/10s 45 | 46 | data_received..................: 33 MB 3.1 MB/s 47 | data_sent......................: 8.8 MB 830 kB/s 48 | http_req_blocked...............: avg=16.11ms min=30.96µs med=287.22µs max=252.36ms p(90)=41.77ms p(95)=110.54ms 49 | http_req_connecting............: avg=15.51ms min=22.36µs med=149.16µs max=252.35ms p(90)=41.05ms p(95)=109.78ms 50 | http_req_duration..............: avg=11.34ms min=34.32µs med=1.61ms max=195.19ms p(90)=45.84ms p(95)=50.2ms 51 | { expected_response:true }...: avg=11.34ms min=34.32µs med=1.61ms max=195.19ms p(90)=45.84ms p(95)=50.2ms 52 | http_req_failed................: 0.00% 0 out of 100000 53 | http_req_receiving.............: avg=1.26ms min=6.09µs med=111.78µs max=47.08ms p(90)=4.28ms p(95)=4.98ms 54 | http_req_sending...............: avg=1.39ms min=3.25µs med=109.49µs max=97.27ms p(90)=3.49ms p(95)=5.27ms 55 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s 56 | http_req_waiting...............: avg=8.68ms min=21.5µs med=1.13ms max=169.61ms p(90)=38.73ms p(95)=42.48ms 57 | http_reqs......................: 100000 9428.3243/s 58 | iteration_duration.............: avg=1.03s min=1s med=1s max=1.3s p(90)=1.09s p(95)=1.16s 59 | iterations.....................: 100000 9428.3243/s 60 | vus............................: 10000 min=10000 max=10000 61 | vus_max........................: 10000 min=10000 max=10000 62 | 63 | 64 | running (10.6s), 00000/10000 VUs, 100000 complete and 0 interrupted iterations 65 | default ✓ [ 100% ] 10000 VUs 10s 66 | -------------------------------------------------------------------------------- /Benchmarks/results_hummingbird.txt: -------------------------------------------------------------------------------- 1 | 2 | /\ Grafana /‾‾/ 3 | /\ / \ |\ __ / / 4 | / \/ \ | |/ / / ‾‾\ 5 | / \ | ( | (‾) | 6 | / __________ \ |_|\_\ \_____/ 7 | 8 | execution: local 9 | script: script.js 10 | output: - 11 | 12 | scenarios: (100.00%) 1 scenario, 10000 max VUs, 40s max duration (incl. graceful stop): 13 | * default: 10000 looping VUs for 10s (gracefulStop: 30s) 14 | 15 | 16 | running (00.8s), 10000/10000 VUs, 0 complete and 0 interrupted iterations 17 | default [ 8% ] 10000 VUs 00.8s/10s 18 | 19 | running (01.8s), 10000/10000 VUs, 2916 complete and 0 interrupted iterations 20 | default [ 18% ] 10000 VUs 01.8s/10s 21 | 22 | running (02.8s), 10000/10000 VUs, 7915 complete and 0 interrupted iterations 23 | default [ 28% ] 10000 VUs 02.8s/10s 24 | 25 | running (03.8s), 10000/10000 VUs, 13810 complete and 0 interrupted iterations 26 | default [ 38% ] 10000 VUs 03.8s/10s 27 | 28 | running (05.0s), 10000/10000 VUs, 20751 complete and 0 interrupted iterations 29 | default [ 50% ] 10000 VUs 05.0s/10s 30 | 31 | running (07.2s), 10000/10000 VUs, 25705 complete and 0 interrupted iterations 32 | default [ 72% ] 10000 VUs 07.2s/10s 33 | 34 | running (08.1s), 10000/10000 VUs, 26818 complete and 0 interrupted iterations 35 | default [ 81% ] 10000 VUs 08.1s/10s 36 | 37 | running (09.5s), 10000/10000 VUs, 28127 complete and 0 interrupted iterations 38 | default [ 95% ] 10000 VUs 09.5s/10s 39 | 40 | running (09.8s), 10000/10000 VUs, 28561 complete and 0 interrupted iterations 41 | default [ 98% ] 10000 VUs 09.8s/10s 42 | 43 | running (10.8s), 08574/10000 VUs, 30066 complete and 0 interrupted iterations 44 | default ↓ [ 100% ] 10000 VUs 10s 45 | 46 | data_received..................: 10 MB 946 kB/s 47 | data_sent......................: 2.5 MB 227 kB/s 48 | http_req_blocked...............: avg=489.78ms min=0s med=2.82ms max=8.16s p(90)=1.08s p(95)=2.06s 49 | http_req_connecting............: avg=489.43ms min=0s med=2.67ms max=8.16s p(90)=1.08s p(95)=2.06s 50 | http_req_duration..............: avg=218.4ms min=0s med=15.37ms max=7.85s p(90)=806.44ms p(95)=1.23s 51 | { expected_response:true }...: avg=297.85ms min=117.13µs med=31.18ms max=7.85s p(90)=874.06ms p(95)=1.37s 52 | http_req_failed................: 26.67% 10306 out of 38635 53 | http_req_receiving.............: avg=5.2ms min=0s med=31.42µs max=965.36ms p(90)=3.02ms p(95)=7.42ms 54 | http_req_sending...............: avg=3.58ms min=0s med=62.75µs max=1.1s p(90)=4.58ms p(95)=12.68ms 55 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s 56 | http_req_waiting...............: avg=209.6ms min=0s med=13.41ms max=7.83s p(90)=663.76ms p(95)=1.09s 57 | http_reqs......................: 38635 3523.607201/s 58 | iteration_duration.............: avg=2.6s min=1s med=2.05s max=10.47s p(90)=5.34s p(95)=5.95s 59 | iterations.....................: 38635 3523.607201/s 60 | vus............................: 8585 min=8585 max=10000 61 | vus_max........................: 10000 min=10000 max=10000 62 | 63 | 64 | running (11.0s), 00000/10000 VUs, 38635 complete and 0 interrupted iterations 65 | default ✓ [ 100% ] 10000 VUs 10s 66 | -------------------------------------------------------------------------------- /Benchmarks/results_vapor.txt: -------------------------------------------------------------------------------- 1 | 2 | /\ Grafana /‾‾/ 3 | /\ / \ |\ __ / / 4 | / \/ \ | |/ / / ‾‾\ 5 | / \ | ( | (‾) | 6 | / __________ \ |_|\_\ \_____/ 7 | 8 | execution: local 9 | script: script.js 10 | output: - 11 | 12 | scenarios: (100.00%) 1 scenario, 10000 max VUs, 40s max duration (incl. graceful stop): 13 | * default: 10000 looping VUs for 10s (gracefulStop: 30s) 14 | 15 | 16 | running (00.8s), 10000/10000 VUs, 0 complete and 0 interrupted iterations 17 | default [ 8% ] 10000 VUs 00.8s/10s 18 | 19 | running (01.8s), 10000/10000 VUs, 6782 complete and 0 interrupted iterations 20 | default [ 18% ] 10000 VUs 01.8s/10s 21 | 22 | running (03.0s), 10000/10000 VUs, 14453 complete and 0 interrupted iterations 23 | default [ 30% ] 10000 VUs 03.0s/10s 24 | 25 | running (03.8s), 10000/10000 VUs, 17467 complete and 0 interrupted iterations 26 | default [ 38% ] 10000 VUs 03.8s/10s 27 | 28 | running (04.8s), 10000/10000 VUs, 21002 complete and 0 interrupted iterations 29 | default [ 48% ] 10000 VUs 04.8s/10s 30 | 31 | running (06.4s), 10000/10000 VUs, 25313 complete and 0 interrupted iterations 32 | default [ 64% ] 10000 VUs 06.4s/10s 33 | 34 | running (06.8s), 10000/10000 VUs, 26292 complete and 0 interrupted iterations 35 | default [ 68% ] 10000 VUs 06.8s/10s 36 | 37 | running (07.8s), 10000/10000 VUs, 27697 complete and 0 interrupted iterations 38 | default [ 78% ] 10000 VUs 07.8s/10s 39 | 40 | running (08.9s), 10000/10000 VUs, 28281 complete and 0 interrupted iterations 41 | default [ 89% ] 10000 VUs 08.9s/10s 42 | 43 | running (09.8s), 10000/10000 VUs, 28451 complete and 0 interrupted iterations 44 | default [ 98% ] 10000 VUs 09.8s/10s 45 | 46 | running (10.8s), 00083/10000 VUs, 38451 complete and 0 interrupted iterations 47 | default ↓ [ 100% ] 10000 VUs 10s 48 | 49 | data_received..................: 11 MB 998 kB/s 50 | data_sent......................: 2.5 MB 225 kB/s 51 | http_req_blocked...............: avg=259.03ms min=0s med=14.66ms max=3.12s p(90)=1.03s p(95)=1.11s 52 | http_req_connecting............: avg=256.87ms min=0s med=13.19ms max=3.09s p(90)=1.03s p(95)=1.11s 53 | http_req_duration..............: avg=99.81ms min=0s med=7.18ms max=2.62s p(90)=315.86ms p(95)=574.66ms 54 | { expected_response:true }...: avg=136.23ms min=82.86µs med=16.9ms max=2.62s p(90)=425.03ms p(95)=754.37ms 55 | http_req_failed................: 26.73% 10302 out of 38534 56 | http_req_receiving.............: avg=5.75ms min=0s med=54.04µs max=1.11s p(90)=6.33ms p(95)=14.77ms 57 | http_req_sending...............: avg=8.03ms min=0s med=95.85µs max=1.11s p(90)=7.7ms p(95)=14.81ms 58 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s 59 | http_req_waiting...............: avg=86.02ms min=0s med=4.21ms max=1.82s p(90)=305.1ms p(95)=523.6ms 60 | http_reqs......................: 38534 3491.934152/s 61 | iteration_duration.............: avg=2.34s min=1s med=1.53s max=6.57s p(90)=4.84s p(95)=5.63s 62 | iterations.....................: 38534 3491.934152/s 63 | vus............................: 83 min=83 max=10000 64 | vus_max........................: 10000 min=10000 max=10000 65 | 66 | 67 | running (11.0s), 00000/10000 VUs, 38534 complete and 0 interrupted iterations 68 | default ✓ [ 100% ] 10000 VUs 10s 69 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 1.0.0 | :white_check_mark: | 8 | 9 | ## Reporting a Vulnerability 10 | 11 | File a GitHub [vulnerability report](https://github.com/RandomHashTags/destiny/security/advisories/new). 12 | 13 | ### What happens next? 14 | - A member of the team will review and verify the report within 5 working days. They may request additional information about reproducing the vulnerability. 15 | - Once the fix has been released we will publish a security advisory on GitHub 16 | -------------------------------------------------------------------------------- /Sources/Destiny/Destiny.swift: -------------------------------------------------------------------------------- 1 | 2 | @_exported import DestinyBlueprint 3 | @_exported import DestinyDefaults 4 | import SwiftCompression 5 | 6 | /// Default macro to create a `HTTPRouter`. 7 | /// 8 | /// - Parameters: 9 | /// - version: The `HTTPVersion` this router responds to. All routes not having a version declared adopt this one. 10 | /// - errorResponder: The error responder when an error is thrown from a route. 11 | /// - dynamicNotFoundResponder: The dynamic responder for requests to unregistered endpoints. 12 | /// - staticNotFoundResponder: The static responder for requests to unregistered endpoints. 13 | /// - supportedCompressionAlgorithms: The supported compression algorithms. All routes will be updated to support these. 14 | /// - redirects: The redirects this router contains. Dynamic & Static redirects are automatically created based on this input. 15 | /// - middleware: The middleware this router contains. All middleware is handled in the order they are declared (put your most important middleware first). 16 | /// - redirects: The redirects this router contains. 17 | /// - routeGroups: The router groups this router contains. 18 | /// - routes: The routes that this router contains. All routes are subject to this router's static middleware. Only dynamic routes are subject to dynamic middleware. 19 | @freestanding(expression) 20 | public macro router( 21 | version: HTTPVersion, 22 | errorResponder: (any ErrorResponderProtocol)? = nil, 23 | dynamicNotFoundResponder: (any DynamicRouteResponderProtocol)? = nil, 24 | staticNotFoundResponder: (any StaticRouteResponderProtocol)? = nil, 25 | supportedCompressionAlgorithms: Set = [], 26 | middleware: [any MiddlewareProtocol], 27 | redirects: [any RedirectionRouteProtocol] = [], 28 | routeGroups: [any RouteGroupProtocol] = [], 29 | _ routes: any RouteProtocol... 30 | ) -> T = #externalMacro(module: "DestinyMacros", type: "Router") 31 | 32 | @freestanding(declaration, names: named(router)) 33 | public macro declareRouter( 34 | mutable: Bool = false, 35 | typeAnnotation: String? = nil, 36 | 37 | version: HTTPVersion, 38 | errorResponder: (any ErrorResponderProtocol)? = nil, 39 | dynamicNotFoundResponder: (any DynamicRouteResponderProtocol)? = nil, 40 | staticNotFoundResponder: (any StaticRouteResponderProtocol)? = nil, 41 | supportedCompressionAlgorithms: Set = [], 42 | middleware: [any MiddlewareProtocol], 43 | redirects: [any RedirectionRouteProtocol] = [], 44 | routeGroups: [any RouteGroupProtocol] = [], 45 | _ routes: any RouteProtocol... 46 | ) = #externalMacro(module: "DestinyMacros", type: "Router") 47 | 48 | 49 | /// A convenience macro to create a complete HTTP Message at compile time. 50 | @freestanding(expression) 51 | public macro httpMessage( 52 | version: HTTPVersion, 53 | status: HTTPResponseStatus, 54 | headers: [String:String] = [:], 55 | body: (any ResponseBodyProtocol)? = nil, 56 | contentType: HTTPMediaType? = nil, 57 | charset: Charset? = nil 58 | ) -> T = #externalMacro(module: "DestinyMacros", type: "HTTPMessage") -------------------------------------------------------------------------------- /Sources/Destiny/SocketAcceptor.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(Android) 3 | import Android 4 | #elseif canImport(Darwin) 5 | import Darwin 6 | #elseif canImport(Glibc) 7 | import Glibc 8 | #elseif canImport(Musl) 9 | import Musl 10 | #elseif canImport(WinSDK) 11 | import WinSDK 12 | #endif 13 | 14 | public protocol SocketAcceptor: Sendable { 15 | } 16 | 17 | extension SocketAcceptor { 18 | @inlinable 19 | public func acceptFunction(noTCPDelay: Bool) -> @Sendable (Int32?) throws -> (fileDescriptor: Int32, instant: ContinuousClock.Instant)? { 20 | noTCPDelay ? Self.acceptClientNoTCPDelay : Self.acceptClient 21 | } 22 | 23 | @inlinable 24 | @Sendable 25 | static func acceptClient(server: Int32?) throws -> (fileDescriptor: Int32, instant: ContinuousClock.Instant)? { 26 | guard let serverFD = server else { return nil } 27 | var addr = sockaddr_in(), len = socklen_t(MemoryLayout.size) 28 | let client = withUnsafeMutablePointer(to: &addr, { $0.withMemoryRebound(to: sockaddr.self, capacity: 1, { accept(serverFD, $0, &len) }) }) 29 | if client == -1 { 30 | if server == nil { 31 | return nil 32 | } 33 | throw SocketError.acceptFailed() 34 | } 35 | return (client, .now) 36 | } 37 | @inlinable 38 | @Sendable 39 | static func acceptClientNoTCPDelay(server: Int32?) throws -> (fileDescriptor: Int32, instant: ContinuousClock.Instant)? { 40 | guard let serverFD = server else { return nil } 41 | var addr = sockaddr_in(), len = socklen_t(MemoryLayout.size) 42 | let client = accept(serverFD, withUnsafeMutablePointer(to: &addr) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { $0 } }, &len) 43 | if client == -1 { 44 | if server == nil { 45 | return nil 46 | } 47 | throw SocketError.acceptFailed() 48 | } 49 | var d:Int32 = 1 50 | setsockopt(client, Int32(IPPROTO_TCP), TCP_NODELAY, &d, socklen_t(MemoryLayout.size)) 51 | return (client, .now) 52 | } 53 | } -------------------------------------------------------------------------------- /Sources/Destiny/commands/StopCommand.swift: -------------------------------------------------------------------------------- 1 | 2 | import ArgumentParser 3 | 4 | public struct StopCommand: AsyncParsableCommand { 5 | public static let configuration:CommandConfiguration = CommandConfiguration(commandName: "stop", aliases: ["shutdown"]) 6 | 7 | public init() { 8 | } 9 | 10 | public func run() async throws { 11 | try await Application.shared.shutdown() 12 | } 13 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/ApplicationProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | import Logging 3 | import ServiceLifecycle 4 | 5 | public protocol ApplicationProtocol: Service { 6 | 7 | /// The application's logger. 8 | var logger: Logger { get } 9 | 10 | /// Shut down the application. 11 | func shutdown() async throws 12 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/DestinyBlueprint.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(SwiftDiagnostics) && canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 3 | import SwiftDiagnostics 4 | import SwiftSyntax 5 | import SwiftSyntaxMacros 6 | #endif 7 | 8 | @freestanding(expression) 9 | public macro inlineArray(_ input: String) -> InlineArray = #externalMacro(module: "DestinyUtilityMacros", type: "InlineArrayMacro") 10 | 11 | @freestanding(expression) 12 | public macro inlineArray(_ input: Int) -> InlineArray = #externalMacro(module: "DestinyUtilityMacros", type: "InlineArrayMacro") 13 | 14 | @freestanding(expression) 15 | public macro inlineArray(count: Int, _ input: String) -> InlineArray = #externalMacro(module: "DestinyUtilityMacros", type: "InlineArrayMacro") 16 | 17 | @freestanding(declaration, names: arbitrary) 18 | macro httpResponseStatuses( 19 | _ entries: [(memberName: String, code: Int, phrase: String)] 20 | ) = #externalMacro(module: "DestinyUtilityMacros", type: "HTTPResponseStatusesMacro") 21 | 22 | 23 | #if canImport(SwiftDiagnostics) && canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 24 | // MARK: SwiftSyntax 25 | 26 | 27 | 28 | 29 | // MARK: SwiftDiagnostics 30 | package struct DiagnosticMsg: DiagnosticMessage { 31 | package let message:String 32 | package let diagnosticID:MessageID 33 | package let severity:DiagnosticSeverity 34 | 35 | package init(id: String, message: String, severity: DiagnosticSeverity = .error) { 36 | self.message = message 37 | self.diagnosticID = MessageID(domain: "DestinyBlueprint", id: id) 38 | self.severity = severity 39 | } 40 | } 41 | extension DiagnosticMsg: FixItMessage { 42 | package var fixItID: MessageID { diagnosticID } 43 | } 44 | 45 | extension Diagnostic { 46 | package static func spacesNotAllowedInRoutePath(context: some MacroExpansionContext, node: SyntaxProtocol) { 47 | context.diagnose(Diagnostic(node: node, message: DiagnosticMsg(id: "spacesNotAllowedInRoutePath", message: "Spaces aren't allowed in route paths."))) 48 | } 49 | package static func routeResponseStatusNotImplemented(context: some MacroExpansionContext, node: SyntaxProtocol) { 50 | context.diagnose(Diagnostic(node: node, message: DiagnosticMsg(id: "routeResponseStatusNotImplemented", message: "Route's response status is \".notImplemented\".", severity: .warning))) 51 | } 52 | } 53 | 54 | // MARK: SwiftSyntax Misc 55 | extension ExprSyntaxProtocol { 56 | package var macroExpansion: MacroExpansionExprSyntax? { self.as(MacroExpansionExprSyntax.self) } 57 | package var functionCall: FunctionCallExprSyntax? { self.as(FunctionCallExprSyntax.self) } 58 | package var stringLiteral: StringLiteralExprSyntax? { self.as(StringLiteralExprSyntax.self) } 59 | package var booleanLiteral: BooleanLiteralExprSyntax? { self.as(BooleanLiteralExprSyntax.self) } 60 | package var integerLiteral: IntegerLiteralExprSyntax? { self.as(IntegerLiteralExprSyntax.self) } 61 | package var memberAccess: MemberAccessExprSyntax? { self.as(MemberAccessExprSyntax.self) } 62 | package var array: ArrayExprSyntax? { self.as(ArrayExprSyntax.self) } 63 | package var dictionary: DictionaryExprSyntax? { self.as(DictionaryExprSyntax.self) } 64 | } 65 | 66 | extension ExprSyntaxProtocol { 67 | package var booleanIsTrue: Bool { 68 | booleanLiteral?.isTrue ?? false 69 | } 70 | } 71 | extension BooleanLiteralExprSyntax { 72 | package var isTrue: Bool { 73 | literal.text == "true" 74 | } 75 | } 76 | 77 | extension StringLiteralExprSyntax { 78 | package var string: String { "\(segments)" } 79 | } 80 | #endif -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/HTTPCookieProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 3 | import SwiftSyntax 4 | import SwiftSyntaxMacros 5 | #endif 6 | 7 | public protocol HTTPCookieProtocol: CustomDebugStringConvertible, CustomStringConvertible, Sendable { 8 | associatedtype CookieName = String 9 | associatedtype CookieValue = String 10 | 11 | associatedtype Expires 12 | 13 | var name: CookieName { get set } 14 | var value: CookieValue { get set } 15 | 16 | var maxAge: UInt64 { get set } 17 | 18 | var expires: Expires? { get set } 19 | 20 | var domain: String? { get set } 21 | var path: String? { get set } 22 | 23 | var isSecure: Bool { get set } 24 | var isHTTPOnly: Bool { get set } 25 | 26 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 27 | /// Parsing logic for this cookie. 28 | /// 29 | /// - Parameters: 30 | /// - expr: SwiftSyntax expression that represents this cookie at compile time. 31 | static func parse(context: some MacroExpansionContext, expr: ExprSyntaxProtocol) -> Self? 32 | #endif 33 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/HTTPHeadersProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Storage for an HTTP Message's headers. 3 | public protocol HTTPHeadersProtocol: Sendable { 4 | associatedtype Key:Sendable 5 | associatedtype Value:Sendable 6 | 7 | @inlinable subscript(_ header: Key) -> Value? { get set } 8 | @inlinable subscript(_ header: Key) -> String? { get set } 9 | 10 | @inlinable subscript(_ header: String) -> Value? { get set } 11 | @inlinable subscript(_ header: String) -> String? { get set } 12 | 13 | /// Whether or not the target header exists. 14 | @inlinable func has(_ header: Key) -> Bool 15 | 16 | /// Whether or not the target header, as a `String`, exists. 17 | @inlinable func has(_ header: String) -> Bool 18 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/HTTPMediaTypeProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | // MARK: HTTPMediaTypeProtocol 3 | public protocol HTTPMediaTypeProtocol: CustomStringConvertible, Sendable { 4 | var type: String { get } 5 | var subType: String { get } 6 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/HTTPMessageProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | public protocol HTTPMessageProtocol: CustomDebugStringConvertible, Sendable { 3 | 4 | @inlinable 5 | var version: HTTPVersion { get set } 6 | 7 | /// Set the message's status. 8 | /// 9 | /// Default behavior of this function calls `setStatusCode(code:)` with the given type's code. 10 | /// 11 | /// - Parameters: 12 | /// - status: A concrete type conforming to `HTTPResponseStatus.StorageProtocol`. 13 | @inlinable 14 | mutating func setStatus(_ status: T) 15 | 16 | /// Set the message's status code. 17 | /// 18 | /// - Parameters: 19 | /// - code: The new status code to set. 20 | @inlinable 21 | mutating func setStatusCode(_ code: HTTPResponseStatus.Code) 22 | 23 | /// Set the body of the message. 24 | /// 25 | /// - Parameters: 26 | /// - body: The new body to set. 27 | @inlinable 28 | mutating func setBody(_ body: T) 29 | 30 | /// Set a header to the given value. 31 | /// 32 | /// - Parameters: 33 | /// - key: The header you want to modify. 34 | /// - value: The new header value to set. 35 | @inlinable 36 | mutating func setHeader(key: String, value: String) 37 | 38 | @inlinable 39 | mutating func appendCookie(_ cookie: T) 40 | 41 | /// - Parameters: 42 | /// - escapeLineBreak: Whether or not to use `\\r\\n` or `\r\n` in the body. 43 | /// - Returns: A string representing an HTTP Message with the given values. 44 | @inlinable 45 | func string(escapeLineBreak: Bool) throws -> String 46 | 47 | /// Writes a message to a socket. 48 | /// 49 | /// - Parameters: 50 | /// - socket: The socket to write to. 51 | @inlinable 52 | func write(to socket: borrowing Socket) throws 53 | } 54 | 55 | extension HTTPMessageProtocol { 56 | @inlinable 57 | public mutating func setStatus(_ status: T) { 58 | setStatusCode(status.code) 59 | } 60 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/HTTPRequestHeadersProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | public protocol HTTPRequestHeadersProtocol: HTTPHeadersProtocol { 3 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/HTTPRequestMethodProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | public protocol HTTPRequestMethodProtocol: CustomDebugStringConvertible, Equatable, Sendable { 3 | 4 | var rawName: any InlineArrayProtocol { get } 5 | 6 | @inlinable 7 | func rawNameString() -> String 8 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/HTTPRequestProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Core Request protocol that lays out how a socket's incoming data is parsed. 3 | public protocol HTTPRequestProtocol: Sendable, ~Copyable { 4 | typealias ConcretePathType = String // TODO: allow custom 5 | 6 | /// Initializes the bare minimum data required to process a socket's data. 7 | init?(socket: borrowing T) throws 8 | 9 | /// The HTTP start-line. 10 | var startLine: SIMD64 { get } 11 | 12 | /// Yields the endpoint the request wants to reach, separated by the forward slash character. 13 | @inlinable 14 | func forEachPath(offset: Int, _ yield: (ConcretePathType) -> Void) 15 | 16 | /// - Parameters: 17 | /// - index: Index of a path component. 18 | /// - Returns: The path component at the given index. 19 | @inlinable 20 | func path(at index: Int) -> ConcretePathType 21 | 22 | /// The number of path components the request contains. 23 | @inlinable 24 | var pathCount: Int { get } 25 | 26 | /// - Returns: Whether or not the request's method matches the given one. 27 | @inlinable 28 | func isMethod(_ method: InlineArray) -> Bool 29 | 30 | //@inlinable func header(forKey key: InlineArray) -> InlineArray? 31 | 32 | @inlinable 33 | func header(forKey key: String) -> String? 34 | } 35 | 36 | /* 37 | extension HTTPRequestProtocol where Self: ~Copyable { 38 | @inlinable 39 | public func isMethod(_ method: T) -> Bool { 40 | isMethod(method.rawName) 41 | } 42 | }*/ 43 | 44 | /* 45 | /// Core Request Storage protocol that lays out how data for a request is stored. 46 | /// 47 | /// Some examples of data that is usually stored include: 48 | /// - Authentication headers 49 | /// - Cookies 50 | /// - Unique IDs 51 | public protocol RequestStorageProtocol: Sendable, ~Copyable { 52 | /// - Returns: The stored value for the associated key. 53 | func get(key: K) -> V? 54 | /// Stores the value for the associated key. 55 | func set(key: K, value: V?) 56 | }*/ -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/HTTPResponseHeadersProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | public protocol HTTPResponseHeadersProtocol: HTTPHeadersProtocol { 3 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/HTTPRouterProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | import Logging 3 | 4 | /// Core HTTPRouter protocol that handles middleware, routes and router groups. 5 | public protocol HTTPRouterProtocol: Sendable, ~Copyable { 6 | @inlinable mutating func loadDynamicMiddleware() 7 | 8 | /// Process an accepted file descriptor. 9 | /// 10 | /// - Parameters: 11 | /// - client: The accepted file descriptor. 12 | /// - received: The instant the socket was accepted. 13 | /// - socket: The socket to write to. 14 | /// - logger: The `Logger` that logs relevant details. 15 | @inlinable 16 | func process( 17 | client: Int32, 18 | received: ContinuousClock.Instant, 19 | socket: borrowing Socket, 20 | logger: Logger 21 | ) async throws 22 | 23 | /// Writes a static responder to the socket. 24 | /// 25 | /// - Parameters: 26 | /// - socket: The socket to write to. 27 | /// - responder: The static route responder that will write to the socket. 28 | @inlinable 29 | func respondStatically( 30 | socket: borrowing Socket, 31 | responder: Responder 32 | ) async throws 33 | 34 | /// Writes a dynamic responder to the socket. 35 | /// 36 | /// - Parameters: 37 | /// - received: The instant the socket was accepted. 38 | /// - loaded: The instant the socket loaded its default values. 39 | /// - socket: The socket to write to. 40 | /// - request: The socket's request. 41 | /// - responder: The dynamic route responder that will write to the socket. 42 | @inlinable 43 | func respondDynamically( 44 | received: ContinuousClock.Instant, 45 | loaded: ContinuousClock.Instant, 46 | socket: borrowing Socket, 47 | request: inout Socket.ConcreteRequest, 48 | responder: Responder 49 | ) async throws 50 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/HTTPServerProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | import Logging 3 | import ServiceLifecycle 4 | 5 | /// Core Server protocol that accepts and processes incoming network requests. 6 | public protocol HTTPServerProtocol: Service { 7 | /// Main logger for the server. 8 | var logger: Logger { get } 9 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/HTTPSocketProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Core Socket protocol that handles incoming http requests. 3 | public protocol HTTPSocketProtocol: SocketProtocol, ~Copyable { 4 | associatedtype ConcreteRequest:HTTPRequestProtocol 5 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/HTTPStartLineProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | public protocol HTTPStartLineProtocol: Sendable { 3 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/HTTPVersion.swift: -------------------------------------------------------------------------------- 1 | 2 | import SwiftSyntax 3 | 4 | /// List of Hypertext Transfer Protocol versions. 5 | public enum HTTPVersion: String, Hashable, Sendable { 6 | case v0_9 7 | case v1_0 8 | case v1_1 9 | case v1_2 10 | case v2_0 11 | case v3_0 12 | 13 | @inlinable 14 | public init?(token: SIMD8) { 15 | switch token { 16 | case SIMD8(72, 84, 84, 80, 47, 48, 46, 57): self = .v0_9 // HTTP/0.9 17 | case SIMD8(72, 84, 84, 80, 47, 49, 46, 48): self = .v1_0 // HTTP/1.0 18 | case SIMD8(72, 84, 84, 80, 47, 49, 46, 49): self = .v1_1 // HTTP/1.1 19 | case SIMD8(72, 84, 84, 80, 47, 49, 46, 50): self = .v1_2 // HTTP/1.2 20 | case SIMD8(72, 84, 84, 80, 47, 50, 46, 48): self = .v2_0 // HTTP/2.0 21 | case SIMD8(72, 84, 84, 80, 47, 51, 46, 48): self = .v3_0 // HTTP/3.0 22 | default: return nil 23 | } 24 | } 25 | 26 | @inlinable 27 | public init?(token: InlineArray<8, UInt8>) { 28 | switch token { 29 | case #inlineArray("HTTP/0.9"): self = .v0_9 30 | case #inlineArray("HTTP/1.0"): self = .v1_0 31 | case #inlineArray("HTTP/1.1"): self = .v1_1 32 | case #inlineArray("HTTP/1.2"): self = .v1_2 33 | case #inlineArray("HTTP/2.0"): self = .v2_0 34 | case #inlineArray("HTTP/3.0"): self = .v3_0 35 | default: return nil 36 | } 37 | } 38 | 39 | @inlinable 40 | public init?(_ path: SIMD64) { 41 | self.init(token: path.lowHalf.lowHalf.lowHalf) 42 | } 43 | 44 | /// String representation of this HTTP Version (`HTTP/.`). 45 | @inlinable 46 | public var string: String { 47 | switch self { 48 | case .v0_9: "HTTP/0.9" 49 | case .v1_0: "HTTP/1.0" 50 | case .v1_1: "HTTP/1.1" 51 | case .v1_2: "HTTP/1.2" 52 | case .v2_0: "HTTP/2.0" 53 | case .v3_0: "HTTP/3.0" 54 | } 55 | } 56 | 57 | /// `SIMD8` representation of this HTTP Version. 58 | @inlinable 59 | public var simd: SIMD8 { 60 | switch self { 61 | case .v0_9: SIMD8(72, 84, 84, 80, 47, 48, 46, 57) // HTTP/0.9 62 | case .v1_0: SIMD8(72, 84, 84, 80, 47, 49, 46, 48) // HTTP/1.0 63 | case .v1_1: SIMD8(72, 84, 84, 80, 47, 49, 46, 49) // HTTP/1.1 64 | case .v1_2: SIMD8(72, 84, 84, 80, 47, 49, 46, 50) // HTTP/1.2 65 | case .v2_0: SIMD8(72, 84, 84, 80, 47, 50, 46, 48) // HTTP/2.0 66 | case .v3_0: SIMD8(72, 84, 84, 80, 47, 51, 46, 48) // HTTP/3.0 67 | } 68 | } 69 | 70 | /// `InlineArray<8, UInt8>` representation of this HTTP Version. 71 | @inlinable 72 | public var inlineArray: InlineArray<8, UInt8> { 73 | switch self { 74 | case .v0_9: #inlineArray("HTTP/0.9") 75 | case .v1_0: #inlineArray("HTTP/1.0") 76 | case .v1_1: #inlineArray("HTTP/1.1") 77 | case .v1_2: #inlineArray("HTTP/1.2") 78 | case .v2_0: #inlineArray("HTTP/2.0") 79 | case .v3_0: #inlineArray("HTTP/3.0") 80 | } 81 | } 82 | } 83 | 84 | #if canImport(SwiftSyntax) 85 | // MARK: SwiftSyntax 86 | extension HTTPVersion { 87 | public static func parse(_ expr: ExprSyntax) -> HTTPVersion? { 88 | switch expr.as(MemberAccessExprSyntax.self)?.declName.baseName.text { 89 | case "v0_9": .v0_9 90 | case "v1_0": .v1_0 91 | case "v1_1": .v1_1 92 | case "v1_2": .v1_2 93 | case "v2_0": .v2_0 94 | case "v3_0": .v3_0 95 | default: nil 96 | } 97 | } 98 | } 99 | #endif -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/InlineArrayProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | public protocol InlineArrayProtocol: InlineCollectionProtocol, ~Copyable where Index == Int { 3 | init(repeating value: Element) 4 | } 5 | 6 | // MARK Conformances 7 | extension InlineArray: InlineArrayProtocol { 8 | @inlinable 9 | public func itemAt(index: Index) -> Element { 10 | self[index] 11 | } 12 | 13 | @inlinable 14 | public mutating func setItemAt(index: Int, element: Element) { 15 | self[index] = element 16 | } 17 | } 18 | 19 | // MARK: Extensions 20 | extension Array where Element: BinaryInteger { 21 | public init(_ inlineArray: T) where T.Index == Index, Element == T.Element { 22 | self = .init() 23 | reserveCapacity(inlineArray.count) 24 | for i in inlineArray.indices { 25 | append(inlineArray.itemAt(index: i)) 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/InlineCollectionProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | public protocol InlineCollectionProtocol: InlineSequenceProtocol, ~Copyable { 3 | associatedtype Index:Comparable 4 | 5 | var startIndex: Index { get } 6 | var endIndex: Index { get } 7 | 8 | var count: Int { get } 9 | var isEmpty: Bool { get } 10 | var indices: Range { get } 11 | 12 | borrowing func index(after i: Index) -> Index 13 | borrowing func index(before i: Index) -> Index 14 | 15 | // TODO: remove the following two functions | temporary workaround for borrowing self in a subscript (_read and _modify accessors aren't final) 16 | func itemAt(index: Index) -> Element 17 | mutating func setItemAt(index: Index, element: Element) 18 | 19 | mutating func swapAt(_ i: Index, _ j: Index) 20 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/InlineSequenceProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | public protocol InlineSequenceProtocol: Sendable, ~Copyable { 3 | associatedtype Element 4 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/ResponseBodyProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | public protocol ResponseBodyProtocol: CustomDebugStringConvertible, Sendable { 3 | @inlinable 4 | var count: Int { get } 5 | 6 | var responderDebugDescription: String { get } 7 | func responderDebugDescription(_ input: String) -> String 8 | func responderDebugDescription(_ input: T) throws -> String 9 | 10 | @inlinable 11 | func string() -> String 12 | 13 | @inlinable 14 | func bytes() -> [UInt8] 15 | 16 | @inlinable 17 | func bytes(_ closure: (inout InlineVLArray) throws -> Void) rethrows 18 | 19 | @inlinable 20 | var hasDateHeader: Bool { get } 21 | 22 | @inlinable 23 | var hasCustomInitializer: Bool { get } 24 | 25 | @inlinable 26 | func customInitializer(bodyString: String) -> String 27 | } 28 | 29 | extension ResponseBodyProtocol { 30 | @inlinable public var hasCustomInitializer: Bool { false } 31 | @inlinable public func customInitializer(bodyString: String) -> String { "" } 32 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/SocketProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(Darwin) 3 | import Darwin 4 | #endif 5 | 6 | /// Core Socket protocol that handles incoming network requests. 7 | public protocol SocketProtocol: ~Copyable, Sendable { 8 | associatedtype Buffer:InlineArrayProtocol where Buffer.Element == UInt8 9 | 10 | /// The unique file descriptor the system assigns to this socket where communication between the server and client are handled. 11 | /// 12 | /// - Warning: Do not close this file descriptor. It is closed automatically by the server. 13 | var fileDescriptor: Int32 { get } 14 | 15 | init(fileDescriptor: Int32) 16 | 17 | /// Reads a buffer from the socket. 18 | @inlinable 19 | func readBuffer( 20 | into baseAddress: UnsafeMutablePointer, 21 | length: Int, 22 | flags: Int32 23 | ) throws -> Int 24 | 25 | @inlinable 26 | func readBuffer() throws -> (Buffer, Int) 27 | 28 | /// Writes a buffer to the socket. 29 | @inlinable 30 | func writeBuffer(_ pointer: UnsafeRawPointer, length: Int) throws 31 | 32 | /// Writes a `String` to the socket. 33 | @inlinable 34 | func writeString(_ string: String) throws 35 | } 36 | 37 | extension SocketProtocol where Self: ~Copyable { 38 | @inlinable 39 | public static func noSigPipe(fileDescriptor: Int32) { 40 | #if canImport(Darwin) 41 | var no_sig_pipe:Int32 = 0 42 | setsockopt(fileDescriptor, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(MemoryLayout.size)) 43 | #endif 44 | } 45 | 46 | /// Writes 2 bytes (carriage return and line feed) to the socket. 47 | @inlinable 48 | public func writeCRLF(count: Int = 1) throws { 49 | let capacity = count * 2 50 | try withUnsafeTemporaryAllocation(of: UInt8.self, capacity: capacity, { p in 51 | var i = 0 52 | while i < count { 53 | p[i] = .carriageReturn 54 | p[i + 1] = .lineFeed 55 | i += 2 56 | } 57 | try writeBuffer(p.baseAddress!, length: capacity) 58 | }) 59 | } 60 | 61 | @inlinable 62 | public func writeString(_ string: String) throws { 63 | try string.utf8.withContiguousStorageIfAvailable { 64 | try self.writeBuffer($0.baseAddress!, length: $0.count) 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/errors/DestinyErrorProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(Android) 3 | import Android 4 | #elseif canImport(Darwin) 5 | import Darwin 6 | #elseif canImport(Glibc) 7 | import Glibc 8 | #elseif canImport(Musl) 9 | import Musl 10 | #elseif canImport(WinSDK) 11 | import WinSDK 12 | #endif 13 | 14 | public protocol DestinyErrorProtocol: Error { 15 | var identifier: String { get } 16 | var reason: String { get } 17 | 18 | init(identifier: String, reason: String) 19 | } 20 | 21 | extension DestinyErrorProtocol { 22 | @usableFromInline 23 | static func cError(_ identifier: String) -> Self { 24 | #if canImport(Android) || canImport(Darwin) || canImport(Glibc) || canImport(Musl) || canImport(WinSDK) 25 | return Self(identifier: identifier, reason: String(cString: strerror(errno)) + " (errno=\(errno))") 26 | #else 27 | return Self(identifier: identifier, reason: "unspecified") 28 | #endif 29 | } 30 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/errors/EpollError.swift: -------------------------------------------------------------------------------- 1 | 2 | public struct EpollError: DestinyErrorProtocol { 3 | public let identifier:String 4 | public let reason:String 5 | 6 | public init(identifier: String, reason: String) { 7 | self.identifier = identifier 8 | self.reason = reason 9 | } 10 | } 11 | 12 | // MARK: Errors 13 | extension EpollError { 14 | @inlinable public static func epollCreateFailed() -> Self { cError("epollCreateFailed") } 15 | @inlinable public static func epollCreateFailed(_ reason: String) -> Self { Self(identifier: "epollCreateFailed", reason: reason) } 16 | 17 | @inlinable public static func epollCtlFailed() -> Self { cError("epollCtlFailed") } 18 | @inlinable public static func epollCtlFailed(_ reason: String) -> Self { Self(identifier: "epollCtlFailed", reason: reason) } 19 | 20 | @inlinable public static func waitFailed() -> Self { cError("waitFailed") } 21 | @inlinable public static func waitFailed(_ reason: String) -> Self { Self(identifier: "waitFailed", reason: reason) } 22 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/errors/MiddlewareError.swift: -------------------------------------------------------------------------------- 1 | 2 | public struct MiddlewareError: DestinyErrorProtocol { 3 | public let identifier:String 4 | public let reason:String 5 | 6 | public init(identifier: String, reason: String) { 7 | self.identifier = identifier 8 | self.reason = reason 9 | } 10 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/errors/ServerError.swift: -------------------------------------------------------------------------------- 1 | 2 | // MARK: ServerError 3 | public struct ServerError: DestinyErrorProtocol { 4 | public let identifier:String 5 | public let reason:String 6 | 7 | public init(identifier: String, reason: String) { 8 | self.identifier = identifier 9 | self.reason = reason 10 | } 11 | } 12 | 13 | // MARK: Errors 14 | extension ServerError { 15 | @inlinable public static func socketCreationFailed() -> Self { cError("socketCreationFailed") } 16 | @inlinable public static func socketCreationFailed(_ reason: String) -> Self { Self(identifier: "socketCreationFailed", reason: reason) } 17 | 18 | @inlinable public static func bindFailed() -> Self { cError("bindFailed") } 19 | @inlinable public static func bindFailed(_ reason: String) -> Self { Self(identifier: "bindFailed", reason: reason) } 20 | 21 | @inlinable public static func listenFailed() -> Self { cError("listenFailed") } 22 | @inlinable public static func listenFailed(_ reason: String) -> Self { Self(identifier: "listenFailed", reason: reason) } 23 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/errors/SocketError.swift: -------------------------------------------------------------------------------- 1 | 2 | public struct SocketError: DestinyErrorProtocol { 3 | public let identifier:String 4 | public let reason:String 5 | 6 | public init(identifier: String, reason: String) { 7 | self.identifier = identifier 8 | self.reason = reason 9 | } 10 | } 11 | 12 | // MARK: Errors 13 | extension SocketError { 14 | @inlinable public static func acceptFailed() -> Self { cError("acceptFailed") } 15 | @inlinable public static func acceptFailed(_ reason: String) -> Self { Self(identifier: "acceptFailed", reason: reason) } 16 | 17 | @inlinable public static func writeFailed() -> Self { cError("writeFailed") } 18 | @inlinable public static func writeFailed(_ reason: String) -> Self { Self(identifier: "writeFailed", reason: reason) } 19 | 20 | @inlinable public static func readSingleByteFailed() -> Self { cError("readSingleByteFailed") } 21 | @inlinable public static func readSingleByteFailed(_ reason: String) -> Self { Self(identifier: "readSingleByteFailed", reason: reason) } 22 | 23 | @inlinable public static func readBufferFailed() -> Self { cError("readBufferFailed") } 24 | @inlinable public static func readBufferFailed(_ reason: String) -> Self { Self(identifier: "readBufferFailed", reason: reason) } 25 | 26 | @inlinable public static func invalidStatus() -> Self { cError("invalidStatus") } 27 | @inlinable public static func invalidStatus(_ reason: String) -> Self { Self(identifier: "invalidStatus", reason: reason) } 28 | 29 | @inlinable public static func closeFailure() -> Self { cError("closeFailure") } 30 | @inlinable public static func closeFailure(_ reason: String) -> Self { Self(identifier: "closeFailure", reason: reason) } 31 | 32 | @inlinable public static func malformedRequest() -> Self { cError("malformedRequest") } 33 | @inlinable public static func malformedRequest(_ reason: String) -> Self { Self(identifier: "malformedRequest", reason: reason) } 34 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/extensions/IntExtensions.swift: -------------------------------------------------------------------------------- 1 | 2 | extension UInt8 { 3 | // symbols 4 | @inlinable public static var lineFeed: Self { 10 } 5 | @inlinable public static var carriageReturn: Self { 13 } 6 | @inlinable public static var space: Self { 32 } 7 | @inlinable public static var comma: Self { 44 } 8 | @inlinable public static var subtract: Self { 45 } 9 | @inlinable public static var colon: Self { 58 } 10 | 11 | // letters 12 | @inlinable public static var A: Self { 65 } 13 | @inlinable public static var B: Self { 66 } 14 | @inlinable public static var C: Self { 67 } 15 | @inlinable public static var D: Self { 68 } 16 | @inlinable public static var E: Self { 69 } 17 | @inlinable public static var F: Self { 70 } 18 | @inlinable public static var G: Self { 71 } 19 | @inlinable public static var H: Self { 72 } 20 | @inlinable public static var I: Self { 73 } 21 | @inlinable public static var J: Self { 74 } 22 | @inlinable public static var K: Self { 75 } 23 | @inlinable public static var L: Self { 76 } 24 | @inlinable public static var M: Self { 77 } 25 | @inlinable public static var N: Self { 78 } 26 | @inlinable public static var O: Self { 79 } 27 | @inlinable public static var P: Self { 80 } 28 | @inlinable public static var Q: Self { 81 } 29 | @inlinable public static var R: Self { 82 } 30 | @inlinable public static var S: Self { 83 } 31 | @inlinable public static var T: Self { 84 } 32 | @inlinable public static var U: Self { 85 } 33 | @inlinable public static var V: Self { 86 } 34 | @inlinable public static var W: Self { 87 } 35 | @inlinable public static var X: Self { 88 } 36 | @inlinable public static var Y: Self { 89 } 37 | @inlinable public static var Z: Self { 90 } 38 | 39 | @inlinable public static var a: Self { 97 } 40 | @inlinable public static var b: Self { 98 } 41 | @inlinable public static var c: Self { 99 } 42 | @inlinable public static var d: Self { 100 } 43 | @inlinable public static var e: Self { 101 } 44 | @inlinable public static var f: Self { 102 } 45 | @inlinable public static var g: Self { 103 } 46 | @inlinable public static var h: Self { 104 } 47 | @inlinable public static var i: Self { 105 } 48 | @inlinable public static var j: Self { 106 } 49 | @inlinable public static var k: Self { 107 } 50 | @inlinable public static var l: Self { 108 } 51 | @inlinable public static var m: Self { 109 } 52 | @inlinable public static var n: Self { 110 } 53 | @inlinable public static var o: Self { 111 } 54 | @inlinable public static var p: Self { 112 } 55 | @inlinable public static var q: Self { 113 } 56 | @inlinable public static var r: Self { 114 } 57 | @inlinable public static var s: Self { 115 } 58 | @inlinable public static var t: Self { 116 } 59 | @inlinable public static var u: Self { 117 } 60 | @inlinable public static var v: Self { 118 } 61 | @inlinable public static var w: Self { 119 } 62 | @inlinable public static var x: Self { 120 } 63 | @inlinable public static var y: Self { 121 } 64 | @inlinable public static var z: Self { 122 } 65 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/extensions/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | 2 | extension String { 3 | @inlinable 4 | public mutating func append(_ array: InlineArray) { 5 | for i in array.indices { 6 | let char = array[i] 7 | if char == 0 { 8 | break 9 | } 10 | self.append(Character(Unicode.Scalar(char))) 11 | } 12 | } 13 | 14 | @inlinable 15 | public func inlineVLArray(_ closure: (inout InlineVLArray) throws -> Void) rethrows { 16 | try InlineVLArray.create(string: self, closure) 17 | } 18 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/extensions/simd/SIMDExtensions+HasPrefix.swift: -------------------------------------------------------------------------------- 1 | 2 | // MARK: SIMD4 3 | extension SIMD4 where Scalar: BinaryInteger { 4 | /// Whether or not this SIMD is prefixed with certain integers. 5 | /// 6 | /// - Complexity: O(1) 7 | @inlinable public func hasPrefix(_ simd: SIMD2) -> Bool { simd == lowHalf } 8 | } 9 | 10 | // MARK: SIMD8 11 | extension SIMD8 where Scalar: BinaryInteger { 12 | /// Whether or not this SIMD is prefixed with certain integers. 13 | /// 14 | /// - Complexity: O(1) 15 | @inlinable public func hasPrefix(_ simd: SIMD2) -> Bool { simd == lowHalf.lowHalf } 16 | 17 | /// Whether or not this SIMD is prefixed with certain integers. 18 | /// 19 | /// - Complexity: O(1) 20 | @inlinable public func hasPrefix(_ simd: SIMD4) -> Bool { simd == lowHalf } 21 | } 22 | 23 | // MARK: SIMD16 24 | extension SIMD16 where Scalar: BinaryInteger { 25 | /// Whether or not this SIMD is prefixed with certain integers. 26 | /// 27 | /// - Complexity: O(1) 28 | @inlinable public func hasPrefix(_ simd: SIMD2) -> Bool { simd == lowHalf.lowHalf.lowHalf } 29 | 30 | /// Whether or not this SIMD is prefixed with certain integers. 31 | /// 32 | /// - Complexity: O(1) 33 | @inlinable public func hasPrefix(_ simd: SIMD4) -> Bool { simd == lowHalf.lowHalf } 34 | 35 | /// Whether or not this SIMD is prefixed with certain integers. 36 | /// 37 | /// - Complexity: O(1) 38 | @inlinable public func hasPrefix(_ simd: SIMD8) -> Bool { simd == lowHalf } 39 | } 40 | 41 | // MARK: SIMD32 42 | extension SIMD32 where Scalar: BinaryInteger { 43 | /// Whether or not this SIMD is prefixed with certain integers. 44 | /// 45 | /// - Complexity: O(1) 46 | @inlinable public func hasPrefix(_ simd: SIMD2) -> Bool { simd == lowHalf.lowHalf.lowHalf.lowHalf } 47 | 48 | /// Whether or not this SIMD is prefixed with certain integers. 49 | /// 50 | /// - Complexity: O(1) 51 | @inlinable public func hasPrefix(_ simd: SIMD4) -> Bool { simd == lowHalf.lowHalf.lowHalf } 52 | 53 | /// Whether or not this SIMD is prefixed with certain integers. 54 | /// 55 | /// - Complexity: O(1) 56 | @inlinable public func hasPrefix(_ simd: SIMD8) -> Bool { simd == lowHalf.lowHalf } 57 | 58 | /// Whether or not this SIMD is prefixed with certain integers. 59 | /// 60 | /// - Complexity: O(1) 61 | @inlinable public func hasPrefix(_ simd: SIMD16) -> Bool { simd == lowHalf } 62 | } 63 | 64 | // MARK: SIMD64 65 | extension SIMD64 where Scalar: BinaryInteger { 66 | /// Whether or not this SIMD is prefixed with certain integers. 67 | /// 68 | /// - Complexity: O(1) 69 | @inlinable public func hasPrefix(_ simd: SIMD2) -> Bool { simd == lowHalf.lowHalf.lowHalf.lowHalf.lowHalf } 70 | 71 | /// Whether or not this SIMD is prefixed with certain integers. 72 | /// 73 | /// - Complexity: O(1) 74 | @inlinable public func hasPrefix(_ simd: SIMD4) -> Bool { simd == lowHalf.lowHalf.lowHalf.lowHalf } 75 | 76 | /// Whether or not this SIMD is prefixed with certain integers. 77 | /// 78 | /// - Complexity: O(1) 79 | @inlinable public func hasPrefix(_ simd: SIMD8) -> Bool { simd == lowHalf.lowHalf.lowHalf } 80 | 81 | /// Whether or not this SIMD is prefixed with certain integers. 82 | /// 83 | /// - Complexity: O(1) 84 | @inlinable public func hasPrefix(_ simd: SIMD16) -> Bool { simd == lowHalf.lowHalf } 85 | 86 | /// Whether or not this SIMD is prefixed with certain integers. 87 | /// 88 | /// - Complexity: O(1) 89 | @inlinable public func hasPrefix(_ simd: SIMD32) -> Bool { simd == lowHalf } 90 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/extensions/simd/SIMDExtensions+LeadingNonByteCount.swift: -------------------------------------------------------------------------------- 1 | 2 | // MARK: SIMD2 3 | extension SIMD2 where Scalar: BinaryInteger { 4 | /// - Complexity: O(1) 5 | @inlinable 6 | public func leadingNonByteCount(byte: Scalar) -> Int { 7 | if x == byte { return 0 } 8 | if y == byte { return 1 } 9 | return scalarCount 10 | } 11 | } 12 | 13 | // MARK: SIMD4 14 | extension SIMD4 where Scalar: BinaryInteger { 15 | /// - Complexity: O(1) 16 | @inlinable 17 | public func leadingNonByteCount(byte: Scalar) -> Int { 18 | let byte_simd:SIMD2 = .init(repeating: byte) 19 | let all_nonbyte:SIMDMask> = .init(repeating: true) 20 | if (lowHalf .!= byte_simd) != all_nonbyte { return lowHalf.leadingNonByteCount(byte: byte) } 21 | if (highHalf .!= byte_simd) != all_nonbyte { return 2 + highHalf.leadingNonByteCount(byte: byte) } 22 | return scalarCount 23 | } 24 | } 25 | 26 | // MARK: SIMD8 27 | extension SIMD8 where Scalar: BinaryInteger { 28 | /// - Complexity: O(1) 29 | @inlinable 30 | public func leadingNonByteCount(byte: Scalar) -> Int { 31 | let byte_simd:SIMD4 = .init(repeating: byte) 32 | let all_nonbyte:SIMDMask> = .init(repeating: true) 33 | if (lowHalf .!= byte_simd) != all_nonbyte { return lowHalf.leadingNonByteCount(byte: byte) } 34 | if (highHalf .!= byte_simd) != all_nonbyte { return 4 + highHalf.leadingNonByteCount(byte: byte) } 35 | return scalarCount 36 | } 37 | } 38 | 39 | // MARK: SIMD16 40 | extension SIMD16 where Scalar: BinaryInteger { 41 | /// - Complexity: O(1) 42 | @inlinable 43 | public func leadingNonByteCount(byte: Scalar) -> Int { 44 | let byte_simd:SIMD8 = .init(repeating: byte) 45 | let all_nonbyte:SIMDMask> = .init(repeating: true) 46 | if (lowHalf .!= byte_simd) != all_nonbyte { return lowHalf.leadingNonByteCount(byte: byte) } 47 | if (highHalf .!= byte_simd) != all_nonbyte { return 8 + highHalf.leadingNonByteCount(byte: byte) } 48 | return scalarCount 49 | } 50 | } 51 | 52 | // MARK: SIMD32 53 | extension SIMD32 where Scalar: BinaryInteger { 54 | /// - Complexity: O(1) 55 | @inlinable 56 | public func leadingNonByteCount(byte: Scalar) -> Int { 57 | let byte_simd:SIMD16 = .init(repeating: byte) 58 | let all_nonbyte:SIMDMask> = .init(repeating: true) 59 | if (lowHalf .!= byte_simd) != all_nonbyte { return lowHalf.leadingNonByteCount(byte: byte) } 60 | if (highHalf .!= byte_simd) != all_nonbyte { return 16 + highHalf.leadingNonByteCount(byte: byte) } 61 | return scalarCount 62 | } 63 | } 64 | 65 | // MARK: SIMD64 66 | extension SIMD64 where Scalar: BinaryInteger { 67 | /// - Complexity: O(1) 68 | @inlinable 69 | public func leadingNonByteCount(byte: Scalar) -> Int { 70 | let byte_simd:SIMD32 = .init(repeating: byte) 71 | let all_nonbyte:SIMDMask> = .init(repeating: true) 72 | if (lowHalf .!= byte_simd) != all_nonbyte { return lowHalf.leadingNonByteCount(byte: byte) } 73 | if (highHalf .!= byte_simd) != all_nonbyte { return 32 + highHalf.leadingNonByteCount(byte: byte) } 74 | return scalarCount 75 | } 76 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/extensions/simd/SIMDExtensions+LeadingNonzeroByteCount.swift: -------------------------------------------------------------------------------- 1 | 2 | // MARK: SIMD2 3 | extension SIMD2 where Scalar: BinaryInteger { 4 | /// - Complexity: O(1) 5 | @inlinable 6 | public var leadingNonzeroByteCount: Int { 7 | if x == 0 { return 0 } 8 | if y == 0 { return 1 } 9 | return scalarCount 10 | } 11 | } 12 | 13 | // MARK: SIMD4 14 | extension SIMD4 where Scalar: BinaryInteger { 15 | /// - Complexity: O(1) 16 | @inlinable 17 | public var leadingNonzeroByteCount: Int { 18 | let all_nonzero:SIMDMask> = .init(repeating: true) 19 | if (lowHalf .!= .zero) != all_nonzero { return lowHalf.leadingNonzeroByteCount } 20 | if (highHalf .!= .zero) != all_nonzero { return 2 + highHalf.leadingNonzeroByteCount } 21 | return scalarCount 22 | } 23 | } 24 | 25 | // MARK: SIMD8 26 | extension SIMD8 where Scalar: BinaryInteger { 27 | /// - Complexity: O(1) 28 | @inlinable 29 | public var leadingNonzeroByteCount: Int { 30 | let all_nonzero:SIMDMask> = .init(repeating: true) 31 | if (lowHalf .!= .zero) != all_nonzero { return lowHalf.leadingNonzeroByteCount } 32 | if (highHalf .!= .zero) != all_nonzero { return 4 + highHalf.leadingNonzeroByteCount } 33 | return scalarCount 34 | } 35 | } 36 | 37 | // MARK: SIMD16 38 | extension SIMD16 where Scalar: BinaryInteger { 39 | /// - Complexity: O(1) 40 | @inlinable 41 | public var leadingNonzeroByteCount: Int { 42 | let all_nonzero:SIMDMask> = .init(repeating: true) 43 | if (lowHalf .!= .zero) != all_nonzero { return lowHalf.leadingNonzeroByteCount } 44 | if (highHalf .!= .zero) != all_nonzero { return 8 + highHalf.leadingNonzeroByteCount } 45 | return scalarCount 46 | } 47 | } 48 | 49 | // MARK: SIMD32 50 | extension SIMD32 where Scalar: BinaryInteger { 51 | /// - Complexity: O(1) 52 | @inlinable 53 | public var leadingNonzeroByteCount: Int { 54 | let all_nonzero:SIMDMask> = .init(repeating: true) 55 | if (lowHalf .!= .zero) != all_nonzero { return lowHalf.leadingNonzeroByteCount } 56 | if (highHalf .!= .zero) != all_nonzero { return 16 + highHalf.leadingNonzeroByteCount } 57 | return scalarCount 58 | } 59 | } 60 | 61 | // MARK: SIMD64 62 | extension SIMD64 where Scalar: BinaryInteger { 63 | /// - Complexity: O(1) 64 | @inlinable 65 | public var leadingNonzeroByteCount: Int { 66 | let all_nonzero:SIMDMask> = .init(repeating: true) 67 | if (lowHalf .!= .zero) != all_nonzero { return lowHalf.leadingNonzeroByteCount } 68 | if (highHalf .!= .zero) != all_nonzero { return 32 + highHalf.leadingNonzeroByteCount } 69 | return scalarCount 70 | } 71 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/extensions/simd/SIMDExtensions+LeadingString.swift: -------------------------------------------------------------------------------- 1 | 2 | // MARK: SIMD2 3 | extension SIMD2 where Scalar == UInt8 { 4 | /// - Complexity: O(1) 5 | @inlinable 6 | func leadingScalars() -> [Scalar] { 7 | return x == 0 ? [] : y == 0 ? [x] : [x, y] 8 | } 9 | /// Creates a `String` based on this vector's leading scalars. 10 | /// 11 | /// - Complexity: O(1) 12 | @inlinable 13 | public func leadingString() -> String { 14 | return String(decoding: leadingScalars(), as: UTF8.self) 15 | } 16 | } 17 | 18 | // MARK: SIMD4 19 | extension SIMD4 where Scalar == UInt8 { 20 | /// - Complexity: O(1) 21 | @inlinable 22 | func leadingScalars() -> [Scalar] { 23 | if (self .!= .zero) == .init(repeating: true) { 24 | return [x, y, z, w] 25 | } 26 | if (lowHalf .!= .zero) != .init(repeating: true) { 27 | return lowHalf.leadingScalars() 28 | } 29 | return [x, y] + highHalf.leadingScalars() 30 | } 31 | 32 | /// Creates a `String` based on this vector's leading scalars. 33 | /// 34 | /// - Complexity: O(1) 35 | @inlinable 36 | public func leadingString() -> String { 37 | return String(decoding: leadingScalars(), as: UTF8.self) 38 | } 39 | } 40 | 41 | // MARK: SIMD8 42 | extension SIMD8 where Scalar == UInt8 { 43 | /// - Complexity: O(1) 44 | @inlinable 45 | func leadingScalars() -> [Scalar] { 46 | if (lowHalf .!= .zero) != .init(repeating: true) { 47 | return lowHalf.leadingScalars() 48 | } 49 | return lowHalf.leadingScalars() + highHalf.leadingScalars() 50 | } 51 | 52 | /// Creates a `String` based on this vector's leading scalars. 53 | /// 54 | /// - Complexity: O(1) 55 | @inlinable 56 | public func leadingString() -> String { 57 | return String(decoding: leadingScalars(), as: UTF8.self) 58 | } 59 | } 60 | 61 | // MARK: SIMD16 62 | extension SIMD16 where Scalar == UInt8 { 63 | /// - Complexity: O(1) 64 | @inlinable 65 | func leadingScalars() -> [Scalar] { 66 | if (lowHalf .!= .zero) != .init(repeating: true) { 67 | return lowHalf.leadingScalars() 68 | } 69 | return lowHalf.leadingScalars() + highHalf.leadingScalars() 70 | } 71 | 72 | /// Creates a `String` based on this vector's leading scalars. 73 | /// 74 | /// - Complexity: O(1) 75 | @inlinable 76 | public func leadingString() -> String { 77 | return String(decoding: leadingScalars(), as: UTF8.self) 78 | } 79 | } 80 | 81 | // MARK: SIMD32 82 | extension SIMD32 where Scalar == UInt8 { 83 | /// - Complexity: O(1) 84 | @inlinable 85 | func leadingScalars() -> [Scalar] { 86 | if (lowHalf .!= .zero) != .init(repeating: true) { 87 | return lowHalf.leadingScalars() 88 | } 89 | return lowHalf.leadingScalars() + highHalf.leadingScalars() 90 | } 91 | 92 | /// Creates a `String` based on this vector's leading scalars. 93 | /// 94 | /// - Complexity: O(1) 95 | @inlinable 96 | public func leadingString() -> String { 97 | return String(decoding: leadingScalars(), as: UTF8.self) 98 | } 99 | } 100 | 101 | // MARK: SIMD64 102 | extension SIMD64 where Scalar == UInt8 { 103 | /// - Complexity: O(1) 104 | @inlinable 105 | func leadingScalars() -> [Scalar] { 106 | if (lowHalf .!= .zero) != .init(repeating: true) { 107 | return lowHalf.leadingScalars() 108 | } 109 | return lowHalf.leadingScalars() + highHalf.leadingScalars() 110 | } 111 | 112 | /// Creates a `String` based on this vector's leading scalars. 113 | /// 114 | /// - Complexity: O(1) 115 | @inlinable 116 | public func leadingString() -> String { 117 | return String(decoding: leadingScalars(), as: UTF8.self) 118 | } 119 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/extensions/simd/SIMDExtensions+Lowercased.swift: -------------------------------------------------------------------------------- 1 | 2 | extension SIMD where Scalar: FixedWidthInteger { 3 | /// - Complexity: O(1) 4 | @inlinable 5 | public func lowercased() -> Self { 6 | var upperCase = self .>= 65 7 | upperCase .&= self .<= 90 8 | 9 | var addition = Self.zero 10 | addition.replace(with: 32, where: upperCase) // TODO: use a SIMD blend operation (no existing standard Swift operation) 11 | return self &+ addition 12 | } 13 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/extensions/simd/SIMDExtensions+Split.swift: -------------------------------------------------------------------------------- 1 | 2 | // MARK: SIMD2 3 | extension SIMD2 where Scalar: BinaryInteger { 4 | /// - Complexity: O(1) 5 | public func split(separator: Scalar) -> Self { 6 | if x == separator { 7 | return y != separator ? Self(x: y, y: 0) : self 8 | } else if y == separator { 9 | return Self(x: x, y: 0) 10 | } 11 | return self 12 | } 13 | } 14 | 15 | // MARK: SIMD4 16 | extension SIMD4 where Scalar: BinaryInteger { 17 | /// - Complexity: O(1) 18 | @inlinable 19 | public func split(separator: Scalar) -> [Self] { 20 | // return self if it doesn't require splitting 21 | guard (self .!= .init(repeating: separator)) != .init(repeating: true) else { return [self] } 22 | var array:[Self] = [] 23 | array.reserveCapacity(2) 24 | array.append(.init()) 25 | let separator_simd:SIMD2 = .init(repeating: separator) 26 | let all_nonseparator:SIMDMask.MaskStorage> = .init(repeating: true) 27 | var did_split:Bool = false 28 | if (lowHalf .!= separator_simd) != all_nonseparator { // whether lowHalf contains separator 29 | array[0].lowHalf = lowHalf.split(separator: separator) 30 | did_split = true 31 | } else { 32 | array[0].lowHalf = lowHalf 33 | } 34 | if (highHalf .!= separator_simd) != all_nonseparator { // whether highHalf contains separator 35 | let value:SIMD2 = highHalf.split(separator: separator) 36 | if did_split { 37 | array.append(.init(lowHalf: value, highHalf: .init())) 38 | } else if highHalf[1] == separator { 39 | array[0].highHalf[0] = highHalf[0] 40 | } else { 41 | array.append(.init(lowHalf: value, highHalf: .init())) 42 | } 43 | } else if did_split && lowHalf[0] == separator { 44 | array[0].lowHalf[1] = highHalf[0] 45 | array[0].highHalf[0] = highHalf[1] 46 | } else { 47 | array.append(.init(lowHalf: highHalf, highHalf: .init())) 48 | } 49 | return array 50 | } 51 | } 52 | 53 | /* 54 | // MARK: SIMD8 55 | extension SIMD8 where Scalar: BinaryInteger { 56 | /// - Complexity: O(1)? 57 | @inlinable 58 | public func split(separator: Scalar) -> [Self] { 59 | let separator_simd:SIMD4 = SIMD4(repeating: separator) 60 | let all_nonseparator:SIMDMask.MaskStorage> = .init(repeating: true) 61 | var array:[Self] = [] 62 | array.reserveCapacity(2) 63 | var keep_lowhalf:Bool = false 64 | if (lowHalf .!= separator_simd) != all_nonseparator { 65 | for value in lowHalf.split(separator: separator) { 66 | array.append(Self(lowHalf: value, highHalf: .init())) 67 | } 68 | } else { 69 | keep_lowhalf = true 70 | } 71 | if (highHalf .!= separator_simd) != all_nonseparator { 72 | let values:[SIMD4] = highHalf.split(separator: separator) 73 | if keep_lowhalf { 74 | if highHalf[0] == separator { 75 | return [ 76 | Self(lowHalf: lowHalf, highHalf: .init()), 77 | Self(lowHalf: value, highHalf: .init()) 78 | ] 79 | } else { 80 | return [Self(lowHalf: lowHalf, highHalf: .init(highHalf.x, 0))] 81 | } 82 | } else { 83 | for value in values { 84 | array.append(Self(lowHalf: value, highHalf: .init())) 85 | } 86 | } 87 | } else if !keep_lowhalf { 88 | array.append(Self(lowHalf: highHalf, highHalf: .init())) 89 | } 90 | return array.isEmpty ? [self] : array 91 | } 92 | }*/ -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/extensions/simd/SIMDExtensions+TrailingNonzeroByteCount.swift: -------------------------------------------------------------------------------- 1 | 2 | // MARK: SIMD2 3 | extension SIMD2 where Scalar: BinaryInteger { 4 | /// - Complexity: O(1) 5 | @inlinable 6 | public var trailingNonzeroByteCount: Int { 7 | if y == 0 { return 0 } 8 | if x == 0 { return 1 } 9 | return scalarCount 10 | } 11 | } 12 | 13 | // MARK: SIMD4 14 | extension SIMD4 where Scalar: BinaryInteger { 15 | /// - Complexity: O(1) 16 | @inlinable 17 | public var trailingNonzeroByteCount: Int { 18 | let all_nonzero:SIMDMask> = .init(repeating: true) 19 | if (highHalf .!= .zero) != all_nonzero { return highHalf.trailingNonzeroByteCount } 20 | if (lowHalf .!= .zero) != all_nonzero { return 2 + lowHalf.trailingNonzeroByteCount } 21 | return scalarCount 22 | } 23 | } 24 | 25 | // MARK: SIMD8 26 | extension SIMD8 where Scalar: BinaryInteger { 27 | /// - Complexity: O(1) 28 | @inlinable 29 | public var trailingNonzeroByteCount: Int { 30 | let all_nonzero:SIMDMask> = .init(repeating: true) 31 | if (highHalf .!= .zero) != all_nonzero { return highHalf.trailingNonzeroByteCount } 32 | if (lowHalf .!= .zero) != all_nonzero { return 4 + lowHalf.trailingNonzeroByteCount } 33 | return scalarCount 34 | } 35 | } 36 | 37 | // MARK: SIMD16 38 | extension SIMD16 where Scalar: BinaryInteger { 39 | /// - Complexity: O(1) 40 | @inlinable 41 | public var trailingNonzeroByteCount: Int { 42 | let all_nonzero:SIMDMask> = .init(repeating: true) 43 | if (highHalf .!= .zero) != all_nonzero { return highHalf.trailingNonzeroByteCount } 44 | if (lowHalf .!= .zero) != all_nonzero { return 8 + lowHalf.trailingNonzeroByteCount } 45 | return scalarCount 46 | } 47 | } 48 | 49 | // MARK: SIMD32 50 | extension SIMD32 where Scalar: BinaryInteger { 51 | /// - Complexity: O(1) 52 | @inlinable 53 | public var trailingNonzeroByteCount: Int { 54 | let all_nonzero:SIMDMask> = .init(repeating: true) 55 | if (highHalf .!= .zero) != all_nonzero { return highHalf.trailingNonzeroByteCount } 56 | if (lowHalf .!= .zero) != all_nonzero { return 16 + lowHalf.trailingNonzeroByteCount } 57 | return scalarCount 58 | } 59 | } 60 | 61 | // MARK: SIMD64 62 | extension SIMD64 where Scalar: BinaryInteger { 63 | /// - Complexity: O(1) 64 | @inlinable 65 | public var trailingNonzeroByteCount: Int { 66 | let all_nonzero:SIMDMask> = .init(repeating: true) 67 | if (highHalf .!= .zero) != all_nonzero { return highHalf.trailingNonzeroByteCount } 68 | if (lowHalf .!= .zero) != all_nonzero { return 32 + lowHalf.trailingNonzeroByteCount } 69 | return scalarCount 70 | } 71 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/extensions/simd/SIMDExtensions+TrailingString.swift: -------------------------------------------------------------------------------- 1 | 2 | // MARK: SIMD2 3 | extension SIMD2 where Scalar == UInt8 { 4 | /// - Complexity: O(1) 5 | @inlinable 6 | func trailingScalars() -> [Scalar] { 7 | return y == 0 ? [] : x == 0 ? [y] : [x, y] 8 | } 9 | 10 | /// Creates a `String` based on this vector's trailing scalars. 11 | /// 12 | /// - Complexity: O(1) 13 | @inlinable 14 | public func trailingString() -> String { 15 | return String(decoding: trailingScalars(), as: UTF8.self) 16 | } 17 | } 18 | 19 | // MARK: SIMD4 20 | extension SIMD4 where Scalar == UInt8 { 21 | /// - Complexity: O(1) 22 | @inlinable 23 | func trailingScalars() -> [Scalar] { 24 | if (self .!= .zero) == .init(repeating: true) { 25 | return [x, y, z, w] 26 | } 27 | if (highHalf .!= .zero) != .init(repeating: true) { 28 | return highHalf.trailingScalars() 29 | } 30 | return lowHalf.trailingScalars() + [z, w] 31 | } 32 | 33 | /// Creates a `String` based on this vector's trailing scalars. 34 | /// 35 | /// - Complexity: O(1) 36 | @inlinable 37 | public func trailingString() -> String { 38 | return String(decoding: trailingScalars(), as: UTF8.self) 39 | } 40 | } 41 | 42 | // MARK: SIMD8 43 | extension SIMD8 where Scalar == UInt8 { 44 | /// - Complexity: O(1) 45 | @inlinable 46 | func trailingScalars() -> [Scalar] { 47 | if (highHalf .!= .zero) != .init(repeating: true) { 48 | return highHalf.trailingScalars() 49 | } 50 | return lowHalf.trailingScalars() + [highHalf.x, highHalf.y, highHalf.z, highHalf.w] 51 | } 52 | 53 | /// Creates a `String` based on this vector's trailing scalars. 54 | /// 55 | /// - Complexity: O(1) 56 | @inlinable 57 | public func trailingString() -> String { 58 | return String(decoding: trailingScalars(), as: UTF8.self) 59 | } 60 | } 61 | 62 | // MARK: SIMD16 63 | extension SIMD16 where Scalar == UInt8 { 64 | /// - Complexity: O(1) 65 | @inlinable 66 | func trailingScalars() -> [Scalar] { 67 | if (highHalf .!= .zero) != .init(repeating: true) { 68 | return highHalf.trailingScalars() 69 | } 70 | return lowHalf.trailingScalars() + highHalf.scalars() 71 | } 72 | 73 | /// Creates a `String` based on this vector's trailing scalars. 74 | /// 75 | /// - Complexity: O(1) 76 | @inlinable 77 | public func trailingString() -> String { 78 | return String(decoding: trailingScalars(), as: UTF8.self) 79 | } 80 | } 81 | 82 | // MARK: SIMD32 83 | extension SIMD32 where Scalar == UInt8 { 84 | /// - Complexity: O(1) 85 | @inlinable 86 | func trailingScalars() -> [Scalar] { 87 | if (highHalf .!= .zero) != .init(repeating: true) { 88 | return highHalf.trailingScalars() 89 | } 90 | return lowHalf.trailingScalars() + highHalf.scalars() 91 | } 92 | 93 | /// Creates a `String` based on this vector's trailing scalars. 94 | /// 95 | /// - Complexity: O(1) 96 | @inlinable 97 | public func trailingString() -> String { 98 | return String(decoding: trailingScalars(), as: UTF8.self) 99 | } 100 | } 101 | 102 | // MARK: SIMD64 103 | extension SIMD64 where Scalar == UInt8 { 104 | /// - Complexity: O(1) 105 | @inlinable 106 | func trailingScalars() -> [Scalar] { 107 | if (highHalf .!= .zero) != .init(repeating: true) { 108 | return highHalf.trailingScalars() 109 | } 110 | return lowHalf.trailingScalars() + highHalf.scalars() 111 | } 112 | 113 | /// Creates a `String` based on this vector's trailing scalars. 114 | /// 115 | /// - Complexity: O(1) 116 | @inlinable 117 | public func trailingString() -> String { 118 | return String(decoding: trailingScalars(), as: UTF8.self) 119 | } 120 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/extensions/simd/SIMDExtensions+TrailingZeroByteCount.swift: -------------------------------------------------------------------------------- 1 | 2 | // MARK: SIMD2 3 | extension SIMD2 where Scalar: BinaryInteger { 4 | /// - Complexity: O(1) 5 | @inlinable 6 | public var trailingZeroByteCount: Int { 7 | if y != 0 { return 0 } 8 | if x != 0 { return 1 } 9 | return scalarCount 10 | } 11 | } 12 | 13 | // MARK: SIMD4 14 | extension SIMD4 where Scalar: BinaryInteger { 15 | /// - Complexity: O(1) 16 | @inlinable 17 | public var trailingZeroByteCount: Int { 18 | let all_zero:SIMDMask> = .init(repeating: true) 19 | if (highHalf .== .zero) != all_zero { return highHalf.trailingZeroByteCount } 20 | if (lowHalf .== .zero) != all_zero { return 2 + lowHalf.trailingZeroByteCount } 21 | return scalarCount 22 | } 23 | } 24 | 25 | // MARK: SIMD8 26 | extension SIMD8 where Scalar: BinaryInteger { 27 | /// - Complexity: O(1) 28 | @inlinable 29 | public var trailingZeroByteCount: Int { 30 | let all_zero:SIMDMask> = .init(repeating: true) 31 | if (highHalf .== .zero) != all_zero { return highHalf.trailingZeroByteCount } 32 | if (lowHalf .== .zero) != all_zero { return 4 + lowHalf.trailingZeroByteCount } 33 | return scalarCount 34 | } 35 | } 36 | 37 | // MARK: SIMD16 38 | extension SIMD16 where Scalar: BinaryInteger { 39 | /// - Complexity: O(1) 40 | @inlinable 41 | public var trailingZeroByteCount: Int { 42 | let all_zero:SIMDMask> = .init(repeating: true) 43 | if (highHalf .== .zero) != all_zero { return highHalf.trailingZeroByteCount } 44 | if (lowHalf .== .zero) != all_zero { return 8 + lowHalf.trailingZeroByteCount } 45 | return scalarCount 46 | } 47 | } 48 | 49 | // MARK: SIMD32 50 | extension SIMD32 where Scalar: BinaryInteger { 51 | /// - Complexity: O(1) 52 | @inlinable 53 | public var trailingZeroByteCount: Int { 54 | let all_zero:SIMDMask> = .init(repeating: true) 55 | if (highHalf .== .zero) != all_zero { return highHalf.trailingZeroByteCount } 56 | if (lowHalf .== .zero) != all_zero { return 16 + lowHalf.trailingZeroByteCount } 57 | return scalarCount 58 | } 59 | } 60 | 61 | // MARK: SIMD64 62 | extension SIMD64 where Scalar: BinaryInteger { 63 | /// - Complexity: O(1) 64 | @inlinable 65 | public var trailingZeroByteCount: Int { 66 | let all_zero:SIMDMask> = .init(repeating: true) 67 | if (highHalf .== .zero) != all_zero { return highHalf.trailingZeroByteCount } 68 | if (lowHalf .== .zero) != all_zero { return 32 + lowHalf.trailingZeroByteCount } 69 | return scalarCount 70 | } 71 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/middleware/CORSMiddlewareAllowedOrigin.swift: -------------------------------------------------------------------------------- 1 | 2 | public enum CORSMiddlewareAllowedOrigin: Sendable { 3 | case all 4 | case any(Set) 5 | case custom(String) 6 | case none 7 | case originBased 8 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/middleware/CORSMiddlewareProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Core CORS Middleware protocol that indicates the middleware controls CORS. 3 | public protocol CORSMiddlewareProtocol: MiddlewareProtocol { 4 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/middleware/DynamicMetricMiddlewareProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Core Dynamic Metric Middleware protocol that records metrics for dynamic requests. 3 | public protocol DynamicMetricMiddlewareProtocol: MetricMiddlewareProtocol { 4 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/middleware/DynamicMiddlewareProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Core Dynamic Middleware protocol which handles requests to dynamic routes. 3 | public protocol DynamicMiddlewareProtocol: MiddlewareProtocol { 4 | /// Load logic when the middleware is ready to process requests. 5 | @inlinable 6 | mutating func load() 7 | 8 | /// The handler. 9 | /// 10 | /// - Parameters: 11 | /// - request: The incoming network request. 12 | /// - response: The current response for the request. 13 | /// - Returns: Whether or not to continue processing the request. 14 | @inlinable 15 | func handle( 16 | request: inout any HTTPRequestProtocol, 17 | response: inout any DynamicResponseProtocol 18 | ) async throws -> Bool 19 | } 20 | 21 | extension DynamicMiddlewareProtocol { 22 | @inlinable 23 | public mutating func load() { 24 | } 25 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/middleware/FileMiddlewareProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Core File Middleware protocol that allows files to be read. 3 | public protocol FileMiddlewareProtocol: Sendable { // TODO: finish 4 | func load() 5 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/middleware/MetricMiddlewareProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Core Metric Middleware protocol that records metrics for requests. 3 | public protocol MetricMiddlewareProtocol: MiddlewareProtocol { 4 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/middleware/MiddlewareProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 3 | import SwiftSyntax 4 | import SwiftSyntaxMacros 5 | #endif 6 | 7 | /// Core Middleware protocol. 8 | public protocol MiddlewareProtocol: CustomDebugStringConvertible, Sendable { 9 | 10 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 11 | /// Parsing logic for this middleware. 12 | /// 13 | /// - Parameters: 14 | /// - function: SwiftSyntax expression that represents this middleware at compile time. 15 | static func parse(context: some MacroExpansionContext, _ function: FunctionCallExprSyntax) -> Self 16 | #endif 17 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/middleware/RateLimitMiddlewareProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Core Rate Limit Middleware protocol that controls how many requests will be accepted for a route over a certain duration. 3 | public protocol RateLimitMiddlewareProtocol: MiddlewareProtocol { // TODO: finish? 4 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/middleware/storage/StaticMiddlewareStorageProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | public protocol StaticMiddlewareStorageProtocol: Sendable { 3 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/responders/ConditionalRouteResponderProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Core Conditional Route Responder protocol that selects a route responder based on a request. 3 | public protocol ConditionalRouteResponderProtocol: CustomDebugStringConvertible, RouteResponderProtocol { 4 | /// Try to write a response to a socket. 5 | /// 6 | /// - Parameters: 7 | /// - router: The router this route belongs to. 8 | /// - received: The instant the socket was accepted. 9 | /// - loaded: The instant the socket loaded its default values. 10 | /// - socket: The socket to write to. 11 | /// - request: The socket's request. 12 | /// - Returns: Whether or not a route responder responded to the request. 13 | @inlinable 14 | func respond( 15 | router: borrowing HTTPRouter, 16 | received: ContinuousClock.Instant, 17 | loaded: ContinuousClock.Instant, 18 | socket: borrowing Socket, 19 | request: inout Socket.ConcreteRequest 20 | ) async throws -> Bool 21 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/responders/DynamicRouteResponderProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Core Dynamic Route Responder protocol that handles requests to dynamic routes. 3 | public protocol DynamicRouteResponderProtocol: RouteResponderProtocol { 4 | /// Yields the path of the route. 5 | @inlinable 6 | func forEachPathComponent(_ yield: (PathComponent) -> Void) 7 | 8 | /// - Returns: The `PathComponent` located at the given index. 9 | func pathComponent(at index: Int) -> PathComponent 10 | 11 | /// The number of path components this route contains. 12 | var pathComponentsCount: Int { get } 13 | 14 | /// Yields the index where parameters are location in the path. 15 | @inlinable 16 | func forEachPathComponentParameterIndex(_ yield: (Int) -> Void) 17 | 18 | /// Default `DynamicResponseProtocol` value computed at compile time taking into account all static middleware. 19 | var defaultResponse: any DynamicResponseProtocol { get } 20 | 21 | /// Writes a response to a socket. 22 | /// 23 | /// - Parameters: 24 | /// - socket: The socket to write to. 25 | /// - request: The socket's request. 26 | /// - response: The response to send to the socket. 27 | @inlinable 28 | func respond( 29 | to socket: borrowing Socket, 30 | request: inout any HTTPRequestProtocol, 31 | response: inout any DynamicResponseProtocol 32 | ) async throws 33 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/responders/ErrorResponderProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | import Logging 3 | 4 | /// Core Error Middleware protocol that handles errors thrown from requests. 5 | public protocol ErrorResponderProtocol: RouteResponderProtocol { 6 | /// Writes a response to a socket. 7 | @inlinable 8 | func respond( 9 | socket: borrowing Socket, 10 | error: E, 11 | request: inout any HTTPRequestProtocol, 12 | logger: Logger 13 | ) async 14 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/responders/RouteResponderProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Core Route Responder protocol that writes its responses to requests. 3 | public protocol RouteResponderProtocol: CustomDebugStringConvertible, Sendable { 4 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/responders/StaticRouteResponderProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Core Static Route Responder protocol that handles requests to static routes. 3 | public protocol StaticRouteResponderProtocol: RouteResponderProtocol { 4 | /// Writes a response to a socket. 5 | @inlinable 6 | func respond(to socket: borrowing Socket) async throws 7 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/routes/DynamicRequestTimestamps.swift: -------------------------------------------------------------------------------- 1 | 2 | public struct DynamicRequestTimestamps: Sendable { 3 | /// When the request was accepted. 4 | public var received:ContinuousClock.Instant 5 | 6 | /// When the request loaded its default values. 7 | public var loaded:ContinuousClock.Instant 8 | 9 | /// When the request was completely processed. 10 | public var processed:ContinuousClock.Instant 11 | 12 | public init( 13 | received: ContinuousClock.Instant, 14 | loaded: ContinuousClock.Instant, 15 | processed: ContinuousClock.Instant 16 | ) { 17 | self.received = received 18 | self.loaded = loaded 19 | self.processed = processed 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/routes/DynamicResponseProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Core Dynamic Response protocol that builds a HTTP Message to dynamic routes before sending it to the client. 3 | public protocol DynamicResponseProtocol: Sendable, CustomDebugStringConvertible { 4 | /// Timestamps when request events happen. 5 | var timestamps: DynamicRequestTimestamps { get set } 6 | 7 | /// - Parameters: 8 | /// - index: Index of a path component. 9 | /// - Returns: The parameter located at the given path component index. 10 | @inlinable 11 | func parameter(at index: Int) -> String 12 | 13 | @inlinable 14 | mutating func setParameter(at index: Int, value: InlineVLArray) 15 | 16 | @inlinable 17 | mutating func appendParameter(value: InlineVLArray) 18 | 19 | @inlinable 20 | func yieldParameters(_ yield: (String) -> Void) 21 | 22 | /// Set the response's HTTP Version. 23 | /// 24 | /// - Parameters: 25 | /// - version: The new HTTP Version to set. 26 | @inlinable 27 | mutating func setHTTPVersion(_ version: HTTPVersion) 28 | 29 | /// Set the response's status. 30 | /// 31 | /// Default behavior of this function calls `setStatusCode(code:)` with the given type's code. 32 | /// 33 | /// - Parameters: 34 | /// - status: A concrete type conforming to `HTTPResponseStatus.StorageProtocol`. 35 | @inlinable 36 | mutating func setStatus(_ status: T) 37 | 38 | /// Set the response's status code. 39 | /// 40 | /// - Parameters: 41 | /// - code: The new status code to set. 42 | @inlinable 43 | mutating func setStatusCode(_ code: HTTPResponseStatus.Code) 44 | 45 | /// Set a response header to the given value. 46 | /// 47 | /// - Parameters: 48 | /// - key: The header you want to modify. 49 | /// - value: The new header value to set. 50 | @inlinable 51 | mutating func setHeader(key: String, value: String) 52 | 53 | @inlinable 54 | mutating func appendCookie(_ cookie: Cookie) 55 | 56 | /// Set the body of the message. 57 | /// 58 | /// - Parameters: 59 | /// - body: The new body to set. 60 | @inlinable 61 | mutating func setBody(_ body: T) 62 | 63 | /// Writes an HTTP Message to a socket. 64 | /// 65 | /// - Parameters: 66 | /// - socket: The socket to write to. 67 | @inlinable 68 | func write(to socket: borrowing Socket) throws 69 | } 70 | 71 | extension DynamicResponseProtocol { 72 | @inlinable 73 | public mutating func setStatus(_ status: T) { 74 | setStatusCode(status.code) 75 | } 76 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/routes/RedirectionRouteProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | import SwiftCompression 3 | 4 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 5 | import SwiftSyntax 6 | import SwiftSyntaxMacros 7 | #endif 8 | 9 | /// Core Redirection Route protocol that redirects certain endpoints to other endpoints. 10 | public protocol RedirectionRouteProtocol: RouteProtocol { 11 | /// The endpoint that has been moved. 12 | var from: [String] { get } 13 | 14 | /// The redirection endpoint. 15 | var to: [String] { get } 16 | 17 | /// Status of this redirection route. 18 | var status: HTTPResponseStatus.Code { get } 19 | 20 | /// The HTTP Message of this route. Computed at compile time. 21 | /// 22 | /// - Throws: any error; if thrown: a compile diagnostic shown describing the issue. 23 | /// - Returns: a string representing a complete HTTP Message. 24 | func response() throws -> String 25 | 26 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 27 | /// Parsing logic for this route. Computed at compile time. 28 | /// 29 | /// - Parameters: 30 | /// - context: The macro expansion context where this route is being parsed from. 31 | /// - version: The `HTTPVersion` of the `HTTPRouterProtocol` this middleware is assigned to. 32 | /// - function: SwiftSyntax expression that represents this route. 33 | static func parse(context: some MacroExpansionContext, version: HTTPVersion, _ function: FunctionCallExprSyntax) -> Self? 34 | #endif 35 | } 36 | 37 | // MARK: SwiftCompression 38 | // Redirects do not use compression. 39 | extension RedirectionRouteProtocol { 40 | public var supportedCompressionAlgorithms: Set { 41 | get { [] } 42 | set {} 43 | } 44 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/routes/RouteProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | import SwiftCompression 3 | 4 | /// Core Route protocol. 5 | public protocol RouteProtocol: CustomDebugStringConvertible, Sendable { 6 | /// `HTTPVersion` associated with this route. 7 | var version: HTTPVersion { get } 8 | 9 | /// HTTP Request Method of this route. 10 | var method: any HTTPRequestMethodProtocol { get } 11 | 12 | /// Supported compression algorithms this route can use to compress its response. 13 | var supportedCompressionAlgorithms: Set { get set } 14 | 15 | /// Whether or not the path for this route is case-sensitive. 16 | var isCaseSensitive: Bool { get } 17 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/storage/DynamicResponderStorageProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | public protocol DynamicResponderStorageProtocol: CustomDebugStringConvertible, Sendable { 3 | /// Try to write a response to a socket. 4 | /// 5 | /// - Parameters: 6 | /// - router: The router this storage belongs to. 7 | /// - received: The instant the socket was accepted. 8 | /// - loaded: The instant the socket loaded its default values. 9 | /// - socket: The socket to write to. 10 | /// - request: The socket's request. 11 | /// - Returns: Whether or not a response was sent. 12 | @inlinable 13 | func respond( 14 | router: borrowing HTTPRouter, 15 | received: ContinuousClock.Instant, 16 | loaded: ContinuousClock.Instant, 17 | socket: borrowing Socket, 18 | request: inout Socket.ConcreteRequest 19 | ) async throws -> Bool 20 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/storage/RouterResponderStorageProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | public protocol RouterResponderStorageProtocol: Sendable { 3 | /// Try to write a response to a socket. 4 | /// 5 | /// - Parameters: 6 | /// - router: The router this storage belongs to. 7 | /// - received: The instant the socket was accepted. 8 | /// - loaded: The instant the socket loaded its default values. 9 | /// - socket: The socket to write to. 10 | /// - request: The socket's request. 11 | /// - Returns: Whether or not a response was sent. 12 | @inlinable 13 | func respond( 14 | router: borrowing HTTPRouter, 15 | received: ContinuousClock.Instant, 16 | loaded: ContinuousClock.Instant, 17 | socket: borrowing Socket, 18 | request: inout Socket.ConcreteRequest 19 | ) async throws -> Bool 20 | 21 | /// Try to write a response to a socket, only checking static storage. 22 | /// 23 | /// - Parameters: 24 | /// - router: The router this storage belongs to. 25 | /// - socket: The socket to write to. 26 | /// - startLine: The socket's target endpoint. 27 | /// - Returns: Whether or not a response was sent. 28 | @inlinable 29 | func respondStatically( 30 | router: borrowing HTTPRouter, 31 | socket: borrowing Socket, 32 | startLine: SIMD64 33 | ) async throws -> Bool 34 | 35 | /// Try to write a response to a socket, only checking dynamic storage. 36 | /// 37 | /// - Parameters: 38 | /// - router: The router this storage belongs to. 39 | /// - received: The instant the socket was accepted. 40 | /// - loaded: The instant the socket loaded its default values. 41 | /// - socket: The socket to write to. 42 | /// - request: The socket's request. 43 | /// - Returns: Whether or not a response was sent. 44 | @inlinable 45 | func respondDynamically( 46 | router: borrowing HTTPRouter, 47 | received: ContinuousClock.Instant, 48 | loaded: ContinuousClock.Instant, 49 | socket: borrowing Socket, 50 | request: inout Socket.ConcreteRequest, 51 | ) async throws -> Bool 52 | } -------------------------------------------------------------------------------- /Sources/DestinyBlueprint/storage/StaticResponderStorageProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | public protocol StaticResponderStorageProtocol: CustomDebugStringConvertible, Sendable { 3 | /// Try to write a response to a socket. 4 | /// 5 | /// - Parameters: 6 | /// - router: The router this storage belongs to. 7 | /// - socket: The socket to write to. 8 | /// - startLine: The socket's requested endpoint. 9 | /// - Returns: Whether or not a response was sent. 10 | @inlinable 11 | func respond( 12 | router: borrowing HTTPRouter, 13 | socket: borrowing Socket, 14 | startLine: SIMD64 15 | ) async throws -> Bool 16 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/Application.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | import Logging 4 | import ServiceLifecycle 5 | 6 | public struct Application: ApplicationProtocol { 7 | public static private(set) var shared:Application! = nil 8 | 9 | public let serviceGroup:ServiceGroup 10 | public let logger:Logger 11 | 12 | public init( 13 | server: T, 14 | services: [Service] = [], 15 | logger: Logger 16 | ) { 17 | var services = services 18 | services.insert(server, at: 0) 19 | serviceGroup = ServiceGroup(services: services, logger: logger) 20 | self.logger = logger 21 | Self.shared = self 22 | } 23 | public func run() async throws { 24 | try await serviceGroup.run() 25 | } 26 | 27 | public func shutdown() async throws { 28 | logger.notice("Application shutting down...") 29 | await serviceGroup.triggerGracefulShutdown() 30 | try await gracefulShutdown() 31 | logger.notice("Application shutdown successfully") 32 | } 33 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/Charset.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(SwiftSyntax) 3 | import SwiftSyntax 4 | #endif 5 | 6 | // MARK: Charset 7 | /// HTTP charset encodings. 8 | public enum Charset: String, CustomDebugStringConvertible, Sendable { 9 | case any 10 | case basicMultilingualPlane 11 | case bocu1 12 | case iso8859_5 13 | case scsu 14 | case ucs2 15 | case ucs4 16 | case utf8 17 | case utf16 18 | case utf16be 19 | case utf16le 20 | case utf32 21 | 22 | // MARK: Debug description 23 | @inlinable 24 | public var debugDescription: String { 25 | "Charset.\(rawValue)" 26 | } 27 | 28 | // MARK: Raw name 29 | @inlinable 30 | public var rawName: String { 31 | switch self { 32 | case .any: "*" 33 | case .basicMultilingualPlane: "BMP" 34 | case .bocu1: "BOCU-1" 35 | case .iso8859_5: "ISO-8859-5" 36 | case .scsu: "SCSU" 37 | case .ucs2: "UCS-2" 38 | case .ucs4: "UCS-4" 39 | case .utf8: "UTF-8" 40 | case .utf16: "UTF-16" 41 | case .utf16be: "UTF-16BE" 42 | case .utf16le: "UTF-16LE" 43 | case .utf32: "UTF-32" 44 | } 45 | } 46 | } 47 | 48 | #if canImport(SwiftSyntax) 49 | /// MARK: SwiftSyntax 50 | extension Charset { 51 | public init?(expr: ExprSyntax) { 52 | guard let string = expr.memberAccess?.declName.baseName.text ?? expr.stringLiteral?.string.lowercased() else { 53 | return nil 54 | } 55 | if let value = Self(rawValue: string) { 56 | self = value 57 | } else { 58 | switch string { 59 | case "bocu-1": self = .bocu1 60 | case "iso-8859-5": self = .iso8859_5 61 | case "ucs-2": self = .ucs2 62 | case "ucs-4": self = .ucs4 63 | case "utf-8": self = .utf8 64 | case "utf-16": self = .utf16 65 | case "utf-16be": self = .utf16be 66 | case "utf-16le": self = .utf16le 67 | case "utf-32": self = .utf32 68 | default: return nil 69 | } 70 | } 71 | } 72 | } 73 | #endif -------------------------------------------------------------------------------- /Sources/DestinyDefaults/DestinyDefaults.swift: -------------------------------------------------------------------------------- 1 | import SwiftDiagnostics 2 | import SwiftSyntax 3 | import SwiftSyntaxMacros 4 | 5 | public typealias DestinyRoutePathType = SIMD64 6 | 7 | @freestanding(declaration, names: arbitrary) 8 | macro HTTPFieldContentType( 9 | category: String, 10 | values: [String:HTTPFieldContentTypeDetails] 11 | ) = #externalMacro(module: "DestinyUtilityMacros", type: "HTTPFieldContentType") 12 | 13 | @freestanding(declaration, names: arbitrary) 14 | macro httpRequestMethods( 15 | _ entries: [(memberName: String, method: String)] 16 | ) = #externalMacro(module: "DestinyUtilityMacros", type: "HTTPRequestMethods") 17 | 18 | struct HTTPFieldContentTypeDetails { 19 | let httpValue:String 20 | let fileExtensions:Set 21 | 22 | init(_ httpValue: String, fileExtensions: Set = []) { 23 | self.httpValue = httpValue 24 | self.fileExtensions = fileExtensions 25 | } 26 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/HTTPStartLine.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | public struct HTTPStartLine: HTTPStartLineProtocol { 5 | public typealias Method = InlineArray<20, UInt8> 6 | public typealias RequestTarget = InlineArray<64, UInt8> 7 | 8 | public let method:Method 9 | public let methodCount:Int 10 | public let path:RequestTarget 11 | public let pathCount:Int 12 | public let version:InlineArray<8, UInt8> 13 | 14 | public let endIndex:Int 15 | 16 | public init(buffer: T) throws where T.Element == UInt8 { 17 | let (methodArray, methodSpaceIndex):(Method, Int) = buffer.firstSlice(separator: .space, defaultValue: 0) 18 | let (pathArray, pathSpaceIndex):(RequestTarget, Int) = buffer.firstSlice(separator: .space, defaultValue: 0, offset: methodSpaceIndex+1) 19 | let versionArray:InlineArray<8, UInt8> = buffer.slice(startIndex: pathSpaceIndex+1, endIndex: pathSpaceIndex+9, defaultValue: 0) 20 | guard let v = HTTPVersion(token: versionArray) else { 21 | throw SocketError.malformedRequest() 22 | } 23 | method = methodArray 24 | methodCount = methodSpaceIndex 25 | path = pathArray 26 | pathCount = methodSpaceIndex.distance(to: pathSpaceIndex)-1 27 | version = versionArray 28 | endIndex = pathSpaceIndex+9 29 | } 30 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/RouteGroupProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 5 | import SwiftSyntax 6 | import SwiftSyntaxMacros 7 | #endif 8 | 9 | /// Core Route Group protocol that handles routes grouped by a single endpoint. 10 | public protocol RouteGroupProtocol: CustomDebugStringConvertible, Sendable { 11 | 12 | /// - Returns: Whether or not this router group responded to the request. 13 | @inlinable 14 | func respond( 15 | router: borrowing HTTPRouter, 16 | received: ContinuousClock.Instant, 17 | loaded: ContinuousClock.Instant, 18 | socket: borrowing Socket, 19 | request: inout Socket.ConcreteRequest 20 | ) async throws -> Bool 21 | 22 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 23 | /// Parsing logic for this router group. 24 | /// 25 | /// - Parameters: 26 | /// - context: The macro expansion context. 27 | /// - version: The `HTTPVersion` of the router this router group belongs to. 28 | /// - staticMiddleware: The static middleware of the router this router group belongs to. 29 | /// - dynamicMiddleware: The dynamic middleware of the router this router group belongs to. 30 | /// - function: SwiftSyntax expression that represents this router group at compile time. 31 | static func parse( 32 | context: some MacroExpansionContext, 33 | version: HTTPVersion, 34 | staticMiddleware: [any StaticMiddlewareProtocol], 35 | dynamicMiddleware: [any DynamicMiddlewareProtocol], 36 | _ function: FunctionCallExprSyntax 37 | ) -> Self 38 | #endif 39 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/commands/BootCommands.swift: -------------------------------------------------------------------------------- 1 | 2 | import ArgumentParser 3 | 4 | /// Defines what boot options can be used for a server. 5 | public struct BootCommands: ParsableCommand { 6 | @Option( 7 | name: .init([ 8 | .customLong("hostname"), 9 | .customShort("h") 10 | ]), 11 | help: "Hostname the server uses." 12 | ) 13 | public var hostname:String? 14 | 15 | @Option( 16 | name: .init([ 17 | .customLong("port"), 18 | .customShort("p") 19 | ]), 20 | help: "Port to run the server on." 21 | ) 22 | public var port:UInt16? 23 | 24 | @Option( 25 | name: .init([ 26 | .customLong("backlog"), 27 | .customShort("b") 28 | ]), 29 | help: "Maximum amount of pending connections the server can have." 30 | ) 31 | public var backlog:Int32? 32 | 33 | @Option( 34 | name: .init([ 35 | .customLong("reuseaddress"), 36 | .customLong("ra", withSingleDash: true) 37 | ]), 38 | help: "Allows the server to reuse the address if its in a TIME_WAIT state, avoiding \"address already in use\" errors when restarting quickly." 39 | ) 40 | public var reuseaddress:Bool = true 41 | 42 | @Option( 43 | name: .init([ 44 | .customLong("reuseport"), 45 | .customLong("rp", withSingleDash: true) 46 | ]), 47 | help: "Allows multiple processes to bind to the same port, avoiding contention on a single socket, while enabling load balancing at the kernel level." 48 | ) 49 | public var reuseport:Bool = true 50 | 51 | @Option( 52 | name: .init([ 53 | .customLong("tcpnodelay"), 54 | .customLong("tcpnd", withSingleDash: true) 55 | ]), 56 | help: "Disables Nagle's algorithm, which buffers small packets before sending them, to improve latency for real-time applications." 57 | ) 58 | public var tcpnodelay:Bool = true 59 | 60 | public init() { 61 | } 62 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/cookies/HTTPCookieFlag.swift: -------------------------------------------------------------------------------- 1 | 2 | public enum HTTPCookieFlag { 3 | } 4 | 5 | // MARK: SameSite 6 | extension HTTPCookieFlag { 7 | public enum SameSite: String, Sendable { 8 | case strict 9 | case lax 10 | case none 11 | 12 | @inlinable 13 | public var httpValue: String { 14 | switch self { 15 | case .strict: return "Strict" 16 | case .lax: return "Lax" 17 | case .none: return "None" 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/cookies/HTTPCookieStorageProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | public protocol HTTPCookieStorageProtocol: Sendable { 3 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/extensions/SequenceExtensions.swift: -------------------------------------------------------------------------------- 1 | 2 | // MARK: Collection 3 | extension Collection { 4 | /// - Returns: The element at the given index, if the index is within bounds, otherwise `nil`. 5 | @usableFromInline 6 | package func get(_ index: Index) -> Element? { 7 | return index < endIndex && index >= startIndex ? self[index] : nil 8 | } 9 | 10 | @usableFromInline 11 | package func getPositive(_ index: Index) -> Element? { 12 | return index < endIndex ? self[index] : nil 13 | } 14 | } 15 | 16 | // MARK: Array 17 | extension Array where Element == SIMD64 { 18 | // TODO: finish 19 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/extensions/UnsafePointerExtensions.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(Android) 3 | import Android 4 | #elseif canImport(Darwin) 5 | import Darwin 6 | #elseif canImport(Glibc) 7 | import Glibc 8 | #elseif canImport(Musl) 9 | import Musl 10 | #elseif canImport(WinSDK) 11 | import WinSDK 12 | #endif 13 | 14 | extension UnsafeMutableBufferPointer where Element == UInt8 { 15 | @inlinable 16 | func copyBuffer(_ buffer: UnsafeBufferPointer, at index: Int) { 17 | var index = index 18 | copyBuffer(buffer, at: &index) 19 | } 20 | 21 | @inlinable 22 | func copyBuffer(_ buffer: UnsafeMutableBufferPointer, at index: Int) { 23 | var index = index 24 | copyBuffer(buffer, at: &index) 25 | } 26 | } 27 | 28 | extension UnsafeMutableBufferPointer where Element == UInt8 { 29 | @inlinable 30 | func copyBuffer(_ buffer: UnsafeBufferPointer, at index: inout Int) { 31 | #if canImport(Android) || canImport(Darwin) || canImport(Glibc) || canImport(Musl) || canImport(WinSDK) 32 | memcpy(baseAddress! + index, buffer.baseAddress!, buffer.count) 33 | #else 34 | buffer.forEach { 35 | self[index] = $0 36 | index += 1 37 | } 38 | #endif 39 | } 40 | 41 | @inlinable 42 | func copyBuffer(_ buffer: UnsafeMutableBufferPointer, at index: inout Int) { 43 | #if canImport(Android) || canImport(Darwin) || canImport(Glibc) || canImport(Musl) || canImport(WinSDK) 44 | memcpy(baseAddress! + index, buffer.baseAddress!, buffer.count) 45 | #else 46 | buffer.forEach { 47 | self[index] = $0 48 | index += 1 49 | } 50 | #endif 51 | } 52 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/mediaTypes/HTTPMediaTypes+Audio.swift: -------------------------------------------------------------------------------- 1 | 2 | extension HTTPMediaType { 3 | #HTTPFieldContentType( 4 | category: "audio", 5 | values: [ 6 | "aac": .init("", fileExtensions: ["aac"]), 7 | 8 | "mp4": .init("", fileExtensions: ["mp4"]), 9 | "mpeg": .init("", fileExtensions: ["mpeg"]), 10 | 11 | "ogg": .init("", fileExtensions: ["ogg"]) 12 | ] 13 | ) 14 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/mediaTypes/HTTPMediaTypes+Font.swift: -------------------------------------------------------------------------------- 1 | 2 | extension HTTPMediaType { 3 | #HTTPFieldContentType( 4 | category: "font", 5 | values: [ 6 | "collection": .init(""), 7 | "otf": .init(""), 8 | "sfnt": .init(""), 9 | "ttf": .init(""), 10 | "woff": .init(""), 11 | "woff2": .init("") 12 | ] 13 | ) 14 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/mediaTypes/HTTPMediaTypes+Haptics.swift: -------------------------------------------------------------------------------- 1 | 2 | extension HTTPMediaType { 3 | #HTTPFieldContentType( 4 | category: "haptics", 5 | values: [ 6 | "ivs": .init(""), 7 | "hjif": .init(""), 8 | "hmpg": .init("") 9 | ] 10 | ) 11 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/mediaTypes/HTTPMediaTypes+Image.swift: -------------------------------------------------------------------------------- 1 | 2 | extension HTTPMediaType { 3 | #HTTPFieldContentType( 4 | category: "image", 5 | values: [ 6 | "aces": .init(""), 7 | "apng": .init(""), 8 | "avci": .init(""), 9 | "avcs": .init(""), 10 | "avif": .init(""), 11 | "bmp": .init(""), 12 | "cgm": .init(""), 13 | "dicomRLE": .init("dicom-rle"), 14 | "dpx": .init(""), 15 | "emf": .init(""), 16 | "example": .init(""), 17 | "fits": .init(""), 18 | "g3fax": .init(""), 19 | "gif": .init("", fileExtensions: ["gif"]), 20 | "heic": .init(""), 21 | "heicSequence": .init("heic-sequence"), 22 | "heif": .init(""), 23 | "heifSequence": .init("heif-sequence"), 24 | "hej2k": .init(""), 25 | "ief": .init(""), 26 | "j2c": .init(""), 27 | "jls": .init(""), 28 | "jp2": .init(""), 29 | "jpeg": .init("", fileExtensions: ["jpeg", "jpg"]), 30 | "jph": .init(""), 31 | "jphc": .init(""), 32 | "jpm": .init(""), 33 | "jpx": .init(""), 34 | "jxl": .init(""), 35 | "jxr": .init(""), 36 | "jxrA": .init(""), 37 | "jxrS": .init(""), 38 | "jxs": .init(""), 39 | "jxsc": .init(""), 40 | "jxsi": .init(""), 41 | "jxss": .init(""), 42 | "ktx": .init(""), 43 | "ktx2": .init(""), 44 | "naplps": .init(""), 45 | "png": .init("", fileExtensions: ["png"]), 46 | "prsBTIF": .init("prs.btif"), 47 | "prsPTI": .init("prs.bti"), 48 | "pwgRaster": .init("pwg-raster"), 49 | "svgXML": .init("svg+xml"), 50 | "t38": .init(""), 51 | "tiff": .init(""), 52 | "tiffFX": .init("tiff-fx"), 53 | 54 | "psd": .init("vnd.adobe.photoshop"), 55 | "airzipAcceleratorAZV": .init("cnd.airzip.accelerator.azv"), 56 | "cnsINF2": .init("vnd.cns.inf2"), 57 | "deceGraphic": .init("vnd.dece.graphic"), 58 | "djvu": .init("vnd.djvu"), 59 | "dwg": .init("vnd.dwg"), 60 | "dxf": .init("vnd.dxf"), 61 | "dvbSubtitle": .init("vnd.dvb.subtitle"), 62 | "fastbidsheet": .init("vnd.fastbidsheet"), 63 | "fpx": .init("vnd.fpx"), 64 | "fst": .init("vnd.fst"), 65 | "fujixeroxEdmicsMMR": .init("vnd.fujixerox.edmics-mmr"), 66 | "fujixeroxEdmicsRLC": .init("vnd.fujixerox.edmics-rlc"), 67 | "globalGraphicsPGB": .init("vnd.globalgraphics.pgb"), 68 | "microsoftIcon": .init("vnd.microsoft.icon"), 69 | "mix": .init("vnd.mix"), 70 | "msModi": .init("vnd.ms-modi"), 71 | "mozillaAPNG": .init("vnd.mozilla.apng"), 72 | "netFPX": .init("vnd.net-fpx"), 73 | "pcoB16": .init("vnd.pco.b16"), 74 | "radiance": .init("vnd.radiance"), 75 | "sealedPNG": .init("vnd.sealed.png"), 76 | "sealedMediaSoftSealGIF": .init("vnd.sealedmedia.softseal.gif"), 77 | "sealedMediaSoftSealJPG": .init("vnd.sealedmedia.softseal.jpg"), 78 | "svf": .init("vnd.svf"), 79 | "tencentTap": .init("vnd.tencent.tap"), 80 | "vtf": .init("vnd.valve.source.texture"), 81 | "wapWBMP": .init("vnd.wap.wbmp"), 82 | "xiff": .init("vnd.xiff"), 83 | "zbrushPCX": .init("vnd.zbrush.pcx"), 84 | "webp": .init(""), 85 | "wmf": .init("") 86 | ] 87 | ) 88 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/mediaTypes/HTTPMediaTypes+Message.swift: -------------------------------------------------------------------------------- 1 | 2 | extension HTTPMediaType { 3 | #HTTPFieldContentType( 4 | category: "message", 5 | values: [ 6 | "bhttp": .init(""), 7 | "cpim": .init("CPIM"), 8 | "deliveryStatus": .init("delivery-status"), 9 | "dispositionNotification": .init("disposition-notification"), 10 | "example": .init(""), 11 | "externalBody": .init("external-body"), 12 | "feedbackReport": .init("feedback-report"), 13 | "global": .init(""), 14 | "globalDeliveryStatus": .init("global-delivery-status"), 15 | "globalDispositionNotification": .init("global-disposition-notification"), 16 | "globalHeaders": .init("global-headers"), 17 | "http": .init(""), 18 | "imdnXML": .init("imdn+xml"), 19 | "mls": .init(""), 20 | "ohttpReq": .init("ohttp-req"), 21 | "ohttpRes": .init("ohttp-res"), 22 | "partial": .init(""), 23 | "rfc822": .init(""), 24 | "sip": .init(""), 25 | "sipfrag": .init(""), 26 | "trackingStatus": .init("tracking-status"), 27 | "wsc": .init("vnd.wfa.wsc") 28 | ] 29 | ) 30 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/mediaTypes/HTTPMediaTypes+Model.swift: -------------------------------------------------------------------------------- 1 | 2 | extension HTTPMediaType { 3 | #HTTPFieldContentType( 4 | category: "model", 5 | values: [ 6 | "_3mf": .init("3mf"), 7 | "e57": .init(""), 8 | "example": .init(""), 9 | "gltfBinary": .init("gltf-binary"), 10 | "gltfJSON": .init("gltf+json"), 11 | "jt": .init("JT"), 12 | "iges": .init(""), 13 | "mesh": .init(""), 14 | "mtl": .init(""), 15 | "obj": .init(""), 16 | "prc": .init(""), 17 | "step": .init(""), 18 | "stepXML": .init("step+xml"), 19 | "stepZip": .init("step+zip"), 20 | "stepXMLZip": .init("step-xml+zip"), 21 | "stl": .init(""), 22 | "u3d": .init(""), 23 | 24 | "bary": .init("vnd.bary"), 25 | "cld": .init("vnd.cld"), 26 | "colladaXML": .init("vnd.collada+xml"), 27 | "dwf": .init("vnd.dwf"), 28 | "_3dm": .init("vnd.flatland.3dml"), 29 | "_3dml": .init("vnd.flatland.3dml"), 30 | "gdl": .init("vnd.gld"), 31 | "gsGdl": .init("vnd.gs-gdl"), 32 | "gtw": .init("vnd.gtw"), 33 | "momlXML": .init("vnd.moml+xml"), 34 | "mts": .init("vnd.mts"), 35 | "opengex": .init("vnd.opengex"), 36 | "parasolidTransmitBinary": .init("vnd.parasolid.transmit.binary"), 37 | "parasolidTransmitText": .init("vnd.parasolid.transmit.text"), 38 | "pythaPyox": .init("vnd.pytha.pyox"), 39 | "rosetteAnnotatedDataModel": .init("vnd.rosette.annotated-data-model"), 40 | "sapVds": .init("vnd.sap.vds"), 41 | "usda": .init("vnd.usda"), 42 | "usdz": .init("vnd.usdz+zip"), 43 | "bsp": .init("vnd.valve.source.compiled-map"), 44 | "vtu": .init("vnd.vtu"), 45 | "vrml": .init("vrml"), 46 | "x3dv": .init("x3d-vrml"), 47 | "x3db": .init("x3d+fastinfoset") 48 | ] 49 | ) 50 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/mediaTypes/HTTPMediaTypes+Multipart.swift: -------------------------------------------------------------------------------- 1 | 2 | extension HTTPMediaType { 3 | #HTTPFieldContentType( 4 | category: "multipart", 5 | values: [ 6 | "alternative": .init(""), 7 | "appledouble": .init(""), 8 | "byteranges": .init(""), 9 | "digest": .init(""), 10 | "encrypted": .init(""), 11 | "example": .init(""), 12 | "formData": .init("form-data"), 13 | "headerSet": .init("header-set"), 14 | "mixed": .init(""), 15 | "multilingual": .init(""), 16 | "parallel": .init(""), 17 | "related": .init(""), 18 | "report": .init(""), 19 | "signed": .init(""), 20 | "medPlus": .init("vnd.bint.med-plus"), 21 | "voiceMessage": .init("voice-message"), 22 | "xMixedReplace": .init("x-mixed-replace") 23 | ] 24 | ) 25 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/middleware/DynamicDateMiddleware.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | import Logging 4 | import ServiceLifecycle 5 | 6 | // MARK: DynamicDateMiddleware 7 | /// Adds the `Date` header to responses for dynamic routes. 8 | public final class DynamicDateMiddleware: DynamicMiddlewareProtocol { 9 | public init() { 10 | } 11 | 12 | @inlinable 13 | public func handle(request: inout any HTTPRequestProtocol, response: inout any DynamicResponseProtocol) async throws -> Bool { 14 | response.setHeader(key: "Date", value: HTTPDateFormat.shared.nowInlineArray.string()) 15 | return true 16 | } 17 | 18 | public var debugDescription: String { 19 | "DynamicDateMiddleware()" 20 | } 21 | } 22 | 23 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 24 | 25 | import SwiftSyntax 26 | import SwiftSyntaxMacros 27 | 28 | // MARK: SwiftSyntax 29 | extension DynamicDateMiddleware { 30 | public static func parse(context: some MacroExpansionContext, _ function: FunctionCallExprSyntax) -> Self { 31 | return Self() 32 | } 33 | } 34 | #endif -------------------------------------------------------------------------------- /Sources/DestinyDefaults/middleware/DynamicMiddleware.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | // MARK: DynamicMiddleware 5 | /// Default Dynamic Middleware implementation which handles requests to dynamic routes. 6 | public struct DynamicMiddleware: DynamicMiddlewareProtocol { 7 | public let handleLogic:@Sendable (_ request: inout any HTTPRequestProtocol, _ response: inout any DynamicResponseProtocol) async throws -> Void 8 | private var logic:String = "{ _, _ in }" 9 | 10 | public init( 11 | _ handleLogic: @escaping @Sendable (_ request: inout any HTTPRequestProtocol, _ response: inout any DynamicResponseProtocol) async throws -> Void 12 | ) { 13 | self.handleLogic = handleLogic 14 | } 15 | 16 | @inlinable 17 | public func handle(request: inout any HTTPRequestProtocol, response: inout any DynamicResponseProtocol) async throws -> Bool { 18 | try await handleLogic(&request, &response) 19 | return true 20 | } 21 | 22 | public var debugDescription: String { 23 | "DynamicMiddleware \(logic)" 24 | } 25 | } 26 | 27 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 28 | 29 | import SwiftSyntax 30 | import SwiftSyntaxMacros 31 | 32 | // MARK: SwiftSyntax 33 | extension DynamicMiddleware { 34 | public static func parse(context: some MacroExpansionContext, _ function: FunctionCallExprSyntax) -> Self { 35 | var logic = "\(function.trailingClosure?.debugDescription ?? "{ _, _ in }")" 36 | for argument in function.arguments { 37 | if let _ = argument.label?.text { 38 | } else { 39 | logic = "\(argument.expression)" 40 | } 41 | } 42 | var middleware = DynamicMiddleware { _, _ in } 43 | middleware.logic = "\(logic)" 44 | return middleware 45 | } 46 | } 47 | #endif -------------------------------------------------------------------------------- /Sources/DestinyDefaults/middleware/DynamicRateLimitMiddleware.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | // MARK: DynamicRateLimitMiddleware 5 | public final class DynamicRateLimitMiddleware: RateLimitMiddlewareProtocol, DynamicMiddlewareProtocol, @unchecked Sendable { // TODO: finish (need a way to identify requests, preferably by IP address or persistent UUID) 6 | private var limits:[String:Int] 7 | 8 | public init() { 9 | limits = [:] 10 | } 11 | 12 | @inlinable 13 | public func handle(request: inout any HTTPRequestProtocol, response: inout any DynamicResponseProtocol) async throws -> Bool { 14 | return true 15 | } 16 | 17 | public var debugDescription: String { 18 | "DynamicRateLimitMiddleware()" // TODO: finish 19 | } 20 | } 21 | 22 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 23 | 24 | import SwiftSyntax 25 | import SwiftSyntaxMacros 26 | 27 | // MARK: SwiftSyntax 28 | extension DynamicRateLimitMiddleware { 29 | public static func parse(context: some MacroExpansionContext, _ function: FunctionCallExprSyntax) -> Self { 30 | return Self() 31 | } 32 | } 33 | #endif -------------------------------------------------------------------------------- /Sources/DestinyDefaults/middleware/StaticFileMiddleware.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(FoundationEssentials) 3 | import FoundationEssentials 4 | #elseif canImport(Foundation) 5 | import Foundation 6 | #endif 7 | 8 | import DestinyBlueprint 9 | import SwiftCompression 10 | 11 | // MARK: StaticFileMiddleware 12 | public struct StaticFileMiddleware: FileMiddlewareProtocol { 13 | 14 | let filePath:String 15 | let endpoint:String 16 | 17 | public init(filePath: String, endpoint: String) { 18 | self.filePath = filePath 19 | self.endpoint = endpoint 20 | } 21 | 22 | public func load() { 23 | } 24 | 25 | /// - Returns: All the routes associated with the files for the given path. 26 | public func routes( 27 | version: HTTPVersion, 28 | method: any HTTPRequestMethodProtocol = HTTPRequestMethod.get, 29 | charset: Charset? = .utf8, 30 | supportedCompressionAlgorithms: Set = [] 31 | ) throws -> [StaticRoute] { 32 | #if canImport(FoundationEssentials) || canImport(Foundation) 33 | return try routesFoundation(version: version, method: method, charset: charset, supportedCompressionAlgorithms: supportedCompressionAlgorithms, path: filePath, endpoint: endpoint) 34 | #else 35 | return [] 36 | #endif 37 | } 38 | } 39 | 40 | #if canImport(FoundationEssentials) || canImport(Foundation) 41 | // MARK: Foundation 42 | extension StaticFileMiddleware { 43 | private func routesFoundation( 44 | version: HTTPVersion, 45 | method: any HTTPRequestMethodProtocol, 46 | charset: Charset?, 47 | supportedCompressionAlgorithms: Set, 48 | path: String, 49 | endpoint: String 50 | ) throws -> [StaticRoute] { 51 | var isDirectory = false 52 | #if canImport(Darwin) 53 | var dir:ObjCBool = false 54 | guard FileManager.default.fileExists(atPath: path, isDirectory: &dir) else { return [] } 55 | isDirectory = dir.boolValue 56 | #else 57 | guard FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) else { return [] } 58 | #endif 59 | if isDirectory { 60 | let paths = try FileManager.default.contentsOfDirectory(atPath: path) 61 | print("paths=\(paths)") 62 | return [] 63 | /*return try paths.flatMap({ 64 | let slug = String($0.split(separator: "/").last ?? "") 65 | return try routesFoundation( 66 | version: version, 67 | method: method, 68 | charset: charset, 69 | supportedCompressionAlgorithms: supportedCompressionAlgorithms, 70 | path: $0, 71 | endpoint: endpoint + "/" + slug 72 | ) 73 | })*/ 74 | } else { 75 | /*let url = URL(filePath: path) 76 | let contentType = HTTPMediaType.parse(fileExtension: url.pathExtension.lowercased()) ?? HTTPMediaType.textPlain 77 | let body:ResponseBody = try .data(Data(contentsOf: url)) 78 | var route = StaticRoute( 79 | version: version, 80 | method: method, 81 | path: [], 82 | status: HTTPResponseStatus.ok, 83 | contentType: contentType, 84 | charset: charset, 85 | body: body, 86 | supportedCompressionAlgorithms: supportedCompressionAlgorithms 87 | ) 88 | route.path = endpoint.split(separator: "/").map({ String($0) }) 89 | return [route]*/ 90 | return [] 91 | } 92 | } 93 | } 94 | #endif -------------------------------------------------------------------------------- /Sources/DestinyDefaults/middleware/StaticMiddlewareProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | import OrderedCollections 3 | import DestinyBlueprint 4 | 5 | /// Core Static Middleware protocol which handles static & dynamic routes at compile time. 6 | public protocol StaticMiddlewareProtocol: MiddlewareProtocol { 7 | associatedtype ConcreteCookie:HTTPCookieProtocol 8 | 9 | @inlinable 10 | func handlesVersion(_ version: HTTPVersion) -> Bool 11 | 12 | @inlinable 13 | func handlesMethod(_ method: Method) -> Bool 14 | 15 | @inlinable 16 | func handlesStatus(_ code: HTTPResponseStatus.Code) -> Bool 17 | 18 | @inlinable 19 | func handlesContentType(_ mediaType: HTTPMediaType?) -> Bool 20 | 21 | /// Response http version this middleware applies to routes. 22 | var appliesVersion: HTTPVersion? { get } 23 | 24 | /// Response status this middleware applies to routes. 25 | var appliesStatus: HTTPResponseStatus.Code? { get } 26 | 27 | /// Response content type this middleware applies to routes. 28 | var appliesContentType: HTTPMediaType? { get } 29 | 30 | /// Response headers this middleware applies to routes. 31 | var appliesHeaders: OrderedDictionary { get } 32 | 33 | /// Response cookies this middleware applies to routes. 34 | var appliesCookies: [ConcreteCookie] { get } 35 | 36 | /// Whether or not this middleware handles a route with the given options. 37 | @inlinable 38 | func handles( 39 | version: HTTPVersion, 40 | path: String, 41 | method: Method, 42 | contentType: HTTPMediaType?, 43 | status: HTTPResponseStatus.Code 44 | ) -> Bool 45 | 46 | /// Updates the given variables by applying this middleware. 47 | @inlinable 48 | func apply( 49 | version: inout HTTPVersion, 50 | contentType: inout HTTPMediaType?, 51 | status: inout HTTPResponseStatus.Code, 52 | headers: inout OrderedDictionary, 53 | cookies: inout [any HTTPCookieProtocol] 54 | ) 55 | } 56 | extension StaticMiddlewareProtocol { 57 | @inlinable 58 | public func apply( 59 | version: inout HTTPVersion, 60 | contentType: inout HTTPMediaType?, 61 | status: inout HTTPResponseStatus.Code, 62 | headers: inout OrderedDictionary, 63 | cookies: inout [any HTTPCookieProtocol] 64 | ) { 65 | if let appliesVersion { 66 | version = appliesVersion 67 | } 68 | if let appliesStatus { 69 | status = appliesStatus 70 | } 71 | if let appliesContentType { 72 | contentType = appliesContentType 73 | } 74 | for (header, value) in appliesHeaders { 75 | headers[header] = value 76 | } 77 | cookies.append(contentsOf: appliesCookies) 78 | } 79 | 80 | @inlinable 81 | public func apply( 82 | contentType: inout HTTPMediaType?, 83 | to response: inout Response 84 | ) { 85 | if let appliesVersion { 86 | response.setHTTPVersion(appliesVersion) 87 | } 88 | if let appliesStatus { 89 | response.setStatusCode(appliesStatus) 90 | } 91 | if let appliesContentType { 92 | contentType = appliesContentType 93 | } 94 | for (header, value) in appliesHeaders { 95 | response.setHeader(key: header, value: value) 96 | } 97 | for cookie in appliesCookies { 98 | response.appendCookie(cookie) 99 | } 100 | // TODO: fix 101 | } 102 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/middleware/storage/CompiledStaticMiddlewareStorage.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | /// Default immutable storage that handles static middleware. 5 | public struct CompiledStaticMiddlewareStorage: StaticMiddlewareStorageProtocol { 6 | public let middleware:(repeat each ConcreteMiddleware) 7 | 8 | public init(_ middleware: (repeat each ConcreteMiddleware)) { 9 | self.middleware = middleware 10 | } 11 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/ConditionalRouteResponder.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | import SwiftCompression 4 | 5 | /// Default Conditional Route Responder implementation where multiple responders are computed at compile time, but only one should be selected based on the request. 6 | public struct ConditionalRouteResponder: ConditionalRouteResponderProtocol { 7 | public private(set) var staticConditions:[@Sendable (inout any HTTPRequestProtocol) -> Bool] 8 | public private(set) var staticResponders:[any StaticRouteResponderProtocol] 9 | public private(set) var dynamicConditions:[@Sendable (inout any HTTPRequestProtocol) -> Bool] 10 | public private(set) var dynamicResponders:[any DynamicRouteResponderProtocol] 11 | 12 | package var staticConditionsDescription = "[]" 13 | package var staticRespondersDescription = "[]" 14 | package var dynamicConditionsDescription = "[]" 15 | package var dynamicRespondersDescription = "[]" 16 | 17 | public init( 18 | staticConditions: [@Sendable (inout any HTTPRequestProtocol) -> Bool], 19 | staticResponders: [any StaticRouteResponderProtocol], 20 | dynamicConditions: [@Sendable (inout any HTTPRequestProtocol) -> Bool], 21 | dynamicResponders: [any DynamicRouteResponderProtocol] 22 | ) { 23 | self.staticConditions = staticConditions 24 | self.staticResponders = staticResponders 25 | self.dynamicConditions = dynamicConditions 26 | self.dynamicResponders = dynamicResponders 27 | } 28 | 29 | public var debugDescription: String { 30 | """ 31 | ConditionalRouteResponder( 32 | staticConditions: \(staticConditionsDescription), 33 | staticResponders: \(staticRespondersDescription), 34 | dynamicConditions: \(dynamicConditionsDescription), 35 | dynamicResponders: \(dynamicRespondersDescription) 36 | ) 37 | """ 38 | } 39 | 40 | @inlinable 41 | public func respond( 42 | router: borrowing HTTPRouter, 43 | received: ContinuousClock.Instant, 44 | loaded: ContinuousClock.Instant, 45 | socket: borrowing Socket, 46 | request: inout Socket.ConcreteRequest 47 | ) async throws -> Bool { 48 | var request:any HTTPRequestProtocol = request 49 | for (index, condition) in staticConditions.enumerated() { 50 | if condition(&request) { 51 | try await staticResponders[index].respond(to: socket) 52 | return true 53 | } 54 | } 55 | for (index, condition) in dynamicConditions.enumerated() { 56 | if condition(&request) { 57 | let responder = dynamicResponders[index] 58 | var response = responder.defaultResponse 59 | try await responder.respond(to: socket, request: &request, response: &response) 60 | //try await router.respondDynamically(received: received, loaded: loaded, socket: socket, request: &request, responder: dynamicResponders[index]) 61 | return true 62 | } 63 | } 64 | return false 65 | } 66 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/DynamicResponse.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | public struct DynamicResponse: DynamicResponseProtocol { 5 | public var timestamps:DynamicRequestTimestamps 6 | public var message:HTTPResponseMessage 7 | public var parameters:[String] 8 | 9 | public init( 10 | timestamps: DynamicRequestTimestamps = DynamicRequestTimestamps(received: .now, loaded: .now, processed: .now), 11 | message: HTTPResponseMessage, 12 | parameters: [String] 13 | ) { 14 | self.timestamps = timestamps 15 | self.message = message 16 | self.parameters = parameters 17 | } 18 | 19 | public var debugDescription: String { 20 | """ 21 | DynamicResponse( 22 | message: \(message.debugDescription), 23 | parameters: \(parameters) 24 | ) 25 | """ 26 | } 27 | 28 | @inlinable 29 | public func parameter(at index: Int) -> String { 30 | parameters[index] 31 | } 32 | 33 | @inlinable 34 | public mutating func setParameter(at index: Int, value: InlineVLArray) { 35 | parameters[index] = value.string() 36 | } 37 | 38 | @inlinable 39 | public mutating func appendParameter(value: InlineVLArray) { 40 | parameters.append(value.string()) 41 | } 42 | 43 | @inlinable 44 | public func yieldParameters(_ yield: (String) -> Void) { 45 | for parameter in parameters { 46 | yield(parameter) 47 | } 48 | } 49 | } 50 | 51 | extension DynamicResponse { 52 | @inlinable 53 | public mutating func setHTTPVersion(_ version: HTTPVersion) { 54 | message.version = version 55 | } 56 | 57 | @inlinable 58 | public mutating func setStatusCode(_ code: HTTPResponseStatus.Code) { 59 | message.setStatusCode(code) 60 | } 61 | 62 | @inlinable 63 | public mutating func setHeader(key: String, value: String) { 64 | message.setHeader(key: key, value: value) 65 | } 66 | 67 | @inlinable 68 | public mutating func appendCookie(_ cookie: Cookie) { 69 | message.appendCookie(cookie) 70 | } 71 | 72 | @inlinable 73 | public mutating func setBody(_ body: T) { 74 | message.setBody(body) 75 | } 76 | 77 | @inlinable 78 | public func write(to socket: borrowing Socket) throws { 79 | try message.write(to: socket) 80 | } 81 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/DynamicRouteProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 5 | import SwiftSyntax 6 | import SwiftSyntaxMacros 7 | #endif 8 | 9 | /// Core Dynamic Route protocol where a complete HTTP Message, computed at compile time, is modified upon requests. 10 | public protocol DynamicRouteProtocol: RouteProtocol { 11 | associatedtype ConcreteDynamicResponse:DynamicResponseProtocol 12 | 13 | /// Default status of this route. May be modified by static middleware at compile time or by dynamic middleware upon requests. 14 | var status: HTTPResponseStatus.Code { get set } 15 | 16 | /// Default content type of this route. May be modified by static middleware at compile time or dynamic middleware upon requests. 17 | var contentType: HTTPMediaType? { get set } 18 | 19 | /// Path of this route. 20 | var path: [PathComponent] { get set } 21 | 22 | /// Default HTTP Message computed by default values and static middleware. 23 | var defaultResponse: ConcreteDynamicResponse { get set } 24 | 25 | /// - Returns: The responder for this route. 26 | @inlinable func responder() -> any DynamicRouteResponderProtocol 27 | 28 | /// String representation of an initialized route responder conforming to `DynamicRouteResponderProtocol`. 29 | var responderDebugDescription: String { get } 30 | 31 | /// Applies static middleware to this route. 32 | /// 33 | /// - Parameters: 34 | /// - middleware: The static middleware to apply to this route. 35 | mutating func applyStaticMiddleware(_ middleware: [T]) 36 | 37 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 38 | /// Parsing logic for this dynamic route. Computed at compile time. 39 | /// 40 | /// - Parameters: 41 | /// - context: The macro expansion context. 42 | /// - version: The `HTTPVersion` associated with the `HTTPRouterProtocol`. 43 | /// - middleware: The static middleware the associated `HTTPRouterProtocol` uses. 44 | /// - function: SwiftSyntax expression that represents this route at compile time. 45 | /// - Warning: You should apply any statuses and headers using the middleware. 46 | static func parse( 47 | context: some MacroExpansionContext, 48 | version: HTTPVersion, 49 | middleware: [any StaticMiddlewareProtocol], 50 | _ function: FunctionCallExprSyntax 51 | ) -> Self? 52 | #endif 53 | } 54 | 55 | extension DynamicRouteProtocol { 56 | @inlinable 57 | public var startLine: String { 58 | return method.rawNameString() + " /" + path.map({ $0.slug }).joined(separator: "/") + " " + version.string 59 | } 60 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/DynamicRouteResponder.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | /// Default Dynamic Route Responder implementation that responds to dynamic routes. 5 | public struct DynamicRouteResponder: DynamicRouteResponderProtocol { 6 | public let path:[PathComponent] 7 | public let parameterPathIndexes:[Int] 8 | public let defaultResponse:any DynamicResponseProtocol 9 | public let logic:@Sendable (inout any HTTPRequestProtocol, inout any DynamicResponseProtocol) async throws -> Void 10 | private let logicDebugDescription:String 11 | 12 | public init( 13 | path: [PathComponent], 14 | defaultResponse: any DynamicResponseProtocol, 15 | logic: @escaping @Sendable (inout any HTTPRequestProtocol, inout any DynamicResponseProtocol) async throws -> Void, 16 | logicDebugDescription: String = "{ _, _ in }" 17 | ) { 18 | self.path = path 19 | parameterPathIndexes = path.enumerated().compactMap({ $1.isParameter ? $0 : nil }) 20 | self.defaultResponse = defaultResponse 21 | self.logic = logic 22 | self.logicDebugDescription = logicDebugDescription 23 | } 24 | 25 | public var debugDescription: String { 26 | """ 27 | DynamicRouteResponder( 28 | path: \(path), 29 | defaultResponse: \(defaultResponse.debugDescription), 30 | logic: \(logicDebugDescription) 31 | ) 32 | """ 33 | } 34 | 35 | @inlinable 36 | public func forEachPathComponent(_ yield: (PathComponent) -> Void) { 37 | for component in path { 38 | yield(component) 39 | } 40 | } 41 | 42 | @inlinable 43 | public var pathComponentsCount: Int { 44 | path.count 45 | } 46 | 47 | @inlinable 48 | public func pathComponent(at index: Int) -> PathComponent { 49 | path[index] 50 | } 51 | 52 | @inlinable 53 | public func forEachPathComponentParameterIndex(_ yield: (Int) -> Void) { 54 | for index in parameterPathIndexes { 55 | yield(index) 56 | } 57 | } 58 | 59 | @inlinable 60 | public func respond( 61 | to socket: borrowing Socket, 62 | request: inout any HTTPRequestProtocol, 63 | response: inout any DynamicResponseProtocol 64 | ) async throws { 65 | try await logic(&request, &response) 66 | try response.write(to: socket) 67 | } 68 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/StaticErrorResponder.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | import Logging 4 | 5 | /// Default Error Responder implementation that does the bare minimum required to log and send an error response known at compile time. 6 | public struct StaticErrorResponder: ErrorResponderProtocol { 7 | public let logic:@Sendable (_ error: any Error) -> any StaticRouteResponderProtocol 8 | 9 | public init(_ logic: @escaping @Sendable (_ error: any Error) -> any StaticRouteResponderProtocol) { 10 | self.logic = logic 11 | } 12 | 13 | /// - Warning: Do not call. 14 | public var debugDescription: String { "" } 15 | 16 | @inlinable 17 | public func respond( 18 | socket: borrowing Socket, 19 | error: E, 20 | request: inout any HTTPRequestProtocol, 21 | logger: Logger 22 | ) async { 23 | #if DEBUG 24 | logger.warning(Logger.Message(stringLiteral: "\(error)")) 25 | #endif 26 | do { 27 | try await logic(error).respond(to: socket) 28 | } catch { 29 | // TODO: do something 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/StaticRedirectionRoute.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | import OrderedCollections 4 | 5 | // MARK: StaticRedirectionRoute 6 | /// Default Redirection Route implementation that handles redirects for static routes. 7 | public struct StaticRedirectionRoute: RedirectionRouteProtocol { 8 | public package(set) var from:[String] 9 | public package(set) var to:[String] 10 | public let version:HTTPVersion 11 | public let method:any HTTPRequestMethodProtocol 12 | public let status:HTTPResponseStatus.Code 13 | public let isCaseSensitive:Bool 14 | 15 | public init( 16 | version: HTTPVersion = .v1_1, 17 | method: any HTTPRequestMethodProtocol, 18 | status: HTTPResponseStatus.Code, 19 | from: [StaticString], 20 | isCaseSensitive: Bool = true, 21 | to: [StaticString] 22 | ) { 23 | self.version = version 24 | self.method = method 25 | self.status = status 26 | self.from = from.map({ $0.description }) 27 | self.isCaseSensitive = isCaseSensitive 28 | self.to = to.map({ $0.description }) 29 | } 30 | 31 | public var debugDescription: String { 32 | """ 33 | StaticRedirectionRoute( 34 | version: .\(version), 35 | method: \(method.debugDescription), 36 | status: \(status), 37 | from: \(from), 38 | isCaseSensitive: \(isCaseSensitive), 39 | to: \(to) 40 | ) 41 | """ 42 | } 43 | 44 | public func response() throws -> String { 45 | let headers:OrderedDictionary = [ 46 | "Date": HTTPDateFormat.placeholder, 47 | "Location": "/" + to.joined(separator: "/") 48 | ] 49 | return HTTPResponseMessage.create(escapeLineBreak: true, version: version, status: status, headers: headers, body: nil, contentType: nil, charset: nil) 50 | } 51 | } 52 | 53 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 54 | 55 | import SwiftSyntax 56 | import SwiftSyntaxMacros 57 | 58 | // MARK: SwiftSyntax 59 | extension StaticRedirectionRoute { 60 | public static func parse(context: some MacroExpansionContext, version: HTTPVersion, _ function: FunctionCallExprSyntax) -> Self? { 61 | var version = version 62 | var method:any HTTPRequestMethodProtocol = HTTPRequestMethod.get 63 | var from = [String]() 64 | var isCaseSensitive = true 65 | var to = [String]() 66 | var status = HTTPResponseStatus.movedPermanently.code 67 | for argument in function.arguments { 68 | switch argument.label?.text { 69 | case "version": version = HTTPVersion.parse(argument.expression) ?? version 70 | case "method": method = HTTPRequestMethod.parse(expr: argument.expression) ?? method 71 | case "status": status = HTTPResponseStatus.parse(expr: argument.expression)?.code ?? status 72 | case "from": from = PathComponent.parseArray(context: context, argument.expression) 73 | case "isCaseSensitive", "caseSensitive": isCaseSensitive = argument.expression.booleanIsTrue 74 | case "to": to = PathComponent.parseArray(context: context, argument.expression) 75 | default: break 76 | } 77 | } 78 | var route = Self(version: version, method: method, status: status, from: [], isCaseSensitive: isCaseSensitive, to: []) 79 | route.from = from 80 | route.to = to 81 | return route 82 | } 83 | } 84 | #endif -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/StaticRouteProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 5 | import SwiftSyntax 6 | import SwiftSyntaxMacros 7 | #endif 8 | 9 | /// Core Static Route protocol where a complete HTTP Message is computed at compile time. 10 | public protocol StaticRouteProtocol: RouteProtocol { 11 | var startLine: String { get } 12 | 13 | mutating func insertPath>(contentsOf newElements: C, at i: Int) 14 | 15 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 16 | /// The HTTP Message of this route. 17 | /// 18 | /// - Parameters: 19 | /// - context: The macro expansion context where it was called. 20 | /// - function: SwiftSyntax expression that represents this route. 21 | /// - middleware: Static middleware that this route will apply. 22 | /// - Returns: An `HTTPResponseMessage`. 23 | /// - Warning: You should apply any statuses and headers using the middleware. 24 | func response( 25 | context: MacroExpansionContext?, 26 | function: FunctionCallExprSyntax?, 27 | middleware: [any StaticMiddlewareProtocol] 28 | ) -> any HTTPMessageProtocol 29 | 30 | /// The `StaticRouteResponderProtocol` responder for this route. 31 | /// 32 | /// - Parameters: 33 | /// - context: The macro expansion context where it was called. 34 | /// - function: SwiftSyntax expression that represents this route. 35 | /// - middleware: Static middleware that this route will apply. 36 | /// - Throws: any error. 37 | func responder( 38 | context: MacroExpansionContext?, 39 | function: FunctionCallExprSyntax?, 40 | middleware: [any StaticMiddlewareProtocol] 41 | ) throws -> (any StaticRouteResponderProtocol)? 42 | 43 | /// Parsing logic for this route. 44 | /// 45 | /// - Parameters: 46 | /// - context: The macro expansion context where this route is being parsed from. 47 | /// - version: The `HTTPVersion` of the `HTTPRouterProtocol` this middleware is assigned to. 48 | /// - function: SwiftSyntax expression that represents this route. 49 | static func parse( 50 | context: some MacroExpansionContext, 51 | version: HTTPVersion, 52 | _ function: FunctionCallExprSyntax 53 | ) -> Self? 54 | #endif 55 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/responses/RouteResponses+FoundationData.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(FoundationEssentials) || canImport(Foundation) 3 | import DestinyBlueprint 4 | 5 | #if canImport(FoundationEssentials) 6 | import struct FoundationEssentials.Data 7 | #else 8 | import struct Foundation.Data 9 | #endif 10 | 11 | extension RouteResponses { 12 | public struct FoundationData: StaticRouteResponderProtocol { 13 | public let value:Data 14 | 15 | public init(_ value: Data) { 16 | self.value = value 17 | } 18 | 19 | public var debugDescription: Swift.String { 20 | "RouteResponses.FoundationData(\(value))" 21 | } 22 | 23 | @inlinable 24 | public func respond(to socket: borrowing T) async throws { 25 | try value.withUnsafeBytes { 26 | try socket.writeBuffer($0.baseAddress!, length: value.count) 27 | } 28 | } 29 | } 30 | } 31 | 32 | #endif -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/responses/RouteResponses+MacroExpansion.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension RouteResponses { 5 | public struct MacroExpansion: StaticRouteResponderProtocol { 6 | public let value:Swift.String 7 | public let body:Swift.String 8 | 9 | public init(_ value: Swift.String, body: Swift.String) { 10 | self.value = value 11 | self.body = body 12 | } 13 | public init(_ response: HTTPResponseMessage) { 14 | value = (try? response.string(escapeLineBreak: true)) ?? "" 15 | body = response.body?.debugDescription ?? "" 16 | } 17 | 18 | public var debugDescription: Swift.String { 19 | """ 20 | RouteResponses.MacroExpansion( 21 | "\(value)", 22 | body: \(body)") 23 | ) 24 | """ 25 | } 26 | 27 | @inlinable 28 | public func respond(to socket: borrowing T) async throws { 29 | try value.utf8.withContiguousStorageIfAvailable { valuePointer in 30 | try Swift.String(body.count).utf8.withContiguousStorageIfAvailable { contentLengthPointer in 31 | try body.utf8.withContiguousStorageIfAvailable { bodyPointer in 32 | try withUnsafeTemporaryAllocation(of: UInt8.self, capacity: valuePointer.count + contentLengthPointer.count + 4 + bodyPointer.count, { buffer in 33 | var i = 0 34 | valuePointer.forEach { 35 | buffer[i] = $0 36 | i += 1 37 | } 38 | contentLengthPointer.forEach { 39 | buffer[i] = $0 40 | i += 1 41 | } 42 | buffer[i] = .carriageReturn 43 | i += 1 44 | buffer[i] = .lineFeed 45 | i += 1 46 | buffer[i] = .carriageReturn 47 | i += 1 48 | buffer[i] = .lineFeed 49 | i += 1 50 | bodyPointer.forEach { 51 | buffer[i] = $0 52 | i += 1 53 | } 54 | try socket.writeBuffer(buffer.baseAddress!, length: buffer.count) 55 | }) 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/responses/RouteResponses+MacroExpansionWithDateHeader.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension RouteResponses { 5 | public struct MacroExpansionWithDateHeader: StaticRouteResponderProtocol { 6 | public let value:Swift.String 7 | public let body:Swift.String 8 | 9 | public init(_ value: Swift.String, body: Swift.String) { 10 | self.value = value 11 | self.body = body 12 | } 13 | public init(_ response: HTTPResponseMessage) { 14 | value = (try? response.string(escapeLineBreak: true)) ?? "" 15 | body = response.body?.debugDescription ?? "" 16 | } 17 | 18 | public var debugDescription: Swift.String { 19 | """ 20 | RouteResponses.MacroExpansionWithDateHeader( 21 | "\(value)", 22 | body: \(body)") 23 | ) 24 | """ 25 | } 26 | 27 | @inlinable 28 | public func respond(to socket: borrowing T) async throws { 29 | try value.utf8.withContiguousStorageIfAvailable { valuePointer in 30 | try HTTPDateFormat.shared.nowInlineArray.span.withUnsafeBufferPointer { datePointer in 31 | try Swift.String(body.count).utf8.withContiguousStorageIfAvailable { contentLengthPointer in 32 | try body.utf8.withContiguousStorageIfAvailable { bodyPointer in 33 | try withUnsafeTemporaryAllocation(of: UInt8.self, capacity: valuePointer.count + contentLengthPointer.count + 4 + bodyPointer.count, { buffer in 34 | var i = 0 35 | // 20 = "HTTP/ \r\n".count + "Date: ".count (14 + 6) where `` is the HTTP Version and `` is the HTTP Status Code 36 | while i < 20 { 37 | buffer[i] = valuePointer[i] 38 | i += 1 39 | } 40 | datePointer.forEach { 41 | buffer[i] = $0 42 | i += 1 43 | } 44 | while i < valuePointer.count { 45 | buffer[i] = valuePointer[i] 46 | i += 1 47 | } 48 | contentLengthPointer.forEach { 49 | buffer[i] = $0 50 | i += 1 51 | } 52 | buffer[i] = .carriageReturn 53 | i += 1 54 | buffer[i] = .lineFeed 55 | i += 1 56 | buffer[i] = .carriageReturn 57 | i += 1 58 | buffer[i] = .lineFeed 59 | i += 1 60 | bodyPointer.forEach { 61 | buffer[i] = $0 62 | i += 1 63 | } 64 | try socket.writeBuffer(buffer.baseAddress!, length: buffer.count) 65 | }) 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/responses/RouteResponses+StaticString.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension RouteResponses { 5 | public struct StaticString: StaticRouteResponderProtocol { 6 | public let value:Swift.StaticString 7 | 8 | public init(_ value: Swift.StaticString) { 9 | self.value = value 10 | } 11 | 12 | public var debugDescription: Swift.String { 13 | "RouteResponses.StaticString(\"" + value.description + "\")" 14 | } 15 | 16 | @inlinable 17 | public func respond(to socket: borrowing T) async throws { 18 | var err:(any Error)? = nil 19 | value.withUTF8Buffer { 20 | do { 21 | try socket.writeBuffer($0.baseAddress!, length: $0.count) 22 | } catch { 23 | err = error 24 | } 25 | } 26 | if let err { 27 | throw err 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/responses/RouteResponses+StaticStringWithDateHeader.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension RouteResponses { 5 | public struct StaticStringWithDateHeader: StaticRouteResponderProtocol { 6 | public let value:Swift.StaticString 7 | 8 | public init(_ value: Swift.StaticString) { 9 | self.value = value 10 | } 11 | 12 | public var debugDescription: Swift.String { 13 | return "RouteResponses.StaticStringWithDateHeader(\"\(value)\")" 14 | } 15 | 16 | @inlinable 17 | public func respond(to socket: borrowing T) async throws { 18 | var err:(any Error)? = nil 19 | value.withUTF8Buffer { valuePointer in 20 | do { 21 | try HTTPDateFormat.shared.nowInlineArray.span.withUnsafeBufferPointer { datePointer in 22 | try withUnsafeTemporaryAllocation(of: UInt8.self, capacity: valuePointer.count, { buffer in 23 | var i = 0 24 | // 20 = "HTTP/ \r\n".count + "Date: ".count (14 + 6) where `` is the HTTP Version and `` is the HTTP Status Code 25 | while i < 20 { 26 | buffer[i] = valuePointer[i] 27 | i += 1 28 | } 29 | datePointer.forEach { 30 | buffer[i] = $0 31 | i += 1 32 | } 33 | while i < valuePointer.count { 34 | buffer[i] = valuePointer[i] 35 | i += 1 36 | } 37 | try socket.writeBuffer(buffer.baseAddress!, length: buffer.count) 38 | }) 39 | } 40 | } catch { 41 | err = error 42 | } 43 | } 44 | if let err { 45 | throw err 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/responses/RouteResponses+String.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension RouteResponses { 5 | public struct String: StaticRouteResponderProtocol { 6 | public let value:Swift.String 7 | 8 | public init(_ value: Swift.String) { 9 | self.value = value 10 | } 11 | public init(_ response: HTTPResponseMessage) { 12 | value = (try? response.string(escapeLineBreak: true)) ?? "" 13 | } 14 | 15 | public var debugDescription: Swift.String { 16 | "RouteResponses.String(\"" + value + "\")" 17 | } 18 | 19 | @inlinable 20 | public func respond(to socket: borrowing T) async throws { 21 | try value.utf8.withContiguousStorageIfAvailable { 22 | try socket.writeBuffer($0.baseAddress!, length: $0.count) 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/responses/RouteResponses+StringWithDateHeader.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension RouteResponses { 5 | public struct StringWithDateHeader: StaticRouteResponderProtocol { 6 | public let value:Swift.String 7 | 8 | public init(_ value: Swift.String) { 9 | self.value = value 10 | } 11 | 12 | public var debugDescription: Swift.String { 13 | return "RouteResponses.StringWithDateHeader(\"\(value)\")" 14 | } 15 | 16 | @inlinable 17 | public func respond(to socket: borrowing T) async throws { 18 | try value.utf8.span.withUnsafeBufferPointer { valuePointer in 19 | try HTTPDateFormat.shared.nowInlineArray.span.withUnsafeBufferPointer { datePointer in 20 | try withUnsafeTemporaryAllocation(of: UInt8.self, capacity: valuePointer.count, { buffer in 21 | buffer.copyBuffer(valuePointer, at: 0) 22 | // 20 = "HTTP/ \r\n".count + "Date: ".count (14 + 6) where `` is the HTTP Version and `` is the HTTP Status Code 23 | var i = 20 24 | datePointer.forEach { 25 | buffer[i] = $0 26 | i += 1 27 | } 28 | try socket.writeBuffer(buffer.baseAddress!, length: buffer.count) 29 | }) 30 | } 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/responses/RouteResponses+UInt16Array.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension RouteResponses { 5 | public struct UInt16Array: StaticRouteResponderProtocol { 6 | public let value:[UInt16] 7 | 8 | public init(_ value: [UInt16]) { 9 | self.value = value 10 | } 11 | 12 | public var debugDescription: Swift.String { 13 | "RouteResponses.UInt16Array(\(value))" 14 | } 15 | 16 | @inlinable 17 | public func respond(to socket: borrowing T) async throws { 18 | try value.withUnsafeBufferPointer { 19 | try socket.writeBuffer($0.baseAddress!, length: $0.count) 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/responses/RouteResponses+UInt8Array.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension RouteResponses { 5 | public struct UInt8Array: StaticRouteResponderProtocol { 6 | public let value:[UInt8] 7 | 8 | public init(_ value: [UInt8]) { 9 | self.value = value 10 | } 11 | 12 | public var debugDescription: Swift.String { 13 | "RouteResponses.UInt8Array(\(value))" 14 | } 15 | 16 | @inlinable 17 | public func respond(to socket: borrowing T) async throws { 18 | try value.withUnsafeBufferPointer { 19 | try socket.writeBuffer($0.baseAddress!, length: $0.count) 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/responses/RouteResponses.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | public enum RouteResponses { 5 | } 6 | 7 | /* 8 | // MARK: UnsafeBufferPointer 9 | extension RouteResponses { 10 | public struct UnsafeBufferPointer: @unchecked Sendable, StaticRouteResponderProtocol { 11 | public let value:Swift.UnsafeBufferPointer 12 | public init(_ value: Swift.UnsafeBufferPointer) { 13 | self.value = value 14 | } 15 | 16 | @inlinable 17 | public func respond(to socket: borrowing T) async throws { 18 | try socket.writeBuffer(value.baseAddress!, length: value.count) 19 | } 20 | } 21 | }*/ 22 | 23 | // MARK: InlineArray 24 | extension InlineArray where Element == UInt8 { 25 | @inlinable 26 | public func respond(to socket: borrowing T) async throws { 27 | try span.withUnsafeBufferPointer { 28 | try socket.writeBuffer($0.baseAddress!, length: $0.count) 29 | } 30 | } 31 | 32 | public var debugDescription: String { 33 | return "[" + indices.map({ String(self.itemAt(index: $0)) }).joined(separator: ", ") + "]" 34 | } 35 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/results/ResponseBody+Bytes.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension ResponseBody { 5 | @inlinable 6 | public static func bytes(_ value: [UInt8]) -> Self.Bytes { 7 | Self.Bytes(value) 8 | } 9 | public struct Bytes: ResponseBodyProtocol { 10 | public let value:[UInt8] 11 | 12 | @inlinable 13 | public init(_ value: [UInt8]) { 14 | self.value = value 15 | } 16 | 17 | public var debugDescription: Swift.String { 18 | "ResponseBody.bytes(\(value))" 19 | } 20 | 21 | public var responderDebugDescription: Swift.String { 22 | "RouteResponses.UInt8Array(\(value))" 23 | } 24 | 25 | public func responderDebugDescription(_ input: Swift.String) -> Swift.String { 26 | Self([UInt8](input.utf8)).responderDebugDescription 27 | } 28 | 29 | public func responderDebugDescription(_ input: T) throws -> Swift.String { 30 | try responderDebugDescription(input.string(escapeLineBreak: false)) 31 | } 32 | 33 | @inlinable 34 | public var count: Int { 35 | value.count 36 | } 37 | 38 | @inlinable 39 | public func string() -> Swift.String { 40 | .init(decoding: value, as: UTF8.self) 41 | } 42 | 43 | @inlinable 44 | public func bytes() -> [UInt8] { 45 | value 46 | } 47 | 48 | @inlinable 49 | public func bytes(_ closure: (inout InlineVLArray) throws -> Void) rethrows { 50 | try InlineVLArray.create(collection: value, closure) 51 | } 52 | 53 | @inlinable public var hasDateHeader: Bool { false } 54 | } 55 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/results/ResponseBody+Data.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(FoundationEssentials) || canImport(Foundation) 3 | import DestinyBlueprint 4 | 5 | #if canImport(FoundationEssentials) 6 | import struct FoundationEssentials.Data 7 | #else 8 | import struct Foundation.Data 9 | #endif 10 | 11 | extension ResponseBody { 12 | @inlinable 13 | public static func data(_ value: Data) -> Self.FoundationData { 14 | Self.FoundationData(value) 15 | } 16 | public struct FoundationData: ResponseBodyProtocol { 17 | public let value:Data 18 | 19 | @inlinable 20 | public init(_ value: Data) { 21 | self.value = value 22 | } 23 | 24 | public var debugDescription: Swift.String { 25 | "ResponseBody.data(\(value))" 26 | } 27 | 28 | public var responderDebugDescription: Swift.String { 29 | "RouteResponses.FoundationData(\(value))" 30 | } 31 | 32 | public func responderDebugDescription(_ input: Swift.String) -> Swift.String { 33 | Self(Data(input.utf8)).responderDebugDescription 34 | } 35 | 36 | public func responderDebugDescription(_ input: T) throws -> Swift.String{ 37 | try responderDebugDescription(input.string(escapeLineBreak: false)) 38 | } 39 | 40 | @inlinable 41 | public var count: Int { 42 | value.count 43 | } 44 | 45 | @inlinable 46 | public func string() -> Swift.String { 47 | .init(decoding: value, as: UTF8.self) 48 | } 49 | 50 | @inlinable 51 | public func bytes() -> [UInt8] { 52 | [UInt8](value) 53 | } 54 | 55 | @inlinable 56 | public func bytes(_ closure: (inout InlineVLArray) throws -> Void) rethrows { 57 | try InlineVLArray.create(collection: value, closure) 58 | } 59 | 60 | @inlinable public var hasDateHeader: Bool { false } 61 | } 62 | } 63 | 64 | #endif -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/results/ResponseBody+MacroExpansion.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension ResponseBody { 5 | @inlinable 6 | public static func macroExpansion(_ value: Value) -> MacroExpansion { 7 | Self.MacroExpansion(value) 8 | } 9 | 10 | public struct MacroExpansion: ResponseBodyProtocol { 11 | public let value:Value 12 | 13 | @inlinable 14 | public init(_ value: Value) { 15 | self.value = value 16 | } 17 | 18 | public var debugDescription: Swift.String { 19 | "ResponseBody.macroExpansion(\"\(value)\")" 20 | } 21 | 22 | public var responderDebugDescription: Swift.String { 23 | "RouteResponses.MacroExpansion(\"\(value))" 24 | } 25 | 26 | public func responderDebugDescription(_ input: Swift.String) -> Swift.String { 27 | MacroExpansion(input).responderDebugDescription 28 | } 29 | 30 | public func responderDebugDescription(_ input: T) throws -> Swift.String { 31 | try responderDebugDescription(input.string(escapeLineBreak: true)) 32 | } 33 | 34 | @inlinable 35 | public var count: Int { 36 | value.count 37 | } 38 | 39 | @inlinable 40 | public func string() -> Swift.String { 41 | value.string() 42 | } 43 | 44 | @inlinable 45 | public func bytes() -> [UInt8] { 46 | value.bytes() 47 | } 48 | 49 | @inlinable 50 | public func bytes(_ closure: (inout InlineVLArray) throws -> Void) rethrows { 51 | try InlineVLArray.create(string: value.string(), closure) 52 | } 53 | 54 | @inlinable public var hasDateHeader: Bool { false } 55 | 56 | @inlinable public var hasCustomInitializer: Bool { true } 57 | 58 | @inlinable 59 | public func customInitializer(bodyString: Swift.String) -> Swift.String { 60 | "\", body: " + bodyString 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/results/ResponseBody+MacroExpansionWithDateHeader.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension ResponseBody { 5 | @inlinable 6 | public static func macroExpansionWithDateHeader(_ value: Value) -> MacroExpansionWithDateHeader { 7 | Self.MacroExpansionWithDateHeader(value) 8 | } 9 | 10 | public struct MacroExpansionWithDateHeader: ResponseBodyProtocol { 11 | public let value:Value 12 | 13 | @inlinable 14 | public init(_ value: Value) { 15 | self.value = value 16 | } 17 | 18 | public var debugDescription: Swift.String { 19 | "ResponseBody.macroExpansionWithDateHeader(\"\(value)\")" 20 | } 21 | 22 | public var responderDebugDescription: Swift.String { 23 | "RouteResponses.MacroExpansionWithDateHeader(\"\(value))" 24 | } 25 | 26 | public func responderDebugDescription(_ input: Swift.String) -> Swift.String { 27 | MacroExpansionWithDateHeader(input).responderDebugDescription 28 | } 29 | 30 | public func responderDebugDescription(_ input: T) throws -> Swift.String { 31 | try responderDebugDescription(input.string(escapeLineBreak: true)) 32 | } 33 | 34 | @inlinable 35 | public var count: Int { 36 | value.count 37 | } 38 | 39 | @inlinable 40 | public func string() -> Swift.String { 41 | value.string() 42 | } 43 | 44 | @inlinable 45 | public func bytes() -> [UInt8] { 46 | value.bytes() 47 | } 48 | 49 | @inlinable 50 | public func bytes(_ closure: (inout InlineVLArray) throws -> Void) rethrows { 51 | try InlineVLArray.create(string: value.string(), closure) 52 | } 53 | 54 | @inlinable public var hasDateHeader: Bool { true } 55 | 56 | @inlinable public var hasCustomInitializer: Bool { true } 57 | 58 | @inlinable 59 | public func customInitializer(bodyString: Swift.String) -> Swift.String { 60 | "\", body: " + bodyString 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/results/ResponseBody+StaticString.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension ResponseBody { 5 | @inlinable 6 | public static func staticString(_ value: Swift.StaticString) -> StaticString { 7 | Self.StaticString(value) 8 | } 9 | 10 | public struct StaticString: ResponseBodyProtocol { 11 | public let value:Swift.StaticString 12 | 13 | @inlinable 14 | public init(_ value: Swift.StaticString) { 15 | self.value = value 16 | } 17 | 18 | public var debugDescription: Swift.String { 19 | "ResponseBody.staticString(\"\(value)\")" 20 | } 21 | 22 | public var responderDebugDescription: Swift.String { 23 | "RouteResponses.StaticString(\"\(value)\")" 24 | } 25 | 26 | public func responderDebugDescription(_ input: Swift.String) -> Swift.String { 27 | fatalError("cannot do that") // TODO: fix? 28 | } 29 | 30 | public func responderDebugDescription(_ input: T) throws -> Swift.String { 31 | try responderDebugDescription(input.string(escapeLineBreak: true)) 32 | } 33 | 34 | @inlinable 35 | public var count: Int { 36 | value.utf8CodeUnitCount 37 | } 38 | 39 | @inlinable 40 | public func string() -> Swift.String { 41 | value.description 42 | } 43 | 44 | @inlinable 45 | public func bytes() -> [UInt8] { 46 | [UInt8](value.description.utf8) 47 | } 48 | 49 | @inlinable 50 | public func bytes(_ closure: (inout InlineVLArray) throws -> Void) rethrows { 51 | try InlineVLArray.create(string: value.description, closure) 52 | } 53 | 54 | @inlinable public var hasDateHeader: Bool { false } 55 | } 56 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/results/ResponseBody+StaticStringWithDateHeader.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension ResponseBody { 5 | @inlinable 6 | public static func staticStringWithDateHeader(_ value: Swift.StaticString) -> StaticStringWithDateHeader { 7 | Self.StaticStringWithDateHeader(value) 8 | } 9 | 10 | public struct StaticStringWithDateHeader: ResponseBodyProtocol { 11 | public var value:Swift.StaticString 12 | 13 | @inlinable 14 | public init(_ value: Swift.StaticString) { 15 | self.value = value 16 | } 17 | 18 | public var debugDescription: Swift.String { 19 | "ResponseBody.staticStringWithDateHeader(\"\(value)\")" 20 | } 21 | 22 | public var responderDebugDescription: Swift.String { 23 | "RouteResponses.StaticStringWithDateHeader(\"\(value)\")" 24 | } 25 | 26 | public func responderDebugDescription(_ input: Swift.String) -> Swift.String { 27 | fatalError("cannot do that") 28 | } 29 | 30 | public func responderDebugDescription(_ input: T) throws -> Swift.String { 31 | try responderDebugDescription(input.string(escapeLineBreak: true)) 32 | } 33 | 34 | @inlinable 35 | public var count: Int { 36 | value.utf8CodeUnitCount 37 | } 38 | 39 | @inlinable 40 | public func string() -> Swift.String { 41 | value.description 42 | } 43 | 44 | @inlinable 45 | public func bytes() -> [UInt8] { 46 | [UInt8](value.description.utf8) 47 | } 48 | 49 | @inlinable 50 | public func bytes(_ closure: (inout InlineVLArray) throws -> Void) rethrows { 51 | try InlineVLArray.create(string: value, closure) 52 | } 53 | 54 | @inlinable public var hasDateHeader: Bool { true } 55 | } 56 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/results/ResponseBody+String.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension ResponseBody { 5 | @inlinable 6 | public static func string(_ value: Swift.String) -> String { 7 | Self.String(value) 8 | } 9 | 10 | public struct String: ResponseBodyProtocol { 11 | public let value:Swift.String 12 | 13 | @inlinable 14 | public init(_ value: Swift.String) { 15 | self.value = value 16 | } 17 | 18 | public var debugDescription: Swift.String { 19 | "ResponseBody.string(\"\(value)\")" 20 | } 21 | 22 | public var responderDebugDescription: Swift.String { 23 | "RouteResponses.String(\"\(value)\")" 24 | } 25 | 26 | public func responderDebugDescription(_ input: Swift.String) -> Swift.String { 27 | Self(input).responderDebugDescription 28 | } 29 | 30 | public func responderDebugDescription(_ input: T) throws -> Swift.String { 31 | try responderDebugDescription(input.string(escapeLineBreak: true)) 32 | } 33 | 34 | @inlinable 35 | public var count: Int { 36 | value.utf8.count 37 | } 38 | 39 | @inlinable 40 | public func string() -> Swift.String { 41 | value 42 | } 43 | 44 | @inlinable 45 | public func bytes() -> [UInt8] { 46 | [UInt8](value.utf8) 47 | } 48 | 49 | @inlinable 50 | public func bytes(_ closure: (inout InlineVLArray) throws -> Void) rethrows { 51 | try InlineVLArray.create(string: value, closure) 52 | } 53 | 54 | @inlinable public var hasDateHeader: Bool { false } 55 | } 56 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/results/ResponseBody+StringWithDateHeader.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | extension ResponseBody { 5 | @inlinable 6 | public static func stringWithDateHeader(_ value: Swift.String) -> StringWithDateHeader { 7 | Self.StringWithDateHeader(value) 8 | } 9 | 10 | public struct StringWithDateHeader: ResponseBodyProtocol { 11 | public var value:Swift.String 12 | 13 | @inlinable 14 | public init(_ value: Swift.String) { 15 | self.value = value 16 | } 17 | 18 | public var debugDescription: Swift.String { 19 | "ResponseBody.stringWithDateHeader(\"\(value)\")" 20 | } 21 | 22 | public var responderDebugDescription: Swift.String { 23 | "RouteResponses.StringWithDateHeader(\"\(value)\")" 24 | } 25 | 26 | public func responderDebugDescription(_ input: Swift.String) -> Swift.String { 27 | Self(input).responderDebugDescription 28 | } 29 | 30 | public func responderDebugDescription(_ input: T) throws -> Swift.String { 31 | try responderDebugDescription(input.string(escapeLineBreak: true)) 32 | } 33 | 34 | @inlinable 35 | public var count: Int { 36 | value.utf8.count 37 | } 38 | 39 | @inlinable 40 | public func string() -> Swift.String { 41 | value 42 | } 43 | 44 | @inlinable 45 | public func bytes() -> [UInt8] { 46 | [UInt8](value.utf8) 47 | } 48 | 49 | @inlinable 50 | public func bytes(_ closure: (inout InlineVLArray) throws -> Void) rethrows { 51 | try InlineVLArray.create(string: value, closure) 52 | } 53 | 54 | @inlinable public var hasDateHeader: Bool { true } 55 | } 56 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/routes/results/ResponseBodyValueProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | /// Types conforming to this protocol can be used as... 3 | public protocol ResponseBodyValueProtocol: Sendable { 4 | @inlinable 5 | var count: Int { get } 6 | 7 | @inlinable 8 | func string() -> String 9 | 10 | @inlinable 11 | func bytes() -> [UInt8] 12 | } 13 | 14 | extension StaticString: ResponseBodyValueProtocol { 15 | @inlinable 16 | public var count: Int { 17 | self.utf8CodeUnitCount 18 | } 19 | 20 | @inlinable 21 | public func string() -> String { 22 | description 23 | } 24 | 25 | @inlinable 26 | public func bytes() -> [UInt8] { 27 | Array(string().utf8) 28 | } 29 | 30 | } 31 | extension String: ResponseBodyValueProtocol { 32 | @inlinable 33 | public func string() -> String { 34 | self 35 | } 36 | 37 | @inlinable 38 | public func bytes() -> [UInt8] { 39 | Array(utf8) 40 | } 41 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/storage/RouterResponderStorage.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | /// Default mutable storage that handles conditional, dynamic and static routes. 5 | public struct RouterResponderStorage< 6 | ConcreteStaticResponderStorage: StaticResponderStorageProtocol, 7 | ConcreteDynamicResponderStorage: DynamicResponderStorageProtocol 8 | >: RouterResponderStorageProtocol { 9 | public var `static`:ConcreteStaticResponderStorage 10 | public var dynamic:ConcreteDynamicResponderStorage 11 | public var conditional:[DestinyRoutePathType:any ConditionalRouteResponderProtocol] 12 | 13 | @inlinable 14 | public init( 15 | static: ConcreteStaticResponderStorage, 16 | dynamic: ConcreteDynamicResponderStorage, 17 | conditional: [DestinyRoutePathType:any ConditionalRouteResponderProtocol] 18 | ) { 19 | self.static = `static` 20 | self.dynamic = dynamic 21 | self.conditional = conditional 22 | } 23 | 24 | @inlinable 25 | public func respond( 26 | router: borrowing HTTPRouter, 27 | received: ContinuousClock.Instant, 28 | loaded: ContinuousClock.Instant, 29 | socket: borrowing Socket, 30 | request: inout Socket.ConcreteRequest 31 | ) async throws -> Bool { 32 | if try await respondStatically(router: router, socket: socket, startLine: request.startLine) { 33 | return true 34 | } 35 | if try await respondDynamically(router: router, received: received, loaded: loaded, socket: socket, request: &request) { 36 | return true 37 | } 38 | if let responder = conditional[request.startLine] { 39 | return try await responder.respond(router: router, received: received, loaded: loaded, socket: socket, request: &request) 40 | } 41 | return false 42 | } 43 | } 44 | 45 | extension RouterResponderStorage { 46 | @inlinable 47 | public func respondStatically( 48 | router: borrowing HTTPRouter, 49 | socket: borrowing Socket, 50 | startLine: SIMD64 51 | ) async throws -> Bool { 52 | return try await `static`.respond(router: router, socket: socket, startLine: startLine) 53 | } 54 | 55 | @inlinable 56 | public func respondDynamically( 57 | router: borrowing HTTPRouter, 58 | received: ContinuousClock.Instant, 59 | loaded: ContinuousClock.Instant, 60 | socket: borrowing Socket, 61 | request: inout Socket.ConcreteRequest, 62 | ) async throws -> Bool { 63 | return try await dynamic.respond(router: router, received: received, loaded: loaded, socket: socket, request: &request) 64 | } 65 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/storage/compiled/CompiledDynamicResponderStorage.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | /// Default immutable storage that handles dynamic routes. 5 | public struct CompiledDynamicResponderStorage: DynamicResponderStorageProtocol { 6 | public let routes:(repeat each ConcreteRoute) 7 | 8 | public init(_ routes: (repeat each ConcreteRoute)) { 9 | self.routes = routes 10 | } 11 | 12 | public var debugDescription: String { 13 | var s = "CompiledDynamicResponderStorage(\n(" 14 | for route in repeat each routes { 15 | s += "\n" + route.debugDescription + "," 16 | } 17 | return s + "\n)\n)" 18 | } 19 | 20 | @inlinable 21 | public func respond( 22 | router: borrowing HTTPRouter, 23 | received: ContinuousClock.Instant, 24 | loaded: ContinuousClock.Instant, 25 | socket: borrowing Socket, 26 | request: inout Socket.ConcreteRequest 27 | ) async throws -> Bool { 28 | for route in repeat each routes { 29 | if route.path == request.startLine { // parameterless 30 | try await router.respondDynamically(received: received, loaded: loaded, socket: socket, request: &request, responder: route.responder) 31 | return true 32 | } else { // parameterized and catchall 33 | var found = true 34 | loop: for i in 0..: CompiledDynamicResponderStorageRouteProtocol { 65 | public let path:DestinyRoutePathType 66 | public let responder:ConcreteResponder 67 | 68 | public init(path: DestinyRoutePathType, responder: ConcreteResponder) { 69 | self.path = path 70 | self.responder = responder 71 | } 72 | 73 | public var debugDescription: String { 74 | """ 75 | CompiledDynamicResponderStorageRoute<\(ConcreteResponder.self)>( 76 | path: \(path.debugDescription), 77 | responder: \(responder.debugDescription) 78 | ) 79 | """ 80 | } 81 | } -------------------------------------------------------------------------------- /Sources/DestinyDefaults/storage/compiled/CompiledStaticResponderStorage.swift: -------------------------------------------------------------------------------- 1 | 2 | import DestinyBlueprint 3 | 4 | /// Default immutable storage that handles static routes. 5 | public struct CompiledStaticResponderStorage: StaticResponderStorageProtocol { 6 | public let routes:(repeat each ConcreteRoute) 7 | 8 | public init(_ routes: (repeat each ConcreteRoute)) { 9 | self.routes = routes 10 | } 11 | 12 | public var debugDescription: String { 13 | var s = "CompiledStaticResponderStorage(\n(" 14 | for route in repeat each routes { 15 | s += "\n" + route.debugDescription + "," 16 | } 17 | if s.utf8.span.count != 33 { // was modified 18 | s.removeLast() 19 | } 20 | return s + "\n)\n)" 21 | } 22 | 23 | @inlinable 24 | public func respond( 25 | router: borrowing HTTPRouter, 26 | socket: borrowing Socket, 27 | startLine: DestinyRoutePathType 28 | ) async throws -> Bool { 29 | for route in repeat each routes { 30 | if route.path == startLine { 31 | try await router.respondStatically(socket: socket, responder: route.responder) 32 | return true 33 | } 34 | } 35 | return false 36 | } 37 | } 38 | 39 | public protocol CompiledStaticResponderStorageRouteProtocol: CustomDebugStringConvertible, Sendable { 40 | associatedtype ConcreteResponder:StaticRouteResponderProtocol 41 | 42 | var path: DestinyRoutePathType { get } 43 | var responder: ConcreteResponder { get } 44 | } 45 | public struct CompiledStaticResponderStorageRoute: CompiledStaticResponderStorageRouteProtocol { 46 | public let path:DestinyRoutePathType 47 | public let responder:ConcreteResponder 48 | 49 | public init(path: DestinyRoutePathType, responder: ConcreteResponder) { 50 | self.path = path 51 | self.responder = responder 52 | } 53 | 54 | public var debugDescription: String { 55 | """ 56 | CompiledStaticResponderStorageRoute<\(ConcreteResponder.self)>( 57 | path: \(path.debugDescription), 58 | responder: \(responder.debugDescription) 59 | ) 60 | """ 61 | } 62 | } -------------------------------------------------------------------------------- /Sources/DestinyMacros/DestinyMacros.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(SwiftCompilerPlugin) && canImport(SwiftDiagnostics) && canImport(SwiftSyntaxMacros) 3 | import SwiftCompilerPlugin 4 | import SwiftDiagnostics 5 | import SwiftSyntaxMacros 6 | 7 | // MARK: ErrorDiagnostic 8 | struct DiagnosticMsg: DiagnosticMessage { 9 | let message:String 10 | let diagnosticID:MessageID 11 | let severity:DiagnosticSeverity 12 | 13 | init(id: String, message: String, severity: DiagnosticSeverity = .error) { 14 | self.message = message 15 | self.diagnosticID = MessageID(domain: "DestinyMacros", id: id) 16 | self.severity = severity 17 | } 18 | } 19 | extension DiagnosticMsg: FixItMessage { 20 | var fixItID: MessageID { diagnosticID } 21 | } 22 | 23 | 24 | @main 25 | struct DestinyMacros: CompilerPlugin { 26 | let providingMacros:[any Macro.Type] = [ 27 | Router.self, 28 | HTTPMessage.self 29 | ] 30 | } 31 | #endif -------------------------------------------------------------------------------- /Sources/DestinyMacros/HTTPMessage.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(DestinyBlueprint) && canImport(DestinyDefaults) && canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 3 | import DestinyBlueprint 4 | import DestinyDefaults 5 | import OrderedCollections 6 | import SwiftSyntax 7 | import SwiftSyntaxMacros 8 | 9 | enum HTTPMessage: DeclarationMacro { 10 | static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] { 11 | var version:HTTPVersion = .v1_1 12 | var status = HTTPResponseStatus.notImplemented.code 13 | var headers:OrderedDictionary = [:] 14 | var body:(any ResponseBodyProtocol)? = nil 15 | var contentType:HTTPMediaType? = nil 16 | var charset:Charset? = nil 17 | var cookies:[any HTTPCookieProtocol] = [] // TODO: fix 18 | for child in node.as(ExprSyntax.self)!.macroExpansion!.arguments { 19 | if let key = child.label?.text { 20 | switch key { 21 | case "version": 22 | version = HTTPVersion.parse(child.expression) ?? version 23 | case "status": 24 | status = HTTPResponseStatus.parse(expr: child.expression)?.code ?? status 25 | case "headers": 26 | headers = HTTPRequestHeader.parse(context: context, child.expression) 27 | case "body": 28 | body = ResponseBody.parse(expr: child.expression) 29 | case "contentType": 30 | contentType = HTTPMediaType.parse(context: context, expr: child.expression) ?? contentType 31 | case "charset": 32 | charset = Charset(expr: child.expression) 33 | default: 34 | break 35 | } 36 | } 37 | } 38 | do { 39 | var response = try DestinyDefaults.HTTPResponseMessage( 40 | version: version, 41 | status: status, 42 | headers: headers, 43 | cookies: cookies, 44 | body: body, 45 | contentType: contentType, 46 | charset: charset 47 | ).string(escapeLineBreak: true) 48 | response = "\"" + response + "\"" 49 | return ["\(raw: response)"] 50 | } catch { 51 | return [] 52 | } 53 | } 54 | } 55 | #endif -------------------------------------------------------------------------------- /Sources/DestinyUtilityMacros/DestinyUtilityMacros.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(SwiftCompilerPlugin) && canImport(SwiftDiagnostics) && canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 3 | import SwiftCompilerPlugin 4 | import SwiftDiagnostics 5 | import SwiftSyntax 6 | import SwiftSyntaxMacros 7 | 8 | // MARK: DiagnosticMsg 9 | struct DiagnosticMsg: DiagnosticMessage { 10 | let message:String 11 | let diagnosticID:MessageID 12 | let severity:DiagnosticSeverity 13 | 14 | init(id: String, message: String, severity: DiagnosticSeverity = .error) { 15 | self.message = message 16 | self.diagnosticID = MessageID(domain: "DestinyUtilityMacros", id: id) 17 | self.severity = severity 18 | } 19 | } 20 | extension DiagnosticMsg: FixItMessage { 21 | var fixItID: MessageID { diagnosticID } 22 | } 23 | 24 | 25 | @main 26 | struct DestinyUtilityMacros: CompilerPlugin { 27 | let providingMacros:[any Macro.Type] = [ 28 | HTTPFieldContentType.self, 29 | InlineArrayMacro.self, 30 | HTTPResponseStatusesMacro.self, 31 | HTTPMediaTypeMacro.self, 32 | HTTPRequestMethods.self 33 | ] 34 | } 35 | 36 | // MARK: SwiftSyntax Misc 37 | extension SyntaxProtocol { 38 | var functionCall: FunctionCallExprSyntax? { self.as(FunctionCallExprSyntax.self) } 39 | var stringLiteral: StringLiteralExprSyntax? { self.as(StringLiteralExprSyntax.self) } 40 | var booleanLiteral: BooleanLiteralExprSyntax? { self.as(BooleanLiteralExprSyntax.self) } 41 | var memberAccess: MemberAccessExprSyntax? { self.as(MemberAccessExprSyntax.self) } 42 | var array: ArrayExprSyntax? { self.as(ArrayExprSyntax.self) } 43 | var dictionary: DictionaryExprSyntax? { self.as(DictionaryExprSyntax.self) } 44 | } 45 | 46 | extension StringLiteralExprSyntax { 47 | var string: String { "\(segments)" } 48 | } 49 | #endif -------------------------------------------------------------------------------- /Sources/DestinyUtilityMacros/HTTPMediaTypeMacro.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 3 | import SwiftSyntax 4 | import SwiftSyntaxMacros 5 | 6 | enum HTTPMediaTypeMacro: ExpressionMacro { 7 | static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> ExprSyntax { 8 | var type = "??????" 9 | var subType = "??????" 10 | for argument in node.arguments { 11 | switch argument.label?.text { 12 | case "type": 13 | type = argument.expression.stringLiteral!.string 14 | case "subType": 15 | subType = argument.expression.stringLiteral!.string 16 | default: 17 | break 18 | } 19 | } 20 | let mimeType = "\(type)/\(subType)".compactMap { $0.asciiValue } 21 | let string = "HTTPMediaType.Storage<\(mimeType.count)>(type: \"\(type)\", subType: \"\(subType)\", mimeType: \(mimeType))" 22 | return "\(raw: string)" 23 | } 24 | } 25 | 26 | #endif -------------------------------------------------------------------------------- /Sources/DestinyUtilityMacros/HTTPRequestMethods.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 3 | import SwiftSyntax 4 | import SwiftSyntaxMacros 5 | 6 | enum HTTPRequestMethods: DeclarationMacro { 7 | static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] { 8 | var entries:[Entry] = [] 9 | for argument in node.arguments { 10 | guard let array = argument.expression.array else { break } 11 | for element in array.elements { 12 | if let tuple = element.expression.as(TupleExprSyntax.self)?.elements { 13 | var memberName = "" 14 | var method = "" 15 | for i in 0..<2 { 16 | let index = tuple.index(at: i) 17 | switch i { 18 | case 0: memberName = tuple[tuple.startIndex].expression.stringLiteral!.string 19 | case 1: method = tuple[index].expression.stringLiteral!.string 20 | default: break 21 | } 22 | } 23 | entries.append(Entry(memberName: memberName, method: method, value: get(method: method))) 24 | } 25 | } 26 | } 27 | var string = entries.map { 28 | "public static let \($0.memberName) = \($0.value)" 29 | }.joined(separator: "\n ") 30 | 31 | string += "\n\n public static func parse(expr: T) -> (any HTTPRequestMethodProtocol)? {\n" 32 | string += " guard let string = expr.memberAccess?.declName.baseName.text ?? expr.stringLiteral?.string.lowercased() else { return nil }\n" 33 | string += " switch string {\n" 34 | for entry in entries { 35 | var cases:[String] = [ 36 | entry.memberName, 37 | entry.memberName.uppercased() 38 | ] 39 | if entry.memberName.first == "`" { 40 | var s = entry.memberName 41 | s = String(s[s.index(after: s.startIndex).. String { 53 | return "HTTPRequestMethod.Storage([\(method.compactMap({ guard let v = $0.asciiValue else { return nil }; return String(v) }).joined(separator: ", "))])" 54 | } 55 | } 56 | 57 | extension HTTPRequestMethods { 58 | struct Entry { 59 | let memberName:String 60 | let method:String 61 | let value:String 62 | } 63 | } 64 | 65 | #endif -------------------------------------------------------------------------------- /Sources/DestinyUtilityMacros/HTTPResponseStatusMacro.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 3 | import SwiftSyntax 4 | import SwiftSyntaxMacros 5 | 6 | enum HTTPResponseStatusMacro: ExpressionMacro { 7 | static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> ExprSyntax { 8 | //var name = "??????" 9 | var code = 0 10 | var phrase = "??????" 11 | for argument in node.arguments { 12 | switch argument.label?.text { 13 | //case "name": 14 | //name = argument.expression.stringLiteral!.string 15 | case "code": 16 | code = Int(argument.expression.as(IntegerLiteralExprSyntax.self)!.literal.text)! 17 | case "phrase": 18 | phrase = argument.expression.stringLiteral!.string 19 | default: 20 | break 21 | } 22 | } 23 | let string = get(code: code, phrase: phrase) 24 | return "\(raw: string)" 25 | } 26 | 27 | static func get(code: Int, phrase: String) -> String { 28 | let phraseCount = phrase.count 29 | let codePhraseValues = "\(code) \(phrase)".compactMap { $0.asciiValue } 30 | let phrase = phrase.compactMap { $0.asciiValue }.description 31 | return "HTTPResponseStatus.Storage<\(phraseCount), \(codePhraseValues.count)>(code: \(code), phrase: \(phrase), codePhrase: \(codePhraseValues))" 32 | } 33 | } 34 | 35 | #endif -------------------------------------------------------------------------------- /Sources/DestinyUtilityMacros/HTTPResponseStatusesMacro.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 3 | import SwiftSyntax 4 | import SwiftSyntaxMacros 5 | 6 | enum HTTPResponseStatusesMacro: DeclarationMacro { 7 | static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] { 8 | var entries:[Entry] = [] 9 | for argument in node.arguments { 10 | guard let array = argument.expression.array else { break } 11 | for element in array.elements { 12 | if let tuple = element.expression.as(TupleExprSyntax.self)?.elements { 13 | var memberName = "" 14 | var code:Int = 0 15 | var phrase = "" 16 | for i in 0..<3 { 17 | let index = tuple.index(at: i) 18 | switch i { 19 | case 0: memberName = tuple[tuple.startIndex].expression.stringLiteral!.string 20 | case 1: code = Int(tuple[index].expression.as(IntegerLiteralExprSyntax.self)!.literal.text)! 21 | case 2: phrase = tuple[index].expression.stringLiteral!.string 22 | default: break 23 | } 24 | } 25 | entries.append(Entry(memberName: memberName, code: code, value: HTTPResponseStatusMacro.get(code: code, phrase: phrase))) 26 | } 27 | } 28 | } 29 | let string = entries.map { 30 | "/// https://www.rfc-editor.org/rfc/rfc9110.html#status.\($0.code)\n public static let \($0.memberName) = \($0.value)" 31 | }.joined(separator: "\n ") 32 | return ["\(raw: string)"] 33 | } 34 | } 35 | 36 | extension HTTPResponseStatusesMacro { 37 | struct Entry { 38 | let memberName:String 39 | let code:Int 40 | let value:String 41 | } 42 | } 43 | 44 | #endif -------------------------------------------------------------------------------- /Sources/DestinyUtilityMacros/InlineArrayMacro.swift: -------------------------------------------------------------------------------- 1 | 2 | #if canImport(SwiftSyntax) && canImport(SwiftSyntaxMacros) 3 | import SwiftSyntax 4 | import SwiftSyntaxMacros 5 | 6 | enum InlineArrayMacro: ExpressionMacro { 7 | static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> ExprSyntax { 8 | var expectedCount:Int? = nil 9 | var values:[UInt8] = [] 10 | for argument in node.arguments { 11 | switch argument.label?.text { 12 | case "count": 13 | if let s = argument.expression.as(IntegerLiteralExprSyntax.self)?.literal.text, let i = Int(s) { 14 | expectedCount = i 15 | } 16 | default: 17 | let expr = argument.expression 18 | if let s = expr.stringLiteral?.string ?? expr.as(IntegerLiteralExprSyntax.self)?.literal.text { 19 | values.append(contentsOf: s.compactMap { $0.asciiValue }) 20 | } 21 | break 22 | } 23 | } 24 | if let expectedCount, values.count < expectedCount { 25 | while values.count < expectedCount { 26 | values.append(0) 27 | } 28 | } 29 | return "\(raw: "\(values)")" 30 | } 31 | } 32 | 33 | #endif -------------------------------------------------------------------------------- /Tests/DestinyTests/HTTPDateFormatTests.swift: -------------------------------------------------------------------------------- 1 | 2 | import Destiny 3 | import FoundationEssentials 4 | import SwiftCompression 5 | import Testing 6 | 7 | struct HTTPDateFormatTests { 8 | @Test 9 | func httpDateFormat() { 10 | var value = HTTPDateFormat.get(year: 2025, month: 3, day: 18, dayOfWeek: 5, hour: 22, minute: 13, second: 0) 11 | #expect(value.string() == "Fri, 18 Apr 2025 22:13:00 GMT") 12 | 13 | value = HTTPDateFormat.get(year: 1, month: 0, day: 1, dayOfWeek: 0, hour: 1, minute: 1, second: 1) 14 | #expect(value.string() == "Sun, 01 Jan 1 01:01:01 GMT") 15 | 16 | value = HTTPDateFormat.get(year: 69, month: 11, day: 32, dayOfWeek: 6, hour: 10, minute: 59, second: 30) 17 | #expect(value.string() == "Sat, 32 Dec 69 10:59:30 GMT") 18 | 19 | value = HTTPDateFormat.get(date: Date(timeIntervalSince1970: 0)) 20 | #expect(value.string() == "Wed, 31 Dec 1969 18:00:00 GMT") 21 | 22 | value = HTTPDateFormat.get(date: Date(timeIntervalSince1970: 1745033167)) 23 | #expect(value.string() == "Fri, 18 Apr 2025 22:26:07 GMT") 24 | } 25 | } -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | import http from 'k6/http'; 2 | import { sleep } from 'k6'; 3 | 4 | export const options = { 5 | // A number specifying the number of VUs to run concurrently. 6 | vus: 10000, 7 | // A string specifying the total duration of the test run. 8 | duration: '10s', 9 | 10 | // Uncomment this section to enable the use of Browser API in your tests. 11 | // 12 | // See https://grafana.com/docs/k6/latest/using-k6-browser/running-browser-tests/ to learn more 13 | // about using Browser API in your test scripts. 14 | // 15 | // scenarios: { 16 | // // The scenario name appears in the result summary, tags, and so on. 17 | // // You can give the scenario any name, as long as each name in the script is unique. 18 | // ui: { 19 | // // Executor is a mandatory parameter for browser-based tests. 20 | // // Shared iterations in this case tells k6 to reuse VUs to execute iterations. 21 | // // 22 | // // See https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/ for other executor types. 23 | // executor: 'shared-iterations', 24 | // options: { 25 | // browser: { 26 | // // This is a mandatory parameter that instructs k6 to launch and 27 | // // connect to a chromium-based browser, and use it to run UI-based 28 | // // tests. 29 | // type: 'chromium', 30 | // }, 31 | // }, 32 | // }, 33 | // } 34 | }; 35 | 36 | // The function that defines VU logic. 37 | // 38 | // See https://grafana.com/docs/k6/latest/examples/get-started-with-k6/ to learn more 39 | // about authoring k6 scripts. 40 | // 41 | export default function() { 42 | http.get('http://192.168.1.174:8080/html'); 43 | sleep(1); 44 | } 45 | --------------------------------------------------------------------------------