diff --git a/packages/openapi-generator/src/apiSpec.ts b/packages/openapi-generator/src/apiSpec.ts index d04514d3..24482261 100644 --- a/packages/openapi-generator/src/apiSpec.ts +++ b/packages/openapi-generator/src/apiSpec.ts @@ -8,6 +8,7 @@ import { resolveLiteralOrIdentifier } from './resolveInit'; import { parseRoute, type Route } from './route'; import { SourceFile } from './sourceFile'; import { OpenAPIV3 } from 'openapi-types'; +import { errorLeft } from './error'; export function parseApiSpec( project: Project, @@ -15,7 +16,7 @@ export function parseApiSpec( expr: swc.Expression, ): E.Either { if (expr.type !== 'ObjectExpression') { - return E.left(`unimplemented route expression type ${expr.type}`); + return errorLeft(`unimplemented route expression type ${expr.type}`); } const result: Route[] = []; @@ -34,7 +35,7 @@ export function parseApiSpec( if (spreadExpr.type === 'CallExpression') { const arg = spreadExpr.arguments[0]; if (arg === undefined) { - return E.left(`unimplemented spread argument type ${arg}`); + return errorLeft(`unimplemented spread argument type ${arg}`); } spreadExpr = arg.expression; } @@ -47,7 +48,7 @@ export function parseApiSpec( } if (apiAction.type !== 'KeyValueProperty') { - return E.left(`unimplemented route property type ${apiAction.type}`); + return errorLeft(`unimplemented route property type ${apiAction.type}`); } const routes = apiAction.value; const routesInitE = resolveLiteralOrIdentifier(project, sourceFile, routes); @@ -56,11 +57,11 @@ export function parseApiSpec( } const [routesSource, routesInit] = routesInitE.right; if (routesInit.type !== 'ObjectExpression') { - return E.left(`unimplemented routes type ${routes.type}`); + return errorLeft(`unimplemented routes type ${routes.type}`); } for (const route of Object.values(routesInit.properties)) { if (route.type !== 'KeyValueProperty') { - return E.left(`unimplemented route type ${route.type}`); + return errorLeft(`unimplemented route type ${route.type}`); } const routeExpr = route.value; const routeInitE = resolveLiteralOrIdentifier(project, routesSource, routeExpr); diff --git a/packages/openapi-generator/src/cli.ts b/packages/openapi-generator/src/cli.ts index 276a3952..dfd061ef 100644 --- a/packages/openapi-generator/src/cli.ts +++ b/packages/openapi-generator/src/cli.ts @@ -18,6 +18,7 @@ import { KNOWN_IMPORTS } from './knownImports'; import { findSymbolInitializer } from './resolveInit'; import { parseCodecInitializer } from './codec'; import { SourceFile } from './sourceFile'; +import { logError, logInfo, logWarn } from './error'; const app = command({ name: 'api-ts', @@ -87,7 +88,7 @@ const app = command({ const codecFilePath = p.resolve(codecFile); const codecModule = await import(codecFilePath); if (codecModule.default === undefined) { - console.error(`Could not find default export in ${codecFilePath}`); + logError(`Could not find default export in ${codecFilePath}`); process.exit(1); } const customCodecs = codecModule.default(E); @@ -96,13 +97,13 @@ const app = command({ const project = await new Project({}, knownImports).parseEntryPoint(filePath); if (E.isLeft(project)) { - console.error(project.left); + logError(`${project.left}`); process.exit(1); } const entryPoint = project.right.get(filePath); if (entryPoint === undefined) { - console.error(`Could not find entry point ${filePath}`); + logError(`Could not find entry point ${filePath}`); process.exit(1); } @@ -119,14 +120,12 @@ const app = command({ symbol.init.callee.type === 'Super' || symbol.init.callee.type === 'Import' ) { - console.error( - `Skipping ${symbol.name} because it is a ${symbol.init.callee.type}`, - ); + logWarn(`Skipping ${symbol.name} because it is a ${symbol.init.callee.type}`); continue; } else if (!isApiSpec(entryPoint, symbol.init.callee)) { continue; } - console.error(`Found API spec in ${symbol.name}`); + logInfo(`[INFO] Found API spec in ${symbol.name}`); const result = parseApiSpec( project.right, @@ -134,7 +133,7 @@ const app = command({ symbol.init.arguments[0]!.expression, ); if (E.isLeft(result)) { - console.error(`Error parsing ${symbol.name}: ${result.left}`); + logError(`Error when parsing ${symbol.name}: ${result.left}`); process.exit(1); } @@ -145,7 +144,7 @@ const app = command({ apiSpec.push(...result.right); } if (apiSpec.length === 0) { - console.error(`Could not find API spec in ${filePath}`); + logError(`Could not find API spec in ${filePath}`); process.exit(1); } @@ -166,14 +165,14 @@ const app = command({ } const sourceFile = project.right.get(ref.location); if (sourceFile === undefined) { - console.error(`Could not find '${ref.name}' from '${ref.location}'`); + logError(`Could not find '${ref.name}' from '${ref.location}'`); process.exit(1); } const initE = findSymbolInitializer(project.right, sourceFile, ref.name); if (E.isLeft(initE)) { console.error( - `Could not find symbol '${ref.name}' in '${ref.location}': ${initE.left}`, + `[ERROR] Could not find symbol '${ref.name}' in '${ref.location}': ${initE.left}`, ); process.exit(1); } @@ -182,7 +181,7 @@ const app = command({ const codecE = parseCodecInitializer(project.right, newSourceFile, init); if (E.isLeft(codecE)) { console.error( - `Could not parse codec '${ref.name}' in '${ref.location}': ${codecE.left}`, + `[ERROR] Could not parse codec '${ref.name}' in '${ref.location}': ${codecE.left}`, ); process.exit(1); } diff --git a/packages/openapi-generator/src/codec.ts b/packages/openapi-generator/src/codec.ts index f420c55d..de5b12ae 100644 --- a/packages/openapi-generator/src/codec.ts +++ b/packages/openapi-generator/src/codec.ts @@ -10,6 +10,7 @@ import { findSymbolInitializer } from './resolveInit'; import type { SourceFile } from './sourceFile'; import type { KnownCodec } from './knownImports'; +import { errorLeft } from './error'; type ResolvedIdentifier = Schema | { type: 'codec'; schema: KnownCodec }; @@ -26,9 +27,9 @@ function codecIdentifier( const imp = source.symbols.imports.find((s) => s.localName === id.value); if (imp === undefined) { - return E.left(`Unknown identifier ${id.value}`); + return errorLeft(`Unknown identifier ${id.value}`); } else if (imp.type === 'star') { - return E.left(`Tried to use star import as codec ${id.value}`); + return errorLeft(`Tried to use star import as codec ${id.value}`); } const knownImport = project.resolveKnownImport(imp.from, imp.importedName); if (knownImport !== undefined) { @@ -54,10 +55,12 @@ function codecIdentifier( const object = id.object; if (object.type !== 'Identifier') { if (object.type === 'MemberExpression') - return E.left( - `Object ${((object as swc.MemberExpression) && { value: String }).value} is deeply nested, which is unsupported`, + return errorLeft( + `Object ${ + ((object as swc.MemberExpression) && { value: String }).value + } is deeply nested, which is unsupported`, ); - return E.left(`Unimplemented object type ${object.type}`); + return errorLeft(`Unimplemented object type ${object.type}`); } // Parse member expressions that come from `* as foo` imports @@ -66,7 +69,7 @@ function codecIdentifier( ); if (starImportSym !== undefined) { if (id.property.type !== 'Identifier') { - return E.left(`Unimplemented property type ${id.property.type}`); + return errorLeft(`Unimplemented property type ${id.property.type}`); } const name = id.property.value; @@ -96,7 +99,7 @@ function codecIdentifier( ); if (objectImportSym !== undefined) { if (id.property.type !== 'Identifier') { - return E.left(`Unimplemented property type ${id.property.type}`); + return errorLeft(`Unimplemented property type ${id.property.type}`); } const name = id.property.value; @@ -113,9 +116,9 @@ function codecIdentifier( if (E.isLeft(objectSchemaE)) { return objectSchemaE; } else if (objectSchemaE.right.type !== 'object') { - return E.left(`Expected object, got '${objectSchemaE.right.type}'`); + return errorLeft(`Expected object, got '${objectSchemaE.right.type}'`); } else if (objectSchemaE.right.properties[name] === undefined) { - return E.left( + return errorLeft( `Unknown property '${name}' in '${objectImportSym.localName}' from '${objectImportSym.from}'`, ); } else { @@ -124,7 +127,7 @@ function codecIdentifier( } if (id.property.type !== 'Identifier') { - return E.left(`Unimplemented property type ${id.property.type}`); + return errorLeft(`Unimplemented property type ${id.property.type}`); } // Parse locally declared member expressions @@ -136,11 +139,11 @@ function codecIdentifier( if (E.isLeft(schemaE)) { return schemaE; } else if (schemaE.right.type !== 'object') { - return E.left( + return errorLeft( `Expected object, got '${schemaE.right.type}' for '${declarationSym.name}'`, ); } else if (schemaE.right.properties[id.property.value] === undefined) { - return E.left( + return errorLeft( `Unknown property '${id.property.value}' in '${declarationSym.name}'`, ); } else { @@ -158,7 +161,7 @@ function codecIdentifier( } } - return E.left(`Unimplemented identifier type ${id.type}`); + return errorLeft(`Unimplemented identifier type ${id.type}`); } function parseObjectExpression( @@ -210,19 +213,19 @@ function parseObjectExpression( schema = schemaE.right; } if (schema.type !== 'object') { - return E.left(`Spread element must be object`); + return errorLeft(`Spread element must be object`); } Object.assign(result.properties, schema.properties); result.required.push(...schema.required); continue; } else if (property.type !== 'KeyValueProperty') { - return E.left(`Unimplemented property type ${property.type}`); + return errorLeft(`Unimplemented property type ${property.type}`); } else if ( property.key.type !== 'Identifier' && property.key.type !== 'StringLiteral' && property.key.type !== 'NumericLiteral' ) { - return E.left(`Unimplemented property key type ${property.key.type}`); + return errorLeft(`Unimplemented property key type ${property.key.type}`); } const commentEndIdx = property.key.span.start; const comments = leadingComment( @@ -254,7 +257,7 @@ function parseArrayExpression( const result: Schema[] = []; for (const element of array.elements) { if (element === undefined) { - return E.left('Undefined array element'); + return errorLeft('Undefined array element'); } const valueE = parsePlainInitializer(project, source, element.expression); if (E.isLeft(valueE)) { @@ -279,7 +282,7 @@ function parseArrayExpression( init = schemaE.right; } if (init.type !== 'tuple') { - return E.left('Spread element must be array literal'); + return errorLeft('Spread element must be array literal'); } result.push(...init.schemas); } else { @@ -342,7 +345,7 @@ export function parseCodecInitializer( } else if (init.type === 'CallExpression') { const callee = init.callee; if (callee.type !== 'Identifier' && callee.type !== 'MemberExpression') { - return E.left(`Unimplemented callee type ${init.callee.type}`); + return errorLeft(`Unimplemented callee type ${init.callee.type}`); } const identifierE = codecIdentifier(project, source, callee); if (E.isLeft(identifierE)) { @@ -364,10 +367,10 @@ export function parseCodecInitializer( // schema.location might be a package name -> need to resolve the path from the project types const path = project.getTypes()[schema.name]; if (path === undefined) - return E.left(`Cannot find module '${schema.location}' in the project`); + return errorLeft(`Cannot find module '${schema.location}' in the project`); refSource = project.get(path); if (refSource === undefined) { - return E.left(`Cannot find '${schema.name}' from '${schema.location}'`); + return errorLeft(`Cannot find '${schema.name}' from '${schema.location}'`); } } const initE = findSymbolInitializer(project, refSource, schema.name); @@ -394,6 +397,6 @@ export function parseCodecInitializer( E.chain((args) => identifier.schema(deref, ...args)), ); } else { - return E.left(`Unimplemented initializer type ${init.type}`); + return errorLeft(`Unimplemented initializer type ${init.type}`); } } diff --git a/packages/openapi-generator/src/error.ts b/packages/openapi-generator/src/error.ts new file mode 100644 index 00000000..f4bcdb2a --- /dev/null +++ b/packages/openapi-generator/src/error.ts @@ -0,0 +1,35 @@ +import * as E from 'fp-ts/Either'; + +/** + * A wrapper around `E.left` that includes a stacktrace. + * @param message the error message + * @returns an `E.left` with the error message and a stacktrace + */ +export function errorLeft(message: string): E.Either { + const stacktrace = new Error().stack!.split('\n').slice(2).join('\n'); + const messageWithStacktrace = message + '\n' + stacktrace; + + return E.left(messageWithStacktrace); +} + +/** + * Testing utility to strip the stacktrace from errors. + * @param errors the list of errors to strip + * @returns the errors without the stacktrace + */ +export function stripStacktraceOfErrors(errors: string[]) { + return errors.map((e) => e!.split('\n')[0]); +} + +// helper functions for logging +export function logError(message: string): void { + console.error(`[ERROR] ${message}`); +} + +export function logWarn(message: string): void { + console.error(`[WARN] ${message}`); +} + +export function logInfo(message: string): void { + console.error(`[INFO] ${message}`); +} diff --git a/packages/openapi-generator/src/knownImports.ts b/packages/openapi-generator/src/knownImports.ts index 000f4417..5734b0b6 100644 --- a/packages/openapi-generator/src/knownImports.ts +++ b/packages/openapi-generator/src/knownImports.ts @@ -1,6 +1,7 @@ import * as E from 'fp-ts/Either'; import { isPrimitive, type Schema } from './ir'; +import { errorLeft } from './error'; export type DerefFn = (ref: Schema) => E.Either; export type KnownCodec = ( @@ -28,13 +29,13 @@ export const KNOWN_IMPORTS: KnownImports = { global: { 'Object.assign': (_, ...schemas) => { if (schemas.length < 2) { - return E.left('assign must have at least 2 arguments'); + return errorLeft('assign must have at least 2 arguments'); } const [target, ...sources] = schemas; if (target === undefined) { - return E.left('assign target must be object'); + return errorLeft('assign target must be object'); } else if (target.type !== 'object') { - return E.left('assign target must be object'); + return errorLeft('assign target must be object'); } const properties = sources.reduce((acc, source) => { if (source.type !== 'object') { @@ -60,7 +61,7 @@ export const KNOWN_IMPORTS: KnownImports = { object: () => E.right({ type: 'object', properties: {}, required: [] }), type: (_, schema) => { if (schema.type !== 'object') { - return E.left('typeC parameter must be object'); + return errorLeft('typeC parameter must be object'); } const props = Object.entries(schema.properties).reduce((acc, [key, prop]) => { return { ...acc, [key]: prop }; @@ -73,7 +74,7 @@ export const KNOWN_IMPORTS: KnownImports = { }, partial: (_, schema) => { if (schema.type !== 'object') { - return E.left('typeC parameter must be object'); + return errorLeft('typeC parameter must be object'); } const props = Object.entries(schema.properties).reduce((acc, [key, prop]) => { return { ...acc, [key]: prop }; @@ -83,7 +84,7 @@ export const KNOWN_IMPORTS: KnownImports = { exact: (_, schema) => E.right(schema), strict: (_, schema) => { if (schema.type !== 'object') { - return E.left('exactC parameter must be object'); + return errorLeft('exactC parameter must be object'); } const props = Object.entries(schema.properties).reduce((acc, [key, prop]) => { return { ...acc, [key]: prop }; @@ -96,33 +97,33 @@ export const KNOWN_IMPORTS: KnownImports = { }, record: (_, domain, codomain) => { if (!codomain) { - return E.left('Codomain of record must be specified'); + return errorLeft('Codomain of record must be specified'); } else { return E.right({ type: 'record', domain, codomain }); } }, union: (_, schema) => { if (schema.type !== 'tuple') { - return E.left('unionC parameter must be array'); + return errorLeft('unionC parameter must be array'); } return E.right({ type: 'union', schemas: schema.schemas }); }, intersection: (_, schema) => { if (schema.type !== 'tuple') { - return E.left('unionC parameter must be array'); + return errorLeft('unionC parameter must be array'); } return E.right({ type: 'intersection', schemas: schema.schemas }); }, literal: (_, arg) => { if (!isPrimitive(arg) || arg.enum === undefined) { - return E.left(`Unimplemented literal type ${arg.type}`); + return errorLeft(`Unimplemented literal type ${arg.type}`); } else { return E.right(arg); } }, keyof: (_, arg) => { if (arg.type !== 'object') { - return E.left(`Unimplemented keyof type ${arg.type}`); + return errorLeft(`Unimplemented keyof type ${arg.type}`); } const schemas: Schema[] = Object.keys(arg.properties).map((prop) => ({ type: 'string', @@ -377,7 +378,7 @@ export const KNOWN_IMPORTS: KnownImports = { E.right({ type: 'union', schemas: [innerSchema, { type: 'undefined' }] }), optionalized: (_, props) => { if (props.type !== 'object') { - return E.left('optionalized parameter must be object'); + return errorLeft('optionalized parameter must be object'); } const required = Object.keys(props.properties).filter( (key) => !isOptional(props.properties[key]!), @@ -386,7 +387,7 @@ export const KNOWN_IMPORTS: KnownImports = { }, httpRequest: (deref, arg) => { if (arg.type !== 'object') { - return E.left(`Unimplemented httpRequest type ${arg.type}`); + return errorLeft(`Unimplemented httpRequest type ${arg.type}`); } const properties: Record = {}; for (const [outerKey, outerValue] of Object.entries(arg.properties)) { @@ -397,7 +398,7 @@ export const KNOWN_IMPORTS: KnownImports = { } const innerProps = innerPropsE.right; if (innerProps.type !== 'object') { - return E.left(`Unimplemented httpRequest type ${innerProps.type}`); + return errorLeft(`Unimplemented httpRequest type ${innerProps.type}`); } innerProps.required = innerProps.required.filter( @@ -416,7 +417,7 @@ export const KNOWN_IMPORTS: KnownImports = { }, httpRoute: (deref, schema) => { if (schema.type !== 'object') { - return E.left('httpRoute parameter must be object'); + return errorLeft('httpRoute parameter must be object'); } const props: Record = {}; for (const [key, value] of Object.entries(schema.properties)) { diff --git a/packages/openapi-generator/src/project.ts b/packages/openapi-generator/src/project.ts index 9aa98cda..fa11ed34 100644 --- a/packages/openapi-generator/src/project.ts +++ b/packages/openapi-generator/src/project.ts @@ -6,6 +6,7 @@ import resolve from 'resolve'; import { KNOWN_IMPORTS, type KnownCodec } from './knownImports'; import { parseSource, type SourceFile } from './sourceFile'; +import { errorLeft } from './error'; const readFile = promisify(fs.readFile); @@ -110,7 +111,7 @@ export class Project { } if (!typesEntryPoint) { - return E.left(`Could not find types entry point for ${library}`); + return errorLeft(`Could not find types entry point for ${library}`); } const entryPoint = resolve.sync(`${library}/${typesEntryPoint}`, { @@ -119,7 +120,7 @@ export class Project { }); return E.right(entryPoint); } catch (err) { - return E.left(`Could not resolve entry point for ${library}: ${err}`); + return errorLeft(`Could not resolve entry point for ${library}: ${err}`); } } @@ -132,10 +133,10 @@ export class Project { return E.right(result); } catch (e: unknown) { if (e instanceof Error && e.message) { - return E.left(e.message); + return errorLeft(e.message); } - return E.left(JSON.stringify(e)); + return errorLeft(JSON.stringify(e)); } } diff --git a/packages/openapi-generator/src/resolveInit.ts b/packages/openapi-generator/src/resolveInit.ts index 6c254b22..567ed1dd 100644 --- a/packages/openapi-generator/src/resolveInit.ts +++ b/packages/openapi-generator/src/resolveInit.ts @@ -5,6 +5,7 @@ import { dirname } from 'path'; import type { Project } from './project'; import type { SourceFile } from './sourceFile'; +import { errorLeft } from './error'; type ResolvedInitializer = [SourceFile, swc.Expression, Block | undefined]; @@ -25,7 +26,7 @@ function resolveImportPath( } const importSourceFile = project.get(importPathE.right); if (importSourceFile === undefined) { - return E.left(importPathE.right); + return errorLeft(importPathE.right); } return E.right(importSourceFile); } @@ -43,14 +44,14 @@ function findExportedDeclaration( for (const starExport of sourceFile.symbols.exportStarFiles) { const starSourceFile = resolveImportPath(project, sourceFile, starExport); if (E.isLeft(starSourceFile)) { - return E.left(`Cannot resolve * export from '${starExport}'`); + return errorLeft(`Cannot resolve * export from '${starExport}'`); } const starExportE = findExportedDeclaration(project, starSourceFile.right, name); if (E.isRight(starExportE)) { return starExportE; } } - return E.left(`Unknown identifier ${name}`); + return errorLeft(`Unknown identifier ${name}`); } export function findSymbolInitializer( @@ -83,12 +84,12 @@ export function findSymbolInitializer( if (imp.localName === name) { const impSourceFile = resolveImportPath(project, sourceFile, imp.from); if (E.isLeft(impSourceFile)) { - return E.left(`Cannot resolve import '${imp.localName}' from '${imp.from}'`); + return errorLeft(`Cannot resolve import '${imp.localName}' from '${imp.from}'`); } return findExportedDeclaration(project, impSourceFile.right, imp.importedName); } } - return E.left(`Unknown identifier ${name}`); + return errorLeft(`Unknown identifier ${name}`); } export function resolveLiteralOrIdentifier( @@ -103,9 +104,9 @@ export function resolveLiteralOrIdentifier( name = expr.value; } else { if (expr.object.type !== 'Identifier') { - return E.left(`Unimplemented object type ${expr.object.type}`); + return errorLeft(`Unimplemented object type ${expr.object.type}`); } else if (expr.property.type !== 'Identifier') { - return E.left(`Unimplemented property type ${expr.property.type}`); + return errorLeft(`Unimplemented property type ${expr.property.type}`); } name = [expr.object.value, expr.property.value]; } diff --git a/packages/openapi-generator/src/route.ts b/packages/openapi-generator/src/route.ts index 1db4d1a1..4a84d674 100644 --- a/packages/openapi-generator/src/route.ts +++ b/packages/openapi-generator/src/route.ts @@ -5,6 +5,7 @@ import { parseCodecInitializer } from './codec'; import type { CombinedType, Schema } from './ir'; import type { Project } from './project'; import { findSymbolInitializer } from './resolveInit'; +import { errorLeft } from './error'; export type Parameter = { type: 'path' | 'query'; @@ -35,7 +36,7 @@ function derefRequestSchema( if (schema.type === 'ref') { const sourceFile = project.get(schema.location); if (sourceFile === undefined) { - return E.left(`Could not find '${schema.name}' from '${schema.location}'`); + return errorLeft(`Could not find '${schema.name}' from '${schema.location}'`); } const initE = findSymbolInitializer(project, sourceFile, schema.name); if (E.isLeft(initE)) { @@ -50,13 +51,13 @@ function derefRequestSchema( function parseRequestObject(schema: Schema): E.Either { if (schema.type !== 'object') { - return E.left('request must be an object'); + return errorLeft('request must be an object'); } const parameters: Parameter[] = []; const querySchema = schema.properties['query']; if (querySchema !== undefined) { if (querySchema.type !== 'object') { - return E.left('Route query must be an object'); + return errorLeft('Route query must be an object'); } else { for (const [name, prop] of Object.entries(querySchema.properties)) { parameters.push({ @@ -71,7 +72,7 @@ function parseRequestObject(schema: Schema): E.Either { const pathSchema = schema.properties['params']; if (pathSchema !== undefined) { if (pathSchema.type !== 'object') { - return E.left('Route path must be an object'); + return errorLeft('Route path must be an object'); } for (const [name, prop] of Object.entries(pathSchema.properties)) { parameters.push({ @@ -94,7 +95,7 @@ function parseRequestUnion( schema: Schema, ): E.Either { if (schema.type !== 'union') { - return E.left('request must be a union'); + return errorLeft('request must be a union'); } // For query params and body, construct a union of the related sub-schemas @@ -114,7 +115,7 @@ function parseRequestUnion( } if (subSchema.type !== 'object') { - return E.left('Route request union must be all objects'); + return errorLeft('Route request union must be all objects'); } if (subSchema.properties['query'] !== undefined) { querySchema.schemas.push(subSchema.properties['query']); @@ -159,7 +160,7 @@ function parseRequestIntersection( schema: Schema, ): E.Either { if (schema.type !== 'intersection') { - return E.left('request must be an intersection'); + return errorLeft('request must be an intersection'); } const result: Request = { parameters: [], @@ -198,36 +199,36 @@ function parseRequestSchema( } else if (schema.type === 'intersection') { return parseRequestIntersection(project, schema); } else { - return E.left(`Unsupported request type ${schema.type}`); + return errorLeft(`Unsupported request type ${schema.type}`); } } export function parseRoute(project: Project, schema: Schema): E.Either { if (schema.type !== 'object') { - return E.left('Route must be an object'); + return errorLeft('Route must be an object'); } if (schema.properties['path'] === undefined) { - return E.left('Route must have a path'); + return errorLeft('Route must have a path'); } else if ( schema.properties['path'].type !== 'string' || schema.properties['path'].enum?.length !== 1 ) { - return E.left('Route path must be a string literal'); + return errorLeft('Route path must be a string literal'); } if (schema.properties['method'] === undefined) { - return E.left('Route must have a method'); + return errorLeft('Route must have a method'); } else if ( schema.properties['method'].type !== 'string' || schema.properties['method'].enum?.length !== 1 ) { - return E.left('Route method must be a string literal'); + return errorLeft('Route method must be a string literal'); } const requestSchema = schema.properties['request']; if (requestSchema === undefined) { - return E.left('Route must have a request'); + return errorLeft('Route must have a request'); } const requestPropertiesE = parseRequestSchema(project, requestSchema); if (E.isLeft(requestPropertiesE)) { @@ -236,9 +237,9 @@ export function parseRoute(project: Project, schema: Schema): E.Either