From 6e2d6046217849c7dfb792b48b2b8e1cdcc2dfd1 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 21 Feb 2025 10:47:21 +0000 Subject: [PATCH] Make everything inlinable. There are a number of performance constraints that can be lifted in the source-available build mode. Many of these are ultimately about specialization, but we should resolve all of these with a judicious sprinkling of @inlinables. --- Sources/HTTPTypes/HTTPField.swift | 31 +++- Sources/HTTPTypes/HTTPFieldName.swift | 161 +++++++++--------- Sources/HTTPTypes/HTTPFields.swift | 70 ++++++-- Sources/HTTPTypes/HTTPParsedFields.swift | 48 ++++-- Sources/HTTPTypes/HTTPRequest.swift | 52 ++++-- Sources/HTTPTypes/HTTPResponse.swift | 129 ++++++++------ Sources/HTTPTypes/ISOLatin1String.swift | 12 +- Sources/HTTPTypes/NIOLock.swift | 22 ++- .../HTTPTypesFoundation/HTTPRequest+URL.swift | 6 +- .../HTTPTypes+ISOLatin1.swift | 3 + .../URLRequest+HTTPTypes.swift | 2 + .../URLResponse+HTTPTypes.swift | 2 + .../URLSession+HTTPTypes.swift | 13 +- 13 files changed, 371 insertions(+), 180 deletions(-) diff --git a/Sources/HTTPTypes/HTTPField.swift b/Sources/HTTPTypes/HTTPField.swift index 8645aa9..44170c5 100644 --- a/Sources/HTTPTypes/HTTPField.swift +++ b/Sources/HTTPTypes/HTTPField.swift @@ -20,28 +20,32 @@ public struct HTTPField: Sendable, Hashable { /// The strategy for whether the field is indexed in the HPACK or QPACK dynamic table. public struct DynamicTableIndexingStrategy: Sendable, Hashable { /// Default strategy. + @inlinable public static var automatic: Self { .init(uncheckedValue: 0) } /// Always put this field in the dynamic table if possible. + @inlinable public static var prefer: Self { .init(uncheckedValue: 1) } /// Don't put this field in the dynamic table. + @inlinable public static var avoid: Self { .init(uncheckedValue: 2) } /// Don't put this field in the dynamic table, and set a flag to disallow intermediaries to /// index this field. + @inlinable public static var disallow: Self { .init(uncheckedValue: 3) } - fileprivate let rawValue: UInt8 + /* fileprivate but */ @usableFromInline let rawValue: UInt8 - private static let maxRawValue: UInt8 = 3 + /* private but */ @usableFromInline static let maxRawValue: UInt8 = 3 - private init(uncheckedValue: UInt8) { + /* private but */ @inlinable init(uncheckedValue: UInt8) { assert(uncheckedValue <= Self.maxRawValue) self.rawValue = uncheckedValue } - fileprivate init?(rawValue: UInt8) { + /* fileprivate but */ @inlinable init?(rawValue: UInt8) { if rawValue > Self.maxRawValue { return nil } @@ -54,6 +58,7 @@ public struct HTTPField: Sendable, Hashable { /// - name: The HTTP field name. /// - value: The HTTP field value is initialized from the UTF-8 encoded bytes of the string. /// Invalid bytes are converted into space characters. + @inlinable public init(name: Name, value: String) { self.name = name self.rawValue = Self.legalizeValue(ISOLatin1String(value)) @@ -63,6 +68,7 @@ public struct HTTPField: Sendable, Hashable { /// - Parameters: /// - name: The HTTP field name. /// - value: The HTTP field value. Invalid bytes are converted into space characters. + @inlinable public init(name: Name, value: some Collection) { self.name = name self.rawValue = Self.legalizeValue(ISOLatin1String(value)) @@ -73,11 +79,13 @@ public struct HTTPField: Sendable, Hashable { /// - name: The HTTP field name. /// - lenientValue: The HTTP field value. Newlines and NULs are converted into space /// characters. + @inlinable public init(name: Name, lenientValue: some Collection) { self.name = name self.rawValue = Self.lenientLegalizeValue(ISOLatin1String(lenientValue)) } + @inlinable init(name: Name, uncheckedValue: ISOLatin1String) { self.name = name self.rawValue = uncheckedValue @@ -94,6 +102,7 @@ public struct HTTPField: Sendable, Hashable { /// /// If the field is not UTF-8 encoded, `withUnsafeBytesOfValue` can be used to access the /// underlying bytes of the field value. + @inlinable public var value: String { get { self.rawValue.string @@ -112,6 +121,7 @@ public struct HTTPField: Sendable, Hashable { /// /// - Parameter body: The closure to be invoked with the buffer. /// - Returns: Result of the `body` closure. + @inlinable public func withUnsafeBytesOfValue( _ body: (UnsafeBufferPointer) throws -> Result ) rethrows -> Result { @@ -121,9 +131,10 @@ public struct HTTPField: Sendable, Hashable { /// The strategy for whether the field is indexed in the HPACK or QPACK dynamic table. public var indexingStrategy: DynamicTableIndexingStrategy = .automatic + @usableFromInline var rawValue: ISOLatin1String - private static func _isValidValue(_ bytes: some Sequence) -> Bool { + /* private but */ @inlinable static func _isValidValue(_ bytes: some Sequence) -> Bool { var iterator = bytes.makeIterator() guard var byte = iterator.next() else { // Empty string is allowed. @@ -155,6 +166,7 @@ public struct HTTPField: Sendable, Hashable { return true } + @inlinable static func legalizeValue(_ value: ISOLatin1String) -> ISOLatin1String { if self._isValidValue(value._storage.utf8) { return value @@ -176,6 +188,7 @@ public struct HTTPField: Sendable, Hashable { } } + @inlinable static func lenientLegalizeValue(_ value: ISOLatin1String) -> ISOLatin1String { if value._storage.utf8.allSatisfy({ $0 != 0x00 && $0 != 0x0A && $0 != 0x0D }) { return value @@ -198,6 +211,7 @@ public struct HTTPField: Sendable, Hashable { /// /// - Parameter value: The string to validate. /// - Returns: Whether the string is valid. + @inlinable public static func isValidValue(_ value: String) -> Bool { self._isValidValue(value.utf8) } @@ -208,30 +222,35 @@ public struct HTTPField: Sendable, Hashable { /// /// - Parameter value: The byte collection to validate. /// - Returns: Whether the byte collection is valid. + @inlinable public static func isValidValue(_ value: some Collection) -> Bool { self._isValidValue(value) } } extension HTTPField: CustomStringConvertible { + @inlinable public var description: String { "\(self.name): \(self.value)" } } extension HTTPField: CustomPlaygroundDisplayConvertible { + @inlinable public var playgroundDescription: Any { self.description } } extension HTTPField: Codable { + @usableFromInline enum CodingKeys: String, CodingKey { case name case value case indexingStrategy } + @inlinable public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.name, forKey: .name) @@ -241,6 +260,7 @@ extension HTTPField: Codable { } } + @inlinable public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let name = try container.decode(Name.self, forKey: .name) @@ -262,6 +282,7 @@ extension HTTPField: Codable { } extension HTTPField { + @inlinable static func isValidToken(_ token: some StringProtocol) -> Bool { !token.isEmpty && token.utf8.allSatisfy { diff --git a/Sources/HTTPTypes/HTTPFieldName.swift b/Sources/HTTPTypes/HTTPFieldName.swift index 550505e..3ecc990 100644 --- a/Sources/HTTPTypes/HTTPFieldName.swift +++ b/Sources/HTTPTypes/HTTPFieldName.swift @@ -40,6 +40,7 @@ extension HTTPField { /// /// - Parameter name: The name of the HTTP field. It can be accessed from the `rawName` /// property. + @inlinable public init?(_ name: String) { guard HTTPField.isValidToken(name) else { return nil @@ -56,6 +57,7 @@ extension HTTPField { /// /// - Parameter name: The name of the HTTP field or the HTTP pseudo header field. It must /// be lowercased. + @inlinable public init?(parsed name: String) { guard !name.isEmpty else { return nil @@ -84,11 +86,12 @@ extension HTTPField { self.canonicalName = name } - private init(rawName: String, canonicalName: String) { + /* private but */ @inlinable init(rawName: String, canonicalName: String) { self.rawName = rawName self.canonicalName = canonicalName } + @inlinable var isPseudo: Bool { self.rawName.utf8.first == UInt8(ascii: ":") } @@ -96,33 +99,39 @@ extension HTTPField { } extension HTTPField.Name: Hashable { + @inlinable public func hash(into hasher: inout Hasher) { hasher.combine(self.canonicalName) } + @inlinable public static func == (lhs: HTTPField.Name, rhs: HTTPField.Name) -> Bool { lhs.canonicalName == rhs.canonicalName } } extension HTTPField.Name: LosslessStringConvertible { + @inlinable public var description: String { self.rawName } } extension HTTPField.Name: CustomPlaygroundDisplayConvertible { + @inlinable public var playgroundDescription: Any { self.description } } extension HTTPField.Name: Codable { + @inlinable public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(self.rawName) } + @inlinable public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let nameString = try container.decode(String.self) @@ -149,213 +158,213 @@ extension HTTPField.Name: Codable { } extension HTTPField.Name { - static var method: Self { .init(rawName: ":method", canonicalName: ":method") } - static var scheme: Self { .init(rawName: ":scheme", canonicalName: ":scheme") } - static var authority: Self { .init(rawName: ":authority", canonicalName: ":authority") } - static var path: Self { .init(rawName: ":path", canonicalName: ":path") } - static var `protocol`: Self { .init(rawName: ":protocol", canonicalName: ":protocol") } - static var status: Self { .init(rawName: ":status", canonicalName: ":status") } + @inlinable static var method: Self { .init(rawName: ":method", canonicalName: ":method") } + @inlinable static var scheme: Self { .init(rawName: ":scheme", canonicalName: ":scheme") } + @inlinable static var authority: Self { .init(rawName: ":authority", canonicalName: ":authority") } + @inlinable static var path: Self { .init(rawName: ":path", canonicalName: ":path") } + @inlinable static var `protocol`: Self { .init(rawName: ":protocol", canonicalName: ":protocol") } + @inlinable static var status: Self { .init(rawName: ":status", canonicalName: ":status") } /// Accept /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var accept: Self { .init(rawName: "Accept", canonicalName: "accept") } + @inlinable public static var accept: Self { .init(rawName: "Accept", canonicalName: "accept") } /// Accept-Encoding /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var acceptEncoding: Self { .init(rawName: "Accept-Encoding", canonicalName: "accept-encoding") } + @inlinable public static var acceptEncoding: Self { .init(rawName: "Accept-Encoding", canonicalName: "accept-encoding") } /// Accept-Language /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var acceptLanguage: Self { .init(rawName: "Accept-Language", canonicalName: "accept-language") } + @inlinable public static var acceptLanguage: Self { .init(rawName: "Accept-Language", canonicalName: "accept-language") } /// Accept-Ranges /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var acceptRanges: Self { .init(rawName: "Accept-Ranges", canonicalName: "accept-ranges") } + @inlinable public static var acceptRanges: Self { .init(rawName: "Accept-Ranges", canonicalName: "accept-ranges") } /// Access-Control-Allow-Credentials /// /// https://fetch.spec.whatwg.org/ - public static var accessControlAllowCredentials: Self { + @inlinable public static var accessControlAllowCredentials: Self { .init(rawName: "Access-Control-Allow-Credentials", canonicalName: "access-control-allow-credentials") } /// Access-Control-Allow-Headers /// /// https://fetch.spec.whatwg.org/ - public static var accessControlAllowHeaders: Self { + @inlinable public static var accessControlAllowHeaders: Self { .init(rawName: "Access-Control-Allow-Headers", canonicalName: "access-control-allow-headers") } /// Access-Control-Allow-Methods /// /// https://fetch.spec.whatwg.org/ - public static var accessControlAllowMethods: Self { + @inlinable public static var accessControlAllowMethods: Self { .init(rawName: "Access-Control-Allow-Methods", canonicalName: "access-control-allow-methods") } /// Access-Control-Allow-Origin /// /// https://fetch.spec.whatwg.org/ - public static var accessControlAllowOrigin: Self { + @inlinable public static var accessControlAllowOrigin: Self { .init(rawName: "Access-Control-Allow-Origin", canonicalName: "access-control-allow-origin") } /// Access-Control-Expose-Headers /// /// https://fetch.spec.whatwg.org/ - public static var accessControlExposeHeaders: Self { + @inlinable public static var accessControlExposeHeaders: Self { .init(rawName: "Access-Control-Expose-Headers", canonicalName: "access-control-expose-headers") } /// Access-Control-Max-Age /// /// https://fetch.spec.whatwg.org/ - public static var accessControlMaxAge: Self { + @inlinable public static var accessControlMaxAge: Self { .init(rawName: "Access-Control-Max-Age", canonicalName: "access-control-max-age") } /// Access-Control-Request-Headers /// /// https://fetch.spec.whatwg.org/ - public static var accessControlRequestHeaders: Self { + @inlinable public static var accessControlRequestHeaders: Self { .init(rawName: "Access-Control-Request-Headers", canonicalName: "access-control-request-headers") } /// Access-Control-Request-Method /// /// https://fetch.spec.whatwg.org/ - public static var accessControlRequestMethod: Self { + @inlinable public static var accessControlRequestMethod: Self { .init(rawName: "Access-Control-Request-Method", canonicalName: "access-control-request-method") } /// Age /// /// https://www.rfc-editor.org/rfc/rfc9111.html - public static var age: Self { .init(rawName: "Age", canonicalName: "age") } + @inlinable public static var age: Self { .init(rawName: "Age", canonicalName: "age") } /// Allow /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var allow: Self { .init(rawName: "Allow", canonicalName: "allow") } + @inlinable public static var allow: Self { .init(rawName: "Allow", canonicalName: "allow") } /// Authentication-Info /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var authenticationInfo: Self { + @inlinable public static var authenticationInfo: Self { .init(rawName: "Authentication-Info", canonicalName: "authentication-info") } /// Authorization /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var authorization: Self { .init(rawName: "Authorization", canonicalName: "authorization") } + @inlinable public static var authorization: Self { .init(rawName: "Authorization", canonicalName: "authorization") } /// Cache-Control /// /// https://www.rfc-editor.org/rfc/rfc9111.html - public static var cacheControl: Self { .init(rawName: "Cache-Control", canonicalName: "cache-control") } + @inlinable public static var cacheControl: Self { .init(rawName: "Cache-Control", canonicalName: "cache-control") } /// Connection /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var connection: Self { .init(rawName: "Connection", canonicalName: "connection") } + @inlinable public static var connection: Self { .init(rawName: "Connection", canonicalName: "connection") } /// Content-Disposition /// /// https://www.rfc-editor.org/rfc/rfc6266.html - public static var contentDisposition: Self { + @inlinable public static var contentDisposition: Self { .init(rawName: "Content-Disposition", canonicalName: "content-disposition") } /// Content-Encoding /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var contentEncoding: Self { .init(rawName: "Content-Encoding", canonicalName: "content-encoding") } + @inlinable public static var contentEncoding: Self { .init(rawName: "Content-Encoding", canonicalName: "content-encoding") } /// Content-Language /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var contentLanguage: Self { .init(rawName: "Content-Language", canonicalName: "content-language") } + @inlinable public static var contentLanguage: Self { .init(rawName: "Content-Language", canonicalName: "content-language") } /// Content-Length /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var contentLength: Self { .init(rawName: "Content-Length", canonicalName: "content-length") } + @inlinable public static var contentLength: Self { .init(rawName: "Content-Length", canonicalName: "content-length") } /// Content-Location /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var contentLocation: Self { .init(rawName: "Content-Location", canonicalName: "content-location") } + @inlinable public static var contentLocation: Self { .init(rawName: "Content-Location", canonicalName: "content-location") } /// Content-Range /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var contentRange: Self { .init(rawName: "Content-Range", canonicalName: "content-range") } + @inlinable public static var contentRange: Self { .init(rawName: "Content-Range", canonicalName: "content-range") } /// Content-Security-Policy /// /// https://www.w3.org/TR/CSP/ - public static var contentSecurityPolicy: Self { + @inlinable public static var contentSecurityPolicy: Self { .init(rawName: "Content-Security-Policy", canonicalName: "content-security-policy") } /// Content-Security-Policy-Report-Only /// /// https://www.w3.org/TR/CSP/ - public static var contentSecurityPolicyReportOnly: Self { + @inlinable public static var contentSecurityPolicyReportOnly: Self { .init(rawName: "Content-Security-Policy-Report-Only", canonicalName: "content-security-policy-report-only") } /// Content-Type /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var contentType: Self { .init(rawName: "Content-Type", canonicalName: "content-type") } + @inlinable public static var contentType: Self { .init(rawName: "Content-Type", canonicalName: "content-type") } /// Cookie /// /// https://www.rfc-editor.org/rfc/rfc6265.html - public static var cookie: Self { .init(rawName: "Cookie", canonicalName: "cookie") } + @inlinable public static var cookie: Self { .init(rawName: "Cookie", canonicalName: "cookie") } /// Cross-Origin-Resource-Policy /// /// https://fetch.spec.whatwg.org/ - public static var crossOriginResourcePolicy: Self { + @inlinable public static var crossOriginResourcePolicy: Self { .init(rawName: "Cross-Origin-Resource-Policy", canonicalName: "cross-origin-resource-policy") } /// Date /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var date: Self { .init(rawName: "Date", canonicalName: "date") } + @inlinable public static var date: Self { .init(rawName: "Date", canonicalName: "date") } /// Early-Data /// /// https://www.rfc-editor.org/rfc/rfc8470.html - public static var earlyData: Self { .init(rawName: "Early-Data", canonicalName: "early-data") } + @inlinable public static var earlyData: Self { .init(rawName: "Early-Data", canonicalName: "early-data") } /// ETag /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var eTag: Self { .init(rawName: "ETag", canonicalName: "etag") } + @inlinable public static var eTag: Self { .init(rawName: "ETag", canonicalName: "etag") } /// Expect /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var expect: Self { .init(rawName: "Expect", canonicalName: "expect") } + @inlinable public static var expect: Self { .init(rawName: "Expect", canonicalName: "expect") } /// Expires /// /// https://www.rfc-editor.org/rfc/rfc9111.html - public static var expires: Self { .init(rawName: "Expires", canonicalName: "expires") } + @inlinable public static var expires: Self { .init(rawName: "Expires", canonicalName: "expires") } /// From /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var from: Self { .init(rawName: "From", canonicalName: "from") } + @inlinable public static var from: Self { .init(rawName: "From", canonicalName: "from") } /// Host /// @@ -366,100 +375,100 @@ extension HTTPField.Name { /// If-Match /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var ifMatch: Self { .init(rawName: "If-Match", canonicalName: "if-match") } + @inlinable public static var ifMatch: Self { .init(rawName: "If-Match", canonicalName: "if-match") } /// If-Modified-Since /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var ifModifiedSince: Self { .init(rawName: "If-Modified-Since", canonicalName: "if-modified-since") } + @inlinable public static var ifModifiedSince: Self { .init(rawName: "If-Modified-Since", canonicalName: "if-modified-since") } /// If-None-Match /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var ifNoneMatch: Self { .init(rawName: "If-None-Match", canonicalName: "if-none-match") } + @inlinable public static var ifNoneMatch: Self { .init(rawName: "If-None-Match", canonicalName: "if-none-match") } /// If-Range /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var ifRange: Self { .init(rawName: "If-Range", canonicalName: "if-range") } + @inlinable public static var ifRange: Self { .init(rawName: "If-Range", canonicalName: "if-range") } /// If-Unmodified-Since /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var ifUnmodifiedSince: Self { + @inlinable public static var ifUnmodifiedSince: Self { .init(rawName: "If-Unmodified-Since", canonicalName: "if-unmodified-since") } /// Last-Modified /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var lastModified: Self { .init(rawName: "Last-Modified", canonicalName: "last-modified") } + @inlinable public static var lastModified: Self { .init(rawName: "Last-Modified", canonicalName: "last-modified") } /// Location /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var location: Self { .init(rawName: "Location", canonicalName: "location") } + @inlinable public static var location: Self { .init(rawName: "Location", canonicalName: "location") } /// Max-Forwards /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var maxForwards: Self { .init(rawName: "Max-Forwards", canonicalName: "max-forwards") } + @inlinable public static var maxForwards: Self { .init(rawName: "Max-Forwards", canonicalName: "max-forwards") } /// Origin /// /// https://www.rfc-editor.org/rfc/rfc6454.html - public static var origin: Self { .init(rawName: "Origin", canonicalName: "origin") } + @inlinable public static var origin: Self { .init(rawName: "Origin", canonicalName: "origin") } /// Priority /// /// https://www.rfc-editor.org/rfc/rfc9218.html - public static var priority: Self { .init(rawName: "Priority", canonicalName: "priority") } + @inlinable public static var priority: Self { .init(rawName: "Priority", canonicalName: "priority") } /// Proxy-Authenticate /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var proxyAuthenticate: Self { + @inlinable public static var proxyAuthenticate: Self { .init(rawName: "Proxy-Authenticate", canonicalName: "proxy-authenticate") } /// Proxy-Authentication-Info /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var proxyAuthenticationInfo: Self { + @inlinable public static var proxyAuthenticationInfo: Self { .init(rawName: "Proxy-Authentication-Info", canonicalName: "proxy-authentication-info") } /// Proxy-Authorization /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var proxyAuthorization: Self { + @inlinable public static var proxyAuthorization: Self { .init(rawName: "Proxy-Authorization", canonicalName: "proxy-authorization") } /// Proxy-Status /// /// https://www.rfc-editor.org/rfc/rfc9209.html - public static var proxyStatus: Self { .init(rawName: "Proxy-Status", canonicalName: "proxy-status") } + @inlinable public static var proxyStatus: Self { .init(rawName: "Proxy-Status", canonicalName: "proxy-status") } /// Range /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var range: Self { .init(rawName: "Range", canonicalName: "range") } + @inlinable public static var range: Self { .init(rawName: "Range", canonicalName: "range") } /// Referer /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var referer: Self { .init(rawName: "Referer", canonicalName: "referer") } + @inlinable public static var referer: Self { .init(rawName: "Referer", canonicalName: "referer") } /// Retry-After /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var retryAfter: Self { .init(rawName: "Retry-After", canonicalName: "retry-after") } + @inlinable public static var retryAfter: Self { .init(rawName: "Retry-After", canonicalName: "retry-after") } /// Sec-Purpose /// /// https://fetch.spec.whatwg.org/ - public static var secPurpose: Self { .init(rawName: "Sec-Purpose", canonicalName: "sec-purpose") } + @inlinable public static var secPurpose: Self { .init(rawName: "Sec-Purpose", canonicalName: "sec-purpose") } /// Sec-WebSocket-Accept /// @@ -471,90 +480,90 @@ extension HTTPField.Name { /// Sec-WebSocket-Extensions /// /// https://www.rfc-editor.org/rfc/rfc6455.html - public static var secWebSocketExtensions: Self { + @inlinable public static var secWebSocketExtensions: Self { .init(rawName: "Sec-WebSocket-Extensions", canonicalName: "sec-websocket-extensions") } /// Sec-WebSocket-Key /// /// https://www.rfc-editor.org/rfc/rfc6455.html - public static var secWebSocketKey: Self { .init(rawName: "Sec-WebSocket-Key", canonicalName: "sec-websocket-key") } + @inlinable public static var secWebSocketKey: Self { .init(rawName: "Sec-WebSocket-Key", canonicalName: "sec-websocket-key") } /// Sec-WebSocket-Protocol /// /// https://www.rfc-editor.org/rfc/rfc6455.html - public static var secWebSocketProtocol: Self { + @inlinable public static var secWebSocketProtocol: Self { .init(rawName: "Sec-WebSocket-Protocol", canonicalName: "sec-websocket-protocol") } /// Sec-WebSocket-Version /// /// https://www.rfc-editor.org/rfc/rfc6455.html - public static var secWebSocketVersion: Self { + @inlinable public static var secWebSocketVersion: Self { .init(rawName: "Sec-WebSocket-Version", canonicalName: "sec-websocket-version") } /// Server /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var server: Self { .init(rawName: "Server", canonicalName: "server") } + @inlinable public static var server: Self { .init(rawName: "Server", canonicalName: "server") } /// Set-Cookie /// /// https://www.rfc-editor.org/rfc/rfc6265.html - public static var setCookie: Self { .init(rawName: "Set-Cookie", canonicalName: "set-cookie") } + @inlinable public static var setCookie: Self { .init(rawName: "Set-Cookie", canonicalName: "set-cookie") } /// Strict-Transport-Security /// /// https://www.rfc-editor.org/rfc/rfc6797.html - public static var strictTransportSecurity: Self { + @inlinable public static var strictTransportSecurity: Self { .init(rawName: "Strict-Transport-Security", canonicalName: "strict-transport-security") } /// TE /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var te: Self { .init(rawName: "TE", canonicalName: "te") } + @inlinable public static var te: Self { .init(rawName: "TE", canonicalName: "te") } /// Trailer /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var trailer: Self { .init(rawName: "Trailer", canonicalName: "trailer") } + @inlinable public static var trailer: Self { .init(rawName: "Trailer", canonicalName: "trailer") } /// Transfer-Encoding /// /// https://www.rfc-editor.org/rfc/rfc9112.html - public static var transferEncoding: Self { .init(rawName: "Transfer-Encoding", canonicalName: "transfer-encoding") } + @inlinable public static var transferEncoding: Self { .init(rawName: "Transfer-Encoding", canonicalName: "transfer-encoding") } /// Upgrade /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var upgrade: Self { .init(rawName: "Upgrade", canonicalName: "upgrade") } + @inlinable public static var upgrade: Self { .init(rawName: "Upgrade", canonicalName: "upgrade") } /// User-Agent /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var userAgent: Self { .init(rawName: "User-Agent", canonicalName: "user-agent") } + @inlinable public static var userAgent: Self { .init(rawName: "User-Agent", canonicalName: "user-agent") } /// Vary /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var vary: Self { .init(rawName: "Vary", canonicalName: "vary") } + @inlinable public static var vary: Self { .init(rawName: "Vary", canonicalName: "vary") } /// Via /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var via: Self { .init(rawName: "Via", canonicalName: "via") } + @inlinable public static var via: Self { .init(rawName: "Via", canonicalName: "via") } /// WWW-Authenticate /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var wwwAuthenticate: Self { .init(rawName: "WWW-Authenticate", canonicalName: "www-authenticate") } + @inlinable public static var wwwAuthenticate: Self { .init(rawName: "WWW-Authenticate", canonicalName: "www-authenticate") } /// X-Content-Type-Options /// /// https://fetch.spec.whatwg.org/ - public static var xContentTypeOptions: Self { + @inlinable public static var xContentTypeOptions: Self { .init(rawName: "X-Content-Type-Options", canonicalName: "x-content-type-options") } } diff --git a/Sources/HTTPTypes/HTTPFields.swift b/Sources/HTTPTypes/HTTPFields.swift index 5b63be5..a2f6901 100644 --- a/Sources/HTTPTypes/HTTPFields.swift +++ b/Sources/HTTPTypes/HTTPFields.swift @@ -25,17 +25,20 @@ import Synchronization /// `HTTPFields` adheres to modern HTTP semantics. In particular, the "Cookie" request header field /// is split into separate header fields by default. public struct HTTPFields: Sendable, Hashable { - private class _Storage: @unchecked Sendable, Hashable { - var fields: [(field: HTTPField, next: UInt16)] = [] - var index: [String: (first: UInt16, last: UInt16)]? = [:] + /* private but */ @usableFromInline class _Storage: @unchecked Sendable, Hashable { + @usableFromInline var fields: [(field: HTTPField, next: UInt16)] = [] + @usableFromInline var index: [String: (first: UInt16, last: UInt16)]? = [:] + @inlinable required init() { } + @inlinable func withLock(_ body: () throws -> Result) rethrows -> Result { fatalError() } + @inlinable var ensureIndex: [String: (first: UInt16, last: UInt16)] { self.withLock { if let index = self.index { @@ -55,6 +58,7 @@ public struct HTTPFields: Sendable, Hashable { } } + @inlinable func copy() -> Self { let newStorage = Self() newStorage.fields = self.fields @@ -64,12 +68,14 @@ public struct HTTPFields: Sendable, Hashable { return newStorage } + @inlinable func hash(into hasher: inout Hasher) { for (field, _) in self.fields { hasher.combine(field) } } + @inlinable static func == (lhs: _Storage, rhs: _Storage) -> Bool { let leftFieldsIndex = lhs.ensureIndex let rightFieldsIndex = rhs.ensureIndex @@ -96,6 +102,7 @@ public struct HTTPFields: Sendable, Hashable { return true } + @inlinable func append(field: HTTPField) { precondition(!field.name.isPseudo, "Pseudo header field \"\(field.name)\" disallowed") let name = field.name.canonicalName @@ -111,9 +118,10 @@ public struct HTTPFields: Sendable, Hashable { #if compiler(>=6.0) @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - private final class _StorageWithMutex: _Storage, @unchecked Sendable { - let mutex = Mutex(()) + /* private but */ @usableFromInline final class _StorageWithMutex: _Storage, @unchecked Sendable { + @usableFromInline let mutex = Mutex(()) + @inlinable override func withLock(_ body: () throws -> Result) rethrows -> Result { try self.mutex.withLock { _ in try body() @@ -122,9 +130,10 @@ public struct HTTPFields: Sendable, Hashable { } #endif // compiler(>=6.0) - private final class _StorageWithNIOLock: _Storage, @unchecked Sendable { - let lock = LockStorage.create(value: ()) + /* private but */ @usableFromInline final class _StorageWithNIOLock: _Storage, @unchecked Sendable { + @usableFromInline let lock = LockStorage.create(value: ()) + @inlinable override func withLock(_ body: () throws -> Result) rethrows -> Result { try self.lock.withLockedValue { _ in try body() @@ -132,7 +141,7 @@ public struct HTTPFields: Sendable, Hashable { } } - private var _storage = { + /* private but */ @usableFromInline var _storage = { #if compiler(>=6.0) if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) { _StorageWithMutex() @@ -145,6 +154,7 @@ public struct HTTPFields: Sendable, Hashable { }() /// Create an empty list of HTTP fields + @inlinable public init() {} /// Access the field value string by name. @@ -163,6 +173,7 @@ public struct HTTPFields: Sendable, Hashable { /// /// When setting a "Cookie" header field value, it is split into multiple "Cookie" fields by /// semicolon. + @inlinable public subscript(name: HTTPField.Name) -> String? { get { let fields = self.fields(for: name) @@ -194,6 +205,7 @@ public struct HTTPFields: Sendable, Hashable { } /// Access the field values by name as an array of strings. The order of fields is preserved. + @inlinable public subscript(values name: HTTPField.Name) -> [String] { get { self.fields(for: name).map(\.value) @@ -204,6 +216,7 @@ public struct HTTPFields: Sendable, Hashable { } /// Access the fields by name as an array. The order of fields is preserved. + @inlinable public subscript(fields name: HTTPField.Name) -> [HTTPField] { get { Array(self.fields(for: name)) @@ -213,14 +226,22 @@ public struct HTTPFields: Sendable, Hashable { } } - private struct HTTPFieldSequence: Sequence { - let fields: [(field: HTTPField, next: UInt16)] - let index: UInt16 + /* private but */ @usableFromInline struct HTTPFieldSequence: Sequence { + @usableFromInline let fields: [(field: HTTPField, next: UInt16)] + @usableFromInline let index: UInt16 + @usableFromInline struct Iterator: IteratorProtocol { - let fields: [(field: HTTPField, next: UInt16)] - var index: UInt16 + @usableFromInline let fields: [(field: HTTPField, next: UInt16)] + @usableFromInline var index: UInt16 + @inlinable + init(fields: [(field: HTTPField, next: UInt16)], index: UInt16) { + self.fields = fields + self.index = index + } + + @inlinable mutating func next() -> HTTPField? { if self.index == .max { return nil @@ -231,17 +252,24 @@ public struct HTTPFields: Sendable, Hashable { } } + @inlinable + init(fields: [(field: HTTPField, next: UInt16)], index: UInt16) { + self.fields = fields + self.index = index + } + + @inlinable func makeIterator() -> Iterator { Iterator(fields: self.fields, index: self.index) } } - private func fields(for name: HTTPField.Name) -> HTTPFieldSequence { + /* private but */ @inlinable func fields(for name: HTTPField.Name) -> HTTPFieldSequence { let index = self._storage.ensureIndex[name.canonicalName]?.first ?? .max return HTTPFieldSequence(fields: self._storage.fields, index: index) } - private mutating func setFields(_ fieldSequence: some Sequence, for name: HTTPField.Name) { + /* private but */ @inlinable mutating func setFields(_ fieldSequence: some Sequence, for name: HTTPField.Name) { if !isKnownUniquelyReferenced(&self._storage) { self._storage = self._storage.copy() } @@ -268,12 +296,14 @@ public struct HTTPFields: Sendable, Hashable { /// Whether one or more field with this name exists in the fields. /// - Parameter name: The field name. /// - Returns: Whether a field exists. + @inlinable public func contains(_ name: HTTPField.Name) -> Bool { self._storage.ensureIndex[name.canonicalName] != nil } } extension HTTPFields: ExpressibleByDictionaryLiteral { + @inlinable public init(dictionaryLiteral elements: (HTTPField.Name, String)...) { self.reserveCapacity(elements.count) for (name, value) in elements { @@ -288,18 +318,22 @@ extension HTTPFields: RangeReplaceableCollection, RandomAccessCollection, Mutabl public typealias Element = HTTPField public typealias Index = Int + @inlinable public var startIndex: Int { self._storage.fields.startIndex } + @inlinable public var endIndex: Int { self._storage.fields.endIndex } + @inlinable public var isEmpty: Bool { self._storage.fields.isEmpty } + @inlinable public subscript(position: Int) -> HTTPField { get { guard position >= self.startIndex, position < self.endIndex else { @@ -325,6 +359,7 @@ extension HTTPFields: RangeReplaceableCollection, RandomAccessCollection, Mutabl } } + @inlinable public mutating func replaceSubrange(_ subrange: Range, with newElements: C) where C: Collection, Element == C.Element { if !isKnownUniquelyReferenced(&self._storage) { @@ -348,6 +383,7 @@ extension HTTPFields: RangeReplaceableCollection, RandomAccessCollection, Mutabl } } + @inlinable public mutating func reserveCapacity(_ capacity: Int) { if !isKnownUniquelyReferenced(&self._storage) { self._storage = self._storage.copy() @@ -358,17 +394,20 @@ extension HTTPFields: RangeReplaceableCollection, RandomAccessCollection, Mutabl } extension HTTPFields: CustomDebugStringConvertible { + @inlinable public var debugDescription: String { self._storage.fields.description } } extension HTTPFields: Codable { + @inlinable public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() try container.encode(contentsOf: self) } + @inlinable public init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() if let count = container.count { @@ -389,6 +428,7 @@ extension HTTPFields: Codable { extension Array { // `removalIndices` must be ordered. + @inlinable mutating func remove(at removalIndices: some Sequence) { var offset = 0 var iterator = removalIndices.makeIterator() diff --git a/Sources/HTTPTypes/HTTPParsedFields.swift b/Sources/HTTPTypes/HTTPParsedFields.swift index 33ff1c0..2ffe77a 100644 --- a/Sources/HTTPTypes/HTTPParsedFields.swift +++ b/Sources/HTTPTypes/HTTPParsedFields.swift @@ -12,15 +12,17 @@ // //===----------------------------------------------------------------------===// +@usableFromInline struct HTTPParsedFields { - private var method: ISOLatin1String? - private var scheme: ISOLatin1String? - private var authority: ISOLatin1String? - private var path: ISOLatin1String? - private var extendedConnectProtocol: ISOLatin1String? - private var status: ISOLatin1String? - private var fields: HTTPFields = .init() + /* private but */ @usableFromInline var method: ISOLatin1String? + /* private but */ @usableFromInline var scheme: ISOLatin1String? + /* private but */ @usableFromInline var authority: ISOLatin1String? + /* private but */ @usableFromInline var path: ISOLatin1String? + /* private but */ @usableFromInline var extendedConnectProtocol: ISOLatin1String? + /* private but */ @usableFromInline var status: ISOLatin1String? + /* private but */ @usableFromInline var fields: HTTPFields = .init() + @usableFromInline enum ParsingError: Error { case invalidName case invalidPseudoName @@ -43,6 +45,26 @@ struct HTTPParsedFields { case multipleLocation } + @inlinable + internal init( + method: ISOLatin1String? = nil, + scheme: ISOLatin1String? = nil, + authority: ISOLatin1String? = nil, + path: ISOLatin1String? = nil, + extendedConnectProtocol: ISOLatin1String? = nil, + status: ISOLatin1String? = nil, + fields: HTTPFields = .init() + ) { + self.method = method + self.scheme = scheme + self.authority = authority + self.path = path + self.extendedConnectProtocol = extendedConnectProtocol + self.status = status + self.fields = fields + } + + @inlinable mutating func add(field: HTTPField) throws { if field.name.isPseudo { if !self.fields.isEmpty { @@ -87,7 +109,7 @@ struct HTTPParsedFields { } } - private func validateFields() throws { + /* private but */ @inlinable func validateFields() throws { guard self.fields[values: .contentLength].allElementsSame else { throw ParsingError.multipleContentLength } @@ -99,6 +121,7 @@ struct HTTPParsedFields { } } + @inlinable var request: HTTPRequest { get throws { guard let method = self.method else { @@ -128,6 +151,7 @@ struct HTTPParsedFields { } } + @inlinable var response: HTTPResponse { get throws { guard let statusString = self.status?._storage else { @@ -146,6 +170,7 @@ struct HTTPParsedFields { } } + @inlinable var trailerFields: HTTPFields { get throws { if self.method != nil || self.scheme != nil || self.authority != nil || self.path != nil @@ -160,7 +185,7 @@ struct HTTPParsedFields { } extension HTTPRequest { - fileprivate init( + /* fileprivate but */ @inlinable init( method: Method, scheme: ISOLatin1String?, authority: ISOLatin1String?, @@ -182,7 +207,7 @@ extension HTTPRequest { } extension Array where Element: Equatable { - fileprivate var allElementsSame: Bool { + /* fileprivate but */ @inlinable var allElementsSame: Bool { guard let first = self.first else { return true } @@ -196,6 +221,7 @@ extension HTTPRequest { /// /// - Parameter fields: The array of parsed `HTTPField` produced by HPACK or QPACK decoders /// used in modern HTTP versions. + @inlinable public init(parsed fields: [HTTPField]) throws { var parsedFields = HTTPParsedFields() for field in fields { @@ -211,6 +237,7 @@ extension HTTPResponse { /// /// - Parameter fields: The array of parsed `HTTPField` produced by HPACK or QPACK decoders /// used in modern HTTP versions. + @inlinable public init(parsed fields: [HTTPField]) throws { var parsedFields = HTTPParsedFields() for field in fields { @@ -226,6 +253,7 @@ extension HTTPFields { /// /// - Parameter fields: The array of parsed `HTTPField` produced by HPACK or QPACK decoders /// used in modern HTTP versions. + @inlinable public init(parsedTrailerFields fields: [HTTPField]) throws { var parsedFields = HTTPParsedFields() for field in fields { diff --git a/Sources/HTTPTypes/HTTPRequest.swift b/Sources/HTTPTypes/HTTPRequest.swift index 5596471..65172ca 100644 --- a/Sources/HTTPTypes/HTTPRequest.swift +++ b/Sources/HTTPTypes/HTTPRequest.swift @@ -32,6 +32,7 @@ public struct HTTPRequest: Sendable, Hashable { /// https://www.rfc-editor.org/rfc/rfc9110.html#name-methods /// /// - Parameter method: The method string. It can be accessed from the `rawValue` property. + @inlinable public init?(_ method: String) { guard HTTPField.isValidToken(method) else { return nil @@ -39,14 +40,16 @@ public struct HTTPRequest: Sendable, Hashable { self.rawValue = method } + @inlinable public init?(rawValue: String) { self.init(rawValue) } - fileprivate init(unchecked: String) { + /* fileprivate but */ @inlinable init(unchecked: String) { self.rawValue = unchecked } + @inlinable public var description: String { self.rawValue } @@ -55,6 +58,7 @@ public struct HTTPRequest: Sendable, Hashable { /// The HTTP request method. /// /// A convenient way to access the value of the ":method" pseudo header field. + @inlinable public var method: Method { get { Method(unchecked: self.pseudoHeaderFields.method.rawValue._storage) @@ -67,6 +71,7 @@ public struct HTTPRequest: Sendable, Hashable { /// A convenient way to access the value of the ":scheme" pseudo header field. /// /// The scheme is ignored in a legacy HTTP/1 context. + @inlinable public var scheme: String? { get { self.pseudoHeaderFields.scheme?.value @@ -88,6 +93,7 @@ public struct HTTPRequest: Sendable, Hashable { /// A convenient way to access the value of the ":authority" pseudo header field. /// /// The authority is translated into the "Host" header in a legacy HTTP/1 context. + @inlinable public var authority: String? { get { self.pseudoHeaderFields.authority?.value @@ -107,6 +113,7 @@ public struct HTTPRequest: Sendable, Hashable { } /// A convenient way to access the value of the ":path" pseudo header field. + @inlinable public var path: String? { get { self.pseudoHeaderFields.path?.value @@ -126,6 +133,7 @@ public struct HTTPRequest: Sendable, Hashable { } /// A convenient way to access the value of the ":protocol" pseudo header field. + @inlinable public var extendedConnectProtocol: String? { get { self.pseudoHeaderFields.extendedConnectProtocol?.value @@ -193,6 +201,21 @@ public struct HTTPRequest: Sendable, Hashable { } } } + + @inlinable + init( + method: HTTPField, + scheme: HTTPField? = nil, + authority: HTTPField? = nil, + path: HTTPField? = nil, + extendedConnectProtocol: HTTPField? = nil + ) { + self.method = method + self.scheme = scheme + self.authority = authority + self.path = path + self.extendedConnectProtocol = extendedConnectProtocol + } } /// The pseudo header fields. @@ -208,6 +231,7 @@ public struct HTTPRequest: Sendable, Hashable { /// - authority: The value of the ":authority" pseudo header field. /// - path: The value of the ":path" pseudo header field. /// - headerFields: The request header fields. + @inlinable public init(method: Method, scheme: String?, authority: String?, path: String?, headerFields: HTTPFields = [:]) { let methodField = HTTPField(name: .method, uncheckedValue: ISOLatin1String(unchecked: method.rawValue)) let schemeField = scheme.map { HTTPField(name: .scheme, value: $0) } @@ -224,12 +248,14 @@ public struct HTTPRequest: Sendable, Hashable { } extension HTTPRequest: CustomDebugStringConvertible { + @inlinable public var debugDescription: String { "(\(self.pseudoHeaderFields.method.rawValue._storage)) \((self.pseudoHeaderFields.scheme?.value).map { "\($0)://" } ?? "")\(self.pseudoHeaderFields.authority?.value ?? "")\(self.pseudoHeaderFields.path?.value ?? "")" } } extension HTTPRequest.PseudoHeaderFields: Codable { + @inlinable public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() try container.encode(self.method) @@ -247,6 +273,7 @@ extension HTTPRequest.PseudoHeaderFields: Codable { } } + @inlinable public init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() var method: HTTPField? @@ -329,17 +356,20 @@ extension HTTPRequest.PseudoHeaderFields: Codable { } extension HTTPRequest: Codable { + @usableFromInline enum CodingKeys: String, CodingKey { case pseudoHeaderFields case headerFields } + @inlinable public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.pseudoHeaderFields, forKey: .pseudoHeaderFields) try container.encode(self.headerFields, forKey: .headerFields) } + @inlinable public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.pseudoHeaderFields = try container.decode(PseudoHeaderFields.self, forKey: .pseudoHeaderFields) @@ -351,47 +381,47 @@ extension HTTPRequest.Method { /// GET /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var get: Self { .init(unchecked: "GET") } + @inlinable public static var get: Self { .init(unchecked: "GET") } /// HEAD /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var head: Self { .init(unchecked: "HEAD") } + @inlinable public static var head: Self { .init(unchecked: "HEAD") } /// POST /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var post: Self { .init(unchecked: "POST") } + @inlinable public static var post: Self { .init(unchecked: "POST") } /// PUT /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var put: Self { .init(unchecked: "PUT") } + @inlinable public static var put: Self { .init(unchecked: "PUT") } /// DELETE /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var delete: Self { .init(unchecked: "DELETE") } + @inlinable public static var delete: Self { .init(unchecked: "DELETE") } /// CONNECT /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var connect: Self { .init(unchecked: "CONNECT") } + @inlinable public static var connect: Self { .init(unchecked: "CONNECT") } /// OPTIONS /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var options: Self { .init(unchecked: "OPTIONS") } + @inlinable public static var options: Self { .init(unchecked: "OPTIONS") } /// TRACE /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var trace: Self { .init(unchecked: "TRACE") } + @inlinable public static var trace: Self { .init(unchecked: "TRACE") } /// PATCH /// /// https://www.rfc-editor.org/rfc/rfc5789.html - public static var patch: Self { .init(unchecked: "PATCH") } + @inlinable public static var patch: Self { .init(unchecked: "PATCH") } /// CONNECT-UDP - static var connectUDP: Self { .init(unchecked: "CONNECT-UDP") } + @inlinable static var connectUDP: Self { .init(unchecked: "CONNECT-UDP") } } diff --git a/Sources/HTTPTypes/HTTPResponse.swift b/Sources/HTTPTypes/HTTPResponse.swift index 6c1dd22..1e6aec4 100644 --- a/Sources/HTTPTypes/HTTPResponse.swift +++ b/Sources/HTTPTypes/HTTPResponse.swift @@ -32,19 +32,21 @@ public struct HTTPResponse: Sendable, Hashable { /// - reasonPhrase: The optional reason phrase. Invalid characters, including any /// characters not representable in ISO Latin 1 encoding, are converted /// into space characters. + @inlinable public init(code: Int, reasonPhrase: String = "") { precondition((0...999).contains(code), "Invalid status code") self.code = code self.reasonPhrase = Self.legalizingReasonPhrase(reasonPhrase) } - fileprivate init(uncheckedCode: Int, reasonPhrase: String) { + /* fileprivate but */ @inlinable init(uncheckedCode: Int, reasonPhrase: String) { self.code = uncheckedCode self.reasonPhrase = reasonPhrase } /// Create a custom status from an integer literal. /// - Parameter value: The status code. + @inlinable public init(integerLiteral value: Int) { precondition((0...999).contains(value), "Invalid status code") self.code = value @@ -68,6 +70,7 @@ public struct HTTPResponse: Sendable, Hashable { } /// The kind of the status code. + @inlinable public var kind: Kind { switch self.code { case 100...199: @@ -85,18 +88,22 @@ public struct HTTPResponse: Sendable, Hashable { } } + @inlinable public func hash(into hasher: inout Hasher) { hasher.combine(self.code) } + @inlinable public static func == (lhs: Status, rhs: Status) -> Bool { lhs.code == rhs.code } + @inlinable public var description: String { "\(self.code) \(self.reasonPhrase)" } + @inlinable var fieldValue: String { if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) { return String(unsafeUninitializedCapacity: 3) { buffer in @@ -114,10 +121,12 @@ public struct HTTPResponse: Sendable, Hashable { } } + @inlinable static func isValidStatus(_ status: String) -> Bool { status.count == 3 && status.utf8.allSatisfy { (0x30...0x39).contains($0) } } + @inlinable static func isValidReasonPhrase(_ reasonPhrase: String) -> Bool { reasonPhrase.utf8.allSatisfy { switch $0 { @@ -131,6 +140,7 @@ public struct HTTPResponse: Sendable, Hashable { } } + @inlinable static func legalizingReasonPhrase(_ reasonPhrase: String) -> String { if self.isValidReasonPhrase(reasonPhrase) { return reasonPhrase @@ -156,6 +166,7 @@ public struct HTTPResponse: Sendable, Hashable { /// /// A convenient way to access the value of the ":status" pseudo header field and the reason /// phrase. + @inlinable public var status: Status { get { var codeIterator = self.pseudoHeaderFields.status.rawValue._storage.utf8.makeIterator() @@ -181,12 +192,17 @@ public struct HTTPResponse: Sendable, Hashable { precondition(Status.isValidStatus(newValue.rawValue._storage), "Invalid status code") } } + + @inlinable + init(status: HTTPField) { + self.status = status + } } /// The pseudo header fields. public var pseudoHeaderFields: PseudoHeaderFields - private var reasonPhrase: String + /* private but */ @usableFromInline var reasonPhrase: String /// The response header fields. public var headerFields: HTTPFields @@ -195,6 +211,7 @@ public struct HTTPResponse: Sendable, Hashable { /// - Parameters: /// - status: The status code and an optional reason phrase. /// - headerFields: The response header fields. + @inlinable public init(status: Status, headerFields: HTTPFields = [:]) { let statusField = HTTPField(name: .status, uncheckedValue: ISOLatin1String(unchecked: status.fieldValue)) self.pseudoHeaderFields = .init(status: statusField) @@ -202,28 +219,33 @@ public struct HTTPResponse: Sendable, Hashable { self.headerFields = headerFields } + @inlinable public func hash(into hasher: inout Hasher) { hasher.combine(self.pseudoHeaderFields) hasher.combine(self.headerFields) } + @inlinable public static func == (lhs: HTTPResponse, rhs: HTTPResponse) -> Bool { lhs.pseudoHeaderFields == rhs.pseudoHeaderFields && lhs.headerFields == rhs.headerFields } } extension HTTPResponse: CustomDebugStringConvertible { + @inlinable public var debugDescription: String { "\(self.status)" } } extension HTTPResponse.PseudoHeaderFields: Codable { + @inlinable public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() try container.encode(self.status) } + @inlinable public init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() var status: HTTPField? @@ -264,12 +286,14 @@ extension HTTPResponse.PseudoHeaderFields: Codable { } extension HTTPResponse: Codable { + @usableFromInline enum CodingKeys: String, CodingKey { case pseudoHeaderFields case headerFields case reasonPhrase } + @inlinable public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.pseudoHeaderFields, forKey: .pseudoHeaderFields) @@ -277,10 +301,11 @@ extension HTTPResponse: Codable { try container.encode(self.headerFields, forKey: .headerFields) } - private enum DecodingError: Error { + /* private but */ @usableFromInline enum DecodingError: Error { case invalidReasonPhrase(String) } + @inlinable public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.pseudoHeaderFields = try container.decode(PseudoHeaderFields.self, forKey: .pseudoHeaderFields) @@ -299,222 +324,222 @@ extension HTTPResponse.Status { /// 100 Continue /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var `continue`: Self { .init(uncheckedCode: 100, reasonPhrase: "Continue") } + @inlinable public static var `continue`: Self { .init(uncheckedCode: 100, reasonPhrase: "Continue") } /// 101 Switching Protocols /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var switchingProtocols: Self { .init(uncheckedCode: 101, reasonPhrase: "Switching Protocols") } + @inlinable public static var switchingProtocols: Self { .init(uncheckedCode: 101, reasonPhrase: "Switching Protocols") } /// 103 Early Hints /// /// https://www.rfc-editor.org/rfc/rfc8297.html - public static var earlyHints: Self { .init(uncheckedCode: 103, reasonPhrase: "Early Hints") } + @inlinable public static var earlyHints: Self { .init(uncheckedCode: 103, reasonPhrase: "Early Hints") } // MARK: 2xx /// 200 OK /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var ok: Self { .init(uncheckedCode: 200, reasonPhrase: "OK") } + @inlinable public static var ok: Self { .init(uncheckedCode: 200, reasonPhrase: "OK") } /// 201 Created /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var created: Self { .init(uncheckedCode: 201, reasonPhrase: "Created") } + @inlinable public static var created: Self { .init(uncheckedCode: 201, reasonPhrase: "Created") } /// 202 Accepted /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var accepted: Self { .init(uncheckedCode: 202, reasonPhrase: "Accepted") } + @inlinable public static var accepted: Self { .init(uncheckedCode: 202, reasonPhrase: "Accepted") } /// 203 Non-Authoritative Information /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var nonAuthoritativeInformation: Self { + @inlinable public static var nonAuthoritativeInformation: Self { .init(uncheckedCode: 203, reasonPhrase: "Non-Authoritative Information") } /// 204 No Content /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var noContent: Self { .init(uncheckedCode: 204, reasonPhrase: "No Content") } + @inlinable public static var noContent: Self { .init(uncheckedCode: 204, reasonPhrase: "No Content") } /// 205 Reset Content /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var resetContent: Self { .init(uncheckedCode: 205, reasonPhrase: "Reset Content") } + @inlinable public static var resetContent: Self { .init(uncheckedCode: 205, reasonPhrase: "Reset Content") } /// 206 Partial Content /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var partialContent: Self { .init(uncheckedCode: 206, reasonPhrase: "Partial Content") } + @inlinable public static var partialContent: Self { .init(uncheckedCode: 206, reasonPhrase: "Partial Content") } // MARK: 3xx /// 300 Multiple Choices /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var multipleChoices: Self { .init(uncheckedCode: 300, reasonPhrase: "Multiple Choices") } + @inlinable public static var multipleChoices: Self { .init(uncheckedCode: 300, reasonPhrase: "Multiple Choices") } /// 301 Moved Permanently /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var movedPermanently: Self { .init(uncheckedCode: 301, reasonPhrase: "Moved Permanently") } + @inlinable public static var movedPermanently: Self { .init(uncheckedCode: 301, reasonPhrase: "Moved Permanently") } /// 302 Found /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var found: Self { .init(uncheckedCode: 302, reasonPhrase: "Found") } + @inlinable public static var found: Self { .init(uncheckedCode: 302, reasonPhrase: "Found") } /// 303 See Other /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var seeOther: Self { .init(uncheckedCode: 303, reasonPhrase: "See Other") } + @inlinable public static var seeOther: Self { .init(uncheckedCode: 303, reasonPhrase: "See Other") } /// 304 Not Modified /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var notModified: Self { .init(uncheckedCode: 304, reasonPhrase: "Not Modified") } + @inlinable public static var notModified: Self { .init(uncheckedCode: 304, reasonPhrase: "Not Modified") } /// 307 Temporary Redirect /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var temporaryRedirect: Self { .init(uncheckedCode: 307, reasonPhrase: "Temporary Redirect") } + @inlinable public static var temporaryRedirect: Self { .init(uncheckedCode: 307, reasonPhrase: "Temporary Redirect") } /// 308 Permanent Redirect /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var permanentRedirect: Self { .init(uncheckedCode: 308, reasonPhrase: "Permanent Redirect") } + @inlinable public static var permanentRedirect: Self { .init(uncheckedCode: 308, reasonPhrase: "Permanent Redirect") } // MARK: 4xx /// 400 Bad Request /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var badRequest: Self { .init(uncheckedCode: 400, reasonPhrase: "Bad Request") } + @inlinable public static var badRequest: Self { .init(uncheckedCode: 400, reasonPhrase: "Bad Request") } /// 401 Unauthorized /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var unauthorized: Self { .init(uncheckedCode: 401, reasonPhrase: "Unauthorized") } + @inlinable public static var unauthorized: Self { .init(uncheckedCode: 401, reasonPhrase: "Unauthorized") } /// 403 Forbidden /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var forbidden: Self { .init(uncheckedCode: 403, reasonPhrase: "Forbidden") } + @inlinable public static var forbidden: Self { .init(uncheckedCode: 403, reasonPhrase: "Forbidden") } /// 404 Not Found /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var notFound: Self { .init(uncheckedCode: 404, reasonPhrase: "Not Found") } + @inlinable public static var notFound: Self { .init(uncheckedCode: 404, reasonPhrase: "Not Found") } /// 405 Method Not Allowed /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var methodNotAllowed: Self { .init(uncheckedCode: 405, reasonPhrase: "Method Not Allowed") } + @inlinable public static var methodNotAllowed: Self { .init(uncheckedCode: 405, reasonPhrase: "Method Not Allowed") } /// 406 Not Acceptable /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var notAcceptable: Self { .init(uncheckedCode: 406, reasonPhrase: "Not Acceptable") } + @inlinable public static var notAcceptable: Self { .init(uncheckedCode: 406, reasonPhrase: "Not Acceptable") } /// 407 Proxy Authentication Required /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var proxyAuthenticationRequired: Self { + @inlinable public static var proxyAuthenticationRequired: Self { .init(uncheckedCode: 407, reasonPhrase: "Proxy Authentication Required") } /// 408 Request Timeout /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var requestTimeout: Self { .init(uncheckedCode: 408, reasonPhrase: "Request Timeout") } + @inlinable public static var requestTimeout: Self { .init(uncheckedCode: 408, reasonPhrase: "Request Timeout") } /// 409 Conflict /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var conflict: Self { .init(uncheckedCode: 409, reasonPhrase: "Conflict") } + @inlinable public static var conflict: Self { .init(uncheckedCode: 409, reasonPhrase: "Conflict") } /// 410 Gone /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var gone: Self { .init(uncheckedCode: 410, reasonPhrase: "Gone") } + @inlinable public static var gone: Self { .init(uncheckedCode: 410, reasonPhrase: "Gone") } /// 411 Length Required /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var lengthRequired: Self { .init(uncheckedCode: 411, reasonPhrase: "Length Required") } + @inlinable public static var lengthRequired: Self { .init(uncheckedCode: 411, reasonPhrase: "Length Required") } /// 412 Precondition Failed /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var preconditionFailed: Self { .init(uncheckedCode: 412, reasonPhrase: "Precondition Failed") } + @inlinable public static var preconditionFailed: Self { .init(uncheckedCode: 412, reasonPhrase: "Precondition Failed") } /// 413 Content Too Large /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var contentTooLarge: Self { .init(uncheckedCode: 413, reasonPhrase: "Content Too Large") } + @inlinable public static var contentTooLarge: Self { .init(uncheckedCode: 413, reasonPhrase: "Content Too Large") } /// 414 URI Too Long /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var uriTooLong: Self { .init(uncheckedCode: 414, reasonPhrase: "URI Too Long") } + @inlinable public static var uriTooLong: Self { .init(uncheckedCode: 414, reasonPhrase: "URI Too Long") } /// 415 Unsupported Media Type /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var unsupportedMediaType: Self { .init(uncheckedCode: 415, reasonPhrase: "Unsupported Media Type") } + @inlinable public static var unsupportedMediaType: Self { .init(uncheckedCode: 415, reasonPhrase: "Unsupported Media Type") } /// 416 Range Not Satisfiable /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var rangeNotSatisfiable: Self { .init(uncheckedCode: 416, reasonPhrase: "Range Not Satisfiable") } + @inlinable public static var rangeNotSatisfiable: Self { .init(uncheckedCode: 416, reasonPhrase: "Range Not Satisfiable") } /// 417 Expectation Failed /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var expectationFailed: Self { .init(uncheckedCode: 417, reasonPhrase: "Expectation Failed") } + @inlinable public static var expectationFailed: Self { .init(uncheckedCode: 417, reasonPhrase: "Expectation Failed") } /// 421 Misdirected Request /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var misdirectedRequest: Self { .init(uncheckedCode: 421, reasonPhrase: "Misdirected Request") } + @inlinable public static var misdirectedRequest: Self { .init(uncheckedCode: 421, reasonPhrase: "Misdirected Request") } /// 422 Unprocessable Content /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var unprocessableContent: Self { .init(uncheckedCode: 422, reasonPhrase: "Unprocessable Content") } + @inlinable public static var unprocessableContent: Self { .init(uncheckedCode: 422, reasonPhrase: "Unprocessable Content") } /// 425 Too Early - public static var tooEarly: Self { .init(uncheckedCode: 425, reasonPhrase: "Too Early") } + @inlinable public static var tooEarly: Self { .init(uncheckedCode: 425, reasonPhrase: "Too Early") } /// 426 Upgrade Required /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var upgradeRequired: Self { .init(uncheckedCode: 426, reasonPhrase: "Upgrade Required") } + @inlinable public static var upgradeRequired: Self { .init(uncheckedCode: 426, reasonPhrase: "Upgrade Required") } /// 428 Precondition Required /// /// https://www.rfc-editor.org/rfc/rfc6585.html - public static var preconditionRequired: Self { .init(uncheckedCode: 428, reasonPhrase: "Precondition Required") } + @inlinable public static var preconditionRequired: Self { .init(uncheckedCode: 428, reasonPhrase: "Precondition Required") } /// 429 Too Many Requests /// /// https://www.rfc-editor.org/rfc/rfc6585.html - public static var tooManyRequests: Self { .init(uncheckedCode: 429, reasonPhrase: "Too Many Requests") } + @inlinable public static var tooManyRequests: Self { .init(uncheckedCode: 429, reasonPhrase: "Too Many Requests") } /// 431 Request Header Fields Too Large /// /// https://www.rfc-editor.org/rfc/rfc6585.html - public static var requestHeaderFieldsTooLarge: Self { + @inlinable public static var requestHeaderFieldsTooLarge: Self { .init(uncheckedCode: 431, reasonPhrase: "Request Header Fields Too Large") } /// 451 Unavailable For Legal Reasons /// /// https://www.rfc-editor.org/rfc/rfc7725.html - public static var unavailableForLegalReasons: Self { + @inlinable public static var unavailableForLegalReasons: Self { .init(uncheckedCode: 451, reasonPhrase: "Unavailable For Legal Reasons") } @@ -523,39 +548,39 @@ extension HTTPResponse.Status { /// 500 Internal Server Error /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var internalServerError: Self { .init(uncheckedCode: 500, reasonPhrase: "Internal Server Error") } + @inlinable public static var internalServerError: Self { .init(uncheckedCode: 500, reasonPhrase: "Internal Server Error") } /// 501 Not Implemented /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var notImplemented: Self { .init(uncheckedCode: 501, reasonPhrase: "Not Implemented") } + @inlinable public static var notImplemented: Self { .init(uncheckedCode: 501, reasonPhrase: "Not Implemented") } /// 502 Bad Gateway /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var badGateway: Self { .init(uncheckedCode: 502, reasonPhrase: "Bad Gateway") } + @inlinable public static var badGateway: Self { .init(uncheckedCode: 502, reasonPhrase: "Bad Gateway") } /// 503 Service Unavailable /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var serviceUnavailable: Self { .init(uncheckedCode: 503, reasonPhrase: "Service Unavailable") } + @inlinable public static var serviceUnavailable: Self { .init(uncheckedCode: 503, reasonPhrase: "Service Unavailable") } /// 504 Gateway Timeout /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var gatewayTimeout: Self { .init(uncheckedCode: 504, reasonPhrase: "Gateway Timeout") } + @inlinable public static var gatewayTimeout: Self { .init(uncheckedCode: 504, reasonPhrase: "Gateway Timeout") } /// 505 HTTP Version Not Supported /// /// https://www.rfc-editor.org/rfc/rfc9110.html - public static var httpVersionNotSupported: Self { + @inlinable public static var httpVersionNotSupported: Self { .init(uncheckedCode: 505, reasonPhrase: "HTTP Version Not Supported") } /// 511 Network Authentication Required /// /// https://www.rfc-editor.org/rfc/rfc6585.html - public static var networkAuthenticationRequired: Self { + @inlinable public static var networkAuthenticationRequired: Self { .init(uncheckedCode: 511, reasonPhrase: "Network Authentication Required") } } diff --git a/Sources/HTTPTypes/ISOLatin1String.swift b/Sources/HTTPTypes/ISOLatin1String.swift index a5c08f7..7a3a921 100644 --- a/Sources/HTTPTypes/ISOLatin1String.swift +++ b/Sources/HTTPTypes/ISOLatin1String.swift @@ -13,22 +13,25 @@ //===----------------------------------------------------------------------===// extension String { + @inlinable var isASCII: Bool { self.utf8.allSatisfy { $0 & 0x80 == 0 } } } +@usableFromInline struct ISOLatin1String: Sendable, Hashable { + @usableFromInline let _storage: String - private static func transcodeSlowPath(from bytes: some Collection) -> String { + /* private but */ @inlinable static func transcodeSlowPath(from bytes: some Collection) -> String { let scalars = bytes.lazy.map { UnicodeScalar(UInt32($0))! } var string = "" string.unicodeScalars.append(contentsOf: scalars) return string } - private func withISOLatin1BytesSlowPath( + /* private but */ @inlinable func withISOLatin1BytesSlowPath( _ body: (UnsafeBufferPointer) throws -> Result ) rethrows -> Result { try withUnsafeTemporaryAllocation(of: UInt8.self, capacity: self._storage.unicodeScalars.count) { buffer in @@ -40,6 +43,7 @@ struct ISOLatin1String: Sendable, Hashable { } } + @inlinable init(_ string: String) { if string.isASCII { self._storage = string @@ -48,6 +52,7 @@ struct ISOLatin1String: Sendable, Hashable { } } + @inlinable init(_ bytes: some Collection) { let ascii = bytes.allSatisfy { $0 & 0x80 == 0 } if ascii { @@ -57,10 +62,12 @@ struct ISOLatin1String: Sendable, Hashable { } } + @inlinable init(unchecked: String) { self._storage = unchecked } + @inlinable var string: String { if self._storage.isASCII { return self._storage @@ -71,6 +78,7 @@ struct ISOLatin1String: Sendable, Hashable { } } + @inlinable func withUnsafeBytes(_ body: (UnsafeBufferPointer) throws -> Result) rethrows -> Result { if self._storage.isASCII { var string = self._storage diff --git a/Sources/HTTPTypes/NIOLock.swift b/Sources/HTTPTypes/NIOLock.swift index b06d6ad..d88024f 100644 --- a/Sources/HTTPTypes/NIOLock.swift +++ b/Sources/HTTPTypes/NIOLock.swift @@ -46,16 +46,18 @@ import wasi_pthread #endif #if os(Windows) -typealias LockPrimitive = SRWLOCK +@usableFromInline typealias LockPrimitive = SRWLOCK #elseif canImport(Darwin) -typealias LockPrimitive = os_unfair_lock +@usableFromInline typealias LockPrimitive = os_unfair_lock #else -typealias LockPrimitive = pthread_mutex_t +@usableFromInline typealias LockPrimitive = pthread_mutex_t #endif +@usableFromInline enum LockOperations {} extension LockOperations { + @inlinable static func create(_ mutex: UnsafeMutablePointer) { mutex.assertValidAlignment() @@ -72,6 +74,7 @@ extension LockOperations { #endif } + @inlinable static func destroy(_ mutex: UnsafeMutablePointer) { mutex.assertValidAlignment() @@ -85,6 +88,7 @@ extension LockOperations { #endif } + @inlinable static func lock(_ mutex: UnsafeMutablePointer) { mutex.assertValidAlignment() @@ -98,6 +102,7 @@ extension LockOperations { #endif } + @inlinable static func unlock(_ mutex: UnsafeMutablePointer) { mutex.assertValidAlignment() @@ -140,8 +145,9 @@ extension LockOperations { // and future maintainers will be happier that we were cautious. // // See also: https://github.com/apple/swift/pull/40000 +@usableFromInline final class LockStorage: ManagedBuffer { - + @inlinable static func create(value: Value) -> Self { let buffer = Self.create(minimumCapacity: 1) { _ in value @@ -158,30 +164,35 @@ final class LockStorage: ManagedBuffer { return storage } + @inlinable func lock() { self.withUnsafeMutablePointerToElements { lockPtr in LockOperations.lock(lockPtr) } } + @inlinable func unlock() { self.withUnsafeMutablePointerToElements { lockPtr in LockOperations.unlock(lockPtr) } } + @inlinable deinit { self.withUnsafeMutablePointerToElements { lockPtr in LockOperations.destroy(lockPtr) } } + @inlinable func withLockPrimitive(_ body: (UnsafeMutablePointer) throws -> T) rethrows -> T { try self.withUnsafeMutablePointerToElements { lockPtr in try body(lockPtr) } } + @inlinable func withLockedValue(_ mutate: (inout Value) throws -> T) rethrows -> T { try self.withUnsafeMutablePointers { valuePtr, lockPtr in LockOperations.lock(lockPtr) @@ -191,9 +202,8 @@ final class LockStorage: ManagedBuffer { } } -extension LockStorage: @unchecked Sendable {} - extension UnsafeMutablePointer { + @inlinable func assertValidAlignment() { assert(UInt(bitPattern: self) % UInt(MemoryLayout.alignment) == 0) } diff --git a/Sources/HTTPTypesFoundation/HTTPRequest+URL.swift b/Sources/HTTPTypesFoundation/HTTPRequest+URL.swift index 288299c..1ea367e 100644 --- a/Sources/HTTPTypesFoundation/HTTPRequest+URL.swift +++ b/Sources/HTTPTypesFoundation/HTTPRequest+URL.swift @@ -22,6 +22,7 @@ import CoreFoundation extension HTTPRequest { /// The URL of the request synthesized from the scheme, authority, and path pseudo header /// fields. + @inlinable public var url: URL? { get { if (self.method == .connect && self.extendedConnectProtocol == nil) @@ -63,6 +64,7 @@ extension HTTPRequest { /// - method: The request method, defaults to GET. /// - url: The URL to populate the scheme, authority, and path pseudo header fields. /// - headerFields: The request header fields. + @inlinable public init(method: Method = .get, url: URL, headerFields: HTTPFields = [:]) { let (scheme, authority, path) = url.httpRequestComponents let schemeString = String(decoding: scheme, as: UTF8.self) @@ -80,7 +82,7 @@ extension HTTPRequest { } extension URL { - fileprivate init?(scheme: some Collection, authority: some Collection, path: some Collection) { + /* fileprivate but */ @inlinable init?(scheme: some Collection, authority: some Collection, path: some Collection) { var buffer = [UInt8]() buffer.reserveCapacity(scheme.count + 3 + authority.count + path.count) buffer.append(contentsOf: scheme) @@ -109,7 +111,7 @@ extension URL { #endif // canImport(CoreFoundation) } - fileprivate var httpRequestComponents: (scheme: [UInt8], authority: [UInt8]?, path: [UInt8]) { + /* fileprivate but */ @inlinable var httpRequestComponents: (scheme: [UInt8], authority: [UInt8]?, path: [UInt8]) { #if canImport(CoreFoundation) // CFURL parser based on byte ranges does not unnecessarily percent-encode WHATWG URL let url = unsafeBitCast(self.absoluteURL as NSURL, to: CFURL.self) diff --git a/Sources/HTTPTypesFoundation/HTTPTypes+ISOLatin1.swift b/Sources/HTTPTypesFoundation/HTTPTypes+ISOLatin1.swift index 15ba245..31b5244 100644 --- a/Sources/HTTPTypesFoundation/HTTPTypes+ISOLatin1.swift +++ b/Sources/HTTPTypesFoundation/HTTPTypes+ISOLatin1.swift @@ -15,12 +15,14 @@ import HTTPTypes extension String { + @inlinable var isASCII: Bool { self.utf8.allSatisfy { $0 & 0x80 == 0 } } } extension HTTPField { + @inlinable init(name: Name, isoLatin1Value: String) { if isoLatin1Value.isASCII { self.init(name: name, value: isoLatin1Value) @@ -39,6 +41,7 @@ extension HTTPField { } } + @inlinable var isoLatin1Value: String { if self.value.isASCII { return self.value diff --git a/Sources/HTTPTypesFoundation/URLRequest+HTTPTypes.swift b/Sources/HTTPTypesFoundation/URLRequest+HTTPTypes.swift index 624f8a3..3771ddc 100644 --- a/Sources/HTTPTypesFoundation/URLRequest+HTTPTypes.swift +++ b/Sources/HTTPTypesFoundation/URLRequest+HTTPTypes.swift @@ -24,6 +24,7 @@ import FoundationNetworking extension URLRequest { /// Create a `URLRequest` from an `HTTPRequest`. /// - Parameter httpRequest: The HTTP request to convert from. + @inlinable public init?(httpRequest: HTTPRequest) { guard let url = httpRequest.url else { return nil @@ -48,6 +49,7 @@ extension URLRequest { } /// Convert the `URLRequest` into an `HTTPRequest`. + @inlinable public var httpRequest: HTTPRequest? { guard let method = HTTPRequest.Method(self.httpMethod ?? "GET"), let url diff --git a/Sources/HTTPTypesFoundation/URLResponse+HTTPTypes.swift b/Sources/HTTPTypesFoundation/URLResponse+HTTPTypes.swift index 7a3e371..ed37046 100644 --- a/Sources/HTTPTypesFoundation/URLResponse+HTTPTypes.swift +++ b/Sources/HTTPTypesFoundation/URLResponse+HTTPTypes.swift @@ -25,6 +25,7 @@ extension HTTPURLResponse { /// Create an `HTTPURLResponse` from an `HTTPResponse`. /// - Parameter httpResponse: The HTTP response to convert from. /// - Parameter url: The URL of the response. + @inlinable public convenience init?(httpResponse: HTTPResponse, url: URL) { var combinedFields = [HTTPField.Name: String](minimumCapacity: httpResponse.headerFields.count) for field in httpResponse.headerFields { @@ -42,6 +43,7 @@ extension HTTPURLResponse { } /// Convert the `HTTPURLResponse` into an `HTTPResponse`. + @inlinable public var httpResponse: HTTPResponse? { guard (0...999).contains(self.statusCode) else { return nil diff --git a/Sources/HTTPTypesFoundation/URLSession+HTTPTypes.swift b/Sources/HTTPTypesFoundation/URLSession+HTTPTypes.swift index cdf2de6..3aa6ac5 100644 --- a/Sources/HTTPTypesFoundation/URLSession+HTTPTypes.swift +++ b/Sources/HTTPTypesFoundation/URLSession+HTTPTypes.swift @@ -23,22 +23,25 @@ import FoundationNetworking extension URLSessionTask { /// The original HTTP request this task was created with. + @inlinable public var originalHTTPRequest: HTTPRequest? { self.originalRequest?.httpRequest } /// The current HTTP request -- may differ from the `originalHTTPRequest` due to HTTP redirection. + @inlinable public var currentHTTPRequest: HTTPRequest? { self.currentRequest?.httpRequest } /// The HTTP response received from the server. + @inlinable public var httpResponse: HTTPResponse? { (self.response as? HTTPURLResponse)?.httpResponse } } -private enum HTTPTypeConversionError: Error { +/* private but */ @usableFromInline enum HTTPTypeConversionError: Error { case failedToConvertHTTPRequestToURLRequest case failedToConvertURLResponseToHTTPResponse } @@ -54,6 +57,7 @@ extension URLSession { /// - Parameter request: The `HTTPRequest` for which to load data. /// - Parameter delegate: Task-specific delegate. /// - Returns: Data and response. + @inlinable public func data( for request: HTTPRequest, delegate: URLSessionTaskDelegate? = nil @@ -74,6 +78,7 @@ extension URLSession { /// - Parameter fileURL: File to upload. /// - Parameter delegate: Task-specific delegate. /// - Returns: Data and response. + @inlinable public func upload( for request: HTTPRequest, fromFile fileURL: URL, @@ -95,6 +100,7 @@ extension URLSession { /// - Parameter bodyData: Data to upload. /// - Parameter delegate: Task-specific delegate. /// - Returns: Data and response. + @inlinable public func upload( for request: HTTPRequest, from bodyData: Data, @@ -115,6 +121,7 @@ extension URLSession { /// - Parameter request: The `HTTPRequest` for which to download. /// - Parameter delegate: Task-specific delegate. /// - Returns: Downloaded file URL and response. The file will not be removed automatically. + @inlinable public func download( for request: HTTPRequest, delegate: URLSessionTaskDelegate? = nil @@ -135,6 +142,7 @@ extension URLSession { /// - Parameter request: The `HTTPRequest` for which to load data. /// - Parameter delegate: Task-specific delegate. /// - Returns: Data stream and response. + @inlinable public func bytes( for request: HTTPRequest, delegate: URLSessionTaskDelegate? = nil @@ -157,6 +165,7 @@ extension URLSession { /// /// - Parameter request: The `HTTPRequest` for which to load data. /// - Returns: Data and response. + @inlinable public func data(for request: HTTPRequest) async throws -> (Data, HTTPResponse) { guard let urlRequest = URLRequest(httpRequest: request) else { throw HTTPTypeConversionError.failedToConvertHTTPRequestToURLRequest @@ -173,6 +182,7 @@ extension URLSession { /// - Parameter request: The `HTTPRequest` for which to upload data. /// - Parameter fileURL: File to upload. /// - Returns: Data and response. + @inlinable public func upload(for request: HTTPRequest, fromFile fileURL: URL) async throws -> (Data, HTTPResponse) { guard let urlRequest = URLRequest(httpRequest: request) else { throw HTTPTypeConversionError.failedToConvertHTTPRequestToURLRequest @@ -189,6 +199,7 @@ extension URLSession { /// - Parameter request: The `HTTPRequest` for which to upload data. /// - Parameter bodyData: Data to upload. /// - Returns: Data and response. + @inlinable public func upload(for request: HTTPRequest, from bodyData: Data) async throws -> (Data, HTTPResponse) { guard let urlRequest = URLRequest(httpRequest: request) else { throw HTTPTypeConversionError.failedToConvertHTTPRequestToURLRequest