Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft autodeprecated: #35

Merged
merged 7 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 39 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,45 +37,46 @@ endpoint permissions in the documentation as well you need to name the function
array as its input parameter.

You can find simple examples of all mentioned in the demo folder of this repository. Quick usage example can also be found below ⬇.


## Config parameters

| Name | Type | Required |Description |
| ------------------------------------------|-------------|:----------------------:|---------------------------------------------------------------------------------------------------- |
| **outputPath** | string | ✅ | Path to directory where output JSON file should be created. |
| **generateUI** | boolean | ✅ | Whether [Swagger UI](https://swagger.io/tools/swagger-ui/) should be generated. |
| **permissions** | object | ❌ | Configuration parameters for parsing permissions.
| **permissions**.parser | function | ❌ | Custom parse function for permission middleware
| **permissions**.middlewareName | string | ✅ | Name of the middleware responsible for handling API permissions. |
| **permissions**.closure | string | ✅ | Name of the permission middleware closure. |
| **permissions**.paramName | string | ❌ | Name of the parameter containing permissions passed to middleware. |
| **permissionsFormatter** | function | ❌ | Custom formatting function for permissions description |
| **requestSchemaName** | string | ❌ | Name of the Joi schema object defining request structure. |
| **responseSchemaName** | string | ❌ | Name of the Joi schema object defining response structure. |
| **requestSchemaParams** | any[] | ❌ | Param for ability to pass mock params for requestSchema |
| **responseSchemaParams** | any[] | ❌ | Param for ability to pass mock params for responseSchema |
| **errorResponseSchemaName** | string | ❌ | Name of the Joi schema object defining error responses structure. |
| **businessLogicName** | string | ✅ | Name of the function responsible for handling business logic of the request. |
| **swaggerInitInfo** | ISwaggerInit | ❌ | Swagger initial information. |
| **swaggerInitInfo**.servers | IServer[] | ❌ | List of API servers |
| **swaggerInitInfo**.servers.url | string | ❌ | API server URL |
| **swaggerInitInfo**.info | IInfo | ❌ | Basic API information. |
| **swaggerInitInfo**.info.description | string | ❌ | API description. |
| **swaggerInitInfo**.info.version | string | ❌ | API version. |
| **swaggerInitInfo**.info.title | string | ❌ | API title. |
| **swaggerInitInfo**.info.termsOfService | string | ❌ | Link to terms of service. |
| **swaggerInitInfo**.info.contact | IContact | ❌ | Swagger initial information. |
| **swaggerInitInfo**.info.contact.email | string | ✅ | Contact email. |
| **swaggerInitInfo**.info.license | ILicense | ❌ | Swagger initial information. |
| **swaggerInitInfo**.info.license.name | string | ✅ | License name. |
| **swaggerInitInfo**.info.license.url | string | ✅ | License url. |
| **tags** | string | ❌ | Configuration parameters for parsing [tags](https://swagger.io/docs/specification/grouping-operations-with-tags/). |
| **tags**.baseUrlSegmentsLength | number | ❌ | Number of base URL segments. |
| **tags**.joinTags | boolean | ❌ | If set to true, array of parsed tags will be joined to string by **tagSeparator**, otherwise array of tags is returned. |
| **tags**.tagSeparator | string | ❌ | String used to join parsed tags. |
| **tags**.versioning | boolean | ❌ | If you are using multiple versions of API, you can separate endpoints also by API version. In this case it is necessary to define param **"baseUrlSegmentsLength"**. |
| **tags**.versionSeparator | string | ❌ | String used to separate parsed tags from API version tag is versioning == true. |
| Name | Type | Required | Description |
| ------------------------------------------|---------------|:--------:|-------------|
| **outputPath** | string | ✅ | Path to directory where output JSON file should be created. |
| **generateUI** | boolean | ✅ | Whether [Swagger UI](https://swagger.io/tools/swagger-ui/) should be generated. |
| **permissions** | object | ❌ | Configuration parameters for parsing permissions. |
| **permissions**.parser | function | ❌ | Custom parse function for permission middleware. |
| **permissions**.middlewareName | string | ✅ | Name of the middleware responsible for handling API permissions. |
| **permissions**.closure | string | ✅ | Name of the permission middleware closure. |
| **permissions**.paramName | string | ❌ | Name of the parameter containing permissions passed to middleware. |
| **permissionsFormatter** | function | ❌ | Custom formatting function for permissions description. |
| **requestSchemaName** | string | ❌ | Name of the Joi schema object defining request structure. |
| **responseSchemaName** | string | ❌ | Name of the Joi schema object defining response structure. |
| **requestSchemaParams** | any[] | ❌ | Param for ability to pass mock params for requestSchema. |
| **responseSchemaParams** | any[] | ❌ | Param for ability to pass mock params for responseSchema. |
| **errorResponseSchemaName** | string | ❌ | Name of the Joi schema object defining error responses structure. |
| **businessLogicName** | string | ✅ | Name of the function responsible for handling business logic of the request. |
| **swaggerInitInfo** | ISwaggerInit | ❌ | Swagger initial information. |
| **swaggerInitInfo**.servers | IServer[] | ❌ | List of API servers. |
| **swaggerInitInfo**.servers.url | string | ❌ | API server URL. |
| **swaggerInitInfo**.info | IInfo | ❌ | Basic API information. |
| **swaggerInitInfo**.info.description | string | ❌ | API description. |
| **swaggerInitInfo**.info.version | string | ❌ | API version. |
| **swaggerInitInfo**.info.title | string | ❌ | API title. |
| **swaggerInitInfo**.info.termsOfService | string | ❌ | Link to terms of service. |
| **swaggerInitInfo**.info.contact | IContact | ❌ | Swagger initial information. |
| **swaggerInitInfo**.info.contact.email | string | ✅ | Contact email. |
| **swaggerInitInfo**.info.license | ILicense | ❌ | Swagger initial information. |
| **swaggerInitInfo**.info.license.name | string | ✅ | License name. |
| **swaggerInitInfo**.info.license.url | string | ✅ | License url. |
| **tags** | string | ❌ | Configuration parameters for parsing [tags](https://swagger.io/docs/specification/grouping-operations-with-tags/). |
| **tags**.baseUrlSegmentsLength | number | ❌ | Number of base URL segments. |
| **tags**.joinTags | boolean | ❌ | If set to true, array of parsed tags will be joined to string by **tagSeparator**, otherwise array of tags is returned. |
| **tags**.tagSeparator | string | ❌ | String used to join parsed tags. |
| **tags**.versioning | boolean | ❌ | If you are using multiple versions of API, you can separate endpoints also by API version. In this case it is necessary to define param **"baseUrlSegmentsLength"**. |
| **tags**.versionSeparator | string | ❌ | String used to separate parsed tags from API version tag is versioning == true. |
| **deprecationPathPattern** | string | ❌ | If provided, all versions of endpoints except latest will be marked as deprecated. <br> Pattern needs to specify api route from start segment to version segment, which have to be specified as **"v\***". <br> For example if we have **api/v1/users** and **api/v2/users** endpoints and we set **deprecationPathPattern='/api/v\*/'**, **api/v1/users** endpoint will be automatically marked as deprecated. For complex route schemas use pattern like **deprecationPathPattern='/api/.+/v\*/'**, **api/b2b/v1/users** |


## Usage example
Expand Down Expand Up @@ -112,9 +113,9 @@ const config: IConfig = {
// Use case example
function workflow() {
getSwagger(app, config).then(() => {
console.log('DONE')
console.log('Apidoc was successfully generated')
}).catch((e) => {
console.log('ERROR', e)
console.log(`Unable to generate apidoc: ${err}`)
})
}

Expand Down
2 changes: 1 addition & 1 deletion demo/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import endpoints from './endpoints'

const app = express()

app.use('/api/v1', endpoints())
app.use('/api', endpoints())

export default app
8 changes: 4 additions & 4 deletions demo/endpoints/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import express from 'express'
import animalsRouter from './animals'
import usersRouter from './users'
import v1Router from './v1'
import v2Router from './v2'

const router = express.Router()

export default () => {
router.use('/animals', animalsRouter())
router.use('/users', usersRouter())
router.use('/v1', v1Router())
router.use('/v2', v2Router())

return router
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import express from 'express'
import validationMiddleware from '../../middlewares/validationMiddleware'
import validationMiddleware from '../../../middlewares/validationMiddleware'
import * as getAnimals from './get.animals'
import * as postAnimal from './post.animal'
import * as postAnimalByFamily from './post.animalByFamily'
Expand Down
12 changes: 12 additions & 0 deletions demo/endpoints/v1/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import express from 'express'
import usersRouter from './users'
import animalsRouter from './animals'

const router = express.Router()

export default () => {
router.use('/users', usersRouter())
router.use('/animals', animalsRouter())

return router
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextFunction, Request, Response } from 'express'
import Joi from 'joi'
import { TranslateFunc } from '../../middlewares/validationMiddleware'
import { TranslateFunc } from '../../../middlewares/validationMiddleware'

const userEndpointDesc = 'This is how to add swagger description for this endpoint'

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import express from 'express'
import passport from 'passport'
import { permissionMiddleware } from '../../middlewares/permissionMiddleware'
import validationMiddleware from '../../middlewares/validationMiddleware'
import { permissionMiddleware } from '../../../middlewares/permissionMiddleware'
import validationMiddleware from '../../../middlewares/validationMiddleware'
import * as getUsers from './get.users'
import * as getUser from './get.user'
import * as postUser from './post.user'
Expand Down
File renamed without changes.
36 changes: 36 additions & 0 deletions demo/endpoints/v2/animals/get.animals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { NextFunction, Request, Response } from 'express'
import Joi from 'joi'

export const requestSchema = Joi.object({
params: Joi.object(),
query: Joi.object(),
body: Joi.object()
})

export const responseSchema = Joi.object({
animals: Joi.array().items({
id: Joi.number().integer().min(1).required(),
name: Joi.string().min(1).required(),
color: Joi.string().valid('BLUE', 'PINK', 'RED').optional()
})
})

export const businessLogic = (_req: Request, res: Response, next: NextFunction) => {
try {
return res.json({
animals: [
{
id: 1,
name: 'Monkey',
color: 'PINK'
},
{
id: 2,
name: 'Dog'
}
]
})
} catch (e) {
return next(e)
}
}
17 changes: 17 additions & 0 deletions demo/endpoints/v2/animals/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import express from 'express'
import validationMiddleware from '../../../middlewares/validationMiddleware'
import * as getAnimals from './get.animals'
import * as postAnimal from './post.animal'
import * as postAnimalByFamily from './post.animalByFamily'

const router = express.Router()

export default () => {
router.get('/', validationMiddleware(getAnimals.requestSchema), getAnimals.businessLogic)

router.post('/', validationMiddleware(postAnimal.requestSchema), postAnimal.businessLogic)

router.post('/family', validationMiddleware(postAnimalByFamily.requestSchema), postAnimalByFamily.businessLogic)

return router
}
25 changes: 25 additions & 0 deletions demo/endpoints/v2/animals/post.animal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { NextFunction, Request, Response } from 'express'
import Joi from 'joi'

export const requestSchema = Joi.object({
params: Joi.object(),
query: Joi.object(),
body: Joi.object({
name: Joi.string().min(1).example('This is name').required(),
color: Joi.string().valid('BLUE', 'PINK', 'RED').optional()
})
})

export const responseSchema = Joi.object({
message: Joi.string().min(1).required()
})

export const businessLogic = (req: Request, res: Response, next: NextFunction) => {
try {
return res.json({
message: 'OK'
})
} catch (e) {
return next(e)
}
}
33 changes: 33 additions & 0 deletions demo/endpoints/v2/animals/post.animalByFamily.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { NextFunction, Request, Response } from 'express'
import Joi from 'joi'

export const requestSchema = Joi.object({
params: Joi.object(),
query: Joi.object(),
body: Joi.alternatives().try(
Joi.object({
name: Joi.string().min(1).required(),
animalFamily: Joi.string().valid('CANINE').invalid('FELINE').required(),
barkType: Joi.string().valid('WOOF', 'BARK', 'AWOO').required()
}),
Joi.object({
name: Joi.string().min(1).required(),
animalFamily: Joi.string().valid('FELINE').invalid('CANINE').required(),
meowType: Joi.string().valid('PURR', 'MEOW', 'HISS').required()
})
)
})

export const responseSchema = Joi.object({
message: Joi.string().min(1).required()
})

export const businessLogic = (_req: Request, res: Response, next: NextFunction) => {
try {
return res.json({
message: 'OK'
})
} catch (e) {
return next(e)
}
}
12 changes: 12 additions & 0 deletions demo/endpoints/v2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import express from 'express'
import usersRouter from './users'
import animalsRouter from './animals'

const router = express.Router()

export default () => {
router.use('/users', usersRouter())
router.use('/animals', animalsRouter())

return router
}
67 changes: 67 additions & 0 deletions demo/endpoints/v2/users/get.user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { NextFunction, Request, Response } from 'express'
import Joi from 'joi'
import { TranslateFunc } from '../../../middlewares/validationMiddleware'

const userEndpointDesc = 'This is how to add swagger description for this endpoint'

export const requestSchema = (translateFn: TranslateFunc) =>
Joi.object({
headers: Joi.object({
language: Joi.string().valid('sk', 'en')
})
.options({ allowUnknown: true })
.example(translateFn('example translate')),
params: Joi.object({
userID: Joi.number()
}),
query: Joi.object({
search: Joi.string().required()
}),
body: Joi.object({
name: Joi.string().required()
})
}).description(userEndpointDesc)

export const userSchema = Joi.object({
id: Joi.number().required(),
name: Joi.string(),
surname: Joi.string().allow(null)
}).meta({ className: 'User' })

export const responseSchema = Joi.object({
user: userSchema
})

export const errorResponseSchemas = [
Joi.object({
messages: Joi.array().items(
Joi.object({
type: Joi.string().required(),
message: Joi.string().required().example('Not found')
})
)
}).description('404'),
Joi.object({
messages: Joi.array().items(
Joi.object({
type: Joi.string().required(),
message: Joi.string().required().example('Conflict')
})
)
}).description('409')
]

export const businessLogic = (req: Request, res: Response, next: NextFunction) => {
try {
const { userID } = req.params
return res.json({
user: {
id: userID,
name: 'John',
surname: 'Snow'
}
})
} catch (e) {
return next(e)
}
}
31 changes: 31 additions & 0 deletions demo/endpoints/v2/users/get.users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NextFunction, Request, Response } from 'express'
import Joi from 'joi'
import { userSchema } from './get.user'

export const requestSchema = Joi.object({
params: Joi.object(),
query: Joi.object(),
body: Joi.object()
}).description('Endpoint returns list of users.')

export const responseSchema = Joi.array().items(
Joi.object({
users: userSchema
})
)

export const businessLogic = (_req: Request, res: Response, next: NextFunction) => {
try {
return res.json({
users: [
{
id: 1,
name: 'John',
surname: 'Snow'
}
]
})
} catch (e) {
return next(e)
}
}
Loading