Skip to content

Commit

Permalink
feat(store, world): throw if required keys are not provided, more val…
Browse files Browse the repository at this point in the history
…idations (#2481)
  • Loading branch information
alvrs authored Mar 20, 2024
1 parent 07e12ea commit cc4f424
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 16 deletions.
2 changes: 1 addition & 1 deletion packages/store/ts/config/v2/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export type TablesInput = {

export type StoreInput = {
readonly namespace?: string;
readonly tables: TablesInput;
readonly tables?: TablesInput;
readonly userTypes?: UserTypes;
readonly enums?: Enums;
readonly codegen?: Partial<Codegen>;
Expand Down
12 changes: 7 additions & 5 deletions packages/store/ts/config/v2/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { hasOwnKey, isObject } from "./generics";
import { SchemaInput } from "./input";
import { FixedArrayAbiType, fixedArrayToArray, isFixedArrayAbiType } from "@latticexyz/schema-type/internal";

export type validateSchema<schema, scope extends Scope = AbiTypeScope> = {
[key in keyof schema]: schema[key] extends FixedArrayAbiType
? schema[key]
: conform<schema[key], keyof scope["types"]>;
};
export type validateSchema<schema, scope extends Scope = AbiTypeScope> = schema extends string
? SchemaInput
: {
[key in keyof schema]: schema[key] extends FixedArrayAbiType
? schema[key]
: conform<schema[key], keyof scope["types"]>;
};

export function validateSchema<scope extends Scope = AbiTypeScope>(
schema: unknown,
Expand Down
50 changes: 50 additions & 0 deletions packages/store/ts/config/v2/table.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,56 @@ describe("resolveTable", () => {
.throws('Invalid key. Expected `("id" | "age")[]`, received `["NotAKey"]`')
.type.errors(`Type '"NotAKey"' is not assignable to type '"id" | "age"'`);
});

it("should throw if no key is provided", () => {
attest(() =>
// @ts-expect-error Property 'key' is missing in type
defineTable({
schema: { id: "address" },
name: "",
}),
)
.throws('Invalid key. Expected `("id")[]`, received `undefined')
.type.errors("Property 'key' is missing in type");
});

it("should throw if a string is provided as key", () => {
attest(() =>
defineTable({
schema: { id: "address" },
// @ts-expect-error Type 'string' is not assignable to type 'string[]'
key: "",
name: "",
}),
)
.throws('Invalid key. Expected `("id")[]`, received ``')
.type.errors("Type 'string' is not assignable to type 'string[]'");
});

it("should throw if a string is provided as schema", () => {
attest(() =>
defineTable({
// @ts-expect-error Type 'string' is not assignable to type 'SchemaInput'.
schema: "uint256",
key: [],
name: "",
}),
)
.throws('Error: Expected schema, received "uint256"')
.type.errors("Type 'string' is not assignable to type 'SchemaInput'.");
});

it("should throw if an unknown key is provided", () => {
attest(() =>
defineTable({
schema: { id: "address" },
key: ["id"],
name: "",
// @ts-expect-error Key `keySchema` does not exist in TableInput
keySchema: { id: "address" },
}),
).type.errors("Key `keySchema` does not exist in TableInput ");
});
});

// TODO: move tests to protocol parser after we add arktype
Expand Down
27 changes: 18 additions & 9 deletions packages/store/ts/config/v2/table.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ErrorMessage, conform, narrow } from "@arktype/util";
import { ErrorMessage, conform, narrow, requiredKeyOf } from "@arktype/util";
import { isStaticAbiType } from "@latticexyz/schema-type/internal";
import { Hex } from "viem";
import { get, hasOwnKey } from "./generics";
Expand Down Expand Up @@ -37,9 +37,11 @@ export function isValidPrimaryKey<schema extends SchemaInput, scope extends Scop
);
}

export type validateKeys<validKeys extends PropertyKey, keys> = {
[i in keyof keys]: keys[i] extends validKeys ? keys[i] : validKeys;
};
export type validateKeys<validKeys extends PropertyKey, keys> = keys extends string[]
? {
[i in keyof keys]: keys[i] extends validKeys ? keys[i] : validKeys;
}
: string[];

export type ValidateTableOptions = { inStoreContext: boolean };

Expand All @@ -48,14 +50,21 @@ export type validateTable<
scope extends Scope = AbiTypeScope,
options extends ValidateTableOptions = { inStoreContext: false },
> = {
[key in keyof input]: key extends "key"
? validateKeys<getStaticAbiTypeKeys<conform<get<input, "schema">, SchemaInput>, scope>, input[key]>
[key in
| keyof input
| Exclude<
requiredKeyOf<TableInput>,
options["inStoreContext"] extends true ? "name" | "namespace" : ""
>]: key extends "key"
? validateKeys<getStaticAbiTypeKeys<conform<get<input, "schema">, SchemaInput>, scope>, get<input, key>>
: key extends "schema"
? validateSchema<input[key], scope>
? validateSchema<get<input, key>, scope>
: key extends "name" | "namespace"
? options["inStoreContext"] extends true
? ErrorMessage<"Overrides of `name` and `namespace` are not allowed for tables in a store config">
: narrow<input[key]>
: key extends keyof input
? narrow<input[key]>
: never
: key extends keyof TableInput
? TableInput[key]
: ErrorMessage<`Key \`${key & string}\` does not exist in TableInput`>;
Expand All @@ -82,7 +91,7 @@ export function validateTable<input, scope extends Scope = AbiTypeScope>(
.join(" | ")})[]\`, received \`${
hasOwnKey(input, "key") && Array.isArray(input.key)
? `[${input.key.map((item) => `"${item}"`).join(", ")}]`
: "undefined"
: String(get(input, "key"))
}\``,
);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/world/ts/config/v2/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function resolveWorld<const world extends WorldInput>(world: world): reso
const resolvedNamespacedTables = Object.fromEntries(
Object.entries(namespaces)
.map(([namespaceKey, namespace]) =>
Object.entries(namespace.tables).map(([tableKey, table]) => {
Object.entries(namespace.tables ?? {}).map(([tableKey, table]) => {
validateTable(table, scope);
return [
`${namespaceKey}__${tableKey}`,
Expand Down

0 comments on commit cc4f424

Please sign in to comment.