diff --git a/packages/bundle-size/README.md b/packages/bundle-size/README.md index ef14b8c68..6da81e8d2 100644 --- a/packages/bundle-size/README.md +++ b/packages/bundle-size/README.md @@ -16,11 +16,11 @@ usually do. We repeat this for an increasing number of files. | code generator | files | bundle size | minified | compressed | | ------------------- | ----: | ----------: | --------: | ---------: | -| Protobuf-ES | 1 | 131,530 b | 68,233 b | 15,681 b | -| Protobuf-ES | 4 | 133,719 b | 69,743 b | 16,342 b | -| Protobuf-ES | 8 | 136,481 b | 71,514 b | 16,880 b | -| Protobuf-ES | 16 | 146,931 b | 79,495 b | 19,248 b | -| Protobuf-ES | 32 | 174,722 b | 101,511 b | 24,714 b | +| Protobuf-ES | 1 | 132,069 b | 68,479 b | 15,746 b | +| Protobuf-ES | 4 | 134,258 b | 69,989 b | 16,445 b | +| Protobuf-ES | 8 | 137,020 b | 71,760 b | 16,938 b | +| Protobuf-ES | 16 | 147,470 b | 79,741 b | 19,274 b | +| Protobuf-ES | 32 | 175,261 b | 101,759 b | 24,722 b | | protobuf-javascript | 1 | 104,048 b | 70,320 b | 15,540 b | | protobuf-javascript | 4 | 130,537 b | 85,672 b | 16,956 b | | protobuf-javascript | 8 | 152,429 b | 98,044 b | 18,138 b | diff --git a/packages/bundle-size/chart.svg b/packages/bundle-size/chart.svg index d0f92a29b..711b3fa01 100644 --- a/packages/bundle-size/chart.svg +++ b/packages/bundle-size/chart.svg @@ -43,14 +43,14 @@ 0 KiB - + Protobuf-ES -Protobuf-ES 15.31 KiB for 1 files -Protobuf-ES 15.96 KiB for 4 files -Protobuf-ES 16.48 KiB for 8 files -Protobuf-ES 18.8 KiB for 16 files -Protobuf-ES 24.13 KiB for 32 files +Protobuf-ES 15.38 KiB for 1 files +Protobuf-ES 16.06 KiB for 4 files +Protobuf-ES 16.54 KiB for 8 files +Protobuf-ES 18.82 KiB for 16 files +Protobuf-ES 24.14 KiB for 32 files diff --git a/packages/protobuf-test/src/enum-open-closed.test.ts b/packages/protobuf-test/src/enum-open-closed.test.ts new file mode 100644 index 000000000..08a5f1e34 --- /dev/null +++ b/packages/protobuf-test/src/enum-open-closed.test.ts @@ -0,0 +1,73 @@ +// Copyright 2021-2025 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. + +import { describe, expect, test } from "@jest/globals"; +import { BinaryWriter, WireType } from "@bufbuild/protobuf/wire"; +import { fromBinary, isFieldSet } from "@bufbuild/protobuf"; +import * as proto3_ts from "./gen/ts/extra/proto3_pb.js"; +import * as proto2_ts from "./gen/ts/extra/proto2_pb.js"; + +describe("open enum", () => { + test("from binary sets foreign value", () => { + expect(proto3_ts.Proto3EnumSchema.open).toBe(true); + const foreignValue = 4; + expect(proto3_ts.Proto3Enum[foreignValue]).toBeUndefined(); + const bytes = new BinaryWriter() + .tag( + proto3_ts.Proto3MessageSchema.field.singularEnumField.number, + WireType.Varint, + ) + .int32(foreignValue) + .finish(); + const msg = fromBinary(proto3_ts.Proto3MessageSchema, bytes); + const set = isFieldSet( + msg, + proto3_ts.Proto3MessageSchema.field.singularEnumField, + ); + expect(set).toBe(true); + expect(msg.singularEnumField).toBe(foreignValue); + expect(msg.$unknown).toBeUndefined(); + }); +}); + +describe("closed enum", () => { + test("from binary sets foreign value as unknown field", () => { + expect(proto2_ts.Proto2EnumSchema.open).toBe(false); + const foreignValue = 4; + expect(proto2_ts.Proto2Enum[foreignValue]).toBeUndefined(); + const bytes = new BinaryWriter() + .tag( + proto2_ts.Proto2MessageSchema.field.optionalEnumField.number, + WireType.Varint, + ) + .int32(foreignValue) + .finish(); + const msg = fromBinary(proto2_ts.Proto2MessageSchema, bytes); + const set = isFieldSet( + msg, + proto2_ts.Proto2MessageSchema.field.optionalEnumField, + ); + expect(set).toBe(false); + expect(msg.optionalEnumField).toBe(proto2_ts.Proto2Enum.YES); + expect(msg.$unknown).toBeDefined(); + expect(msg.$unknown?.length).toBe(1); + expect(msg.$unknown?.[0].no).toBe( + proto2_ts.Proto2MessageSchema.field.optionalEnumField.number, + ); + expect(msg.$unknown?.[0].wireType).toBe(WireType.Varint); + expect(msg.$unknown?.[0].data).toStrictEqual( + new BinaryWriter().int32(foreignValue).finish(), + ); + }); +}); diff --git a/packages/protobuf/src/from-binary.ts b/packages/protobuf/src/from-binary.ts index d5bd7e491..47ee627ab 100644 --- a/packages/protobuf/src/from-binary.ts +++ b/packages/protobuf/src/from-binary.ts @@ -22,7 +22,11 @@ import type { import { scalarZeroValue } from "./reflect/scalar.js"; import type { ScalarValue } from "./reflect/scalar.js"; import { reflect } from "./reflect/reflect.js"; -import { BinaryReader, WireType } from "./wire/binary-encoding.js"; +import { + BinaryReader, + BinaryWriter, + WireType, +} from "./wire/binary-encoding.js"; /** * Options for parsing binary data. @@ -151,7 +155,20 @@ export function readField( message.set(field, readScalar(reader, field.scalar)); break; case "enum": - message.set(field, readScalar(reader, ScalarType.INT32) as number); + const val = readScalar(reader, ScalarType.INT32); + if (field.enum.open) { + message.set(field, val); + } else { + const ok = field.enum.values.some((v) => v.number === val); + if (ok) { + message.set(field, val); + } else if (options.readUnknownFields) { + const data = new BinaryWriter().int32(val as number).finish(); + const unknownFields = message.getUnknown() ?? []; + unknownFields.push({ no: field.number, wireType, data }); + message.setUnknown(unknownFields); + } + } break; case "message": message.set(