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