Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(server-functions): omitted any from fieldErrors #6720

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/docs/src/routes/api/qwik-city/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@
}
],
"kind": "TypeAlias",
"content": "```typescript\nexport type ValidatorErrorKeyDotNotation<T, Prefix extends string = ''> = T extends object ? {\n [K in keyof T & string]: T[K] extends (infer U)[] ? U extends object ? `${Prefix}${K}[]` | `${Prefix}${K}[]${ValidatorErrorKeyDotNotation<U, '.'>}` : `${Prefix}${K}[]` : T[K] extends object ? ValidatorErrorKeyDotNotation<T[K], `${Prefix}${K}.`> : `${Prefix}${K}`;\n}[keyof T & string] : never;\n```\n**References:** [ValidatorErrorKeyDotNotation](#validatorerrorkeydotnotation)",
"content": "```typescript\nexport type ValidatorErrorKeyDotNotation<T, Prefix extends string = ''> = IsAny<T> extends true ? never : T extends object ? {\n [K in keyof T & string]: IsAny<T[K]> extends true ? never : T[K] extends (infer U)[] ? IsAny<U> extends true ? never : U extends object ? `${Prefix}${K}[]` | ValidatorErrorKeyDotNotation<U, `${Prefix}${K}[].`> : `${Prefix}${K}[]` : T[K] extends object ? ValidatorErrorKeyDotNotation<T[K], `${Prefix}${K}.`> : `${Prefix}${K}`;\n}[keyof T & string] : never;\n```\n**References:** [ValidatorErrorKeyDotNotation](#validatorerrorkeydotnotation)",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts",
"mdFile": "qwik-city.validatorerrorkeydotnotation.md"
},
Expand Down
36 changes: 20 additions & 16 deletions packages/docs/src/routes/api/qwik-city/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2398,22 +2398,26 @@ validator$: ValidatorConstructor;
## ValidatorErrorKeyDotNotation

```typescript
export type ValidatorErrorKeyDotNotation<
T,
Prefix extends string = "",
> = T extends object
? {
[K in keyof T & string]: T[K] extends (infer U)[]
? U extends object
?
| `${Prefix}${K}[]`
| `${Prefix}${K}[]${ValidatorErrorKeyDotNotation<U, ".">}`
: `${Prefix}${K}[]`
: T[K] extends object
? ValidatorErrorKeyDotNotation<T[K], `${Prefix}${K}.`>
: `${Prefix}${K}`;
}[keyof T & string]
: never;
export type ValidatorErrorKeyDotNotation<T, Prefix extends string = ""> =
IsAny<T> extends true
? never
: T extends object
? {
[K in keyof T & string]: IsAny<T[K]> extends true
? never
: T[K] extends (infer U)[]
? IsAny<U> extends true
? never
: U extends object
?
| `${Prefix}${K}[]`
| ValidatorErrorKeyDotNotation<U, `${Prefix}${K}[].`>
: `${Prefix}${K}[]`
: T[K] extends object
? ValidatorErrorKeyDotNotation<T[K], `${Prefix}${K}.`>
: `${Prefix}${K}`;
}[keyof T & string]
: never;
```

