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