/
field.js
284 lines (248 loc) · 7.85 KB
/
field.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
"use strict";
module.exports = Field;
Field.className = "Field";
var ReflectionObject = require("./object");
/** @alias Field.prototype */
var FieldPrototype = ReflectionObject.extend(Field);
var Enum = require("./enum"),
types = require("./types"),
util = require("./util");
var Type, // cyclic
MapField; // cyclic
var _TypeError = util._TypeError;
/**
* Constructs a new message field instance. Note that {@link MapField|map fields} have their own class.
* @classdesc Reflected message field.
* @extends ReflectionObject
* @constructor
* @param {string} name Unique name within its namespace
* @param {number} id Unique id within its namespace
* @param {string} type Value type
* @param {string|Object} [rule="optional"] Field rule
* @param {string|Object} [extend] Extended type if different from parent
* @param {Object} [options] Declared options
*/
function Field(name, id, type, rule, extend, options) {
if (util.isObject(rule)) {
options = rule;
rule = extend = undefined;
} else if (util.isObject(extend)) {
options = extend;
extend = undefined;
}
ReflectionObject.call(this, name, options);
if (!util.isInteger(id) || id < 0)
throw _TypeError("id", "a non-negative integer");
if (!util.isString(type))
throw _TypeError("type");
if (extend !== undefined && !util.isString(extend))
throw _TypeError("extend");
if (rule !== undefined && !/^required|optional|repeated$/.test(rule = rule.toString().toLowerCase()))
throw _TypeError("rule", "a valid rule string");
/**
* Field rule, if any.
* @type {string|undefined}
*/
this.rule = rule && rule !== "optional" ? rule : undefined; // toJSON
/**
* Field type.
* @type {string}
*/
this.type = type; // toJSON
/**
* Unique field id.
* @type {number}
*/
this.id = id; // toJSON, marker
/**
* Extended type if different from parent.
* @type {string|undefined}
*/
this.extend = extend || undefined; // toJSON
/**
* Whether this field is required.
* @type {boolean}
*/
this.required = rule === "required";
/**
* Whether this field is optional.
* @type {boolean}
*/
this.optional = !this.required;
/**
* Whether this field is repeated.
* @type {boolean}
*/
this.repeated = rule === "repeated";
/**
* Whether this field is a map or not.
* @type {boolean}
*/
this.map = false;
/**
* Message this field belongs to.
* @type {?Type}
*/
this.message = null;
/**
* OneOf this field belongs to, if any,
* @type {?OneOf}
*/
this.partOf = null;
/**
* The field's default value. Only relevant when working with proto2.
* @type {*}
*/
this.defaultValue = null;
/**
* Whether this field's value should be treated as a long.
* @type {boolean}
*/
this.long = util.Long ? types.long[type] !== undefined : false;
/**
* Resolved type if not a basic type.
* @type {?(Type|Enum)}
*/
this.resolvedType = null;
/**
* Sister-field within the extended type if a declaring extension field.
* @type {?Field}
*/
this.extensionField = null;
/**
* Sister-field within the declaring namespace if an extended field.
* @type {?Field}
*/
this.declaringField = null;
/**
* Internally remembers whether this field is packed.
* @type {?boolean}
* @private
*/
this._packed = null;
}
util.props(FieldPrototype, {
/**
* Determines whether this field is packed. Only relevant when repeated and working with proto2.
* @name Field#packed
* @type {boolean}
* @readonly
*/
packed: {
get: FieldPrototype.isPacked = function() {
if (this._packed === null)
this._packed = this.getOption("packed") !== false;
return this._packed;
}
}
/**
* Determines whether this field is packed. This is an alias of {@link Field#packed}'s getter for use within non-ES5 environments.
* @name Field#isPacked
* @function
* @returns {boolean}
*/
});
/**
* @override
*/
FieldPrototype.setOption = function setOption(name, value, ifNotSet) {
if (name === "packed")
this._packed = null;
return ReflectionObject.prototype.setOption.call(this, name, value, ifNotSet);
};
/**
* Tests if the specified JSON object describes a field.
* @param {*} json Any JSON object to test
* @returns {boolean} `true` if the object describes a field
*/
Field.testJSON = function testJSON(json) {
return Boolean(json && json.id !== undefined);
};
/**
* Constructs a field from JSON.
* @param {string} name Field name
* @param {Object} json JSON object
* @returns {Field} Created field
* @throws {TypeError} If arguments are invalid
*/
Field.fromJSON = function fromJSON(name, json) {
if (json.keyType !== undefined) {
if (!MapField)
MapField = require("./mapfield");
return MapField.fromJSON(name, json);
}
return new Field(name, json.id, json.type, json.rule, json.extend, json.options);
};
/**
* @override
*/
FieldPrototype.toJSON = function toJSON() {
return {
rule : this.rule !== "optional" && this.rule || undefined,
type : this.type,
id : this.id,
extend : this.extend,
options : this.options
};
};
/**
* Resolves this field's type references.
* @returns {Field} `this`
* @throws {Error} If any reference cannot be resolved
*/
FieldPrototype.resolve = function resolve() {
if (this.resolved)
return this;
var typeDefault = types.defaults[this.type];
// if not a basic type, resolve it
if (typeDefault === undefined) {
var resolved = this.parent.lookup(this.type);
if (!Type)
Type = require("./type");
if (resolved instanceof Type) {
this.resolvedType = resolved;
typeDefault = null;
} else if (resolved instanceof Enum) {
this.resolvedType = resolved;
typeDefault = 0;
} else
throw Error("unresolvable field type: " + this.type);
}
// when everything is resolved determine the default value
var optionDefault;
if (this.map)
this.defaultValue = {};
else if (this.repeated)
this.defaultValue = [];
else if (this.options && (optionDefault = this.options["default"]) !== undefined) // eslint-disable-line dot-notation
this.defaultValue = optionDefault;
else
this.defaultValue = typeDefault;
if (this.long)
this.defaultValue = util.Long.fromValue(this.defaultValue);
return ReflectionObject.prototype.resolve.call(this);
};
/**
* Converts a field value to JSON using the specified options. Note that this method does not account for repeated fields and must be called once for each repeated element instead.
* @param {*} value Field value
* @param {Object.<string,*>} [options] Conversion options
* @returns {*} Converted value
* @see {@link Message#asJSON}
*/
FieldPrototype.jsonConvert = function(value, options) {
if (options) {
if (this.resolvedType instanceof Enum && options["enum"] === String) // eslint-disable-line dot-notation
return this.resolvedType.getValuesById()[value];
else if (this.long && options.long)
return options.long === Number
? typeof value === "number"
? value
: util.Long.fromValue(value).toNumber()
: util.Long.fromValue(value, this.type.charAt(0) === "u").toString();
else if (options.bytes && this.type === "bytes")
return options.bytes === Array
? Array.prototype.slice.call(value)
: util.base64.encode(value, 0, value.length);
}
return value;
};