Skip to content

Commit

Permalink
refactor: move http decorators to plugin-koa, update usage for core
Browse files Browse the repository at this point in the history
  • Loading branch information
thonatos committed Feb 21, 2024
1 parent 8c53f32 commit 43c0fb7
Show file tree
Hide file tree
Showing 8 changed files with 644 additions and 517 deletions.
853 changes: 459 additions & 394 deletions common/config/rush/pnpm-lock.yaml

Large diffs are not rendered by default.

54 changes: 0 additions & 54 deletions packages/apps/artusx-koa/src/controller/validator.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ArtusInjectEnum, Inject, GET, Controller } from '@artusx/core';
import { ArtusInjectEnum, Inject, GET, Controller, ContentType } from '@artusx/core';
import type { ArtusxContext } from '@artusx/core';
import APIService from './api.service';

Expand All @@ -12,10 +12,11 @@ export default class APIController {

@GET('/')
async home(ctx: ArtusxContext) {
ctx.body = 'home';
ctx.body = 'api';
}

@GET('/info')
@GET('/mockApi')
@ContentType('application/json; charset=utf-8')
async getInfo(ctx: ArtusxContext) {
ctx.body = await this.apiService.mockApi();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Controller, StatusCode, GET, Query, Params, Body, POST } from '@artusx/core';
import type { ArtusxContext } from '@artusx/core';

import {
QueryTypes,
QueryScheme,
BodyTypes,
BodyScheme,
ParamsTypes,
ParamsScheme,
} from './validator.validator';

@Controller('/validator')
export default class ValidatorController {
@GET('/:uuid')
@POST('/:uuid')

/**
* validator.index.handler
* @description validate query / params / body
* @example:
* - url: /validator/e8b847b9-cb23-4fbf-8e7c-0c4ba72b9629?foo=foo&bar=bar
* - body: { "key": 123456 }
*/
@Query<QueryTypes>(QueryScheme)
@Body<BodyTypes>(BodyScheme)
@Params<ParamsTypes>(ParamsScheme)
@StatusCode(200)
async index(ctx: ArtusxContext): Promise<Object> {
const query = ctx.context.output.data.query;
const params = ctx.context.output.data.params;
const body = ctx.context.output.data.body;

return {
query,
body,
params,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// https://json-schema.org/specification

import { JSONSchemaType } from '@artusx/core';

export interface QueryTypes {
foo: string;
bar?: string;
}

export const QueryScheme: JSONSchemaType<QueryTypes> = {
type: 'object',
properties: {
foo: { type: 'string' },
bar: { type: 'string', nullable: true },
},
required: ['foo'],
additionalProperties: false,
};

export interface ParamsTypes {
uuid: string;
}

export const ParamsScheme: JSONSchemaType<ParamsTypes> = {
type: 'object',
properties: {
uuid: { type: 'string', nullable: false },
},
required: ['uuid'],
additionalProperties: false,
};

export interface BodyTypes {
key: number;
}

export const BodyScheme: JSONSchemaType<BodyTypes> = {
type: 'object',
properties: {
key: { type: 'integer', nullable: false },
},
required: ['key'],
additionalProperties: false,
};
49 changes: 1 addition & 48 deletions packages/libs/core/src/decorator.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,9 @@
import Ajv, { JSONSchemaType } from 'ajv';
import { ArtusStdError, ArtusxContext, ArtusxNext } from './types';
import { ArtusxContext, ArtusxNext } from './types';
export type { JSONSchemaType, JSONType } from 'ajv';

const ajv = new Ajv();

export function Headers(params: Record<string, string | string[]>) {
return (_target: object, _key: string, descriptor: TypedPropertyDescriptor<any>) => {
const originalDef = descriptor.value;

descriptor.value = function (...args: any[]) {
const [ctx, _next] = args as [ArtusxContext, ArtusxNext];

Object.keys(params).forEach((header) => {
ctx.set(header, params[header]);
});

return originalDef.apply(this, args);
};
return descriptor;
};
}

export function StatusCode(statusCode: number) {
return (_target: object, _key: string, descriptor: TypedPropertyDescriptor<any>) => {
const originalDef = descriptor.value;

descriptor.value = async function (...args: any[]) {
const [ctx, _next] = args as [ArtusxContext, ArtusxNext];

try {
const response = await originalDef.apply(this, args);
if (response) {
ctx.status = statusCode || 200;
ctx.body = response;
}
} catch (error) {
let _statusCode = 500;

if (error.name === 'ArtusStdError') {
const err = error as ArtusStdError;
const desc = err.desc;
_statusCode = parseInt(desc) || 500;
}

ctx.status = _statusCode;
ctx.body = error;
}
};
return descriptor;
};
}

function buildValidatorFactory<T>(key: 'query' | 'params' | 'body', schema: JSONSchemaType<T>) {
const validate = ajv.compile(schema);

Expand Down
114 changes: 96 additions & 18 deletions packages/plugins/koa/src/decorator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { addTag, Injectable, ScopeEnum } from '@artus/core';
import type { ArtusStdError } from '@artus/core';

import {
ControllerMetadata,
Expand All @@ -7,6 +8,8 @@ import {
HTTPRouteMetadata,
HTTPMiddlewareMetadata,
ArtusxHandler,
ArtusxContext,
ArtusxNext,
} from './types';

export const CLASS_CONTROLLER_TAG = 'CLASS_CONTROLLER_TAG';
Expand All @@ -18,7 +21,12 @@ export const CLASS_MIDDLEWARE_METADATA = 'CLASS_MIDDLEWARE_METADATA';
export const HTTP_ROUTER_METADATA = Symbol.for('HTTP_ROUTER_METADATA');
export const HTTP_MIDDLEWARE_METADATA = Symbol.for('HTTP_MIDDLEWARE_METADATA');

// class decorator
/**
* Controler decorator
* @param prefix string
* @example @Controller('/api')
* @returns void
*/
export function Controller(prefix?: string) {
return (target: any) => {
const controllerMetadata: ControllerMetadata = {
Expand All @@ -31,6 +39,7 @@ export function Controller(prefix?: string) {
};
}

// class middleware decorator
export function Middleware(params: { enable: boolean }) {
return (target: any) => {
const middlewareMetadata: MiddlewareMetadata = {
Expand All @@ -43,23 +52,76 @@ export function Middleware(params: { enable: boolean }) {
};
}

function buildMethodFactory(method: HTTPMethod, path: string) {
return (_target: object, _key: string | symbol, descriptor: TypedPropertyDescriptor<any>) => {
const routeMetadataList: HTTPRouteMetadata[] =
Reflect.getMetadata(HTTP_ROUTER_METADATA, descriptor.value) ?? [];
export function Headers(params: Record<string, string | string[]>) {
return (_target: object, _key: string, descriptor: TypedPropertyDescriptor<any>) => {
const originalDef = descriptor.value;

routeMetadataList.push({
path,
method,
});
descriptor.value = function (...args: any[]) {
const [ctx, _next] = args as [ArtusxContext, ArtusxNext];

Reflect.defineMetadata(HTTP_ROUTER_METADATA, routeMetadataList, descriptor.value);
Object.keys(params).forEach((header) => {
ctx.set(header, params[header]);
});

return originalDef.apply(this, args);
};
return descriptor;
};
}

/**
* ContentType decorator
* @description set content-type, https://www.npmjs.com/package/mime-types
* @param contentType
* @returns void
*/

export function ContentType(contentType: string) {
return (_target: object, _key: string, descriptor: TypedPropertyDescriptor<any>) => {
const originalDef = descriptor.value;

descriptor.value = async function (...args: any[]) {
const [ctx, _next] = args as [ArtusxContext, ArtusxNext];

await originalDef.apply(this, args);

ctx.type = contentType || 'text/plain; charset=utf-8';
};
return descriptor;
};
}

export function StatusCode(statusCode: number) {
return (_target: object, _key: string, descriptor: TypedPropertyDescriptor<any>) => {
const originalDef = descriptor.value;

descriptor.value = async function (...args: any[]) {
const [ctx, _next] = args as [ArtusxContext, ArtusxNext];

try {
const response = await originalDef.apply(this, args);
if (response) {
ctx.status = statusCode || 200;
ctx.body = response;
}
} catch (error) {
let _statusCode = 500;

if (error.name === 'ArtusStdError') {
const err = error as ArtusStdError;
const desc = err.desc;
_statusCode = parseInt(desc) || 500;
}

ctx.status = _statusCode;
ctx.body = error;
}
};
return descriptor;
};
}

// function decorator
// function middleware decorator
export const MW = (middlewares: ArtusxHandler[]) => {
return (_target: object, _key: string | symbol, descriptor: TypedPropertyDescriptor<any>) => {
const httpMiddlewareMetadata: HTTPMiddlewareMetadata = {
Expand All @@ -71,30 +133,46 @@ export const MW = (middlewares: ArtusxHandler[]) => {
};
};

function buildHttpMethodFactory(method: HTTPMethod, path: string) {
return (_target: object, _key: string | symbol, descriptor: TypedPropertyDescriptor<any>) => {
const routeMetadataList: HTTPRouteMetadata[] =
Reflect.getMetadata(HTTP_ROUTER_METADATA, descriptor.value) ?? [];

routeMetadataList.push({
path,
method,
});

Reflect.defineMetadata(HTTP_ROUTER_METADATA, routeMetadataList, descriptor.value);

return descriptor;
};
}

export const GET = (path: string) => {
return buildMethodFactory(HTTPMethod.GET, path);
return buildHttpMethodFactory(HTTPMethod.GET, path);
};

export const PUT = (path: string) => {
return buildMethodFactory(HTTPMethod.PUT, path);
return buildHttpMethodFactory(HTTPMethod.PUT, path);
};

export const POST = (path: string) => {
return buildMethodFactory(HTTPMethod.POST, path);
return buildHttpMethodFactory(HTTPMethod.POST, path);
};

export const HEAD = (path: string) => {
return buildMethodFactory(HTTPMethod.HEAD, path);
return buildHttpMethodFactory(HTTPMethod.HEAD, path);
};

export const PATCH = (path: string) => {
return buildMethodFactory(HTTPMethod.PATCH, path);
return buildHttpMethodFactory(HTTPMethod.PATCH, path);
};

export const DELETE = (path: string) => {
return buildMethodFactory(HTTPMethod.DELETE, path);
return buildHttpMethodFactory(HTTPMethod.DELETE, path);
};

export const OPTIONS = (path: string) => {
return buildMethodFactory(HTTPMethod.OPTIONS, path);
return buildHttpMethodFactory(HTTPMethod.OPTIONS, path);
};

0 comments on commit 43c0fb7

Please sign in to comment.