Skip to content

Commit

Permalink
Enabled noUncheckedIndexedAccess
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-chambers committed Nov 22, 2023
1 parent f882748 commit 0845f7a
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 31 deletions.
5 changes: 3 additions & 2 deletions src/query.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Argument, BadRequest, Field, InternalServerError, NotSupported, ObjectType, QueryRequest, QueryResponse, Type } from "@hasura/ndc-sdk-typescript"
import { ByKeysFunction, ConnectorSchema, QueryFields, QueryFunction, RowFieldValue, schemaConstants } from "./schema-ndc";
import { ifNotNull, isArray, mapObjectValues, unreachable } from "./util";
import { ifNotNull, isArray, mapObjectValues, throwBadRequest, unreachable } from "./util";
import { AttributeValue, BatchGetItemCommand, DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoAttributeType, DynamoType, ObjectNamedType, ScalarNamedType, ScalarType, determineNamedTypeKind } from "./schema-dynamo";
import { fromBase64, toBase64 } from "@smithy/util-base64";
Expand Down Expand Up @@ -338,7 +338,8 @@ function mkNdcResponseRowFromDynamoResponseRow(dynamoResponseRow: Record<string,
case "column":
const attributeName = field.column;
const attributeValue = dynamoResponseRow[attributeName] ?? { NULL: true };
return resolveTypedValueFromAttributeValue(attributeValue, tableRowType.fields[attributeName].type, objectTypes);
const attributeSchemaType = tableRowType.fields[attributeName]?.type ?? throwBadRequest(`Attribute '${attributeName}' not declared in table schema`);
return resolveTypedValueFromAttributeValue(attributeValue, attributeSchemaType, objectTypes);
case "relationship":
throw new NotSupported("Relationship fields are not supported");
default:
Expand Down
57 changes: 29 additions & 28 deletions src/schema-ndc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ArgumentInfo, FunctionInfo, ObjectField, ObjectType, Query, SchemaRespo
import { TableSchema, ScalarType, dynamoTypeToType, AttributeSchema, DynamoType, dynamoArrayTypes, scalarTypeToDynamoType, ScalarNamedType, ObjectNamedType, determineNamedTypeKind } from "./schema-dynamo";
import { ConfigurationPath, ConfigurationRangeError, InvalidConfigurationError, ObjectTypes } from "./configuration";
import { Err, Ok, Result } from "./result";
import { unreachable } from "./util";
import { findWithIndex, throwInternalServerError, unreachable } from "./util";

export type ConnectorSchema = {
schemaResponse: SchemaResponse,
Expand Down Expand Up @@ -49,9 +49,10 @@ export function createSchema(tableSchema: TableSchema[], objectTypes: ObjectType
return createTableRowTypes(tableSchema, objectTypes)
.bind(([tableRowTypeNames, tableRowTypes]) =>
Result.traverseAndCollectErrors(
tableSchema.map((table, tableIndex) =>
createByKeysFunctionForTable(table, tableIndex, tableRowTypeNames[table.tableName], { ...objectTypes, ...tableRowTypes })
)
tableSchema.map((table, tableIndex) => {
const tableRowTypeName = tableRowTypeNames[table.tableName] ?? throwInternalServerError(`Table row type name not found: ${table.tableName}`);
return createByKeysFunctionForTable(table, tableIndex, tableRowTypeName, { ...objectTypes, ...tableRowTypes });
})
)
.bind(generatedFunctionDefinitions => {
const functionDefinitions = combineGenerated(...generatedFunctionDefinitions);
Expand Down Expand Up @@ -162,36 +163,36 @@ function createTablePkObjectType(tableSchema: TableSchema, tableSchemaIndex: num
: new Ok(tablePkObjectTypeName)
);

const hashAttrIndex = tableSchema.attributeSchema.findIndex(attr => attr.name === tableSchema.keySchema.hashKeyAttributeName)
const hashAttrField: Result<[{[k: string]: ObjectField}, KeySchema], ConfigurationRangeError[]> =
hashAttrIndex !== -1
? attributeSchemaAsObjectField(tableSchema.attributeSchema[hashAttrIndex], true, tableSchemaIndex, hashAttrIndex, objectTypes)
findWithIndex(tableSchema.attributeSchema, attr => attr.name === tableSchema.keySchema.hashKeyAttributeName)
.mapErr(_ => [{
path: ["tables", tableSchemaIndex, "keySchema", "hashKeyAttributeName"],
message: `Cannot find an attribute schema defined for the specified hash key attribute '${tableSchema.keySchema.hashKeyAttributeName}'`
}])
.bind(([hashAttrSchema, hashAttrIndex]) =>
attributeSchemaAsObjectField(hashAttrSchema, true, tableSchemaIndex, hashAttrIndex, objectTypes)
.map(([key, value]) => {
const hashAttrField = {[key]: value};
const hashKeySchema = { attributeName: tableSchema.attributeSchema[hashAttrIndex].name, schemaType: value.type, dynamoType: tableSchema.attributeSchema[hashAttrIndex].dynamoType };
const hashKeySchema = { attributeName: tableSchema.attributeSchema[hashAttrIndex]!.name, schemaType: value.type, dynamoType: tableSchema.attributeSchema[hashAttrIndex]!.dynamoType };
return [hashAttrField, hashKeySchema];
})
: new Err([{
path: ["tables", tableSchemaIndex, "keySchema", "hashKeyAttributeName"],
message: `Cannot find an attribute schema defined for the specified hash key attribute '${tableSchema.keySchema.hashKeyAttributeName}'`
}]);
);

const rangeAttrField: Result<[{[k: string]: ObjectField}, KeySchema | null], ConfigurationRangeError[]> =
tableSchema.keySchema.rangeKeyAttributeName === null
? new Ok([{}, null])
: new Ok<number, ConfigurationRangeError[]>(tableSchema.attributeSchema.findIndex(attr => attr.name === tableSchema.keySchema.rangeKeyAttributeName))
.bind(rangeAttrIndex =>
rangeAttrIndex !== -1
? attributeSchemaAsObjectField(tableSchema.attributeSchema[rangeAttrIndex], true, tableSchemaIndex, rangeAttrIndex, objectTypes)
.map(([key, value]) => {
const rangeAttrField = {[key]: value};
const rangeKeySchema = { attributeName: tableSchema.attributeSchema[rangeAttrIndex].name, schemaType: value.type, dynamoType: tableSchema.attributeSchema[rangeAttrIndex].dynamoType };
return [rangeAttrField, rangeKeySchema];
})
: new Err([{
path: ["tables", tableSchemaIndex, "keySchema", "rangeKeyAttributeName"],
message: `Cannot find an attribute schema defined for the specified range key attribute '${tableSchema.keySchema.rangeKeyAttributeName}'`
}])
: findWithIndex(tableSchema.attributeSchema, attr => attr.name === tableSchema.keySchema.rangeKeyAttributeName)
.mapErr(_ => [{
path: ["tables", tableSchemaIndex, "keySchema", "rangeKeyAttributeName"],
message: `Cannot find an attribute schema defined for the specified range key attribute '${tableSchema.keySchema.rangeKeyAttributeName}'`
}])
.bind(([rangeAttrSchema, rangeAttrIndex]) =>
attributeSchemaAsObjectField(rangeAttrSchema, true, tableSchemaIndex, rangeAttrIndex, objectTypes)
.map(([key, value]) => {
const rangeAttrField = {[key]: value};
const rangeKeySchema = { attributeName: rangeAttrSchema.name, schemaType: value.type, dynamoType: rangeAttrSchema.dynamoType };
return [rangeAttrField, rangeKeySchema];
})
);

return Result.collectErrors3(tablePkObjectTypeName, hashAttrField, rangeAttrField)
Expand Down Expand Up @@ -254,7 +255,7 @@ function createByKeysFunctionForTable(tableSchema: TableSchema, tableSchemaIndex
},
},
primaryKeySchema,
tableRowType: objectTypes[tableRowTypeName],
tableRowType: objectTypes[tableRowTypeName] ?? throwInternalServerError(`Expected table row ObjectType ${tableRowTypeName} not found`),
},
newObjectTypes: {
[tablePkObjectTypeName]: tablePkObjectType
Expand All @@ -275,7 +276,7 @@ function validateTypeUsages(functionInfos: FunctionInfo[], allObjectTypes: Objec
.flatMap<[string, ObjectType]>(usedType => {
const underlyingType = getUnderlyingNamedType(usedType, []);
return underlyingType.kind === "object"
? [[underlyingType.name, allObjectTypes[underlyingType.name]]]
? [[underlyingType.name, allObjectTypes[underlyingType.name] ?? throwInternalServerError<ObjectType>(`Could not find object type '${underlyingType.name}'`)]]
: []
});

Expand Down Expand Up @@ -304,7 +305,7 @@ function validateTypeUsages(functionInfos: FunctionInfo[], allObjectTypes: Objec
fieldTypeValidationResults.oks
.flatMap<[string, ObjectType]>(underlyingFieldType =>
underlyingFieldType.kind === "object"
? [[underlyingFieldType.name, allObjectTypes[underlyingFieldType.name]]]
? [[underlyingFieldType.name, (allObjectTypes[underlyingFieldType.name] ?? throwInternalServerError<ObjectType>(`Could not find object type '${underlyingFieldType.name}'`))]]
: []
);

Expand Down
20 changes: 20 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { BadRequest, InternalServerError } from "@hasura/ndc-sdk-typescript";
import { Err, Ok, Result } from "./result";

export const unreachable = (x: never): never => { throw new Error(`Unreachable code reached! The types lied! 😭 Unexpected value: ${x}`) };

export function isArray(x: unknown): x is unknown[] {
Expand All @@ -15,3 +18,20 @@ export function mapObjectValues<T, U>(obj: { [k: string]: T }, fn: (value: T, pr
export function mapObjectValues<T, U>(obj: { [k: string]: T }, fn: (value: T, propertyName: string) => U): Record<string, U> {
return Object.fromEntries(Object.entries(obj).map(([prop, val]) => [prop, fn(val, prop)]));
}

// Throws an internal server error. Useful for using after a short-circuiting ?? operator to eliminate null/undefined from the type
export function throwInternalServerError<T>(...args: ConstructorParameters<typeof InternalServerError>): NonNullable<T> {
throw new InternalServerError(...args);
}

// Throws an bad request error. Useful for using after a short-circuiting ?? operator to eliminate null/undefined from the type
export function throwBadRequest<T>(...args: ConstructorParameters<typeof BadRequest>): NonNullable<T> {
throw new BadRequest(...args);
}

export function findWithIndex<T>(array: T[], predicate: (value: T, index: number, obj: T[]) => boolean): Result<[T, number], undefined> {
const index = array.findIndex(predicate);
return index !== -1
? new Ok([array[index]!, index])
: new Err(undefined);
}
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
"resolveJsonModule": true
"resolveJsonModule": true,
"noUncheckedIndexedAccess": true
},
"include": [
"src/**/*.ts",
Expand Down

0 comments on commit 0845f7a

Please sign in to comment.