Skip to content

Commit

Permalink
fix: Type Error when map contains string enums #382 (#529)
Browse files Browse the repository at this point in the history
* fix: Type Error when map contains string enums

* regenerate ts

* update bins

* Refactor detectMapType to return keyField+valueField as well.

Co-authored-by: Stephen Haberman <stephen.haberman@gmail.com>
  • Loading branch information
boukeversteegh and stephenh committed Mar 13, 2022
1 parent 2eed34d commit c2107b9
Show file tree
Hide file tree
Showing 24 changed files with 266 additions and 52 deletions.
Binary file modified integration/const-enum/const-enum.bin
Binary file not shown.
1 change: 1 addition & 0 deletions integration/const-enum/const-enum.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ message DividerData {
}

DividerType type = 1;
map<string, DividerType> typeMap = 2;
}
100 changes: 99 additions & 1 deletion integration/const-enum/const-enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const protobufPackage = '';

export interface DividerData {
type: DividerData_DividerType;
typeMap: { [key: string]: DividerData_DividerType };
}

export const enum DividerData_DividerType {
Expand Down Expand Up @@ -67,15 +68,23 @@ export function dividerData_DividerTypeToNumber(object: DividerData_DividerType)
}
}

export interface DividerData_TypeMapEntry {
key: string;
value: DividerData_DividerType;
}

function createBaseDividerData(): DividerData {
return { type: DividerData_DividerType.DOUBLE };
return { type: DividerData_DividerType.DOUBLE, typeMap: {} };
}

