Skip to content

Commit

Permalink
Merge pull request #262 from frouriojs/develop
Browse files Browse the repository at this point in the history
feat: add response schema and hooks to controller
  • Loading branch information
solufa authored Nov 24, 2022
2 parents 6712d27 + 4ac03cf commit 9e49b0a
Show file tree
Hide file tree
Showing 49 changed files with 1,464 additions and 1,394 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: [14, 16, 18]
node-version: [14, 16]
os: [ubuntu-latest]
include:
- os: windows-latest
node-version: 18
node-version: 16
steps:
- uses: actions/checkout@v2
- name: setup Node.js ${{ matrix.node-version }}
Expand All @@ -31,7 +31,7 @@ jobs:
- run: yarn typecheck
- run: yarn test --coverage
- run: npx codecov
if: github.ref == 'refs/heads/main' && matrix.os == 'ubuntu-latest' && matrix.node-version == 14
if: github.ref == 'refs/heads/main' && matrix.os == 'ubuntu-latest' && matrix.node-version == 16
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

Expand All @@ -44,13 +44,13 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: 18
node-version: 16
registry-url: "https://registry.npmjs.org"
- uses: actions/cache@v2
id: yarn-cache
with:
path: "node_modules"
key: ${{ runner.os }}-node-v18-yarn-${{ hashFiles('yarn.lock') }}
key: ${{ runner.os }}-node-v16-yarn-${{ hashFiles('yarn.lock') }}
- run: yarn --frozen-lockfile
if: steps.yarn-cache.outputs.cache-hit != 'true'
- run: yarn build
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

## [0.31.0](https://github.com/frouriojs/frourio/compare/v0.30.2...v0.31.0) (2022-11-24)

### Features

