Claude-skill-registry-data mapper-validation
Mapper field validation process ensuring zero missing fields
git clone https://github.com/majiayu000/claude-skill-registry-data
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry-data "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/mapper-validation" ~/.claude/skills/majiayu000-claude-skill-registry-data-mapper-validation && rm -rf "$T"
data/mapper-validation/SKILL.mdMapper Field Validation Process
Complete validation workflow for ensuring mappers correctly handle all interface fields.
MANDATORY 3-STEP VALIDATION
Every mapper MUST follow this validation process:
Step 1: Interface Analysis
READ the target interface from
generated/api/index.ts:
// Example: After running npm run clean && npm run generate interface UserInfo { id: UUID; // Required name: string; // Required email?: Email; // Optional createdAt: Date; // Required status?: StatusEnum;// Optional }
DOCUMENT ALL FIELDS:
- Count total fields: 5
- Required fields (3): id, name, createdAt
- Optional fields (2): email, status
- CRITICAL: Record the exact count
Step 2: API Response Schema Validation
EXAMINE the corresponding response schema in api.yml:
components: schemas: UserInfo: type: object required: - id - name - created_at properties: id: type: string name: type: string email: type: string format: email created_at: type: string format: date-time status: type: string enum: [active, inactive, pending]
VERIFY:
- Each interface field has corresponding API response field
- Required fields match between interface and schema
- Optional fields are correctly optional
Step 3: Complete Mapping Implementation
IMPLEMENT mapper with ALL fields using any:
export function toUserInfo(raw: any): UserInfo { // Validate required fields first using ensureProperties ensureProperties(raw, ['id', 'name', 'created_at']); // 🚨 CRITICAL: Map ALL fields (must match interface count) const output: UserInfo = { // Required (3) - validated above id: String(raw.id), name: raw.name, createdAt: map(DateTime, raw.created_at), // Optional (2) - MUST be mapped too email: map(Email, raw.email), status: toEnum(StatusEnum, raw.status) }; return output; }
Type Safety Rules:
- ✅ ALL mapper parameters use
for simplicityany - ✅ Use
helper for validating required fieldsensureProperties() - ✅ Add
at top of mapper file/* eslint-disable */
**VALIDATION**: - **ZERO MISSING FIELDS**: Interface field count = Mapped field count - If interface has 5 fields → mapper MUST map 5 fields - If interface has 10 fields → mapper MUST map 10 fields ## Field Count Validation **MANDATORY CHECK**: Before considering mapper complete, count fields: ```typescript // ✅ CORRECT - All 5 fields mapped interface UserInfo { id: UUID; // 1 name: string; // 2 email?: Email; // 3 createdAt: Date; // 4 status?: StatusEnum;// 5 } export function toUserInfo(raw: any): UserInfo { ensureProperties(raw, ['id', 'name', 'created_at']); const output: UserInfo = { id: map(UUID, raw.id), // 1 ✓ name: raw.name, // 2 ✓ email: map(Email, raw.email), // 3 ✓ createdAt: map(DateTime, raw.created_at), // 4 ✓ status: toEnum(StatusEnum, raw.status) // 5 ✓ }; return output; } // Field count: 5 = 5 ✅ PASS // ❌ WRONG - Missing fields export function toUserInfo(raw: any): UserInfo { ensureProperties(raw, ['id', 'name', 'created_at']); const output: UserInfo = { id: map(UUID, raw.id), // 1 ✓ name: raw.name, // 2 ✓ createdAt: map(DateTime, raw.created_at) // 3 ✓ // Missing: email (field 4) // Missing: status (field 5) }; return output; } // Field count: 3 ≠ 5 ❌ FAIL
RULE: Interface field count MUST EQUAL mapped field count.
Nested Object Handling
For nested objects, use non-exported helper mappers:
// Parent interface interface Organization { id: UUID; name: string; address?: Address; // Nested object } // Nested interface interface Address { street: string; city: string; country?: string; } // ✅ CORRECT - Helper mapper for nested object with validation function toAddress(raw: any): Address { // 🚨 CRITICAL: Helper functions MUST validate required fields ensureProperties(raw, ['street', 'city']); const output: Address = { street: raw.street, city: raw.city, country: optional(raw.country) }; return output; } // Main mapper uses helper with mapWith export function toOrganization(raw: any): Organization { ensureProperties(raw, ['id', 'name']); const output: Organization = { id: String(raw.id), name: raw.name, address: mapWith(toAddress, raw.address) // mapWith handles null/undefined }; return output; }
CRITICAL RULES:
- ✅ Helper mappers MUST use
parameter typeany - ✅ Helper mappers MUST validate their own required fields with
ensureProperties() - ✅ Helper mappers are NOT exported (no
keyword)export - ✅ Defined above the main mapper function
- ✅ Use
in parent to handle null/undefined at boundarymapWith() - ✅ Helper returns plain type
, notT
(mapWith adds the undefined)T | undefined - ❌ NEVER skip validation in helper functions
Optional Field Handling
Optional fields MUST still be mapped, returning
undefined when absent:
// ✅ CORRECT - Optional field mapped export function toUser(data: any): User { ensureProperties(data, ['id', 'name']); const output: User = { id: map(UUID, data.id), // Required name: data.name, // Required email: map(Email, data.email), // Optional - returns Email | undefined website: map(URL, data.website) // Optional - returns URL | undefined }; return output; } // ❌ WRONG - Optional field missing export function toUser(data: any): User { ensureProperties(data, ['id', 'name']); const output: User = { id: map(UUID, data.id), name: data.name, email: map(Email, data.email) // Missing: website - even though optional, must be mapped }; return output; }
WHY: TypeScript interface includes the field, mapper must provide it (even if undefined).
Validation Checklist
Before completing a mapper:
- Read generated interface from
generated/api/index.ts - Counted total interface fields (required + optional)
- Documented required vs optional fields
- Checked API response schema in api.yml
- Validated required fields with error throwing
- Mapped ALL required fields
- Mapped ALL optional fields (with
)| undefined - Used
patternconst output: Type - Verified field count: interface count = mapper count
- No fields missing from mapper
- Helper mappers for nested objects (not exported)
Common Mistakes
Mistake 1: Skipping Optional Fields
// ❌ WRONG - Skipping optional fields interface User { id: UUID; name: string; email?: Email; // Optional but must be mapped! phone?: string; // Optional but must be mapped! } export function toUser(data: any): User { ensureProperties(data, ['id', 'name']); return { id: map(UUID, data.id), name: data.name // Missing email and phone - WRONG even if optional }; }
FIX: Map all fields, optional or not.
Mistake 2: Not Counting Fields
// ❌ WRONG - No validation of field count // Developer maps 3 fields, interface has 5, doesn't notice
FIX: Explicitly count before and after.
Mistake 3: Assuming Defaults
// ❌ WRONG - Assuming undefined for missing fields interface Resource { id: UUID; name: string; tags?: string[]; // Optional array } export function toResource(data: any): Resource { ensureProperties(data, ['id', 'name']); return { id: map(UUID, data.id), name: data.name // Missing tags - TypeScript might allow but it's wrong }; }
FIX: Explicitly map optional fields:
export function toResource(data: any): Resource { ensureProperties(data, ['id', 'name']); return { id: map(UUID, data.id), name: data.name, tags: data.tags || undefined // Explicit }; }
Nested Model Example
Complete example with nested objects:
// Interfaces (from generated/api/index.ts) interface Organization { id: UUID; name: string; address?: Address; owner?: User; } interface Address { street: string; city: string; postalCode?: string; } interface User { id: UUID; name: string; email?: Email; } // Helper mappers (not exported) function toAddress(data: any): Address { ensureProperties(data, ['street', 'city']); const output: Address = { street: data.street, city: data.city, postalCode: data.postal_code || undefined // All 3 fields mapped }; return output; } function toUser(data: any): User { ensureProperties(data, ['id', 'name']); const output: User = { id: map(UUID, data.id), name: data.name, email: map(Email, data.email) // All 3 fields mapped }; return output; } // Main mapper (exported) export function toOrganization(data: any): Organization { ensureProperties(data, ['id', 'name']); const output: Organization = { id: map(UUID, data.id), name: data.name, address: mapWith(toAddress, data.address), // mapWith handles null/undefined owner: mapWith(toUser, data.owner) // mapWith handles null/undefined }; return output; // All 4 fields mapped }
VALIDATION:
- Organization interface: 4 fields → Mapper: 4 fields ✅
- Address interface: 3 fields → Helper mapper: 3 fields ✅
- User interface: 3 fields → Helper mapper: 3 fields ✅
Discovery Process
When implementing a new mapper:
-
Run generation:
npm run clean && npm run generate -
Open generated interface:
// generated/api/index.ts export interface NewResource { // Read all fields here } -
Count fields:
- Total: ?
- Required: ?
- Optional: ?
-
Check API schema:
# api.yml components: schemas: NewResource: required: [...] properties: ... -
Implement mapper:
export function toNewResource(data: any): NewResource { ensureProperties(data, ['required_field1', 'required_field2']); const output: NewResource = { // Map ALL fields }; return output; } -
Validate:
- Count fields in mapper
- Compare with interface count
- Must match exactly
Runtime Validation (MANDATORY)
CRITICAL: Static field counting is necessary but NOT sufficient.
After implementing mapper, you MUST perform runtime validation to detect:
- Field name mismatches (API
vs specmobilePhone
)phoneNumber - Fields in API but not in spec
- Fields in spec but not mapped correctly
- Type conversion errors
When to Run Runtime Validation
MANDATORY for:
- New module creation (after all operations implemented)
- Adding new operations to existing module
- API spec updates
- After mapper refactoring
Quick Runtime Validation Process
-
Add temporary debug log in producer BEFORE mapping:
this.logger.debug('[TEMP-RAW-API] operationName:', JSON.stringify(response.data, null, 2)); -
Run tests with debug logging:
env LOG_LEVEL=debug npm run test:integration > /tmp/debug.log 2>&1 -
Compare raw API vs mapped output:
# Extract raw API response grep -A 100 "\[TEMP-RAW-API\]" /tmp/debug.log # Compare with mapped result in test logs grep "Api\." /tmp/debug.log -
Fix any missing fields then remove temp logs
RULE: Mapper validation is NOT complete until runtime validation shows ZERO missing fields.
See complete process: mapper-runtime-validation skill
References
- Mapper Pattern: implementation skill (Mapper Pattern section)
- Runtime Validation: mapper-runtime-validation skill
- Type Mapping: type-mapping skill
- Error Handling: error-handling skill
- Operation Engineer agent: @.claude/agents/operation-engineer.md
- Mapping Engineer agent: @.claude/agents/mapping-engineer.md