blob: eb8a10c5c94b35dc5ab458e045f1430565d0267b [file] [log] [blame]
Austin Schuh58b9b472020-11-25 19:12:44 -08001/*
James Kuszmaul8e62b022022-03-22 09:33:25 -07002 * Copyright 2021 Google Inc. All rights reserved.
Austin Schuh58b9b472020-11-25 19:12:44 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Austin Schuh272c6132020-11-14 16:37:52 -080017import XCTest
18@testable import FlatBuffers
19
20final class FlatBuffersUnionTests: XCTestCase {
Austin Schuh58b9b472020-11-25 19:12:44 -080021
22 func testCreateMonstor() {
23
24 var b = FlatBufferBuilder(initialSize: 20)
25 let dmg: Int16 = 5
26 let str = "Axe"
27 let axe = b.create(string: str)
28 let weapon = Weapon.createWeapon(builder: &b, offset: axe, dmg: dmg)
29 let weapons = b.createVector(ofOffsets: [weapon])
30 let root = LocalMonster.createMonster(
31 builder: &b,
32 offset: weapons,
33 equipment: .Weapon,
34 equippedOffset: weapon.o)
35 b.finish(offset: root)
36 let buffer = b.sizedByteArray
James Kuszmaul8e62b022022-03-22 09:33:25 -070037 // swiftformat:disable all
Austin Schuh58b9b472020-11-25 19:12:44 -080038 XCTAssertEqual(buffer, [16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 8, 0, 7, 0, 12, 0, 10, 0, 0, 0, 0, 0, 0, 1, 8, 0, 0, 0, 20, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 8, 0, 12, 0, 8, 0, 6, 0, 8, 0, 0, 0, 0, 0, 5, 0, 4, 0, 0, 0, 3, 0, 0, 0, 65, 120, 101, 0])
James Kuszmaul8e62b022022-03-22 09:33:25 -070039 // swiftformat:enable all
Austin Schuh58b9b472020-11-25 19:12:44 -080040 let monster = LocalMonster.getRootAsMonster(bb: ByteBuffer(bytes: buffer))
41 XCTAssertEqual(monster.weapon(at: 0)?.dmg, dmg)
42 XCTAssertEqual(monster.weapon(at: 0)?.name, str)
43 XCTAssertEqual(monster.weapon(at: 0)?.nameVector, [65, 120, 101])
44 let p: Weapon? = monster.equiped()
45 XCTAssertEqual(p?.dmg, dmg)
46 XCTAssertEqual(p?.name, str)
47 XCTAssertEqual(p?.nameVector, [65, 120, 101])
48 }
49
50 func testEndTableFinish() {
51 var builder = FlatBufferBuilder(initialSize: 20)
52 let sword = builder.create(string: "Sword")
53 let axe = builder.create(string: "Axe")
James Kuszmaul8e62b022022-03-22 09:33:25 -070054 let weaponOne = Weapon.createWeapon(
55 builder: &builder,
56 offset: sword,
57 dmg: 3)
Austin Schuh58b9b472020-11-25 19:12:44 -080058 let weaponTwo = Weapon.createWeapon(builder: &builder, offset: axe, dmg: 5)
59 let name = builder.create(string: "Orc")
60 let inventory: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
61 let inv = builder.createVector(inventory, size: 10)
62 let weapons = builder.createVector(ofOffsets: [weaponOne, weaponTwo])
James Kuszmaul8e62b022022-03-22 09:33:25 -070063 let path = builder.createVector(ofStructs: [
64 Vec(x: 4.0, y: 5.0, z: 6.0),
65 Vec(x: 1.0, y: 2.0, z: 3.0),
66 ])
Austin Schuh58b9b472020-11-25 19:12:44 -080067 let orc = FinalMonster.createMonster(
68 builder: &builder,
James Kuszmaul8e62b022022-03-22 09:33:25 -070069 position: Vec(x: 1, y: 2, z: 3),
Austin Schuh58b9b472020-11-25 19:12:44 -080070 hp: 300,
71 name: name,
72 inventory: inv,
73 color: .red,
74 weapons: weapons,
75 equipment: .Weapon,
76 equippedOffset: weaponTwo,
77 path: path)
78 builder.finish(offset: orc)
James Kuszmaul8e62b022022-03-22 09:33:25 -070079 // swiftformat:disable all
80 XCTAssertEqual(builder.sizedByteArray, [32, 0, 0, 0, 0, 0, 26, 0, 48, 0, 36, 0, 0, 0, 34, 0, 28, 0, 0, 0, 24, 0, 23, 0, 16, 0, 15, 0, 8, 0, 4, 0, 26, 0, 0, 0, 44, 0, 0, 0, 104, 0, 0, 0, 0, 0, 0, 1, 60, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 76, 0, 0, 0, 0, 0, 44, 1, 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 2, 0, 0, 0, 0, 0, 128, 64, 0, 0, 160, 64, 0, 0, 192, 64, 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 2, 0, 0, 0, 52, 0, 0, 0, 28, 0, 0, 0, 10, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 3, 0, 0, 0, 79, 114, 99, 0, 244, 255, 255, 255, 0, 0, 5, 0, 24, 0, 0, 0, 8, 0, 12, 0, 8, 0, 6, 0, 8, 0, 0, 0, 0, 0, 3, 0, 12, 0, 0, 0, 3, 0, 0, 0, 65, 120, 101, 0, 5, 0, 0, 0, 83, 119, 111, 114, 100, 0, 0, 0])
81 // swiftformat:enable all
Austin Schuh58b9b472020-11-25 19:12:44 -080082 }
83
84 func testEnumVector() {
85 let vectorOfEnums: [ColorsNameSpace.RGB] = [.blue, .green]
86
87 var builder = FlatBufferBuilder(initialSize: 1)
88 let off = builder.createVector(vectorOfEnums)
89 let start = ColorsNameSpace.Monster.startMonster(&builder)
90 ColorsNameSpace.Monster.add(colors: off, &builder)
91 let end = ColorsNameSpace.Monster.endMonster(&builder, start: start)
92 builder.finish(offset: end)
James Kuszmaul8e62b022022-03-22 09:33:25 -070093 // swiftformat:disable all
Austin Schuh58b9b472020-11-25 19:12:44 -080094 XCTAssertEqual(builder.sizedByteArray, [12, 0, 0, 0, 0, 0, 6, 0, 8, 0, 4, 0, 6, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0])
James Kuszmaul8e62b022022-03-22 09:33:25 -070095 // swiftformat:enable all
Austin Schuh58b9b472020-11-25 19:12:44 -080096 let monster = ColorsNameSpace.Monster.getRootAsMonster(bb: builder.buffer)
97 XCTAssertEqual(monster.colorsCount, 2)
98 XCTAssertEqual(monster.colors(at: 0), .blue)
99 XCTAssertEqual(monster.colors(at: 1), .green)
100 }
101
102 func testUnionVector() {
103 var fb = FlatBufferBuilder()
104
105 let swordDmg: Int32 = 8
106 let attackStart = Attacker.startAttacker(&fb)
107 Attacker.add(swordAttackDamage: swordDmg, &fb)
108 let attack = Attacker.endAttacker(&fb, start: attackStart)
109
110 let characterType: [Character] = [.belle, .mulan, .bookfan]
111
112 let characters = [
James Kuszmaul8e62b022022-03-22 09:33:25 -0700113 fb.create(struct: BookReader(booksRead: 7)),
Austin Schuh58b9b472020-11-25 19:12:44 -0800114 attack,
James Kuszmaul8e62b022022-03-22 09:33:25 -0700115 fb.create(struct: BookReader(booksRead: 2)),
Austin Schuh58b9b472020-11-25 19:12:44 -0800116 ]
117 let types = fb.createVector(characterType)
118 let characterVector = fb.createVector(ofOffsets: characters)
James Kuszmaul8e62b022022-03-22 09:33:25 -0700119 let end = Movie.createMovie(
120 &fb,
121 charactersTypeVectorOffset: types,
122 charactersVectorOffset: characterVector)
Austin Schuh58b9b472020-11-25 19:12:44 -0800123 Movie.finish(&fb, end: end)
124
125 var movie = Movie.getRootAsMovie(bb: fb.buffer)
126 XCTAssertEqual(movie.charactersTypeCount, Int32(characterType.count))
127 XCTAssertEqual(movie.charactersCount, Int32(characters.count))
128
129 for i in 0..<movie.charactersTypeCount {
130 XCTAssertEqual(movie.charactersType(at: i), characterType[Int(i)])
Austin Schuh272c6132020-11-14 16:37:52 -0800131 }
Austin Schuh58b9b472020-11-25 19:12:44 -0800132
James Kuszmaul8e62b022022-03-22 09:33:25 -0700133 XCTAssertEqual(
134 movie.characters(at: 0, type: BookReader_Mutable.self)?.booksRead,
135 7)
136 XCTAssertEqual(
137 movie.characters(at: 1, type: Attacker.self)?.swordAttackDamage,
138 swordDmg)
139 XCTAssertEqual(
140 movie.characters(at: 2, type: BookReader_Mutable.self)?.booksRead,
141 2)
Austin Schuh58b9b472020-11-25 19:12:44 -0800142
143 var objc: MovieT? = movie.unpack()
James Kuszmaul8e62b022022-03-22 09:33:25 -0700144 XCTAssertEqual(
145 movie.charactersTypeCount,
146 Int32(objc?.characters.count ?? 0))
147 XCTAssertEqual(
148 movie.characters(at: 0, type: BookReader_Mutable.self)?.booksRead,
149 (objc?.characters[0]?.value as? BookReader)?.booksRead)
Austin Schuh58b9b472020-11-25 19:12:44 -0800150 fb.clear()
151 let newMovie = Movie.pack(&fb, obj: &objc)
152 fb.finish(offset: newMovie)
153
154 let packedMovie = Movie.getRootAsMovie(bb: fb.buffer)
155
James Kuszmaul8e62b022022-03-22 09:33:25 -0700156 XCTAssertEqual(
157 packedMovie.characters(at: 0, type: BookReader_Mutable.self)?.booksRead,
158 movie.characters(at: 0, type: BookReader_Mutable.self)?.booksRead)
159 XCTAssertEqual(
160 packedMovie.characters(at: 1, type: Attacker.self)?.swordAttackDamage,
161 movie.characters(at: 1, type: Attacker.self)?.swordAttackDamage)
162 XCTAssertEqual(
163 packedMovie.characters(at: 2, type: BookReader_Mutable.self)?.booksRead,
164 movie.characters(at: 2, type: BookReader_Mutable.self)?.booksRead)
165 }
166
167 func testStringUnion() {
168 let string = "Awesome \\\\t\t\nstring!"
169 var fb = FlatBufferBuilder()
170 let stringOffset = fb.create(string: string)
171 let characterType: [Character] = [.bookfan, .other]
172
173 let characters = [
174 fb.create(struct: BookReader(booksRead: 7)),
175 stringOffset,
176 ]
177 let types = fb.createVector(characterType)
178 let characterVector = fb.createVector(ofOffsets: characters)
179
180 let end = Movie.createMovie(
181 &fb,
182 mainCharacterType: .other,
183 mainCharacterOffset: Offset(offset: stringOffset.o),
184 charactersTypeVectorOffset: types,
185 charactersVectorOffset: characterVector)
186 Movie.finish(&fb, end: end)
187
188 var movie = Movie.getRootAsMovie(bb: fb.sizedBuffer)
189 XCTAssertEqual(movie.mainCharacter(type: String.self), string)
190 XCTAssertEqual(
191 movie.characters(at: 0, type: BookReader_Mutable.self)?.booksRead,
192 7)
193 XCTAssertEqual(movie.characters(at: 1, type: String.self), string)
194
195 var objc: MovieT? = movie.unpack()
196 XCTAssertEqual(objc?.mainCharacter?.value as? String, string)
197 XCTAssertEqual((objc?.characters[0]?.value as? BookReader)?.booksRead, 7)
198 XCTAssertEqual(objc?.characters[1]?.value as? String, string)
199 fb.clear()
200 let newMovie = Movie.pack(&fb, obj: &objc)
201 fb.finish(offset: newMovie)
202
203 let packedMovie = Movie.getRootAsMovie(bb: fb.buffer)
204 XCTAssertEqual(packedMovie.mainCharacter(type: String.self), string)
205 XCTAssertEqual(
206 packedMovie.characters(at: 0, type: BookReader_Mutable.self)?.booksRead,
207 7)
208 XCTAssertEqual(packedMovie.characters(at: 1, type: String.self), string)
209 }
210
211 func testEncoding() {
212 let string = "Awesome \\\\t\t\nstring!"
213 var fb = FlatBufferBuilder()
214
215 let stringOffset = fb.create(string: string)
216
217 let swordDmg: Int32 = 8
218 let attackStart = Attacker.startAttacker(&fb)
219 Attacker.add(swordAttackDamage: swordDmg, &fb)
220 let attack = Attacker.endAttacker(&fb, start: attackStart)
221
222 let characterType: [Character] = [.belle, .mulan, .bookfan, .other]
223
224 let characters = [
225 fb.create(struct: BookReader(booksRead: 7)),
226 attack,
227 fb.create(struct: BookReader(booksRead: 2)),
228 stringOffset,
229 ]
230 let types = fb.createVector(characterType)
231 let characterVector = fb.createVector(ofOffsets: characters)
232 let end = Movie.createMovie(
233 &fb,
234 charactersTypeVectorOffset: types,
235 charactersVectorOffset: characterVector)
236 Movie.finish(&fb, end: end)
237
238 var sizedBuffer = fb.sizedBuffer
239 do {
240 let reader: Movie = try getCheckedRoot(byteBuffer: &sizedBuffer)
241 let encoder = JSONEncoder()
242 encoder.keyEncodingStrategy = .convertToSnakeCase
243 let data = try encoder.encode(reader)
244 XCTAssertEqual(data, jsonData.data(using: .utf8))
245 } catch {
246 XCTFail(error.localizedDescription)
247 }
248 }
249
250 var jsonData: String {
251 "{\"characters_type\":[\"Belle\",\"MuLan\",\"BookFan\",\"Other\"],\"characters\":[{\"books_read\":7},{\"sword_attack_damage\":8},{\"books_read\":2},\"Awesome \\\\\\\\t\\t\\nstring!\"]}"
Austin Schuh58b9b472020-11-25 19:12:44 -0800252 }
Austin Schuh272c6132020-11-14 16:37:52 -0800253}
254
255public enum ColorsNameSpace {
Austin Schuh58b9b472020-11-25 19:12:44 -0800256
257 enum RGB: Int32, Enum {
258 typealias T = Int32
259 static var byteSize: Int { MemoryLayout<Int32>.size }
260 var value: Int32 { rawValue }
261 case red = 0, green = 1, blue = 2
262 }
263
264 struct Monster: FlatBufferObject {
265 var __buffer: ByteBuffer! { _accessor.bb }
266
267 private var _accessor: Table
268 static func getRootAsMonster(bb: ByteBuffer) -> Monster { Monster(Table(
269 bb: bb,
James Kuszmaul8e62b022022-03-22 09:33:25 -0700270 position: Int32(bb.read(def: UOffset.self, position: bb.reader)) +
271 Int32(bb.reader))) }
Austin Schuh58b9b472020-11-25 19:12:44 -0800272
273 init(_ t: Table) { _accessor = t }
274 init(_ bb: ByteBuffer, o: Int32) { _accessor = Table(bb: bb, position: o) }
275
James Kuszmaul8e62b022022-03-22 09:33:25 -0700276 public var colorsCount: Int32 {
277 let o = _accessor.offset(4); return o == 0 ? 0 : _accessor
278 .vector(count: o) }
279 public func colors(at index: Int32) -> ColorsNameSpace
280 .RGB?
281 { let o = _accessor.offset(4); return o == 0 ? ColorsNameSpace
282 .RGB(rawValue: 0)! : ColorsNameSpace.RGB(rawValue: _accessor.directRead(
283 of: Int32.self,
284 offset: _accessor.vector(at: o) + index * 4)) }
285 static func startMonster(_ fbb: inout FlatBufferBuilder) -> UOffset { fbb
286 .startTable(with: 1) }
287 static func add(colors: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(
Austin Schuh58b9b472020-11-25 19:12:44 -0800288 offset: colors,
289 at: 4) }
James Kuszmaul8e62b022022-03-22 09:33:25 -0700290 static func endMonster(
291 _ fbb: inout FlatBufferBuilder,
292 start: UOffset)
293 -> Offset
294 { let end = Offset(offset: fbb.endTable(at: start)); return end
295 }
Austin Schuh58b9b472020-11-25 19:12:44 -0800296 }
Austin Schuh272c6132020-11-14 16:37:52 -0800297}
298
299
300enum Equipment: Byte { case none, Weapon }
301
302enum Color3: Int8 { case red = 0, green, blue }
303
304struct FinalMonster {
Austin Schuh58b9b472020-11-25 19:12:44 -0800305
306 @inlinable
307 static func createMonster(
308 builder: inout FlatBufferBuilder,
James Kuszmaul8e62b022022-03-22 09:33:25 -0700309 position: Vec,
Austin Schuh58b9b472020-11-25 19:12:44 -0800310 hp: Int16,
James Kuszmaul8e62b022022-03-22 09:33:25 -0700311 name: Offset,
312 inventory: Offset,
Austin Schuh58b9b472020-11-25 19:12:44 -0800313 color: Color3,
James Kuszmaul8e62b022022-03-22 09:33:25 -0700314 weapons: Offset,
Austin Schuh58b9b472020-11-25 19:12:44 -0800315 equipment: Equipment = .none,
James Kuszmaul8e62b022022-03-22 09:33:25 -0700316 equippedOffset: Offset,
317 path: Offset) -> Offset
Austin Schuh58b9b472020-11-25 19:12:44 -0800318 {
319 let start = builder.startTable(with: 11)
James Kuszmaul8e62b022022-03-22 09:33:25 -0700320 builder.create(struct: position, position: 4)
Austin Schuh58b9b472020-11-25 19:12:44 -0800321 builder.add(element: hp, def: 100, at: 8)
322 builder.add(offset: name, at: 10)
323 builder.add(offset: inventory, at: 14)
324 builder.add(element: color.rawValue, def: Color3.green.rawValue, at: 16)
325 builder.add(offset: weapons, at: 18)
James Kuszmaul8e62b022022-03-22 09:33:25 -0700326 builder.add(
327 element: equipment.rawValue,
328 def: Equipment.none.rawValue,
329 at: 20)
Austin Schuh58b9b472020-11-25 19:12:44 -0800330 builder.add(offset: equippedOffset, at: 22)
331 builder.add(offset: path, at: 24)
332 return Offset(offset: builder.endTable(at: start))
333 }
Austin Schuh272c6132020-11-14 16:37:52 -0800334}
335
336struct LocalMonster {
Austin Schuh58b9b472020-11-25 19:12:44 -0800337
338 private var __t: Table
339
340 init(_ fb: ByteBuffer, o: Int32) { __t = Table(bb: fb, position: o) }
341 init(_ t: Table) { __t = t }
342
James Kuszmaul8e62b022022-03-22 09:33:25 -0700343 func weapon(at index: Int32) -> Weapon? { let o = __t
344 .offset(4); return o == 0 ? nil : Weapon.assign(
345 __t.indirect(__t.vector(at: o) + (index * 4)),
346 __t.bb) }
Austin Schuh58b9b472020-11-25 19:12:44 -0800347
348 func equiped<T: FlatBufferObject>() -> T? {
349 let o = __t.offset(8); return o == 0 ? nil : __t.union(o)
350 }
351
352 static func getRootAsMonster(bb: ByteBuffer) -> LocalMonster {
James Kuszmaul8e62b022022-03-22 09:33:25 -0700353 LocalMonster(Table(
354 bb: bb,
355 position: Int32(bb.read(def: UOffset.self, position: 0))))
Austin Schuh58b9b472020-11-25 19:12:44 -0800356 }
357
358 @inlinable
359 static func createMonster(
360 builder: inout FlatBufferBuilder,
James Kuszmaul8e62b022022-03-22 09:33:25 -0700361 offset: Offset,
Austin Schuh58b9b472020-11-25 19:12:44 -0800362 equipment: Equipment = .none,
James Kuszmaul8e62b022022-03-22 09:33:25 -0700363 equippedOffset: UOffset) -> Offset
Austin Schuh58b9b472020-11-25 19:12:44 -0800364 {
365 let start = builder.startTable(with: 3)
366 builder.add(element: equippedOffset, def: 0, at: 8)
367 builder.add(offset: offset, at: 4)
James Kuszmaul8e62b022022-03-22 09:33:25 -0700368 builder.add(
369 element: equipment.rawValue,
370 def: Equipment.none.rawValue,
371 at: 6)
Austin Schuh58b9b472020-11-25 19:12:44 -0800372 return Offset(offset: builder.endTable(at: start))
373 }
Austin Schuh272c6132020-11-14 16:37:52 -0800374}
375
376struct Weapon: FlatBufferObject {
Austin Schuh58b9b472020-11-25 19:12:44 -0800377
378 var __buffer: ByteBuffer! { __t.bb }
379
380 static let offsets: (name: VOffset, dmg: VOffset) = (4, 6)
381 private var __t: Table
382
383 init(_ t: Table) { __t = t }
384 init(_ fb: ByteBuffer, o: Int32) { __t = Table(bb: fb, position: o)}
385
James Kuszmaul8e62b022022-03-22 09:33:25 -0700386 var dmg: Int16 { let o = __t.offset(6); return o == 0 ? 0 : __t.readBuffer(
387 of: Int16.self,
388 at: o) }
Austin Schuh58b9b472020-11-25 19:12:44 -0800389 var nameVector: [UInt8]? { __t.getVector(at: 4) }
James Kuszmaul8e62b022022-03-22 09:33:25 -0700390 var name: String? {
391 let o = __t.offset(4); return o == 0 ? nil : __t.string(at: o) }
Austin Schuh58b9b472020-11-25 19:12:44 -0800392
James Kuszmaul8e62b022022-03-22 09:33:25 -0700393 static func assign(_ i: Int32, _ bb: ByteBuffer) -> Weapon { Weapon(Table(
394 bb: bb,
395 position: i)) }
Austin Schuh58b9b472020-11-25 19:12:44 -0800396
397 @inlinable
398 static func createWeapon(
399 builder: inout FlatBufferBuilder,
James Kuszmaul8e62b022022-03-22 09:33:25 -0700400 offset: Offset,
401 dmg: Int16) -> Offset
Austin Schuh58b9b472020-11-25 19:12:44 -0800402 {
403 let _start = builder.startTable(with: 2)
404 Weapon.add(builder: &builder, name: offset)
405 Weapon.add(builder: &builder, dmg: dmg)
406 return Weapon.end(builder: &builder, startOffset: _start)
407 }
408
409 @inlinable
James Kuszmaul8e62b022022-03-22 09:33:25 -0700410 static func end(
411 builder: inout FlatBufferBuilder,
412 startOffset: UOffset) -> Offset
413 {
Austin Schuh58b9b472020-11-25 19:12:44 -0800414 Offset(offset: builder.endTable(at: startOffset))
415 }
416
417 @inlinable
James Kuszmaul8e62b022022-03-22 09:33:25 -0700418 static func add(builder: inout FlatBufferBuilder, name: Offset) {
Austin Schuh58b9b472020-11-25 19:12:44 -0800419 builder.add(offset: name, at: Weapon.offsets.name)
420 }
421
422 @inlinable
423 static func add(builder: inout FlatBufferBuilder, dmg: Int16) {
424 builder.add(element: dmg, def: 0, at: Weapon.offsets.dmg)
425 }
Austin Schuh272c6132020-11-14 16:37:52 -0800426}