diff --git a/skills/boundary-validator/SKILL.md b/skills/boundary-validator/SKILL.md index f11b52c..d3189c9 100644 --- a/skills/boundary-validator/SKILL.md +++ b/skills/boundary-validator/SKILL.md @@ -1,253 +1,46 @@ --- name: boundary-validator description: Validates code changes against safe-formdata's boundary-focused design principles. Detects violations of "keys are opaque", "no silent behavior", "no inference", and "explicit issue reporting". Use when reviewing PRs, implementing features, or checking adherence to design rules. -license: MIT -compatibility: Designed for Claude Code (or similar products) -metadata: - author: safe-formdata-project - version: "1.0" - source: Extracted from AGENTS.md -allowed-tools: Read Grep --- # Boundary Validator -This skill validates code changes against safe-formdata's **boundary-focused design principles**. +## Review Process -## When to use this skill - -Use this skill when: - -- Creating or reviewing Pull Requests -- Implementing new features for safe-formdata -- Refactoring existing code -- Performing code reviews -- Checking adherence to design rules before committing - -**Trigger keywords**: code review, PR review, check boundary, validate design, implement, refactor - -## What this skill does - -This skill automatically detects violations of safe-formdata's four core design rules: - -1. **Keys are opaque strings** - No interpretation of key naming conventions -2. **No silent behavior** - No implicit merging or overwriting -3. **No inference, no convenience** - No structural inference or type coercion -4. **Explicit issue reporting** - Never throw for input-derived errors - -## How to use this skill - -### Automatic Activation - -In Claude Code, this skill activates automatically when: - -- You create a Pull Request -- You request a code review -- You mention "boundary rules" or "design principles" - -### Manual Activation - -You can explicitly invoke this skill by saying: - -``` -Review this code against boundary-validator rules -``` - -### Review Process - -The skill will do the following. - -1. **Read the changed files** using the Read and Grep tools -2. **Check for violations** against the four design rules -3. **Report findings** with specific line references and explanations -4. **Suggest fixes** aligned with boundary principles +1. Read the changed files using the Read and Grep tools +2. Check for violations against the four design rules +3. Report findings with specific line references and explanations +4. Suggest fixes aligned with boundary principles ## Validation Criteria -### 1. Keys are opaque strings - -**Rule**: Do not interpret key naming conventions. - -**Violations to detect**: - -```typescript -// ❌ BAD: Parsing bracket notation -if (key.endsWith("[]")) { - // array inference logic -} - -// ❌ BAD: Parsing dot notation -if (key.includes(".")) { - // nested object inference -} - -// ❌ BAD: Interpreting key structure -const [parent, child] = key.split("."); -``` - -**Correct approach**: +For violation patterns and examples, see [design-rules.md](references/design-rules.md). -```typescript -// ✅ GOOD: Treat keys as opaque strings -const data = Object.create(null); -data[key] = value; // No interpretation -``` +### 1. Keys are opaque strings -**Reference**: See [design-rules.md](references/design-rules.md#1-keys-are-opaque-strings) for details. +Do not interpret key naming conventions (`[]`, `.`, `_`, etc.). Store keys as-is. ### 2. No silent behavior -**Rule**: Do not merge duplicate keys, overwrite values, or apply first-wins/last-wins semantics. - -**Violations to detect**: - -```typescript -// ❌ BAD: Merging duplicate keys -Object.assign(result, newData); - -// ❌ BAD: Overwriting values -data[key] = value; // if key exists, this overwrites - -// ❌ BAD: First-wins logic -if (!data[key]) { - data[key] = value; -} - -// ❌ BAD: Last-wins logic -data[key] = value; // always overwrites -``` - -**Correct approach**: - -```typescript -// ✅ GOOD: Report duplicate keys as issues -if (seen.has(key)) { - issues.push({ - code: "duplicate_key", - key, - }); - // Do NOT continue processing -} -``` - -**Reference**: See [design-rules.md](references/design-rules.md#2-no-silent-behavior) for details. +Do not merge duplicate keys, overwrite values, or apply first-wins/last-wins semantics. Report duplicates as `duplicate_key` issues. ### 3. No inference, no convenience -**Rule**: Do not infer arrays or objects, coerce types, validate values, or add configuration options. - -**Violations to detect**: - -```typescript -// ❌ BAD: Array inference -if (key.endsWith("[]")) { - result[key] = []; -} - -// ❌ BAD: Type coercion -const numValue = Number(value); - -// ❌ BAD: Value validation -if (!isValidEmail(value)) { - throw new Error("Invalid email"); -} - -// ❌ BAD: Configuration options -function parse(formData, options = {}) { - if (options.allowDuplicates) { - /* ... */ - } -} -``` - -**Correct approach**: - -```typescript -// ✅ GOOD: No inference, no validation -const data = Object.create(null); -data[key] = value; // Store as-is (string or File) -``` - -**Reference**: See [design-rules.md](references/design-rules.md#3-no-inference-no-convenience) for details. +Do not infer arrays or objects, coerce types, validate values, or add configuration options. ### 4. Explicit issue reporting -**Rule**: Never throw for input-derived errors. Always return a ParseResult with issues. - -**Violations to detect**: - -```typescript -// ❌ BAD: Throwing exceptions -if (invalidKey) { - throw new Error('Invalid key'); -} - -// ❌ BAD: Partial success -return { - data: partialData, // Some keys processed - issues: [...] // Some keys failed -}; -``` - -**Correct approach**: - -```typescript -// ✅ GOOD: Return null data when issues exist -if (issues.length > 0) { - return { - data: null, - issues, - }; -} - -return { - data, - issues: [], -}; -``` - -**Reference**: See [design-rules.md](references/design-rules.md#4-explicit-issue-reporting) for details. +Never throw for input-derived errors. Return `{ data: null, issues }` when any issue exists. ## Additional Checks -### Security Rules - -**Prototype safety**: - -```typescript -// ✅ GOOD: Reject forbidden keys -const FORBIDDEN_KEYS = ["__proto__", "constructor", "prototype"]; -if (FORBIDDEN_KEYS.includes(key)) { - issues.push({ - code: "forbidden_key", - key, - }); -} - -// ✅ GOOD: Use prototype-less objects -const data = Object.create(null); // Not {} -``` - -**Reference**: See [security-rules.md](references/security-rules.md) for details. - -### API Contract - -**IssueCode stability** - -- No new IssueCode values without major version bump -- Existing codes: `invalid_key`, `forbidden_key`, `duplicate_key` - -**ParseResult type** - -- Must be a discriminated union -- `data !== null` for type narrowing -- No `.ok` property +**Security**: Reject `__proto__`, `constructor`, `prototype` as `forbidden_key`. Use `Object.create(null)` for data. See [security-rules.md](references/security-rules.md). -**Reference**: See [api-contract.md](references/api-contract.md) for details. +**API contract**: No new `IssueCode` values without major version bump. `ParseResult` must be a discriminated union; use `data !== null` for type narrowing. No `.ok` property. See [api-contract.md](references/api-contract.md). ## Review Output Format -When violations are found, report them in this format: +When violations are found, report findings in this format: ```markdown ## Boundary Validation Results @@ -266,16 +59,6 @@ result[key.slice(0, -2)] = []; \`\`\` **Suggestion**: Remove array inference. Treat `key` as opaque string. -#### src/parser.ts:78 - -**Rule**: No silent behavior -**Issue**: Duplicate keys are overwritten (last-wins) -**Code**: -\`\`\`typescript -data[key] = value; // Overwrites existing key -\`\`\` -**Suggestion**: Check for duplicates and report as `duplicate_key` issue. - ### ✅ Design Principles Followed - ✅ Uses `Object.create(null)` for data container @@ -283,68 +66,18 @@ data[key] = value; // Overwrites existing key - ✅ Returns ParseResult with `data: null` when issues exist ``` -## File References +## Scope -For detailed rules and patterns, see: +Validates **implementation code** (e.g., `src/parse.ts`). Does not flag: + +- Tests, documentation, or configuration files +- Performance optimizations (unless they compromise correctness) +- Code style preferences (use linter) +- Out-of-scope feature requests (design discussions, not violations) + +## File References - [design-rules.md](references/design-rules.md) - Complete design rules from AGENTS.md - [security-rules.md](references/security-rules.md) - Security implementation requirements - [api-contract.md](references/api-contract.md) - API stability constraints - [validation-patterns.md](references/validation-patterns.md) - Concrete violation patterns - -## Examples - -See the `examples/` directory for: - -- [good-code.md](examples/good-code.md) - ✅ Correct implementations -- [bad-code.md](examples/bad-code.md) - ❌ Common violations - -## Important Notes - -### Scope - -This skill validates **implementation code** (e.g., `src/parse.ts`), not: - -- Tests (though test logic should also follow principles) -- Documentation -- Configuration files - -### Non-Goals - -Do **not** flag these as violations: - -- Performance optimizations (unless they compromise correctness) -- Code style preferences (use linter for that) -- Feature requests that are out of scope (those are design discussions, not violations) - -### Philosophy - -The boundary-focused approach prioritizes: - -1. **Correctness** over convenience -2. **Explicitness** over inference -3. **Stability** over features - -When in doubt, consult [AGENTS.md](../../AGENTS.md) section "Review rule of thumb": - -> If a change makes the parser smarter, more convenient, or more opinionated, it likely violates the boundary. - -## Limitations - -- **Context required**: This skill needs to read source code files -- **Human judgment**: Final decision on edge cases remains with humans -- **False positives**: Some patterns may be flagged incorrectly - use judgment - -## Versioning - -This skill follows AGENTS.md. When AGENTS.md is updated: - -1. Update the corresponding reference files in `references/` -2. Increment the `metadata.version` in this frontmatter -3. Test with actual code reviews - ---- - -**Current version**: 1.0 -**Source**: Extracted from [AGENTS.md](../../AGENTS.md) -**License**: MIT (see [LICENSE.txt](LICENSE.txt)) diff --git a/skills/boundary-validator/examples/bad-code.md b/skills/boundary-validator/examples/bad-code.md deleted file mode 100644 index 7f62ecb..0000000 --- a/skills/boundary-validator/examples/bad-code.md +++ /dev/null @@ -1,528 +0,0 @@ -# ❌ Bad Code Examples - -This document shows common violations of safe-formdata's boundary principles. - -**These examples should NOT be used in production code.** - ---- - -## Rule 1 Violation: Keys are Opaque Strings - -### ❌ Bracket Notation Parsing - -```typescript -// ❌ WRONG: Parsing [] to infer arrays -function parse(formData: FormData): any { - const data: any = {}; - - for (const [key, value] of formData.entries()) { - if (key.endsWith("[]")) { - // Inferring array structure from key name - const arrayKey = key.slice(0, -2); - if (!data[arrayKey]) { - data[arrayKey] = []; - } - data[arrayKey].push(value); - } else { - data[key] = value; - } - } - - return data; -} - -// Problem: Treats "items[]" as special, creates array -// Violates: Keys as opaque strings -``` - -### ❌ Dot Notation Parsing - -```typescript -// ❌ WRONG: Parsing . to create nested objects -function parse(formData: FormData): any { - const data: any = {}; - - for (const [key, value] of formData.entries()) { - const parts = key.split("."); - let current = data; - - for (let i = 0; i < parts.length - 1; i++) { - if (!current[parts[i]]) { - current[parts[i]] = {}; - } - current = current[parts[i]]; - } - - current[parts[parts.length - 1]] = value; - } - - return data; -} - -// Problem: Treats "user.name" as nested structure -// Violates: Keys as opaque strings -``` - -### ❌ PHP-Style Array Parsing - -```typescript -// ❌ WRONG: Parsing PHP-style array notation -function parse(formData: FormData): any { - const data: any = {}; - - for (const [key, value] of formData.entries()) { - const match = key.match(/^(.+?)\[(.+?)\]$/); - - if (match) { - const [, parent, child] = match; - if (!data[parent]) { - data[parent] = {}; - } - data[parent][child] = value; - } else { - data[key] = value; - } - } - - return data; -} - -// Problem: Treats "user[name]" as nested structure -// Violates: Keys as opaque strings -``` - ---- - -## Rule 2 Violation: No Silent Behavior - -### ❌ Last-Wins Semantics - -```typescript -// ❌ WRONG: Silently overwrites duplicate keys -function parse(formData: FormData): any { - const data: any = {}; - - for (const [key, value] of formData.entries()) { - data[key] = value; // If key exists, this silently overwrites - } - - return data; -} - -// Problem: Duplicate keys are silently overwritten (last wins) -// Violates: No silent behavior -``` - -### ❌ First-Wins Semantics - -```typescript -// ❌ WRONG: Silently ignores duplicate keys -function parse(formData: FormData): any { - const data: any = {}; - - for (const [key, value] of formData.entries()) { - if (!data[key]) { - data[key] = value; // Only sets if not already present - } - } - - return data; -} - -// Problem: Duplicate keys are silently ignored (first wins) -// Violates: No silent behavior -``` - -### ❌ Array Merging - -```typescript -// ❌ WRONG: Merging duplicate keys into arrays -function parse(formData: FormData): any { - const data: any = {}; - - for (const [key, value] of formData.entries()) { - if (data[key]) { - // Convert to array if duplicate - if (Array.isArray(data[key])) { - data[key].push(value); - } else { - data[key] = [data[key], value]; - } - } else { - data[key] = value; - } - } - - return data; -} - -// Problem: Silently resolves duplicates by creating arrays -// Violates: No silent behavior -``` - -### ❌ Object.assign / spread syntax - -```typescript -// ❌ WRONG: Using Object.assign (merges) -function parse(formData: FormData): any { - let data: any = {}; - - for (const [key, value] of formData.entries()) { - data = Object.assign(data, { [key]: value }); - // or: data = { ...data, [key]: value }; - } - - return data; -} - -// Problem: Merges objects, overwrites duplicates silently -// Violates: No silent behavior -``` - ---- - -## Rule 3 Violation: No Inference, No Convenience - -### ❌ Type Coercion - -```typescript -// ❌ WRONG: Converting strings to numbers -function parse(formData: FormData): any { - const data: any = {}; - - for (const [key, value] of formData.entries()) { - if (typeof value === "string") { - // Type coercion - const num = Number(value); - data[key] = isNaN(num) ? value : num; - } else { - data[key] = value; - } - } - - return data; -} - -// Problem: Infers types and coerces values -// Violates: No inference -``` - -### ❌ Boolean Conversion - -```typescript -// ❌ WRONG: Converting strings to booleans -function parse(formData: FormData): any { - const data: any = {}; - - for (const [key, value] of formData.entries()) { - if (value === "true") { - data[key] = true; - } else if (value === "false") { - data[key] = false; - } else { - data[key] = value; - } - } - - return data; -} - -// Problem: Infers boolean type from string value -// Violates: No inference -``` - -### ❌ Value Validation - -```typescript -// ❌ WRONG: Validating email format -function parse(formData: FormData): any { - const data: any = {}; - - for (const [key, value] of formData.entries()) { - if (typeof value === "string" && key.includes("email")) { - if (!isValidEmail(value)) { - throw new Error(`Invalid email: ${value}`); - } - } - data[key] = value; - } - - return data; -} - -// Problem: Validates values (business logic) -// Violates: No inference, no convenience -``` - -### ❌ Configuration Options - -```typescript -// ❌ WRONG: Adding options parameter -interface ParseOptions { - allowDuplicates?: boolean; - convertTypes?: boolean; - strict?: boolean; -} - -function parse(formData: FormData, options: ParseOptions = {}): any { - const data: any = {}; - - for (const [key, value] of formData.entries()) { - if (options.convertTypes) { - // Type conversion based on options - } - data[key] = value; - } - - return data; -} - -// Problem: Adds configuration, increases complexity -// Violates: No inference, no convenience -``` - -### ❌ Schema Validation - -```typescript -// ❌ WRONG: Integrating with Zod for validation -import { z } from "zod"; - -const schema = z.object({ - username: z.string().min(3), - email: z.string().email(), -}); - -function parse(formData: FormData): any { - const data: any = {}; - - for (const [key, value] of formData.entries()) { - data[key] = value; - } - - // Validate against schema - const validated = schema.parse(data); - return validated; -} - -// Problem: Adds schema validation (business logic) -// Violates: No inference, no convenience -``` - ---- - -## Rule 4 Violation: Explicit Issue Reporting - -### ❌ Throwing Exceptions - -```typescript -// ❌ WRONG: Throwing for invalid keys -function parse(formData: FormData): any { - const data: any = {}; - - for (const [key, value] of formData.entries()) { - if (key.length === 0) { - throw new Error("Empty key not allowed"); - } - - if (key === "__proto__") { - throw new SecurityError("Forbidden key"); - } - - data[key] = value; - } - - return data; -} - -// Problem: Throws exceptions, forces try-catch on caller -// Violates: Explicit issue reporting -``` - -### ❌ Partial Success - -```typescript -// ❌ WRONG: Returning partial data with issues -function parse(formData: FormData): ParseResult { - const issues: ParseIssue[] = []; - const data = Object.create(null); - - for (const [key, value] of formData.entries()) { - if (key === "__proto__") { - issues.push({ - code: "forbidden_key", - key, - }); - // Problem: Continues processing other keys - } else { - data[key] = value; - } - } - - // Problem: Returns partial data even when issues exist - return { data, issues }; // WRONG: data should be null if issues.length > 0 -} - -// Violates: Explicit issue reporting (no partial success) -``` - -### ❌ Silent Errors - -```typescript -// ❌ WRONG: Silently skipping invalid keys -function parse(formData: FormData): any { - const data: any = {}; - - for (const [key, value] of formData.entries()) { - if (key.length === 0) { - continue; // Silently skips without reporting - } - - data[key] = value; - } - - return data; // No way to know invalid keys were found -} - -// Problem: Errors are silent, no feedback to caller -// Violates: Explicit issue reporting -``` - ---- - -## Security Violations - -### ❌ Plain Object (Prototype Pollution Risk) - -```typescript -// ❌ WRONG: Using {} instead of Object.create(null) -function parse(formData: FormData): any { - const data = {}; // Has Object.prototype in chain - - for (const [key, value] of formData.entries()) { - data[key] = value; - } - - return data; -} - -// Problem: If key === '__proto__', this pollutes the prototype -// Violates: Security rules (data container) -``` - -### ❌ Missing Forbidden Key Check - -```typescript -// ❌ WRONG: Not checking for forbidden keys -function parse(formData: FormData): ParseResult { - const issues: ParseIssue[] = []; - const data = Object.create(null); - - for (const [key, value] of formData.entries()) { - if (typeof key !== "string" || key.length === 0) { - issues.push({ - code: "invalid_key", - key, - }); - continue; - } - - // Problem: No check for __proto__, constructor, prototype - data[key] = value; - } - - return issues.length > 0 ? { data: null, issues } : { data, issues: [] }; -} - -// Violates: Security rules (prototype safety) -``` - ---- - -## API Contract Violations - -### ❌ Function Overloads - -```typescript -// ❌ WRONG: Adding overloads -export function parse(formData: FormData): ParseResult; -export function parse(formData: FormData, strict: boolean): ParseResult; -export function parse(formData: FormData, strict?: boolean): ParseResult { - // Implementation -} - -// Problem: Adds complexity, violates minimal API -// Violates: API contract -``` - -### ❌ Adding New IssueCode (Without Major Version) - -```typescript -// ❌ WRONG: Adding new issue code in minor version -export type IssueCode = "invalid_key" | "forbidden_key" | "duplicate_key" | "invalid_value"; // NEW - requires major version bump! - -// Problem: Breaking change without major version bump -// Violates: API contract (IssueCode stability) -``` - -### ❌ Using `.ok` Property - -```typescript -// ❌ WRONG: Adding .ok property like Result -export type ParseResult = - | { - ok: true; - data: Record; - issues: []; - } - | { - ok: false; - data: null; - issues: ParseIssue[]; - }; - -// Problem: Deviates from established discriminated union pattern -// Violates: API contract (type narrowing with data !== null) -``` - ---- - -## Testing antipatterns - -### ❌ Not Testing Forbidden Keys - -```typescript -// ❌ WRONG: Missing security tests -it("parses basic formdata", () => { - const formData = new FormData(); - formData.append("username", "john"); - - const result = parse(formData); - - expect(result.data).toEqual({ username: "john" }); -}); - -// Problem: No tests for __proto__, constructor, prototype -// Missing: Security test coverage -``` - -### ❌ Not Testing Duplicate Keys - -```typescript -// ❌ WRONG: Missing duplicate key tests -it("parses formdata", () => { - const formData = new FormData(); - formData.append("key", "value1"); - formData.append("key", "value2"); // Duplicate - - const result = parse(formData); - - // Problem: Not testing how duplicates are handled - expect(result.data).toBeDefined(); -}); - -// Missing: Duplicate key test coverage -``` - ---- - -**These examples violate safe-formdata's boundary principles and should be avoided.** diff --git a/skills/boundary-validator/examples/good-code.md b/skills/boundary-validator/examples/good-code.md deleted file mode 100644 index b0be59c..0000000 --- a/skills/boundary-validator/examples/good-code.md +++ /dev/null @@ -1,364 +0,0 @@ -# ✅ Good Code Examples - -This document shows correct implementations that follow safe-formdata's boundary principles. - ---- - -## Complete Implementation Example - -```typescript -import type { ParseResult, ParseIssue, IssueCode } from "./types"; - -export function parse(formData: FormData): ParseResult { - const issues: ParseIssue[] = []; - const data = Object.create(null); // ✅ Prototype-less object - const seen = new Set(); - - const FORBIDDEN_KEYS = ["__proto__", "constructor", "prototype"] as const; - - for (const [key, value] of formData.entries()) { - // ✅ Validate key type and length - if (typeof key !== "string" || key.length === 0) { - issues.push({ - code: "invalid_key" as IssueCode, - key, - }); - continue; - } - - // ✅ Check for forbidden keys (security) - if (FORBIDDEN_KEYS.includes(key as any)) { - issues.push({ - code: "forbidden_key" as IssueCode, - key, - }); - continue; - } - - // ✅ Check for duplicate keys (no silent behavior) - if (seen.has(key)) { - issues.push({ - code: "duplicate_key" as IssueCode, - key, - }); - continue; - } - - // ✅ Store key-value pair without inference - seen.add(key); - data[key] = value; // No type coercion, no transformation - } - - // ✅ Return null data when issues exist (no partial success) - if (issues.length > 0) { - return { data: null, issues }; - } - - return { data, issues: [] }; -} -``` - ---- - -## Key Validation (Correct) - -```typescript -// ✅ Validate key as string -if (typeof key !== "string") { - issues.push({ - code: "invalid_key", - key, - }); - continue; -} - -// ✅ Validate non-empty key -if (key.length === 0) { - issues.push({ - code: "invalid_key", - key: "", - }); - continue; -} - -// ✅ Whitespace-only keys are valid (opaque strings principle) -// " " is a valid key, even if it's just whitespace -``` - ---- - -## Security (Correct) - -```typescript -// ✅ Forbidden keys check -const FORBIDDEN_KEYS = ["__proto__", "constructor", "prototype"] as const; - -if (FORBIDDEN_KEYS.includes(key as any)) { - issues.push({ - code: "forbidden_key", - key, - }); - continue; // Do not process forbidden keys -} - -// ✅ Prototype-less data container -const data = Object.create(null); - -// ✅ No inherited properties -console.log(data.toString); // undefined -console.log(data.hasOwnProperty); // undefined -``` - ---- - -## Duplicate Key Handling (Correct) - -```typescript -// ✅ Detect duplicates using Set -const seen = new Set(); - -for (const [key, value] of formData.entries()) { - if (seen.has(key)) { - issues.push({ - code: "duplicate_key", - key, - }); - continue; // Do not process duplicate - } - seen.add(key); - data[key] = value; -} - -// ✅ Alternative: Detect duplicates using Map -const keyCount = new Map(); - -for (const [key] of formData.entries()) { - keyCount.set(key, (keyCount.get(key) || 0) + 1); -} - -for (const [key, count] of keyCount) { - if (count > 1) { - issues.push({ - code: "duplicate_key", - key, - }); - } -} -``` - ---- - -## Keys as Opaque Strings (Correct) - -```typescript -// ✅ Treat keys as opaque strings, no interpretation -data[key] = value; - -// ✅ Exact string comparison is fine -if (key === "specific_field_name") { - // Special handling for a known field is OK -} - -// ✅ Keys with special characters are treated as-is -data["user[name]"] = value; // Stored as literal key "user[name]" -data["user.email"] = value; // Stored as literal key "user.email" -data["items[]"] = value; // Stored as literal key "items[]" -``` - ---- - -## No Inference (Correct) - -```typescript -// ✅ Store values as-is (string or File) -for (const [key, value] of formData.entries()) { - data[key] = value; // No type checking, no coercion -} - -// ✅ File and string values are both accepted -data['username'] = 'john'; // string -data['avatar'] = new File([...]); // File -``` - ---- - -## Explicit Issue Reporting (Correct) - -```typescript -// ✅ Never throw for input-derived errors -try { - const result = parse(formData); - // No try-catch needed -} catch (e) { - // This will never happen for input issues -} - -// ✅ Return structured result -const result = parse(formData); - -if (result.data !== null) { - // Success: process data - console.log(result.data); -} else { - // Failure: handle issues - for (const issue of result.issues) { - console.error(`Issue: ${issue.code}, Key: ${issue.key}`); - } -} - -// ✅ All-or-nothing: no partial success -if (issues.length > 0) { - return { data: null, issues }; -} -return { data, issues: [] }; -``` - ---- - -## API Contract (Correct) - -```typescript -// ✅ Single function signature, no overloads -export function parse(formData: FormData): ParseResult { - // Implementation -} - -// ✅ No options parameter -// NOT: parse(formData, options) - -// ✅ Stable IssueCode values -export type IssueCode = "invalid_key" | "forbidden_key" | "duplicate_key"; - -// ✅ ParseResult discriminated union -export type ParseResult = - | { data: Record; issues: [] } - | { data: null; issues: [ParseIssue, ...ParseIssue[]] }; - -// ✅ Type narrowing with data !== null -if (result.data !== null) { - // TypeScript knows: data is Record, issues is [] -} -``` - ---- - -## Testing (Correct) - -```typescript -// ✅ Test forbidden keys -it("rejects __proto__ key", () => { - const formData = new FormData(); - formData.append("__proto__", "malicious"); - - const result = parse(formData); - - expect(result.data).toBeNull(); - expect(result.issues).toContainEqual({ - code: "forbidden_key", - key: "__proto__", - }); -}); - -// ✅ Test duplicate keys -it("reports duplicate keys", () => { - const formData = new FormData(); - formData.append("username", "john"); - formData.append("username", "jane"); // Duplicate - - const result = parse(formData); - - expect(result.data).toBeNull(); - expect(result.issues).toContainEqual({ - code: "duplicate_key", - key: "username", - }); -}); - -// ✅ Test opaque keys with special characters -it("accepts keys with brackets", () => { - const formData = new FormData(); - formData.append("items[]", "value1"); - - const result = parse(formData); - - expect(result.data).not.toBeNull(); - expect(result.data?.["items[]"]).toBe("value1"); - // Key is stored as literal "items[]", not as array -}); - -// ✅ Test Object.create(null) usage -it("creates prototype-less object", () => { - const formData = new FormData(); - formData.append("key", "value"); - - const result = parse(formData); - - expect(result.data).not.toBeNull(); - expect(Object.getPrototypeOf(result.data)).toBeNull(); - // No toString, hasOwnProperty, etc. -}); -``` - ---- - -## Type Safety (Correct) - -```typescript -// ✅ Discriminated union with type narrowing -function handleResult(result: ParseResult) { - if (result.data !== null) { - // TypeScript knows: result.data is Record - // TypeScript knows: result.issues is [] - const username: string | File = result.data.username; - - // result.issues is [] here - console.log(result.issues.length); // 0 - } else { - // TypeScript knows: result.data is null - // TypeScript knows: result.issues is [ParseIssue, ...ParseIssue[]] - for (const issue of result.issues) { - console.error(issue.code); - } - } -} - -// ✅ Type annotations on data container -const data: Record = Object.create(null); - -// ✅ Type annotations on issues -const issues: ParseIssue[] = []; -``` - ---- - -## JSDoc (Correct) - -````typescript -/** - * Parses FormData into a flat key-value structure. - * - * @param formData - The FormData object to parse - * @returns ParseResult - Success with data, or failure with issues - * - * @example - * ```ts - * const formData = new FormData(); - * formData.append('username', 'john'); - * - * const result = parse(formData); - * - * if (result.data !== null) { - * console.log(result.data.username); // 'john' - * } else { - * console.error(result.issues); - * } - * ``` - */ -export function parse(formData: FormData): ParseResult { - // Implementation -} -```` - ---- - -**These examples demonstrate correct adherence to all boundary principles.**