Skip to content

Commit 6c4d307

Browse files
feat: proto3 optional support (#1584)
Co-authored-by: Benjamin E. Coe <[email protected]>
1 parent 94e9ef0 commit 6c4d307

File tree

4 files changed

+71
-5
lines changed

4 files changed

+71
-5
lines changed

cli/targets/static.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,8 @@ function buildType(ref, type) {
394394
if (config.comments) {
395395
push("");
396396
var jsType = toJsType(field);
397-
if (field.optional && !field.map && !field.repeated && field.resolvedType instanceof Type)
397+
if (field.optional && !field.map && !field.repeated && field.resolvedType instanceof Type ||
398+
field.options && field.options.proto3_optional)
398399
jsType = jsType + "|null|undefined";
399400
pushComment([
400401
field.comment || type.name + " " + field.name + ".",
@@ -410,6 +411,9 @@ function buildType(ref, type) {
410411
push(escapeName(type.name) + ".prototype" + prop + " = $util.emptyArray;"); // overwritten in constructor
411412
else if (field.map)
412413
push(escapeName(type.name) + ".prototype" + prop + " = $util.emptyObject;"); // overwritten in constructor
414+
else if (field.options && field.options.proto3_optional) {
415+
push(escapeName(type.name) + ".prototype" + prop + " = null;"); // do not set default value for proto3 optional fields
416+
}
413417
else if (field.long)
414418
push(escapeName(type.name) + ".prototype" + prop + " = $util.Long ? $util.Long.fromBits("
415419
+ JSON.stringify(field.typeDefault.low) + ","

src/field.js

+3
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ function Field(name, id, type, rule, extend, options, comment) {
8282
* Field rule, if any.
8383
* @type {string|undefined}
8484
*/
85+
if (rule === "proto3_optional") {
86+
rule = "optional";
87+
}
8588
this.rule = rule && rule !== "optional" ? rule : undefined; // toJSON
8689

8790
/**

src/parse.js

+37-4
Original file line numberDiff line numberDiff line change
@@ -316,11 +316,19 @@ function parse(source, root, options) {
316316
break;
317317

318318
case "required":
319-
case "optional":
320319
case "repeated":
321320
parseField(type, token);
322321
break;
323322

323+
case "optional":
324+
/* istanbul ignore if */
325+
if (isProto3) {
326+
parseField(type, "proto3_optional");
327+
} else {
328+
parseField(type, "optional");
329+
}
330+
break;
331+
324332
case "oneof":
325333
parseOneOf(type, token);
326334
break;
@@ -379,7 +387,16 @@ function parse(source, root, options) {
379387
}, function parseField_line() {
380388
parseInlineOptions(field);
381389
});
382-
parent.add(field);
390+
391+
if (rule === "proto3_optional") {
392+
// for proto3 optional fields, we create a single-member Oneof to mimic "optional" behavior
393+
var oneof = new OneOf("_" + name);
394+
field.setOption("proto3_optional", true);
395+
oneof.add(field);
396+
parent.add(oneof);
397+
} else {
398+
parent.add(field);
399+
}
383400

384401
// JSON defaults to packed=true if not set so we have to set packed=false explicity when
385402
// parsing proto2 descriptors without the option, where applicable. This must be done for
@@ -413,11 +430,19 @@ function parse(source, root, options) {
413430
break;
414431

415432
case "required":
416-
case "optional":
417433
case "repeated":
418434
parseField(type, token);
419435
break;
420436

437+
case "optional":
438+
/* istanbul ignore if */
439+
if (isProto3) {
440+
parseField(type, "proto3_optional");
441+
} else {
442+
parseField(type, "optional");
443+
}
444+
break;
445+
421446
/* istanbul ignore next */
422447
default:
423448
throw illegal(token); // there are no groups with proto3 semantics
@@ -699,10 +724,18 @@ function parse(source, root, options) {
699724

700725
case "required":
701726
case "repeated":
702-
case "optional":
703727
parseField(parent, token, reference);
704728
break;
705729

730+
case "optional":
731+
/* istanbul ignore if */
732+
if (isProto3) {
733+
parseField(parent, "proto3_optional", reference);
734+
} else {
735+
parseField(parent, "optional", reference);
736+
}
737+
break;
738+
706739
default:
707740
/* istanbul ignore if */
708741
if (!isProto3 || !typeRefRe.test(token))

tests/comp_optional.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
var tape = require("tape");
2+
3+
var protobuf = require("..");
4+
5+
var proto = "syntax = \"proto3\";\
6+
\
7+
message Message {\
8+
int32 regular_int32 = 1;\
9+
optional int32 optional_int32 = 2;\
10+
oneof _oneof_int32 {\
11+
int32 oneof_int32 = 3;\
12+
}\
13+
}\
14+
";
15+
16+
tape.test("proto3 optional", function(test) {
17+
var root = protobuf.parse(proto).root;
18+
19+
var Message = root.lookup("Message");
20+
test.equal(Message.fields.optionalInt32.optional, true);
21+
test.equal(Message.fields.optionalInt32.options.proto3_optional, true);
22+
test.equal(Message.oneofs._optionalInt32.name, '_optionalInt32');
23+
test.deepEqual(Message.oneofs._optionalInt32.oneof, ['optionalInt32']);
24+
25+
test.end();
26+
});

0 commit comments

Comments
 (0)