Skip to content

Commit 7080556

Browse files
committed
Introduce subpath export "wire"
In order to reduce the number of main exports, move types that are rarely needed by end-users to a new subpath "wire". - BinaryWriter and BinaryReader have been moved, and their interfaces discarded. - Users can bring their own Text encoding API via wire/configureTextEncoding() now instead of the serialization options readerFactory / writerFactory. - protoBase64 has been split into the functions base64Decode and base64Encode, with the latter allowing to encode with padding, without padding, or URL-safe. The exports protoDelimited and protoBase64 are also candidates for moving to wire, but are not moved by this commit.
1 parent ea52fa2 commit 7080556

23 files changed

+1294
-624
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ lint: node_modules $(BUILD)/protobuf $(BUILD)/protobuf-test $(BUILD)/protobuf-co
145145
.PHONY: format
146146
format: node_modules ## Format all files, adding license headers
147147
npx prettier --write '**/*.{json,js,jsx,ts,tsx,css,mjs,cjs}' --log-level error
148-
npx license-header --ignore packages/protobuf/src/google/varint.ts
148+
npx license-header --ignore 'packages/protobuf/src/**/varint.ts'
149149

150150
.PHONY: bench
151151
bench: node_modules $(GEN)/protobuf-bench $(BUILD)/protobuf ## Benchmark code size

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,6 @@ The generated code is compatible with TypeScript **v4.1.2** or later, with the d
129129

130130
## Copyright
131131

132-
The [code to encode and decode varint](packages/protobuf/src/google/varint.ts) is Copyright 2008 Google Inc., licensed
132+
The [code to encode and decode varint](packages/protobuf/src/next/wire/varint.ts) is Copyright 2008 Google Inc., licensed
133133
under BSD-3-Clause.
134134
All other files are licensed under Apache-2.0, see [LICENSE](LICENSE).

packages/protobuf-bench/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ server would usually do.
1010

