A fast, safe, and efficient CBOR serialization library for Swift on any platform.
swiftpackageindex.com/thecoolwinter/CBOR/1.1.1/documentation/cbor
atproto
swift
cbor
1//
2// EncodableTests.swift
3// SwiftCBOR
4//
5// Created by Khan Winter on 8/17/25.
6//
7
8import Testing
9#if canImport(FoundationEssentials)
10import FoundationEssentials
11#else
12import Foundation
13#endif
14@testable import CBOR
15
16@Suite("EncodableTests")
17struct EncodableTests {
18 @Test
19 func encodeNull() throws {
20 let encoded = try CBOREncoder().encode(String?(nil))
21 #expect(encoded == Data([0xf6]))
22 }
23
24 @Test
25 func encodeBools() throws {
26 let falseVal = try CBOREncoder().encode(false)
27 #expect(falseVal == Data([0xf4]))
28
29 let trueVal = try CBOREncoder().encode(true)
30 #expect(trueVal == Data([0xf5]))
31 }
32
33 @Test
34 func encodeInts() throws {
35 // Less than 24
36 let zero = try CBOREncoder().encode(0)
37 #expect(zero == Data([0x00]))
38
39 let eight = try CBOREncoder().encode(8)
40 #expect(eight == Data([0x08]))
41
42 let ten = try CBOREncoder().encode(10)
43 #expect(ten == Data([0x0a]))
44
45 let twentyThree = try CBOREncoder().encode(23)
46 #expect(twentyThree == Data([0x17]))
47
48 // Just bigger than 23
49 let twentyFour = try CBOREncoder().encode(24)
50 #expect(twentyFour == Data([0x18, 0x18]))
51
52 let twentyFive = try CBOREncoder().encode(25)
53 #expect(twentyFive == Data([0x18, 0x19]))
54
55 // Bigger
56 let hundred = try CBOREncoder().encode(100)
57 #expect(hundred == Data([0x18, 0x64]))
58
59 let thousand = try CBOREncoder().encode(1_000)
60 #expect(thousand == Data([0x19, 0x03, 0xe8]))
61
62 let million = try CBOREncoder().encode(1_000_000)
63 #expect(million == Data([0x1a, 0x00, 0x0f, 0x42, 0x40]))
64
65 let trillion = try CBOREncoder().encode(1_000_000_000_000)
66 #expect(trillion == Data([0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00]))
67 }
68
69 @Test
70 func encodeNegativeInts() throws {
71 // Less than 24
72 let minusOne = try CBOREncoder().encode(-1)
73 #expect(minusOne == Data([0x20]))
74
75 let minusTen = try CBOREncoder().encode(-10)
76 #expect(minusTen == Data([0x29]))
77
78 // Bigger
79 let minusHundred = try CBOREncoder().encode(-100)
80 #expect(minusHundred == Data([0x38, 0x63]))
81
82 let minusThousand = try CBOREncoder().encode(-1_000)
83 #expect(minusThousand == Data([0x39, 0x03, 0xe7]))
84
85 let minusMillion = try CBOREncoder().encode(-1_000_001)
86 #expect(minusMillion == Data([0x3A, 0x00, 0x0F, 0x42, 0x40]))
87
88 let minusTrillion = try CBOREncoder().encode(-1_000_000_001)
89 #expect(minusTrillion == Data([0x3A, 0x3B, 0x9A, 0xCA, 0x00]))
90 }
91
92 @Test
93 func encodeFloat() throws {
94 let value: Float = 100000.0
95 let data = "fa47c35000".asHexData()
96 let result = try CBOREncoder().encode(value)
97 #expect(data == result)
98 }
99
100 @Test
101 func encodeDouble() throws {
102 let value: Double = 0.10035
103 let data = "FB3FB9B089A0275254".asHexData()
104 let result = try CBOREncoder().encode(value)
105 #expect(data == result)
106 }
107
108 @Test
109 func encodeStrings() throws {
110 let empty = try CBOREncoder().encode("")
111 #expect(empty == Data([0x60]))
112
113 let a = try CBOREncoder().encode("a")
114 #expect(a == Data([0x61, 0x61]))
115
116 let IETF = try CBOREncoder().encode("IETF")
117 #expect(IETF == Data([0x64, 0x49, 0x45, 0x54, 0x46]))
118
119 let quoteSlash = try CBOREncoder().encode("\"\\")
120 #expect(quoteSlash == Data([0x62, 0x22, 0x5c]))
121
122 let littleUWithDiaeresis = try CBOREncoder().encode("\u{00FC}")
123 #expect(littleUWithDiaeresis == Data([0x62, 0xc3, 0xbc]))
124 }
125
126 @Test
127 func encodeByteStrings() throws {
128 let fourByteByteString = try CBOREncoder().encode(Data([0x01, 0x02, 0x03, 0x04]))
129 #expect(fourByteByteString == Data([0x44, 0x01, 0x02, 0x03, 0x04]))
130 }
131
132 @Test
133 func mixedByteArraysEncodeCorrectly() throws {
134 // TODO: Make the container swap to mixed mode if necessary.
135
136 /// See note in ``UnkeyeyedCBOREncodingContainer`` about mixed collections of ints
137 struct Mixed: Encodable {
138 func encode(to encoder: any Encoder) throws {
139 var container = encoder.unkeyedContainer()
140 try container.encode(UInt8.zero)
141 try container.encode(UInt8.max)
142 try container.encode("Hello World")
143 try container.encode(1000000)
144 }
145 }
146
147 let data = try CBOREncoder().encode(Mixed())
148 // swiftlint:disable:next line_length
149 #expect(data == Data([0x84, 0x00, 0x18, 0xff, 0x6b, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x1a, 0x00, 0x0f, 0x42, 0x40]))
150 }
151
152 @Test
153 func encodeArrays() throws {
154 let empty = try CBOREncoder().encode([String]())
155 #expect(empty == Data([0x80]))
156
157 let oneTwoThree = try CBOREncoder().encode([1, 2, 3])
158 #expect(oneTwoThree == Data([0x83, 0x01, 0x02, 0x03]))
159
160 // swiftlint:disable:next line_length
161 let lotsOfInts = try CBOREncoder().encode([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])
162
163 // swiftlint:disable:next line_length
164 #expect(lotsOfInts == Data([0x98, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19]))
165
166 let nestedSimple = try CBOREncoder().encode([[1], [2, 3], [4, 5]])
167 #expect(nestedSimple == Data([0x83, 0x81, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05]))
168 }
169
170 @Test
171 func encodeMaps() throws {
172 let empty = try CBOREncoder().encode([String: String]())
173 #expect(empty == Data([0xa0]))
174
175 let stringToString = try CBOREncoder().encode(["a": "A", "b": "B", "c": "C", "d": "D", "e": "E"])
176 #expect(stringToString.first! == 0xa5)
177
178 let dataMinusFirstByte = stringToString[1...]
179 .map { $0 }
180 .chunked(into: 4)
181 .sorted(by: { $0.lexicographicallyPrecedes($1) })
182 let dataForKeyValuePairs: [[UInt8]] = [
183 [0x61, 0x61, 0x61, 0x41],
184 [0x61, 0x62, 0x61, 0x42],
185 [0x61, 0x63, 0x61, 0x43],
186 [0x61, 0x64, 0x61, 0x44],
187 [0x61, 0x65, 0x61, 0x45]
188 ]
189 #expect(dataMinusFirstByte == dataForKeyValuePairs)
190
191 let oneTwoThreeFour = try CBOREncoder().encode([1: 2, 3: 4])
192 #expect(
193 oneTwoThreeFour == Data([0xa2, 0x01, 0x02, 0x03, 0x04]) ||
194 oneTwoThreeFour == Data([0xa2, 0x03, 0x04, 0x01, 0x02])
195 )
196
197 let encoder = CBOREncoder(forceStringKeys: true)
198 let encodedWithStringKeys = try encoder.encode([1: 2, 3: 4])
199 #expect(
200 encodedWithStringKeys == Data([0xa2, 0x61, 0x31, 0x02, 0x61, 0x33, 0x04])
201 )
202 }
203
204 @Test
205 func encodeSimpleStructs() throws {
206 struct MyStruct: Codable {
207 let age: Int
208 let name: String
209 }
210
211 let encoded = try CBOREncoder().encode(MyStruct(age: 27, name: "Ham")).map { $0 }
212
213 #expect(
214 encoded == [0xa2, 0x63, 0x61, 0x67, 0x65, 0x18, 0x1b, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x63, 0x48, 0x61, 0x6d]
215 )
216 }
217
218 @Test
219 func encodeMoreComplexStructs() throws {
220 let encoder = CBOREncoder()
221
222 let data = try encoder.encode(Company.mock).hexString().uppercased()
223 // swiftlint:disable:next line_length
224 #expect(data == "A4646E616D656941636D6520436F727067666F756E6465641907CF686D65746164617461A268696E6475737472796474656368686C6F636174696F6E6672656D6F746569656D706C6F796565738AA563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5A563616765181E646E616D6565416C6963656474616773836573776966746463626F726962656E63686D61726B65656D61696C71616C696365406578616D706C652E636F6D686973416374697665F5")
225 }
226
227 @Test
228 func uint16() throws {
229 let encoder = CBOREncoder()
230 let data = try encoder.encode(1999)
231 #expect(data == "1907CF".asHexData())
232 }
233
234 @Test(
235 .disabled("Disabled until we can figure out how to make Float16 compile on all platforms."),
236 arguments: [
237 ("F90000", 0),
238 ("F93C00", 1.0),
239 ("F9BE00", -1.5),
240 ("F97C00", .infinity),
241 ("F93E32", 1.548828125),
242 ("F9F021", -8456)
243 ]
244 )
245 func halfFloat(expected: String, value: Float16) throws {
246 let expectedData = expected.asHexData()
247 let encoder = CBOREncoder()
248 let data = try encoder.encode(value)
249 #expect(data == expectedData)
250 }
251
252 @Test
253 func duplicateKeysAreDeduplicatedOnEncode() throws {
254 let encoder = CBOREncoder()
255 struct Mock: Encodable {
256 enum CodingKeys: String, CodingKey {
257 case one
258 }
259
260 func encode(to encoder: any Encoder) throws {
261 var container = encoder.container(keyedBy: CodingKeys.self)
262 try container.encode(true, forKey: .one)
263 try container.encode(false, forKey: .one)
264 }
265 }
266
267 let data = try encoder.encode(Mock()).hexString()
268 #expect(data == "a1636f6e65f4")
269 }
270
271 @Test
272 func uuid() throws {
273 let uuid = UUID(uuidString: "A0BDA068-60AD-4111-B4C9-04746791028B")
274 let data = try CBOREncoder().encode(uuid).hexString()
275 let expected = "d82550a0bda06860ad4111b4c904746791028b"
276 #expect(data == expected)
277 }
278}