Skip to content
This repository has been archived by the owner on Jun 14, 2021. It is now read-only.

feat: stub generator code #43

Merged
merged 5 commits into from
Jun 6, 2019
Merged
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
144 changes: 134 additions & 10 deletions codegen/src/generators/ResourcesGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,155 @@ import { isErrorStatusCode, isStatusCodeRange, StatusCodeRange, StatusCode, stat
import { capitalize, isReferenceObject } from '../typescript/module/utils';
import { StatusCodeClassNames } from '../StatusCodesClassNames';
import { TSClassBuilder } from '../typescript/TSClassBuilder';
import { groupBy } from 'lodash';
import * as path from 'path'

export class ResourcesGenerator implements Generator {
dependsOn = [ComponentSchemaTypesGenerator]

generate(_document: OpenAPIV3.Document, _tsModule: TSModule): Promise<void> {
async generate(document: OpenAPIV3.Document, tsModule: TSModule): Promise<void> {
// TODO: This replaces ResourceTemplate
// TODO: Create Responses (done)
// TODO: Create Parameters ()
// TODO: Create ResourceInterface
// TODO: Create Resource ()
// TODO: Create ResourceRouter ()

// const responseTypeFactory = new ResponseTypeFactory()
throw new Error("Method not implemented.");
// throw new Error("Method not implemented.")
const resourceRouterNames: string[] = []

const pathsWithResourceName = Object.entries(document.paths).map(([path, pathItemObject]) => ({ path, pathItemObject, resourceName: this.getResourceNameForPath(path) }))
const groupedPathsWithResourceName = groupBy(pathsWithResourceName, item => item.resourceName)

// For every resource, e.g. /pet create the response type, the parameter type and the resource router.
for (const [resourceName, resourcePathDescriptions] of Object.entries(groupedPathsWithResourceName)) {
const tsFile = tsModule.file(path.join('resources', `${capitalize(resourceName)}.ts`))
const resourceOperations: ResourceOperation[] = []

// For every path on a resource
for (const resourcePathDescription of resourcePathDescriptions) {
const { path, pathItemObject } = resourcePathDescription

// For every operation on a resource, e.g. getPet (GET /pets)
for (const pathOperationKey of httpVerbPathOperations) {
const operationObject = pathItemObject[pathOperationKey]
if (!operationObject) {
continue
}

const responseTypeFactory = new ResponseTypeFactory()
const responseType = responseTypeFactory.declarePathResponseType(operationObject, tsFile)

const parameterTypeFactory = new ParameterTypeFactory()
const parameterType = parameterTypeFactory.declareParameterType(operationObject, tsFile)
resourceOperations.push({ path, responseType, parameterType, pathItemObject, method: pathOperationKey })
}

}

const resourceRouterFactory = new ResourceRouterFactory()
const resourceRouterName = resourceRouterFactory.declareResourceRouter(resourceName, resourceOperations, tsFile)
resourceRouterNames.push(resourceRouterName)
}

// TODO: Do things with resourceRouterNames -> Generate the ResourcesConfiguration
}

/**
* For a given path create a resource name.
* @example
* / -> Index
* /pets -> Pets
* /pets/:id -> Pets
*/
private getResourceNameForPath(path: string): string {
const [, prefix] = path.split('/')
if (!prefix || prefix.startsWith('{')) {
return capitalize('index')
}
return capitalize(prefix)
}
}

/**
* A list of all http verbs that are supported by the OpenApi path.
*/
const httpVerbPathOperations = [
'get' as 'get',
'put' as 'put',
'post' as 'post',
'delete' as 'delete',
'options' as 'options',
'head' as 'head',
'patch' as 'patch',
'trace' as 'trace',
]

export interface ResourceOperation {
parameterType: string
responseType: string
path: string
method: string
pathItemObject: OpenAPIV3.PathItemObject
}

/**
* Creates a resource router (resource definition).
* @example
* export class PetsResourceRouter {
* bind(router: SlushyRouter, resource: PetsResource) {
* router.get('/pets', resource.getPets)
* }
* }
*/
export class ResourceRouterFactory {
declareResourceRouter(_resourceName: string, _resourceOperations: Array<ResourceOperation>, _tsFile: TSFile): string {
throw new Error("Method not implemented.")
// TODO: return the resource router class name
}
}

/**
* Creates a resource interface.
* @example
* export interface PetsResource {
* getPetById(params: GetPetByIdParams): Promise<GetPetByIdResponse>
* }
*/
export class ResourceFactory {
}

/**
* Creates a resource operation parameter.
* @example
* export type GetPetByIdParams = { petId: string }
*/
export class ParameterTypeFactory {
declareParameterType(
_operationObject: OpenAPIV3.OperationObject,
_tsFile: TSFile,
): string {
throw new Error("Method not implemented.")
// TODO: return the paramter type name
}
}

/**
* Creates a resource operation response.
* @example
* export class GetPetOK { status = 200 }
* export class GetPetBadRequest extends SlushyError { status = 400 }
* export type GetPetResponse = GetPetOK | GetPetBadRequest
*/
export class ResponseTypeFactory {
declarePathResponseType(
pathItemObject: OpenAPIV3.OperationObject,
operationObject: OpenAPIV3.OperationObject,
tsFile: TSFile,
): string {
if (!pathItemObject.responses) {
if (!operationObject.responses) {
throw new Error('Missing responses')
}

if (!pathItemObject.operationId) {
if (!operationObject.operationId) {
throw new Error('Missing operationId')
}

Expand All @@ -41,15 +165,15 @@ export class ResponseTypeFactory {
const responseClassNames: string[] = []

// Generate one class for every possible response value
for (const [responseStatusCodeString, response] of Object.entries(pathItemObject.responses)) {
for (const [responseStatusCodeString, response] of Object.entries(operationObject.responses)) {
const responseStatusCode = responseStatusCodeString as keyof typeof StatusCodeRange | StatusCode
const responseClassSuffix = StatusCodeClassNames[responseStatusCode];
const responseClassName = `${capitalize(pathItemObject.operationId)}${responseClassSuffix}`;
const responseClassName = `${capitalize(operationObject.operationId)}${responseClassSuffix}`;
responseClassNames.push(responseClassName)
this.declareStatusCodeResponseClass(responseClassName, response, responseStatusCode, tsFile);
}

const responseTypeName = `${capitalize(pathItemObject.operationId)}Response`
const responseTypeName = `${capitalize(operationObject.operationId)}Response`
const responseTypeDefinition = `export type ${responseTypeName} = ${responseClassNames.join(' | ')}`
tsFile.addSourceText(responseTypeDefinition)
return responseTypeName
Expand Down