π§Dark Magic at Moonfare
Valeriy Kuzmin, Moonfare, Berlin, 2022
Plan
- Problem
- Solution overview
- Implementation details
- Limitations / future plans
Problem π§ͺ
- Api docs...
- Developers...
Swagger / OpenAPI
/**
* @swagger
*
* /v3/documents/:documentId/download:
* get:
* description: <truncated>
* consumes:
* - application/json
* produces:
* - application/octet-stream
* parameters:
* - in: path
* name: documentId
* required: true
* type: string
* responses:
* "200":
* description: ok
*
* */
Goals π―
- You don't have to write docs β 70%
- But you can augment generated docs β 100%
- Docs match code β 80%
- Docs can be executed β 20%
Solution overview
DTOs
docs/**/*.yaml
@swagger comments
*.swagger.yaml
*.swagger.yaml
*.swagger.yaml
mf_spec.yaml
Controllers
Swaggershift
Implementation details
Pipeline
derive-comments
derive-comments
derive-comments
dto-to-comments
derive-comments
aux.json
derive-comments
swagger.yaml
derive-comments
ast-tools cache
derive-comments
comments + lint
Controller anatomy
@Routing.ApiController(ROUTE_PREFIX_V1, '/root-url/root-2')
class Controller {
...
}
- Detect by decorator / attribute
- Url β Base path
Method anatomy
@Routing.Get('/:id/some-string/:some-other-id(\\d+)')
@Routing.HttpCode(HTTP.OK)
@Routing.Authorized([Roles.DOCUMENT_ADMIN])
@Routing.ContentType('application/json')
public async paramsAndQueryMethod(@Routing.Param('id') _categoryId: number): Promise<{}> {
...
}
- Detect by decorator β HTTP method
- Url β method path + query + path params
- ContentType β return type, otherwise json
- Signature β params list
Parameter anatomy - query & path
@Routing.Get('/referrals/list/:id')
public async doesNotEatDecorators(
@Routing.Param('id') id: number,
@Routing.QueryParam('hint') hint: number,
@Routing.QueryParam('hint2') hint2: boolean,
@Routing.QueryParam('order') order: 'ASC' | 'DESC' = 'DESC',
@Routing.QueryParam('perPage') perPage = DEFAULT_RESULTS_PER_PAGE,
) {
...
}
- Names β from strings, not var names
- PathParam β always string for now β
- QueryParam β try deriving β
- Requiredness β try deriving βΒ πΒ
Parameter anatomy - body & bodyParam
@Routing.Post('/')
@Routing.HttpCode(HTTP.OK)
public async fullBodyMethod(
@Routing.Body() _body: SomeDTO
): Promise<void> {
...
}
@Routing.Put('/')
@Routing.HttpCode(HTTP.OK)
public async virtualBodyMethod(
@Routing.BodyParam('dtoAsBodyField') _body: SomeDTO
): Promise<void> {
...
}
- Body β wins over BodyParam
- BodyParams β assemble into virtualBody
- Any references β write into aux.json
- Requiredness β always required βΒ
Type mapping - params & DTOs
export interface MyInterfaceDTO {
count: number,
name: string,
flag: boolean,
strangeStuff: Partial<MyTypeDTO>,
}
export type MyTypeDTO = {
count: number,
name: string,
flag: boolean,
extra: SomeOtherType,
parts: Array<string>,
};
- Unique referenced-by-controllers declarations βΒ
- Primitives β swagger primitives
- Auxiliary, Unions, Intersections β #unknown
- Complex β $ref, 1 level deep β
- Built-ins / Externals Β β Manually in external.yaml
Testing
// __testfixtures__/name.input.ts
SomeCode
// __testfixtures__/name.output.ts
SomeTransformedCode
// __testfixtures__/name.side-effect.ts
Stuff that is written into files
// .spec
runAsyncTestWithSideEffects(name)
// βοΈ it is possible to pass options to transforms!
- Input + output formatting is normalized via eslint βοΈ
unit-tests:ast-tools
Augmentation & tricks
- Generation is wrong? β @swaggershift-ignore, write yourself βοΈ
- Something is missing or doesn't fit? β Write manually in docs/**/*.yaml π
- Want it to be auto-generated? β Go improve the tool π§ π
- Don't like formatting? β Bother Sergii π€£ or go write eslint rules with fixes π§
Limitations & further plans π₯
- Missing features
- Nesting DTOs
- Enums
- Some aux types
- Return shapes
- Make it invisible π₯
- Make it typed β
- Related features
- Some more eslint rules
- Better autoformatting
The end!
Questions?
Dark magic in Moonfare
By Valeriy Kuzmin
Dark magic in Moonfare
- 189