Skip to content

Commit f384171

Browse files
committed
Add a variant of RESP3Token that does not do validation, while providing errors when unwrapping the value
1 parent 600d239 commit f384171

File tree

2 files changed

+165
-93
lines changed

2 files changed

+165
-93
lines changed

Sources/RESP3/RESP3Token.swift

+161-89
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,162 @@
1515
import NIOCore
1616

1717
public struct RESP3Token: Hashable, Sendable {
18+
public struct Unchecked: Hashable, Sendable {
19+
let base: ByteBuffer
20+
21+
public init(buffer: ByteBuffer) {
22+
self.base = buffer
23+
}
24+
25+
public func getValue() throws -> Value {
26+
var local = self.base
27+
28+
switch local.readValidatedRESP3TypeIdentifier() {
29+
case .null:
30+
return .null
31+
32+
case .boolean:
33+
switch local.readInteger(as: UInt8.self) {
34+
case UInt8.t:
35+
return .boolean(true)
36+
case UInt8.f:
37+
return .boolean(false)
38+
default:
39+
throw RESP3ParsingError(code: .invalidData, buffer: base)
40+
}
41+
42+
case .blobString:
43+
guard
44+
var lengthSlice = try local.readCRLFTerminatedSlice2(),
45+
let lengthString = lengthSlice.readString(length: lengthSlice.readableBytes),
46+
let length = Int(lengthString),
47+
let string = local.readSlice(length: length)
48+
else {
49+
throw RESP3ParsingError(code: .invalidData, buffer: base)
50+
}
51+
52+
return .blobString(string)
53+
54+
case .blobError:
55+
guard
56+
var lengthSlice = try local.readCRLFTerminatedSlice2(),
57+
let lengthString = lengthSlice.readString(length: lengthSlice.readableBytes),
58+
let length = Int(lengthString),
59+
let slice = local.readSlice(length: length)
60+
else {
61+
throw RESP3ParsingError(code: .invalidData, buffer: base)
62+
}
63+
64+
return .blobError(slice)
65+
66+
case .simpleString:
67+
guard let slice = try local.readCRLFTerminatedSlice2() else {
68+
throw RESP3ParsingError(code: .invalidData, buffer: base)
69+
}
70+
71+
return .simpleString(slice)
72+
73+
case .simpleError:
74+
guard let slice = try local.readCRLFTerminatedSlice2() else {
75+
throw RESP3ParsingError(code: .invalidData, buffer: base)
76+
}
77+
78+
return .simpleError(slice)
79+
80+
case .array:
81+
guard
82+
var countSlice = try local.readCRLFTerminatedSlice2(),
83+
let countString = countSlice.readString(length: countSlice.readableBytes),
84+
let count = Int(countString)
85+
else {
86+
throw RESP3ParsingError(code: .invalidData, buffer: base)
87+
}
88+
89+
return .array(.init(count: count, buffer: local))
90+
91+
case .push:
92+
guard
93+
var countSlice = try local.readCRLFTerminatedSlice2(),
94+
let countString = countSlice.readString(length: countSlice.readableBytes),
95+
let count = Int(countString)
96+
else {
97+
throw RESP3ParsingError(code: .invalidData, buffer: base)
98+
}
99+
100+
return .push(.init(count: count, buffer: local))
101+
102+
case .set:
103+
guard
104+
var countSlice = try local.readCRLFTerminatedSlice2(),
105+
let countString = countSlice.readString(length: countSlice.readableBytes),
106+
let count = Int(countString)
107+
else {
108+
throw RESP3ParsingError(code: .invalidData, buffer: base)
109+
}
110+
111+
return .set(.init(count: count, buffer: local))
112+
113+
case .attribute:
114+
guard
115+
var countSlice = try local.readCRLFTerminatedSlice2(),
116+
let countString = countSlice.readString(length: countSlice.readableBytes),
117+
let count = Int(countString)
118+
else {
119+
throw RESP3ParsingError(code: .invalidData, buffer: base)
120+
}
121+
122+
return .attribute(.init(count: count, buffer: local))
123+
124+
case .map:
125+
guard
126+
var countSlice = try local.readCRLFTerminatedSlice2(),
127+
let countString = countSlice.readString(length: countSlice.readableBytes),
128+
let count = Int(countString)
129+
else {
130+
throw RESP3ParsingError(code: .invalidData, buffer: base)
131+
}
132+
133+
return .map(.init(count: count, buffer: local))
134+
135+
case .integer:
136+
var numberSlice = try local.readCRLFTerminatedSlice2()!
137+
let numberString = numberSlice.readString(length: numberSlice.readableBytes)!
138+
let number = Int64(numberString)!
139+
return .number(number)
140+
141+
case .double:
142+
guard
143+
var numberSlice = try local.readCRLFTerminatedSlice2(),
144+
let numberString = numberSlice.readString(length: numberSlice.readableBytes),
145+
let number = Double(numberString)
146+
else {
147+
throw RESP3ParsingError(code: .invalidData, buffer: base)
148+
}
149+
150+
return .double(number)
151+
152+
case .verbatimString:
153+
guard
154+
var lengthSlice = try! local.readCRLFTerminatedSlice2(),
155+
let lengthString = lengthSlice.readString(length: lengthSlice.readableBytes),
156+
let length = Int(lengthString),
157+
let slice = local.readSlice(length: length)
158+
else {
159+
throw RESP3ParsingError(code: .invalidData, buffer: base)
160+
}
161+
162+
return .verbatimString(slice)
163+
164+
case .bigNumber:
165+
guard let lengthSlice = try local.readCRLFTerminatedSlice2() else {
166+
throw RESP3ParsingError(code: .invalidData, buffer: base)
167+
}
168+
169+
return .bigNumber(lengthSlice)
170+
}
171+
}
172+
}
173+
18174
public struct Array: Sequence, Sendable, Hashable {
19175
public typealias Element = RESP3Token
20176

@@ -93,90 +249,10 @@ public struct RESP3Token: Hashable, Sendable {
93249
case push(Array)
94250
}
95251

96-
let base: ByteBuffer
252+
let wrapped: Unchecked
97253

98254
public var value: Value {
99-
var local = self.base
100-
101-
switch local.readValidatedRESP3TypeIdentifier() {
102-
case .null:
103-
return .null
104-
105-
case .boolean:
106-
return .boolean(local.readInteger(as: UInt8.self)! == .t)
107-
108-
case .blobString:
109-
var lengthSlice = try! local.readCRLFTerminatedSlice2()!
110-
let lengthString = lengthSlice.readString(length: lengthSlice.readableBytes)!
111-
let length = Int(lengthString)!
112-
return .blobString(local.readSlice(length: length)!)
113-
114-
case .blobError:
115-
var lengthSlice = try! local.readCRLFTerminatedSlice2()!
116-
let lengthString = lengthSlice.readString(length: lengthSlice.readableBytes)!
117-
let length = Int(lengthString)!
118-
return .blobError(local.readSlice(length: length)!)
119-
120-
case .simpleString:
121-
let slice = try! local.readCRLFTerminatedSlice2()!
122-
return .simpleString(slice)
123-
124-
case .simpleError:
125-
let slice = try! local.readCRLFTerminatedSlice2()!
126-
return .simpleError(slice)
127-
128-
case .array:
129-
var countSlice = try! local.readCRLFTerminatedSlice2()!
130-
let countString = countSlice.readString(length: countSlice.readableBytes)!
131-
let count = Int(countString)!
132-
return .array(.init(count: count, buffer: local))
133-
134-
case .push:
135-
var countSlice = try! local.readCRLFTerminatedSlice2()!
136-
let countString = countSlice.readString(length: countSlice.readableBytes)!
137-
let count = Int(countString)!
138-
return .push(.init(count: count, buffer: local))
139-
140-
case .set:
141-
var countSlice = try! local.readCRLFTerminatedSlice2()!
142-
let countString = countSlice.readString(length: countSlice.readableBytes)!
143-
let count = Int(countString)!
144-
return .set(.init(count: count, buffer: local))
145-
146-
case .attribute:
147-
var countSlice = try! local.readCRLFTerminatedSlice2()!
148-
let countString = countSlice.readString(length: countSlice.readableBytes)!
149-
let count = Int(countString)!
150-
return .attribute(.init(count: count, buffer: local))
151-
152-
case .map:
153-
var countSlice = try! local.readCRLFTerminatedSlice2()!
154-
let countString = countSlice.readString(length: countSlice.readableBytes)!
155-
let count = Int(countString)!
156-
return .map(.init(count: count, buffer: local))
157-
158-
case .integer:
159-
var numberSlice = try! local.readCRLFTerminatedSlice2()!
160-
let numberString = numberSlice.readString(length: numberSlice.readableBytes)!
161-
let number = Int64(numberString)!
162-
return .number(number)
163-
164-
case .double:
165-
var numberSlice = try! local.readCRLFTerminatedSlice2()!
166-
let numberString = numberSlice.readString(length: numberSlice.readableBytes)!
167-
let number = Double(numberString)!
168-
return .double(number)
169-
170-
case .verbatimString:
171-
var lengthSlice = try! local.readCRLFTerminatedSlice2()!
172-
let lengthString = lengthSlice.readString(length: lengthSlice.readableBytes)!
173-
let length = Int(lengthString)!
174-
return .verbatimString(local.readSlice(length: length)!)
175-
176-
case .bigNumber:
177-
let lengthSlice = try! local.readCRLFTerminatedSlice2()!
178-
return .bigNumber(lengthSlice)
179-
}
255+
try! wrapped.getValue()
180256
}
181257

182258
public init?(consuming buffer: inout ByteBuffer) throws {
@@ -222,12 +298,8 @@ public struct RESP3Token: Hashable, Sendable {
222298
return nil
223299
}
224300

225-
guard let validated = validated else { return nil }
226-
self.base = validated
227-
}
228-
229-
init(validated: ByteBuffer) {
230-
self.base = validated
301+
guard let validated else { return nil }
302+
self.wrapped = Unchecked(buffer: validated)
231303
}
232304
}
233305

@@ -361,7 +433,7 @@ extension ByteBuffer {
361433
guard let new = try RESP3Token(consuming: &localCopy, depth: depth + 1) else {
362434
return nil
363435
}
364-
bodyLength += new.base.readableBytes
436+
bodyLength += new.wrapped.base.readableBytes
365437
}
366438
return bodyLength
367439
}

Sources/RediStackPerformanceTester/RESP3ParsingBenchmark.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ func benchmarkRESP3Parsing() throws {
2121
.blobString(ByteBuffer(string: "GET")),
2222
.blobString(ByteBuffer(string: "welcome")),
2323
]
24+
let token = RESP3Token.Unchecked(buffer: valueBuffer)
2425

2526
try benchmark {
26-
var valueBuffer = valueBuffer
27+
let token = token
2728

2829
guard
29-
let token = try RESP3Token(consuming: &valueBuffer),
30-
case .array(let array) = token.value,
31-
array.map(\.value) == values
30+
case .array(let array) = try token.getValue(),
31+
array.count == values.count
3232
else {
3333
fatalError("\(#function) Test failed: Invalid test result")
3434
}

0 commit comments

Comments
 (0)