Skip to content

Commit

Permalink
feat: add an option to disable Exact types (#456)
Browse files Browse the repository at this point in the history
  • Loading branch information
aikoven authored Dec 30, 2021
1 parent e5f4c3d commit 9c53d7e
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 5 deletions.
4 changes: 4 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,10 @@ Generated code will be placed in the Gradle build directory.

- With `--ts_proto_opt=enumsAsLiterals=true`, the generated enum types will be enum-ish object with `as const`.

- With `--ts_proto_opt=useExactTypes=false`, the generated `fromPartial` method will not use Exact types.

The default behavior is `useExactTypes=true`, which makes `fromPartial` use Exact type for its argument to make TypeScript reject any unknown properties.

### Only Types

If you're looking for `ts-proto` to generate only types for your Protobuf types then passing all three of `outputEncodeMethods`, `outputJsonMethods`, and `outputClientImpl` as `false` is probably what you want, i.e.:
Expand Down
Binary file added integration/use-exact-types-false/foo.bin
Binary file not shown.
8 changes: 8 additions & 0 deletions integration/use-exact-types-false/foo.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
syntax = "proto3";

package foo;

message Foo {
string bar = 1;
string baz = 2;
}
87 changes: 87 additions & 0 deletions integration/use-exact-types-false/foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/* eslint-disable */
import { util, configure, Writer, Reader } from 'protobufjs/minimal';
import * as Long from 'long';

export const protobufPackage = 'foo';

export interface Foo {
bar: string;
baz: string;
}

function createBaseFoo(): Foo {
return { bar: '', baz: '' };
}

export const Foo = {
encode(message: Foo, writer: Writer = Writer.create()): Writer {
if (message.bar !== '') {
writer.uint32(10).string(message.bar);
}
if (message.baz !== '') {
writer.uint32(18).string(message.baz);
}
return writer;
},

decode(input: Reader | Uint8Array, length?: number): Foo {
const reader = input instanceof Reader ? input : new Reader(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseFoo();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
message.bar = reader.string();
break;
case 2:
message.baz = reader.string();
break;
default:
reader.skipType(tag & 7);
break;
}
}
return message;
},

fromJSON(object: any): Foo {
const message = createBaseFoo();
message.bar = object.bar !== undefined && object.bar !== null ? String(object.bar) : '';
message.baz = object.baz !== undefined && object.baz !== null ? String(object.baz) : '';
return message;
},

toJSON(message: Foo): unknown {
const obj: any = {};
message.bar !== undefined && (obj.bar = message.bar);
message.baz !== undefined && (obj.baz = message.baz);
return obj;
},

fromPartial(object: DeepPartial<Foo>): Foo {
const message = createBaseFoo();
message.bar = object.bar ?? '';
message.baz = object.baz ?? '';
return message;
},
};

type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;

export type DeepPartial<T> = T extends Builtin
? T
: T extends Array<infer U>
? Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U>
? ReadonlyArray<DeepPartial<U>>
: T extends {}
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;

// If you get a compile-error about 'Constructor<Long> and ... have no overlap',
// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'.
if (util.Long !== Long) {
util.Long = Long as any;
configure();
}
1 change: 1 addition & 0 deletions integration/use-exact-types-false/parameters.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
useExactTypes=false
17 changes: 12 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1249,14 +1249,21 @@ function generateToJson(ctx: Context, fullName: string, messageDesc: DescriptorP
function generateFromPartial(ctx: Context, fullName: string, messageDesc: DescriptorProto): Code {
const { options, utils, typeMap } = ctx;
const chunks: Code[] = [];
const Timestamp = impProto(options, 'google/protobuf/timestamp', 'Timestamp');

// create the basic function declaration
const paramName = messageDesc.field.length > 0 ? 'object' : '_';
chunks.push(code`
fromPartial<I extends ${utils.Exact}<${utils.DeepPartial}<${fullName}>, I>>(${paramName}: I): ${fullName} {
const message = createBase${fullName}();
`);

if (ctx.options.useExactTypes) {
chunks.push(code`
fromPartial<I extends ${utils.Exact}<${utils.DeepPartial}<${fullName}>, I>>(${paramName}: I): ${fullName} {
`);
} else {
chunks.push(code`
fromPartial(${paramName}: ${utils.DeepPartial}<${fullName}>): ${fullName} {
`);
}

chunks.push(code`const message = createBase${fullName}();`);

// add a check for each incoming field
messageDesc.field.forEach((field) => {
Expand Down
2 changes: 2 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export type Options = {
// An alias of !output
onlyTypes: boolean;
emitImportedFiles: boolean;
useExactTypes: boolean;
};

export function defaultOptions(): Options {
Expand Down Expand Up @@ -90,6 +91,7 @@ export function defaultOptions(): Options {
outputSchema: false,
onlyTypes: false,
emitImportedFiles: true,
useExactTypes: true,
};
}

Expand Down
1 change: 1 addition & 0 deletions tests/options-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('options', () => {
"stringEnums": false,
"unrecognizedEnum": true,
"useDate": "timestamp",
"useExactTypes": true,
"useOptionals": "none",
}
`);
Expand Down

0 comments on commit 9c53d7e

Please sign in to comment.