Skip to content

Commit ec7db8e

Browse files
committed
f
1 parent 37f59a9 commit ec7db8e

File tree

3 files changed

+742
-0
lines changed

3 files changed

+742
-0
lines changed

plan.md

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
# Plan: Add Human-Readable Error Formatting for Relationship Validation
2+
3+
## Overview
4+
Create error formatting that groups errors by context (relationship/table/schema) and lists specific issues beneath each context. Format: **Context header****List of specific errors**.
5+
6+
---
7+
8+
## Message Format Structure
9+
10+
### Grouping Principle
11+
- **Header**: Location and object type (`Relationship 'schema.table.relationship':`)
12+
- **Body**: Bulleted list of specific errors indented under header
13+
- **No Repetition**: Context stated once, errors listed below
14+
15+
### Example Output
16+
17+
```
18+
Relationship 'public.posts.user':
19+
- Type mismatch: Column type 'BIGINT' incompatible with referenced column 'public.users.id' (INTEGER)
20+
- Missing column: Referenced column 'public.users.tenant_id' does not exist
21+
22+
Relationship 'public.comments.post':
23+
- Missing table: Referenced table 'public.posts' does not exist
24+
25+
Relationship 'public.posts.composite_fk':
26+
- Length mismatch: 2 column(s) [user_id, tenant_id] mapped to 1 reference(s) [public.users.id]
27+
```
28+
29+
---
30+
31+
## Technical Design
32+
33+
### File Structure
34+
**File:** `src/packages/dumbo/src/core/schema/components/relationships/formatRelationshipErrors.ts` (new)
35+
36+
### Implementation Approach
37+
38+
```typescript
39+
// Helper: Join array elements
40+
type Join<T extends readonly string[], Sep extends string> =
41+
T extends readonly [infer First extends string, ...infer Rest extends readonly string[]]
42+
? Rest extends readonly []
43+
? First
44+
: `${First}${Sep}${Join<Rest, Sep>}`
45+
: '';
46+
47+
// Helper: Indent lines with bullet points
48+
type IndentErrors<Messages extends readonly string[]> =
49+
Messages extends readonly [infer First extends string, ...infer Rest extends readonly string[]]
50+
? [` - ${First}`, ...IndentErrors<Rest>]
51+
: [];
52+
53+
// Format single error message (no path, just the issue)
54+
type FormatSingleError<Error> =
55+
Error extends {
56+
errorCode: 'reference_length_mismatch';
57+
columns: infer Cols extends readonly string[];
58+
references: infer Refs extends readonly string[];
59+
}
60+
? `Length mismatch: ${Cols['length']} column(s) [${Join<Cols, ', '>}] mapped to ${Refs['length']} reference(s) [${Join<Refs, ', '>}]`
61+
62+
: Error extends {
63+
errorCode: 'reference_columns_mismatch';
64+
invalidColumns: infer Invalid extends readonly string[];
65+
availableColumns: infer Available extends readonly string[];
66+
}
67+
? `Invalid columns: [${Join<Invalid, ', '>}] not found in table. Available: [${Join<Available, ', '>}]`
68+
69+
: Error extends {
70+
errorCode: 'missing_schema';
71+
reference: infer Ref extends string;
72+
}
73+
? `Missing schema: Referenced schema in '${Ref}' does not exist`
74+
75+
: Error extends {
76+
errorCode: 'missing_table';
77+
reference: infer Ref extends string;
78+
}
79+
? `Missing table: Referenced table '${Ref}' does not exist`
80+
81+
: Error extends {
82+
errorCode: 'missing_column';
83+
reference: infer Ref extends string;
84+
}
85+
? `Missing column: Referenced column '${Ref}' does not exist`
86+
87+
: Error extends {
88+
errorCode: 'type_mismatch';
89+
reference: infer Ref extends string;
90+
referenceType: infer RefType extends string;
91+
columnTypeName: infer ColType extends string;
92+
}
93+
? `Type mismatch: Column type '${ColType}' incompatible with referenced column '${Ref}' (${RefType})`
94+
95+
: 'Unknown validation error';
96+
97+
// Format array of errors as messages
98+
type FormatErrorMessages<Errors extends readonly unknown[]> =
99+
Errors extends readonly [infer First, ...infer Rest]
100+
? [FormatSingleError<First>, ...FormatErrorMessages<Rest>]
101+
: [];
102+
103+
// Format relationship block: header + indented errors
104+
type FormatRelationshipBlock<
105+
Path extends string,
106+
Errors extends readonly unknown[]
107+
> = [
108+
`Relationship '${Path}':`,
109+
...IndentErrors<FormatErrorMessages<Errors>>
110+
];
111+
112+
// Traverse table level and format each relationship
113+
type FormatTableLevel<
114+
Errors,
115+
SchemaName extends string,
116+
TableName extends string
117+
> = Errors extends readonly [infer First, ...infer Rest]
118+
? [
119+
...FormatTableLevel<First, SchemaName, TableName>,
120+
...FormatTableLevel<Rest, SchemaName, TableName>
121+
]
122+
: Errors extends {
123+
relationship: infer RelName extends string;
124+
errors: infer RelErrors extends readonly unknown[];
125+
}
126+
? [...FormatRelationshipBlock<`${SchemaName}.${TableName}.${RelName}`, RelErrors>]
127+
: [];
128+
129+
// Traverse schema level
130+
type FormatSchemaLevel<Errors, SchemaName extends string> =
131+
Errors extends readonly [infer First, ...infer Rest]
132+
? [
133+
...FormatSchemaLevel<First, SchemaName>,
134+
...FormatSchemaLevel<Rest, SchemaName>
135+
]
136+
: Errors extends {
137+
table: infer TableName extends string;
138+
errors: infer TableErrors;
139+
}
140+
? FormatTableLevel<TableErrors, SchemaName, TableName>
141+
: [];
142+
143+
// Traverse database level
144+
export type FormatDatabaseValidationErrors<Errors> =
145+
Errors extends readonly [infer First, ...infer Rest]
146+
? [
147+
...FormatDatabaseValidationErrors<First>,
148+
...FormatDatabaseValidationErrors<Rest>
149+
]
150+
: Errors extends {
151+
schema: infer SchemaName extends string;
152+
errors: infer SchemaErrors;
153+
}
154+
? FormatSchemaLevel<SchemaErrors, SchemaName>
155+
: [];
156+
157+
// Main entry point
158+
export type FormatValidationErrors<Result> =
159+
Result extends TypeValidationResult<false, infer Errors>
160+
? FormatDatabaseValidationErrors<Errors>
161+
: [];
162+
```
163+
164+
---
165+
166+
## Integration Point
167+
168+
### Database Level
169+
**File:** `src/packages/dumbo/src/core/schema/components/relationships/relationshipValidation.ts` (addition at end)
170+
171+
```typescript
172+
import type { FormatValidationErrors } from './formatRelationshipErrors';
173+
174+
// Export combined validation + messages
175+
export type ValidateDatabaseSchemasWithMessages<Schemas extends DatabaseSchemas> =
176+
ValidateDatabaseSchemas<Schemas> extends infer Result
177+
? Result extends TypeValidationResult<false, any>
178+
? {
179+
validation: Result;
180+
messages: FormatValidationErrors<Result>;
181+
}
182+
: { validation: TypeValidationResult<true>; messages: [] }
183+
: never;
184+
```
185+
186+
---
187+
188+
## Implementation Steps (TDD)
189+
190+
### Step 1: Helper Utilities
191+
**Test:** `formatRelationshipErrors.type.spec.ts`
192+
**Implement:** `formatRelationshipErrors.ts`
193+
194+
1. Test `Join` with various arrays
195+
2. Test `IndentErrors` with bullet formatting
196+
3. Build: `npm run build:ts`
197+
198+
### Step 2: Single Error Formatting
199+
1. Test each of 6 error codes:
200+
- `reference_length_mismatch`
201+
- `reference_columns_mismatch`
202+
- `missing_schema`
203+
- `missing_table`
204+
- `missing_column`
205+
- `type_mismatch`
206+
2. Implement `FormatSingleError`
207+
3. Build: `npm run build:ts`
208+
209+
### Step 3: Relationship Block Formatting
210+
1. Test relationship header + multiple indented errors
211+
2. Implement `FormatRelationshipBlock`
212+
3. Test `FormatErrorMessages` array transformation
213+
4. Build: `npm run build:ts`
214+
215+
### Step 4: Table Level Traversal
216+
1. Test table with multiple relationships
217+
2. Implement `FormatTableLevel`
218+
3. Verify multiple relationship blocks generated
219+
4. Build: `npm run build:ts`
220+
221+
### Step 5: Schema Level Traversal
222+
1. Test schema with multiple tables
223+
2. Implement `FormatSchemaLevel`
224+
3. Verify complete output with all tables
225+
4. Build: `npm run build:ts`
226+
227+
### Step 6: Database Level Traversal
228+
1. Test multiple schemas
229+
2. Implement `FormatDatabaseValidationErrors`
230+
3. Verify all errors grouped correctly
231+
4. Build: `npm run build:ts`
232+
233+
### Step 7: Main Entry Point
234+
1. Test `FormatValidationErrors` with real validation results
235+
2. Integrate with `TypeValidationResult`
236+
3. Build: `npm run build:ts`
237+
238+
### Step 8: Export and Integration
239+
1. Add `ValidateDatabaseSchemasWithMessages` export
240+
2. Export from main index
241+
3. Build: `npm run build:ts`
242+
4. Lint: `npm run fix`
243+
244+
---
245+
246+
## Example Outputs
247+
248+
### Single Relationship, Single Error
249+
```
250+
Relationship 'public.posts.user':
251+
- Type mismatch: Column type 'BIGINT' incompatible with referenced column 'public.users.id' (INTEGER)
252+
```
253+
254+
### Single Relationship, Multiple Errors
255+
```
256+
Relationship 'public.posts.user':
257+
- Type mismatch: Column type 'BIGINT' incompatible with referenced column 'public.users.id' (INTEGER)
258+
- Missing column: Referenced column 'public.users.tenant_id' does not exist
259+
- Length mismatch: 2 column(s) [user_id, tenant_id] mapped to 1 reference(s) [public.users.id]
260+
```
261+
262+
### Multiple Relationships
263+
```
264+
Relationship 'public.posts.user':
265+
- Type mismatch: Column type 'BIGINT' incompatible with referenced column 'public.users.id' (INTEGER)
266+
267+
Relationship 'public.posts.tenant':
268+
- Missing table: Referenced table 'public.tenants' does not exist
269+
270+
Relationship 'public.comments.post':
271+
- Invalid columns: [post_uuid] not found in table. Available: [id, post_id, user_id, content]
272+
```
273+
274+
### Multiple Schemas
275+
```
276+
Relationship 'public.posts.user':
277+
- Type mismatch: Column type 'BIGINT' incompatible with referenced column 'public.users.id' (INTEGER)
278+
279+
Relationship 'audit.logs.user':
280+
- Missing schema: Referenced schema in 'public.users' does not exist
281+
282+
Relationship 'reporting.stats.post':
283+
- Length mismatch: 3 column(s) [post_id, schema_id, table_id] mapped to 1 reference(s) [public.posts.id]
284+
```
285+
286+
---
287+
288+
## Key Principles
289+
290+
1. **Context Once**: State relationship path once as header
291+
2. **Errors Listed**: Bullet points under header for each issue
292+
3. **Blank Lines**: Separate relationship blocks for readability
293+
4. **Complete Info**: Include all error details (types, lists, counts)
294+
5. **Scannable**: Easy to read in IDE hover or CLI
295+
6. **Actionable**: Clear where to look (header) and what to fix (bullets)
296+
297+
---
298+
299+
## Benefits
300+
301+
- **No Repetition**: Context not repeated per error
302+
- **Grouped**: Related errors together under relationship
303+
- **IDE Friendly**: Formatted for tooltips and diagnostics
304+
- **CLI Friendly**: Readable in terminal output
305+
- **Maintainable**: Format changes don't affect validation
306+
- **Extensible**: Can add table/schema level formatting later

0 commit comments

Comments
 (0)