Skip to content

Commit

Permalink
Merge pull request #8 from TheAppleFreak/refine-functions
Browse files Browse the repository at this point in the history
v3.2.0 - Added refine and superRefine methods
  • Loading branch information
TheAppleFreak authored May 2, 2023
2 parents fd68d7a + 390bafe commit f8afb7d
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 18 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "18.x"
node-version: "17.x"
registry-url: "https://registry.npmjs.org"
- run: npm ci
- run: npm run build
Expand Down
18 changes: 9 additions & 9 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ jobs:

strategy:
matrix:
node-version: [17.x, 18.x]
node-version: [17.x, latest]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
Expand All @@ -28,12 +28,12 @@ jobs:

strategy:
matrix:
node-version: [17.x, 18.x]
node-version: [17.x, latest]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
Expand All @@ -44,12 +44,12 @@ jobs:

strategy:
matrix:
node-version: [17.x, 18.x]
node-version: [17.x, latest]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

(all dates use the ISO-8601 format, which is YYYY/MM/DD)

## 3.2.0 (2023/5/2)

* Added support for the `.refine` and `.superRefine` methods. These add additional flexibility for validation. This closes [Issue #7](https://github.com/TheAppleFreak/moleculer-zod-validator/issues/7).
* Added new unit tests for both methods
* Updated Github Actions workflows to use checkout@v3 and setup-node@v3
* Changed GitHub Actions workflows to work with Node 17 (earliest supported) and the current latest version, as opposed to 17 and 18.

## 3.1.0 (2023/4/21)

* Type inference now works (mostly) as expected! While the runtime behavior of the package worked as expected during work on 3.0.0 and was what I had been testing, I somehow was completely unaware that type inference was completely broken. It's a bit embarrassing for me, to be honest. It took more energy than I had anticipated to make it work, but type inferences now behave when using `typeof .call` and `typeof .context` almost exactly as they do in Zod with `z.input<typeof validator>` and `z.infer<typeof validator>`/`z.output<typeof validator>`. This closes [Issue #5](https://github.com/TheAppleFreak/moleculer-zod-validator/issues/5).
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,23 @@ The `ZodParams` constructor takes one or two arguments, `schema` and optionally
typeof zodParamObject.call & {[index: string]: string}
>({ ... })
```
* `refine` (function OR object) - Adds custom validation logic to the Zod object that can't be represented in TypeScript's type system or purely using Zod validators on their own (for example, making sure that at least one of several optional items are present). Returning any falsy value will fail validation, while returning any truthy value will pass validation. ([docs](https://github.com/colinhacks/zod/#refine))

There are two ways to use this property. You can either pass in a validation function taking one parameter (representing the object being passed in) or an object with optionally two properties.

* `validator` (function) - A validation function taking one parameter, representing the object being validated currently.
* `params` (object, optional) - Additional properties to customize the error handling behavior, [as described in the Zod documentation](https://github.com/colinhacks/zod/#arguments)

If both `refine` and `superRefine` are defined, `refine` will run last (after `superRefine`).

* `superRefine` (function) - Adds custom validation logic to the Zod object that can't be represented in TypeScript's type system or purely using Zod validators on their own (for example, making sure that at least one of several optional items are present). This is a more powerful and verbose method of performing refinements. Validation will pass unless `ctx.addIssue` is called. ([docs](https://github.com/colinhacks/zod/#superrefine))

This property takes a function with two arguments, `val` and `ctx` (not to be confused with Moleculer's `ctx` option).

* `val` (object) - An object representing the object being validated currently.
* `ctx` (object) - An object provided by Zod.

If both `refine` and `superRefine` are defined, `superRefine` will run first (before `refine`).

Additionally, support for object transformations is present, allowing for the use of features such as [preprocessing](https://github.com/colinhacks/zod#preprocess), [refinements](https://github.com/colinhacks/zod#refine), [transforms](https://github.com/colinhacks/zod#transform), and [defaults](https://github.com/colinhacks/zod#default).

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "moleculer-zod-validator",
"version": "3.1.0",
"version": "3.2.0",
"description": "A validator for the Moleculer microservice framework to allow the use of Zod.",
"author": "TheAppleFreak <TheAppleFreak@gmail.com>",
"main": "build/index.js",
Expand Down
58 changes: 54 additions & 4 deletions src/params.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { z } from "zod";
import { ZodType, z } from "zod";

import type {
ZodArray,
Expand All @@ -24,7 +24,6 @@ export class ZodParams<
$$$options: z.infer<typeof ZodParamsOptions>;
};

// These types are used purely to assist in type inference within this class
/** This property is purely for type inference and should not be used. */
public _mode!: ZPOptions["strip"] extends true
? "strip"
Expand Down Expand Up @@ -118,6 +117,24 @@ export class ZodParams<
this._validator = this._validator.catchall(opts.catchall);
}

// Functions should be considered truthy
if (opts.superRefine) {
// @ts-expect-error
this._validator = this._validator.superRefine(opts.superRefine);
}
if (opts.refine) {
if (typeof opts.refine === "function") {
// @ts-expect-error
this._validator = this._validator.refine(opts.refine);
} else {
// @ts-expect-error
this._validator = this._validator.refine(
opts.refine.validator,
opts.refine.params
);
}
}

this._rawSchemaWithOptions = Object.assign({}, schema, {
$$$options: opts
});
Expand Down Expand Up @@ -191,13 +208,46 @@ const ZodParamsOptions = z
strict: z.boolean().default(false),
catchall: z.any(),
passthrough: z.boolean(),
strip: z.boolean().default(false)
strip: z.boolean().default(false),
refine: z.union([
// Not sure how best to represent the validator function params
z.function().args(z.any()),
z.object({
validator: z.function().args(z.any()),
params: z
.object({
message: z.string(),
path: z.array(z.union([z.string(), z.number()])),
params: z.object({}).passthrough()
})
.partial()
.optional()
})
]),
superRefine: z.function().args(z.any(), z.any())
})
.partial();

export type ZodParamsOptionsType = {
catchall?: ZodTypeAny;
} & Omit<z.input<typeof ZodParamsOptions>, "catchall">;
refine?:
| Parameters<ZodType["refine"]>[0]
| {
validator: Parameters<ZodType["refine"]>[0];
params?: {
// override error message
message?: string;

// appended to error path
path?: (string | number)[];

// params object you can use to customize message
// in error map
params?: object;
};
};
superRefine?: Parameters<ZodType["superRefine"]>[0];
} & Omit<z.input<typeof ZodParamsOptions>, "catchall" | "refine" | "superRefine">;

type ZodParamsMakeOptionalSchema<T extends Parameters<(typeof z)["object"]>[0]> = {
[K in keyof T]: T[K] extends ZodOptional<T[K]> ? T[K] : ZodOptional<T[K]>;
Expand Down
17 changes: 16 additions & 1 deletion src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class ZodValidator extends Validators.Base {
} else if (opts.passthrough) {
compiled = z.object(schema).passthrough();
} else {
compiled = z.object(schema).strip();
compiled = z.object(schema);
}

if (opts.partial) {
Expand All @@ -73,6 +73,21 @@ export class ZodValidator extends Validators.Base {
compiled = compiled.catchall(opts.catchall);
}

// Functions should be considered truthy
if (opts.superRefine) {
compiled = compiled.superRefine(opts.superRefine);
}
if (opts.refine) {
if (typeof opts.refine === "function") {
compiled = compiled.refine(opts.refine);
} else {
compiled = compiled.refine(
opts.refine.validator,
opts.refine.params
);
}
}

const results = compiled.parse(params);

// params is passed by reference, meaning that we can apply transformations
Expand Down
Loading

0 comments on commit f8afb7d

Please sign in to comment.