/ protobuf_serialization / sizer.nim
sizer.nim
1 {.push raises: [], gcsafe.} 2 3 import 4 std/[typetraits, tables], 5 stew/shims/macros, 6 serialization, 7 "."/[codec, internal, types] 8 9 func computeObjectSize*[T: object](value: T): int 10 11 func computeFieldSize( 12 fieldNum: int, fieldVal: auto, ProtoType: type UnsupportedType, 13 _: static bool) = 14 # TODO turn this into an extension point 15 unsupportedProtoType ProtoType.FieldType, ProtoType.RootType, ProtoType.fieldName 16 17 func computeFieldSize[T: object and not PBOption]( 18 fieldNum: int, fieldVal: T, ProtoType: type pbytes, 19 skipDefault: static bool): int = 20 let 21 size = computeObjectSize(fieldVal) 22 23 when skipDefault: 24 if size == 0: 25 return 0 26 27 computeSize(FieldHeader.init(fieldNum, ProtoType.wireKind())) + 28 computeSize(puint64(size)) + 29 size 30 31 proc computeFieldSize*[T: not object]( 32 fieldNum: int, fieldVal: T, 33 ProtoType: type SomeScalar, skipDefault: static bool): int = 34 when skipDefault: 35 const def = default(typeof(fieldVal)) 36 if fieldVal == def: 37 return 38 39 computeSize(fieldNum, ProtoType(fieldVal)) 40 41 proc computeFieldSize*( 42 fieldNum: int, fieldVal: PBOption, ProtoType: type, 43 skipDefault: static bool): int = 44 if fieldVal.isSome(): # TODO required field checking 45 computeFieldSize(fieldNum, fieldVal.get(), ProtoType, skipDefault) 46 else: 47 0 48 49 when defined(ConformanceTest): 50 proc computeFieldSize*[T]( 51 fieldNum: int, fieldVal: ref T, 52 ProtoType: type pbytes, skipDefault: static bool): int = 53 if not fieldVal.isNil(): 54 computeFieldSize(fieldNum, fieldVal[], ProtoType, skipDefault) 55 else: 56 0 57 58 proc writeField[T: enum]( 59 stream: OutputStream, fieldNum: int, fieldVal: T, ProtoType: type) = 60 when 0 notin T: 61 {.fatal: $T & " definition must contain a constant that maps to zero".} 62 stream.writeField(fieldNum, pint32(fieldVal.ord())) 63 64 proc computeFieldSize*[K, V]( 65 fieldNum: int, fieldVal: Table[K, V], ProtoType: type pbytes, 66 skipDefault: static bool): int = 67 tableObject(TableObject, K, V) 68 for k, v in fieldVal.pairs(): 69 let tmp = TableObject(key: k, value: v) 70 result += computeFieldSize(fieldNum, tmp, ProtoType, false) 71 72 proc computeSizePacked*[T: not byte, ProtoType: SomePrimitive]( 73 values: openArray[T], _: type ProtoType): int = 74 const canCopyMem = 75 ProtoType is SomeFixed32 or ProtoType is SomeFixed64 or ProtoType is pbool 76 when canCopyMem: 77 values.len() * sizeof(T) 78 else: 79 var total = 0 80 for item in values: 81 total += computeSize(ProtoType(item)) 82 total 83 84 proc computeFieldSizePacked*[ProtoType: SomePrimitive]( 85 field: int, values: openArray, _: type ProtoType): int = 86 # Packed encoding uses a length-delimited field byte length of the sum of the 87 # byte lengths of each field followed by the header-free contents 88 let 89 dataSize = computeSizePacked(values, ProtoType) 90 91 computeSize(FieldHeader.init(field, WireKind.LengthDelim)) + 92 computeSize(puint64(dataSize)) + 93 dataSize 94 95 func computeObjectSize*[T: object](value: T): int = 96 const 97 isProto2: bool = T.isProto2() 98 isProto3: bool = T.isProto3() 99 static: 100 doAssert isProto2 xor isProto3 101 102 var total = 0 103 enumInstanceSerializedFields(value, fieldName, fieldVal): 104 const 105 fieldNum = T.fieldNumberOf(fieldName) 106 107 type 108 FlatType = flatType(fieldVal) 109 110 protoType(ProtoType, T, FlatType, fieldName) 111 112 let fieldSize = when FlatType is seq and FlatType isnot seq[byte]: 113 const 114 isPacked = T.isPacked(fieldName).get(isProto3) 115 when isPacked and ProtoType is SomePrimitive: 116 computeFieldSizePacked(fieldNum, fieldVal, ProtoType) 117 else: 118 var dataSize = 0 119 for i in 0..<fieldVal.len: 120 # don't skip defaults so as to preserve length 121 dataSize += computeFieldSize(fieldNum, fieldVal[i], ProtoType, false) 122 dataSize 123 124 else: 125 computeFieldSize(fieldNum, fieldVal, ProtoType, isProto3) 126 127 total += fieldSize 128 129 total