-
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #480 from streamich/json-type-value-router
JSON Type Value based router
- Loading branch information
Showing
26 changed files
with
421 additions
and
292 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,15 @@ | ||
import type {JsonValueCodec} from '../json-pack/codecs/types'; | ||
import type {ResolveType, Type} from '../json-type'; | ||
|
||
export class Value<T extends Type> { | ||
constructor(public type: T, public data: ResolveType<T>) {} | ||
|
||
public encode(codec: JsonValueCodec): void { | ||
const value = this.data; | ||
const type = this.type; | ||
if (value === undefined) return; | ||
const encoder = codec.encoder; | ||
if (!type) encoder.writeAny(value); | ||
else type.encoder(codec.format)(value, encoder); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import {TypeSystem} from '../../json-type/system'; | ||
import {ObjectValue} from '../ObjectValue'; | ||
|
||
test('can retrieve field as Value', () => { | ||
const system = new TypeSystem(); | ||
const {t} = system; | ||
const obj = new ObjectValue(t.Object(t.prop('foo', t.str)), {foo: 'bar'}); | ||
const foo = obj.get('foo'); | ||
expect(foo.type.getTypeName()).toBe('str'); | ||
expect(foo.data).toBe('bar'); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
125 changes: 125 additions & 0 deletions
125
src/reactive-rpc/common/rpc/caller/ObjectValueCaller.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import {RpcError} from './error'; | ||
import {RpcCaller, type RpcApiCallerOptions} from './RpcCaller'; | ||
import {type AbstractType, FunctionStreamingType, FunctionType} from '../../../../json-type/type/classes'; | ||
import {StaticRpcMethod, type StaticRpcMethodOptions} from '../methods/StaticRpcMethod'; | ||
import {StreamingRpcMethod, type StreamingRpcMethodOptions} from '../methods/StreamingRpcMethod'; | ||
import { | ||
type ObjectType, | ||
type Schema, | ||
type TypeSystem, | ||
ObjectFieldType, | ||
TypeOf, | ||
SchemaOf, | ||
Type, | ||
} from '../../../../json-type'; | ||
import type {ObjectValue, UnObjectType, UnObjectValue} from '../../../../json-type-value/ObjectValue'; | ||
import type {Value} from '../../../../json-type-value/Value'; | ||
import type {Observable} from 'rxjs'; | ||
import type {RpcValue} from '../../messages/Value'; | ||
|
||
type ObjectFieldToTuple<F> = F extends ObjectFieldType<infer K, infer V> ? [K, V] : never; | ||
type ToObject<T> = T extends [string, unknown][] ? {[K in T[number] as K[0]]: K[1]} : never; | ||
type ObjectFieldsToMap<F> = ToObject<{[K in keyof F]: ObjectFieldToTuple<F[K]>}>; | ||
type ObjectValueToTypeMap<V> = ObjectFieldsToMap<UnObjectType<UnObjectValue<V>>>; | ||
|
||
type MethodReq<F> = F extends FunctionType<infer Req, any> | ||
? TypeOf<SchemaOf<Req>> | ||
: F extends FunctionStreamingType<infer Req, any> | ||
? TypeOf<SchemaOf<Req>> | ||
: never; | ||
|
||
type MethodRes<F> = F extends FunctionType<any, infer Res> | ||
? TypeOf<SchemaOf<Res>> | ||
: F extends FunctionStreamingType<any, infer Res> | ||
? TypeOf<SchemaOf<Res>> | ||
: never; | ||
|
||
type MethodDefinition<Ctx, F> = F extends FunctionType<any, any> | ||
? StaticRpcMethodOptions<Ctx, MethodReq<F>, MethodRes<F>> | ||
: F extends FunctionStreamingType<any, any> | ||
? StreamingRpcMethodOptions<Ctx, MethodReq<F>, MethodRes<F>> | ||
: never; | ||
|
||
export interface ObjectValueCallerOptions<V extends ObjectValue<ObjectType<any>>, Ctx = unknown> | ||
extends Omit<RpcApiCallerOptions<Ctx>, 'getMethod'> { | ||
router: V; | ||
} | ||
|
||
export class ObjectValueCaller<V extends ObjectValue<ObjectType<any>>, Ctx = unknown> extends RpcCaller<Ctx> { | ||
protected readonly router: V; | ||
protected readonly system: TypeSystem; | ||
protected readonly methods = new Map<string, StaticRpcMethod<Ctx> | StreamingRpcMethod<Ctx>>(); | ||
|
||
constructor({router: value, ...rest}: ObjectValueCallerOptions<V, Ctx>) { | ||
super({ | ||
...rest, | ||
getMethod: (name) => this.get(name) as any as StaticRpcMethod<Ctx> | StreamingRpcMethod<Ctx>, | ||
}); | ||
this.router = value; | ||
const system = value.type.system; | ||
if (!system) throw new Error('NO_SYSTEM'); | ||
this.system = system; | ||
} | ||
|
||
public get<K extends keyof ObjectValueToTypeMap<V>>( | ||
id: K, | ||
): MethodDefinition<Ctx, ObjectValueToTypeMap<V>[K]> | undefined { | ||
let method = this.methods.get(id as string) as any; | ||
if (method) return method; | ||
const fn = this.router.get(<string>id) as Value<Type>; | ||
if (!fn || !(fn.type instanceof FunctionType || fn.type instanceof FunctionStreamingType)) { | ||
return undefined; | ||
} | ||
const fnType = fn.type as FunctionType<Type, Type> | FunctionStreamingType<Type, Type>; | ||
const {req, res} = fnType; | ||
const call = fn.data; | ||
const validator = fnType.req.validator('object'); | ||
const requestSchema = (fnType.req as AbstractType<Schema>).getSchema(); | ||
const isRequestVoid = requestSchema.__t === 'const' && requestSchema.value === undefined; | ||
const validate = isRequestVoid | ||
? () => {} | ||
: (req: unknown) => { | ||
const error: any = validator(req); | ||
if (error) { | ||
const message = error.message + (Array.isArray(error?.path) ? ' Path: /' + error.path.join('/') : ''); | ||
throw RpcError.value(RpcError.validation(message, error)); | ||
} | ||
}; | ||
method = | ||
fnType instanceof FunctionType | ||
? new StaticRpcMethod({req, res, validate, call}) | ||
: new StreamingRpcMethod({req, res, validate, call$: call}); | ||
this.methods.set(id as string, method as any); | ||
return method; | ||
} | ||
|
||
public async call<K extends keyof ObjectValueToTypeMap<V>>( | ||
id: K, | ||
request: MethodReq<ObjectValueToTypeMap<V>[K]>, | ||
ctx: Ctx, | ||
): Promise<RpcValue<MethodRes<ObjectValueToTypeMap<V>[K]>>> { | ||
return super.call(id as string, request, ctx) as any; | ||
} | ||
|
||
public async callSimple<K extends keyof ObjectValueToTypeMap<V>>( | ||
id: K, | ||
request: MethodReq<ObjectValueToTypeMap<V>[K]>, | ||
ctx: Ctx = {} as any, | ||
): Promise<MethodRes<ObjectValueToTypeMap<V>[K]>> { | ||
try { | ||
const res = await this.call(id as string, request, ctx); | ||
return res.data; | ||
} catch (err) { | ||
const error = err as RpcValue<RpcError>; | ||
throw error.data; | ||
} | ||
} | ||
|
||
public call$<K extends keyof ObjectValueToTypeMap<V>>( | ||
id: K, | ||
request: Observable<MethodReq<ObjectValueToTypeMap<V>[K]>>, | ||
ctx: Ctx, | ||
): Observable<RpcValue<MethodRes<ObjectValueToTypeMap<V>[K]>>> { | ||
return super.call$(id as string, request, ctx) as any; | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
src/reactive-rpc/common/rpc/caller/__tests__/ObjectValueCaller.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import {TypeSystem} from '../../../../../json-type'; | ||
import {ObjectValue} from '../../../../../json-type-value/ObjectValue'; | ||
import {ObjectValueCaller} from '../ObjectValueCaller'; | ||
|
||
test('can execute simple calls', async () => { | ||
const system = new TypeSystem(); | ||
const {t} = system; | ||
const router = ObjectValue.create(system) | ||
.prop('ping', t.Function(t.any, t.Const(<const>'pong')), async () => 'pong') | ||
.prop('echo', t.Function(t.any, t.any), async (req) => req); | ||
const caller = new ObjectValueCaller({router}); | ||
const res1 = await caller.call('ping', null, {}); | ||
expect(res1.data).toBe('pong'); | ||
const res2 = await caller.callSimple('echo', {foo: 'bar'}, {}); | ||
expect(res2).toEqual({foo: 'bar'}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.