Skip to content

Commit

Permalink
Add strict option to v1
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasIO committed Dec 30, 2024
1 parent 2966b9c commit 24c1bc3
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 50 deletions.
8 changes: 5 additions & 3 deletions packages/protobuf/src/codegen-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ interface CodegenInfo {
| DescOneof
| DescField
| DescService
| DescMethod,
| DescMethod
) => string;
readonly symbols: Record<RuntimeSymbolName, RuntimeSymbolInfo>;
readonly getUnwrappedFieldType: (
field: DescField | DescExtension,
field: DescField | DescExtension
) => ScalarType | undefined;
readonly wktSourceFiles: readonly string[];
/**
Expand All @@ -60,7 +60,7 @@ interface CodegenInfo {
readonly scalarDefaultValue: (type: ScalarType, longType: LongType) => any; // eslint-disable-line @typescript-eslint/no-explicit-any
readonly scalarZeroValue: <T extends ScalarType, L extends LongType>(
type: T,
longType: L,
longType: L
) => ScalarValue<T, L>;
/**
* @deprecated please use reifyWkt from @bufbuild/protoplugin/ecmascript instead
Expand All @@ -75,6 +75,7 @@ type RuntimeSymbolName =
| "proto3"
| "Message"
| "PartialMessage"
| "PartialStrictMessage"
| "PlainMessage"
| "FieldList"
| "MessageType"
Expand Down Expand Up @@ -116,6 +117,7 @@ export const codegenInfo: CodegenInfo = {
proto3: {typeOnly: false, privateImportPath: "./proto3.js", publicImportPath: packageName},
Message: {typeOnly: false, privateImportPath: "./message.js", publicImportPath: packageName},
PartialMessage: {typeOnly: true, privateImportPath: "./message.js", publicImportPath: packageName},
PartialStrictMessage: {typeOnly: true, privateImportPath: "./message.js", publicImportPath: packageName},
PlainMessage: {typeOnly: true, privateImportPath: "./message.js", publicImportPath: packageName},
FieldList: {typeOnly: true, privateImportPath: "./field-list.js", publicImportPath: packageName},
MessageType: {typeOnly: true, privateImportPath: "./message-type.js", publicImportPath: packageName},
Expand Down
7 changes: 6 additions & 1 deletion packages/protobuf/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ export { protoDelimited } from "./proto-delimited.js";
export { codegenInfo } from "./codegen-info.js";

export { Message } from "./message.js";
export type { AnyMessage, PartialMessage, PlainMessage } from "./message.js";
export type {
AnyMessage,
PartialMessage,
PartialStrictMessage,
PlainMessage,
} from "./message.js";
export { isMessage } from "./is-message.js";

export type { FieldInfo, OneofInfo } from "./field.js";
Expand Down
20 changes: 18 additions & 2 deletions packages/protobuf/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class Message<T extends Message<T> = AnyMessage> {
return this.getType().runtime.util.equals(
this.getType(),
this as unknown as T,
other,
other
);
}

Expand Down Expand Up @@ -96,7 +96,7 @@ export class Message<T extends Message<T> = AnyMessage> {
throw new Error(
`cannot decode ${this.getType().typeName} from JSON: ${
e instanceof Error ? e.message : String(e)
}`,
}`
);
}
return this.fromJson(json, options);
Expand Down Expand Up @@ -204,6 +204,22 @@ export type PartialMessage<T extends Message<T>> = {
[P in keyof T as T[P] extends Function ? never : P]?: PartialField<T[P]>;
};

export type PartialStrictMessage<T extends Message<T>> = {
// eslint-disable-next-line @typescript-eslint/ban-types -- we use `Function` to identify methods
[P in keyof T as T[P] extends Function ? never : P]: PartialStrictField<T[P]>;
};

// prettier-ignore
type PartialStrictField<F> =
F extends (Date | Uint8Array | bigint | boolean | string | number) ? F
: F extends Array<infer U> ? Array<PartialField<U>>
: F extends ReadonlyArray<infer U> ? ReadonlyArray<PartialField<U>>
: F extends Message<infer U> ? PartialStrictMessage<U>
: F extends OneofSelectedMessage<infer C, infer V> ? {case: C; value: PartialStrictMessage<V>}
: F extends { case: string | undefined; value?: unknown; } ? F
: F extends {[key: string|number]: Message<infer U>} ? {[key: string|number]: PartialStrictMessage<U>}
: F ;

// prettier-ignore
type PartialField<F> =
F extends (Date | Uint8Array | bigint | boolean | string | number) ? F
Expand Down
14 changes: 8 additions & 6 deletions packages/protoc-gen-es/src/declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ function generateEnum(schema: Schema, f: GeneratedFile, enumeration: DescEnum) {
function generateMessage(schema: Schema, f: GeneratedFile, message: DescMessage) {
const protoN = getNonEditionRuntime(schema, message.file);
const {
PartialMessage,
FieldList,
Message,
PlainMessage,
PartialStrictMessage,
PartialMessage,
BinaryReadOptions,
JsonReadOptions,
JsonValue
Expand All @@ -86,7 +87,8 @@ function generateMessage(schema: Schema, f: GeneratedFile, message: DescMessage)
}
f.print();
}
f.print(" constructor(data?: ", PartialMessage, "<", m, ">);");
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
f.print(" constructor(data?: ", schema.strict ? PartialStrictMessage : PartialMessage, "<", m, ">);");
f.print();
generateWktMethods(schema, f, message);
f.print(" static readonly runtime: typeof ", protoN, ";");
Expand Down Expand Up @@ -125,7 +127,7 @@ function generateOneof(schema: Schema, f: GeneratedFile, oneof: DescOneof) {
f.print(` } | {`);
}
f.print(f.jsDoc(field, " "));
const { typing } = getFieldTypeInfo(field);
const { typing } = getFieldTypeInfo(field, schema.strict);
f.print(` value: `, typing, `;`);
f.print(` case: "`, localName(field), `";`);
}
Expand All @@ -136,7 +138,7 @@ function generateOneof(schema: Schema, f: GeneratedFile, oneof: DescOneof) {
function generateField(schema: Schema, f: GeneratedFile, field: DescField) {
f.print(f.jsDoc(field, " "));
const e: Printable = [];
const { typing, optional } = getFieldTypeInfo(field);
const { typing, optional } = getFieldTypeInfo(field, schema.strict);
if (!optional) {
e.push(" ", localName(field), ": ", typing, ";");
} else {
Expand All @@ -151,7 +153,7 @@ function generateExtension(
f: GeneratedFile,
ext: DescExtension,
) {
const { typing } = getFieldTypeInfo(ext);
const { typing } = getFieldTypeInfo(ext, schema.strict);
const e = f.import(ext.extendee).toTypeOnly();
f.print(f.jsDoc(ext));
f.print(f.exportDecl("declare const", localName(ext)), ": ", schema.runtime.Extension, "<", e, ", ", typing, ">;");
Expand Down Expand Up @@ -232,7 +234,7 @@ function generateWktStaticMethods(schema: Schema, f: GeneratedFile, message: Des
case "google.protobuf.BoolValue":
case "google.protobuf.StringValue":
case "google.protobuf.BytesValue": {
const {typing} = getFieldTypeInfo(ref.value);
const {typing} = getFieldTypeInfo(ref.value, schema.strict);
f.print(" static readonly fieldWrapper: {")
f.print(" wrapField(value: ", typing, "): ", message, ",")
f.print(" unwrapField(value: ", message, "): ", typing, ",")
Expand Down
11 changes: 6 additions & 5 deletions packages/protoc-gen-es/src/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function generateMessage(schema: Schema, f: GeneratedFile, message: DescMessage)
const protoN = getNonEditionRuntime(schema, message.file);
const {
PartialMessage,
PartialStrictMessage,
FieldList,
Message,
PlainMessage,
Expand All @@ -94,7 +95,7 @@ function generateMessage(schema: Schema, f: GeneratedFile, message: DescMessage)
}
f.print();
}
f.print(" constructor(data?: ", PartialMessage, "<", message, ">) {");
f.print(" constructor(data?: ", schema.strict ? PartialStrictMessage : PartialMessage, "<", message, ">) {");
f.print(" super();");
f.print(" ", protoN, ".util.initPartial(data, this);");
f.print(" }");
Expand Down Expand Up @@ -148,7 +149,7 @@ function generateOneof(schema: Schema, f: GeneratedFile, oneof: DescOneof) {
f.print(` } | {`);
}
f.print(f.jsDoc(field, " "));
const { typing } = getFieldTypeInfo(field);
const { typing } = getFieldTypeInfo(field, schema.strict);
f.print(` value: `, typing, `;`);
f.print(` case: "`, localName(field), `";`);
}
Expand All @@ -159,7 +160,7 @@ function generateOneof(schema: Schema, f: GeneratedFile, oneof: DescOneof) {
function generateField(schema: Schema, f: GeneratedFile, field: DescField) {
f.print(f.jsDoc(field, " "));
const e: Printable = [];
const { typing, optional, typingInferrableFromZeroValue } = getFieldTypeInfo(field);
const { typing, optional, typingInferrableFromZeroValue } = getFieldTypeInfo(field, schema.strict);
if (optional) {
e.push(" ", localName(field), "?: ", typing, ";");
} else {
Expand All @@ -184,7 +185,7 @@ function generateExtension(
ext: DescExtension,
) {
const protoN = getNonEditionRuntime(schema, ext.file);
const { typing } = getFieldTypeInfo(ext);
const { typing } = getFieldTypeInfo(ext, schema.strict);
f.print(f.jsDoc(ext));
f.print(f.exportDecl("const", ext), " = ", protoN, ".makeExtension<", ext.extendee, ", ", typing, ">(");
f.print(" ", f.string(ext.typeName), ", ");
Expand Down Expand Up @@ -651,7 +652,7 @@ function generateWktStaticMethods(schema: Schema, f: GeneratedFile, message: Des
case "google.protobuf.BoolValue":
case "google.protobuf.StringValue":
case "google.protobuf.BytesValue": {
const {typing} = getFieldTypeInfo(ref.value);
const {typing} = getFieldTypeInfo(ref.value, schema.strict);
f.print(" static readonly fieldWrapper = {")
f.print(" wrapField(value: ", typing, "): ", message, " {")
f.print(" return new ", message, "({value});")
Expand Down
35 changes: 19 additions & 16 deletions packages/protoc-gen-es/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import {
import type { Printable } from "@bufbuild/protoplugin/ecmascript";
import { localName } from "@bufbuild/protoplugin/ecmascript";

export function getFieldTypeInfo(field: DescField | DescExtension): {
export function getFieldTypeInfo(
field: DescField | DescExtension,
strict: boolean
): {
typing: Printable;
optional: boolean;
typingInferrableFromZeroValue: boolean;
Expand All @@ -38,7 +41,7 @@ export function getFieldTypeInfo(field: DescField | DescExtension): {
typing.push(scalarTypeScriptType(field.scalar, field.longType));
optional =
field.optional ||
field.proto.label === FieldDescriptorProto_Label.REQUIRED;
(!strict && field.proto.label === FieldDescriptorProto_Label.REQUIRED);
typingInferrableFromZeroValue = true;
break;
case "message": {
Expand All @@ -64,7 +67,7 @@ export function getFieldTypeInfo(field: DescField | DescExtension): {
});
optional =
field.optional ||
field.proto.label === FieldDescriptorProto_Label.REQUIRED;
(!strict && field.proto.label === FieldDescriptorProto_Label.REQUIRED);
typingInferrableFromZeroValue = true;
break;
case "map": {
Expand All @@ -86,7 +89,7 @@ export function getFieldTypeInfo(field: DescField | DescExtension): {
case "scalar":
valueType = scalarTypeScriptType(
field.mapValue.scalar,
LongType.BIGINT,
LongType.BIGINT
);
break;
case "message":
Expand Down Expand Up @@ -127,7 +130,7 @@ export function getFieldDefaultValueExpression(
enumAs:
| "enum_value_as_is"
| "enum_value_as_integer"
| "enum_value_as_cast_integer" = "enum_value_as_is",
| "enum_value_as_cast_integer" = "enum_value_as_is"
): Printable | undefined {
if (field.repeated) {
return undefined;
Expand All @@ -142,11 +145,11 @@ export function getFieldDefaultValueExpression(
switch (field.fieldKind) {
case "enum": {
const enumValue = field.enum.values.find(
(value) => value.number === defaultValue,
(value) => value.number === defaultValue
);
if (enumValue === undefined) {
throw new Error(
`invalid enum default value: ${String(defaultValue)} for ${enumValue}`,
`invalid enum default value: ${String(defaultValue)} for ${enumValue}`
);
}
return literalEnumValue(enumValue, enumAs);
Expand All @@ -171,7 +174,7 @@ export function getFieldZeroValueExpression(
enumAs:
| "enum_value_as_is"
| "enum_value_as_integer"
| "enum_value_as_cast_integer" = "enum_value_as_is",
| "enum_value_as_cast_integer" = "enum_value_as_is"
): Printable | undefined {
if (field.repeated) {
return "[]";
Expand All @@ -193,7 +196,7 @@ export function getFieldZeroValueExpression(
case "scalar": {
const defaultValue = codegenInfo.scalarZeroValue(
field.scalar,
field.longType,
field.longType
);
return literalScalarValue(defaultValue, field);
}
Expand All @@ -202,7 +205,7 @@ export function getFieldZeroValueExpression(

function literalScalarValue(
value: ScalarValue,
field: (DescField | DescExtension) & { fieldKind: "scalar" },
field: (DescField | DescExtension) & { fieldKind: "scalar" }
): Printable {
switch (field.scalar) {
case ScalarType.DOUBLE:
Expand All @@ -214,28 +217,28 @@ function literalScalarValue(
case ScalarType.SINT32:
if (typeof value != "number") {
throw new Error(
`Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`,
`Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`
);
}
return value;
case ScalarType.BOOL:
if (typeof value != "boolean") {
throw new Error(
`Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`,
`Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`
);
}
return value;
case ScalarType.STRING:
if (typeof value != "string") {
throw new Error(
`Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`,
`Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`
);
}
return { kind: "es_string", value };
case ScalarType.BYTES:
if (!(value instanceof Uint8Array)) {
throw new Error(
`Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`,
`Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`
);
}
return value;
Expand All @@ -246,7 +249,7 @@ function literalScalarValue(
case ScalarType.FIXED64:
if (typeof value != "bigint" && typeof value != "string") {
throw new Error(
`Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`,
`Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`
);
}
return {
Expand All @@ -263,7 +266,7 @@ function literalEnumValue(
enumAs:
| "enum_value_as_is"
| "enum_value_as_integer"
| "enum_value_as_cast_integer",
| "enum_value_as_cast_integer"
): Printable {
switch (enumAs) {
case "enum_value_as_is":
Expand Down
Loading

0 comments on commit 24c1bc3

Please sign in to comment.