- add response schema and hooks to controller (https://github.com/frouriojs/frourio/pull/262)

## [0.30.2](https://github.com/frouriojs/frourio/compare/v0.30.1...v0.30.2) (2022-11-24)

### Bug Fixes
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ We are always forced to write "Two TypeScript".
We waste a lot of time on dynamic testing using the browser and server.

<div align="center">
<img src="https://frourio.io/img/TwoTS.svg" width="1200" alt="Why frourio ?" />
<img src="https://frourio.com/img/TwoTS.svg" width="1200" alt="Why frourio ?" />
</div>
<br />
<br />

Frourio is a framework for developing web apps quickly and safely in **"One TypeScript"**.

<div align="center">
<img src="https://frourio.io/img/OneTS.svg" width="1200" alt="Architecture of create-frourio-app" />
<img src="https://frourio.com/img/OneTS.svg" width="1200" alt="Architecture of create-frourio-app" />
</div>
<br />
<br />
Expand All @@ -49,7 +49,6 @@ Frourio is a framework for developing web apps quickly and safely in **"One Type

https://frourio.com/docs


## License

Frourio is licensed under a [MIT License](./LICENSE).
30 changes: 16 additions & 14 deletions __test__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ beforeEach(() => {
subServerPlainToInstanceCallCount = 0
subServerValidateOrRejectCallCount = 0
return Promise.all([
frourio(server).listen(port),
frourio(server).listen({ port }),
frourio(subServer, {
basePath: subBasePath,
plainToInstance: (cls, object, options): object => {
Expand All @@ -41,7 +41,7 @@ beforeEach(() => {
subServerValidateOrRejectCallCount++
return validateOrReject(instance, options)
}
}).listen(subPort)
}).listen({ port: subPort })
])
})

Expand Down Expand Up @@ -207,18 +207,6 @@ test('POST: formdata', async () => {

test('PUT: zod validations', async () => {
const port = '3000'
const res1 = await fetchClient.$put({
query: {
requiredNum: 0,
requiredNumArr: [],
id: '1',
disable: 'true',
bool: false,
boolArray: []
},
body: { port }
})
expect(res1.port).toBe(port)

await Promise.all([
expect(
Expand Down Expand Up @@ -248,6 +236,20 @@ test('PUT: zod validations', async () => {
})
).rejects.toHaveProperty('response.status', 400)
])

await expect(
fetchClient.put({
query: {
requiredNum: 0,
requiredNumArr: [],
id: '1',
disable: 'true',
bool: false,
boolArray: []
},
body: { port }
})
).resolves.toHaveProperty('status', 201)
})

test('POST: multi file upload', async () => {
Expand Down
2 changes: 1 addition & 1 deletion jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { compilerOptions } from './tsconfig.json'
const config: Config.InitialOptions = {
preset: 'ts-jest',
testEnvironment: 'node',
globals: { Blob: {}, 'ts-jest': {} },
globals: { Blob: {} },
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/' }),
coveragePathIgnorePatterns: ['\\$api.ts', 'dist']
}
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"main": "dist/index.js",
"types": "dist/index.d.ts",
"bin": "bin/index.js",
"homepage": "https://frourio.io",
"homepage": "https://frourio.com",
"repository": {
"type": "git",
"url": "git+https://github.com/frouriojs/frourio.git"
Expand Down Expand Up @@ -85,9 +85,9 @@
"devDependencies": {
"@aspida/axios": "^1.11.0",
"@aspida/node-fetch": "^1.11.0",
"@fastify/multipart": "^6.0.0",
"@fastify/multipart": "^7.3.0",
"@types/busboy": "^1.3.0",
"@types/jest": "^27.4.1",
"@types/jest": "^29.2.3",
"@types/node-fetch": "^2.5.10",
"@types/rimraf": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^5.42.1",
Expand All @@ -103,14 +103,14 @@
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-standard": "^5.0.0",
"fastify": "^3.18.1",
"jest": "^27.0.6",
"fastify": "^4.10.2",
"jest": "^29.3.1",
"node-fetch": "^2.6.1",
"prettier": "^2.7.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"typescript": "^4.8.4"
"typescript": "^4.9.3"
}
}
45 changes: 28 additions & 17 deletions servers/all/$server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import type { ClassTransformOptions } from 'class-transformer'
import { plainToInstance as defaultPlainToInstance } from 'class-transformer'
import type { ValidatorOptions } from 'class-validator'
import { validateOrReject as defaultValidateOrReject } from 'class-validator'
import type { FastifyMultipartAttactFieldsToBodyOptions, Multipart } from '@fastify/multipart'
import type { FastifyMultipartAttachFieldsToBodyOptions, Multipart, MultipartFile } from '@fastify/multipart'
import multipart from '@fastify/multipart'
import * as Validators from './validators'
import type { ReadStream } from 'fs'
import type { HttpStatusOk, AspidaMethodParams } from 'aspida'
import type { Schema } from 'fast-json-stringify'
import type { z } from 'zod'
import hooksFn0 from './api/hooks'
import hooksFn1 from './api/empty/hooks'
Expand All @@ -26,16 +27,15 @@ import controllerFn6 from './api/texts/_label@string/controller'
import controllerFn7, { hooks as ctrlHooksFn1 } from './api/users/controller'
import controllerFn8 from './api/users/_userId@number/controller'
import controllerFn9 from './api/users/_userId@number/_name/controller'

import type { FastifyInstance, RouteHandlerMethod, preValidationHookHandler, FastifyRequest, FastifySchema, FastifySchemaCompiler, RouteShorthandOptions } from 'fastify'
import type { FastifyInstance, RouteHandlerMethod, preValidationHookHandler, FastifyRequest, FastifySchema, FastifySchemaCompiler, RouteShorthandOptions, onRequestHookHandler, preParsingHookHandler, preHandlerHookHandler } from 'fastify'

export type FrourioOptions = {
basePath?: string | undefined
transformer?: ClassTransformOptions | undefined
validator?: ValidatorOptions | undefined
plainToInstance?: ((cls: new (...args: any[]) => object, object: unknown, options: ClassTransformOptions) => object) | undefined
validateOrReject?: ((instance: object, options: ValidatorOptions) => Promise<void>) | undefined
multipart?: FastifyMultipartAttactFieldsToBodyOptions | undefined
basePath?: string
transformer?: ClassTransformOptions
validator?: ValidatorOptions
plainToInstance?: (cls: new (...args: any[]) => object, object: unknown, options: ClassTransformOptions) => object
validateOrReject?: (instance: object, options: ValidatorOptions) => Promise<void>
multipart?: FastifyMultipartAttachFieldsToBodyOptions
}

type HttpStatusNoOk = 301 | 302 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 409 | 500 | 501 | 502 | 503 | 504 | 505
Expand Down Expand Up @@ -64,9 +64,9 @@ type ServerResponse<K extends AspidaMethodParams> =
type BlobToFile<T extends AspidaMethodParams> = T['reqFormat'] extends FormData
? {
[P in keyof T['reqBody']]: Required<T['reqBody']>[P] extends Blob | ReadStream
? Multipart
? MultipartFile
: Required<T['reqBody']>[P] extends (Blob | ReadStream)[]
? Multipart[]
? MultipartFile[]
: T['reqBody'][P]
}
: T['reqBody']
Expand All @@ -91,6 +91,13 @@ type ServerHandlerPromise<T extends AspidaMethodParams, U extends Record<string,

export type ServerMethodHandler<T extends AspidaMethodParams, U extends Record<string, any> = {}> = ServerHandler<T, U> | ServerHandlerPromise<T, U> | {
validators?: Partial<{ [Key in keyof RequestParams<T>]?: z.ZodType<RequestParams<T>[Key]>}>
schemas?: { response?: { [V in HttpStatusOk]?: Schema }}
hooks?: {
onRequest?: onRequestHookHandler | onRequestHookHandler[]
preParsing?: preParsingHookHandler | preParsingHookHandler[]
preValidation?: preValidationHookHandler | preValidationHookHandler[]
preHandler?: preHandlerHookHandler | preHandlerHookHandler[]
}
handler: ServerHandler<T, U> | ServerHandlerPromise<T, U>
}

Expand Down Expand Up @@ -206,9 +213,9 @@ const formatMultipartData = (arrayTypeKeys: [string, boolean][]): preValidationH

Object.entries(body).forEach(([key, val]) => {
if (Array.isArray(val)) {
body[key] = (val as Multipart[]).map(v => v.file ? v : (v as any).value)
body[key] = (val as Multipart[]).map(v => 'file' in v ? v : (v as any).value)
} else {
body[key] = (val as Multipart).file ? val : (val as any).value
body[key] = 'file' in (val as Multipart) ? val : (val as any).value
}
})

Expand Down Expand Up @@ -284,7 +291,6 @@ export default (fastify: FastifyInstance, options: FrourioOptions = {}) => {
schema: {
response: responseSchema0.get
},
validatorCompiler,
onRequest: [...hooks0.onRequest, ctrlHooks0.onRequest],
preParsing: hooks0.preParsing,
preValidation: [
Expand Down Expand Up @@ -321,14 +327,19 @@ export default (fastify: FastifyInstance, options: FrourioOptions = {}) => {
fastify.put(
basePath || '/',
{
schema: validatorsToSchema(controller0.put.validators),
schema: {
...validatorsToSchema(controller0.put.validators),
...controller0.put.schemas
},
validatorCompiler,
onRequest: [...hooks0.onRequest, ctrlHooks0.onRequest],
preParsing: hooks0.preParsing,
preValidation: [
parseNumberTypeQueryParams([['requiredNum', false, false], ['optionalNum', true, false], ['optionalNumArr', true, true], ['emptyNum', true, false], ['requiredNumArr', false, true]]),
parseBooleanTypeQueryParams([['bool', false, false], ['optionalBool', true, false], ['boolArray', false, true], ['optionalBoolArray', true, true]])
]
parseBooleanTypeQueryParams([['bool', false, false], ['optionalBool', true, false], ['boolArray', false, true], ['optionalBoolArray', true, true]]),
...controller0.put.hooks.preValidation
],
preHandler: controller0.put.hooks.preHandler
},
methodToHandler(controller0.put.handler)
)
Expand Down
14 changes: 7 additions & 7 deletions servers/all/api/$relay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ import type { ServerMethodHandler } from '../$server'
import type { Methods } from './'

type Hooks = {
onRequest?: onRequestHookHandler | onRequestHookHandler[] | undefined
preParsing?: preParsingHookHandler | preParsingHookHandler[] | undefined
preValidation?: preValidationHookHandler | preValidationHookHandler[] | undefined
preHandler?: preHandlerHookHandler | preHandlerHookHandler[] | undefined
onRequest?: onRequestHookHandler | onRequestHookHandler[]
preParsing?: preParsingHookHandler | preParsingHookHandler[]
preValidation?: preValidationHookHandler | preValidationHookHandler[]
preHandler?: preHandlerHookHandler | preHandlerHookHandler[]
}

export function defineResponseSchema<T extends { [U in keyof Methods]?: { [V in HttpStatusOk]?: Schema | undefined } | undefined}>(methods: () => T) {
export function defineResponseSchema<T extends { [U in keyof Methods]?: { [V in HttpStatusOk]?: Schema }}>(methods: () => T) {
return methods
}

export function defineHooks<T extends Hooks>(hooks: (fastify: FastifyInstance) => T): (fastify: FastifyInstance) => T
export function defineHooks<T extends Record<string, any>, U extends Hooks>(deps: T, cb: (d: T, fastify: FastifyInstance) => U): Injectable<T, [FastifyInstance], U>
export function defineHooks<T extends Record<string, any>>(hooks: (fastify: FastifyInstance) => Hooks | T, cb?: ((deps: T, fastify: FastifyInstance) => Hooks) | undefined) {
export function defineHooks<T extends Record<string, any>>(hooks: (fastify: FastifyInstance) => Hooks | T, cb?: ((deps: T, fastify: FastifyInstance) => Hooks)) {
return cb && typeof hooks !== 'function' ? depend(hooks, cb) : hooks
}

Expand All @@ -29,6 +29,6 @@ type ServerMethods = {

export function defineController<M extends ServerMethods>(methods: (fastify: FastifyInstance) => M): (fastify: FastifyInstance) => M
export function defineController<M extends ServerMethods, T extends Record<string, any>>(deps: T, cb: (d: T, fastify: FastifyInstance) => M): Injectable<T, [FastifyInstance], M>
export function defineController<M extends ServerMethods, T extends Record<string, any>>(methods: ((fastify: FastifyInstance) => M) | T, cb?: ((deps: T, fastify: FastifyInstance) => M) | undefined) {
export function defineController<M extends ServerMethods, T extends Record<string, any>>(methods: ((fastify: FastifyInstance) => M) | T, cb?: ((deps: T, fastify: FastifyInstance) => M)) {
return cb && typeof methods !== 'function' ? depend(methods, cb) : methods
}
14 changes: 7 additions & 7 deletions servers/all/api/500/$relay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ import type { ServerMethodHandler } from '../../$server'
import type { Methods } from './'

type Hooks = {
onRequest?: onRequestHookHandler | onRequestHookHandler[] | undefined
preParsing?: preParsingHookHandler | preParsingHookHandler[] | undefined
preValidation?: preValidationHookHandler | preValidationHookHandler[] | undefined
preHandler?: preHandlerHookHandler | preHandlerHookHandler[] | undefined
onRequest?: onRequestHookHandler | onRequestHookHandler[]
preParsing?: preParsingHookHandler | preParsingHookHandler[]
preValidation?: preValidationHookHandler | preValidationHookHandler[]
preHandler?: preHandlerHookHandler | preHandlerHookHandler[]
}

export function defineResponseSchema<T extends { [U in keyof Methods]?: { [V in HttpStatusOk]?: Schema | undefined } | undefined}>(methods: () => T) {
export function defineResponseSchema<T extends { [U in keyof Methods]?: { [V in HttpStatusOk]?: Schema }}>(methods: () => T) {
return methods
}

export function defineHooks<T extends Hooks>(hooks: (fastify: FastifyInstance) => T): (fastify: FastifyInstance) => T
export function defineHooks<T extends Record<string, any>, U extends Hooks>(deps: T, cb: (d: T, fastify: FastifyInstance) => U): Injectable<T, [FastifyInstance], U>
export function defineHooks<T extends Record<string, any>>(hooks: (fastify: FastifyInstance) => Hooks | T, cb?: ((deps: T, fastify: FastifyInstance) => Hooks) | undefined) {
export function defineHooks<T extends Record<string, any>>(hooks: (fastify: FastifyInstance) => Hooks | T, cb?: ((deps: T, fastify: FastifyInstance) => Hooks)) {
return cb && typeof hooks !== 'function' ? depend(hooks, cb) : hooks
}

Expand All @@ -29,6 +29,6 @@ type ServerMethods = {

export function defineController<M extends ServerMethods>(methods: (fastify: FastifyInstance) => M): (fastify: FastifyInstance) => M
export function defineController<M extends ServerMethods, T extends Record<string, any>>(deps: T, cb: (d: T, fastify: FastifyInstance) => M): Injectable<T, [FastifyInstance], M>
export function defineController<M extends ServerMethods, T extends Record<string, any>>(methods: ((fastify: FastifyInstance) => M) | T, cb?: ((deps: T, fastify: FastifyInstance) => M) | undefined) {
export function defineController<M extends ServerMethods, T extends Record<string, any>>(methods: ((fastify: FastifyInstance) => M) | T, cb?: ((deps: T, fastify: FastifyInstance) => M)) {
return cb && typeof methods !== 'function' ? depend(methods, cb) : methods
}
Loading

0 comments on commit 9e49b0a

Please sign in to comment.