feat: add lightweight runtime validation system to map() and reverseMap()#1
feat: add lightweight runtime validation system to map() and reverseMap()#1
Conversation
Co-authored-by: kylebrodeur <811103+kylebrodeur@users.noreply.github.com>
…ap() Co-authored-by: kylebrodeur <811103+kylebrodeur@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds an opt-in runtime validation layer to the mapping utilities in @kylebrodeur/type-safe-mapping, enabling field-presence checks, optional unknown-field enforcement, and an extensible custom validation hook.
Changes:
- Introduces
MapOptionsandvalidateMapping()for lightweight runtime validation. - Extends
MappedServiceBase.map()/reverseMap()to acceptMapOptionsand apply validation when enabled. - Updates exports, README documentation, and adds tests covering validation behavior.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
src/validation.ts |
Adds MapOptions and validateMapping() utility used by mapping methods and exposed publicly. |
src/MappedServiceBase.ts |
Integrates opt-in validation and custom validator hook into map() / reverseMap(). |
src/index.ts |
Exports MapOptions and validateMapping for consumers. |
tests/mapping.test.ts |
Adds coverage for built-in validation, unknown-field behavior, reverse mapping validation, and validateWith. |
README.md |
Documents validation usage, options, and standalone utility API. |
package.json |
Adds devDependencies (ESLint/Prettier/Vitest/TypeScript) and formats files. |
package-lock.json |
Introduces an npm lockfile for the new devDependencies. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (options.validate) { | ||
| validateMapping( | ||
| target as Record<string, unknown>, | ||
| Object.values(this.fieldMapping) as string[], | ||
| options, | ||
| ); |
There was a problem hiding this comment.
Same issue as map(): when options.validate is true, validateMapping(...) can throw before the validateWith hook runs, which contradicts the “called whenever it is provided” wording. Either ensure the hook is invoked even when built-in validation fails (via ordering or aggregation) or clarify the behavior in docs.
|
|
||
| // Check for unmapped (unknown) fields. | ||
| if (options.allowUnknownFields === false) { | ||
| const unknownFields = Object.keys(source).filter((key) => !expectedKeys.includes(key)); |
There was a problem hiding this comment.
unknownFields computation is O(sourceKeys × expectedKeys) because it calls expectedKeys.includes(...) for each source key. If these mappings can be non-trivial in size or called frequently, consider precomputing a Set from expectedKeys and doing O(1) lookups.
| const unknownFields = Object.keys(source).filter((key) => !expectedKeys.includes(key)); | |
| const expectedKeySet = new Set(expectedKeys); | |
| const unknownFields = Object.keys(source).filter((key) => !expectedKeySet.has(key)); |
| @@ -48,5 +53,14 @@ | |||
| }, | |||
| "engines": { | |||
| "node": ">=18.0.0" | |||
There was a problem hiding this comment.
engines.node is still set to ">=18.0.0", but the newly added devDependencies (notably eslint@10 and vitest@4) require Node 20+ per their published engines fields. This makes installs/tests fail on Node 18 despite the engine declaration—either bump the engine range or pin devDependencies to versions that support Node 18.
| "node": ">=18.0.0" | |
| "node": ">=20.0.0" |
| if (options.validate) { | ||
| validateMapping( | ||
| source as Record<string, unknown>, | ||
| Object.keys(this.fieldMapping), | ||
| options, | ||
| ); |
There was a problem hiding this comment.
validateWith is documented as running whenever provided, but in map() it will never run when options.validate is true and validateMapping(...) throws (since that throw happens before the validateWith block). Consider either running validateWith before the built-in validation, or catching/aggregating the validateWith error into the same thrown error, or adjusting the docs to clarify ordering/short-circuiting behavior.
Adds a self-contained, zero-dependency runtime validation layer to
map()andreverseMap(). Validation is opt-in (validate: falseby default) — no breaking changes.Description
New:
src/validation.tsMapOptionsinterface with three options:validate— enables built-in field-presence and unknown-field checksallowUnknownFields— controls whether unmapped source keys are allowed (default:true)validateWith— custom validator hook for integrating Zod, Yup, etc.; runs whenever provided, independent ofvalidatevalidateMapping(source, expectedKeys, options)— exported standalone utility; collects all errors before throwing, not fail-fastUpdated:
src/MappedServiceBase.tsBoth
map()andreverseMap()accept an optionaloptions: MapOptionssecond argument.reverseMapvalidates against internal keys (mapping values), not external keys.Updated:
src/index.tsExports
MapOptionsandvalidateMappingfor consumers needing direct access.Type of change
Checklist
Additional Notes
validateWithintentionally runs outside thevalidategate — allows custom validators without enabling built-in structural checks.reverseMapvalidates against mapping values (internal field names), consistent with its input shape.Original prompt
That's a great direction! Keeping dependencies minimal and building your own tailored validation system complements the core philosophy of
@kylebrodeur/type-safe-mapping. Here's a detailed plan for implementing a lightweight, self-contained validation system that ensures type safety and avoids unnecessary dependencies.Key Goals for Your Validation System
type-safe-mapping, validating only what’s essential to mapping transformations.Plan for Building the Internal Validation System
1. Core Validation Requirements
Your validation system should:
Validate Field Presence:
Validate Field Types:
Handle Optional Fields Gracefully:
Report Unmapped Fields (Optional):
2. Suggested API Updates
Enhance the
map()andreverseMap()methods to support validation options.Updated Method Parameters:
Behavior Examples:
Validation Enabled:
Validation Error:
Optional Fields:
Unknown Fields:
3. Internal Validation Logic
Implement a utility to handle mapping validation.
Validation Utility:
4. Extensibility for Advanced Use Cases
Allow Extending Validation:
Provide hooks for passing custom validation logic.
Provide Convenient Utilities:
Define helper functions like
validateFieldTypeandvalidateFieldPresencefor developers who want finer control over validation.Optional Validation Frameworks:
Encourage users to optionall...
🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.