Skip to content

Commit 8345f7b

Browse files
committed
Add HTTPTypes extensions
1 parent baa923a commit 8345f7b

File tree

1 file changed

+98
-0
lines changed

1 file changed

+98
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//
2+
// HTTPTypes.swift
3+
// HTTP
4+
//
5+
// Created by Alsey Coleman Miller on 10/1/25.
6+
//
7+
8+
#if canImport(HTTPTypesFoundation)
9+
import Foundation
10+
#if canImport(FoundationNetworking)
11+
import FoundationNetworking
12+
#endif
13+
import HTTPTypes
14+
import HTTPTypesFoundation
15+
16+
internal extension URLRequest {
17+
18+
init?(_ request: HTTPRequest, baseURL: URL) {
19+
guard var baseUrlComponents = URLComponents(string: baseURL.absoluteString),
20+
let requestUrlComponents = URLComponents(string: request.path ?? "")
21+
else {
22+
return nil
23+
}
24+
let path = requestUrlComponents.percentEncodedPath
25+
baseUrlComponents.percentEncodedPath += path
26+
baseUrlComponents.percentEncodedQuery = requestUrlComponents.percentEncodedQuery
27+
guard let url = baseUrlComponents.url else {
28+
return nil
29+
}
30+
self.init(url: url)
31+
self.httpMethod = request.method.rawValue
32+
var combinedFields = [HTTPField.Name: String](minimumCapacity: request.headerFields.count)
33+
for field in request.headerFields {
34+
if let existingValue = combinedFields[field.name] {
35+
let separator = field.name == .cookie ? "; " : ", "
36+
combinedFields[field.name] = "\(existingValue)\(separator)\(field.isoLatin1Value)"
37+
} else {
38+
combinedFields[field.name] = field.isoLatin1Value
39+
}
40+
}
41+
var headerFields = [String: String](minimumCapacity: combinedFields.count)
42+
for (name, value) in combinedFields { headerFields[name.rawName] = value }
43+
self.allHTTPHeaderFields = headerFields
44+
}
45+
}
46+
47+
internal extension HTTPResponse {
48+
49+
init?(_ urlResponse: URLResponse) {
50+
guard let httpResponse = urlResponse as? HTTPURLResponse else {
51+
return nil
52+
}
53+
guard (0...999).contains(httpResponse.statusCode) else {
54+
return nil
55+
}
56+
self.init(status: .init(code: httpResponse.statusCode))
57+
if let fields = httpResponse.allHeaderFields as? [String: String] {
58+
self.headerFields.reserveCapacity(fields.count)
59+
for (name, value) in fields {
60+
if let name = HTTPField.Name(name) {
61+
self.headerFields.append(HTTPField(name: name, isoLatin1Value: value))
62+
}
63+
}
64+
}
65+
}
66+
}
67+
#endif
68+
69+
internal extension HTTPField {
70+
71+
init(name: Name, isoLatin1Value: String) {
72+
if isoLatin1Value.isASCII {
73+
self.init(name: name, value: isoLatin1Value)
74+
} else {
75+
self = withUnsafeTemporaryAllocation(of: UInt8.self, capacity: isoLatin1Value.unicodeScalars.count) {
76+
buffer in
77+
for (index, scalar) in isoLatin1Value.unicodeScalars.enumerated() {
78+
if scalar.value > UInt8.max {
79+
buffer[index] = 0x20
80+
} else {
81+
buffer[index] = UInt8(truncatingIfNeeded: scalar.value)
82+
}
83+
}
84+
return HTTPField(name: name, value: buffer)
85+
}
86+
}
87+
}
88+
89+
var isoLatin1Value: String {
90+
if self.value.isASCII { return self.value }
91+
return self.withUnsafeBytesOfValue { buffer in
92+
let scalars = buffer.lazy.map { UnicodeScalar(UInt32($0))! }
93+
var string = ""
94+
string.unicodeScalars.append(contentsOf: scalars)
95+
return string
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)