Skip to content

Commit

Permalink
Merge pull request #517 from bitgopatmcl/deref-cleanup
Browse files Browse the repository at this point in the history
Handle enums and const/as assertions
  • Loading branch information
andrew-scott-fischer committed Aug 24, 2023
2 parents d9486c2 + 2078d09 commit 9f50ec7
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 123 deletions.
40 changes: 22 additions & 18 deletions packages/openapi-generator/src/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ function parseObjectExpression(
if (E.isLeft(initE)) {
return initE;
}
const [newSourceFile, init] = initE.right;
let [newSourceFile, init] = initE.right;
if (init.type === 'TsAsExpression' || init.type === 'TsConstAssertion') {
init = init.expression;
}
if (init.type !== 'ObjectExpression') {
return E.left('Spread element must be object');
}
Expand Down Expand Up @@ -209,6 +212,8 @@ export function parsePlainInitializer(
return E.right({ type: 'literal', kind: 'null', value: null });
} else if (init.type === 'Identifier' && init.value === 'undefined') {
return E.right({ type: 'undefined' });
} else if (init.type === 'TsConstAssertion' || init.type === 'TsAsExpression') {
return parsePlainInitializer(project, source, init.expression);
} else if (
init.type === 'Identifier' ||
init.type === 'MemberExpression' ||
Expand Down Expand Up @@ -253,23 +258,21 @@ export function parseCodecInitializer(
return E.right(identifier);
}

function deref(source: SourceFile): DerefFn {
return (schema, fn) => {
if (schema.type !== 'ref') {
return fn(deref(source), schema);
} else {
const initE = findSymbolInitializer(project, source, schema.name);
if (E.isLeft(initE)) {
return initE;
}
const [newSourceFile, init] = initE.right;
const newSchemaE = parsePlainInitializer(project, newSourceFile, init);
if (E.isLeft(newSchemaE)) {
return newSchemaE;
}
return fn(deref(newSourceFile), newSchemaE.right);
function deref(schema: Schema): E.Either<string, Schema> {
if (schema.type !== 'ref') {
return E.right(schema);
} else {
const refSource = project.get(schema.location);
if (refSource === undefined) {
return E.left(`Unknown source ${schema.location}`);
}
};
const initE = findSymbolInitializer(project, refSource, schema.name);
if (E.isLeft(initE)) {
return initE;
}
const [newSourceFile, init] = initE.right;
return parsePlainInitializer(project, newSourceFile, init);
}
}
const args = init.arguments.map<E.Either<string, Schema>>(({ expression }) => {
return parsePlainInitializer(project, source, expression);
Expand All @@ -278,7 +281,8 @@ export function parseCodecInitializer(
return pipe(
args,
E.sequenceArray,
E.chain((args) => identifier.schema(deref(source), ...args)),
E.chain((args) => pipe(args.map(deref), E.sequenceArray)),
E.chain((args) => identifier.schema(deref, ...args)),
);
} else {
return E.left(`Unimplemented initializer type ${init.type}`);
Expand Down
160 changes: 70 additions & 90 deletions packages/openapi-generator/src/knownImports.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import * as E from 'fp-ts/Either';
import { pipe } from 'fp-ts/lib/function';

import type { Schema } from './ir';

export type DerefFn = (ref: Schema, fn: KnownCodec) => E.Either<string, Schema>;
export type DerefFn = (ref: Schema) => E.Either<string, Schema>;
export type KnownCodec = (
deref: DerefFn,
...schemas: Schema[]
Expand Down Expand Up @@ -32,31 +31,27 @@ export const KNOWN_IMPORTS: KnownImports = {
boolean: () => E.right({ type: 'primitive', value: 'boolean' }),
null: () => E.right({ type: 'primitive', value: 'null' }),
array: (_, innerSchema) => E.right({ type: 'array', items: innerSchema }),
type: (deref, schema) => {
return deref(schema, (_, schema) => {
if (schema.type !== 'object') {
return E.left('typeC parameter must be object');
}
const props = Object.entries(schema.properties).reduce((acc, [key, prop]) => {
return { ...acc, [key]: prop };
}, {});
return E.right({
type: 'object',
properties: props,
required: Object.keys(props),
});
type: (_, schema) => {
if (schema.type !== 'object') {
return E.left('typeC parameter must be object');
}
const props = Object.entries(schema.properties).reduce((acc, [key, prop]) => {
return { ...acc, [key]: prop };
}, {});
return E.right({
type: 'object',
properties: props,
required: Object.keys(props),
});
},
partial: (deref, schema) => {
return deref(schema, (_, schema) => {
if (schema.type !== 'object') {
return E.left('typeC parameter must be object');
}
const props = Object.entries(schema.properties).reduce((acc, [key, prop]) => {
return { ...acc, [key]: prop };
}, {});
return E.right({ type: 'object', properties: props, required: [] });
});
partial: (_, schema) => {
if (schema.type !== 'object') {
return E.left('typeC parameter must be object');
}
const props = Object.entries(schema.properties).reduce((acc, [key, prop]) => {
return { ...acc, [key]: prop };
}, {});
return E.right({ type: 'object', properties: props, required: [] });
},
record: (_, _domain, codomain) => {
if (!codomain) {
Expand All @@ -65,46 +60,37 @@ export const KNOWN_IMPORTS: KnownImports = {
return E.right({ type: 'record', codomain });
}
},
union: (deref, schema) => {
return deref(schema, (_, schema) => {
if (schema.type !== 'tuple') {
return E.left('unionC parameter must be array');
}
return E.right({ type: 'union', schemas: schema.schemas });
});
union: (_, schema) => {
if (schema.type !== 'tuple') {
return E.left('unionC parameter must be array');
}
return E.right({ type: 'union', schemas: schema.schemas });
},
intersection: (deref, schema) => {
return deref(schema, (_, schema) => {
if (schema.type !== 'tuple') {
return E.left('unionC parameter must be array');
}
return E.right({ type: 'intersection', schemas: schema.schemas });
});
intersection: (_, schema) => {
if (schema.type !== 'tuple') {
return E.left('unionC parameter must be array');
}
return E.right({ type: 'intersection', schemas: schema.schemas });
},
literal: (deref, arg) => {
return deref(arg, (_, arg) => {
if (arg.type !== 'literal') {
return E.left(`Unimplemented literal type ${arg.type}`);
} else {
return E.right(arg);
}
});
literal: (_, arg) => {
if (arg.type !== 'literal') {
return E.left(`Unimplemented literal type ${arg.type}`);
} else {
return E.right(arg);
}
},
keyof: (deref, arg) => {
return deref(arg, (_, arg) => {
if (arg.type !== 'object') {
return E.left(`Unimplemented keyof type ${arg.type}`);
}
const schemas: E.Either<string, Schema>[] = Object.keys(arg.properties).map(
(prop) => {
return E.right({ type: 'literal', kind: 'string', value: prop });
},
);
return pipe(
schemas,
E.sequenceArray,
E.map((schemas) => ({ type: 'union', schemas: [...schemas] })),
);
keyof: (_, arg) => {
if (arg.type !== 'object') {
return E.left(`Unimplemented keyof type ${arg.type}`);
}
const schemas: Schema[] = Object.keys(arg.properties).map((prop) => ({
type: 'literal',
kind: 'string',
value: prop,
}));
return E.right({
type: 'union',
schemas,
});
},
brand: (_, arg) => E.right(arg),
Expand All @@ -120,26 +106,22 @@ export const KNOWN_IMPORTS: KnownImports = {
'@api-ts/io-ts-http': {
optional: (_, innerSchema) =>
E.right({ type: 'union', schemas: [innerSchema, { type: 'undefined' }] }),
optionalized: (deref, props) => {
return deref(props, (_, props) => {
if (props.type !== 'object') {
return E.left('optionalized parameter must be object');
}
const required = Object.keys(props.properties).filter(
(key) => !isOptional(props.properties[key]!),
);
return E.right({ type: 'object', properties: props.properties, required });
});
optionalized: (_, props) => {
if (props.type !== 'object') {
return E.left('optionalized parameter must be object');
}
const required = Object.keys(props.properties).filter(
(key) => !isOptional(props.properties[key]!),
);
return E.right({ type: 'object', properties: props.properties, required });
},
httpRequest: (deref, arg) => {
if (arg.type !== 'object') {
return E.left(`Unimplemented httpRequest type ${arg.type}`);
}
const properties: Record<string, Schema> = {};
for (const [outerKey, outerValue] of Object.entries(arg.properties)) {
const innerPropsE = deref(outerValue, (_, innerProps) => {
return E.right(innerProps);
});
const innerPropsE = deref(outerValue);

if (E.isLeft(innerPropsE)) {
return innerPropsE;
Expand All @@ -164,22 +146,20 @@ export const KNOWN_IMPORTS: KnownImports = {
});
},
httpRoute: (deref, schema) => {
return deref(schema, (_, schema) => {
if (schema.type !== 'object') {
return E.left('httpRoute parameter must be object');
if (schema.type !== 'object') {
return E.left('httpRoute parameter must be object');
}
const props = Object.entries(schema.properties).reduce((acc, [key, prop]) => {
const derefedE = deref(prop);
if (E.isLeft(derefedE)) {
return acc;
}
const props = Object.entries(schema.properties).reduce((acc, [key, prop]) => {
const derefedE = deref(prop, (_, derefed) => E.right(derefed));
if (E.isLeft(derefedE)) {
return acc;
}
return { ...acc, [key]: derefedE.right };
}, {});
return E.right({
type: 'object',
properties: props,
required: Object.keys(props),
});
return { ...acc, [key]: derefedE.right };
}, {});
return E.right({
type: 'object',
properties: props,
required: Object.keys(props),
});
},
},
Expand Down
42 changes: 41 additions & 1 deletion packages/openapi-generator/src/symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,45 @@ function parseDeclaration(
}
}
});
} else if (subDeclaration.type === 'TsEnumDeclaration') {
const comment =
prev !== undefined
? leadingComment(src, srcSpanStart, prev.span.end, decl.span.start)[0]
: undefined;
// Construct a synthetic object declaration
const properties: swc.KeyValueProperty[] = subDeclaration.members.map((member) => {
return {
type: 'KeyValueProperty',
key: {
type: 'Identifier',
value: member.id.value,
span: member.id.span,
optional: false,
},
value: member.init ?? {
type: 'StringLiteral',
value: member.id.value,
span: member.id.span,
},
};
});
const syntheticObject: swc.ObjectExpression = {
type: 'ObjectExpression',
span: subDeclaration.span,
properties,
};
result.declarations.push({
name: subDeclaration.id.value,
init: syntheticObject,
comment,
});
if (decl.type === 'ExportDeclaration') {
result.exports.push({
type: 'named',
exportedName: subDeclaration.id.value,
localName: subDeclaration.id.value,
});
}
}
return result;
}
Expand Down Expand Up @@ -179,7 +218,8 @@ export function parseTopLevelSymbols(
symbols.imports.push(...newSyms);
} else if (
item.type === 'VariableDeclaration' ||
item.type === 'ExportDeclaration'
item.type === 'ExportDeclaration' ||
item.type === 'TsEnumDeclaration'
) {
const newSyms = parseDeclaration(src, srcSpanStart, items[idx - 1], item);
addTable(newSyms);
Expand Down
Loading

0 comments on commit 9f50ec7

Please sign in to comment.