Skip to content

Commit

Permalink
Support extensions (#666)
Browse files Browse the repository at this point in the history
  • Loading branch information
timostamm authored Jan 18, 2024
1 parent 5ee26a1 commit 5a226ed
Show file tree
Hide file tree
Showing 93 changed files with 11,479 additions and 704 deletions.
2 changes: 1 addition & 1 deletion packages/protobuf-bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ server would usually do.

| code generator | bundle size | minified | compressed |
|---------------------|------------------------:|-----------------------:|-------------------:|
| protobuf-es | 91,410 b | 38,462 b | 9,870 b |
| protobuf-es | 95,723 b | 40,853 b | 10,615 b |
| protobuf-javascript | 394,384 b | 288,654 b | 45,122 b |
3 changes: 0 additions & 3 deletions packages/protobuf-conformance/failing_tests_with_bigint.txt
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
# proto2 extensions are not implemented
# WARNING, test=Recommended.Proto2.JsonInput.FieldNameExtension.Validator: Expected JSON payload but got type 1. request=json_payload: "{\n \"[protobuf_test_messages.proto2.extension_int32]\": 1\n }" requested_output_format: JSON message_type: "protobuf_test_messages.proto2.TestAllTypesProto2" test_category: JSON_TEST, response=parse_error: "Error: cannot decode message protobuf_test_messages.proto2.TestAllTypesProto2 from JSON: key \"[protobuf_test_messages.proto2.extension_int32]\" is unknown"
Recommended.Proto2.JsonInput.FieldNameExtension.Validator
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
# proto2 extensions are not implemented
# WARNING, test=Recommended.Proto2.JsonInput.FieldNameExtension.Validator: Expected JSON payload but got type 1. request=json_payload: "{\n \"[protobuf_test_messages.proto2.extension_int32]\": 1\n }" requested_output_format: JSON message_type: "protobuf_test_messages.proto2.TestAllTypesProto2" test_category: JSON_TEST, response=parse_error: "Error: cannot decode message protobuf_test_messages.proto2.TestAllTypesProto2 from JSON: key \"[protobuf_test_messages.proto2.extension_int32]\" is unknown"
Recommended.Proto2.JsonInput.FieldNameExtension.Validator

# Without BigInt support, a JavaScript runtime can only safely represent values
# in the range from Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER.
# That means number values in JSON will be unsafe as well. If you plan to use
Expand Down
6 changes: 5 additions & 1 deletion packages/protobuf-conformance/src/conformance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import {
WireFormat,
} from "./gen/conformance/conformance_pb.js";
import { TestAllTypesProto3 } from "./gen/google/protobuf/test_messages_proto3_pb.js";
import { TestAllTypesProto2 } from "./gen/google/protobuf/test_messages_proto2_pb.js";
import {
extension_int32,
TestAllTypesProto2,
} from "./gen/google/protobuf/test_messages_proto2_pb.js";
import type { MessageType } from "@bufbuild/protobuf";
import {
Any,
Expand All @@ -45,6 +48,7 @@ const registry = createRegistry(
TestAllTypesProto3,
TestAllTypesProto2,
Any,
extension_int32,
);

void main();
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

88 changes: 88 additions & 0 deletions packages/protobuf-test/extra/extensions-proto2.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto2";
package proto2ext;

import "extra/example.proto";
import "google/protobuf/wrappers.proto";

// The message we're going to extend
message Proto2Extendee {
optional int32 own_field = 1;
extensions 1000 to 9999;
}

// An enumeration used in extensions
enum Proto2ExtEnum {
PROTO2_EXT_ENUM_YES = 1;
PROTO2_EXT_ENUM_NO = 2;
}

// A message used in extensions
message Proto2ExtMessage {
optional string string_field = 1;
}

// Testing all kinds of extensions.
// Required fields, maps, oneof are not allowed in extensions.
extend Proto2Extendee {

optional uint32 uint32_ext = 1001;
optional uint32 uint32_ext_with_default = 1002 [default = 999];

optional string string_ext = 2001;
optional string string_ext_with_default = 2002 [default = "hello \" */ "];

optional uint64 uint64_ext = 3001;
optional uint64 uint64_ext_js_string = 3002 [jstype = JS_STRING];

optional bytes bytes_ext = 4001;
optional bytes bytes_ext_with_default = 4002 [default = "\0x\\x\"x\'A\101\x41\x41\u0041\U00000041\b\f\n\r\t\v"];

optional Proto2ExtEnum enum_ext = 5001;
optional Proto2ExtEnum enum_ext_with_default = 5002 [default = PROTO2_EXT_ENUM_NO];

optional Proto2ExtMessage message_ext = 6001;
optional docs.User message_ext_proto3 = 6002;

repeated Proto2ExtMessage repeated_message_ext = 7001;
repeated Proto2ExtEnum repeated_enum_ext = 7005;
repeated string repeated_string_ext = 7002;
repeated uint32 packed_uint32_ext = 7003 [packed = true];
repeated uint32 unpacked_uint32_ext = 7004; // unpacked by default in proto2

optional google.protobuf.UInt32Value wrapper_ext = 8001;

optional group GroupExt = 8100 {
optional int32 a = 1;
optional int32 b = 2;
}
repeated group RepeatedGroupExt = 8101 {
optional int32 a = 1;
optional int32 b = 2;
}
}

// A container for nested extensions
message Proto2ExtContainer {
extend Proto2Extendee {
optional uint32 uint32_ext = 9001;
}
message Child {
extend Proto2Extendee {
optional uint32 uint32_ext = 9010;
}
}
}
28 changes: 28 additions & 0 deletions packages/protobuf-test/extra/extensions-proto3.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";
package proto3ext;

import "google/protobuf/descriptor.proto";
import "extra/example.proto";
import "google/protobuf/wrappers.proto";

// In proto3, we can only extend options.
extend google.protobuf.FileOptions {
uint32 uint32_ext = 1001;
optional uint32 optional_uint32_ext = 1002;
repeated uint32 packed_uint32_ext = 7003;
repeated uint32 unpacked_uint32_ext = 7004 [packed = false];
}
77 changes: 63 additions & 14 deletions packages/protobuf-test/src/create-registry-from-desc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,50 +13,99 @@
// limitations under the License.

import { describe, expect, test } from "@jest/globals";
import { readFileSync } from "fs";
import type {
IExtensionRegistry,
IMessageTypeRegistry,
} from "@bufbuild/protobuf";
import {
createDescriptorSet,
createRegistry,
createRegistryFromDescriptors,
FileDescriptorSet,
MethodKind,
proto3,
} from "@bufbuild/protobuf";
import { TestAllTypes } from "./gen/ts/google/protobuf/unittest_proto3_pb.js";
import { assertMessageTypeEquals } from "./helpers.js";
import {
TestAllTypes,
TestAllTypes_NestedEnum,
} from "./gen/ts/google/protobuf/unittest_proto3_pb.js";
import {
assertEnumTypeEquals,
assertExtensionEquals,
assertMessageTypeEquals,
getTestFileDescriptorSetBytes,
} from "./helpers.js";
import {
ExampleRequest,
ExampleResponse,
} from "./gen/ts/extra/service-example_pb.js";

const fdsBytes = readFileSync("./descriptorset.bin");
const fds = FileDescriptorSet.fromBinary(fdsBytes);
import {
Proto2Extendee,
string_ext,
uint32_ext,
} from "./gen/ts/extra/extensions-proto2_pb.js";

describe("createRegistryFromDescriptors()", () => {
test("finds nothing if empty", () => {
const dr = createRegistryFromDescriptors(createDescriptorSet([]));
expect(dr.findMessage("foo.Foo")).toBeUndefined();
expect(dr.findEnum("foo.Foo")).toBeUndefined();
expect(dr.findService("foo.Foo")).toBeUndefined();
expect(dr.findExtension("foo.bar_ext")).toBeUndefined();
expect(dr.findExtensionFor("foo.Bar", 123)).toBeUndefined();
});
test("from google.protobuf.FileDescriptorSet", () => {
const dr = createRegistryFromDescriptors(fds);
assertExpectedRegistry(dr);
const dr = createRegistryFromDescriptors(
FileDescriptorSet.fromBinary(getTestFileDescriptorSetBytes()),
);
expectMessageTypes(dr);
expectEnumTypes(dr);
expectServiceTypes(dr);
expectExtensions(dr);
});
test("from serialized google.protobuf.FileDescriptorSet", () => {
const dr = createRegistryFromDescriptors(fdsBytes);
assertExpectedRegistry(dr);
const dr = createRegistryFromDescriptors(getTestFileDescriptorSetBytes());
expectMessageTypes(dr);
expectEnumTypes(dr);
expectServiceTypes(dr);
expectExtensions(dr);
});
});

function assertExpectedRegistry(
registry: ReturnType<typeof createRegistry>,
): void {
expect(registry.findEnum("foo.Foo")).toBeUndefined();
function expectExtensions(registry: IExtensionRegistry) {
const extByExtendee = registry.findExtensionFor(
Proto2Extendee.typeName,
uint32_ext.field.no,
);
expect(extByExtendee).toBeDefined();
if (extByExtendee) {
assertExtensionEquals(extByExtendee, uint32_ext);
}
const extByName = registry.findExtension(string_ext.typeName);
expect(extByName).toBeDefined();
if (extByName) {
assertExtensionEquals(extByName, string_ext);
}
}

function expectMessageTypes(registry: IMessageTypeRegistry) {
const mt = registry.findMessage(TestAllTypes.typeName);
expect(mt).toBeDefined();
if (mt) {
assertMessageTypeEquals(mt, TestAllTypes);
}
}

function expectEnumTypes(registry: ReturnType<typeof createRegistry>) {
const { typeName } = proto3.getEnumType(TestAllTypes_NestedEnum);
const enumType = registry.findEnum(typeName);
expect(enumType).toBeDefined();
if (enumType) {
assertEnumTypeEquals(enumType, proto3.getEnumType(TestAllTypes_NestedEnum));
}
}

function expectServiceTypes(registry: ReturnType<typeof createRegistry>) {
const st = registry.findService("spec.ExampleService");
expect(st).toBeDefined();
expect(Object.keys(st?.methods ?? {})).toStrictEqual([
Expand Down
Loading

0 comments on commit 5a226ed

Please sign in to comment.