**References:** [ValidatorErrorKeyDotNotation](#validatorerrorkeydotnotation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,6 @@ For this example the `fieldErrors` look like this:
}
```

> [!WARNING]
> If you use `z.any()` you have to handle the everything yourself in the action. The `fieldErrors` will never contain this field.

6 changes: 4 additions & 2 deletions packages/qwik-city/src/runtime/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,11 @@ export const useNavigate: () => RouteNavigate;
// @public (undocumented)
export const validator$: ValidatorConstructor;

// Warning: (ae-forgotten-export) The symbol "IsAny" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export type ValidatorErrorKeyDotNotation<T, Prefix extends string = ''> = T extends object ? {
[K in keyof T & string]: T[K] extends (infer U)[] ? U extends object ? `${Prefix}${K}[]` | `${Prefix}${K}[]${ValidatorErrorKeyDotNotation<U, '.'>}` : `${Prefix}${K}[]` : T[K] extends object ? ValidatorErrorKeyDotNotation<T[K], `${Prefix}${K}.`> : `${Prefix}${K}`;
export type ValidatorErrorKeyDotNotation<T, Prefix extends string = ''> = IsAny<T> extends true ? never : T extends object ? {
[K in keyof T & string]: IsAny<T[K]> extends true ? never : T[K] extends (infer U)[] ? IsAny<U> extends true ? never : U extends object ? `${Prefix}${K}[]` | ValidatorErrorKeyDotNotation<U, `${Prefix}${K}[].`> : `${Prefix}${K}[]` : T[K] extends object ? ValidatorErrorKeyDotNotation<T[K], `${Prefix}${K}.`> : `${Prefix}${K}`;
}[keyof T & string] : never;

// @public (undocumented)
Expand Down
99 changes: 98 additions & 1 deletion packages/qwik-city/src/runtime/src/server-functions.unit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expectTypeOf, test } from 'vitest';
import { z } from '.';
import { server$ } from './server-functions';
import type { RequestEventBase } from './types';
import type { RequestEventBase, ValidatorErrorType } from './types';

describe('types', () => {
test('matching', () => () => {
Expand Down Expand Up @@ -64,4 +65,100 @@ describe('types', () => {
}>
>();
});

test('easy zod type', () => () => {
const zodSchema = z.object({
username: z.string(),
password: z.string(),
});
type ErrorType = ValidatorErrorType<z.infer<typeof zodSchema>>['fieldErrors'];

expectTypeOf<ErrorType>().toEqualTypeOf<{
username?: string;
password?: string;
}>();
});

test('array zod type with string', () => () => {
const zodSchema = z.object({
arrayWithStrings: z.array(z.string()),
});
type ErrorType = ValidatorErrorType<z.infer<typeof zodSchema>>['fieldErrors'];

expectTypeOf<ErrorType>().toEqualTypeOf<{
['arrayWithStrings[]']?: string[];
}>();
});

test('array zod type with object', () => () => {
const zodSchema = z.object({
persons: z.array(
z.object({
name: z.string(),
age: z.number(),
})
),
});
type ErrorType = ValidatorErrorType<z.infer<typeof zodSchema>>['fieldErrors'];

expectTypeOf<ErrorType>().toEqualTypeOf<{
'persons[]'?: string[];
'persons[].name'?: string[];
'persons[].age'?: string[];
}>();
});

test('Complex zod type', () => () => {
const BaseUserSchema = z.object({
id: z.string().uuid(),
username: z.string().min(3).max(20),
email: z.string().email(),
createdAt: z.date().default(new Date()),
isActive: z.boolean().default(true),
someAnyType: z.any(),
roles: z.array(z.enum(['user', 'admin', 'moderator'])).default(['user']),
preferences: z
.object({
theme: z.enum(['light', 'dark']).default('light'),
notifications: z.boolean().default(true),
})
.optional(),
});

// Schema for an Admin user with additional fields
const AdminUserSchema = BaseUserSchema.extend({
adminSince: z.date(),
permissions: z.array(z.string()),
}).refine((data) => data.roles.includes('admin'), {
message: 'Admin role must be included in roles',
});

// Schema for a Moderator user with additional fields
const ModeratorUserSchema = BaseUserSchema.extend({
moderatedSections: z.array(z.string()),
}).refine((data) => data.roles.includes('moderator'), {
message: 'Moderator role must be included in roles',
});

// Union of all user types
const UserSchema = z.union([AdminUserSchema, ModeratorUserSchema, BaseUserSchema]);

type ErrorType = ValidatorErrorType<z.infer<typeof UserSchema>>['fieldErrors'];
type EqualType = {
username?: string;
id?: string;
email?: string;
isActive?: string;
preferences?: string;
'roles[]'?: string[];
'permissions[]'?: string[];
'moderatedSections[]'?: string[];
};

expectTypeOf<ErrorType>().toEqualTypeOf<EqualType>();

expectTypeOf<ErrorType>().not.toEqualTypeOf<{
someAnyType?: string;
}>();
});
});
32 changes: 21 additions & 11 deletions packages/qwik-city/src/runtime/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,17 +362,27 @@ export type FailOfRest<REST extends readonly DataValidator[]> = REST extends rea
: never;

/** @public */
export type ValidatorErrorKeyDotNotation<T, Prefix extends string = ''> = T extends object
? {
[K in keyof T & string]: T[K] extends (infer U)[]
? U extends object
? `${Prefix}${K}[]` | `${Prefix}${K}[]${ValidatorErrorKeyDotNotation<U, '.'>}`
: `${Prefix}${K}[]`
: T[K] extends object
? ValidatorErrorKeyDotNotation<T[K], `${Prefix}${K}.`>
: `${Prefix}${K}`;
}[keyof T & string]
: never;
export type IsAny<T> = 0 extends T & 1 ? true : false;
Copy link
Contributor

@fabian-hiller fabian-hiller Aug 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the IsAny type is just a internal util type similar to the Prettify type above and should not be exported.


/** @public */
export type ValidatorErrorKeyDotNotation<T, Prefix extends string = ''> =
IsAny<T> extends true
? never
: T extends object
? {
[K in keyof T & string]: IsAny<T[K]> extends true
? never
: T[K] extends (infer U)[]
? IsAny<U> extends true
? never
: U extends object
? `${Prefix}${K}[]` | ValidatorErrorKeyDotNotation<U, `${Prefix}${K}[].`>
: `${Prefix}${K}[]`
: T[K] extends object
? ValidatorErrorKeyDotNotation<T[K], `${Prefix}${K}.`>
: `${Prefix}${K}`;
}[keyof T & string]
: never;

/** @public */
export type ValidatorErrorType<T, U = string> = {
Expand Down
Loading