sanity-advanced-validators 0.2.0 → 0.3.0

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.
Files changed (2) hide show
  1. package/README.md +166 -73
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,8 +1,10 @@
1
+ *🚓 Never trust a user! 👮*
2
+
1
3
  # Sanity Advanced Validators
2
4
 
3
- This package includes a set of Sanity validators for aggressive and weird edge cases. Please let me know if you find these helpful!
5
+ This package includes a set of Sanity validators for aggressive and weird edge cases. *Maintain sanity with micro-managed validation.*
4
6
 
5
- Note that every validator can accept an optional custom error message as its last parameter. `minDimensions` lists one example; all the others work the same way.
7
+ Note: Every validator can accept an optional custom error message as its last parameter. See [minDimensions](#minDimensions) for an example.
6
8
 
7
9
  ## Tools
8
10
 
@@ -18,7 +20,10 @@ Note that every validator can accept an optional custom error message as its las
18
20
 
19
21
  ## Mega-example
20
22
 
21
- Imagine that you’ve got a document that has an optional video file but it's required on the `/about` page. If the video exists, it must either MP4 or MOV, and have a poster image that's between 1250x800 and 2500x1600 in size.
23
+ Imagine that you’ve got a document that has an optional video file, but
24
+ - it’s required on the `/about` page
25
+ - if the video exists, it must be either **MP4** or **MOV**
26
+ - and there must be a poster image that's between **1250x800** and **2500x1600** pixels in size
22
27
 
23
28
  ```typescript
24
29
  const Page = defineType({
@@ -51,7 +56,7 @@ const Page = defineType({
51
56
 
52
57
  ## Examples
53
58
 
54
- ### fileType
59
+ ### fileExtension
55
60
 
56
61
  Enforces that an uploaded file asset is of a certain format.
57
62
 
@@ -65,7 +70,7 @@ const Page = defineType({
65
70
  defineField({
66
71
  name: "catalog",
67
72
  type: "file",
68
- validation: (rule) => rule.custom(fileType("pdf")),
73
+ validation: (rule) => rule.custom(fileExtension("pdf")),
69
74
  }),
70
75
  defineField({
71
76
  name: "video",
@@ -76,6 +81,14 @@ const Page = defineType({
76
81
  })
77
82
  ```
78
83
 
84
+ #### parameters
85
+ ```typescript
86
+ fileType: string | Array<string>,
87
+ message?: string // optional custom error message; replaces {validFileExtension} with fileType (flattened)
88
+ ```
89
+
90
+ ---
91
+
79
92
  ### minDimensions
80
93
 
81
94
  Enforces that an uploaded image asset is at minimum certain dimensions.
@@ -84,17 +97,14 @@ Enforces that an uploaded image asset is at minimum certain dimensions.
84
97
  import { minDimensions } from "sanity-advanced-validation"
85
98
 
86
99
  const ImageWithCaption = defineType({
87
- name: "imageWithCaption",
100
+ name: "article",
88
101
  type: "object",
89
102
  fields: [
103
+ // …
90
104
  defineField({
91
- name: "caption",
92
- type: "string",
93
- }),
94
- defineField({
95
- name: "image",
105
+ name: "heroImage",
96
106
  type: "image",
97
- validation: (rule) => rule.custom(minDimensions({ x: 100, y: 100 })),
107
+ validation: (rule) => rule.custom(minDimensions({ x: 1200, y: 800 })),
98
108
  }),
99
109
  ],
100
110
  })
@@ -104,60 +114,68 @@ You can also enforce on only one dimension, or feed a custom error message:
104
114
 
105
115
  ```typescript
106
116
  defineField({
107
- name: "image",
117
+ name: "heroImage",
108
118
  type: "image",
109
- description: "At least 100px wide; as tall as you like.",
119
+ description: "At least 1200px wide; as tall as you like.",
110
120
  validation: (rule) => rule.custom(
111
121
  minDimensions(
112
- { x: 100 },
122
+ { x: 1200 },
113
123
  "Uh oh, your image is {width} pixels wide. That’s less than {x}!"
114
124
  )
115
125
  ),
116
126
  })
117
127
  ```
118
128
 
129
+ #### parameters
130
+ ```typescript
131
+ dimensions: {x?: number, y?: number},
132
+ message?: string // optional custom error message; replaces {x} and {y} with your dimension requirements, and {width} and {height} with submitted image dimensions
133
+ ```
134
+
135
+ ---
136
+
137
+
119
138
  ### maxDimensions
120
139
 
121
140
  Enforces that an uploaded image asset is at most certain dimensions.
122
141
 
123
142
  ```typescript
124
- import { maxDimensions } from "sanity-advanced-validation"
125
-
126
- const ImageWithCaption = defineType({
127
- name: "imageWithCaption",
128
- type: "object",
129
- fields: [
130
- defineField({
131
- name: "caption",
132
- type: "string",
133
- }),
134
- defineField({
135
- name: "image",
136
- type: "image",
137
- validation: (rule) => rule.custom(maxDimensions({ x: 2000, y: 2000 })),
138
- }),
139
- ],
140
- })
143
+ defineField({
144
+ name: "heroImage",
145
+ type: "image",
146
+ validation: (rule) => rule.custom(maxDimensions({ x: 2400, y: 1600 })),
147
+ }),
141
148
  ```
142
149
 
143
150
  Chain for min and max dimensions:
144
151
 
145
152
  ```typescript
146
153
  defineField({
147
- name: "image",
154
+ name: "heroImage",
148
155
  type: "image",
149
- description: "Min: 100x100, max: 2000x2000.",
156
+ description: "Min: 1200x800, max: 2400x1600.",
150
157
  validation: (rule) =>
151
158
  rule
152
159
  .required()
153
- .custom(minDimensions({ x: 1000, y: 1000 }))
154
- .custom(maxDimensions({ x: 2000, y: 2000 })),
160
+ .custom(minDimensions({ x: 1200, y: 800 }))
161
+ .custom(maxDimensions({ x: 2400, y: 1600 })),
155
162
  })
156
163
  ```
157
164
 
165
+ #### parameters
166
+ ```typescript
167
+ dimensions: {x?: number, y?: number},
168
+ message?: string // optional custom error message; replaces {x} and {y} with your dimension requirements, and {width} and {height} with submitted image dimensions
169
+ ```
170
+
171
+ ---
172
+
173
+
158
174
  ### requiredIfSiblingEq
159
175
 
160
- For a given object that has multiple fields, mark a field as `required` if a sibling has a particular value.
176
+ Mark a field as `required` if a sibling field has a particular value. This is the validator we use most. *It’s super effective!*
177
+
178
+ This is handy if you have a field that is hidden under some circumstances, but is `required()` when it’s visible.
161
179
 
162
180
  _note:_ This does not work for slugs, because they have to match a nested `.current` value. Use the [requiredIfSlugEq validator](#requiredIfSlugEq) instead.
163
181
 
@@ -186,15 +204,15 @@ defineType({
186
204
  list: [
187
205
  'javascript', 'rust', 'python', 'swift'
188
206
  ]
189
- }
207
+ },
208
+ validation: rule => rule.custom(requiredIfSiblingEq('occupation', 'software engineer')),
190
209
  hidden: ({parent}) => parent.occuption !== 'software engineer',
191
- validation: rule => rule.custom(requiredIfSiblingEq('occupation', 'software engineer'))
192
210
  }),
