Skip to content

Commit 8daadf6

Browse files
Use Result while deriving schema types
1 parent c4c36ed commit 8daadf6

File tree

2 files changed

+41
-46
lines changed

2 files changed

+41
-46
lines changed

ndc-lambda-sdk/src/inference.ts

Lines changed: 35 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -157,36 +157,36 @@ function deriveFunctionSchema(functionDeclaration: ts.FunctionDeclaration, conte
157157

158158
const paramTypeResult = deriveSchemaTypeForTsType(paramType, paramTypePath, context);
159159

160-
if ("errors" in paramTypeResult) {
160+
if (paramTypeResult instanceof Err) {
161161
// Record the error, discard the parameter, but mark the function
162162
// as broken so we discard the whole thing at the end
163-
issues.push(...paramTypeResult.errors)
163+
issues.push(...paramTypeResult.error)
164164
functionIsBroken = true;
165165
return [];
166166
} else {
167-
issues.push(...paramTypeResult.warnings)
167+
issues.push(...paramTypeResult.data.warnings)
168168
return [{
169169
argumentName: paramName,
170170
description: paramDesc ? paramDesc : null,
171-
type: paramTypeResult.typeDefinition,
171+
type: paramTypeResult.data.typeDefinition,
172172
}]
173173
}
174174
});
175175

176176
const returnTypeResult = deriveSchemaTypeForTsType(functionCallSig.getReturnType(), [{segmentType: "FunctionReturn", functionName}], context);
177177
let functionDefinition: schema.FunctionDefinition | null = null;
178-
if ("errors" in returnTypeResult) {
178+
if (returnTypeResult instanceof Err) {
179179
// Record the error, mark the function as broken so we discard the whole thing at the end
180-
issues.push(...returnTypeResult.errors)
180+
issues.push(...returnTypeResult.error)
181181
functionIsBroken = true;
182182
functionDefinition = null;
183183
} else {
184-
issues.push(...returnTypeResult.warnings)
184+
issues.push(...returnTypeResult.data.warnings)
185185
functionDefinition = {
186186
description: functionDescription ? functionDescription : null,
187187
ndcKind: markedPureInJsDoc ? schema.FunctionNdcKind.Function : schema.FunctionNdcKind.Procedure,
188188
arguments: functionSchemaArguments,
189-
resultType: returnTypeResult.typeDefinition
189+
resultType: returnTypeResult.data.typeDefinition
190190
}
191191
}
192192

@@ -219,26 +219,25 @@ function typePathSegmentToString(segment: TypePathSegment): string {
219219
}
220220
}
221221

222-
type DeriveSchemaTypeResult =
222+
type DerivedSchemaType =
223223
{ typeDefinition: schema.TypeDefinition, warnings: string[] }
224-
| { errors: string[] }
225224

