Skip to content

Commit

Permalink
feat: add method to generate openApi documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Quentin Nativel committed Nov 17, 2023
1 parent a0ccbb4 commit 358715e
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './lambdaHandler';
export * from './fetchRequest';
export * from './axiosRequest';
export * from './openApiDocumentation';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { getContractDocumentation } from './openApiDocumentation';
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { z } from 'zod';

import { ApiGatewayContract } from 'apiGateway/ApiGatewayContract';
import { HttpStatusCodes } from 'types/http';

import { getContractDocumentation } from './openApiDocumentation';

describe('apiGateway openApi contract documentation', () => {
const pathParametersSchema = z.object({
userId: z.string(),
pageNumber: z.string(),
});

const queryStringParametersSchema = z.object({
testId: z.string(),
});

const headersSchema = z.object({
myHeader: z.string(),
});

const bodySchema = z.object({
foo: z.string(),
});

const outputSchema = z.object({
id: z.string(),
name: z.string(),
});

const unauthorizedSchema = z.object({
message: z.string(),
});

const outputSchemas = {
[HttpStatusCodes.OK]: outputSchema,
[HttpStatusCodes.UNAUTHORIZED]: unauthorizedSchema,
};

describe('httpApi, when all parameters are set', () => {
const httpApiContract = new ApiGatewayContract({
id: 'testContract',
path: '/users/{userId}',
method: 'GET',
integrationType: 'httpApi',
pathParametersSchema,
queryStringParametersSchema,
headersSchema,
bodySchema,
outputSchemas,
});

it('should generate open api documentation', () => {
expect(getContractDocumentation(httpApiContract)).toEqual({
path: '/users/{userId}',
method: 'get',
documentation: {
parameters: [
{
in: 'header',
name: 'myHeader',
required: true,
schema: {
type: 'string',
},
},
{
in: 'query',
name: 'testId',
required: true,
schema: {
type: 'string',
},
},
{
in: 'path',
name: 'userId',
required: true,
schema: {
type: 'string',
},
},
{
in: 'path',
name: 'pageNumber',
required: true,
schema: {
type: 'string',
},
},
],
requestBody: {
content: {
'application/json': {
schema: {
type: 'object',
properties: {
foo: {
type: 'string',
},
},
required: ['foo'],
},
},
},
},
responses: {
'200': {
description: 'Response: 200',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
id: {
type: 'string',
},
name: {
type: 'string',
},
},
required: ['id', 'name'],
},
},
},
},
'401': {
description: 'Response: 401',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
message: {
type: 'string',
},
},
required: ['message'],
},
},
},
},
},
},
});
});
});

describe('restApi, when it is instanciated with a subset of schemas', () => {
const restApiContract = new ApiGatewayContract({
id: 'testContract',
path: 'coucou',
method: 'POST',
integrationType: 'restApi',
});

it('should generate open api documentation', () => {
expect(getContractDocumentation(restApiContract)).toEqual({
path: 'coucou',
method: 'post',
documentation: {
responses: {}, // no response is configured
},
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { generateSchema } from '@anatine/zod-openapi';
import isUndefined from 'lodash/isUndefined';
import omitBy from 'lodash/omitBy';
import { OpenAPIV3 } from 'openapi-types';

import { GenericApiGatewayContract } from 'apiGateway/ApiGatewayContract';
import { ContractOpenApiDocumentation } from 'types/contractOpenApiDocumentation';

export const getContractDocumentation = <
Contract extends GenericApiGatewayContract,
>(
contract: Contract,
): ContractOpenApiDocumentation => {
const initialDocumentation: OpenAPIV3.OperationObject = {
responses: {},
};

const definedOutputSchema = omitBy(contract.outputSchemas, isUndefined);
console.log(definedOutputSchema);

// add responses to the object
const contractDocumentation = Object.keys(definedOutputSchema).reduce(
(config: OpenAPIV3.OperationObject, responseCode) => {
const schema = definedOutputSchema[responseCode];

if (schema === undefined) {
return config;
}

const openApiSchema = generateSchema(schema);

return {
...config,
responses: {
...config.responses,
[responseCode]: {
description: `Response: ${responseCode}`,
content: {
'application/json': {
schema: openApiSchema as OpenAPIV3.SchemaObject,
},
},
},
},
};
},
initialDocumentation,
);

if (contract.pathParametersSchema !== undefined) {
contractDocumentation.parameters = [
...Object.entries(contract.pathParametersSchema.shape).map(
([variableName, variableDefinition]) => ({
name: variableName,
in: 'path',
schema: generateSchema(variableDefinition) as OpenAPIV3.SchemaObject,
required: !variableDefinition.isOptional(),
}),
),
...(contractDocumentation.parameters ?? []),
];
}

if (contract.queryStringParametersSchema !== undefined) {
contractDocumentation.parameters = [
...Object.entries(contract.queryStringParametersSchema.shape).map(
([variableName, variableDefinition]) => ({
name: variableName,
in: 'query',
schema: generateSchema(variableDefinition) as OpenAPIV3.SchemaObject,
required: !variableDefinition.isOptional(),
}),
),
...(contractDocumentation.parameters ?? []),
];
}

if (contract.headersSchema !== undefined) {
contractDocumentation.parameters = [
...Object.entries(contract.headersSchema.shape).map(
([variableName, variableDefinition]) => ({
name: variableName,
in: 'header',
schema: generateSchema(variableDefinition) as OpenAPIV3.SchemaObject,
required: !variableDefinition.isOptional(),
}),
),
...(contractDocumentation.parameters ?? []),
];
}

if (contract.bodySchema !== undefined) {
contractDocumentation.requestBody = {
content: {
'application/json': {
schema: generateSchema(contract.bodySchema) as OpenAPIV3.SchemaObject,
},
},
};
}

return {
path: contract.path,
method: contract.method.toLowerCase(),
documentation: contractDocumentation,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { OpenAPIV3 } from 'openapi-types';

export interface ContractOpenApiDocumentation {
path: string;
method: string;
documentation: OpenAPIV3.OperationObject;
}

0 comments on commit 358715e

Please sign in to comment.