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

Commit

Permalink
Merge pull request #72 from superfaceai/feature/allow-any-result-type
Browse files Browse the repository at this point in the history
feat(generate): Allow every type for Result
  • Loading branch information
lukas-valenta authored Apr 29, 2021
2 parents b5185c1 + 8ce00f9 commit aac219b
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 110 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Changed
* Export SuperfaceClient type from generated SDK
* Allow every type for Result in generated SDK

## [0.0.10] - 2021-04-26

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { typeHelper, TypedProfile } from '@superfaceai/one-sdk';
/** Starwars **/
export interface RetrieveCharacterInformationInput {
export type RetrieveCharacterInformationInput = {
characterName?: unknown;
}
/** Starwars **/
export interface RetrieveCharacterInformationResult {
};
export type RetrieveCharacterInformationResult = {
height?: unknown;
weight?: unknown;
yearOfBirth?: unknown;
}
export const profile = {
};
const profile = {
/** Starwars **/
"RetrieveCharacterInformation": typeHelper<RetrieveCharacterInformationInput, RetrieveCharacterInformationResult>()
};
export type StarwarsCharacterInformationProfile = TypedProfile<typeof profile>;
Expand Down
1 change: 1 addition & 0 deletions src/common/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export enum ContentType {
PROFILE = 'application/vnd.superface.profile',
AST = 'application/vnd.superface.profile+json',
}

export function getStoreUrl(): string {
const envUrl = process.env.SUPERFACE_API_URL;

Expand Down
103 changes: 40 additions & 63 deletions src/logic/generate.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
import { ProfileDocumentNode } from '@superfaceai/ast';
import {
DocumentedStructureType,
EnumStructure,
getProfileOutput,
ProfileOutput,
StructureType,
} from '@superfaceai/parser';
import {
InterfaceDeclaration,
isExportDeclaration,
isIdentifier,
isImportDeclaration,
isObjectLiteralExpression,
isSpreadAssignment,
isVariableStatement,
Statement,
TypeAliasDeclaration,
VariableStatement,
} from 'typescript';

import { developerError } from '../common/error';
import { filterUndefined } from '../common/util';
import {
addDoc,
callExpression,
camelize,
capitalize,
Expand All @@ -30,14 +28,11 @@ import {
getImportText,
getVariableName,
id,
interfaceType,
literalUnion,
namedImport,
objectLiteral,
parseSource,
pascalize,
propertyAssignment,
propertySignature,
reexport,
typeAlias,
typedClientStatement,
Expand All @@ -54,70 +49,45 @@ export function isDocumentedStructure(
return 'title' in structure;
}

export function createEnumTypes(
prefix: string,
fields: Record<string, StructureType | undefined>
): TypeAliasDeclaration[] {
return Object.entries(fields)
.filter(
(entry): entry is [string, EnumStructure] =>
entry[1]?.kind === 'EnumStructure'
)
.map(([field, value]) =>
typeAlias(
prefix + capitalize(field),
literalUnion(value.enums.map(enumValue => enumValue.value))
)
);
}

export function createInterfaceFromStructure(
prefix: string,
fields: Record<string, StructureType>,
untypedType: 'any' | 'unknown',
doc?: { title?: string; description?: string }
): InterfaceDeclaration {
const members = Object.entries(fields).map(([property, value]) =>
propertySignature(
property,
value.required,
variableType(prefix + capitalize(property), value, untypedType),
isDocumentedStructure(value)
? { title: value.title, description: value.description }
: undefined
)
);

return interfaceType(prefix, members, doc);
}

export function createUsecaseTypes(
usecase: ProfileOutput['usecases'][number],
untypedType: 'any' | 'unknown'
): Statement[] {
let inputs: Statement[] = [];
let results: Statement[] = [];

if (usecase.input?.fields) {
if (usecase.input !== undefined) {
const { title, description } = usecase.input;
inputs = [
...createEnumTypes(usecase.useCaseName + 'Input', usecase.input.fields),
createInterfaceFromStructure(
usecase.useCaseName + 'Input',
usecase.input.fields,
untypedType,
{ title: usecase.title, description: usecase.description }
addDoc(
typeAlias(
capitalize(usecase.useCaseName) + 'Input',
variableType(
capitalize(usecase.useCaseName),
usecase.input,
untypedType
)
),
{ title, description }
),
];
}

if (usecase.result?.kind === 'ObjectStructure' && usecase.result.fields) {
if (usecase.result !== undefined) {
const doc = isDocumentedStructure(usecase.result)
? { title: usecase.result.title, description: usecase.result.description }
: undefined;
results = [
...createEnumTypes(usecase.useCaseName + 'Result', usecase.result.fields),
createInterfaceFromStructure(
usecase.useCaseName + 'Result',
usecase.result.fields,
untypedType,
{ title: usecase.title, description: usecase.description }
addDoc(
typeAlias(
capitalize(usecase.useCaseName) + 'Result',
variableType(
capitalize(usecase.useCaseName),
usecase.result,
untypedType
)
),
doc
),
];
}
Expand All @@ -127,20 +97,24 @@ export function createUsecaseTypes(

export function createProfileType(
profileName: string,
usecaseNames: string[]
usecases: { name: string; doc: { title?: string; description?: string } }[]
): Statement[] {
const usecases = usecaseNames.map(usecaseName => {
const pascalizedUsecaseName = pascalize(usecaseName);
const usecaseAssignments = usecases.map(usecase => {
const pascalizedUsecaseName = pascalize(usecase.name);
const helperCall = callExpression(
'typeHelper',
[],
[pascalizedUsecaseName + 'Input', pascalizedUsecaseName + 'Result']
);

return propertyAssignment(usecaseName, helperCall);
return addDoc(propertyAssignment(usecase.name, helperCall), usecase.doc);
});

const profile = variableStatement('profile', objectLiteral(usecases));
const profile = variableStatement(
'profile',
objectLiteral(usecaseAssignments),
true
);
const profileType = typeAlias(
pascalize(profileName) + 'Profile',
typeReference('TypedProfile', [typeQuery('profile')])
Expand Down Expand Up @@ -280,7 +254,10 @@ export function generateTypingsForProfile(
...inputTypes,
...createProfileType(
profileName,
output.usecases.map(usecase => usecase.useCaseName)
output.usecases.map(usecase => ({
name: usecase.useCaseName,
doc: { title: usecase.title, description: usecase.description },
}))
),
];

Expand Down
73 changes: 33 additions & 40 deletions src/logic/generate.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
FalseLiteral,
Identifier,
ImportDeclaration,
InterfaceDeclaration,
isExportSpecifier,
isIdentifier,
isImportSpecifier,
Expand Down Expand Up @@ -46,11 +45,13 @@ import {
VariableStatement,
} from 'typescript';

function addDoc<T extends Node>(
import { isDocumentedStructure } from './generate';

export function addDoc<T extends Node>(
node: T,
doc: { title?: string; description?: string }
doc?: { title?: string; description?: string }
): T {
if (doc.title === undefined) {
if (doc === undefined || doc.title === undefined) {
return node;
}

Expand Down Expand Up @@ -134,11 +135,15 @@ export function keyword(
}
}

export function arrayType(elementType: TypeNode): ArrayTypeNode {
return factory.createArrayTypeNode(elementType);
}

export function variableType(
prefix: string,
structure: StructureType,
untypedType: 'any' | 'unknown'
): KeywordTypeNode | TypeReferenceNode | ArrayTypeNode | TypeLiteralNode {
): KeywordTypeNode | UnionTypeNode | ArrayTypeNode | TypeLiteralNode {
switch (structure.kind) {
case 'PrimitiveStructure':
return keyword(structure.type);
Expand All @@ -147,22 +152,30 @@ export function variableType(
return keyword(untypedType);

case 'EnumStructure':
return factory.createTypeReferenceNode(prefix);
return literalUnion(structure.enums.map(enumValue => enumValue.value));

case 'ListStructure':
return factory.createArrayTypeNode(
variableType(prefix, structure.value, untypedType)
);
return arrayType(variableType(prefix, structure.value, untypedType));

case 'ObjectStructure': {
const properties = Object.entries(
structure.fields ?? {}
).map(([name, innerStructure]) =>
propertySignature(
name,
innerStructure.required,
variableType('', innerStructure, untypedType)
)
const properties = Object.entries(structure.fields ?? {}).map(
([name, innerStructure]) => {
const doc = isDocumentedStructure(innerStructure)
? {
title: innerStructure.title,
description: innerStructure.description,
}
: undefined;

return addDoc(
propertySignature(
name,
innerStructure.required,
variableType('', innerStructure, untypedType)
),
doc
);
}
);

return factory.createTypeLiteralNode(properties);
Expand Down Expand Up @@ -231,27 +244,6 @@ export function namedTuple(
);
}

export function interfaceType(
name: string,
members: TypeElement[],
doc?: { title?: string; description?: string }
): InterfaceDeclaration {
const interfaceType = factory.createInterfaceDeclaration(
undefined,
[exportKeyword],
id(name),
undefined,
undefined,
members
);

if (doc !== undefined) {
return addDoc(interfaceType, doc);
}

return interfaceType;
}

export function propertySignature(
name: string,
required: boolean | undefined,
Expand All @@ -274,10 +266,11 @@ export function propertySignature(

export function variableStatement(
name: string,
initializer: Expression
initializer: Expression,
skipExport = false
): VariableStatement {
return factory.createVariableStatement(
[factory.createToken(SyntaxKind.ExportKeyword)],
skipExport ? [] : [factory.createToken(SyntaxKind.ExportKeyword)],
factory.createVariableDeclarationList(
[
factory.createVariableDeclaration(
Expand Down

0 comments on commit aac219b

Please sign in to comment.