Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix broken URI behaviors #3140

Merged
merged 10 commits into from
Jan 22, 2024
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,26 @@
</a>

<p align="center">
<a href="https://docs.vapor.codes/4.0/">
<img src="http://img.shields.io/badge/read_the-docs-2196f3.svg" alt="Documentation">
<a href="https://docs.vapor.codes/4.0/">
<img src="https://design.vapor.codes/images/readthedocs.svg" alt="Documentation">
</a>
<a href="https://discord.gg/vapor">
<img src="https://img.shields.io/discord/431917998102675485.svg" alt="Team Chat">
<img src="https://design.vapor.codes/images/discordchat.svg" alt="Team Chat">
MahdiBM marked this conversation as resolved.
Show resolved Hide resolved
</a>
<a href="LICENSE">
<img src="https://img.shields.io/badge/license-MIT-brightgreen.svg" alt="MIT License">
<img src="https://design.vapor.codes/images/mitlicense.svg" alt="MIT License">
</a>
<a href="https://github.com/vapor/vapor/actions">
<img src="https://github.com/vapor/vapor/actions/workflows/test.yml/badge.svg?branch=main" alt="Continuous Integration">
<a href="https://github.com/vapor/vapor/actions/workflows/test.yml">
<img src="https://img.shields.io/github/actions/workflow/status/vapor/vapor/test.yml?event=push&style=plastic&logo=github&label=tests&logoColor=%23ccc" alt="Continuous Integration">
</a>
<a href="https://codecov.io/gh/vapor/vapor">
<img src="https://img.shields.io/codecov/c/github/vapor/vapor?style=plastic&logo=codecov&label=codecov" alt="Code Coverage">
</a>
<a href="https://swift.org">
<img src="https://img.shields.io/badge/swift-5.7-brightgreen.svg" alt="Swift 5.7">
<img src="https://design.vapor.codes/images/swift57up.svg" alt="Swift 5.7+">
</a>
<a href="https://twitter.com/codevapor">
<img src="https://img.shields.io/badge/twitter-codevapor-5AA9E7.svg" alt="Twitter">
<a href="https://hachyderm.io/@codevapor">
<img src="https://img.shields.io/badge/%20-@codevapor-6364f6.svg?style=plastic&logo=mastodon&labelColor=gray&logoColor=%239394ff" alt="Mastodon">
</a>
</p>

