πŸ§™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 {
  ...
}
  1. Detect by decorator / attribute
  2. 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<{}> {
   ...
}
  1. Detect by decorator β†’ HTTP method
  2. Url β†’ method path + query + path params
  3. ContentType β†’ return type, otherwise json
  4. 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,
) {
...
}
  1. Names β†’ from strings, not var names
  2. PathParam β†’ always string for now ❗
  3. QueryParam β†’ try deriving ❗
  4. 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> {
   ...
}
  1. Body β†’ wins over BodyParam
  2. BodyParams β†’ assemble into virtualBody
  3. Any references β†’ write into aux.json
  4. 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>,
};
  1. Unique referenced-by-controllers declarations ❗ 
  2. Primitives β†’ swagger primitives
  3. Auxiliary, Unions, Intersections β†’ #unknown
  4. Complex β†’ $ref, 1 level deep ❗
  5. 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

  1. Generation is wrong? β†’ @swaggershift-ignore, write yourself ✏️
  2. Something is missing or doesn't fit? β†’ Write manually in docs/**/*.yaml πŸ“–
  3. Want it to be auto-generated? β†’ Go improve the tool πŸ”§ πŸ˜€
  4. 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