Skip to content

Commit 5a7a6db

Browse files
authored
Merge pull request #233 from AthennaIO/develop
feat(openapi): add full support to openapi
2 parents cbec0e6 + e378b56 commit 5a7a6db

File tree

11 files changed

+723
-21
lines changed

11 files changed

+723
-21
lines changed

package-lock.json

Lines changed: 13 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@athenna/http",
3-
"version": "5.45.0",
3+
"version": "5.47.0",
44
"description": "The Athenna Http server. Built on top of fastify.",
55
"license": "MIT",
66
"author": "João Lenon <lenon@athenna.io>",
@@ -110,7 +110,8 @@
110110
"ora": "^8.2.0",
111111
"prettier": "^2.8.8",
112112
"vite": "^6.4.1",
113-
"vite-plugin-restart": "^0.4.2"
113+
"vite-plugin-restart": "^0.4.2",
114+
"zod": "^4.3.6"
114115
},
115116
"c8": {
116117
"all": true,
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* @athenna/http
3+
*
4+
* (c) João Lenon <lenon@athenna.io>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import type { ZodError } from 'zod'
11+
import { HttpException } from '#src/exceptions/HttpException'
12+
13+
export class ZodValidationException extends HttpException {
14+
public constructor(error: ZodError) {
15+
const name = 'ValidationException'
16+
const code = 'E_VALIDATION_ERROR'
17+
const status = 422
18+
const message = 'Validation error happened.'
19+
const details = error.issues
20+
21+
super({ name, message, status, code, details })
22+
}
23+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ declare module 'fastify' {
3737

3838
export * from '#src/types'
3939

40+
export * from '#src/router/RouteSchema'
4041
export * from '#src/context/Request'
4142
export * from '#src/context/Response'
4243
export * from '#src/annotations/Controller'

src/router/Route.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@ import type {
1717
InterceptorRouteType
1818
} from '#src/types'
1919

20-
import type { HTTPMethods, FastifySchema, RouteOptions } from 'fastify'
20+
import type { HTTPMethods, RouteOptions } from 'fastify'
2121
import { Is, Options, Macroable, Route as RouteHelper } from '@athenna/common'
2222
import { UndefinedMethodException } from '#src/exceptions/UndefinedMethodException'
2323
import { NotFoundValidatorException } from '#src/exceptions/NotFoundValidatorException'
24+
import {
25+
type RouteSchemaOptions,
26+
normalizeRouteSchema
27+
} from '#src/router/RouteSchema'
2428
import { NotFoundMiddlewareException } from '#src/exceptions/NotFoundMiddlewareException'
2529

2630
export class Route extends Macroable {
@@ -341,8 +345,20 @@ export class Route extends Macroable {
341345
* })
342346
* ```
343347
*/
344-
public schema(options: FastifySchema): Route {
345-
this.route.fastify.schema = options
348+
public schema(options: RouteSchemaOptions): Route {
349+
const { schema, zod } = normalizeRouteSchema(options)
350+
351+
this.route.fastify.schema = schema
352+
353+
if (zod) {
354+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
355+
// @ts-ignore
356+
this.route.fastify.config.zod = zod
357+
} else {
358+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
359+
// @ts-ignore
360+
delete this.route.fastify.config.zod
361+
}
346362

347363
return this
348364
}

src/router/RouteResource.ts

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616

1717
import type { HTTPMethods } from 'fastify'
1818
import { Route } from '#src/router/Route'
19+
import type { RouteSchemaOptions } from '#src/router/RouteSchema'
1920
import { Is, String, Macroable, Options } from '@athenna/common'
2021

2122
export class RouteResource extends Macroable {
@@ -37,7 +38,7 @@ export class RouteResource extends Macroable {
3738
public constructor(resource: string, controller: any) {
3839
super()
3940

40-
this.resource = resource
41+
this.resource = resource.replace(/^\/|\/$/g, '')
4142
this.controller = controller
4243

4344
this.buildRoutes()
@@ -89,15 +90,15 @@ export class RouteResource extends Macroable {
8990

9091
return this
9192
}
92-
93+
9394
if (options.except.length) {
9495
this.filter(options.except, true).forEach(route => {
9596
route.middleware(middleware, options.prepend)
9697
})
9798

9899
return this
99100
}
100-
101+
101102
this.routes.forEach(route => route.middleware(middleware, options.prepend))
102103

103104
return this
@@ -132,16 +133,18 @@ export class RouteResource extends Macroable {
132133

133134
return this
134135
}
135-
136+
136137
if (options.except.length) {
137138
this.filter(options.except, true).forEach(route => {
138139
route.interceptor(interceptor, options.prepend)
139140
})
140141

141142
return this
142143
}
143-
144-
this.routes.forEach(route => route.interceptor(interceptor, options.prepend))
144+
145+
this.routes.forEach(route =>
146+
route.interceptor(interceptor, options.prepend)
147+
)
145148

146149
return this
147150
}
@@ -175,15 +178,15 @@ export class RouteResource extends Macroable {
175178

176179
return this
177180
}
178-
181+
179182
if (options.except.length) {
180183
this.filter(options.except, true).forEach(route => {
181184
route.terminator(terminator, options.prepend)
182185
})
183186

184187
return this
185188
}
186-
189+
187190
this.routes.forEach(route => route.terminator(terminator, options.prepend))
188191

189192
return this
@@ -254,6 +257,31 @@ export class RouteResource extends Macroable {
254257
return this
255258
}
256259

260+
/**
261+
* Set up schema options for specific route resource methods.
262+
*
263+
* @example
264+
* ```ts
265+
* Route.resource('/test', 'TestController').schema({
266+
* index: { response: { 200: { type: 'object' } } },
267+
* store: { body: { type: 'object' } }
268+
* })
269+
* ```
270+
*/
271+
public schema(
272+
options: Partial<Record<RouteResourceTypes, RouteSchemaOptions>>
273+
): RouteResource {
274+
Object.entries(options).forEach(([name, schema]) => {
275+
if (!schema) {
276+
return
277+
}
278+
279+
this.filter([name]).forEach(route => route.schema(schema))
280+
})
281+
282+
return this
283+
}
284+
257285
/**
258286
* Filter routes by name.
259287
*/

0 commit comments

Comments
 (0)