1 module google.protobuf.encoding; 2 3 import std.algorithm : map; 4 import std.range : chain, ElementType, empty; 5 import std.traits : isArray, isAssociativeArray, isBoolean, isFloatingPoint, isIntegral, ValueType; 6 import google.protobuf.common; 7 import google.protobuf.internal; 8 9 auto toProtobuf(T)(T value) 10 if (isBoolean!T) 11 { 12 return value ? [cast(ubyte) 0x01] : [cast(ubyte) 0x00]; 13 } 14 15 unittest 16 { 17 import std.array : array; 18 19 assert(true.toProtobuf.array == [0x01]); 20 assert(false.toProtobuf.array == [0x00]); 21 } 22 23 auto toProtobuf(Wire wire = Wire.none, T)(T value) 24 if (isIntegral!T) 25 { 26 static if (wire == Wire.none) 27 { 28 return Varint(value); 29 } 30 else static if (wire == Wire.fixed) 31 { 32 return value.encodeFixed; 33 } 34 else static if (wire == Wire.zigzag) 35 { 36 return Varint(zigZag(value)); 37 } 38 else 39 { 40 assert(0, "Invalid wire encoding"); 41 } 42 } 43 44 unittest 45 { 46 import std.array : array; 47 48 assert(10.toProtobuf.array == [0x0a]); 49 assert((-1).toProtobuf.array == [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]); 50 assert((-1L).toProtobuf.array == [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]); 51 assert(0xffffffffffffffffUL.toProtobuf.array == [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]); 52 53 assert(1L.toProtobuf!(Wire.fixed).array == [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); 54 assert(1.toProtobuf!(Wire.fixed).array == [0x01, 0x00, 0x00, 0x00]); 55 assert((-1).toProtobuf!(Wire.fixed).array == [0xff, 0xff, 0xff, 0xff]); 56 assert(0xffffffffU.toProtobuf!(Wire.fixed).array == [0xff, 0xff, 0xff, 0xff]); 57 58 assert(1.toProtobuf!(Wire.zigzag).array == [0x02]); 59 assert((-1).toProtobuf!(Wire.zigzag).array == [0x01]); 60 assert(1L.toProtobuf!(Wire.zigzag).array == [0x02]); 61 assert((-1L).toProtobuf!(Wire.zigzag).array == [0x01]); 62 } 63 64 auto toProtobuf(T)(T value) 65 if (isFloatingPoint!T) 66 { 67 return value.encodeFixed; 68 } 69 70 unittest 71 { 72 import std.array : array; 73 74 assert((0.0).toProtobuf.array == [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); 75 assert((0.0f).toProtobuf.array == [0x00, 0x00, 0x00, 0x00]); 76 } 77 78 auto toProtobuf(T)(T value) 79 if (is(T == string) || is(T == bytes)) 80 { 81 return chain(Varint(value.length), cast(ubyte[]) value); 82 } 83 84 unittest 85 { 86 import std.array : array; 87 88 assert("abc".toProtobuf.array == [0x03, 'a', 'b', 'c']); 89 assert("".toProtobuf.array == [0x00]); 90 assert((cast(bytes) [1, 2, 3]).toProtobuf.array == [0x03, 1, 2, 3]); 91 assert((cast(bytes) []).toProtobuf.array == [0x00]); 92 } 93 94 auto toProtobuf(Wire wire = Wire.none, T)(T value) 95 if (isArray!T && !is(T == string) && !is(T == bytes)) 96 { 97 import std.range : hasLength; 98 99 static assert(hasLength!T, "Cannot encode array with unknown length"); 100 101 static if (wire == Wire.none) 102 { 103 auto result = value.map!(a => a.toProtobuf).sizedJoiner; 104 } 105 else 106 { 107 static assert(isIntegral!(ElementType!T), "Cannot specify wire format for non-integral arrays"); 108 109 auto result = value.map!(a => a.toProtobuf!wire).sizedJoiner; 110 } 111 112 return chain(Varint(result.length), result); 113 } 114 115 unittest 116 { 117 import std.array : array; 118 119 assert([false, false, true].toProtobuf.array == [0x03, 0x00, 0x00, 0x01]); 120 assert([1, 2].toProtobuf!(Wire.fixed).array == [0x08, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]); 121 assert([1, 2].toProtobuf.array == [0x02, 0x01, 0x02]); 122 } 123 124 auto toProtobuf(T)(T value) 125 if (is(T == class) || is(T == struct)) 126 { 127 import std.meta : AliasSeq; 128 import std.traits : hasMember; 129 130 static if (hasMember!(T, "toProtobuf")) 131 { 132 return value.toProtobuf; 133 } 134 else static if (is(Message!T.fields == AliasSeq!())) 135 { 136 return cast(ubyte[]) null; 137 } 138 else 139 { 140 import std.algorithm : joiner; 141 import std.array : array; 142 143 enum fieldExpressions = [Message!T.fieldNames] 144 .map!(a => "value.toProtobufByField!(T." ~ a ~ ")") 145 .joiner(", ") 146 .array; 147 enum resultExpression = "chain(" ~ fieldExpressions ~ ")"; 148 149 static if (isRecursive!T) 150 { 151 static if (is(T == class)) 152 { 153 if (value is null) 154 return cast(SizedRange!ubyte) sizedRangeObject(cast(ubyte[]) null); 155 } 156 157 return cast(SizedRange!ubyte) mixin(resultExpression).sizedRangeObject; 158 } 159 else 160 { 161 static if (is(T == class)) 162 { 163 if (value is null) 164 return typeof(mixin(resultExpression)).init; 165 } 166 167 return mixin(resultExpression); 168 } 169 } 170 } 171 172 unittest 173 { 174 import std.array : array; 175 176 class Foo 177 { 178 @Proto(1) int bar = protoDefaultValue!int; 179 @Proto(3) bool qux = protoDefaultValue!bool; 180 @Proto(2, Wire.fixed) long baz = protoDefaultValue!long; 181 @Proto(4) string quux = protoDefaultValue!string; 182 183 @Proto(5) Foo recursion = protoDefaultValue!Foo; 184 } 185 186 Foo foo; 187 assert(foo.toProtobuf.empty); 188 foo = new Foo; 189 assert(foo.toProtobuf.empty); 190 foo.bar = 5; 191 foo.baz = 1; 192 foo.qux = true; 193 assert(foo.toProtobuf.array == [0x08, 0x05, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01]); 194 } 195 196 unittest 197 { 198 import std.array : array; 199 200 struct EmptyMessage 201 { 202 } 203 204 EmptyMessage emptyMessage; 205 assert(emptyMessage.toProtobuf.empty); 206 } 207 208 unittest 209 { 210 struct Foo 211 { 212 @Proto(1) int bar = protoDefaultValue!int; 213 214 auto toProtobuf() 215 { 216 return [0x08, 0x00]; 217 } 218 } 219 220 Foo foo; 221 assert(foo.toProtobuf == [0x08, 0x00]); 222 } 223 224 private static auto toProtobufByField(alias field, T)(T message) 225 { 226 import std.meta : Alias; 227 228 static assert(is(Alias!(__traits(parent, field)) == T), "Field and message are different types"); 229 static assert(validateProto!(protoByField!field, typeof(field))); 230 enum proto = protoByField!field; 231 enum fieldName = __traits(identifier, field); 232 enum fieldInstanceName = "message." ~ fieldName; 233 234 static if (isOneof!field) 235 { 236 auto oneofCase = __traits(getMember, message, oneofCaseFieldName!field); 237 enum fieldCase = "T." ~ typeof(oneofCase).stringof ~ "." ~ oneofAccessorName!field; 238 239 if (oneofCase != mixin(fieldCase)) 240 return emptySizedRange!(typeof(mixin(fieldInstanceName).toProtobufByProto!proto)); 241 } 242 else 243 { 244 if (mixin(fieldInstanceName) == protoDefaultValue!(typeof(field))) 245 return emptySizedRange!(typeof(mixin(fieldInstanceName).toProtobufByProto!proto)); 246 } 247 248 return mixin(fieldInstanceName).toProtobufByProto!proto; 249 } 250 251 unittest 252 { 253 import std.array : array; 254 import std.typecons : Yes; 255 256 struct Foo 257 { 258 @Proto(1) int f10 = 10; 259 @Proto(16) int f11 = 10; 260 @Proto(2048) bool f12 = true; 261 @Proto(262144) bool f13 = true; 262 263 @Proto(20) bool f20 = false; 264 @Proto(21) int f21 = 0; 265 @Proto(22, Wire.fixed) int f22 = 0; 266 @Proto(23, Wire.zigzag) int f23 = 0; 267 @Proto(24) long f24 = 0L; 268 @Proto(25) double f25 = 0.0; 269 @Proto(26) string f26 = ""; 270 @Proto(27) bytes f27 = []; 271 272 @Proto(30) int[] f30 = [1, 2]; 273 @Proto(31, Wire.none, Yes.packed) int[] f31 = [1, 2]; 274 @Proto(32, Wire.none, Yes.packed) int[] f32 = [128, 2]; 275 } 276 277 Foo foo; 278 279 assert(foo.toProtobufByField!(Foo.f10).array == [0x08, 0x0a]); 280 assert(foo.toProtobufByField!(Foo.f11).array == [0x80, 0x01, 0x0a]); 281 assert(foo.toProtobufByField!(Foo.f12).array == [0x80, 0x80, 0x01, 0x01]); 282 assert(foo.toProtobufByField!(Foo.f13).array == [0x80, 0x80, 0x80, 0x01, 0x01]); 283 284 assert(foo.toProtobufByField!(Foo.f20).empty); 285 assert(foo.toProtobufByField!(Foo.f21).empty); 286 assert(foo.toProtobufByField!(Foo.f22).empty); 287 assert(foo.toProtobufByField!(Foo.f23).empty); 288 assert(foo.toProtobufByField!(Foo.f24).empty); 289 assert(foo.toProtobufByField!(Foo.f25).empty); 290 assert(foo.toProtobufByField!(Foo.f26).empty); 291 assert(foo.toProtobufByField!(Foo.f27).empty); 292 293 assert(foo.toProtobufByField!(Foo.f30).array == [0xf0, 0x01, 0x01, 0xf0, 0x01, 0x02]); 294 assert(foo.toProtobufByField!(Foo.f31).array == [0xfa, 0x01, 0x02, 0x01, 0x02]); 295 assert(foo.toProtobufByField!(Foo.f32).array == [0x82, 0x02, 0x03, 0x80, 0x01, 0x02]); 296 } 297 298 private auto toProtobufByProto(Proto proto, T)(T value) 299 if (isBoolean!T || 300 isFloatingPoint!T || 301 is(T == string) || 302 is(T == bytes) || 303 (isArray!T && proto.packed)) 304 { 305 static assert(validateProto!(proto, T)); 306 307 return chain(encodeTag!(proto, T), value.toProtobuf); 308 } 309 310 private auto toProtobufByProto(Proto proto, T)(T value) 311 if (isIntegral!T) 312 { 313 static assert(validateProto!(proto, T)); 314 315 enum wire = proto.wire; 316 return chain(encodeTag!(proto, T), value.toProtobuf!wire); 317 } 318 319 private auto toProtobufByProto(Proto proto, T)(T value) 320 if (isArray!T && !proto.packed && !is(T == string) && !is(T == bytes)) 321 { 322 static assert(validateProto!(proto, T)); 323 324 enum elementProto = Proto(proto.tag, proto.wire); 325 return value 326 .map!(a => a.toProtobufByProto!elementProto) 327 .sizedJoiner; 328 } 329 330 private auto toProtobufByProto(Proto proto, T)(T value) 331 if (isAssociativeArray!T) 332 { 333 static assert(validateProto!(proto, T)); 334 335 enum keyProto = Proto(MapFieldTag.key, keyWireToWire(proto.wire)); 336 enum valueProto = Proto(MapFieldTag.value, valueWireToWire(proto.wire)); 337 338 return value 339 .byKeyValue 340 .map!(a => chain(a.key.toProtobufByProto!keyProto, a.value.toProtobufByProto!valueProto)) 341 .map!(a => chain(encodeTag!(proto, T), Varint(a.length), a)) 342 .sizedJoiner; 343 } 344 345 unittest 346 { 347 import std.array : array; 348 import std.typecons : Yes; 349 350 assert([1, 2].toProtobufByProto!(Proto(1)).array == [0x08, 0x01, 0x08, 0x02]); 351 assert((int[]).init.toProtobufByProto!(Proto(1)).empty); 352 assert([1, 2].toProtobufByProto!(Proto(1, Wire.none, Yes.packed)).array == [0x0a, 0x02, 0x01, 0x02]); 353 assert([128, 2].toProtobufByProto!(Proto(1, Wire.none, Yes.packed)).array == [0x0a, 0x03, 0x80, 0x01, 0x02]); 354 assert((int[]).init.toProtobufByProto!(Proto(1, Wire.none, Yes.packed)).array == [0x0a, 0x00]); 355 356 assert((int[int]).init.toProtobufByProto!(Proto(1)).empty); 357 assert([1: 2].toProtobufByProto!(Proto(1)).array == [0x0a, 0x04, 0x08, 0x01, 0x10, 0x02]); 358 assert([1: 2].toProtobufByProto!(Proto(1, Wire.fixedValue)).array == 359 [0x0a, 0x07, 0x08, 0x01, 0x15, 0x02, 0x00, 0x00, 0x00]); 360 } 361 362 private auto toProtobufByProto(Proto proto, T)(T value) 363 if (is(T == class) || is(T == struct)) 364 { 365 static assert(validateProto!(proto, T)); 366 367 auto encodedValue = value.toProtobuf; 368 auto result = chain(encodeTag!(proto, T), Varint(encodedValue.length), encodedValue); 369 370 static if (isRecursive!T) 371 { 372 return cast(SizedRange!ubyte) result.sizedRangeObject; 373 } 374 else 375 { 376 return result; 377 } 378 }