Skip to content

Commit

Permalink
feat: add support for Struct in NestJS (#762)
Browse files Browse the repository at this point in the history
* feat: add support for Struct in NestJS

* Add another codegen update.

* Fix tests

* Format.

* Make const/base output more conditional.

* Use isStructType.

* Consistently wrap & unwrap.

* Use conditional shallow/deep wrapping.

* Fix oneofs.

---------

Co-authored-by: Stephen Haberman <stephen.haberman@gmail.com>
  • Loading branch information
ZimGil and stephenh committed Jan 31, 2023
1 parent 2be03eb commit e8c6d8b
Show file tree
Hide file tree
Showing 25 changed files with 904 additions and 582 deletions.
2 changes: 0 additions & 2 deletions integration/fieldmask/google/protobuf/field_mask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,7 @@ export const FieldMask = {

wrap(paths: string[]): FieldMask {
const result = createBaseFieldMask();

result.paths = paths;

return result;
},

Expand Down
28 changes: 15 additions & 13 deletions integration/grpc-js/google/protobuf/struct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,11 @@ export const Struct = {

unwrap(message: Struct): { [key: string]: any } {
const object: { [key: string]: any } = {};
Object.keys(message.fields).forEach((key) => {
object[key] = message.fields[key];
});
if (message.fields) {
Object.keys(message.fields).forEach((key) => {
object[key] = message.fields[key];
});
}
return object;
},
};
Expand Down Expand Up @@ -360,7 +362,6 @@ export const Value = {

wrap(value: any): Value {
const result = createBaseValue();

if (value === null) {
result.nullValue = NullValue.NULL_VALUE;
} else if (typeof value === "boolean") {
Expand All @@ -376,19 +377,18 @@ export const Value = {
} else if (typeof value !== "undefined") {
throw new Error("Unsupported any value type: " + typeof value);
}

return result;
},

unwrap(message: Value): string | number | boolean | Object | null | Array<any> | undefined {
if (message?.stringValue !== undefined) {
unwrap(message: any): string | number | boolean | Object | null | Array<any> | undefined {
if (message.stringValue !== undefined) {
return message.stringValue;
} else if (message?.numberValue !== undefined) {
return message.numberValue;
} else if (message?.boolValue !== undefined) {
return message.boolValue;
} else if (message?.structValue !== undefined) {
return message.structValue;
return message.structValue as any;
} else if (message?.listValue !== undefined) {
return message.listValue;
} else if (message?.nullValue !== undefined) {
Expand Down Expand Up @@ -452,16 +452,18 @@ export const ListValue = {
return message;
},

wrap(value: Array<any> | undefined): ListValue {
wrap(array: Array<any> | undefined): ListValue {
const result = createBaseListValue();

result.values = value ?? [];

result.values = array ?? [];
return result;
},

unwrap(message: ListValue): Array<any> {
return message.values;
if (message?.hasOwnProperty("values") && Array.isArray(message.values)) {
return message.values;
} else {
return message as any;
}
},
};

Expand Down
172 changes: 172 additions & 0 deletions integration/nestjs-simple/google/protobuf/struct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/* eslint-disable */
import { wrappers } from "protobufjs";

export const protobufPackage = "google.protobuf";

/**
* `NullValue` is a singleton enumeration to represent the null value for the
* `Value` type union.
*
* The JSON representation for `NullValue` is JSON `null`.
*/
export enum NullValue {
/** NULL_VALUE - Null value. */
NULL_VALUE = 0,
UNRECOGNIZED = -1,
}

/**
* `Struct` represents a structured data value, consisting of fields
* which map to dynamically typed values. In some languages, `Struct`
* might be supported by a native representation. For example, in
* scripting languages like JS a struct is represented as an
* object. The details of that representation are described together
* with the proto support for the language.
*
* The JSON representation for `Struct` is JSON object.
*/
export interface Struct {
/** Unordered map of dynamically typed values. */
fields: { [key: string]: any | undefined };
}

export interface Struct_FieldsEntry {
key: string;
value: any | undefined;
}

/**
* `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.
*
* The JSON representation for `Value` is JSON value.
*/
export interface Value {
/** Represents a null value. */
nullValue?:
| NullValue
| undefined;
/** Represents a double value. */
numberValue?:
| number
| undefined;
/** Represents a string value. */
stringValue?:
| string
| undefined;
/** Represents a boolean value. */
boolValue?:
| boolean
| undefined;
/** Represents a structured value. */
structValue?:
| { [key: string]: any }
| undefined;
/** Represents a repeated `Value`. */
listValue?: Array<any> | undefined;
}

/**
* `ListValue` is a wrapper around a repeated field of values.
*
* The JSON representation for `ListValue` is JSON array.
*/
export interface ListValue {
/** Repeated field of dynamically typed values. */
values: any[];
}

export const GOOGLE_PROTOBUF_PACKAGE_NAME = "google.protobuf";

function createBaseStruct(): Struct {
return { fields: {} };
}

export const Struct = {
wrap(object: { [key: string]: any } | undefined): Struct {
const struct = createBaseStruct();
if (object !== undefined) {
Object.keys(object).forEach((key) => {
struct.fields[key] = Value.wrap(object[key]);
});
}
return struct;
},

unwrap(message: Struct): { [key: string]: any } {
const object: { [key: string]: any } = {};
if (message.fields) {
Object.keys(message.fields).forEach((key) => {
object[key] = Value.unwrap(message.fields[key]);
});
}
return object;
},
};

function createBaseValue(): Value {
return {};
}

export const Value = {
wrap(value: any): Value {
const result = {} as any;
if (value === null) {
result.nullValue = NullValue.NULL_VALUE;
} else if (typeof value === "boolean") {
result.boolValue = value;
} else if (typeof value === "number") {
result.numberValue = value;
} else if (typeof value === "string") {
result.stringValue = value;
} else if (Array.isArray(value)) {
result.listValue = ListValue.wrap(value);
} else if (typeof value === "object") {
result.structValue = Struct.wrap(value);
} else if (typeof value !== "undefined") {
throw new Error("Unsupported any value type: " + typeof value);
}
return result;
},

unwrap(message: any): string | number | boolean | Object | null | Array<any> | undefined {
if (message?.hasOwnProperty("stringValue") && message.stringValue !== undefined) {
return message.stringValue;
} else if (message?.hasOwnProperty("numberValue") && message?.numberValue !== undefined) {
return message.numberValue;
} else if (message?.hasOwnProperty("boolValue") && message?.boolValue !== undefined) {
return message.boolValue;
} else if (message?.hasOwnProperty("structValue") && message?.structValue !== undefined) {
return Struct.unwrap(message.structValue as any);
} else if (message?.hasOwnProperty("listValue") && message?.listValue !== undefined) {
return ListValue.unwrap(message.listValue);
} else if (message?.hasOwnProperty("nullValue") && message?.nullValue !== undefined) {
return null;
}
return undefined;
},
};

function createBaseListValue(): ListValue {
return { values: [] };
}

export const ListValue = {
wrap(array: Array<any> | undefined): ListValue {
const result = createBaseListValue();
result.values = (array ?? []).map(Value.wrap);
return result;
},

unwrap(message: ListValue): Array<any> {
if (message?.hasOwnProperty("values") && Array.isArray(message.values)) {
return message.values.map(Value.unwrap);
} else {
return message as any;
}
},
};

wrappers[".google.protobuf.Struct"] = { fromObject: Struct.wrap, toObject: Struct.unwrap } as any;
Binary file modified integration/nestjs-simple/hero.bin
Binary file not shown.
4 changes: 3 additions & 1 deletion integration/nestjs-simple/hero.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ syntax = "proto3";

import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/struct.proto";

package hero;

Expand All @@ -25,7 +26,8 @@ message VillainById {
message Hero {
int32 id = 1;
string name = 2;
google.protobuf.Timestamp birth_date = 3;
.google.protobuf.Timestamp birth_date = 3;
.google.protobuf.Struct external_data = 4;
}

message Villain {
Expand Down
5 changes: 5 additions & 0 deletions integration/nestjs-simple/hero.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/* eslint-disable */
import { GrpcMethod, GrpcStreamMethod } from "@nestjs/microservices";
import { wrappers } from "protobufjs";
import { Observable } from "rxjs";
import { Empty } from "./google/protobuf/empty";
import { Struct } from "./google/protobuf/struct";
import { Timestamp } from "./google/protobuf/timestamp";

export const protobufPackage = "hero";
Expand All @@ -18,6 +20,7 @@ export interface Hero {
id: number;
name: string;
birthDate: Timestamp | undefined;
externalData: { [key: string]: any } | undefined;
}

export interface Villain {
Expand All @@ -27,6 +30,8 @@ export interface Villain {

export const HERO_PACKAGE_NAME = "hero";

wrappers[".google.protobuf.Struct"] = { fromObject: Struct.wrap, toObject: Struct.unwrap } as any;

export interface HeroServiceClient {
addOneHero(request: Hero): Observable<Empty>;

Expand Down
27 changes: 20 additions & 7 deletions integration/nestjs-simple/nestjs-project/hero.controller.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import { Controller } from '@nestjs/common';
import { Observable, Subject } from 'rxjs';
import { Hero, HeroById, HeroServiceController, HeroServiceControllerMethods, Villain, VillainById } from '../hero';
import { Controller } from "@nestjs/common";
import { Observable, Subject } from "rxjs";
import { Hero, HeroById, HeroServiceController, HeroServiceControllerMethods, Villain, VillainById } from "../hero";

@Controller('hero')
@Controller("hero")
@HeroServiceControllerMethods()
export class HeroController implements HeroServiceController {
private readonly heroes: Hero[] = [
{ id: 1, name: 'Stephenh', birthDate: { seconds: 1, nanos: 2 } },
{ id: 2, name: 'Iangregsondev', birthDate: { seconds: 1, nanos: 3 } },
{
id: 1,
name: "Stephenh",
birthDate: { seconds: 1, nanos: 2 },
externalData: { foo: "bar", fizz: 1, nested: { isFailing: false, arr: [1, "foo", ["bar"]] } },
},
{
id: 2,
name: "Iangregsondev",
birthDate: { seconds: 1, nanos: 3 },
externalData: { bar: 10, baz: "foo", isPassing: true },
},
];

private readonly villains: Villain[] = [{ id: 1, name: 'John' }, { id: 2, name: 'Doe' }];
private readonly villains: Villain[] = [
{ id: 1, name: "John" },
{ id: 2, name: "Doe" },
];

addOneHero(request: Hero) {
this.heroes.push(request);
Expand Down
Loading

0 comments on commit e8c6d8b

Please sign in to comment.