-
Notifications
You must be signed in to change notification settings - Fork 343
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
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,343 @@ | ||
/* eslint-disable */ | ||
import { util, configure, Writer, Reader } from 'protobufjs/minimal'; | ||
import * as Long from 'long'; | ||
import { Observable } from 'rxjs'; | ||
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?: GenericMetadata): Promise<Hero>; | ||
FindOneVillain(request: VillainById, metadata?: GenericMetadata): Promise<Villain>; | ||
FindManyVillain(request: Observable<VillainById>, metadata?: GenericMetadata): 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>; | ||
} | ||
|
||
export interface Strings { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Types used in the new field. |
||
values: string[]; | ||
} | ||
type GenericMetadata = { [key: string]: Strings }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not super-close to how like specific frameworks like GRPC handle metadata, but just naively I was thinking that even this generic type is probably making assumptions about the shape of the metadata, right? I.e. that it must be a Does that come from somewhere official like a protobuf spec? If it doesn't I was wondering if we could make this even "more generic" by allowing the user to really specify their own type. At first I was thinking a generic, like:
But then the user would have to pass
To be:
Using ts-proto's And the output would be:
Which seems like it'd accomplish truly-custom metadata types (so they could potentially be strongly typed, and so "generic" as in "per company" and not "generic" as in "just strings"), without the overhead of always typing out Wdyt? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The shape of the data is pretty locked into I agree with you that the generic approach is a little clunky, and the last approach you settled on is pretty good. I think we prefer an optional metadata: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
:-) Ah yeah, I guess if the metadata is coming in over the wire, in an extra/baggage type property, it makes sense it's basically HTTP header style key/value(s). I was thinking maybe a server could inject "other things" like logging or something, but that wouldn't be metadata, so n/m I guess...
Cool!
Ah yeah, I agree that's fine with me & what we do now. I was being kind of lazy typing it out, but was also thinking that, well, if the user has gone to the trouble of setting |
||
|
||
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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
addGenericMetadata=true,outputServices=generic-definitions,outputServices=default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the new field!