Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add generic metadata parameter to the generic service definition interface. #530

Merged
merged 5 commits into from
Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ Generated code will be placed in the Gradle build directory.

- With `--ts_proto_opt=outputServices=generic-definitions`, ts-proto will output generic (framework-agnostic) service definitions. These definitions contain descriptors for each method with links to request and response types, which allows to generate server and client stubs at runtime, and also generate strong types for them at compile time. An example of a library that uses this approach is [nice-grpc](https://github.com/deeplay-io/nice-grpc).

- With `--ts_proto_opt=metadataType=Foo@./some-file`, ts-proto add a generic (framework-agnostic) metadata field to the generic service definition.

- With `--ts_proto_opt=outputServices=generic-definitions,outputServices=default`, ts-proto will output both generic definitions and interfaces. This is useful if you want to rely on the interfaces, but also have some reflection capabilities at runtime.

- With `--ts_proto_opt=outputServices=false`, or `=none`, ts-proto will output NO service definitions.
Expand Down
Binary file added integration/generic-metadata/hero.bin
Binary file not shown.
27 changes: 27 additions & 0 deletions integration/generic-metadata/hero.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
syntax = "proto3";

package hero;

service HeroService {
rpc FindOneHero (HeroById) returns (Hero) {}
rpc FindOneVillain (VillainById) returns (Villain) {}
rpc FindManyVillain (stream VillainById) returns (stream Villain) {}
}

message HeroById {
int32 id = 1;
}

message VillainById {
int32 id = 1;
}

message Hero {
int32 id = 1;
string name = 2;
}

message Villain {
int32 id = 1;
string name = 2;
}
339 changes: 339 additions & 0 deletions integration/generic-metadata/hero.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
/* eslint-disable */
import { util, configure, Writer, Reader } from 'protobufjs/minimal';
import * as Long from 'long';
import { Observable } from 'rxjs';
import { Foo } from './some-file';
import { map } from 'rxjs/operators';

export const protobufPackage = 'hero';

export interface HeroById {
id: number;
}

export interface VillainById {
id: number;
}

export interface Hero {
id: number;
name: string;
}

export interface Villain {
id: number;
name: string;
}

function createBaseHeroById(): HeroById {
return { id: 0 };
}

export const HeroById = {
encode(message: HeroById, writer: Writer = Writer.create()): Writer {
if (message.id !== 0) {
writer.uint32(8).int32(message.id);
}
return writer;
},

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

fromJSON(object: any): HeroById {
return {
id: isSet(object.id) ? Number(object.id) : 0,
};
},

toJSON(message: HeroById): unknown {
const obj: any = {};
message.id !== undefined && (obj.id = Math.round(message.id));
return obj;
},

fromPartial<I extends Exact<DeepPartial<HeroById>, I>>(object: I): HeroById {
const message = createBaseHeroById();
message.id = object.id ?? 0;
return message;
},
};

function createBaseVillainById(): VillainById {
return { id: 0 };
}

export const VillainById = {
encode(message: VillainById, writer: Writer = Writer.create()): Writer {
if (message.id !== 0) {
writer.uint32(8).int32(message.id);
}
return writer;
},

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

fromJSON(object: any): VillainById {
return {
id: isSet(object.id) ? Number(object.id) : 0,
};
},

toJSON(message: VillainById): unknown {
const obj: any = {};
message.id !== undefined && (obj.id = Math.round(message.id));
return obj;
},

fromPartial<I extends Exact<DeepPartial<VillainById>, I>>(object: I): VillainById {
const message = createBaseVillainById();
message.id = object.id ?? 0;
return message;
},
};

function createBaseHero(): Hero {
return { id: 0, name: '' };
}

export const Hero = {
encode(message: Hero, writer: Writer = Writer.create()): Writer {
if (message.id !== 0) {
writer.uint32(8).int32(message.id);
}
if (message.name !== '') {
writer.uint32(18).string(message.name);
}
return writer;
},

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

fromJSON(object: any): Hero {
return {
id: isSet(object.id) ? Number(object.id) : 0,
name: isSet(object.name) ? String(object.name) : '',
};
},

toJSON(message: Hero): unknown {
const obj: any = {};
message.id !== undefined && (obj.id = Math.round(message.id));
message.name !== undefined && (obj.name = message.name);
return obj;
},

fromPartial<I extends Exact<DeepPartial<Hero>, I>>(object: I): Hero {
const message = createBaseHero();
message.id = object.id ?? 0;
message.name = object.name ?? '';
return message;
},
};

function createBaseVillain(): Villain {
return { id: 0, name: '' };
}

export const Villain = {
encode(message: Villain, writer: Writer = Writer.create()): Writer {
if (message.id !== 0) {
writer.uint32(8).int32(message.id);
}
if (message.name !== '') {
writer.uint32(18).string(message.name);
}
return writer;
},

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

fromJSON(object: any): Villain {
return {
id: isSet(object.id) ? Number(object.id) : 0,
name: isSet(object.name) ? String(object.name) : '',
};
},

toJSON(message: Villain): unknown {
const obj: any = {};
message.id !== undefined && (obj.id = Math.round(message.id));
message.name !== undefined && (obj.name = message.name);
return obj;
},

fromPartial<I extends Exact<DeepPartial<Villain>, I>>(object: I): Villain {
const message = createBaseVillain();
message.id = object.id ?? 0;
message.name = object.name ?? '';
return message;
},
};

export interface HeroService {
FindOneHero(request: HeroById, metadata?: Foo): Promise<Hero>;
FindOneVillain(request: VillainById, metadata?: Foo): Promise<Villain>;
FindManyVillain(request: Observable<VillainById>, metadata?: Foo): Observable<Villain>;
}

export class HeroServiceClientImpl implements HeroService {
private readonly rpc: Rpc;
constructor(rpc: Rpc) {
this.rpc = rpc;
this.FindOneHero = this.FindOneHero.bind(this);
this.FindOneVillain = this.FindOneVillain.bind(this);
this.FindManyVillain = this.FindManyVillain.bind(this);
}
FindOneHero(request: HeroById): Promise<Hero> {
const data = HeroById.encode(request).finish();
const promise = this.rpc.request('hero.HeroService', 'FindOneHero', data);
return promise.then((data) => Hero.decode(new Reader(data)));
}

FindOneVillain(request: VillainById): Promise<Villain> {
const data = VillainById.encode(request).finish();
const promise = this.rpc.request('hero.HeroService', 'FindOneVillain', data);
return promise.then((data) => Villain.decode(new Reader(data)));
}

FindManyVillain(request: Observable<VillainById>): Observable<Villain> {
const data = request.pipe(map((request) => VillainById.encode(request).finish()));
const result = this.rpc.bidirectionalStreamingRequest('hero.HeroService', 'FindManyVillain', data);
return result.pipe(map((data) => Villain.decode(new Reader(data))));
}
}

export const HeroServiceDefinition = {
name: 'HeroService',
fullName: 'hero.HeroService',
methods: {
findOneHero: {
name: 'FindOneHero',
requestType: HeroById,
requestStream: false,
responseType: Hero,
responseStream: false,
options: {},
},
findOneVillain: {
name: 'FindOneVillain',
requestType: VillainById,
requestStream: false,
responseType: Villain,
responseStream: false,
options: {},
},
findManyVillain: {
name: 'FindManyVillain',
requestType: VillainById,
requestStream: true,
responseType: Villain,
responseStream: true,
options: {},
},
},
} as const;

interface Rpc {
request(service: string, method: string, data: Uint8Array): Promise<Uint8Array>;
clientStreamingRequest(service: string, method: string, data: Observable<Uint8Array>): Promise<Uint8Array>;
serverStreamingRequest(service: string, method: string, data: Uint8Array): Observable<Uint8Array>;
bidirectionalStreamingRequest(service: string, method: string, data: Observable<Uint8Array>): Observable<Uint8Array>;
}

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>;

type KeysOfUnion<T> = T extends T ? keyof T : never;
export type Exact<P, I extends P> = P extends Builtin
? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & Record<Exclude<keyof I, KeysOfUnion<P>>, never>;

// 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();
}

function isSet(value: any): boolean {
return value !== null && value !== undefined;
}
1 change: 1 addition & 0 deletions integration/generic-metadata/parameters.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
metadataType=Foo@./some-file,outputServices=generic-definitions,outputServices=default
Binary file not shown.
Binary file not shown.
3 changes: 3 additions & 0 deletions src/generate-services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export function generateService(
const Metadata = imp('Metadata@@grpc/grpc-js');
const q = options.addNestjsRestParameter ? '' : '?';
params.push(code`metadata${q}: ${Metadata}`);
} else if (options.metadataType) {
const Metadata = imp(options.metadataType);
params.push(code`metadata?: ${Metadata}`);
}
if (options.addNestjsRestParameter) {
params.push(code`...rest: any`);
Expand Down
Loading