193
211
  ],
194
212
  })
195
213
  ```
196
214
 
197
- This also works for null. It’s very effective!
215
+ “If not that, then this.” This also works for null.
198
216
 
199
217
  ```typescript
200
218
  defineType({
@@ -215,7 +233,7 @@ defineType({
215
233
  validation: rule => rule.custom(requiredIfSiblingEq(
216
234
  'email',
217
235
  null,
218
- "If you don’t have an email address, please provide a phone number."
236
+ "If you don’t have an email address, a phone number is required."
219
237
  ))
220
238
  })
221
239
  ],
@@ -233,34 +251,38 @@ defineType({
233
251
  name: 'name',
234
252
  type: 'string'
235
253
  }),
236
- defineType({
237
- name: 'person',
238
- type: 'object',
239
- fields: [
240
- defineField({
241
- name: 'name',
242
- type: 'string'
243
- }),
244
- defineField({
245
- name: 'occupation',
246
- type: 'string',
247
- options: {
248
- list: ['doctor', 'lawyer', 'software engineer']
249
- }
250
- }),
251
- defineField({
252
- name: 'explanation',
253
- description: 'Why are you wasting your life this way?',
254
- type: 'text',
255
- hidden: ({parent}) => parent.occuption === 'software engineer',
256
- validation: rule => rule.custom(requiredIfSiblingEq('occupation', ['doctor', 'lawyer']))
257
- })
258
- ],
254
+ defineField({
255
+ name: 'name',
256
+ type: 'string'
257
+ }),
258
+ defineField({
259
+ name: 'occupation',
260
+ type: 'string',
261
+ options: {
262
+ list: ['doctor', 'lawyer', 'software engineer']
263
+ }
264
+ }),
265
+ defineField({
266
+ name: 'explanation',
267
+ description: 'Why are you wasting your life this way?',
268
+ type: 'text',
269
+ validation: rule => rule.custom(requiredIfSiblingEq('occupation', ['doctor', 'lawyer'])),
270
+ hidden: ({parent}) => parent.occuption === 'software engineer',
259
271
  })
260
272
  ],
261
273
  })