1111
| code generator | bundle size | minified | compressed |
1212
|---------------------|------------------------:|-----------------------:|-------------------:|
13-
| protobuf-es | 98,902 b | 42,212 b | 11,004 b |
13+
| protobuf-es | 100,769 b | 42,988 b | 11,232 b |
1414
| protobuf-javascript | 394,384 b | 288,654 b | 45,122 b |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
// Copyright 2021-2024 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { describe, expect, test } from "@jest/globals";
16+
import { base64Decode, base64Encode } from "@bufbuild/protobuf/next/wire";
17+
18+
// The following test cases match https://cs.opensource.google/go/go/+/master:src/encoding/base64/base64_test.go;l=25
19+
// One example specific for URL encoding was added.
20+
const exampleCases: {
21+
name: string;
22+
bytes: Uint8Array | string;
23+
std: string;
24+
std_raw: string;
25+
url: string;
26+
}[] = [
27+
{ name: "RFC 3548 example #1", bytes: "", std: "", std_raw: "", url: "" },
28+
{
29+
name: "RFC 3548 example #1",
30+
bytes: new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]),
31+
std: "FPucA9l+",
32+
std_raw: "FPucA9l+",
33+
url: "FPucA9l-",
34+
},
35+
{
36+
name: "RFC 3548 example #2",
37+
bytes: new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9]),
38+
std: "FPucA9k=",
39+
std_raw: "FPucA9k",
40+
url: "FPucA9k",
41+
},
42+
{
43+
name: "RFC 3548 example #3",
44+
bytes: new Uint8Array([0x14, 0xfb, 0x9c, 0x03]),
45+
std: "FPucAw==",
46+
std_raw: "FPucAw",
47+
url: "FPucAw",
48+
},
49+
50+
{ name: "RFC 4648 example #1", bytes: "", std: "", std_raw: "", url: "" },
51+
{
52+
name: "RFC 4648 example #2",
53+
bytes: "f",
54+
std: "Zg==",
55+
std_raw: "Zg",
56+
url: "Zg",
57+
},
58+
{
59+
name: "RFC 4648 example #4",
60+
bytes: "fo",
61+
std: "Zm8=",
62+
std_raw: "Zm8",
63+
url: "Zm8",
64+
},
65+
{
66+
name: "RFC 4648 example #5",
67+
bytes: "foo",
68+
std: "Zm9v",
69+
std_raw: "Zm9v",
70+
url: "Zm9v",
71+
},
72+
{
73+
name: "RFC 4648 example #6",
74+
bytes: "foob",
75+
std: "Zm9vYg==",
76+
std_raw: "Zm9vYg",
77+
url: "Zm9vYg",
78+
},
79+
{
80+
name: "RFC 4648 example #7",
81+
bytes: "fooba",
82+
std: "Zm9vYmE=",
83+
std_raw: "Zm9vYmE",
84+
url: "Zm9vYmE",
85+
},
86+
{
87+
name: "RFC 4648 example #8",
88+
bytes: "foobar",
89+
std: "Zm9vYmFy",
90+
std_raw: "Zm9vYmFy",
91+
url: "Zm9vYmFy",
92+
},
93+
94+
{
95+
name: "Wikipedia example #1",
96+
bytes: "sure.",
97+
std: "c3VyZS4=",
98+
std_raw: "c3VyZS4",
99+
url: "c3VyZS4",
100+
},
101+
{
102+
name: "Wikipedia example #2",
103+
bytes: "sure",
104+
std: "c3VyZQ==",
105+
std_raw: "c3VyZQ",
106+
url: "c3VyZQ",
107+
},
108+
{
109+
name: "Wikipedia example #3",
110+
bytes: "sur",
111+
std: "c3Vy",
112+
std_raw: "c3Vy",
113+
url: "c3Vy",
114+
},
115+
{
116+
name: "Wikipedia example #4",
117+
bytes: "su",
118+
std: "c3U=",
119+
std_raw: "c3U",
120+
url: "c3U",
121+
},
122+
{
123+
name: "Wikipedia example #5",
124+
bytes: "leasure.",
125+
std: "bGVhc3VyZS4=",
126+
std_raw: "bGVhc3VyZS4",
127+
url: "bGVhc3VyZS4",
128+
},
129+
{
130+
name: "Wikipedia example #6",
131+
bytes: "easure.",
132+
std: "ZWFzdXJlLg==",
133+
std_raw: "ZWFzdXJlLg",
134+
url: "ZWFzdXJlLg",
135+
},
136+
{
137+
name: "Wikipedia example #7",
138+
bytes: "asure.",
139+
std: "YXN1cmUu",
140+
std_raw: "YXN1cmUu",
141+
url: "YXN1cmUu",
142+
},
143+
{
144+
name: "Wikipedia example #8",
145+
bytes: "sure.",
146+
std: "c3VyZS4=",
147+
std_raw: "c3VyZS4",
148+
url: "c3VyZS4",
149+
},
150+
151+
{
152+
name: "Example with URL relevant characters #1",
153+
bytes: "<<???>>",
154+
std: "PDw/Pz8+Pg==",
155+
std_raw: "PDw/Pz8+Pg",
156+
url: "PDw_Pz8-Pg",
157+
},
158+
];
159+
160+
describe("base64Decode()", () => {
161+
test.each(exampleCases)("decodes $name", ({ bytes, std }) => {
162+
expect(base64Decode(std)).toStrictEqual(
163+
bytes instanceof Uint8Array ? bytes : new TextEncoder().encode(bytes),
164+
);
165+
});
166+
test.each(exampleCases)("decodes std_raw $name", ({ bytes, std_raw }) => {
167+
expect(base64Decode(std_raw)).toStrictEqual(
168+
bytes instanceof Uint8Array ? bytes : new TextEncoder().encode(bytes),
169+
);
170+
});
171+
test.each(exampleCases)("decodes url $name", ({ bytes, url }) => {
172+
expect(base64Decode(url)).toStrictEqual(
173+
bytes instanceof Uint8Array ? bytes : new TextEncoder().encode(bytes),
174+
);
175+
});
176+
test.each([
177+
"c3VyZQ==",
178+
"c3VyZQ== ",
179+
"c3VyZQ==\t",
180+
"c3VyZQ==\r",
181+
"c3VyZQ==\n",
182+
"c3VyZQ==\r\n",
183+
"c3VyZ\r\nQ==",
184+
"c3V\ryZ\nQ==",
185+
"c3V\nyZ\rQ==",
186+
"c3VyZ\nQ==",
187+
"c3VyZQ\n==",
188+
"c3VyZQ=\n=",
189+
"c3VyZQ=\r\n\r\n=",
190+
])("ignores white-space, including line breaks and tabs in %s", (b64) => {
191+
expect(base64Decode(b64)).toStrictEqual(new TextEncoder().encode("sure"));
192+
});
193+
test.each([
194+
"c3VyZQ==c3VyZQ==",
195+
"c3VyZQ==\nc3VyZQ==",
196+
"c3VyZQ==\tc3VyZQ==",
197+
"c3VyZQ==\rc3VyZQ==",
198+
"c3VyZQ== c3VyZQ==",
199+
])("allows inner padding in %s", (b64) => {
200+
expect(base64Decode(b64)).toStrictEqual(
201+
new TextEncoder().encode("suresure"),
202+
);
203+
});
204+
test("does not require padding", () => {
205+
expect(base64Decode("c3VyZQ")).toStrictEqual(
206+
new TextEncoder().encode("sure"),
207+
);
208+
});
209+
test.each([
210+
"c3VyZQ==",
211+
"c3VyZQ==\r",
212+
"c3VyZQ==\n",
213+
"c3VyZQ==\r\n",
214+
"c3VyZ\r\nQ==",
215+
"c3V\ryZ\nQ==",
216+
"c3V\nyZ\rQ==",
217+
"c3VyZ\nQ==",
218+
"c3VyZQ\n==",
219+
"c3VyZQ=\n=",
220+
"c3VyZQ=\r\n\r\n=",
221+
])("ignores whitespace in %s", (b64) => {
222+
expect(base64Decode(b64)).toStrictEqual(new TextEncoder().encode("sure"));
223+
});
224+
test("understands URL encoding", () => {
225+
const b64 = "PDw_Pz8-Pg";
226+
const bytes = base64Decode(b64);
227+
expect(bytes).toStrictEqual(new TextEncoder().encode("<<???>>"));
228+
});
229+
});
230+
231+
describe("base64Encode()", () => {
232+
test.each(exampleCases)("std encodes $name", ({ bytes, std }) => {
233+
const input =
234+
bytes instanceof Uint8Array ? bytes : new TextEncoder().encode(bytes);
235+
expect(base64Encode(input)).toBe(std);
236+
});
237+
test.each(exampleCases)("std_raw encodes $name", ({ bytes, std_raw }) => {
238+
const input =
239+
bytes instanceof Uint8Array ? bytes : new TextEncoder().encode(bytes);
240+
expect(base64Encode(input, "std_raw")).toBe(std_raw);
241+
});
242+
test.each(exampleCases)("url encodes $name", ({ bytes, url }) => {
243+
const input =
244+
bytes instanceof Uint8Array ? bytes : new TextEncoder().encode(bytes);
245+
expect(base64Encode(input, "url")).toBe(url);
246+
});
247+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright 2021-2024 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { beforeEach, describe, expect, test } from "@jest/globals";
16+
import {
17+
getTextEncoding,
18+
configureTextEncoding,
19+
} from "@bufbuild/protobuf/next/wire";
20+
import { afterEach } from "node:test";
21+
22+
describe("getTextEncoding()", () => {
23+
test("returns TextEncoding", () => {
24+
const te = getTextEncoding();
25+
expect(te).toBeDefined();
26+
});
27+
test("returns same TextEncoding", () => {
28+
const te1 = getTextEncoding();
29+
const te2 = getTextEncoding();
30+
expect(te2).toBe(te1);
31+
});
32+
describe("encodeUtf8()", () => {
33+
test("encodes", () => {
34+
const bytes = getTextEncoding().encodeUtf8("hello 🌍");
35+
expect(bytes).toStrictEqual(
36+
new Uint8Array([104, 101, 108, 108, 111, 32, 240, 159, 140, 141]),
37+
);
38+
});
39+
});
40+
describe("decodeUtf8()", () => {
41+
test("decodes", () => {
42+
const text = getTextEncoding().decodeUtf8(
43+
new Uint8Array([104, 101, 108, 108, 111, 32, 240, 159, 140, 141]),
44+
);
45+
expect(text).toBe("hello 🌍");
46+
});
47+
});
48+
describe("checkUtf8()", () => {
49+
test("returns true for valid", () => {
50+
const valid = "🌍";
51+
const ok = getTextEncoding().checkUtf8(valid);
52+
expect(ok).toBe(true);
53+
});
54+
test("returns false for invalid", () => {
55+
const invalid = "🌍".substring(0, 1);
56+
const ok = getTextEncoding().checkUtf8(invalid);
57+
expect(ok).toBe(false);
58+
});
59+
});
60+
});
61+
62+
describe("configureTextEncoding()", () => {
63+
let backup: ReturnType<typeof getTextEncoding>;
64+
beforeEach(() => {
65+
backup = getTextEncoding();
66+
});
67+
afterEach(() => {
68+
configureTextEncoding(backup);
69+
});
70+
test("configures checkUtf8", () => {
71+
configureTextEncoding({
72+
checkUtf8(text: string): boolean {
73+
if (text === "valid") {
74+
return true;
75+
}
76+
return false;
77+
},
78+
decodeUtf8: backup.decodeUtf8,
79+
encodeUtf8: backup.encodeUtf8,
80+
});
81+
expect(getTextEncoding().checkUtf8("valid")).toBe(true);
82+
expect(getTextEncoding().checkUtf8("no valid")).toBe(false);
83+
});
84+
test("configures checkUtf8", () => {
85+
configureTextEncoding({
86+
checkUtf8: backup.checkUtf8,
87+
decodeUtf8: backup.decodeUtf8,
88+
encodeUtf8: backup.encodeUtf8,
89+
});
90+
expect(getTextEncoding().checkUtf8("valid")).toBe(true);
91+
expect(getTextEncoding().checkUtf8("no valid")).toBe(false);
92+
});
93+
test("configures decodeUtf8", () => {
94+
let arg: Uint8Array | undefined;
95+
configureTextEncoding({
96+
checkUtf8: backup.checkUtf8,
97+
decodeUtf8(bytes: Uint8Array): string {
98+
arg = bytes;
99+
return "custom decodeUtf8";
100+
},
101+
encodeUtf8: backup.encodeUtf8,
102+
});
103+
const bytes = new Uint8Array(10);
104+
const text = getTextEncoding().decodeUtf8(bytes);
105+
expect(text).toBe("custom decodeUtf8");
106+
expect(arg).toBe(bytes);
107+
});
108+
test("configures encodeUtf8", () => {
109+
let arg: string | undefined;
110+
configureTextEncoding({
111+
checkUtf8: backup.checkUtf8,
112+
decodeUtf8: backup.decodeUtf8,
113+
encodeUtf8(text: string): Uint8Array {
114+
arg = text;
115+
return new Uint8Array(10);
116+
},
117+
});
118+
expect(getTextEncoding().encodeUtf8("test")).toStrictEqual(
119+
new Uint8Array(10),
120+
);
121+
expect(arg).toBe("test");
122+
});
123+
});

0 commit comments

Comments
 (0)