Coverage Report

Created: 2026-06-01 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/swift-nio/Sources/NIOCore/NIOAny.swift
Line
Count
Source
1
//===----------------------------------------------------------------------===//
2
//
3
// This source file is part of the SwiftNIO open source project
4
//
5
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
6
// Licensed under Apache License v2.0
7
//
8
// See LICENSE.txt for license information
9
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10
//
11
// SPDX-License-Identifier: Apache-2.0
12
//
13
//===----------------------------------------------------------------------===//
14
15
/// `NIOAny` is an opaque container for values of *any* type, similar to Swift's builtin `Any` type. Contrary to
16
/// `Any` the overhead of `NIOAny` depends on the the type of the wrapped value. Certain types that are important
17
/// for the performance of a SwiftNIO application like `ByteBuffer`, `FileRegion` and `AddressEnvelope<ByteBuffer>` can be expected
18
/// to be wrapped almost without overhead. All others will have similar performance as if they were passed as an `Any` as
19
/// `NIOAny` just like `Any` will contain them within an existential container.
20
///
21
/// The most important use-cases for `NIOAny` are values travelling through the `ChannelPipeline` whose type can't
22
/// be calculated at compile time. For example:
23
///
24
///  - the `channelRead` of any `ChannelInboundHandler`
25
///  - the `write` method of a `ChannelOutboundHandler`
26
///
27
/// The abstraction that delivers a `NIOAny` to user code must provide a mechanism to unwrap a `NIOAny` as a
28
/// certain type known at run-time. Canonical example:
29
///
30
///     class SandwichHandler: ChannelInboundHandler {
31
///         typealias InboundIn = Bacon /* we expected to be delivered `Bacon` ... */
32
///         typealias InboundOut = Sandwich /* ... and we will make and deliver a `Sandwich` from that */
33
///
34
///         func channelRead(context: ChannelHandlerContext, data: NIOAny) {
35
///              /* we receive the `Bacon` as a `NIOAny` as at compile-time the exact configuration of the channel
36
///                 pipeline can't be computed. The pipeline can't be computed at compile time as it can change
37
///                 dynamically at run-time. Yet, we assert that in any configuration the channel handler before
38
///                 `SandwichHandler` does actually send us a stream of `Bacon`.
39
///              */
40
///              let bacon = Self.unwrapInboundIn(data) /* `Bacon` or crash */
41
///              let sandwich = makeSandwich(bacon)
42
///              context.fireChannelRead(Self.wrapInboundOut(sandwich)) /* as promised we deliver a wrapped `Sandwich` */
43
///         }
44
///     }
45
public struct NIOAny {
46
    @usableFromInline
47
    let _storage: _NIOAny
48
49
    /// Wrap a value in a `NIOAny`. In most cases you should not create a `NIOAny` directly using this constructor.
50
    /// The abstraction that accepts values of type `NIOAny` must also provide a mechanism to do the wrapping. An
51
    /// example is a `ChannelInboundHandler` which provides `Self.wrapInboundOut(aValueOfTypeInboundOut)`.
52
    @inlinable
53
506M
    public init<T>(_ value: T) {
54
506M
        self._storage = _NIOAny(value)
55
506M
    }
56
57
    @usableFromInline
58
    enum _NIOAny {
59
        case ioData(IOData)
60
        case bufferEnvelope(AddressedEnvelope<ByteBuffer>)
61
        case other(Any)
62
63
        @inlinable
64
506M
        init<T>(_ value: T) {
65
506M
            switch value {
66
506M
            case let value as ByteBuffer:
67
2.00M
                self = .ioData(.byteBuffer(value))
68
506M
            case let value as FileRegion:
69
0
                self = .ioData(.fileRegion(value))
70
506M
            case let value as IOData:
71
0
                self = .ioData(value)
72
506M
            case let value as AddressedEnvelope<ByteBuffer>:
73
0
                self = .bufferEnvelope(value)
74
506M
            default:
75
504M
                assert(!(value is NIOAny))
76
504M
                self = .other(value)
77
506M
            }
78
506M
        }
79
    }
80
81
    /// Try unwrapping the wrapped message as `ByteBuffer`.
82
    ///
83
    /// - Returns: The wrapped `ByteBuffer` or `nil` if the wrapped message is not a `ByteBuffer`.
84
    @inlinable
85
33.7k
    func tryAsByteBuffer() -> ByteBuffer? {
86
33.7k
        if case .ioData(.byteBuffer(let bb)) = self._storage {
87
33.7k
            return bb
88
33.7k
        } else {
89
0
            return nil
90
0
        }
91
33.7k
    }
92
93
    /// Force unwrapping the wrapped message as `ByteBuffer`.
94
    ///
95
    /// - Returns: The wrapped `ByteBuffer` or crash if the wrapped message is not a `ByteBuffer`.
96
    @inlinable
97
12.9k
    func forceAsByteBuffer() -> ByteBuffer {
98
12.9k
        if let v = tryAsByteBuffer() {
99
12.9k
            return v
100
12.9k
        } else {
101
0
            fatalError(
102
0
                "tried to decode as type \(ByteBuffer.self) but found \(Mirror(reflecting: Mirror(reflecting: self._storage).children.first!.value).subjectType) with contents \(self._storage)"
103
0
            )
104
0
        }
105
0
    }
106
107
    /// Try unwrapping the wrapped message as `IOData`.
108
    ///
109
    /// - Returns: The wrapped `IOData` or `nil` if the wrapped message is not a `IOData`.
110
    @inlinable
111
0
    func tryAsIOData() -> IOData? {
112
0
        if case .ioData(let data) = self._storage {
113
0
            return data
114
0
        } else {
115
0
            return nil
116
0
        }
117
0
    }
118
119
    /// Force unwrapping the wrapped message as `IOData`.
120
    ///
121
    /// - Returns: The wrapped `IOData` or crash if the wrapped message is not a `IOData`.
122
    @inlinable
123
0
    func forceAsIOData() -> IOData {
124
0
        if let v = tryAsIOData() {
125
0
            return v
126
0
        } else {
127
0
            fatalError(
128
0
                "tried to decode as type \(IOData.self) but found \(Mirror(reflecting: Mirror(reflecting: self._storage).children.first!.value).subjectType) with contents \(self._storage)"
129
0
            )
130
0
        }
131
0
    }
132
133
    /// Try unwrapping the wrapped message as `FileRegion`.
134
    ///
135
    /// - Returns: The wrapped `FileRegion` or `nil` if the wrapped message is not a `FileRegion`.
136
    @inlinable
137
0
    func tryAsFileRegion() -> FileRegion? {
138
0
        if case .ioData(.fileRegion(let f)) = self._storage {
139
0
            return f
140
0
        } else {
141
0
            return nil
142
0
        }
143
0
    }
144
145
    /// Force unwrapping the wrapped message as `FileRegion`.
146
    ///
147
    /// - Returns: The wrapped `FileRegion` or crash if the wrapped message is not a `FileRegion`.
148
    @inlinable
149
0
    func forceAsFileRegion() -> FileRegion {
150
0
        if let v = tryAsFileRegion() {
151
0
            return v
152
0
        } else {
153
0
            fatalError(
154
0
                "tried to decode as type \(FileRegion.self) but found \(Mirror(reflecting: Mirror(reflecting: self._storage).children.first!.value).subjectType) with contents \(self._storage)"
155
0
            )
156
0
        }
157
0
    }
158
159
    /// Try unwrapping the wrapped message as `AddressedEnvelope<ByteBuffer>`.
160
    ///
161
    /// - Returns: The wrapped `AddressedEnvelope<ByteBuffer>` or `nil` if the wrapped message is not an `AddressedEnvelope<ByteBuffer>`.
162
    @inlinable
163
0
    func tryAsByteEnvelope() -> AddressedEnvelope<ByteBuffer>? {
164
0
        if case .bufferEnvelope(let e) = self._storage {
165
0
            return e
166
0
        } else {
167
0
            return nil
168
0
        }
169
0
    }
170
171
    /// Force unwrapping the wrapped message as `AddressedEnvelope<ByteBuffer>`.
172
    ///
173
    /// - Returns: The wrapped `AddressedEnvelope<ByteBuffer>` or crash if the wrapped message is not an `AddressedEnvelope<ByteBuffer>`.
174
    @inlinable
175
0
    func forceAsByteEnvelope() -> AddressedEnvelope<ByteBuffer> {
176
0
        if let e = tryAsByteEnvelope() {
177
0
            return e
178
0
        } else {
179
0
            fatalError(
180
0
                "tried to decode as type \(AddressedEnvelope<ByteBuffer>.self) but found \(Mirror(reflecting: Mirror(reflecting: self._storage).children.first!.value).subjectType) with contents \(self._storage)"
181
0
            )
182
0
        }
183
0
    }
184
185
    /// Try unwrapping the wrapped message as `T`.
186
    ///
187
    /// - Returns: The wrapped `T` or `nil` if the wrapped message is not a `T`.
188
    @inlinable
189
0
    func tryAsOther<T>(type: T.Type = T.self) -> T? {
190
0
        switch self._storage {
191
0
        case .bufferEnvelope(let v):
192
0
            return v as? T
193
0
        case .ioData(let v):
194
0
            return v as? T
195
0
        case .other(let v):
196
0
            return v as? T
197
0
        }
198
0
    }
199
200
    /// Force unwrapping the wrapped message as `T`.
201
    ///
202
    /// - Returns: The wrapped `T` or crash if the wrapped message is not a `T`.
203
    @inlinable
204
0
    func forceAsOther<T>(type: T.Type = T.self) -> T {
205
0
        if let v = tryAsOther(type: type) {
206
0
            return v
207
0
        } else {
208
0
            fatalError(
209
0
                "tried to decode as type \(T.self) but found \(Mirror(reflecting: Mirror(reflecting: self._storage).children.first!.value).subjectType) with contents \(self._storage)"
210
0
            )
211
0
        }
212
0
    }
213
214
    /// Force unwrapping the wrapped message as `T`.
215
    ///
216
    /// - Returns: The wrapped `T` or crash if the wrapped message is not a `T`.
217
    @inlinable
218
1.17M
    func forceAs<T>(type: T.Type = T.self) -> T {
219
1.17M
        switch T.self {
220
1.17M
        case let t where t == ByteBuffer.self:
221
1.17M
            return self.forceAsByteBuffer() as! T
222
1.17M
        case let t where t == FileRegion.self:
223
0
            return self.forceAsFileRegion() as! T
224
1.17M
        case let t where t == IOData.self:
225
0
            return self.forceAsIOData() as! T
226
1.17M
        case let t where t == AddressedEnvelope<ByteBuffer>.self:
227
0
            return self.forceAsByteEnvelope() as! T
228
1.17M
        default:
229
0
            return self.forceAsOther(type: type)
230
1.17M
        }
231
1.17M
    }
232
233
    /// Try unwrapping the wrapped message as `T`.
234
    ///
235
    /// - Returns: The wrapped `T` or `nil` if the wrapped message is not a `T`.
236
    @inlinable
237
0
    func tryAs<T>(type: T.Type = T.self) -> T? {
238
0
        switch T.self {
239
0
        case let t where t == ByteBuffer.self:
240
0
            return self.tryAsByteBuffer() as! T?
241
0
        case let t where t == FileRegion.self:
242
0
            return self.tryAsFileRegion() as! T?
243
0
        case let t where t == IOData.self:
244
0
            return self.tryAsIOData() as! T?
245
0
        case let t where t == AddressedEnvelope<ByteBuffer>.self:
246
0
            return self.tryAsByteEnvelope() as! T?
247
0
        default:
248
0
            return self.tryAsOther(type: type)
249
0
        }
250
0
    }
251
252
    /// Unwrap the wrapped message.
253
    ///
254
    /// - Returns: The wrapped message.
255
    @inlinable
256
0
    func asAny() -> Any {
257
0
        switch self._storage {
258
0
        case .ioData(.byteBuffer(let bb)):
259
0
            return bb
260
0
        case .ioData(.fileRegion(let f)):
261
0
            return f
262
0
        case .bufferEnvelope(let e):
263
0
            return e
264
0
        case .other(let o):
265
0
            return o
266
0
        }
267
0
    }
268
}
269
270
@available(*, unavailable)
271
extension NIOAny._NIOAny: Sendable {}
272
273
@available(*, unavailable)
274
extension NIOAny: Sendable {}
275
276
extension NIOAny: CustomStringConvertible {
277
0
    public var description: String {
278
0
        "\(type(of: self.asAny())): \(self.asAny())"
279
0
    }
280
}
281
282
extension NIOAny: CustomDebugStringConvertible {
283
0
    public var debugDescription: String {
284
0
        "(\(self.description))"
285
0
    }
286
}