262
274
  ```
263
275
 
276
+ #### parameters
277
+ ```typescript
278
+ key: string, // name of sibling
279
+ operand: string | number | null | Array<string, number, null> // value that you’re testing for (i.e. if 'name' === operand)
280
+ message?: string // optional custom error message; replaces {key} and {operand} with your input, and {siblingValue} with the value of the sibling you’re testing against.
281
+ ```
282
+
283
+ ---
284
+
285
+
264
286
  ### requiredIfSiblingNeq
265
287
 
266
288
  For a given object that has multiple fields, mark a field as `required` if a sibling does _not_ have a particular value.
@@ -287,15 +309,24 @@ defineType({
287
309
  })
288
310
  defineField({
289
311
  name: 'why',
290
- description: 'Why are you wasting your life this way?',
291
- type: 'text',
292
- hidden: ({parent}) => parent.occuption === 'software engineer',
312
+ description: 'How many years will you spend paying off your degree?',
313
+ type: 'number',
293
314
  validation: rule => rule.custom(requiredIfSiblingNeq('occupation', 'software engineer'))
294
315
  }),
295
316
  ],
296
317
  })
297
318
  ```
298
319
 
320
+ #### parameters
321
+ ```typescript
322
+ key: string, // name of sibling
323
+ operand: string | number | null | Array<string, number, null> // value that you’re testing for (i.e. if 'name' === operand)
324
+ message?: string // optional custom error message; replaces {key} and {operand} with your input, and {siblingValue} with the value of the sibling you’re testing against.
325
+ ```
326
+
327
+ ---
328
+
329
+
299
330
  ### requiredIfSlugEq
300
331
 
301
332
  Require for matching slugs.
@@ -317,7 +348,7 @@ defineType({
317
348
  of: [
318
349
  {type: 'qaItem'}
319
350
  ],
320
- validation: rule => rule.custom(requiredIfSlugEq('faq'))
351
+ validation: rule => rule.custom(requiredIfSlugEq('faq')),
321
352
  hidden: ({parent}) => parent.slug.current !== 'faq'
322
353
  })
323
354
  ]
@@ -333,6 +364,16 @@ defineField({
333
364
  }),
334
365
  ```
335
366
 
367
+ #### parameters
368
+ ```typescript
369
+ operand: string | number | null | Array<string, number, null> // possible slug or slugs you’re testing
370
+ key?: string, // name of sibling if not "slug"
371
+ message?: string // optional custom error message; replaces {slugKey} and {operand} with your input, and {siblingSlugValue} with the value of the sibling you’re testing against.
372
+ ```
373
+
374
+ ---
375
+
376
+
336
377
  ### requiredIfSlugNeq
337
378
 
338
379
  Require fields on pages that don't match one or more slugs.
@@ -350,25 +391,35 @@ defineType({
350
391
  }),
351
392
  defineField({
352
393
  name: 'subnav',
353
- description: 'Subnav is required on documents that aren't '/home'`,
394
+ description: `Subnav is required on documents that arent '/home'`,
354
395
  type: 'array',
355
396
  of: [
356
397
  {type: 'navLink'}
357
398
  ],
358
- validation: rule => rule.custom(requiredIfSlugNeq('home'))
399
+ validation: rule => rule.custom(requiredIfSlugNeq('home')),
359
400
  hidden: ({parent}) => parent.slug.current !== 'home'
360
401
  })
361
402
  ]
362
403
  })
363
404
  ```
364
405
 
406
+ #### parameters
407
+ ```typescript
408
+ operand: string | number | null | Array<string, number, null> // possible slug or slugs you’re testing
409
+ key?: string, // name of sibling if not "slug"
410
+ message?: string // optional custom error message; replaces {slugKey} and {operand} with your input, and {siblingSlugValue} with the value of the sibling you’re testing against.
411
+ ```
412
+
413
+ ---
414
+
415
+
365
416
  ### referencedDocumentRequires
366
417
 
367
- You might want to enforce some validation on a referred document. This validator enforces that a given value is not null in the referenced document.
418
+ You might want to enforce some validation on a referenced document. This validator enforces that a given value is not null in the referenced document.
368
419
 
369
420
  ```typescript
