prisma-generator-express 1.14.0 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -164,7 +164,7 @@ const afterFindFirst: RequestHandler = (
164
164
 
165
165
  /**
166
166
  * For generated route the middleware order will be as follows:
167
- * 1. Query parser
167
+ * 1. Query parser (kicks in for GET requests)
168
168
  * 2. Custom middlewares: config.{method}.before[]
169
169
  * 3. Input validator middleware (Optional): config.{method}.input
170
170
  * 4. Generated middleware
@@ -176,7 +176,7 @@ const someRouterConfig: RouteConfig<RequestHandler> = {
176
176
  before: [beforeFindFirst],
177
177
  after: [afterFindFirst],
178
178
  input: {
179
- schema: UserAccountFindFirstSchema,
179
+ schema: UserAccountFindFirstSchema, // make sure you set `isGenerateSelect = true` in prisma-zod-generator
180
180
  allow: [
181
181
  'select.id',
182
182
  'select.full_name',
@@ -27,7 +27,7 @@ import { ${modelName}GroupBy } from './${modelName}GroupBy';
27
27
  import { createValidatorMiddleware, ValidatorOptions } from '../createValidatorMiddleware'
28
28
  import { createOutputValidatorMiddleware } from '../createOutputValidatorMiddleware'
29
29
  import { RouteConfig, ValidatorConfig } from '../routeConfig'
30
- import { parseQueryParams } from "../ParseQueryParams";
30
+ import { parseQueryParams } from "../parseQueryParams";
31
31
 
32
32
  const defaultBeforeAfter = {
33
33
  before: [] as RequestHandler[],
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "prisma-generator-express",
3
3
  "description": "Prisma generator of Express CRUD API",
4
- "version": "1.14.0",
4
+ "version": "1.14.1",
5
5
  "main": "dist/generator.js",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -553,106 +553,7 @@ describe('Cryptocurrency Schema Validation', () => {
553
553
  expect(error).toBeInstanceOf(ZodError)
554
554
  }
555
555
  })
556
-
557
- it('deep nesting', () => {
558
- const taskSchema = z.object({
559
- select: z
560
- .object({
561
- id: z.boolean().optional(),
562
- project_id: z.boolean().optional(),
563
- list_id: z.boolean().optional(),
564
- user_assignments: z
565
- .object({
566
- select: z.object({
567
- user: z.boolean().optional(),
568
- }),
569
- })
570
- .optional(),
571
- tags_mappings: z
572
- .object({
573
- select: z.object({
574
- tag: z.boolean().optional(),
575
- }),
576
- })
577
- .optional(),
578
- attachments: z
579
- .object({
580
- select: z.object({
581
- attachment: z.boolean().optional(),
582
- }),
583
- where: z
584
- .object({
585
- is_image: z.boolean().optional(),
586
- })
587
- .optional(),
588
- take: z.number().optional(),
589
- orderBy: z
590
- .object({
591
- created_at: z.enum(['asc', 'desc']).optional(),
592
- })
593
- .optional(),
594
- })
595
- .optional(),
596
- rendered_description: z.boolean().optional(),
597
- description: z.boolean().optional(),
598
- created_at: z.boolean().optional(),
599
- start_date: z.boolean().optional(),
600
- reactions: z.boolean().optional(),
601
- intervals: z.boolean().optional(),
602
- column_id: z.boolean().optional(),
603
- priority: z.boolean().optional(),
604
- due_date: z.boolean().optional(),
605
- column: z.boolean().optional(),
606
- title: z.boolean().optional(),
607
- order: z.boolean().optional(),
608
- color: z.boolean().optional(),
609
- })
610
- .optional(),
611
- where: z
612
- .object({
613
- id: z.string().optional(),
614
- AND: z
615
- .array(
616
- z.object({
617
- OR: z
618
- .array(
619
- z.object({
620
- to_delete: z.boolean().nullable().optional(),
621
- }),
622
- )
623
- .optional(),
624
- }),
625
- )
626
- .optional(),
627
- })
628
- .optional(),
629
- })
630
-
631
- const allowedFields = [
632
- 'select.id',
633
- 'select.project_id',
634
- 'select.list_id',
635
- 'select.user_assignments.select.user',
636
- 'select.tags_mappings.select.tag',
637
- 'select.attachments.select.attachment',
638
- 'select.attachments.where.is_image',
639
- 'select.attachments.take',
640
- 'select.attachments.orderBy.created_at',
641
- 'select.rendered_description',
642
- 'select.description',
643
- 'select.created_at',
644
- 'select.start_date',
645
- 'select.reactions',
646
- 'select.intervals',
647
- 'select.column_id',
648
- 'select.priority',
649
- 'select.due_date',
650
- 'select.column',
651
- 'select.order',
652
- 'select.color',
653
- 'where.id',
654
- 'where.AND[].OR[].to_delete',
655
- ]
556
+ describe('Prisma example', () => {
656
557
  const inputData = {
657
558
  select: {
658
559
  id: true,
@@ -703,12 +604,160 @@ describe('Cryptocurrency Schema Validation', () => {
703
604
  ],
704
605
  },
705
606
  }
607
+ it('deep nesting', () => {
608
+ const taskSchema = z.object({
609
+ select: z
610
+ .object({
611
+ id: z.boolean().optional(),
612
+ project_id: z.boolean().optional(),
613
+ list_id: z.boolean().optional(),
614
+ user_assignments: z
615
+ .object({
616
+ select: z.object({
617
+ user: z.boolean().optional(),
618
+ }),
619
+ })
620
+ .optional(),
621
+ tags_mappings: z
622
+ .object({
623
+ select: z.object({
624
+ tag: z.boolean().optional(),
625
+ }),
626
+ })
627
+ .optional(),
628
+ attachments: z
629
+ .object({
630
+ select: z.object({
631
+ attachment: z.boolean().optional(),
632
+ }),
633
+ where: z
634
+ .object({
635
+ is_image: z.boolean().optional(),
636
+ })
637
+ .optional(),
638
+ take: z.number().optional(),
639
+ orderBy: z
640
+ .object({
641
+ created_at: z.enum(['asc', 'desc']).optional(),
642
+ })
643
+ .optional(),
644
+ })
645
+ .optional(),
646
+ rendered_description: z.boolean().optional(),
647
+ description: z.boolean().optional(),
648
+ created_at: z.boolean().optional(),
649
+ start_date: z.boolean().optional(),
650
+ reactions: z.boolean().optional(),
651
+ intervals: z.boolean().optional(),
652
+ column_id: z.boolean().optional(),
653
+ priority: z.boolean().optional(),
654
+ due_date: z.boolean().optional(),
655
+ column: z.boolean().optional(),
656
+ title: z.boolean().optional(),
657
+ order: z.boolean().optional(),
658
+ color: z.boolean().optional(),
659
+ })
660
+ .optional(),
661
+ where: z
662
+ .object({
663
+ id: z.string().optional(),
664
+ AND: z
665
+ .array(
666
+ z.object({
667
+ OR: z
668
+ .array(
669
+ z.object({
670
+ to_delete: z.boolean().nullable().optional(),
671
+ }),
672
+ )
673
+ .optional(),
674
+ }),
675
+ )
676
+ .optional(),
677
+ })
678
+ .optional(),
679
+ })
706
680
 
707
- try {
708
- const result = allow(taskSchema, allowedFields).safeParse(inputData)
709
- expect(result.success).toBe(false)
710
- } catch (error) {
711
- expect(error).toBeInstanceOf(ZodError)
712
- }
681
+ const allowedFields = [
682
+ 'select.id',
683
+ 'select.project_id',
684
+ 'select.list_id',
685
+ 'select.user_assignments.select.user',
686
+ 'select.tags_mappings.select.tag',
687
+ 'select.attachments.select.attachment',
688
+ 'select.attachments.where.is_image',
689
+ 'select.attachments.take',
690
+ 'select.attachments.orderBy.created_at',
691
+ 'select.rendered_description',
692
+ 'select.description',
693
+ 'select.created_at',
694
+ 'select.start_date',
695
+ 'select.reactions',
696
+ 'select.intervals',
697
+ 'select.column_id',
698
+ 'select.priority',
699
+ 'select.due_date',
700
+ 'select.column',
701
+ 'select.order',
702
+ 'select.color',
703
+ 'where.id',
704
+ 'where.AND[].OR[].to_delete',
705
+ ]
706
+
707
+ try {
708
+ const result = allow(taskSchema, allowedFields).safeParse(inputData)
709
+ expect(result.success).toBe(false)
710
+ } catch (error) {
711
+ expect(error).toBeInstanceOf(ZodError)
712
+ }
713
+ })
714
+
715
+ it('deep nesting 2', () => {
716
+ const taskSchema = z.object({
717
+ where: z
718
+ .object({
719
+ id: z.string().optional(),
720
+ AND: z
721
+ .array(
722
+ z.object({
723
+ OR: z
724
+ .array(
725
+ z.object({
726
+ to_delete: z.boolean().nullable().optional(),
727
+ }),
728
+ )
729
+ .optional(),
730
+ }),
731
+ )
732
+ .optional(),
733
+ })
734
+ .optional(),
735
+ })
736
+
737
+ const allowedFields = [
738
+ 'select.id',
739
+ 'select.project_id',
740
+ 'select.list_id',
741
+ 'select.user_assignments.select.user',
742
+ 'select.tags_mappings.select.tag',
743
+ 'select.attachments.select.attachment',
744
+ 'select.attachments.where.is_image',
745
+ 'select.attachments.take',
746
+ 'select.attachments.orderBy.created_at',
747
+ 'select.rendered_description',
748
+ 'select.description',
749
+ 'select.created_at',
750
+ 'select.start_date',
751
+ 'where.id',
752
+ 'where.AND[].OR[].to_delete',
753
+ ]
754
+
755
+ try {
756
+ const result = allow(taskSchema, allowedFields).safeParse(inputData)
757
+ expect(result.success).toBe(false)
758
+ } catch (error) {
759
+ expect(error).toBeInstanceOf(ZodError)
760
+ }
761
+ })
713
762
  })
714
763
  })
@@ -38,10 +38,11 @@ export function allow<T extends ZodTypeAny>(
38
38
  schema: T,
39
39
  allowedPaths: string[],
40
40
  ): ZodEffects<T, any, any> {
41
- const rootSchema = schema instanceof z.ZodObject ? schema : undefined
41
+ const rootSchema = schema instanceof z.ZodObject ? schema.strict() : undefined
42
42
 
43
- return schema.transform((data) => {
43
+ return rootSchema?.transform((data) => {
44
44
  const flatData = flattenObject(data, '', rootSchema)
45
+
45
46
  const disallowedPaths: string[] = []
46
47
 
47
48
  for (const key of Object.keys(flatData)) {
@@ -51,19 +52,11 @@ export function allow<T extends ZodTypeAny>(
51
52
  }
52
53
 
53
54
  if (disallowedPaths.length > 0) {
54
- const errors: ZodIssue[] = []
55
- for (const path of disallowedPaths) {
56
- errors.push({
57
- code: ZodIssueCode.custom,
58
- message: `Field '${path}' is not allowed.`,
59
- path: path.split('.'),
60
- })
61
- }
62
- throw new ZodError(errors)
55
+ throw createZodErrorFromPaths(disallowedPaths, 'Field is not allowed:')
63
56
  }
64
57
 
65
58
  return data
66
- }) as ZodEffects<T, any, any>
59
+ }) as unknown as ZodEffects<T, any, any>
67
60
  }
68
61
 
69
62
  export function forbid<T extends z.ZodTypeAny>(
@@ -82,15 +75,7 @@ export function forbid<T extends z.ZodTypeAny>(
82
75
  }
83
76
 
84
77
  if (forbiddenMatches.length > 0) {
85
- const errors: ZodIssue[] = []
86
- for (const path of forbiddenMatches) {
87
- errors.push({
88
- code: ZodIssueCode.custom,
89
- message: `Field '${path}' is forbidden.`,
90
- path: [path],
91
- })
92
- }
93
- throw new ZodError(errors)
78
+ throw createZodErrorFromPaths(forbiddenMatches, 'Field is forbidden:')
94
79
  }
95
80
  return data
96
81
  }) as ZodEffects<T, any, any>
@@ -138,3 +123,18 @@ export function flattenObject(
138
123
  flatten(obj, prefix, schema)
139
124
  return result
140
125
  }
126
+
127
+ function createZodErrorFromPaths(
128
+ disallowedPaths: string[],
129
+ errorMessage: string,
130
+ ): ZodError {
131
+ const errors: ZodIssue[] = []
132
+ for (const path of disallowedPaths) {
133
+ errors.push({
134
+ code: ZodIssueCode.custom,
135
+ message: `${errorMessage} '${path}'`,
136
+ path: path.split('.'),
137
+ })
138
+ }
139
+ return new ZodError(errors)
140
+ }
@@ -31,7 +31,7 @@ import { ${modelName}GroupBy } from './${modelName}GroupBy';
31
31
  import { createValidatorMiddleware, ValidatorOptions } from '../createValidatorMiddleware'
32
32
  import { createOutputValidatorMiddleware } from '../createOutputValidatorMiddleware'
33
33
  import { RouteConfig, ValidatorConfig } from '../routeConfig'
34
- import { parseQueryParams } from "../ParseQueryParams";
34
+ import { parseQueryParams } from "../parseQueryParams";
35
35
 
36
36
  const defaultBeforeAfter = {
37
37
  before: [] as RequestHandler[],