226-
function deriveSchemaTypeForTsType(tsType: ts.Type, typePath: TypePathSegment[], context: TypeDerivationContext, recursionDepth: number = 0): DeriveSchemaTypeResult {
225+
function deriveSchemaTypeForTsType(tsType: ts.Type, typePath: TypePathSegment[], context: TypeDerivationContext, recursionDepth: number = 0): Result<DerivedSchemaType, string[]> {
227226
const typeRenderedName = context.typeChecker.typeToString(tsType);
228227

229228
if (recursionDepth > MAX_TYPE_DERIVATION_RECURSION)
230229
throw new Error(`Schema inference validation exceeded depth ${MAX_TYPE_DERIVATION_RECURSION} for type ${typeRenderedName}`)
231230

232231
if (unwrapPromiseType(tsType, context.typeChecker) !== undefined) {
233-
return { errors: [`Promise types are not supported, but one was encountered in ${typePathToString(typePath)}.`] };
232+
return new Err([`Promise types are not supported, but one was encountered in ${typePathToString(typePath)}.`]);
234233
}
235234

236235
if (tsutils.isObjectType(tsType) && tsutils.isObjectFlagSet(tsType, ts.ObjectFlags.Class)) {
237-
return { errors: [`Class types are not supported, but one was encountered in ${typePathToString(typePath)}`] }
236+
return new Err([`Class types are not supported, but one was encountered in ${typePathToString(typePath)}`]);
238237
}
239238

240239
if (tsutils.isIntrinsicVoidType(tsType)) {
241-
return { errors: [`The void type is not supported, but one was encountered in ${typePathToString(typePath)}`] }
240+
return new Err([`The void type is not supported, but one was encountered in ${typePathToString(typePath)}`]);
242241
}
243242

244243
const schemaTypeResult =
@@ -253,79 +252,70 @@ function deriveSchemaTypeForTsType(tsType: ts.Type, typePath: TypePathSegment[],
253252
// We don't know how to deal with this type, so just make it an opaque scalar
254253
const typeName = generateTypeNameFromTypePath(typePath);
255254
context.scalarTypeDefinitions[typeName] = {};
256-
return {
255+
return new Ok({
257256
warnings: [`Unable to derive an NDC type for ${typePathToString(typePath)} (type: ${context.typeChecker.typeToString(tsType)}). Assuming that it is a scalar type.`],
258257
typeDefinition: { type: "named", kind: "scalar", name: typeName }
259-
};
258+
});
260259
}
261260

262-
function deriveSchemaTypeIfTsArrayType(tsType: ts.Type, typePath: TypePathSegment[], context: TypeDerivationContext, recursionDepth: number): DeriveSchemaTypeResult | undefined {
261+
function deriveSchemaTypeIfTsArrayType(tsType: ts.Type, typePath: TypePathSegment[], context: TypeDerivationContext, recursionDepth: number): Result<DerivedSchemaType, string[]> | undefined {
263262
if (context.typeChecker.isArrayType(tsType) && tsutils.isTypeReference(tsType)) {
264263
const typeArgs = context.typeChecker.getTypeArguments(tsType)
265264
if (typeArgs.length === 1) {
266265
const innerType = typeArgs[0]!;
267-
const innerTypeResult = deriveSchemaTypeForTsType(innerType, [...typePath, {segmentType: "Array"}], context, recursionDepth + 1);
268-
return "errors" in innerTypeResult
269-
? innerTypeResult
270-
: { typeDefinition: { type: "array", elementType: innerTypeResult.typeDefinition }, warnings: innerTypeResult.warnings };
266+
return deriveSchemaTypeForTsType(innerType, [...typePath, {segmentType: "Array"}], context, recursionDepth + 1)
267+
.map(innerTypeResult => ({ typeDefinition: { type: "array", elementType: innerTypeResult.typeDefinition }, warnings: innerTypeResult.warnings }));
271268
}
272269
}
273270
}
274271

275-
function deriveSchemaTypeIfScalarType(tsType: ts.Type, context: TypeDerivationContext): DeriveSchemaTypeResult | undefined {
272+
function deriveSchemaTypeIfScalarType(tsType: ts.Type, context: TypeDerivationContext): Result<DerivedSchemaType, string[]> | undefined {
276273
if (tsutils.isIntrinsicBooleanType(tsType)) {
277274
context.scalarTypeDefinitions["Boolean"] = {};
278-
return { typeDefinition: { type: "named", kind: "scalar", name: "Boolean" }, warnings: [] };
275+
return new Ok({ typeDefinition: { type: "named", kind: "scalar", name: "Boolean" }, warnings: [] });
279276
}
280277
if (tsutils.isIntrinsicStringType(tsType)) {
281278
context.scalarTypeDefinitions["String"] = {};
282-
return { typeDefinition: { type: "named", kind: "scalar", name: "String" }, warnings: [] };
279+
return new Ok({ typeDefinition: { type: "named", kind: "scalar", name: "String" }, warnings: [] });
283280
}
284281
if (tsutils.isIntrinsicNumberType(tsType)) {
285282
context.scalarTypeDefinitions["Float"] = {};
286-
return { typeDefinition: { type: "named", kind: "scalar", name: "Float" }, warnings: [] };
283+
return new Ok({ typeDefinition: { type: "named", kind: "scalar", name: "Float" }, warnings: [] });
287284
}
288285
}
289286

290-
function deriveSchemaTypeIfNullableType(tsType: ts.Type, typePath: TypePathSegment[], context: TypeDerivationContext, recursionDepth: number): DeriveSchemaTypeResult | undefined {
287+
function deriveSchemaTypeIfNullableType(tsType: ts.Type, typePath: TypePathSegment[], context: TypeDerivationContext, recursionDepth: number): Result<DerivedSchemaType, string[]> | undefined {
291288
const notNullableResult = unwrapNullableType(tsType);
292289
if (notNullableResult !== null) {
293290
const [notNullableType, nullOrUndefinability] = notNullableResult;
294-
const notNullableTypeResult = deriveSchemaTypeForTsType(notNullableType, typePath, context, recursionDepth + 1);
295-
if ("errors" in notNullableTypeResult) return notNullableTypeResult;
296-
return { typeDefinition: { type: "nullable", underlyingType: notNullableTypeResult.typeDefinition, nullOrUndefinability }, warnings: notNullableTypeResult.warnings }
291+
return deriveSchemaTypeForTsType(notNullableType, typePath, context, recursionDepth + 1)
292+
.map(notNullableTypeResult => ({ typeDefinition: { type: "nullable", underlyingType: notNullableTypeResult.typeDefinition, nullOrUndefinability }, warnings: notNullableTypeResult.warnings }))
297293
}
298294
}
299295

300-
function deriveSchemaTypeIfObjectType(tsType: ts.Type, typePath: TypePathSegment[], context: TypeDerivationContext, recursionDepth: number): DeriveSchemaTypeResult | undefined {
296+
function deriveSchemaTypeIfObjectType(tsType: ts.Type, typePath: TypePathSegment[], context: TypeDerivationContext, recursionDepth: number): Result<DerivedSchemaType, string[]> | undefined {
301297
const info = getObjectTypeInfo(tsType, typePath, context.typeChecker, context.functionsFilePath);
302298
if (info) {
303299
// Shortcut recursion if the type has already been named
304300
if (context.objectTypeDefinitions[info.generatedTypeName]) {
305-
return { typeDefinition: { type: 'named', name: info.generatedTypeName, kind: "object" }, warnings: [] };
301+
return new Ok({ typeDefinition: { type: 'named', name: info.generatedTypeName, kind: "object" }, warnings: [] });
306302
}
307303

308304
context.objectTypeDefinitions[info.generatedTypeName] = { properties: [] }; // Break infinite recursion
309305

310-
const errors: string[] = [];
311306
const warnings: string[] = [];
312-
const properties = Array.from(info.members).flatMap(([propertyName, propertyType]) => {
313-
const propertyTypeResult = deriveSchemaTypeForTsType(propertyType, [...typePath, { segmentType: "ObjectProperty", typeName: info.generatedTypeName, propertyName }], context, recursionDepth + 1);
314-
if ("errors" in propertyTypeResult) {
315-
errors.push(...propertyTypeResult.errors)
316-
return [];
317-
} else {
318-
warnings.push(...propertyTypeResult.warnings)
319-
return [{ propertyName: propertyName, type: propertyTypeResult.typeDefinition }]
320-
}
307+
const propertyResults = Result.traverseAndCollectErrors(Array.from(info.members), ([propertyName, propertyType]) => {
308+
return deriveSchemaTypeForTsType(propertyType, [...typePath, { segmentType: "ObjectProperty", typeName: info.generatedTypeName, propertyName }], context, recursionDepth + 1)
309+
.map(propertyTypeResult => {
310+
warnings.push(...propertyTypeResult.warnings)
311+
return { propertyName: propertyName, type: propertyTypeResult.typeDefinition }
312+
});
321313
});
322314

323-
if (errors.length === 0) {
315+
return propertyResults.map(properties => {
324316
context.objectTypeDefinitions[info.generatedTypeName] = { properties }
325317
return { typeDefinition: { type: 'named', name: info.generatedTypeName, kind: "object" }, warnings }
326-
} else {
327-
return { errors };
328-
}
318+
})
329319
}
330320
}
331321

ndc-lambda-sdk/src/result.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ export class Err<T, TError> extends ResultBase<T, TError> {
5050

5151
export type Result<T, TError> = Ok<T, TError> | Err<T, TError>
5252

53-
function traverseAndCollectErrors<T, TErr>(results: Result<T, TErr[]>[]): Result<T[], TErr[]> {
53+
function traverseAndCollectErrors<T1, T2, TErr>(inputs: T1[], fn: (input: T1) => Result<T2, TErr[]>): Result<T2[], TErr[]> {
54+
return sequenceAndCollectErrors(inputs.map(fn))
55+
}
56+
57+
function sequenceAndCollectErrors<T, TErr>(results: Result<T, TErr[]>[]): Result<T[], TErr[]> {
5458
const data: T[] = [];
5559
const errors: TErr[] = [];
5660

@@ -109,6 +113,7 @@ function collectErrors3<T1, T2, T3, TErr>(result1: Result<T1, TErr[]>, result2:
109113

110114
export const Result = {
111115
traverseAndCollectErrors,
116+
sequenceAndCollectErrors,
112117
partitionAndCollectErrors,
113118
collectErrors,
114119
collectErrors3,

0 commit comments

Comments
 (0)