diff --git a/docs/runtime_api.md b/docs/runtime_api.md index db05c0d08..181136f96 100644 --- a/docs/runtime_api.md +++ b/docs/runtime_api.md @@ -10,7 +10,6 @@ provided by the library. - [Default field values](#default-field-values) - [Accessing fields](#accessing-fields) - [Accessing oneof groups](#accessing-oneof-groups) - - [Field presence](#field-presence) - [Cloning messages](#cloning-messages) - [Comparing messages](#comparing-messages) - [Serializing messages](#serializing-messages) @@ -234,27 +233,6 @@ the TypeScript compiler option [`strictNullChecks`](https://www.typescriptlang.o This option is automatically enabled with the option `strict`, which is recommended. -### Field presence - -As we explained above, fields have [default values](#default-field-values). To -determine whether a field has an actual value, you can use the function `isFieldSet`. -To reset a field to its initial value, use the function `clearField`. - -```typescript -import { isFieldSet, clearField } from "@bufbuild/protobuf"; - -const user = new User({ - active: true, -}); - -isFieldSet(user, "active"); // true -isFieldSet(user, "firstName"); // false - -clearField(user, "active"); -isFieldSet(user, "active"); // false -``` - - ### Cloning messages While a shallow copy of a message can be created by using the spread operator with the @@ -761,9 +739,6 @@ walkFields(user); For a more practical example that covers all cases, you can take a look at the source of [`toPlainMessage`][src-toPlainMessage]. -Not that the functions `isFieldSet` and `clearField` (see [Field presence](#field-presence)) -optionally accept a field info object instead of a field name. - ### Message types diff --git a/packages/protobuf-bench/README.md b/packages/protobuf-bench/README.md index 390c5bb84..8fca98b16 100644 --- a/packages/protobuf-bench/README.md +++ b/packages/protobuf-bench/README.md @@ -10,5 +10,5 @@ server would usually do. | code generator | bundle size | minified | compressed | |---------------------|------------------------:|-----------------------:|-------------------:| -| protobuf-es | 97,073 b | 41,481 b | 10,747 b | +| protobuf-es | 96,999 b | 41,438 b | 10,763 b | | protobuf-javascript | 394,384 b | 288,654 b | 45,122 b | diff --git a/packages/protobuf-test/extra/proto3.proto b/packages/protobuf-test/extra/proto3.proto index 81ba94107..e96d1d79d 100644 --- a/packages/protobuf-test/extra/proto3.proto +++ b/packages/protobuf-test/extra/proto3.proto @@ -29,36 +29,21 @@ message Proto3UnpackedMessage { repeated uint64 unpacked_uint64_field = 203 [packed = false]; } -message Proto3UnspecifiedPackedMessage { +message Proto3UnlabelledMessage { repeated double double_field = 1; repeated uint32 uint32_field = 2; repeated uint64 uint64_field = 3; } -message Proto3UnlabelledMessage { - string string_field = 1; - bytes bytes_field = 2; - int32 int32_field = 3; - int64 int64_field = 4; - float float_field = 5; - bool bool_field = 6; - Proto3Enum enum_field = 7; - Proto3OptionalMessage message_field = 8; -} - message Proto3OptionalMessage { optional string string_field = 1; optional bytes bytes_field = 2; - optional int32 int32_field = 3; - optional int64 int64_field = 4; - optional float float_field = 5; - optional bool bool_field = 6; - optional Proto3Enum enum_field = 7; - optional Proto3OptionalMessage message_field = 8; + optional Proto3Enum enum_field = 3; + optional Proto3OptionalMessage message_field = 4; } enum Proto3Enum { PROTO3_ENUM_UNSPECIFIED = 0; PROTO3_ENUM_YES = 1; PROTO3_ENUM_NO = 2; -} +} \ No newline at end of file diff --git a/packages/protobuf-test/src/gen/js/extra/proto3_pb.d.ts b/packages/protobuf-test/src/gen/js/extra/proto3_pb.d.ts index 6277817ad..eadab1506 100644 --- a/packages/protobuf-test/src/gen/js/extra/proto3_pb.d.ts +++ b/packages/protobuf-test/src/gen/js/extra/proto3_pb.d.ts @@ -108,9 +108,9 @@ export declare class Proto3UnpackedMessage extends Message { +export declare class Proto3UnlabelledMessage extends Message { /** * @generated from field: repeated double double_field = 1; */ @@ -126,65 +126,6 @@ export declare class Proto3UnspecifiedPackedMessage extends Message); - - static readonly runtime: typeof proto3; - static readonly typeName = "spec.Proto3UnspecifiedPackedMessage"; - static readonly fields: FieldList; - - static fromBinary(bytes: Uint8Array, options?: Partial): Proto3UnspecifiedPackedMessage; - - static fromJson(jsonValue: JsonValue, options?: Partial): Proto3UnspecifiedPackedMessage; - - static fromJsonString(jsonString: string, options?: Partial): Proto3UnspecifiedPackedMessage; - - static equals(a: Proto3UnspecifiedPackedMessage | PlainMessage | undefined, b: Proto3UnspecifiedPackedMessage | PlainMessage | undefined): boolean; -} - -/** - * @generated from message spec.Proto3UnlabelledMessage - */ -export declare class Proto3UnlabelledMessage extends Message { - /** - * @generated from field: string string_field = 1; - */ - stringField: string; - - /** - * @generated from field: bytes bytes_field = 2; - */ - bytesField: Uint8Array; - - /** - * @generated from field: int32 int32_field = 3; - */ - int32Field: number; - - /** - * @generated from field: int64 int64_field = 4; - */ - int64Field: bigint; - - /** - * @generated from field: float float_field = 5; - */ - floatField: number; - - /** - * @generated from field: bool bool_field = 6; - */ - boolField: boolean; - - /** - * @generated from field: spec.Proto3Enum enum_field = 7; - */ - enumField: Proto3Enum; - - /** - * @generated from field: spec.Proto3OptionalMessage message_field = 8; - */ - messageField?: Proto3OptionalMessage; - constructor(data?: PartialMessage); static readonly runtime: typeof proto3; @@ -215,32 +156,12 @@ export declare class Proto3OptionalMessage extends Message [ - { no: 1, name: "double_field", kind: "scalar", T: 1 /* ScalarType.DOUBLE */, repeated: true }, - { no: 2, name: "uint32_field", kind: "scalar", T: 13 /* ScalarType.UINT32 */, repeated: true }, - { no: 3, name: "uint64_field", kind: "scalar", T: 4 /* ScalarType.UINT64 */, repeated: true }, - ], -); - /** * @generated from message spec.Proto3UnlabelledMessage */ export const Proto3UnlabelledMessage = proto3.makeMessageType( "spec.Proto3UnlabelledMessage", () => [ - { no: 1, name: "string_field", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - { no: 2, name: "bytes_field", kind: "scalar", T: 12 /* ScalarType.BYTES */ }, - { no: 3, name: "int32_field", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, - { no: 4, name: "int64_field", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, - { no: 5, name: "float_field", kind: "scalar", T: 2 /* ScalarType.FLOAT */ }, - { no: 6, name: "bool_field", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, - { no: 7, name: "enum_field", kind: "enum", T: proto3.getEnumType(Proto3Enum) }, - { no: 8, name: "message_field", kind: "message", T: Proto3OptionalMessage }, + { no: 1, name: "double_field", kind: "scalar", T: 1 /* ScalarType.DOUBLE */, repeated: true }, + { no: 2, name: "uint32_field", kind: "scalar", T: 13 /* ScalarType.UINT32 */, repeated: true }, + { no: 3, name: "uint64_field", kind: "scalar", T: 4 /* ScalarType.UINT64 */, repeated: true }, ], ); @@ -91,12 +74,8 @@ export const Proto3OptionalMessage = proto3.makeMessageType( () => [ { no: 1, name: "string_field", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, { no: 2, name: "bytes_field", kind: "scalar", T: 12 /* ScalarType.BYTES */, opt: true }, - { no: 3, name: "int32_field", kind: "scalar", T: 5 /* ScalarType.INT32 */, opt: true }, - { no: 4, name: "int64_field", kind: "scalar", T: 3 /* ScalarType.INT64 */, opt: true }, - { no: 5, name: "float_field", kind: "scalar", T: 2 /* ScalarType.FLOAT */, opt: true }, - { no: 6, name: "bool_field", kind: "scalar", T: 8 /* ScalarType.BOOL */, opt: true }, - { no: 7, name: "enum_field", kind: "enum", T: proto3.getEnumType(Proto3Enum), opt: true }, - { no: 8, name: "message_field", kind: "message", T: Proto3OptionalMessage, opt: true }, + { no: 3, name: "enum_field", kind: "enum", T: proto3.getEnumType(Proto3Enum), opt: true }, + { no: 4, name: "message_field", kind: "message", T: Proto3OptionalMessage, opt: true }, ], ); diff --git a/packages/protobuf-test/src/gen/ts/extra/proto3_pb.ts b/packages/protobuf-test/src/gen/ts/extra/proto3_pb.ts index 7e630fb03..798ea852f 100644 --- a/packages/protobuf-test/src/gen/ts/extra/proto3_pb.ts +++ b/packages/protobuf-test/src/gen/ts/extra/proto3_pb.ts @@ -17,7 +17,7 @@ /* eslint-disable */ import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; -import { Message, proto3, protoInt64 } from "@bufbuild/protobuf"; +import { Message, proto3 } from "@bufbuild/protobuf"; /** * @generated from enum spec.Proto3Enum @@ -144,9 +144,9 @@ export class Proto3UnpackedMessage extends Message { } /** - * @generated from message spec.Proto3UnspecifiedPackedMessage + * @generated from message spec.Proto3UnlabelledMessage */ -export class Proto3UnspecifiedPackedMessage extends Message { +export class Proto3UnlabelledMessage extends Message { /** * @generated from field: repeated double double_field = 1; */ @@ -162,98 +162,19 @@ export class Proto3UnspecifiedPackedMessage extends Message) { + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = "spec.Proto3UnspecifiedPackedMessage"; + static readonly typeName = "spec.Proto3UnlabelledMessage"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 1, name: "double_field", kind: "scalar", T: 1 /* ScalarType.DOUBLE */, repeated: true }, { no: 2, name: "uint32_field", kind: "scalar", T: 13 /* ScalarType.UINT32 */, repeated: true }, { no: 3, name: "uint64_field", kind: "scalar", T: 4 /* ScalarType.UINT64 */, repeated: true }, ]); - static fromBinary(bytes: Uint8Array, options?: Partial): Proto3UnspecifiedPackedMessage { - return new Proto3UnspecifiedPackedMessage().fromBinary(bytes, options); - } - - static fromJson(jsonValue: JsonValue, options?: Partial): Proto3UnspecifiedPackedMessage { - return new Proto3UnspecifiedPackedMessage().fromJson(jsonValue, options); - } - - static fromJsonString(jsonString: string, options?: Partial): Proto3UnspecifiedPackedMessage { - return new Proto3UnspecifiedPackedMessage().fromJsonString(jsonString, options); - } - - static equals(a: Proto3UnspecifiedPackedMessage | PlainMessage | undefined, b: Proto3UnspecifiedPackedMessage | PlainMessage | undefined): boolean { - return proto3.util.equals(Proto3UnspecifiedPackedMessage, a, b); - } -} - -/** - * @generated from message spec.Proto3UnlabelledMessage - */ -export class Proto3UnlabelledMessage extends Message { - /** - * @generated from field: string string_field = 1; - */ - stringField = ""; - - /** - * @generated from field: bytes bytes_field = 2; - */ - bytesField = new Uint8Array(0); - - /** - * @generated from field: int32 int32_field = 3; - */ - int32Field = 0; - - /** - * @generated from field: int64 int64_field = 4; - */ - int64Field = protoInt64.zero; - - /** - * @generated from field: float float_field = 5; - */ - floatField = 0; - - /** - * @generated from field: bool bool_field = 6; - */ - boolField = false; - - /** - * @generated from field: spec.Proto3Enum enum_field = 7; - */ - enumField = Proto3Enum.UNSPECIFIED; - - /** - * @generated from field: spec.Proto3OptionalMessage message_field = 8; - */ - messageField?: Proto3OptionalMessage; - - constructor(data?: PartialMessage) { - super(); - proto3.util.initPartial(data, this); - } - - static readonly runtime: typeof proto3 = proto3; - static readonly typeName = "spec.Proto3UnlabelledMessage"; - static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: "string_field", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - { no: 2, name: "bytes_field", kind: "scalar", T: 12 /* ScalarType.BYTES */ }, - { no: 3, name: "int32_field", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, - { no: 4, name: "int64_field", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, - { no: 5, name: "float_field", kind: "scalar", T: 2 /* ScalarType.FLOAT */ }, - { no: 6, name: "bool_field", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, - { no: 7, name: "enum_field", kind: "enum", T: proto3.getEnumType(Proto3Enum) }, - { no: 8, name: "message_field", kind: "message", T: Proto3OptionalMessage }, - ]); - static fromBinary(bytes: Uint8Array, options?: Partial): Proto3UnlabelledMessage { return new Proto3UnlabelledMessage().fromBinary(bytes, options); } @@ -286,32 +207,12 @@ export class Proto3OptionalMessage extends Message { bytesField?: Uint8Array; /** - * @generated from field: optional int32 int32_field = 3; - */ - int32Field?: number; - - /** - * @generated from field: optional int64 int64_field = 4; - */ - int64Field?: bigint; - - /** - * @generated from field: optional float float_field = 5; - */ - floatField?: number; - - /** - * @generated from field: optional bool bool_field = 6; - */ - boolField?: boolean; - - /** - * @generated from field: optional spec.Proto3Enum enum_field = 7; + * @generated from field: optional spec.Proto3Enum enum_field = 3; */ enumField?: Proto3Enum; /** - * @generated from field: optional spec.Proto3OptionalMessage message_field = 8; + * @generated from field: optional spec.Proto3OptionalMessage message_field = 4; */ messageField?: Proto3OptionalMessage; @@ -325,12 +226,8 @@ export class Proto3OptionalMessage extends Message { static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 1, name: "string_field", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, { no: 2, name: "bytes_field", kind: "scalar", T: 12 /* ScalarType.BYTES */, opt: true }, - { no: 3, name: "int32_field", kind: "scalar", T: 5 /* ScalarType.INT32 */, opt: true }, - { no: 4, name: "int64_field", kind: "scalar", T: 3 /* ScalarType.INT64 */, opt: true }, - { no: 5, name: "float_field", kind: "scalar", T: 2 /* ScalarType.FLOAT */, opt: true }, - { no: 6, name: "bool_field", kind: "scalar", T: 8 /* ScalarType.BOOL */, opt: true }, - { no: 7, name: "enum_field", kind: "enum", T: proto3.getEnumType(Proto3Enum), opt: true }, - { no: 8, name: "message_field", kind: "message", T: Proto3OptionalMessage, opt: true }, + { no: 3, name: "enum_field", kind: "enum", T: proto3.getEnumType(Proto3Enum), opt: true }, + { no: 4, name: "message_field", kind: "message", T: Proto3OptionalMessage, opt: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Proto3OptionalMessage { diff --git a/packages/protobuf-test/src/msg-maps.test.ts b/packages/protobuf-test/src/msg-maps.test.ts index 715037861..adc169587 100644 --- a/packages/protobuf-test/src/msg-maps.test.ts +++ b/packages/protobuf-test/src/msg-maps.test.ts @@ -17,7 +17,7 @@ import type { JsonValue, PlainMessage } from "@bufbuild/protobuf"; import { describeMT } from "./helpers.js"; import { MapsMessage as TS_MapsMessage } from "./gen/ts/extra/msg-maps_pb.js"; import { MapsMessage as JS_MapsMessage } from "./gen/js/extra/msg-maps_pb.js"; -import { protoInt64, isFieldSet, clearField } from "@bufbuild/protobuf"; +import { protoInt64 } from "@bufbuild/protobuf"; describeMT({ ts: TS_MapsMessage, js: JS_MapsMessage }, (messageType) => { const defaultFields: PlainMessage = { @@ -122,33 +122,6 @@ describeMT({ ts: TS_MapsMessage, js: JS_MapsMessage }, (messageType) => { }, }); }); - describe("isFieldSet()", () => { - test("returns false for empty map", () => { - const msg = new messageType({ - strStrField: {}, - }); - expect(isFieldSet(msg, "strStrField")).toBe(false); - }); - test("returns true for non-empty map", () => { - const msg = new messageType({ - strStrField: { - foo: "bar", - }, - }); - expect(isFieldSet(msg, "strStrField")).toBe(true); - }); - }); - describe("clearField()", () => { - test("clears map", () => { - const msg = new messageType({ - strStrField: { - foo: "bar", - }, - }); - clearField(msg, "strStrField"); - expect(Object.keys(msg.strStrField).length).toBe(0); - }); - }); describe("field info", () => { test.each(messageType.fields.byNumber())("$name", (field) => { expect(typeof field.no).toBe("number"); diff --git a/packages/protobuf-test/src/msg-message.test.ts b/packages/protobuf-test/src/msg-message.test.ts index b83909c13..4d9e222b5 100644 --- a/packages/protobuf-test/src/msg-message.test.ts +++ b/packages/protobuf-test/src/msg-message.test.ts @@ -14,7 +14,6 @@ import { describe, expect, test } from "@jest/globals"; import type { JsonValue, PlainMessage } from "@bufbuild/protobuf"; -import { clearField, isFieldSet } from "@bufbuild/protobuf"; import { MessageFieldMessage as TS_MessageFieldMessage } from "./gen/ts/extra/msg-message_pb.js"; import { MessageFieldMessage as JS_MessageFieldMessage } from "./gen/js/extra/msg-message_pb.js"; import { describeMT } from "./helpers.js"; @@ -54,46 +53,6 @@ describeMT( defaultFields.repeatedMessageField.length, ); }); - describe("isFieldSet()", () => { - test("returns false for empty repeated field", () => { - const msg = new messageType({ - repeatedMessageField: [], - }); - expect(isFieldSet(msg, "repeatedMessageField")).toBe(false); - }); - test("returns false for empty singular field", () => { - const msg = new messageType(); - expect(isFieldSet(msg, "messageField")).toBe(false); - }); - test("returns true for non-empty repeated field", () => { - const msg = new messageType({ - repeatedMessageField: [{}], - }); - expect(isFieldSet(msg, "repeatedMessageField")).toBe(true); - }); - test("returns true for non-empty singular field", () => { - const msg = new messageType({ - messageField: { name: "test" }, - }); - expect(isFieldSet(msg, "messageField")).toBe(true); - }); - }); - describe("clearField()", () => { - test("clears repeated field", () => { - const msg = new messageType({ - repeatedMessageField: [{}], - }); - clearField(msg, "repeatedMessageField"); - expect(msg.repeatedMessageField.length).toBe(0); - }); - test("clears singular field", () => { - const msg = new messageType({ - messageField: { name: "test" }, - }); - clearField(msg, "messageField"); - expect(msg.messageField).toBeUndefined(); - }); - }); test("example encodes to JSON", () => { /* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access */ const got = new messageType(exampleFields).toJson(); diff --git a/packages/protobuf-test/src/msg-oneof.test.ts b/packages/protobuf-test/src/msg-oneof.test.ts index 08c61ecf3..cadeb1a5d 100644 --- a/packages/protobuf-test/src/msg-oneof.test.ts +++ b/packages/protobuf-test/src/msg-oneof.test.ts @@ -14,7 +14,6 @@ import { describe, expect, test } from "@jest/globals"; import type { JsonValue, PlainMessage } from "@bufbuild/protobuf"; -import { clearField, isFieldSet } from "@bufbuild/protobuf"; import { describeMT } from "./helpers.js"; import { OneofMessage as TS_OneofMessage } from "./gen/ts/extra/msg-oneof_pb.js"; import { OneofMessage as JS_OneofMessage } from "./gen/js/extra/msg-oneof_pb.js"; @@ -79,42 +78,6 @@ describeMT({ ts: TS_OneofMessage, js: JS_OneofMessage }, (messageType) => { scalar: { case: "bytes", value: new Uint8Array(bytes) }, }); }); - describe("isFieldSet()", () => { - test("returns false for unselected oneof", () => { - const msg = new messageType({ - scalar: { case: undefined }, - }); - expect(isFieldSet(msg, "value")).toBe(false); - expect(isFieldSet(msg, "error")).toBe(false); - expect(isFieldSet(msg, "bytes")).toBe(false); - }); - test("returns true for selected oneof", () => { - const msg = new messageType({ - scalar: { case: "value", value: 123 }, - }); - expect(isFieldSet(msg, "value")).toBe(true); - expect(isFieldSet(msg, "error")).toBe(false); - expect(isFieldSet(msg, "bytes")).toBe(false); - }); - }); - describe("clearField()", () => { - test("deselects selected oneof", () => { - const msg = new messageType({ - scalar: { case: "value", value: 123 }, - }); - clearField(msg, "value"); - expect(msg.scalar.case).toBeUndefined(); - expect(msg.scalar.value).toBeUndefined(); - }); - test("skips if field is not selected", () => { - const msg = new messageType({ - scalar: { case: "error", value: "test" }, - }); - clearField(msg, "value"); - expect(msg.scalar.case).toBe("error"); - expect(msg.scalar.value).toBe("test"); - }); - }); describe("field info", () => { test.each(messageType.fields.byNumber())("$name", (field) => { expect(typeof field.no).toBe("number"); diff --git a/packages/protobuf-test/src/msg-scalar.test.ts b/packages/protobuf-test/src/msg-scalar.test.ts index d6062824e..e3796be06 100644 --- a/packages/protobuf-test/src/msg-scalar.test.ts +++ b/packages/protobuf-test/src/msg-scalar.test.ts @@ -13,18 +13,23 @@ // limitations under the License. import { describe, expect, test } from "@jest/globals"; -import * as TS from "./gen/ts/extra/msg-scalar_pb.js"; -import * as JS from "./gen/js/extra/msg-scalar_pb.js"; +import { + RepeatedScalarValuesMessage as TS_RepeatedScalarValuesMessage, + ScalarValuesMessage as TS_ScalarValuesMessage, +} from "./gen/ts/extra/msg-scalar_pb.js"; +import { + RepeatedScalarValuesMessage as JS_RepeatedScalarValuesMessage, + ScalarValuesMessage as JS_ScalarValuesMessage, +} from "./gen/js/extra/msg-scalar_pb.js"; import type { JsonValue, PlainMessage } from "@bufbuild/protobuf"; -import { clearField, isFieldSet } from "@bufbuild/protobuf"; import { protoInt64, ScalarType } from "@bufbuild/protobuf"; import { describeMT } from "./helpers.js"; describeMT( - { ts: TS.ScalarValuesMessage, js: JS.ScalarValuesMessage }, + { ts: TS_ScalarValuesMessage, js: JS_ScalarValuesMessage }, (messageType) => { const defaultFields: PlainMessage< - TS.ScalarValuesMessage | JS.ScalarValuesMessage + TS_ScalarValuesMessage | JS_ScalarValuesMessage > = { doubleField: 0, floatField: 0, @@ -44,7 +49,7 @@ describeMT( }; const defaultJson: JsonValue = {}; const exampleFields: PlainMessage< - TS.ScalarValuesMessage | JS.ScalarValuesMessage + TS_ScalarValuesMessage | JS_ScalarValuesMessage > = { doubleField: 0.75, floatField: -0.75, @@ -138,10 +143,10 @@ describeMT( ); describeMT( - { ts: TS.RepeatedScalarValuesMessage, js: JS.RepeatedScalarValuesMessage }, + { ts: TS_RepeatedScalarValuesMessage, js: JS_RepeatedScalarValuesMessage }, (messageType) => { const defaultFields: PlainMessage< - TS.RepeatedScalarValuesMessage | JS.RepeatedScalarValuesMessage + TS_RepeatedScalarValuesMessage | JS_RepeatedScalarValuesMessage > = { doubleField: [], floatField: [], @@ -161,7 +166,7 @@ describeMT( }; const defaultJson: JsonValue = {}; const exampleFields: PlainMessage< - TS.RepeatedScalarValuesMessage | JS.RepeatedScalarValuesMessage + TS_RepeatedScalarValuesMessage | JS_RepeatedScalarValuesMessage > = { doubleField: [0.75, 0, 1], floatField: [0.75, -0.75], @@ -239,31 +244,6 @@ describeMT( bytesField: [new Uint8Array(bytes)], }); }); - describe("isFieldSet()", () => { - // singular fields are covered in proto3.test.ts - test("returns false for repeated field", () => { - const msg = new messageType({ - doubleField: [], - }); - expect(isFieldSet(msg, "doubleField")).toBe(false); - }); - test("returns true for non-empty repeated field", () => { - const msg = new messageType({ - doubleField: [3.14], - }); - expect(isFieldSet(msg, "doubleField")).toBe(true); - }); - }); - describe("clearField()", () => { - // singular fields are covered in proto3.test.ts - test("clears repeated field", () => { - const msg = new messageType({ - doubleField: [3.14], - }); - clearField(msg, "doubleField"); - expect(msg.doubleField.length).toBe(0); - }); - }); describe("field info", () => { test.each(messageType.fields.byNumber())("$name", (field) => { expect(typeof field.no).toBe("number"); diff --git a/packages/protobuf-test/src/proto2.test.ts b/packages/protobuf-test/src/proto2.test.ts index 777842a35..443e0f6eb 100644 --- a/packages/protobuf-test/src/proto2.test.ts +++ b/packages/protobuf-test/src/proto2.test.ts @@ -16,15 +16,27 @@ import { describe, expect, test } from "@jest/globals"; import * as TS from "./gen/ts/extra/proto2_pb.js"; import * as JS from "./gen/js/extra/proto2_pb.js"; import { describeMT } from "./helpers.js"; +import type { AnyMessage } from "@bufbuild/protobuf"; import { BinaryReader, BinaryWriter, protoInt64, WireType, - isFieldSet, - clearField, } from "@bufbuild/protobuf"; -import { Proto2Enum } from "./gen/ts/extra/proto2_pb.js"; + +function objectHasOwn( + obj: object, + property: string | number | symbol, +): boolean { + if ("hasOwn" in Object) { + // ES2022 + return (Object as unknown as { hasOwn: typeof objectHasOwn }).hasOwn( + obj, + property, + ); + } + return Object.prototype.hasOwnProperty.call(obj, property); +} describe("proto2 required fields", () => { describeMT( @@ -43,69 +55,10 @@ describe("proto2 required fields", () => { expect(msg.messageField).toBeUndefined(); }); test.each(messageType.fields.byNumber())( - "field $name is not set", + "field $name is not an own property", (field) => { const msg = new messageType(); - expect(isFieldSet(msg, field)).toBeFalsy(); - expect( - Object.prototype.hasOwnProperty.call(msg, field.localName), - ).toBe(false); - }, - ); - }); - describe("isFieldSet()", () => { - test.each(messageType.fields.byNumber())( - "returns true for field $name set to zero value", - (field) => { - if (field.kind == "message") { - // message fields do not have zero values - return; - } - const msg = new messageType({ - stringField: "", - bytesField: new Uint8Array(), - int32Field: 0, - int64Field: protoInt64.zero, - floatField: 0, - boolField: false, - enumField: Proto2Enum.YES, - }); - expect(isFieldSet(msg, field)).toBe(true); - }, - ); - test.each(messageType.fields.byNumber())( - "returns true for field $name set to non-zero value", - (field) => { - const msg = new messageType({ - stringField: "abc", - bytesField: new Uint8Array([0xde, 0xad, 0xbe, 0xef]), - int32Field: 1, - int64Field: protoInt64.parse("123456"), - floatField: 3.14, - boolField: true, - enumField: Proto2Enum.YES, - messageField: new messageType(), - }); - expect(isFieldSet(msg, field)).toBe(true); - }, - ); - }); - describe("clearField()", () => { - test.each(messageType.fields.byNumber())( - "clears field $name", - (field) => { - const msg = new messageType({ - stringField: "", - bytesField: new Uint8Array(), - int32Field: 0, - int64Field: protoInt64.zero, - floatField: 0, - boolField: false, - enumField: Proto2Enum.YES, - messageField: new messageType(), - }); - clearField(msg, field); - expect(isFieldSet(msg, field)).toBe(false); + expect(objectHasOwn(msg, field.localName)).toBeFalsy(); }, ); }); @@ -116,9 +69,9 @@ describe("proto2 required fields", () => { stringField: "", }); expect(msg.enumField).toBeUndefined(); - expect(isFieldSet(msg, "enumField")).toBeFalsy(); + expect(objectHasOwn(msg, "enumField")).toBeFalsy(); expect(msg.stringField).toBeDefined(); - expect(isFieldSet(msg, "stringField")).toBeTruthy(); + expect(objectHasOwn(msg, "stringField")).toBeTruthy(); }); }); describe("fromBinary", () => { @@ -129,9 +82,9 @@ describe("proto2 required fields", () => { .finish(); const msg = messageType.fromBinary(bytes); expect(msg.enumField).toBeUndefined(); - expect(isFieldSet(msg, "enumField")).toBeFalsy(); + expect(objectHasOwn(msg, "enumField")).toBeFalsy(); expect(msg.stringField).toBeDefined(); - expect(isFieldSet(msg, "stringField")).toBeTruthy(); + expect(objectHasOwn(msg, "stringField")).toBeTruthy(); }); }); }); @@ -154,8 +107,8 @@ describe("proto2 required fields", () => { test.each(messageType.fields.byNumber())( "raises error with unset field $name", (field) => { - const invalidMsg = validMsg.clone(); - clearField(invalidMsg, field); + const invalidMsg = validMsg.clone() as AnyMessage; + delete invalidMsg[field.localName]; expect(() => invalidMsg.toJson()).toThrow( `cannot encode field ${messageType.typeName}.${field.name} to JSON: required field not set`, ); @@ -170,8 +123,8 @@ describe("proto2 required fields", () => { test.each(messageType.fields.byNumber())( "raises error with unset field $name", (field) => { - const invalidMsg = validMsg.clone(); - clearField(invalidMsg, field); + const invalidMsg = validMsg.clone() as AnyMessage; + delete invalidMsg[field.localName]; expect(() => invalidMsg.toBinary()).toThrow( `cannot encode field ${messageType.typeName}.${field.name} to binary: required field not set`, ); @@ -202,13 +155,10 @@ describe("proto2 required fields", () => { expect(msg.messageField).toBeUndefined(); }); test.each(messageType.fields.byNumber())( - "field $name is not set", + "field $name is not an own property", (field) => { const msg = new messageType(); - expect(isFieldSet(msg, field)).toBeFalsy(); - expect( - Object.prototype.hasOwnProperty.call(msg, field.localName), - ).toBe(false); + expect(objectHasOwn(msg, field.localName)).toBeFalsy(); }, ); }); @@ -231,8 +181,8 @@ describe("proto2 required fields", () => { test.each(messageType.fields.byNumber())( "raises error with unset field $name", (field) => { - const invalidMsg = validMsg.clone(); - clearField(invalidMsg, field); + const invalidMsg = validMsg.clone() as AnyMessage; + delete invalidMsg[field.localName]; expect(() => invalidMsg.toJson()).toThrow( `cannot encode field ${messageType.typeName}.${field.name} to JSON: required field not set`, ); @@ -247,8 +197,8 @@ describe("proto2 required fields", () => { test.each(messageType.fields.byNumber())( "raises error with unset field $name", (field) => { - const invalidMsg = validMsg.clone(); - clearField(invalidMsg, field); + const invalidMsg = validMsg.clone() as AnyMessage; + delete invalidMsg[field.localName]; expect(() => invalidMsg.toBinary()).toThrow( `cannot encode field ${messageType.typeName}.${field.name} to binary: required field not set`, ); @@ -278,69 +228,10 @@ describe("proto2 optional fields", () => { expect(msg.messageField).toBeUndefined(); }); test.each(messageType.fields.byNumber())( - "field $name is not set", + "field $name is not an own property", (field) => { const msg = new messageType(); - expect(isFieldSet(msg, field)).toBeFalsy(); - expect( - Object.prototype.hasOwnProperty.call(msg, field.localName), - ).toBe(false); - }, - ); - }); - describe("isFieldSet()", () => { - test.each(messageType.fields.byNumber())( - "returns true for field $name set to zero value", - (field) => { - if (field.kind == "message") { - // message fields do not have zero values - return; - } - const msg = new messageType({ - stringField: "", - bytesField: new Uint8Array(), - int32Field: 0, - int64Field: protoInt64.zero, - floatField: 0, - boolField: false, - enumField: Proto2Enum.YES, - }); - expect(isFieldSet(msg, field)).toBe(true); - }, - ); - test.each(messageType.fields.byNumber())( - "returns true for field $name set to non-zero value", - (field) => { - const msg = new messageType({ - stringField: "abc", - bytesField: new Uint8Array([0xde, 0xad, 0xbe, 0xef]), - int32Field: 1, - int64Field: protoInt64.parse("123456"), - floatField: 3.14, - boolField: true, - enumField: Proto2Enum.YES, - messageField: new messageType(), - }); - expect(isFieldSet(msg, field)).toBe(true); - }, - ); - }); - describe("clearField()", () => { - test.each(messageType.fields.byNumber())( - "clears field $name", - (field) => { - const msg = new messageType({ - stringField: "", - bytesField: new Uint8Array(), - int32Field: 0, - int64Field: protoInt64.zero, - floatField: 0, - boolField: false, - enumField: Proto2Enum.YES, - messageField: new messageType(), - }); - clearField(msg, field); - expect(isFieldSet(msg, field)).toBe(false); + expect(objectHasOwn(msg, field.localName)).toBeFalsy(); }, ); }); @@ -364,13 +255,10 @@ describe("proto2 optional fields", () => { expect(msg.messageField).toBeUndefined(); }); test.each(messageType.fields.byNumber())( - "field $name is not set", + "field $name is not an own property", (field) => { const msg = new messageType(); - expect(isFieldSet(msg, field)).toBe(false); - expect( - Object.prototype.hasOwnProperty.call(msg, field.localName), - ).toBe(false); + expect(objectHasOwn(msg, field.localName)).toBeFalsy(); }, ); }); diff --git a/packages/protobuf-test/src/proto3.test.ts b/packages/protobuf-test/src/proto3.test.ts index 9add8a4cf..bb09e016d 100644 --- a/packages/protobuf-test/src/proto3.test.ts +++ b/packages/protobuf-test/src/proto3.test.ts @@ -16,8 +16,6 @@ import { describe, expect, test } from "@jest/globals"; import * as TS from "./gen/ts/extra/proto3_pb.js"; import * as JS from "./gen/js/extra/proto3_pb.js"; import { describeMT } from "./helpers.js"; -import { clearField, isFieldSet, protoInt64 } from "@bufbuild/protobuf"; -import { Proto3Enum } from "./gen/ts/extra/proto3_pb.js"; describe("proto3 field info packed", () => { // Also see msg-scalars.test.ts @@ -41,8 +39,8 @@ describe("proto3 field info packed", () => { ); describeMT( { - ts: TS.Proto3UnspecifiedPackedMessage, - js: JS.Proto3UnspecifiedPackedMessage, + ts: TS.Proto3UnlabelledMessage, + js: JS.Proto3UnlabelledMessage, }, (messageType) => { test.each(messageType.fields.byNumber())("$name is unpacked", (field) => { @@ -53,178 +51,6 @@ describe("proto3 field info packed", () => { ); }); -describeMT( - { ts: TS.Proto3OptionalMessage, js: JS.Proto3OptionalMessage }, - (messageType) => { - describe("initially", () => { - test("has expected properties", () => { - const msg = new messageType(); - expect(msg.stringField).toBeUndefined(); - expect(msg.bytesField).toBeUndefined(); - expect(msg.int32Field).toBeUndefined(); - expect(msg.int64Field).toBeUndefined(); - expect(msg.floatField).toBeUndefined(); - expect(msg.boolField).toBeUndefined(); - expect(msg.enumField).toBeUndefined(); - expect(msg.messageField).toBeUndefined(); - }); - test.each(messageType.fields.byNumber())( - "field $name is not set", - (field) => { - const msg = new messageType(); - expect(isFieldSet(msg, field)).toBeFalsy(); - expect( - Object.prototype.hasOwnProperty.call(msg, field.localName), - ).toBe(false); - }, - ); - }); - describe("isFieldSet()", () => { - test.each(messageType.fields.byNumber())( - "returns true for field $name set to zero value", - (field) => { - if (field.kind == "message") { - // message fields do not have zero values - return; - } - const msg = new messageType({ - stringField: "", - bytesField: new Uint8Array(), - int32Field: 0, - int64Field: protoInt64.zero, - floatField: 0, - boolField: false, - enumField: Proto3Enum.UNSPECIFIED, - }); - expect(isFieldSet(msg, field)).toBe(true); - }, - ); - test.each(messageType.fields.byNumber())( - "returns true for field $name set to non-zero value", - (field) => { - const msg = new messageType({ - stringField: "abc", - bytesField: new Uint8Array([0xde, 0xad, 0xbe, 0xef]), - int32Field: 1, - int64Field: protoInt64.parse("123456"), - floatField: 3.14, - boolField: true, - enumField: Proto3Enum.YES, - messageField: new messageType(), - }); - expect(isFieldSet(msg, field)).toBe(true); - }, - ); - }); - describe("clearField()", () => { - test.each(messageType.fields.byNumber())( - "clears field $name", - (field) => { - const msg = new messageType({ - stringField: "", - bytesField: new Uint8Array(), - int32Field: 0, - int64Field: protoInt64.zero, - floatField: 0, - boolField: false, - enumField: Proto3Enum.UNSPECIFIED, - messageField: new messageType(), - }); - clearField(msg, field); - expect(isFieldSet(msg, field)).toBe(false); - }, - ); - }); - }, -); - -describeMT( - { ts: TS.Proto3UnlabelledMessage, js: JS.Proto3UnlabelledMessage }, - (messageType) => { - describe("initially", () => { - test("has expected properties", () => { - const msg = new messageType(); - expect(msg.stringField).toBe(""); - expect(msg.bytesField).toBeInstanceOf(Uint8Array); - expect(msg.bytesField.length).toBe(0); - expect(msg.int32Field).toBe(0); - expect(msg.int64Field).toBe(protoInt64.zero); - expect(msg.floatField).toBe(0); - expect(msg.boolField).toBe(false); - expect(msg.enumField).toBe(Proto3Enum.UNSPECIFIED); - expect(msg.messageField).toBeUndefined(); - }); - test.each(messageType.fields.byNumber())( - "field $name is not set", - (field) => { - const msg = new messageType(); - expect(isFieldSet(msg, field)).toBeFalsy(); - const shouldHaveOwn = field.kind != "message"; - expect( - Object.prototype.hasOwnProperty.call(msg, field.localName), - ).toBe(shouldHaveOwn); - }, - ); - }); - describe("isFieldSet()", () => { - test.each(messageType.fields.byNumber())( - "returns false for field $name set to zero value", - (field) => { - if (field.kind == "message") { - // message fields do not have zero values - return; - } - const msg = new messageType({ - stringField: "", - bytesField: new Uint8Array(), - int32Field: 0, - int64Field: protoInt64.zero, - floatField: 0, - boolField: false, - enumField: Proto3Enum.UNSPECIFIED, - }); - expect(isFieldSet(msg, field)).toBe(false); - }, - ); - test.each(messageType.fields.byNumber())( - "returns true for field $name set to non-zero value", - (field) => { - const msg = new messageType({ - stringField: "abc", - bytesField: new Uint8Array([0xde, 0xad, 0xbe, 0xef]), - int32Field: 1, - int64Field: protoInt64.parse("123456"), - floatField: 3.14, - boolField: true, - enumField: Proto3Enum.YES, - messageField: new messageType(), - }); - expect(isFieldSet(msg, field)).toBe(true); - }, - ); - }); - describe("clearField()", () => { - test.each(messageType.fields.byNumber())( - "clears field $name", - (field) => { - const msg = new messageType({ - stringField: "abc", - bytesField: new Uint8Array([0xde, 0xad, 0xbe, 0xef]), - int32Field: 1, - int64Field: protoInt64.parse("123456"), - floatField: 3.14, - boolField: true, - enumField: Proto3Enum.YES, - messageField: new messageType(), - }); - clearField(msg, field); - expect(isFieldSet(msg, field)).toBe(false); - }, - ); - }); - }, -); - describe("proto3 field info optional / required", () => { describeMT( { ts: TS.Proto3OptionalMessage, js: JS.Proto3OptionalMessage }, @@ -236,10 +62,7 @@ describe("proto3 field info optional / required", () => { }, ); describeMT( - { - ts: TS.Proto3UnspecifiedPackedMessage, - js: JS.Proto3UnspecifiedPackedMessage, - }, + { ts: TS.Proto3UnlabelledMessage, js: JS.Proto3UnlabelledMessage }, (messageType) => { test.each(messageType.fields.byNumber())("$name is optional", (field) => { expect(field.req).toBe(false); diff --git a/packages/protobuf/src/field-accessor.ts b/packages/protobuf/src/field-accessor.ts deleted file mode 100644 index 3d8659b83..000000000 --- a/packages/protobuf/src/field-accessor.ts +++ /dev/null @@ -1,85 +0,0 @@ -// 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. - -import type { Message } from "./message.js"; -import type { FieldInfo } from "./field.js"; -import { - isFieldSet as privateIsFieldSet, - clearField as privateClearField, -} from "./private/reflect.js"; - -/** - * Returns true if the field is set. - */ -export function isFieldSet>( - target: T, - field: MessageFieldNames, -): boolean; -export function isFieldSet(target: Message, field: FieldInfo): boolean; -export function isFieldSet>( - target: T, - field: MessageFieldNames | FieldInfo, -): boolean { - const fi = getFieldInfo(target, field); - if (fi) { - return privateIsFieldSet(fi, target); - } - return false; -} - -/** - * Resets the field, so that isFieldSet() will return false. - */ -export function clearField>( - target: T, - field: MessageFieldNames, -): void; -export function clearField(target: Message, field: FieldInfo): void; -export function clearField>( - target: T, - field: MessageFieldNames | FieldInfo, -): void { - const fi = getFieldInfo(target, field); - if (fi) { - privateClearField(fi, target); - } -} - -function getFieldInfo( - message: Message, - field: string | FieldInfo, -): FieldInfo | undefined { - if (typeof field == "string") { - return message - .getType() - .fields.list() - .find((fi) => fi.localName === field); - } - return field; -} - -// prettier-ignore -type MessageFieldNames> = Exclude ? K - : P - ]-?: true; -}, number | symbol> - -type Oneof = { - case: K | undefined; - value?: unknown; -}; diff --git a/packages/protobuf/src/index.ts b/packages/protobuf/src/index.ts index 47f74cb7e..b50dee32d 100644 --- a/packages/protobuf/src/index.ts +++ b/packages/protobuf/src/index.ts @@ -37,7 +37,6 @@ export { hasExtension, clearExtension, } from "./extension-accessor.js"; -export { isFieldSet, clearField } from "./field-accessor.js"; export type { ServiceType, MethodInfo, diff --git a/packages/protobuf/src/private/reflect.ts b/packages/protobuf/src/private/reflect.ts index d33156029..93d896319 100644 --- a/packages/protobuf/src/private/reflect.ts +++ b/packages/protobuf/src/private/reflect.ts @@ -60,12 +60,7 @@ export function clearField( if (field.repeated) { target[localName] = []; } else if (field.oneof) { - if ( - (target[field.oneof.localName] as { case: string }).case === - field.localName - ) { - target[field.oneof.localName] = { case: undefined }; - } + target[field.oneof.localName] = { case: undefined }; } else { switch (field.kind) { case "map":