From bbe8bbd387563ab3045b5aedff196b86fe2cc01f Mon Sep 17 00:00:00 2001
From: Teun Mooij <tgmooij@hotmail.com>
Date: Sun, 23 Jun 2024 22:18:46 +0200
Subject: [PATCH] Dependency feedback (#13)

* improve error messages

* improve missing dependency messages

* fix error validation type distribution
---
 src/types/dependencies.ts       |  6 +++---
 src/types/systemic.ts           | 30 ++++++++++++++++--------------
 test/types/dependencies.spec.ts |  5 +++--
 test/types/systemic.spec.ts     | 24 +++++++++++++++---------
 4 files changed, 37 insertions(+), 28 deletions(-)

diff --git a/src/types/dependencies.ts b/src/types/dependencies.ts
index c32be94..2a39185 100644
--- a/src/types/dependencies.ts
+++ b/src/types/dependencies.ts
@@ -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<
diff --git a/src/types/systemic.ts b/src/types/systemic.ts
index 561b4b5..31ea449 100644
--- a/src/types/systemic.ts
+++ b/src/types/systemic.ts
@@ -150,7 +150,7 @@ export type DependsOn<
     infer First extends DependencyValidationError<any, any, any>,
     ...any[],
   ]
-    ? SystemicWithInvalidDependency<First>
+    ? SystemicWithInvalidDependency<TCurrent, First>
     : SystemicBuild<
         TSystemic,
         TCurrent,
@@ -158,23 +158,25 @@ export type DependsOn<
       >;
 };
 
-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>>,
@@ -182,7 +184,7 @@ export type SystemicBuild<
   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>>,
diff --git a/test/types/dependencies.spec.ts b/test/types/dependencies.spec.ts
index aa0e019..c627708 100644
--- a/test/types/dependencies.spec.ts
+++ b/test/types/dependencies.spec.ts
@@ -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>;
 
diff --git a/test/types/systemic.spec.ts b/test/types/systemic.spec.ts
index 353cedd..bbe36fa 100644
--- a/test/types/systemic.spec.ts
+++ b/test/types/systemic.spec.ts
@@ -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();
@@ -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();
@@ -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
@@ -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();
   });
@@ -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();
@@ -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();
   });
@@ -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