export const DividerData = {
encode(message: DividerData, writer: Writer = Writer.create()): Writer {
if (message.type !== DividerData_DividerType.DOUBLE) {
writer.uint32(8).int32(dividerData_DividerTypeToNumber(message.type));
}
Object.entries(message.typeMap).forEach(([key, value]) => {
DividerData_TypeMapEntry.encode({ key: key as any, value }, writer.uint32(18).fork()).ldelim();
});
return writer;
},

Expand All @@ -89,6 +98,12 @@ export const DividerData = {
case 1:
message.type = dividerData_DividerTypeFromJSON(reader.int32());
break;
case 2:
const entry2 = DividerData_TypeMapEntry.decode(reader, reader.uint32());
if (entry2.value !== undefined) {
message.typeMap[entry2.key] = entry2.value;
}
break;
default:
reader.skipType(tag & 7);
break;
Expand All @@ -100,18 +115,97 @@ export const DividerData = {
fromJSON(object: any): DividerData {
return {
type: isSet(object.type) ? dividerData_DividerTypeFromJSON(object.type) : DividerData_DividerType.DOUBLE,
typeMap: isObject(object.typeMap)
? Object.entries(object.typeMap).reduce<{ [key: string]: DividerData_DividerType }>((acc, [key, value]) => {
acc[key] = value as DividerData_DividerType;
return acc;
}, {})
: {},
};
},

toJSON(message: DividerData): unknown {
const obj: any = {};
message.type !== undefined && (obj.type = dividerData_DividerTypeToJSON(message.type));
obj.typeMap = {};
if (message.typeMap) {
Object.entries(message.typeMap).forEach(([k, v]) => {
obj.typeMap[k] = dividerData_DividerTypeToJSON(v);
});
}
return obj;
},

fromPartial<I extends Exact<DeepPartial<DividerData>, I>>(object: I): DividerData {
const message = createBaseDividerData();
message.type = object.type ?? DividerData_DividerType.DOUBLE;
message.typeMap = Object.entries(object.typeMap ?? {}).reduce<{ [key: string]: DividerData_DividerType }>(
(acc, [key, value]) => {
if (value !== undefined) {
acc[key] = value as DividerData_DividerType;
}
return acc;
},
{}
);
return message;
},
};

function createBaseDividerData_TypeMapEntry(): DividerData_TypeMapEntry {
return { key: '', value: DividerData_DividerType.DOUBLE };
}

export const DividerData_TypeMapEntry = {
encode(message: DividerData_TypeMapEntry, writer: Writer = Writer.create()): Writer {
if (message.key !== '') {
writer.uint32(10).string(message.key);
}
if (message.value !== DividerData_DividerType.DOUBLE) {
writer.uint32(16).int32(dividerData_DividerTypeToNumber(message.value));
}
return writer;
},

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

fromJSON(object: any): DividerData_TypeMapEntry {
return {
key: isSet(object.key) ? String(object.key) : '',
value: isSet(object.value) ? dividerData_DividerTypeFromJSON(object.value) : DividerData_DividerType.DOUBLE,
};
},

toJSON(message: DividerData_TypeMapEntry): unknown {
const obj: any = {};
message.key !== undefined && (obj.key = message.key);
message.value !== undefined && (obj.value = dividerData_DividerTypeToJSON(message.value));
return obj;
},

fromPartial<I extends Exact<DeepPartial<DividerData_TypeMapEntry>, I>>(object: I): DividerData_TypeMapEntry {
const message = createBaseDividerData_TypeMapEntry();
message.key = object.key ?? '';
message.value = object.value ?? DividerData_DividerType.DOUBLE;
return message;
},
};
Expand Down Expand Up @@ -140,6 +234,10 @@ if (util.Long !== Long) {
configure();
}

function isObject(value: any): boolean {
return typeof value === 'object' && value !== null;
}

function isSet(value: any): boolean {
return value !== null && value !== undefined;
}
4 changes: 2 additions & 2 deletions integration/grpc-js/google/protobuf/struct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ export interface Struct_FieldsEntry {
/**
* `Value` represents a dynamically typed value which can be either
* null, a number, a string, a boolean, a recursive struct value, or a
* list of values. A producer of value is expected to set one of these
* variants. Absence of any variant indicates an error.
* list of values. A producer of value is expected to set one of that
* variants, absence of any variant indicates an error.
*
* The JSON representation for `Value` is JSON value.
*/
Expand Down
Binary file modified integration/grpc-js/google/protobuf/wrappers.bin
Binary file not shown.
Binary file modified integration/grpc-js/simple.bin
Binary file not shown.
4 changes: 2 additions & 2 deletions integration/oneof-unions/google/protobuf/struct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ export interface Struct_FieldsEntry {
/**
* `Value` represents a dynamically typed value which can be either
* null, a number, a string, a boolean, a recursive struct value, or a
* list of values. A producer of value is expected to set one of these
* variants. Absence of any variant indicates an error.
* list of values. A producer of value is expected to set one of that
* variants, absence of any variant indicates an error.
*
* The JSON representation for `Value` is JSON value.
*/
Expand Down
Binary file modified integration/oneof-unions/oneof.bin
Binary file not shown.
4 changes: 2 additions & 2 deletions integration/simple-prototype-defaults/simple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1812,7 +1812,7 @@ export const SimpleWithMapOfEnums = {
return {
enumsById: isObject(object.enumsById)
? Object.entries(object.enumsById).reduce<{ [key: number]: StateEnum }>((acc, [key, value]) => {
acc[Number(key)] = value as number;
acc[Number(key)] = value as StateEnum;
return acc;
}, {})
: {},
Expand All @@ -1835,7 +1835,7 @@ export const SimpleWithMapOfEnums = {
message.enumsById = Object.entries(object.enumsById ?? {}).reduce<{ [key: number]: StateEnum }>(
(acc, [key, value]) => {
if (value !== undefined) {
acc[Number(key)] = value as number;
acc[Number(key)] = value as StateEnum;
}
return acc;
},
Expand Down
4 changes: 2 additions & 2 deletions integration/simple-string-enums/google/protobuf/struct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ export interface Struct_FieldsEntry {
/**
* `Value` represents a dynamically typed value which can be either
* null, a number, a string, a boolean, a recursive struct value, or a
* list of values. A producer of value is expected to set one of these
* variants. Absence of any variant indicates an error.
* list of values. A producer of value is expected to set one of that
* variants, absence of any variant indicates an error.
*
* The JSON representation for `Value` is JSON value.
*/
Expand Down
13 changes: 11 additions & 2 deletions integration/simple-string-enums/simple-test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import { Simple, StateEnum } from './simple';
import { NullValue } from "./google/protobuf/struct";
import { NullValue } from './google/protobuf/struct';

describe('simple-string-enums', () => {
it('encodes', () => {
const s1: Simple = { name: 'a', state: StateEnum.ON, states: [StateEnum.ON, StateEnum.OFF], nullValue: NullValue.NULL_VALUE };
const s1: Simple = {
name: 'a',
state: StateEnum.ON,
states: [StateEnum.ON, StateEnum.OFF],
nullValue: NullValue.NULL_VALUE,
stateMap: { on: StateEnum.ON },
};
const b = Simple.encode(s1).finish();
const s2 = Simple.decode(b);
expect(s2).toMatchInlineSnapshot(`
Object {
"name": "a",
"nullValue": "NULL_VALUE",
"state": "ON",
"stateMap": Object {
"on": "ON",
},
"states": Array [
"ON",
"OFF",
Expand Down
Binary file modified integration/simple-string-enums/simple.bin
Binary file not shown.
1 change: 1 addition & 0 deletions integration/simple-string-enums/simple.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ message Simple {
StateEnum state = 4;
repeated StateEnum states = 5;
google.protobuf.NullValue nullValue = 6;
map <string, StateEnum> stateMap = 7;
}

enum StateEnum {
Expand Down
100 changes: 99 additions & 1 deletion integration/simple-string-enums/simple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,16 @@ export interface Simple {
state: StateEnum;
states: StateEnum[];
nullValue: NullValue;
stateMap: { [key: string]: StateEnum };
}

export interface Simple_StateMapEntry {
key: string;
value: StateEnum;
}

function createBaseSimple(): Simple {
return { name: '', state: StateEnum.UNKNOWN, states: [], nullValue: NullValue.NULL_VALUE };
return { name: '', state: StateEnum.UNKNOWN, states: [], nullValue: NullValue.NULL_VALUE, stateMap: {} };
}

export const Simple = {
Expand All @@ -83,6 +89,9 @@ export const Simple = {
if (message.nullValue !== NullValue.NULL_VALUE) {
writer.uint32(48).int32(nullValueToNumber(message.nullValue));
}
Object.entries(message.stateMap).forEach(([key, value]) => {
Simple_StateMapEntry.encode({ key: key as any, value }, writer.uint32(58).fork()).ldelim();
});
return writer;
},

Expand Down Expand Up @@ -112,6 +121,12 @@ export const Simple = {
case 6:
message.nullValue = nullValueFromJSON(reader.int32());
break;
case 7:
const entry7 = Simple_StateMapEntry.decode(reader, reader.uint32());
if (entry7.value !== undefined) {
message.stateMap[entry7.key] = entry7.value;
}
break;
default:
reader.skipType(tag & 7);
break;
Expand All @@ -126,6 +141,12 @@ export const Simple = {
state: isSet(object.state) ? stateEnumFromJSON(object.state) : StateEnum.UNKNOWN,
states: Array.isArray(object?.states) ? object.states.map((e: any) => stateEnumFromJSON(e)) : [],
nullValue: isSet(object.nullValue) ? nullValueFromJSON(object.nullValue) : NullValue.NULL_VALUE,
stateMap: isObject(object.stateMap)
? Object.entries(object.stateMap).reduce<{ [key: string]: StateEnum }>((acc, [key, value]) => {
acc[key] = value as StateEnum;
return acc;
}, {})
: {},
};
},

Expand All @@ -139,6 +160,12 @@ export const Simple = {
obj.states = [];
}
message.nullValue !== undefined && (obj.nullValue = nullValueToJSON(message.nullValue));
obj.stateMap = {};
if (message.stateMap) {
Object.entries(message.stateMap).forEach(([k, v]) => {
obj.stateMap[k] = stateEnumToJSON(v);
});
}
return obj;
},

Expand All @@ -148,6 +175,73 @@ export const Simple = {
message.state = object.state ?? StateEnum.UNKNOWN;
message.states = object.states?.map((e) => e) || [];
message.nullValue = object.nullValue ?? NullValue.NULL_VALUE;
message.stateMap = Object.entries(object.stateMap ?? {}).reduce<{ [key: string]: StateEnum }>(
(acc, [key, value]) => {
if (value !== undefined) {
acc[key] = value as StateEnum;
}
return acc;
},
{}
);
return message;
},
};

function createBaseSimple_StateMapEntry(): Simple_StateMapEntry {
return { key: '', value: StateEnum.UNKNOWN };
}

export const Simple_StateMapEntry = {
encode(message: Simple_StateMapEntry, writer: Writer = Writer.create()): Writer {
if (message.key !== '') {
writer.uint32(10).string(message.key);
}
if (message.value !== StateEnum.UNKNOWN) {
writer.uint32(16).int32(stateEnumToNumber(message.value));
}
return writer;
},

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

fromJSON(object: any): Simple_StateMapEntry {
return {
key: isSet(object.key) ? String(object.key) : '',
value: isSet(object.value) ? stateEnumFromJSON(object.value) : StateEnum.UNKNOWN,
};
},

toJSON(message: Simple_StateMapEntry): unknown {
const obj: any = {};
message.key !== undefined && (obj.key = message.key);
message.value !== undefined && (obj.value = stateEnumToJSON(message.value));
return obj;
},

fromPartial<I extends Exact<DeepPartial<Simple_StateMapEntry>, I>>(object: I): Simple_StateMapEntry {
const message = createBaseSimple_StateMapEntry();
message.key = object.key ?? '';
message.value = object.value ?? StateEnum.UNKNOWN;
return message;
},
};
Expand Down Expand Up @@ -176,6 +270,10 @@ if (util.Long !== Long) {
configure();
}

function isObject(value: any): boolean {
return typeof value === 'object' && value !== null;
}

function isSet(value: any): boolean {
return value !== null && value !== undefined;
}
Loading

0 comments on commit c2107b9

Please sign in to comment.