2 * Constructs a new Element implementation that checks and converts values for a
\r
3 * particular field type, as appropriate.
\r
5 * An Element represents a single value: either the value of a singular field,
\r
6 * or a value contained in one entry of a repeated field or map field. This
\r
7 * class does not implement these higher-level concepts; it only encapsulates
\r
8 * the low-level typechecking and conversion.
\r
10 * @exports ProtoBuf.Reflect.Element
\r
11 * @param {{name: string, wireType: number}} type Resolved data type
\r
12 * @param {ProtoBuf.Reflect.T|null} resolvedType Resolved type, if relevant
\r
13 * (e.g. submessage field).
\r
14 * @param {boolean} isMapKey Is this element a Map key? The value will be
\r
15 * converted to string form if so.
\r
16 * @param {string} syntax Syntax level of defining message type, e.g.,
\r
18 * @param {string} name Name of the field containing this element (for error
\r
22 var Element = function(type, resolvedType, isMapKey, syntax, name) {
\r
25 * Element type, as a string (e.g., int32).
\r
26 * @type {{name: string, wireType: number}}
\r
31 * Element type reference to submessage or enum definition, if needed.
\r
32 * @type {ProtoBuf.Reflect.T|null}
\r
34 this.resolvedType = resolvedType;
\r
37 * Element is a map key.
\r
40 this.isMapKey = isMapKey;
\r
43 * Syntax level of defining message type, e.g., proto2 or proto3.
\r
46 this.syntax = syntax;
\r
49 * Name of the field containing this element (for error messages)
\r
54 if (isMapKey && ProtoBuf.MAP_KEY_TYPES.indexOf(type) < 0)
\r
55 throw Error("Invalid map key type: " + type.name);
\r
58 var ElementPrototype = Element.prototype;
\r
61 * Obtains a (new) default value for the specified type.
\r
62 * @param type {string|{name: string, wireType: number}} Field type
\r
63 * @returns {*} Default value
\r
66 function mkDefault(type) {
\r
67 if (typeof type === 'string')
\r
68 type = ProtoBuf.TYPES[type];
\r
69 if (typeof type.defaultValue === 'undefined')
\r
70 throw Error("default value for type "+type.name+" is not supported");
\r
71 if (type == ProtoBuf.TYPES["bytes"])
\r
72 return new ByteBuffer(0);
\r
73 return type.defaultValue;
\r
77 * Returns the default value for this field in proto3.
\r
79 * @param type {string|{name: string, wireType: number}} the field type
\r
80 * @returns {*} Default value
\r
82 Element.defaultFieldValue = mkDefault;
\r
85 * Makes a Long from a value.
\r
86 * @param {{low: number, high: number, unsigned: boolean}|string|number} value Value
\r
87 * @param {boolean=} unsigned Whether unsigned or not, defaults to reuse it from Long-like objects or to signed for
\r
88 * strings and numbers
\r
90 * @throws {Error} If the value cannot be converted to a Long
\r
93 function mkLong(value, unsigned) {
\r
94 if (value && typeof value.low === 'number' && typeof value.high === 'number' && typeof value.unsigned === 'boolean'
\r
95 && value.low === value.low && value.high === value.high)
\r
96 return new ProtoBuf.Long(value.low, value.high, typeof unsigned === 'undefined' ? value.unsigned : unsigned);
\r
97 if (typeof value === 'string')
\r
98 return ProtoBuf.Long.fromString(value, unsigned || false, 10);
\r
99 if (typeof value === 'number')
\r
100 return ProtoBuf.Long.fromNumber(value, unsigned || false);
\r
101 throw Error("not convertible to Long");
\r
104 ElementPrototype.toString = function() {
\r
105 return (this.name || '') + (this.isMapKey ? 'map' : 'value') + ' element';
\r
109 * Checks if the given value can be set for an element of this type (singular
\r
110 * field or one element of a repeated field or map).
\r
111 * @param {*} value Value to check
\r
112 * @return {*} Verified, maybe adjusted, value
\r
113 * @throws {Error} If the value cannot be verified for this element slot
\r
116 ElementPrototype.verifyValue = function(value) {
\r
118 function fail(val, msg) {
\r
119 throw Error("Illegal value for "+self.toString(true)+" of type "+self.type.name+": "+val+" ("+msg+")");
\r
121 switch (this.type) {
\r
123 case ProtoBuf.TYPES["int32"]:
\r
124 case ProtoBuf.TYPES["sint32"]:
\r
125 case ProtoBuf.TYPES["sfixed32"]:
\r
126 // Account for !NaN: value === value
\r
127 if (typeof value !== 'number' || (value === value && value % 1 !== 0))
\r
128 fail(typeof value, "not an integer");
\r
129 return value > 4294967295 ? value | 0 : value;
\r
132 case ProtoBuf.TYPES["uint32"]:
\r
133 case ProtoBuf.TYPES["fixed32"]:
\r
134 if (typeof value !== 'number' || (value === value && value % 1 !== 0))
\r
135 fail(typeof value, "not an integer");
\r
136 return value < 0 ? value >>> 0 : value;
\r
139 case ProtoBuf.TYPES["int64"]:
\r
140 case ProtoBuf.TYPES["sint64"]:
\r
141 case ProtoBuf.TYPES["sfixed64"]: {
\r
144 return mkLong(value, false);
\r
146 fail(typeof value, e.message);
\r
149 fail(typeof value, "requires Long.js");
\r
153 case ProtoBuf.TYPES["uint64"]:
\r
154 case ProtoBuf.TYPES["fixed64"]: {
\r
157 return mkLong(value, true);
\r
159 fail(typeof value, e.message);
\r
162 fail(typeof value, "requires Long.js");
\r
166 case ProtoBuf.TYPES["bool"]:
\r
167 if (typeof value !== 'boolean')
\r
168 fail(typeof value, "not a boolean");
\r
172 case ProtoBuf.TYPES["float"]:
\r
173 case ProtoBuf.TYPES["double"]:
\r
174 if (typeof value !== 'number')
\r
175 fail(typeof value, "not a number");
\r
178 // Length-delimited string
\r
179 case ProtoBuf.TYPES["string"]:
\r
180 if (typeof value !== 'string' && !(value && value instanceof String))
\r
181 fail(typeof value, "not a string");
\r
182 return ""+value; // Convert String object to string
\r
184 // Length-delimited bytes
\r
185 case ProtoBuf.TYPES["bytes"]:
\r
186 if (ByteBuffer.isByteBuffer(value))
\r
188 return ByteBuffer.wrap(value, "base64");
\r
190 // Constant enum value
\r
191 case ProtoBuf.TYPES["enum"]: {
\r
192 var values = this.resolvedType.getChildren(ProtoBuf.Reflect.Enum.Value);
\r
193 for (i=0; i<values.length; i++)
\r
194 if (values[i].name == value)
\r
195 return values[i].id;
\r
196 else if (values[i].id == value)
\r
197 return values[i].id;
\r
199 if (this.syntax === 'proto3') {
\r
200 // proto3: just make sure it's an integer.
\r
201 if (typeof value !== 'number' || (value === value && value % 1 !== 0))
\r
202 fail(typeof value, "not an integer");
\r
203 if (value > 4294967295 || value < 0)
\r
204 fail(typeof value, "not in range for uint32")
\r
207 // proto2 requires enum values to be valid.
\r
208 fail(value, "not a valid enum value");
\r
211 // Embedded message
\r
212 case ProtoBuf.TYPES["group"]:
\r
213 case ProtoBuf.TYPES["message"]: {
\r
214 if (!value || typeof value !== 'object')
\r
215 fail(typeof value, "object expected");
\r
216 if (value instanceof this.resolvedType.clazz)
\r
218 if (value instanceof ProtoBuf.Builder.Message) {
\r
219 // Mismatched type: Convert to object (see: https://github.com/dcodeIO/ProtoBuf.js/issues/180)
\r
221 for (var i in value)
\r
222 if (value.hasOwnProperty(i))
\r
226 // Else let's try to construct one from a key-value object
\r
227 return new (this.resolvedType.clazz)(value); // May throw for a hundred of reasons
\r
231 // We should never end here
\r
232 throw Error("[INTERNAL] Illegal value for "+this.toString(true)+": "+value+" (undefined type "+this.type+")");
\r
236 * Calculates the byte length of an element on the wire.
\r
237 * @param {number} id Field number
\r
238 * @param {*} value Field value
\r
239 * @returns {number} Byte length
\r
240 * @throws {Error} If the value cannot be calculated
\r
243 ElementPrototype.calculateLength = function(id, value) {
\r
244 if (value === null) return 0; // Nothing to encode
\r
245 // Tag has already been written
\r
247 switch (this.type) {
\r
248 case ProtoBuf.TYPES["int32"]:
\r
249 return value < 0 ? ByteBuffer.calculateVarint64(value) : ByteBuffer.calculateVarint32(value);
\r
250 case ProtoBuf.TYPES["uint32"]:
\r
251 return ByteBuffer.calculateVarint32(value);
\r
252 case ProtoBuf.TYPES["sint32"]:
\r
253 return ByteBuffer.calculateVarint32(ByteBuffer.zigZagEncode32(value));
\r
254 case ProtoBuf.TYPES["fixed32"]:
\r
255 case ProtoBuf.TYPES["sfixed32"]:
\r
256 case ProtoBuf.TYPES["float"]:
\r
258 case ProtoBuf.TYPES["int64"]:
\r
259 case ProtoBuf.TYPES["uint64"]:
\r
260 return ByteBuffer.calculateVarint64(value);
\r
261 case ProtoBuf.TYPES["sint64"]:
\r
262 return ByteBuffer.calculateVarint64(ByteBuffer.zigZagEncode64(value));
\r
263 case ProtoBuf.TYPES["fixed64"]:
\r
264 case ProtoBuf.TYPES["sfixed64"]:
\r
266 case ProtoBuf.TYPES["bool"]:
\r
268 case ProtoBuf.TYPES["enum"]:
\r
269 return ByteBuffer.calculateVarint32(value);
\r
270 case ProtoBuf.TYPES["double"]:
\r
272 case ProtoBuf.TYPES["string"]:
\r
273 n = ByteBuffer.calculateUTF8Bytes(value);
\r
274 return ByteBuffer.calculateVarint32(n) + n;
\r
275 case ProtoBuf.TYPES["bytes"]:
\r
276 if (value.remaining() < 0)
\r
277 throw Error("Illegal value for "+this.toString(true)+": "+value.remaining()+" bytes remaining");
\r
278 return ByteBuffer.calculateVarint32(value.remaining()) + value.remaining();
\r
279 case ProtoBuf.TYPES["message"]:
\r
280 n = this.resolvedType.calculate(value);
\r
281 return ByteBuffer.calculateVarint32(n) + n;
\r
282 case ProtoBuf.TYPES["group"]:
\r
283 n = this.resolvedType.calculate(value);
\r
284 return n + ByteBuffer.calculateVarint32((id << 3) | ProtoBuf.WIRE_TYPES.ENDGROUP);
\r
286 // We should never end here
\r
287 throw Error("[INTERNAL] Illegal value to encode in "+this.toString(true)+": "+value+" (unknown type)");
\r
291 * Encodes a value to the specified buffer. Does not encode the key.
\r
292 * @param {number} id Field number
\r
293 * @param {*} value Field value
\r
294 * @param {ByteBuffer} buffer ByteBuffer to encode to
\r
295 * @return {ByteBuffer} The ByteBuffer for chaining
\r
296 * @throws {Error} If the value cannot be encoded
\r
299 ElementPrototype.encodeValue = function(id, value, buffer) {
\r
300 if (value === null) return buffer; // Nothing to encode
\r
301 // Tag has already been written
\r
303 switch (this.type) {
\r
304 // 32bit signed varint
\r
305 case ProtoBuf.TYPES["int32"]:
\r
306 // "If you use int32 or int64 as the type for a negative number, the resulting varint is always ten bytes
\r
307 // long – it is, effectively, treated like a very large unsigned integer." (see #122)
\r
309 buffer.writeVarint64(value);
\r
311 buffer.writeVarint32(value);
\r
314 // 32bit unsigned varint
\r
315 case ProtoBuf.TYPES["uint32"]:
\r
316 buffer.writeVarint32(value);
\r
319 // 32bit varint zig-zag
\r
320 case ProtoBuf.TYPES["sint32"]:
\r
321 buffer.writeVarint32ZigZag(value);
\r
324 // Fixed unsigned 32bit
\r
325 case ProtoBuf.TYPES["fixed32"]:
\r
326 buffer.writeUint32(value);
\r
329 // Fixed signed 32bit
\r
330 case ProtoBuf.TYPES["sfixed32"]:
\r
331 buffer.writeInt32(value);
\r
334 // 64bit varint as-is
\r
335 case ProtoBuf.TYPES["int64"]:
\r
336 case ProtoBuf.TYPES["uint64"]:
\r
337 buffer.writeVarint64(value); // throws
\r
340 // 64bit varint zig-zag
\r
341 case ProtoBuf.TYPES["sint64"]:
\r
342 buffer.writeVarint64ZigZag(value); // throws
\r
345 // Fixed unsigned 64bit
\r
346 case ProtoBuf.TYPES["fixed64"]:
\r
347 buffer.writeUint64(value); // throws
\r
350 // Fixed signed 64bit
\r
351 case ProtoBuf.TYPES["sfixed64"]:
\r
352 buffer.writeInt64(value); // throws
\r
356 case ProtoBuf.TYPES["bool"]:
\r
357 if (typeof value === 'string')
\r
358 buffer.writeVarint32(value.toLowerCase() === 'false' ? 0 : !!value);
\r
360 buffer.writeVarint32(value ? 1 : 0);
\r
363 // Constant enum value
\r
364 case ProtoBuf.TYPES["enum"]:
\r
365 buffer.writeVarint32(value);
\r
369 case ProtoBuf.TYPES["float"]:
\r
370 buffer.writeFloat32(value);
\r
374 case ProtoBuf.TYPES["double"]:
\r
375 buffer.writeFloat64(value);
\r
378 // Length-delimited string
\r
379 case ProtoBuf.TYPES["string"]:
\r
380 buffer.writeVString(value);
\r
383 // Length-delimited bytes
\r
384 case ProtoBuf.TYPES["bytes"]:
\r
385 if (value.remaining() < 0)
\r
386 throw Error("Illegal value for "+this.toString(true)+": "+value.remaining()+" bytes remaining");
\r
387 var prevOffset = value.offset;
\r
388 buffer.writeVarint32(value.remaining());
\r
389 buffer.append(value);
\r
390 value.offset = prevOffset;
\r
393 // Embedded message
\r
394 case ProtoBuf.TYPES["message"]:
\r
395 var bb = new ByteBuffer().LE();
\r
396 this.resolvedType.encode(value, bb);
\r
397 buffer.writeVarint32(bb.offset);
\r
398 buffer.append(bb.flip());
\r
402 case ProtoBuf.TYPES["group"]:
\r
403 this.resolvedType.encode(value, buffer);
\r
404 buffer.writeVarint32((id << 3) | ProtoBuf.WIRE_TYPES.ENDGROUP);
\r
408 // We should never end here
\r
409 throw Error("[INTERNAL] Illegal value to encode in "+this.toString(true)+": "+value+" (unknown type)");
\r
415 * Decode one element value from the specified buffer.
\r
416 * @param {ByteBuffer} buffer ByteBuffer to decode from
\r
417 * @param {number} wireType The field wire type
\r
418 * @param {number} id The field number
\r
419 * @return {*} Decoded value
\r
420 * @throws {Error} If the field cannot be decoded
\r
423 ElementPrototype.decode = function(buffer, wireType, id) {
\r
424 if (wireType != this.type.wireType)
\r
425 throw Error("Unexpected wire type for element");
\r
428 switch (this.type) {
\r
429 // 32bit signed varint
\r
430 case ProtoBuf.TYPES["int32"]:
\r
431 return buffer.readVarint32() | 0;
\r
433 // 32bit unsigned varint
\r
434 case ProtoBuf.TYPES["uint32"]:
\r
435 return buffer.readVarint32() >>> 0;
\r
437 // 32bit signed varint zig-zag
\r
438 case ProtoBuf.TYPES["sint32"]:
\r
439 return buffer.readVarint32ZigZag() | 0;
\r
441 // Fixed 32bit unsigned
\r
442 case ProtoBuf.TYPES["fixed32"]:
\r
443 return buffer.readUint32() >>> 0;
\r
445 case ProtoBuf.TYPES["sfixed32"]:
\r
446 return buffer.readInt32() | 0;
\r
448 // 64bit signed varint
\r
449 case ProtoBuf.TYPES["int64"]:
\r
450 return buffer.readVarint64();
\r
452 // 64bit unsigned varint
\r
453 case ProtoBuf.TYPES["uint64"]:
\r
454 return buffer.readVarint64().toUnsigned();
\r
456 // 64bit signed varint zig-zag
\r
457 case ProtoBuf.TYPES["sint64"]:
\r
458 return buffer.readVarint64ZigZag();
\r
460 // Fixed 64bit unsigned
\r
461 case ProtoBuf.TYPES["fixed64"]:
\r
462 return buffer.readUint64();
\r
464 // Fixed 64bit signed
\r
465 case ProtoBuf.TYPES["sfixed64"]:
\r
466 return buffer.readInt64();
\r
469 case ProtoBuf.TYPES["bool"]:
\r
470 return !!buffer.readVarint32();
\r
472 // Constant enum value (varint)
\r
473 case ProtoBuf.TYPES["enum"]:
\r
474 // The following Builder.Message#set will already throw
\r
475 return buffer.readVarint32();
\r
478 case ProtoBuf.TYPES["float"]:
\r
479 return buffer.readFloat();
\r
482 case ProtoBuf.TYPES["double"]:
\r
483 return buffer.readDouble();
\r
485 // Length-delimited string
\r
486 case ProtoBuf.TYPES["string"]:
\r
487 return buffer.readVString();
\r
489 // Length-delimited bytes
\r
490 case ProtoBuf.TYPES["bytes"]: {
\r
491 nBytes = buffer.readVarint32();
\r
492 if (buffer.remaining() < nBytes)
\r
493 throw Error("Illegal number of bytes for "+this.toString(true)+": "+nBytes+" required but got only "+buffer.remaining());
\r
494 value = buffer.clone(); // Offset already set
\r
495 value.limit = value.offset+nBytes;
\r
496 buffer.offset += nBytes;
\r
500 // Length-delimited embedded message
\r
501 case ProtoBuf.TYPES["message"]: {
\r
502 nBytes = buffer.readVarint32();
\r
503 return this.resolvedType.decode(buffer, nBytes);
\r
507 case ProtoBuf.TYPES["group"]:
\r
508 return this.resolvedType.decode(buffer, -1, id);
\r
511 // We should never end here
\r
512 throw Error("[INTERNAL] Illegal decode type");
\r
516 * Converts a value from a string to the canonical element type.
\r
518 * Legal only when isMapKey is true.
\r
520 * @param {string} str The string value
\r
521 * @returns {*} The value
\r
523 ElementPrototype.valueFromString = function(str) {
\r
524 if (!this.isMapKey) {
\r
525 throw Error("valueFromString() called on non-map-key element");
\r
528 switch (this.type) {
\r
529 case ProtoBuf.TYPES["int32"]:
\r
530 case ProtoBuf.TYPES["sint32"]:
\r
531 case ProtoBuf.TYPES["sfixed32"]:
\r
532 case ProtoBuf.TYPES["uint32"]:
\r
533 case ProtoBuf.TYPES["fixed32"]:
\r
534 return this.verifyValue(parseInt(str));
\r
536 case ProtoBuf.TYPES["int64"]:
\r
537 case ProtoBuf.TYPES["sint64"]:
\r
538 case ProtoBuf.TYPES["sfixed64"]:
\r
539 case ProtoBuf.TYPES["uint64"]:
\r
540 case ProtoBuf.TYPES["fixed64"]:
\r
541 // Long-based fields support conversions from string already.
\r
542 return this.verifyValue(str);
\r
544 case ProtoBuf.TYPES["bool"]:
\r
545 return str === "true";
\r
547 case ProtoBuf.TYPES["string"]:
\r
548 return this.verifyValue(str);
\r
550 case ProtoBuf.TYPES["bytes"]:
\r
551 return ByteBuffer.fromBinary(str);
\r
556 * Converts a value from the canonical element type to a string.
\r
558 * It should be the case that `valueFromString(valueToString(val))` returns
\r
559 * a value equivalent to `verifyValue(val)` for every legal value of `val`
\r
560 * according to this element type.
\r
562 * This may be used when the element must be stored or used as a string,
\r
563 * e.g., as a map key on an Object.
\r
565 * Legal only when isMapKey is true.
\r
567 * @param {*} val The value
\r
568 * @returns {string} The string form of the value.
\r
570 ElementPrototype.valueToString = function(value) {
\r
571 if (!this.isMapKey) {
\r
572 throw Error("valueToString() called on non-map-key element");
\r
575 if (this.type === ProtoBuf.TYPES["bytes"]) {
\r
576 return value.toString("binary");
\r
578 return value.toString();
\r