Expand All @@ -33,11 +36,11 @@ Take a look at some of the [awesome stuff](https://github.com/Cellane/awesome-va

### 💧 Community

Join the welcoming community of fellow Vapor developers on [Discord](http://vapor.team).
Join the welcoming community of fellow Vapor developers on [Discord](https://vapor.team).

### 🚀 Contributing

To contribute a **feature or idea** to Vapor, [create an issue](https://github.com/vapor/vapor/issues/new) explaining your idea or bring it up on [Discord](http://vapor.team).
To contribute a **feature or idea** to Vapor, [create an issue](https://github.com/vapor/vapor/issues/new) explaining your idea or bring it up on [Discord](https://vapor.team).

If you find a **bug**, please [create an issue](https://github.com/vapor/vapor/issues/new).

Expand Down
2 changes: 1 addition & 1 deletion Sources/Vapor/Content/ContainerGetPathExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
internal struct ContainerGetPathExecutor<D: Decodable>: Decodable {
let result: D

static func userInfo(for keyPath: [CodingKey]) -> [CodingUserInfoKey: Any] {
static func userInfo(for keyPath: [CodingKey]) -> [CodingUserInfoKey: Sendable] {
[.containerGetKeypath: keyPath]
}

Expand Down
8 changes: 4 additions & 4 deletions Sources/Vapor/Content/ContentCoders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public protocol ContentEncoder {
///
/// For legacy API compatibility reasons, the default protocol conformance for this method forwards it to the legacy
/// encode method.
func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders, userInfo: [CodingUserInfoKey: Any]) throws
func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders, userInfo: [CodingUserInfoKey: Sendable]) throws
where E: Encodable
}

Expand All @@ -42,20 +42,20 @@ public protocol ContentDecoder {
///
/// For legacy API compatibility reasons, the default protocol conformance for this method forwards it to the legacy
/// decode method.
func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders, userInfo: [CodingUserInfoKey: Any]) throws -> D
func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders, userInfo: [CodingUserInfoKey: Sendable]) throws -> D
where D: Decodable
}

extension ContentEncoder {
public func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders, userInfo: [CodingUserInfoKey: Any]) throws
public func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders, userInfo: [CodingUserInfoKey: Sendable]) throws
where E: Encodable
{
try self.encode(encodable, to: &body, headers: &headers)
}
}

extension ContentDecoder {
public func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders, userInfo: [CodingUserInfoKey: Any]) throws -> D
public func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders, userInfo: [CodingUserInfoKey: Sendable]) throws -> D
where D: Decodable
{
try self.decode(decodable, from: body, headers: headers)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Vapor/Content/ContentContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,12 @@ extension ContentContainer {

/// Injects coder userInfo into a ``ContentDecoder`` so we don't have to add passthroughs to ``ContentContainer``.
fileprivate struct ForwardingContentDecoder: ContentDecoder {
let base: ContentDecoder, info: [CodingUserInfoKey: Any]
let base: ContentDecoder, info: [CodingUserInfoKey: Sendable]

func decode<D: Decodable>(_: D.Type, from body: ByteBuffer, headers: HTTPHeaders) throws -> D {
try self.base.decode(D.self, from: body, headers: headers, userInfo: self.info)
}
func decode<D: Decodable>(_: D.Type, from body: ByteBuffer, headers: HTTPHeaders, userInfo: [CodingUserInfoKey: Any]) throws -> D {
func decode<D: Decodable>(_: D.Type, from body: ByteBuffer, headers: HTTPHeaders, userInfo: [CodingUserInfoKey: Sendable]) throws -> D {
try self.base.decode(D.self, from: body, headers: headers, userInfo: userInfo.merging(self.info) { $1 })
}
}
4 changes: 2 additions & 2 deletions Sources/Vapor/Content/JSONCoders+Content.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extension JSONEncoder: ContentEncoder {
try self.encode(encodable, to: &body, headers: &headers, userInfo: [:])
}

public func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders, userInfo: [CodingUserInfoKey: Any]) throws
public func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders, userInfo: [CodingUserInfoKey: Sendable]) throws
where E: Encodable
{
headers.contentType = .json
Expand All @@ -36,7 +36,7 @@ extension JSONDecoder: ContentDecoder {
try self.decode(D.self, from: body, headers: headers, userInfo: [:])
}

public func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders, userInfo: [CodingUserInfoKey: Any]) throws -> D
public func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders, userInfo: [CodingUserInfoKey: Sendable]) throws -> D
where D: Decodable
{
let data = body.getData(at: body.readerIndex, length: body.readableBytes) ?? Data()
Expand Down
4 changes: 2 additions & 2 deletions Sources/Vapor/Content/PlaintextDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public struct PlaintextDecoder: ContentDecoder {
}

/// `ContentDecoder` conformance.
public func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders, userInfo: [CodingUserInfoKey: Any]) throws -> D
public func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders, userInfo: [CodingUserInfoKey: Sendable]) throws -> D
where D : Decodable
{
let string = body.getString(at: body.readerIndex, length: body.readableBytes)
Expand All @@ -29,7 +29,7 @@ private final class _PlaintextDecoder: Decoder, SingleValueDecodingContainer {
let userInfo: [CodingUserInfoKey: Any]
let plaintext: String?

init(plaintext: String?, userInfo: [CodingUserInfoKey: Any] = [:]) {
init(plaintext: String?, userInfo: [CodingUserInfoKey: Sendable] = [:]) {
self.plaintext = plaintext
self.userInfo = userInfo
}
Expand Down
9 changes: 5 additions & 4 deletions Sources/Vapor/Content/PlaintextEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ public struct PlaintextEncoder: ContentEncoder {
try self.encode(encodable, to: &body, headers: &headers, userInfo: [:])
}

public func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders, userInfo: [CodingUserInfoKey: Any]) throws
public func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders, userInfo: [CodingUserInfoKey: Sendable]) throws
where E: Encodable
{
let actualEncoder: _PlaintextEncoder
if !userInfo.isEmpty { // Changing a coder's userInfo is a thread-unsafe mutation, operate on a copy
actualEncoder = _PlaintextEncoder(userInfo: self.encoder.userInfo.merging(userInfo) { $1 })
actualEncoder = _PlaintextEncoder(userInfo: self.encoder.userInfoSendable.merging(userInfo) { $1 })
} else {
actualEncoder = self.encoder
}
Expand All @@ -51,10 +51,11 @@ public struct PlaintextEncoder: ContentEncoder {

private final class _PlaintextEncoder: Encoder, SingleValueEncodingContainer {
public var codingPath: [CodingKey] = []
public var userInfo: [CodingUserInfoKey: Any]
gwynne marked this conversation as resolved.
Show resolved Hide resolved
fileprivate var userInfoSendable: [CodingUserInfoKey: Sendable]
public var userInfo: [CodingUserInfoKey: Any] { self.userInfoSendable }
public var plaintext: String?

public init(userInfo: [CodingUserInfoKey: Any] = [:]) { self.userInfo = userInfo }
public init(userInfo: [CodingUserInfoKey: Sendable] = [:]) { self.userInfoSendable = userInfo }

public func container<Key: CodingKey>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> { .init(FailureEncoder<Key>()) }
public func unkeyedContainer() -> UnkeyedEncodingContainer { FailureEncoder() }
Expand Down
8 changes: 4 additions & 4 deletions Sources/Vapor/Content/URLQueryCoders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,28 @@ public protocol URLQueryDecoder {
func decode<D>(_ decodable: D.Type, from url: URI) throws -> D
where D: Decodable

func decode<D>(_ decodable: D.Type, from url: URI, userInfo: [CodingUserInfoKey: Any]) throws -> D
func decode<D>(_ decodable: D.Type, from url: URI, userInfo: [CodingUserInfoKey: Sendable]) throws -> D
where D: Decodable
}

public protocol URLQueryEncoder {
func encode<E>(_ encodable: E, to url: inout URI) throws
where E: Encodable

func encode<E>(_ encodable: E, to url: inout URI, userInfo: [CodingUserInfoKey: Any]) throws
func encode<E>(_ encodable: E, to url: inout URI, userInfo: [CodingUserInfoKey: Sendable]) throws
where E: Encodable
}

extension URLQueryEncoder {
public func encode<E>(_ encodable: E, to url: inout URI, userInfo: [CodingUserInfoKey: Any]) throws
public func encode<E>(_ encodable: E, to url: inout URI, userInfo: [CodingUserInfoKey: Sendable]) throws
where E: Encodable
{
try self.encode(encodable, to: &url)
}
}

extension URLQueryDecoder {
public func decode<D>(_ decodable: D.Type, from url: URI, userInfo: [CodingUserInfoKey: Any]) throws -> D
public func decode<D>(_ decodable: D.Type, from url: URI, userInfo: [CodingUserInfoKey: Sendable]) throws -> D
where D: Decodable
{
try self.decode(decodable, from: url)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Vapor/Content/URLQueryContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ extension URLQueryContainer {

/// Injects coder userInfo into a ``URLQueryDecoder`` so we don't have to add passthroughs to ``URLQueryContainer``.
fileprivate struct ForwardingURLQueryDecoder: URLQueryDecoder {
let base: URLQueryDecoder, info: [CodingUserInfoKey: Any]
let base: URLQueryDecoder, info: [CodingUserInfoKey: Sendable]

func decode<D: Decodable>(_: D.Type, from url: URI) throws -> D { try self.base.decode(D.self, from: url, userInfo: self.info) }
func decode<D: Decodable>(_: D.Type, from url: URI, userInfo: [CodingUserInfoKey: Any]) throws -> D {
func decode<D: Decodable>(_: D.Type, from url: URI, userInfo: [CodingUserInfoKey: Sendable]) throws -> D {
try self.base.decode(D.self, from: url, userInfo: userInfo.merging(self.info) { $1 })
}
}
2 changes: 1 addition & 1 deletion Sources/Vapor/Multipart/FormDataDecoder+Content.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extension FormDataDecoder: ContentDecoder {
try self.decode(D.self, from: body, headers: headers, userInfo: [:])
}

public func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders, userInfo: [CodingUserInfoKey: Any]) throws -> D
public func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders, userInfo: [CodingUserInfoKey: Sendable]) throws -> D
where D: Decodable
{
guard let boundary = headers.contentType?.parameters["boundary"] else {
Expand Down
10 changes: 4 additions & 6 deletions Sources/Vapor/Multipart/FormDataEncoder+Content.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,17 @@ import NIOHTTP1
import NIOCore

extension FormDataEncoder: ContentEncoder {
public func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders) throws
where E: Encodable
{
public func encode<E: Encodable>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders) throws {
try self.encode(encodable, to: &body, headers: &headers, userInfo: [:])
}

public func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders, userInfo: [CodingUserInfoKey: Any]) throws
where E: Encodable
{
public func encode<E: Encodable>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders, userInfo: [CodingUserInfoKey: Sendable]) throws {
let boundary = "----vaporBoundary\(randomBoundaryData())"

headers.contentType = HTTPMediaType(type: "multipart", subType: "form-data", parameters: ["boundary": boundary])
if !userInfo.isEmpty {
var actualEncoder = self // Changing a coder's userInfo is a thread-unsafe mutation, operate on a copy

actualEncoder.userInfo.merge(userInfo) { $1 }
return try actualEncoder.encode(encodable, boundary: boundary, into: &body)
} else {
Expand Down