Skip to content

Commit

Permalink
Dependency feedback (#13)
Browse files Browse the repository at this point in the history
* improve error messages

* improve missing dependency messages

* fix error validation type distribution
  • Loading branch information
teunmooij authored Jun 23, 2024
1 parent 9737e5f commit bbe8bbd
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 28 deletions.
6 changes: 3 additions & 3 deletions src/types/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ type ValidateMappingDependency<
? [DependencyValidationError<string, unknown, unknown>] // Dependency not created as constant
: [PropAt<TDependencies, DependencyDestinationOf<TMapping>>] extends [never]
? [] // Unexpected dependency
: Injected<TSystemic, TCurrent, TMapping> | undefined extends
| PropAt<TDependencies, DependencyDestinationOf<TMapping>>
| undefined
: [Injected<TSystemic, TCurrent, TMapping>] extends [
PropAt<TDependencies, DependencyDestinationOf<TMapping>>,
]
? [] // Correct dependency
: [
DependencyValidationError<
Expand Down
30 changes: 16 additions & 14 deletions src/types/systemic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,39 +150,41 @@ export type DependsOn<
infer First extends DependencyValidationError<any, any, any>,
...any[],
]
? SystemicWithInvalidDependency<First>
? SystemicWithInvalidDependency<TCurrent, First>
: SystemicBuild<
TSystemic,
TCurrent,
StripEmptyObjectsRecursively<DeleteProps<TDependencies, DependencyDestinationsOf<TNames>>>
>;
};

