Skip to content

Commit

Permalink
feat: Add generic metadata parameter to the generic service definitio…
Browse files Browse the repository at this point in the history
…n interface. (#530)

* wip

* generic metadta

* readme

* implement metadata suggestion

* run the linter

Co-authored-by: Kyle Maxwell <[email protected]>
  • Loading branch information
fizx and Kyle Maxwell authored Mar 15, 2022
1 parent b8a4e7e commit 0f5525a
Show file tree
Hide file tree
Showing 8 changed files with 375 additions and 0 deletions.
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
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

0 comments on commit 0f5525a

Please sign in to comment.