370
421
  defineField({
371
- name: 'refferedArticle',
422
+ name: 'referredArticle',
372
423
  description: 'An article (must include a valid poster image)',
373
424
  type: 'reference',
374
425
  to: [{type: 'article'}],
@@ -376,6 +427,16 @@ defineField({
376
427
  }),
377
428
  ```
378
429
 
430
+ #### parameters
431
+ ```typescript
432
+ documentType: string // type of document you’re referring to
433
+ field: string, // name of document field that is required
434
+ message?: string // optional custom error message; replaces {documentType} and {field} with your input
435
+ ```
436
+
437
+ ---
438
+
439
+
379
440
  ### maxDepth
380
441
 
381
442
  It can be useful to have a nested type. This often comes up when making some kind of navigation tree, like…
@@ -448,7 +509,20 @@ const navLink = defineType({
448
509
 
449
510
  This will enforce that a subnav list can embed in a subnav, which can also be embedded in a subnav — but no further.
450
511
 
451
- _Note to any Sanity dev who looks at this_: I’d love to include similar logic on my `hidden:` attribute, but I don’t think that’t possible without a `path` array in the `hidden` context that’s similar to the one fed to the `ValidationContext` (todo: type this correctly). Wouldn’t this be cool?
512
+
513
+ #### parameters
514
+ ```typescript
515
+ maxDepth: number // maximum "depth" of embedding (including parent)
516
+ key: string, // name of the field that includes the cursive value (i.e. the field’s own name)
517
+ message?: string // optional custom error message; replaces {maxDepth} and {key} with your input
518
+ ```
519
+
520
+ ---
521
+
522
+
523
+ #### Note to any Sanity dev who looks at this
524
+
525
+ I’d love to include similar logic on my `hidden:` attribute, but I don’t think that’t possible without a `path` array in `hidden`’s `ConditionalPropertyCallbackContext` that’s similar to the one fed to the `ValidationContext` (todo: type this correctly). Wouldn’t this be cool?
452
526
 
453
527
  ```typescript
454
528
  defineField({
@@ -463,18 +537,37 @@ defineField({
463
537
  })
464
538
  ```
465
539
 
540
+ ---
541
+
466
542
  ## Extending these and writing your own
467
543
 
468
544
  Most of these validators rely on a function called `getSibling()`. If you’re thinking about picking this apart and writing your own custom validator, take a close look at how these validators use it.
469
545
 
470
546
  ## Upcoming
471
547
 
548
+ ### Nested pathfinders
549
+
472
550
  Since building these validator, I took to putting my slugs in a metadata object. I need to update `requiredIfSlugEq` to accept a path, like `requiredIfSlugEq('metadata.slug', 'some-values')`.
473
551
 
474
552
  This pathfinding should be added to any validator that takes a sibling, like `requiredIfSiblingEq`. It can probably be snapped into `getSibling`.
475
553
 
476
554
  While I’m at it, there’s a possibility that `getSibling` could detect the target type. If that type is `slug`, then it could add `current` to the path, and then I can deprecate `requiredIfSlugEq` altogether.
477
555
 
556
+ ### Image and File checks
557
+
558
+ `minDimensions`, `maxDimensions`, and `fileExtension` should check to see if the field is of type `image` or `file`.
559
+
560
+ Some of the other checks should probably make sure the field is _not_ `image` or `file`.
561
+
562
+ ### new referencedDocumentFieldEq validator
563
+
564
+ ```
565
+ // only articles by Jimmy Olsen
566
+ rule => rule.custom(referencedDocumentFieldEq('article', 'author', 'Jimmy Olsen'))
567
+ // only articles whose authors are not null. replaces `referencedDocumentRequires`.
568
+ rule => rule.custom(referencedDocumentFieldNeq('article', 'author', null))
569
+ ```
570
+
478
571
  ## MOAR
479
572
 
480
573
  Do you have any ideas or edge cases that these validators don’t cover? Leave an issue, maybe I can hack it out.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package-name": "sanity-advanced-validators",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Advanced input validation tools for Sanity CMS.",
5
5
  "author": "Eric_WVGG",
6
6
  "license": "MIT",