Skip to content

Commit

Permalink
feat: request model args + refactor parameter handler components WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
vitorsalgado committed Dec 27, 2021
1 parent 4d45b5e commit 1cba0f4
Show file tree
Hide file tree
Showing 21 changed files with 398 additions and 56 deletions.
5 changes: 5 additions & 0 deletions pkgs/core/ApiParameterization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,8 @@ export function createParameterDecorator(decorator: Function, configurer?: (ctx:
})
}
}

export interface PropertyDecoratorContext {
target: object | Function
property: string
}
21 changes: 12 additions & 9 deletions pkgs/core/Drizzle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Interceptor } from './Interceptor'
import { InterceptorFactory } from './Interceptor'
import { RawRequestConverter, RawResponseConverter } from './builtin'
import { Parameter, ParameterHandlerFactory } from './builtin'
import { ParameterHandler } from './builtin'
import { NoParameterHandlerError } from './internal'
import { AnyCtor } from './internal'
import { HttpHeaders } from './HttpHeaders'
Expand Down Expand Up @@ -124,22 +125,24 @@ export class Drizzle {
}

/**
* Search a {@link ParameterHandlerFactory} that handles the parameter type from argument
* Search a {@link ParameterHandler} that handles the parameter type from argument
*
* @returns {@link ParameterHandlerFactory} instance
* @throws {@link NoParameterHandlerFoundForType} if no factory is found for provided parameter type
* @returns {@link ParameterHandler} instance
* @throws {@link NoParameterHandlerError} if no handler is found for provided parameter type
*/
parameterHandlerFactory<P extends Parameter, R>(
parameterHandler<P extends Parameter, V>(
requestFactory: RequestFactory,
parameter: Parameter
): ParameterHandlerFactory<P, R> {
const factory = this._parameterHandlerFactories.filter(x => x.forType() === parameter.type)[0]
): ParameterHandler<P, V> {
for (const factory of this._parameterHandlerFactories) {
const handler = factory.provide(this, requestFactory, parameter)

if (factory === null || typeof factory === 'undefined') {
throw new NoParameterHandlerError(parameter.type, requestFactory.method, parameter.index)
if (handler !== null) {
return handler as ParameterHandler<P, V>
}
}

return factory as ParameterHandlerFactory<P, R>
throw new NoParameterHandlerError(parameter.type, requestFactory.method, parameter.index)
}

/**
Expand Down
18 changes: 11 additions & 7 deletions pkgs/core/RequestFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export class RequestFactory {
public noResponseConverter: boolean = false,
public noResponseHandler: boolean = false,
public readonly bag: Map<string, unknown> = new Map<string, unknown>(),
public checkIfPathParamsAreInSyncWithUrl: boolean = true,
private preProcessed: boolean = false,
private readonly decorators: Function[] = []
) {}
Expand All @@ -69,6 +70,7 @@ export class RequestFactory {
other.noResponseConverter,
other.noResponseHandler,
new Map<string, unknown>(other.bag),
other.checkIfPathParamsAreInSyncWithUrl,
other.preProcessed,
[...other.decorators]
)
Expand Down Expand Up @@ -153,7 +155,7 @@ export class RequestFactory {

const nonDupPathParams = new Set(this.path.match(REGEX_EXTRACT_TEMPLATE_PARAMS) ?? [])

if (pathParameters.length !== nonDupPathParams.size) {
if (pathParameters.length !== nonDupPathParams.size && this.checkIfPathParamsAreInSyncWithUrl) {
throw this.invalidArgErr(
'Path parameter configuration is not in sync with URL. ' +
'Check your path and arguments decorated with @Param().'
Expand Down Expand Up @@ -206,12 +208,7 @@ export class RequestFactory {

this.parameters
.sort((a, b) => a.index - b.index)
.forEach(parameter => {
const handlerFactory = drizzle.parameterHandlerFactory(this, parameter)
const handler = handlerFactory.provide(drizzle, this, parameter)

this.parameterHandlers.push(handler)
})
.forEach(parameter => this.parameterHandlers.push(drizzle.parameterHandler(this, parameter)))

this.preProcessed = true
}
Expand Down Expand Up @@ -268,6 +265,13 @@ export class RequestFactory {
return new NoParametersRequestBuilder(this)
}

/**
* Skip path params validation. Use this when customizing path params building.
*/
skipCheckIfPathParamsAreInSyncWithUrl(): void {
this.checkIfPathParamsAreInSyncWithUrl = false
}

/**
* Checks if there is any argument decorated with \@Body
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,5 @@ import { Parameter } from './Parameter'
import { ParameterHandler } from './ParameterHandler'

export interface ParameterHandlerFactory<P extends Parameter, R> {
forType(): string

provide(drizzle: Drizzle, requestFactory: RequestFactory, parameter: P): ParameterHandler<P, R>
provide(drizzle: Drizzle, requestFactory: RequestFactory, parameter: P): ParameterHandler<P, R> | null
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ export class BodyParameterHandler implements ParameterHandler<BodyParameter, Bod
export class BodyParameterHandlerFactory implements ParameterHandlerFactory<BodyParameter, BodyType> {
static INSTANCE: BodyParameterHandlerFactory = new BodyParameterHandlerFactory()

forType = (): string => BodyParameter.Type

provide(
drizzle: Drizzle,
requestFactory: RequestFactory,
p: BodyParameter
): ParameterHandler<BodyParameter, BodyType> {
): ParameterHandler<BodyParameter, BodyType> | null {
if (p.type !== BodyParameter.Type) {
return null
}

return new BodyParameterHandler(
drizzle.requestBodyConverter(requestFactory.method, requestFactory),
requestFactory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ export class FormParameterHandler implements ParameterHandler<FormParameter, str
export class FormParameterHandlerFactory implements ParameterHandlerFactory<FormParameter, string | string[]> {
static INSTANCE: FormParameterHandlerFactory = new FormParameterHandlerFactory()

forType = (): string => FormParameter.Type

provide(
_drizzle: Drizzle,
_rf: RequestFactory,
drizzle: Drizzle,
rf: RequestFactory,
p: FormParameter
): ParameterHandler<FormParameter, string | string[]> {
return new FormParameterHandler(p)
): ParameterHandler<FormParameter, string | string[]> | null {
if (p.type === FormParameter.Type) {
return new FormParameterHandler(p)
}

return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ export class HeaderParameterHandler implements ParameterHandler<HeaderParameter,
export class HeaderParameterHandlerFactory implements ParameterHandlerFactory<HeaderParameter, string | string[]> {
static INSTANCE: HeaderParameterHandlerFactory = new HeaderParameterHandlerFactory()

forType = (): string => HeaderParameter.Type

provide(
_drizzle: Drizzle,
_rf: RequestFactory,
drizzle: Drizzle,
rf: RequestFactory,
p: HeaderParameter
): ParameterHandler<HeaderParameter, string | string[]> {
return new HeaderParameterHandler(p)
): ParameterHandler<HeaderParameter, string | string[]> | null {
if (p.type === HeaderParameter.Type) {
return new HeaderParameterHandler(p)
}

return null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import { ParameterHandler } from '../ParameterHandler'
import { RequestFactory } from '../../../RequestFactory'
import { Drizzle } from '../../../Drizzle'
import { Parameter } from '../Parameter'
import { ParameterHandlerFactory } from '../ParameterHandlerFactory'
import { RequestParameterization } from '../../../RequestParameterization'
import { QueryParameterHandler } from './QueryParameterHandler'
import { HeaderParameterHandler } from './HeaderParameterHandler'
import { QueryNameParameterHandler } from './QueryNameParameterHandler'
import { FormParameterHandler } from './FormParameterHandler'
import { BodyParameterHandler } from './BodyParameterHandler'
import { PathParameterHandler } from './PathParameterHandler'
import { ModelMapping } from "./ModelRegistry";
import { ModelRegistry } from "./ModelRegistry";
import { HeaderParameter } from "./HeaderParameterHandler";

export class ModelArgumentParameter extends Parameter {
static Type = 'request_map'

constructor(public readonly index: number, public readonly source: string, public readonly target: any) {
super(index, ModelArgumentParameter.Type)
}
}

export class ModelParameterHandler implements ParameterHandler<ModelArgumentParameter, Record<string, unknown>> {
constructor(
readonly parameter: ModelArgumentParameter,
readonly mappings: ModelMapping[],
readonly headerParameterHandler: HeaderParameterHandler,
readonly queryParameterHandler: QueryParameterHandler,
readonly queryNameParameterHandler: QueryNameParameterHandler,
readonly pathParameterHandler: PathParameterHandler,
readonly formParameterHandler: FormParameterHandler,
readonly bodyParameterHandler: BodyParameterHandler
) {}

apply(requestValues: RequestParameterization, value: any): void {
for (const map of this.mappings) {
let v

if (map.type === 'property') {
v = value[map.key]
} else if (map.type === 'method') {
v = (value[map.key] as Function)()
} else if (map.type === 'static') {
const source = this.parameter.target[map.key]

if (typeof source === 'function') {
v = this.parameter.target[map.key]()
} else {
v = this.parameter.target[map.key]
}
}

switch (map.to) {
case 'header':
this.headerParameterHandler.apply(requestValues, v)
break
case 'query':
this.queryParameterHandler.apply(requestValues, v)
break
case 'queryname':
this.queryNameParameterHandler.apply(requestValues, v)
break
case 'param':
this.pathParameterHandler.apply(requestValues, v)
break
case 'field':
this.formParameterHandler.apply(requestValues, v)
break
case 'body':
this.bodyParameterHandler.apply(requestValues, v)
break
}
}
}
}

export class ModelArgumentParameterHandlerFactory implements ParameterHandlerFactory<ModelArgumentParameter, object> {
static INSTANCE: ModelArgumentParameterHandlerFactory = new ModelArgumentParameterHandlerFactory()

forType = (): string => ModelArgumentParameter.Type

provide(
drizzle: Drizzle,
requestFactory: RequestFactory,
p: ModelArgumentParameter
): ParameterHandler<ModelArgumentParameter, object> {
const mapping = ModelRegistry.mapping(p.target, p.source)
const mappings = ModelRegistry.mappingsForType(p.target)

return new ModelParameterHandler(
p,
mappings,
ModelRegistry.register().(p.type), new HeaderParameterHandler(mapping.parameter as HeaderParameter))
}
}
96 changes: 96 additions & 0 deletions pkgs/core/builtin/parameterhandlers/handlers/ModelRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Class } from '../../../internal'
import { pathParameterRegex } from '../../../internal'
import { ToDest } from '../../../decorators/To'
import { Parameter } from '../Parameter'
import { HeaderParameter } from './HeaderParameterHandler'
import { QueryParameter } from './QueryParameterHandler'
import { QueryNameParameter } from './QueryNameParameterHandler'
import { PathParameter } from './PathParameterHandler'
import { FormParameter } from './FormParameterHandler'
import { BodyParameter } from './BodyParameterHandler'

export type ModelMapSourceTypes = 'method' | 'property' | 'static'
export type ModelMapping = { to: ToDest; key: string; source: string; type: ModelMapSourceTypes; parameter: Parameter }
export type RequestModelMappings = Map<Class, Array<ModelMapping>>

export class ModelRegistry {
private static readonly data: RequestModelMappings = new Map()

static register(
target: Class,
to: ToDest,
key: string,
source: string,
type: ModelMapSourceTypes,
parameter: Parameter
): void {
const current = ModelRegistry.data.get(target)

if (!current) {
ModelRegistry.data.set(target, [
{
key,
type,
to,
parameter,
source
}
])
return
}

current.push({
key,
type,
to,
parameter,
source
})
}

static mappingsForType(type: Class): Array<ModelMapping> {
return ModelRegistry.data.get(type) ?? []
}

static mapping(type: Class, key: string): ModelMapping | undefined {
return (ModelRegistry.data.get(type) ?? []).find(x => x.key === key)
}
}

export function createModelDecorator(source: string, to: ToDest, key?: string) {
return function (target: object | Class, prop: string, descriptor?: PropertyDescriptor): void {
const type: ModelMapSourceTypes = typeof target === 'function' ? 'static' : descriptor ? 'method' : 'property'
const k = key || prop
let parameter: Parameter

switch (to) {
case 'header':
parameter = new HeaderParameter(k, -1)
break
case 'query':
parameter = new QueryParameter(k, -1)
break
case 'queryname':
parameter = new QueryNameParameter(-1)
break
case 'param':
parameter = new PathParameter(k, pathParameterRegex(k), -1)
break
case 'field':
parameter = new FormParameter(k, -1)
break
case 'body':
parameter = new BodyParameter(-1)
break
}

ModelRegistry.register(
typeof target === 'function' ? (target as Class) : (target.constructor as Class),
to,
k,
source,
type,
parameter
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ export class PathParameterHandler implements ParameterHandler<PathParameter, str
export class PathParameterHandlerFactory implements ParameterHandlerFactory<PathParameter, string | string[]> {
static INSTANCE: PathParameterHandlerFactory = new PathParameterHandlerFactory()

forType = (): string => PathParameter.Type

provide(
_drizzle: Drizzle,
_rf: RequestFactory,
drizzle: Drizzle,
rf: RequestFactory,
p: PathParameter
): ParameterHandler<PathParameter, string | string[]> {
return new PathParameterHandler(p)
): ParameterHandler<PathParameter, string | string[]> | null {
if (p.type === PathParameter.Type) {
return new PathParameterHandler(p)
}

return null
}
}
Loading

0 comments on commit 1cba0f4

Please sign in to comment.