export type IncompleteSystemic<TMissing> = {
export type IncompleteSystemic<TCurrent extends string, TMissing> = {
[X in keyof Systemic<any>]: (
error: `Please add missing dependencies`,
error: `Please add missing dependencies for component "${TCurrent}"`,
expected: StripEmptyObjectsRecursively<DeepRequiredOnly<TMissing>>,
) => void;
};

export type SystemicWithInvalidDependency<TError extends DependencyValidationError<any, any, any>> =
{
[X in keyof Systemic<any>]: (
error: string extends TError[0]
? "Destination of a dependency is unknown. Did you neglect to mark it 'as const'?"
: `Dependency "${TError[0]}" is not of the required type`,
expected: TError[1],
actual: TError[2],
) => void;
};
export type SystemicWithInvalidDependency<
TCurrent extends string,
TError extends DependencyValidationError<any, any, any>,
> = {
[X in keyof Systemic<any>]: (
error: string extends TError[0]
? `Destination of a dependency for component "${TCurrent}" is unknown. Did you neglect to mark it 'as const'?`
: `Dependency "${TError[0]}" on component "${TCurrent}" is not of the required type`,
expected: TError[1],
actual: TError[2],
) => void;
};

export type SystemicBuild<
TSystemic extends Record<string, Registration<unknown, boolean>>,
TCurrent extends keyof TSystemic & string,
TDependencies extends Record<string, unknown>,
> = [RequiredKeys<TDependencies>] extends [never]
? Systemic<TSystemic> & DependsOn<TSystemic, TCurrent, TDependencies>
: DependsOn<TSystemic, TCurrent, TDependencies> & IncompleteSystemic<TDependencies>;
: DependsOn<TSystemic, TCurrent, TDependencies> & IncompleteSystemic<TCurrent, TDependencies>;

type SystemicBuildDefaultComponent<
TSystemic extends Record<string, Registration<unknown, boolean>>,
Expand Down
5 changes: 3 additions & 2 deletions test/types/dependencies.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,12 @@ describe("dependencies types", () => {
it("validates an invalid simple dependency", () => {
type Systemic = {
foo: { component: { foo: string }; scoped: false };
bar: { component: any; scoped: false };
};

type Dependencies = { foo: { bar: number } };
type Dependencies = { foo: { bar: number }; bar: string };

type Given = ["foo"];
type Given = ["foo", "bar"];

type Result = ValidateDependencies<Systemic, "foo", Dependencies, Given>;

Expand Down
24 changes: 15 additions & 9 deletions test/types/systemic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ describe("systemic types", () => {
foo: { component: { foo: string }; scoped: false };
bar: { component: number; scoped: false };
};
type Expected = IncompleteSystemic<{ foo: { foo: string } }> &
type Expected = IncompleteSystemic<"bar", { foo: { foo: string } }> &
DependsOn<Registrations, "bar", { foo: { foo: string } }>;

expectTypes<typeof system, Expected>().toBeEqual();
Expand All @@ -144,7 +144,7 @@ describe("systemic types", () => {
foo: { component: { foo: string; bar: { baz: string } }; scoped: true };
bar: { component: number; scoped: false };
};
type Expected = IncompleteSystemic<{ foo: { baz: string } }> &
type Expected = IncompleteSystemic<"bar", { foo: { baz: string } }> &
DependsOn<Registrations, "bar", { foo: { baz: string } }>;

expectTypes<typeof system, Expected>().toBeEqual();
Expand All @@ -156,13 +156,13 @@ describe("systemic types", () => {
.add("bar", { start: async (deps: { foo: { foo: number } }) => 42 })
.dependsOn("foo");

type Expected = SystemicWithInvalidDependency<["foo", { foo: number }, { foo: string }]>;
type Expected = SystemicWithInvalidDependency<"bar", ["foo", { foo: number }, { foo: string }]>;

expectTypes<typeof system, Expected>().toBeEqual();
expectTypes<
(typeof system)["start"],
(
error: 'Dependency "foo" is not of the required type',
error: 'Dependency "foo" on component "bar" is not of the required type',
expected: { foo: number },
actual: { foo: string },
) => void
Expand Down Expand Up @@ -215,7 +215,10 @@ describe("systemic types", () => {
baz: { component: { foo: { bar: { qux: number } } }; scoped: true };
"foo.bar": { component: number; scoped: false };
};
type Expected = SystemicWithInvalidDependency<["baz", { qux: string }, { qux: number }]>;
type Expected = SystemicWithInvalidDependency<
"foo.bar",
["baz", { qux: string }, { qux: number }]
>;

expectTypes<typeof system, Expected>().toBeEqual();
});
Expand Down Expand Up @@ -247,7 +250,7 @@ describe("systemic types", () => {
"foo.bar": { component: { baz: number }; scoped: false };
qux: { component: number; scoped: false };
};
type Expected = IncompleteSystemic<{ foo: { baz: { quux: number } } }> &
type Expected = IncompleteSystemic<"qux", { foo: { baz: { quux: number } } }> &
DependsOn<Registrations, "qux", { foo: { baz: { quux: number } } }>;

expectTypes<typeof system, Expected>().toBeEqual();
Expand All @@ -263,7 +266,10 @@ describe("systemic types", () => {
"foo.bar": { component: { baz: number }; scoped: false };
qux: { component: number; scoped: false };
};
type Expected = SystemicWithInvalidDependency<["foo.bar", { baz: string }, { baz: number }]>;
type Expected = SystemicWithInvalidDependency<
"qux",
["foo.bar", { baz: string }, { baz: number }]
>;

expectTypes<typeof system, Expected>().toBeEqual();
});
Expand Down Expand Up @@ -306,13 +312,13 @@ describe("systemic types", () => {
.add("bar", { start: async (deps: { baz: { bar: string } }) => 42 })
.dependsOn({ component: "foo", destination: "baz" });

type Expected = SystemicWithInvalidDependency<[string, unknown, unknown]>;
type Expected = SystemicWithInvalidDependency<"bar", [string, unknown, unknown]>;

expectTypes<typeof system, Expected>().toBeEqual();
expectTypes<
(typeof system)["start"],
(
error: "Destination of a dependency is unknown. Did you neglect to mark it 'as const'?",
error: `Destination of a dependency for component "bar" is unknown. Did you neglect to mark it 'as const'?`,
expected: unknown,
actual: unknown,
) => void
Expand Down

0 comments on commit bbe8bbd

Please sign in to comment.