|
15 | 15 | import struct Foundation.Data
|
16 | 16 | import NIO
|
17 | 17 |
|
18 |
| -extension String { |
19 |
| - @inline(__always) |
20 |
| - var byteBuffer: ByteBuffer { |
21 |
| - var buffer = RESPValue.allocator.buffer(capacity: self.count) |
22 |
| - buffer.writeString(self) |
23 |
| - return buffer |
24 |
| - } |
25 |
| -} |
26 |
| - |
27 |
| -extension Data { |
28 |
| - @inline(__always) |
29 |
| - var byteBuffer: ByteBuffer { |
30 |
| - var buffer = RESPValue.allocator.buffer(capacity: self.count) |
31 |
| - buffer.writeBytes(self) |
32 |
| - return buffer |
33 |
| - } |
34 |
| -} |
35 |
| - |
36 | 18 | /// A representation of a Redis Serialization Protocol (RESP) primitive value.
|
37 | 19 | ///
|
| 20 | +/// This enum representation should be used only as a temporary intermediate representation of values, and should be sent to a Redis server or converted to Swift |
| 21 | +/// types as soon as possible. |
| 22 | +/// |
| 23 | +/// Redis servers expect a single message packed into an `.array`, with all elements being `.bulkString` representations of values. As such, all initializers |
| 24 | +/// convert to `.bulkString` representations, as well as default conformances for `RESPValueConvertible`. |
| 25 | +/// |
| 26 | +/// Each case of this type is a different listing in the RESP specification, and several computed properties are available to consistently convert values into Swift types. |
| 27 | +/// |
38 | 28 | /// See: [https://redis.io/topics/protocol](https://redis.io/topics/protocol)
|
39 | 29 | public enum RESPValue {
|
40 | 30 | case null
|
41 | 31 | case simpleString(ByteBuffer)
|
42 | 32 | case bulkString(ByteBuffer?)
|
43 | 33 | case error(RedisError)
|
44 | 34 | case integer(Int)
|
45 |
| - case array(ContiguousArray<RESPValue>) |
| 35 | + case array([RESPValue]) |
46 | 36 |
|
47 |
| - fileprivate static let allocator = ByteBufferAllocator() |
| 37 | + /// A `NIO.ByteBufferAllocator` for use in creating `.simpleString` and `.bulkString` representations directly, if needed. |
| 38 | + static let allocator = ByteBufferAllocator() |
48 | 39 |
|
49 |
| - /// Initializes a `bulkString` by converting the provided string input. |
50 |
| - public init(bulk value: String? = nil) { |
51 |
| - self = .bulkString(value?.byteBuffer) |
| 40 | + /// Initializes a `bulkString` value. |
| 41 | + /// - Parameter value: The `String` to store in a `.bulkString` representation. |
| 42 | + public init(bulk value: String) { |
| 43 | + var buffer = RESPValue.allocator.buffer(capacity: value.count) |
| 44 | + buffer.writeString(value) |
| 45 | + self = .bulkString(buffer) |
52 | 46 | }
|
53 | 47 |
|
| 48 | + /// Initializes a `bulkString` value. |
| 49 | + /// - Parameter value: The `Int` value to store in a `.bulkString` representation. |
54 | 50 | public init(bulk value: Int) {
|
55 |
| - self = .bulkString(value.description.byteBuffer) |
56 |
| - } |
57 |
| - |
58 |
| - public init(_ source: RESPValueConvertible) { |
59 |
| - self = source.convertedToRESPValue() |
60 |
| - } |
61 |
| -} |
62 |
| - |
63 |
| -// MARK: Expressible by Literals |
64 |
| - |
65 |
| -extension RESPValue: ExpressibleByStringLiteral { |
66 |
| - /// Initializes a bulk string from a String literal |
67 |
| - public init(stringLiteral value: String) { |
68 |
| - self = .bulkString(value.byteBuffer) |
69 |
| - } |
70 |
| -} |
71 |
| - |
72 |
| -extension RESPValue: ExpressibleByArrayLiteral { |
73 |
| - /// Initializes an array from an Array literal |
74 |
| - public init(arrayLiteral elements: RESPValue...) { |
75 |
| - self = .array(.init(elements)) |
| 51 | + self.init(bulk: value.description) |
76 | 52 | }
|
77 |
| -} |
78 | 53 |
|
79 |
| -extension RESPValue: ExpressibleByNilLiteral { |
80 |
| - /// Initializes null from a nil literal |
81 |
| - public init(nilLiteral: ()) { |
82 |
| - self = .null |
83 |
| - } |
84 |
| -} |
85 |
| - |
86 |
| -extension RESPValue: ExpressibleByIntegerLiteral { |
87 |
| - /// Initializes an integer from an integer literal |
88 |
| - public init(integerLiteral value: Int) { |
89 |
| - self = .integer(value) |
| 54 | + /// Stores the representation determined by the `RESPValueConvertible` value. |
| 55 | + /// - Important: If you are sending this value to a Redis server, the type should be convertible to a `.bulkString`. |
| 56 | + /// - Parameter value: The value that needs to be converted and stored in `RESPValue` format. |
| 57 | + public init<Value: RESPValueConvertible>(_ value: Value) { |
| 58 | + self = value.convertedToRESPValue() |
90 | 59 | }
|
91 | 60 | }
|
92 | 61 |
|
93 | 62 | // MARK: Custom String Convertible
|
94 | 63 |
|
95 | 64 | extension RESPValue: CustomStringConvertible {
|
| 65 | + /// See `CustomStringConvertible.description` |
96 | 66 | public var description: String {
|
97 | 67 | switch self {
|
98 |
| - case .integer, .simpleString, .bulkString: return self.string! |
| 68 | + case let .simpleString(buffer), |
| 69 | + let .bulkString(.some(buffer)): |
| 70 | + guard let value = String(fromRESP: self) else { return "\(buffer)" } // default to ByteBuffer's representation |
| 71 | + return value |
| 72 | + |
| 73 | + // .integer, .error, and .bulkString(.none) conversions to String always succeed |
| 74 | + case .integer, |
| 75 | + .bulkString(.none): |
| 76 | + return String(fromRESP: self)! |
| 77 | + |
99 | 78 | case .null: return "NULL"
|
100 |
| - case let .array(elements): return "[\(elements.map({ $0.description }).joined(separator: ","))]" |
101 | 79 | case let .error(e): return e.message
|
| 80 | + case let .array(elements): return "[\(elements.map({ $0.description }).joined(separator: ","))]" |
102 | 81 | }
|
103 | 82 | }
|
104 | 83 | }
|
105 | 84 |
|
106 |
| -// MARK: Computed Values |
| 85 | +// MARK: Unwrapped Values |
107 | 86 |
|
108 | 87 | extension RESPValue {
|
109 |
| - /// The `ByteBuffer` storage for either `.simpleString` or `.bulkString` representations. |
110 |
| - public var byteBuffer: ByteBuffer? { |
111 |
| - switch self { |
112 |
| - case let .simpleString(buffer), |
113 |
| - let .bulkString(.some(buffer)): return buffer |
114 |
| - default: return nil |
115 |
| - } |
116 |
| - } |
117 |
| - |
118 |
| - /// The storage value for `array` representations. |
119 |
| - public var array: ContiguousArray<RESPValue>? { |
120 |
| - guard case .array(let array) = self else { return nil } |
121 |
| - return array |
122 |
| - } |
| 88 | + /// The unwrapped value for `.array` representations. |
| 89 | + /// - Note: This is a shorthand for `Array<RESPValue>.init(fromRESP:)` |
| 90 | + public var array: [RESPValue]? { return [RESPValue](fromRESP: self) } |
123 | 91 |
|
124 |
| - /// The storage value for `integer` representations. |
125 |
| - public var int: Int? { |
126 |
| - switch self { |
127 |
| - case let .integer(value): return value |
128 |
| - default: return nil |
129 |
| - } |
130 |
| - } |
| 92 | + /// The unwrapped value as an `Int`. |
| 93 | + /// - Note: This is a shorthand for `Int(fromRESP:)`. |
| 94 | + public var int: Int? { return Int(fromRESP: self) } |
131 | 95 |
|
132 |
| - /// Returns `true` if the value represents a `null` value from Redis. |
| 96 | + /// Returns `true` if the unwrapped value is `.null`. |
133 | 97 | public var isNull: Bool {
|
134 |
| - switch self { |
135 |
| - case .null: return true |
136 |
| - default: return false |
137 |
| - } |
138 |
| - } |
139 |
| - |
140 |
| - /// The error returned from Redis. |
141 |
| - public var error: RedisError? { |
142 |
| - switch self { |
143 |
| - case .error(let error): return error |
144 |
| - default: return nil |
145 |
| - } |
| 98 | + guard case .null = self else { return false } |
| 99 | + return true |
146 | 100 | }
|
147 |
| -} |
148 |
| - |
149 |
| -// MARK: Conversion Values |
150 |
| - |
151 |
| -extension RESPValue { |
152 |
| - /// The `RESPValue` converted to a `String`. |
153 |
| - /// - Important: This will always return `nil` from `.error`, `.null`, and `array` cases. |
154 |
| - /// - Note: This creates a `String` using UTF-8 encoding. |
155 |
| - public var string: String? { |
156 |
| - switch self { |
157 |
| - case let .integer(value): return value.description |
158 |
| - case let .simpleString(buffer), |
159 |
| - let .bulkString(.some(buffer)): |
160 |
| - return buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) |
161 | 101 |
|
162 |
| - case .bulkString(.none): return "" |
163 |
| - default: return nil |
164 |
| - } |
165 |
| - } |
| 102 | + /// The unwrapped `RedisError` that was returned from Redis. |
| 103 | + /// - Note: This is a shorthand for `RedisError(fromRESP:)`. |
| 104 | + public var error: RedisError? { return RedisError(fromRESP: self) } |
166 | 105 |
|
167 |
| - /// The raw bytes of the `RESPValue` representation. |
168 |
| - /// - Important: This will always return `nil` from `.error` and `.null` cases. |
169 |
| - public var bytes: [UInt8]? { |
| 106 | + /// The unwrapped `NIO.ByteBuffer` for `.simpleString` or `.bulkString` representations. |
| 107 | + public var byteBuffer: ByteBuffer? { |
170 | 108 | switch self {
|
171 |
| - case let .integer(value): return withUnsafeBytes(of: value, RESPValue.copyMemory) |
172 |
| - case let .array(values): return values.withUnsafeBytes(RESPValue.copyMemory) |
173 | 109 | case let .simpleString(buffer),
|
174 |
| - let .bulkString(.some(buffer)): |
175 |
| - return buffer.getBytes(at: buffer.readerIndex, length: buffer.readableBytes) |
| 110 | + let .bulkString(.some(buffer)): return buffer |
176 | 111 |
|
177 |
| - case .bulkString(.none): return [] |
178 | 112 | default: return nil
|
179 | 113 | }
|
180 | 114 | }
|
| 115 | +} |
181 | 116 |
|
182 |
| - public var data: Data? { |
183 |
| - switch self { |
184 |
| - case let .integer(value): return withUnsafeBytes(of: value, RESPValue.copyMemory) |
185 |
| - case let .array(values): return values.withUnsafeBytes(RESPValue.copyMemory) |
186 |
| - case let .simpleString(buffer), |
187 |
| - let .bulkString(.some(buffer)): |
188 |
| - return buffer.withUnsafeReadableBytes(RESPValue.copyMemory) |
189 |
| - |
190 |
| - case .bulkString(.none): return Data() |
191 |
| - default: return nil |
192 |
| - } |
193 |
| - } |
| 117 | +// MARK: Conversion Values |
194 | 118 |
|
195 |
| - // SR-9604 |
196 |
| - @inline(__always) |
197 |
| - private static func copyMemory(_ ptr: UnsafeRawBufferPointer) -> Data { |
198 |
| - return Data(UnsafeRawBufferPointer(ptr).bindMemory(to: UInt8.self)) |
199 |
| - } |
200 |
| - @inline(__always) |
201 |
| - private static func copyMemory(_ ptr: UnsafeRawBufferPointer) -> [UInt8]? { |
202 |
| - return Array<UInt8>(UnsafeRawBufferPointer(ptr).bindMemory(to: UInt8.self)) |
203 |
| - } |
| 119 | +extension RESPValue { |
| 120 | + /// The value as a UTF-8 `String` representation. |
| 121 | + /// - Note: This is a shorthand for `String.init(fromRESP:)`. |
| 122 | + public var string: String? { return String(fromRESP: self) } |
| 123 | + |
| 124 | + /// The data stored in either a `.simpleString` or `.bulkString` represented as `Foundation.Data` instead of `NIO.ByteBuffer`. |
| 125 | + /// - Note: This is a shorthand for `Data.init(fromRESP:)`. |
| 126 | + public var data: Data? { return Data(fromRESP: self) } |
| 127 | + |
| 128 | + /// The raw bytes stored in the `.simpleString` or `.bulkString` representations. |
| 129 | + /// - Note: This is a shorthand for `Array<UInt8>.init(fromRESP:)`. |
| 130 | + public var bytes: [UInt8]? { return [UInt8](fromRESP: self) } |
204 | 131 | }
|
0 commit comments