@sanity/validation 2.21.12-shopify.7 → 2.22.3-reference-updates.78

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Rule.d.ts","sourceRoot":"","sources":["../../src/Rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,SAAS,EAUV,MAAM,eAAe,CAAA;AA0DtB,QAAA,MAAM,IAAI,EAAE,SAwVX,CAAA;AAED,eAAe,IAAI,CAAA"}
1
+ {"version":3,"file":"Rule.d.ts","sourceRoot":"","sources":["../../src/Rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,SAAS,EAUV,MAAM,eAAe,CAAA;AA0DtB,QAAA,MAAM,IAAI,EAAE,SA2VX,CAAA;AAED,eAAe,IAAI,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"normalizeValidationRules.d.ts","sourceRoot":"","sources":["../../../src/util/normalizeValidationRules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,IAAI,EAAqB,MAAM,eAAe,CAAA;AA+ElE;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,GAAG,IAAI,EAAE,CAqCxF"}
1
+ {"version":3,"file":"normalizeValidationRules.d.ts","sourceRoot":"","sources":["../../../src/util/normalizeValidationRules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,IAAI,EAAqB,MAAM,eAAe,CAAA;AA6ElE;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,GAAG,IAAI,EAAE,CAqCxF"}
@@ -3,7 +3,7 @@ import { SanityDocument, Schema, SchemaType, ValidationContext, ValidationMarker
3
3
  * @internal
4
4
  */
5
5
  export declare function resolveTypeForArrayItem(item: unknown, candidates: SchemaType[]): SchemaType | undefined;
6
- export default function validateDocument(doc: SanityDocument, schema: Schema): Promise<ValidationMarker[]>;
6
+ export default function validateDocument(doc: SanityDocument, schema: Schema, context?: Pick<ValidationContext, 'getDocumentExists'>): Promise<ValidationMarker[]>;
7
7
  /**
8
8
  * this is used make optional properties required by replacing optionals with
9
9
  * `T[P] | undefined`. this is used to prevent errors in `validateItem` where
@@ -1 +1 @@
1
- {"version":3,"file":"validateDocument.d.ts","sourceRoot":"","sources":["../../src/validateDocument.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,MAAM,EACN,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EAGjB,MAAM,eAAe,CAAA;AAYtB;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,UAAU,EAAE,GACvB,UAAU,GAAG,SAAS,CAiBxB;AAED,wBAA8B,gBAAgB,CAC5C,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA0B7B;AAED;;;;;;GAMG;AACH,aAAK,iBAAiB,CAAC,CAAC,IAAI;KACzB,CAAC,IAAI,MAAM,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS;CAC5F,CAAA;AAED,aAAK,mBAAmB,GAAG;IACzB,KAAK,EAAE,OAAO,CAAA;CACf,GAAG,iBAAiB,CAAC,iBAAiB,CAAC,CAAA;AAExC,wBAAsB,YAAY,CAAC,EACjC,KAAK,EACL,IAAI,EACJ,IAAS,EACT,MAAM,EACN,GAAG,aAAa,EACjB,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA8FnD"}
1
+ {"version":3,"file":"validateDocument.d.ts","sourceRoot":"","sources":["../../src/validateDocument.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,MAAM,EACN,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EAMjB,MAAM,eAAe,CAAA;AAYtB;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,UAAU,EAAE,GACvB,UAAU,GAAG,SAAS,CAiBxB;AAED,wBAA8B,gBAAgB,CAC5C,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,GACrD,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA2B7B;AAED;;;;;;GAMG;AACH,aAAK,iBAAiB,CAAC,CAAC,IAAI;KACzB,CAAC,IAAI,MAAM,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS;CAC5F,CAAA;AAED,aAAK,mBAAmB,GAAG;IACzB,KAAK,EAAE,OAAO,CAAA;CACf,GAAG,iBAAiB,CAAC,iBAAiB,CAAC,CAAA;AAExC,wBAAsB,YAAY,CAAC,EACjC,KAAK,EACL,IAAI,EACJ,IAAS,EACT,MAAM,EACN,GAAG,aAAa,EACjB,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA4HnD"}
@@ -1 +1 @@
1
- {"version":3,"file":"objectValidator.d.ts","sourceRoot":"","sources":["../../../src/validators/objectValidator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,eAAe,CAAA;AAKxC,QAAA,MAAM,gBAAgB,EAAE,UA8CvB,CAAA;AAED,eAAe,gBAAgB,CAAA"}
1
+ {"version":3,"file":"objectValidator.d.ts","sourceRoot":"","sources":["../../../src/validators/objectValidator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAc,MAAM,eAAe,CAAA;AAKrD,QAAA,MAAM,gBAAgB,EAAE,UAwDvB,CAAA;AAED,eAAe,gBAAgB,CAAA"}
package/lib/Rule.js CHANGED
@@ -232,6 +232,17 @@ var Rule = (_temp = _class = class Rule {
232
232
  constraint: fn
233
233
  }]);
234
234
  }
235
+ /**
236
+ * @deprecated use `Rule.custom` instead
237
+ */
238
+
239
+
240
+ block(fn) {
241
+ return this.cloneWithRules([{
242
+ flag: 'custom',
243
+ constraint: fn
244
+ }]);
245
+ }
235
246
 
236
247
  min(len) {
237
248
  return this.cloneWithRules([{
@@ -383,13 +394,6 @@ var Rule = (_temp = _class = class Rule {
383
394
  }]);
384
395
  }
385
396
 
386
- block(blockValidator) {
387
- return this.cloneWithRules([{
388
- flag: 'block',
389
- constraint: blockValidator
390
- }]);
391
- }
392
-
393
397
  fields(rules) {
394
398
  if (this._type !== 'Object') {
395
399
  throw new Error('fields() can only be called on an object type');
@@ -7,8 +7,6 @@ exports.default = normalizeValidationRules;
7
7
 
8
8
  var _Rule = _interopRequireDefault(require("../Rule"));
9
9
 
10
- var _blockValidator = require("../validators/blockValidator");
11
-
12
10
  var _slugValidator = require("../validators/slugValidator");
13
11
 
14
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -61,7 +59,6 @@ function baseRuleReducer(inputRule, type) {
61
59
  if (type.name === 'slug') return baseRule.custom(_slugValidator.slugValidator);
62
60
  if (type.name === 'reference') return baseRule.reference();
63
61
  if (type.name === 'email') return baseRule.email();
64
- if (type.name === 'block') return baseRule.block(_blockValidator.blockValidator);
65
62
  return baseRule;
66
63
  }
67
64
 
@@ -74,7 +74,7 @@ function resolveTypeForArrayItem(item, candidates) {
74
74
  }) || candidates.find(candidate => candidate.name === itemType) || candidates.find(candidate => candidate.name === 'object' && primitive === 'object');
75
75
  }
76
76
 
77
- function validateDocument(_x, _x2) {
77
+ function validateDocument(_x, _x2, _x3) {
78
78
  return _validateDocument.apply(this, arguments);
79
79
  }
80
80
  /**
@@ -87,7 +87,7 @@ function validateDocument(_x, _x2) {
87
87
 
88
88
 
89
89
  function _validateDocument() {
90
- _validateDocument = _asyncToGenerator(function* (doc, schema) {
90
+ _validateDocument = _asyncToGenerator(function* (doc, schema, context) {
91
91
  var documentType = schema.get(doc._type);
92
92
 
93
93
  if (!documentType) {
@@ -101,7 +101,8 @@ function _validateDocument() {
101
101
  value: doc,
102
102
  path: [],
103
103
  document: doc,
104
- type: documentType
104
+ type: documentType,
105
+ getDocumentExists: context === null || context === void 0 ? void 0 : context.getDocumentExists
105
106
  });
106
107
  } catch (err) {
107
108
  console.error(err);
@@ -116,7 +117,7 @@ function _validateDocument() {
116
117
  return _validateDocument.apply(this, arguments);
117
118
  }
118
119
 
119
- function validateItem(_x3) {
120
+ function validateItem(_x4) {
120
121
  return _validateItem.apply(this, arguments);
121
122
  }
122
123
 
@@ -192,9 +193,32 @@ function _validateItem() {
192
193
  } : value.indexOf(item)),
193
194
  type: resolveTypeForArrayItem(item, type.of)
194
195
  }))));
196
+ } // markDefs also do no run nested validation if the parent object is undefined
197
+ // for a similar reason to arrays
198
+
199
+
200
+ var shouldRunNestedValidationForMarkDefs = (0, _types.isBlock)(value) && value.markDefs.length && (0, _types.isBlockSchemaType)(type);
201
+
202
+ if (shouldRunNestedValidationForMarkDefs) {
203
+ var _type$fields = _slicedToArray(type.fields, 1),
204
+ spanChildrenField = _type$fields[0];
205
+
206
+ var spanType = spanChildrenField.type.of.find(_types.isSpanSchemaType);
207
+ var annotations = ((spanType === null || spanType === void 0 ? void 0 : spanType.annotations) || []).reduce((map, annotationType) => {
208
+ map.set(annotationType.name, annotationType);
209
+ return map;
210
+ }, new Map());
211
+ nestedChecks = nestedChecks.concat(value.markDefs.map(markDef => validateItem(_objectSpread(_objectSpread({}, restOfContext), {}, {
212
+ parent: value,
213
+ value: markDef,
214
+ path: path.concat(['markDefs', {
215
+ _key: markDef._key
216
+ }]),
217
+ type: annotations.get(markDef._type)
218
+ }))));
195
219
  }
196
220
 
197
- var results = (yield Promise.all([...selfChecks, ...nestedChecks])).flat(); // run `dedupeValidationMarkers` if `_fieldRules` are present because they can
221
+ var results = (yield Promise.all([...selfChecks, ...nestedChecks])).flat(); // run `uniqBy` if `_fieldRules` are present because they can
198
222
  // cause repeat markers
199
223
 
200
224
  if (rules.some(rule => rule._fieldRules)) {
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
 
8
+ var _types = require("@sanity/types");
9
+
8
10
  var _genericValidator = _interopRequireDefault(require("./genericValidator"));
9
11
 
10
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -35,33 +37,47 @@ var objectValidators = _objectSpread(_objectSpread({}, _genericValidator.default
35
37
 
36
38
  return true;
37
39
  },
38
- reference: (_unused, value, message) => {
39
- if (!value) {
40
- return true;
41
- }
40
+ reference: function () {
41
+ var _reference = _asyncToGenerator(function* (_unused, value, message, context) {
42
+ if (!value) {
43
+ return true;
44
+ }
42
45
 
43
- if (typeof value._ref !== 'string') {
44
- return message || 'Must be a reference to a document';
45
- }
46
+ if (!(0, _types.isReference)(value)) {
47
+ return message || 'Must be a reference to a document';
48
+ }
46
49
 
47
- return true;
48
- },
49
- block: function () {
50
- var _block = _asyncToGenerator(function* (validateBlock, value, message, context) {
51
- var result = yield validateBlock(value, context);
50
+ var type = context.type,
51
+ getDocumentExists = context.getDocumentExists;
52
52
 
53
- if (typeof result === 'string') {
54
- return message || result;
53
+ if (!type) {
54
+ throw new Error("`type` was not provided in validation context");
55
55
  }
56
56
 
57
- return result;
57
+ if ('weak' in type && type.weak) {
58
+ return true;
59
+ }
60
+
61
+ if (!getDocumentExists) {
62
+ throw new Error("`getDocumentExists` was not provided in validation context");
63
+ }
64
+
65
+ var exists = yield getDocumentExists({
66
+ id: value._ref
67
+ });
68
+
69
+ if (!exists) {
70
+ return 'This reference must be published';
71
+ }
72
+
73
+ return true;
58
74
  });
59
75
 
60
- function block(_x, _x2, _x3, _x4) {
61
- return _block.apply(this, arguments);
76
+ function reference(_x, _x2, _x3, _x4) {
77
+ return _reference.apply(this, arguments);
62
78
  }
63
79
 
64
- return block;
80
+ return reference;
65
81
  }(),
66
82
  assetRequired: (flag, value, message) => {
67
83
  if (!value || !value.asset || !value.asset._ref) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/validation",
3
- "version": "2.21.12-shopify.7+43c0621a9",
3
+ "version": "2.22.3-reference-updates.78+f6f3095b1c",
4
4
  "description": "Validation and warning infrastructure for Sanity projects",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./dist/dts",
@@ -32,8 +32,8 @@
32
32
  "directory": "packages/@sanity/validation"
33
33
  },
34
34
  "devDependencies": {
35
- "@sanity/client": "2.21.12-shopify.7+43c0621a9",
36
- "@sanity/schema": "2.21.12-shopify.7+43c0621a9"
35
+ "@sanity/client": "2.22.3-reference-updates.78+f6f3095b1c",
36
+ "@sanity/schema": "2.22.3-reference-updates.78+f6f3095b1c"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@sanity/client": "^2.0.0"
@@ -44,9 +44,9 @@
44
44
  }
45
45
  },
46
46
  "dependencies": {
47
- "@sanity/types": "2.21.12-shopify.7+43c0621a9",
47
+ "@sanity/types": "2.22.3-reference-updates.78+f6f3095b1c",
48
48
  "date-fns": "^2.16.1",
49
49
  "lodash": "^4.17.15"
50
50
  },
51
- "gitHead": "43c0621a981601c64af5fec68da8cda9d4d6d4e7"
51
+ "gitHead": "f6f3095b1c482db616c36f54320e1d970fecea5a"
52
52
  }
package/src/Rule.ts CHANGED
@@ -4,13 +4,13 @@ import {
4
4
  RuleClass,
5
5
  CustomValidator,
6
6
  RuleSpecConstraint,
7
- BlockValidator,
8
7
  FieldRules,
9
8
  ValidationContext,
10
9
  RuleSpec,
11
10
  ValidationMarker,
12
11
  RuleTypeConstraint,
13
12
  Validator,
13
+ Block,
14
14
  } from '@sanity/types'
15
15
  import {cloneDeep, get} from 'lodash'
16
16
  import ValidationErrorClass from './ValidationError'
@@ -219,6 +219,13 @@ const Rule: RuleClass = class Rule implements IRule {
219
219
  return this.cloneWithRules([{flag: 'custom', constraint: fn as CustomValidator}])
220
220
  }
221
221
 
222
+ /**
223
+ * @deprecated use `Rule.custom` instead
224
+ */
225
+ block(fn: CustomValidator<Block>): Rule {
226
+ return this.cloneWithRules([{flag: 'custom', constraint: fn as CustomValidator}])
227
+ }
228
+
222
229
  min(len: number): Rule {
223
230
  return this.cloneWithRules([{flag: 'min', constraint: len}])
224
231
  }
@@ -336,10 +343,6 @@ const Rule: RuleClass = class Rule implements IRule {
336
343
  return this.cloneWithRules([{flag: 'reference'}])
337
344
  }
338
345
 
339
- block(blockValidator: BlockValidator): Rule {
340
- return this.cloneWithRules([{flag: 'block', constraint: blockValidator}])
341
- }
342
-
343
346
  fields(rules: FieldRules): Rule {
344
347
  if (this._type !== 'Object') {
345
348
  throw new Error('fields() can only be called on an object type')
@@ -1,6 +1,5 @@
1
1
  import {SchemaType, Rule, RuleTypeConstraint} from '@sanity/types'
2
2
  import RuleClass from '../Rule'
3
- import {blockValidator} from '../validators/blockValidator'
4
3
  import {slugValidator} from '../validators/slugValidator'
5
4
 
6
5
  const ruleConstraintTypes: {[P in Lowercase<RuleTypeConstraint>]: true} = {
@@ -54,7 +53,6 @@ function baseRuleReducer(inputRule: Rule, type: SchemaType) {
54
53
  if (type.name === 'slug') return baseRule.custom(slugValidator)
55
54
  if (type.name === 'reference') return baseRule.reference()
56
55
  if (type.name === 'email') return baseRule.email()
57
- if (type.name === 'block') return baseRule.block(blockValidator)
58
56
  return baseRule
59
57
  }
60
58
 
@@ -1,6 +1,6 @@
1
1
  /// <reference types="@sanity/types/parts" />
2
2
 
3
- import {Rule, SchemaType, SanityDocument, Schema, ArraySchemaType} from '@sanity/types'
3
+ import {Rule, SchemaType, SanityDocument, Schema, ArraySchemaType, Block} from '@sanity/types'
4
4
  import createSchema from 'part:@sanity/base/schema-creator'
5
5
  import validateDocument, {resolveTypeForArrayItem, validateItem} from './validateDocument'
6
6
  import convertToValidationMarker from './util/convertToValidationMarker'
@@ -149,14 +149,17 @@ describe('validateItem', () => {
149
149
  },
150
150
  ],
151
151
  },
152
- {name: 'registeredString', type: 'string', validation},
152
+ {name: 'registeredString', title: 'Registered String', type: 'string', validation},
153
153
  {
154
+ title: 'Registered Object Field',
154
155
  name: 'registeredObjectField',
155
156
  type: 'object',
156
157
  fields: [{name: 'foo', type: 'string', validation}],
157
158
  },
158
159
  ],
159
160
  })
161
+ // ensures there are no schema formatting issues
162
+ expect(schema._validation).toHaveLength(0)
160
163
 
161
164
  await expect(
162
165
  validateItem({
@@ -165,6 +168,7 @@ describe('validateItem', () => {
165
168
  path: [],
166
169
  parent: undefined,
167
170
  type: schema.get('testObj'),
171
+ getDocumentExists: undefined,
168
172
  })
169
173
  ).resolves.toMatchObject([
170
174
  {
@@ -228,6 +232,9 @@ describe('validateItem', () => {
228
232
  ],
229
233
  })
230
234
 
235
+ // ensures there are no schema formatting issues
236
+ expect(schema._validation).toHaveLength(0)
237
+
231
238
  await expect(
232
239
  validateItem({
233
240
  document: undefined,
@@ -235,6 +242,7 @@ describe('validateItem', () => {
235
242
  path: undefined,
236
243
  type: schema.get('testObj'),
237
244
  value: {foo: 5},
245
+ getDocumentExists: undefined,
238
246
  })
239
247
  ).resolves.toMatchObject([
240
248
  {
@@ -252,17 +260,232 @@ describe('validateItem', () => {
252
260
  ])
253
261
  })
254
262
 
263
+ it('runs nested validation for markDefs', async () => {
264
+ const linkValidationSpy = jest.fn(() => true as const)
265
+ const internalLinkSpy = jest.fn(() => 'mock invalid response')
266
+
267
+ const schema = createSchema({
268
+ types: [
269
+ {
270
+ name: 'post',
271
+ title: 'Post',
272
+ type: 'document',
273
+ fields: [
274
+ {name: 'title', type: 'string'},
275
+ {name: 'body', type: 'string'},
276
+ ],
277
+ },
278
+ {
279
+ name: 'registeredEditor',
280
+ type: 'object',
281
+ title: 'Registered Editor',
282
+ fields: [
283
+ {
284
+ name: 'editor',
285
+ type: 'array',
286
+ of: [
287
+ {
288
+ type: 'block',
289
+ marks: {
290
+ annotations: [
291
+ {
292
+ name: 'exampleAnnotation',
293
+ type: 'object',
294
+ fields: [{name: 'value', type: 'string'}],
295
+ },
296
+ ],
297
+ },
298
+ },
299
+ ],
300
+ },
301
+ ],
302
+ },
303
+ {
304
+ name: 'blockTest',
305
+ type: 'document',
306
+ title: 'blockTest',
307
+ fields: [
308
+ {
309
+ name: 'content',
310
+ title: 'Content',
311
+ type: 'array',
312
+ of: [
313
+ {
314
+ type: 'block',
315
+ marks: {
316
+ annotations: [
317
+ {
318
+ name: 'link',
319
+ type: 'object',
320
+ title: 'link',
321
+ fields: [{name: 'url', type: 'url'}],
322
+ validation: (rule: Rule) => rule.custom(linkValidationSpy),
323
+ },
324
+ {
325
+ name: 'internalLink',
326
+ type: 'object',
327
+ title: 'Internal link',
328
+ fields: [{name: 'reference', type: 'reference', to: [{type: 'post'}]}],
329
+ validation: (rule: Rule) => rule.custom(internalLinkSpy),
330
+ },
331
+ {name: 'nestedEditor', type: 'registeredEditor'},
332
+ ],
333
+ },
334
+ },
335
+ ],
336
+ },
337
+ ],
338
+ },
339
+ ],
340
+ })
341
+
342
+ expect(schema._validation).toHaveLength(0)
343
+
344
+ const nestedBlock: Block = {
345
+ _type: 'block',
346
+ _key: 'nested-block-key',
347
+ children: [
348
+ {
349
+ _key: 'some-key',
350
+ _type: 'span',
351
+ text: 'hey',
352
+ marks: ['example-annotation-key'],
353
+ },
354
+ ],
355
+ markDefs: [
356
+ {
357
+ _type: 'exampleAnnotation',
358
+ _key: 'example-annotation-key',
359
+ value: 5,
360
+ },
361
+ ],
362
+ style: 'normal',
363
+ }
364
+
365
+ const block: Block = {
366
+ _key: 'block-key',
367
+ _type: 'block',
368
+ children: [
369
+ {
370
+ _key: 'child-0',
371
+ _type: 'span',
372
+ marks: ['0', '1', '2'],
373
+ text: 'hey',
374
+ },
375
+ ],
376
+ markDefs: [
377
+ {
378
+ _key: '0',
379
+ _type: 'link',
380
+ url: 'https://example.com',
381
+ },
382
+ {
383
+ _key: '1',
384
+ _type: 'internalLink',
385
+ _ref: 'post-id',
386
+ },
387
+ {
388
+ _key: '2',
389
+ _type: 'nestedEditor',
390
+ editor: [nestedBlock],
391
+ },
392
+ ],
393
+ style: 'normal',
394
+ }
395
+
396
+ const document: SanityDocument = {
397
+ _id: 'mock-id',
398
+ _type: 'blockTest',
399
+ _createdAt: '2021-11-15T21:06:41.812Z',
400
+ _rev: 'example-ref',
401
+ _updatedAt: '2021-11-15T21:06:41.812Z',
402
+ content: [block],
403
+ }
404
+
405
+ const result = await validateItem({
406
+ document: document,
407
+ parent: undefined,
408
+ path: [],
409
+ type: schema.get('blockTest'),
410
+ value: document,
411
+ getDocumentExists: undefined,
412
+ })
413
+
414
+ expect(result).toMatchObject([
415
+ {
416
+ item: {message: 'mock invalid response', paths: []},
417
+ level: 'error',
418
+ path: ['content', {_key: 'block-key'}, 'markDefs', {_key: '1'}],
419
+ type: 'validation',
420
+ },
421
+ // this tests for nested markDef validation
422
+ {
423
+ item: {
424
+ message: 'Expected type "String", got "Number"',
425
+ paths: [],
426
+ },
427
+ level: 'error',
428
+ path: [
429
+ 'content',
430
+ {_key: 'block-key'},
431
+ 'markDefs',
432
+ {_key: '2'},
433
+ 'editor',
434
+ {_key: 'nested-block-key'},
435
+ 'markDefs',
436
+ {_key: 'example-annotation-key'},
437
+ 'value',
438
+ ],
439
+ type: 'validation',
440
+ },
441
+ ])
442
+
443
+ expect(linkValidationSpy.mock.calls).toMatchObject([
444
+ [
445
+ {
446
+ _key: '0',
447
+ _type: 'link',
448
+ url: 'https://example.com',
449
+ },
450
+ {
451
+ document: {_id: 'mock-id'},
452
+ parent: {_key: 'block-key'},
453
+ path: ['content', {_key: 'block-key'}, 'markDefs', {_key: '0'}],
454
+ type: {name: 'link'},
455
+ },
456
+ ],
457
+ ])
458
+
459
+ expect(internalLinkSpy.mock.calls).toMatchObject([
460
+ [
461
+ {
462
+ _key: '1',
463
+ _ref: 'post-id',
464
+ _type: 'internalLink',
465
+ },
466
+ {
467
+ document: {_id: 'mock-id'},
468
+ parent: {_key: 'block-key'},
469
+ path: ['content', {_key: 'block-key'}, 'markDefs', {_key: '1'}],
470
+ type: {name: 'internalLink'},
471
+ },
472
+ ],
473
+ ])
474
+ })
475
+
255
476
  it('resolves an array item type if there is just one type', async () => {
256
477
  const schema = createSchema({
257
478
  types: [
258
479
  {
259
480
  name: 'values',
481
+ title: 'Values',
260
482
  type: 'array',
261
483
  // note that there is only one type available
262
484
  of: [{type: 'arrayItem'}],
263
485
  validation: (rule: Rule) => rule.required(),
264
486
  },
265
487
  {
488
+ title: 'Array Item',
266
489
  name: 'arrayItem',
267
490
  type: 'object',
268
491
  fields: [{name: 'title', type: 'string'}],
@@ -270,6 +493,9 @@ describe('validateItem', () => {
270
493
  ],
271
494
  })
272
495
 
496
+ // ensures there are no schema formatting issues
497
+ expect(schema._validation).toHaveLength(0)
498
+
273
499
  const values = [
274
500
  {
275
501
  // note how this doesn't have a _type
@@ -285,6 +511,7 @@ describe('validateItem', () => {
285
511
  path: [],
286
512
  type: schema.get('values'),
287
513
  value: values,
514
+ getDocumentExists: undefined,
288
515
  })
289
516
  ).resolves.toEqual([
290
517
  {
@@ -305,6 +532,7 @@ describe('validateItem', () => {
305
532
  {
306
533
  name: 'root',
307
534
  type: 'object',
535
+ title: 'Root',
308
536
  fields: [
309
537
  {
310
538
  name: 'level1Object',
@@ -341,6 +569,10 @@ describe('validateItem', () => {
341
569
  ],
342
570
  })
343
571
 
572
+ // ensures there are no schema formatting issues
573
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
574
+ expect((schema as any)._validation).toHaveLength(0)
575
+
344
576
  const value = {
345
577
  level1Object: {level3String: 'a string'},
346
578
  level1Array: [{level2Number: 5}],
@@ -377,6 +609,7 @@ describe('validateItem', () => {
377
609
  document,
378
610
  parent: document,
379
611
  path: undefined,
612
+ getDocumentExists: undefined,
380
613
  })
381
614
  ).resolves.toMatchObject([
382
615
  {
@@ -517,6 +750,7 @@ describe('validateItem', () => {
517
750
  parent: undefined,
518
751
  path: undefined,
519
752
  type: schema.get('root'),
753
+ getDocumentExists: undefined,
520
754
  })
521
755
 
522
756
  // after `validateItem(...)` has been initiated, all of the custom
@@ -6,6 +6,9 @@ import {
6
6
  ValidationMarker,
7
7
  isKeyedObject,
8
8
  isTypedObject,
9
+ isBlock,
10
+ isBlockSchemaType,
11
+ isSpanSchemaType,
9
12
  } from '@sanity/types'
10
13
  import {uniqBy} from 'lodash'
11
14
  import typeString from './util/typeString'
@@ -45,7 +48,8 @@ export function resolveTypeForArrayItem(
45
48
 
46
49
  export default async function validateDocument(
47
50
  doc: SanityDocument,
48
- schema: Schema
51
+ schema: Schema,
52
+ context?: Pick<ValidationContext, 'getDocumentExists'>
49
53
  ): Promise<ValidationMarker[]> {
50
54
  const documentType = schema.get(doc._type)
51
55
  if (!documentType) {
@@ -60,6 +64,7 @@ export default async function validateDocument(
60
64
  path: [],
61
65
  document: doc,
62
66
  type: documentType,
67
+ getDocumentExists: context?.getDocumentExists,
63
68
  })
64
69
  } catch (err) {
65
70
  console.error(err)
@@ -180,9 +185,39 @@ export async function validateItem({
180
185
  )
181
186
  }
182
187
 
188
+ // markDefs also do no run nested validation if the parent object is undefined
189
+ // for a similar reason to arrays
190
+ const shouldRunNestedValidationForMarkDefs =
191
+ isBlock(value) && value.markDefs.length && isBlockSchemaType(type)
192
+
193
+ if (shouldRunNestedValidationForMarkDefs) {
194
+ const [spanChildrenField] = type.fields
195
+ const spanType = spanChildrenField.type.of.find(isSpanSchemaType)
196
+
197
+ const annotations = (spanType?.annotations || []).reduce<Map<string, SchemaType>>(
198
+ (map, annotationType) => {
199
+ map.set(annotationType.name, annotationType)
200
+ return map
201
+ },
202
+ new Map()
203
+ )
204
+
205
+ nestedChecks = nestedChecks.concat(
206
+ value.markDefs.map((markDef) =>
207
+ validateItem({
208
+ ...restOfContext,
209
+ parent: value,
210
+ value: markDef,
211
+ path: path.concat(['markDefs', {_key: markDef._key}]),
212
+ type: annotations.get(markDef._type),
213
+ })
214
+ )
215
+ )
216
+ }
217
+
183
218
  const results = (await Promise.all([...selfChecks, ...nestedChecks])).flat()
184
219
 
185
- // run `dedupeValidationMarkers` if `_fieldRules` are present because they can
220
+ // run `uniqBy` if `_fieldRules` are present because they can
186
221
  // cause repeat markers
187
222
  if (rules.some((rule) => rule._fieldRules)) {
188
223
  return uniqBy(results, (rule) => JSON.stringify(rule))
@@ -1,4 +1,4 @@
1
- import {Validators} from '@sanity/types'
1
+ import {Validators, isReference} from '@sanity/types'
2
2
  import genericValidator from './genericValidator'
3
3
 
4
4
  const metaKeys = ['_key', '_type', '_weak']
@@ -20,25 +20,35 @@ const objectValidators: Validators = {
20
20
  return true
21
21
  },
22
22
 
23
- reference: (_unused, value, message) => {
23
+ reference: async (_unused, value: unknown, message, context) => {
24
24
  if (!value) {
25
25
  return true
26
26
  }
27
27
 
28
- if (typeof value._ref !== 'string') {
28
+ if (!isReference(value)) {
29
29
  return message || 'Must be a reference to a document'
30
30
  }
31
31
 
32
- return true
33
- },
32
+ const {type, getDocumentExists} = context
33
+
34
+ if (!type) {
35
+ throw new Error(`\`type\` was not provided in validation context`)
36
+ }
37
+
38
+ if ('weak' in type && type.weak) {
39
+ return true
40
+ }
34
41
 
35
- block: async (validateBlock, value, message, context) => {
36
- const result = await validateBlock(value, context)
37
- if (typeof result === 'string') {
38
- return message || result
42
+ if (!getDocumentExists) {
43
+ throw new Error(`\`getDocumentExists\` was not provided in validation context`)
39
44
  }
40
45
 
41
- return result
46
+ const exists = await getDocumentExists({id: value._ref})
47
+ if (!exists) {
48
+ return 'This reference must be published'
49
+ }
50
+
51
+ return true
42
52
  },
43
53
 
44
54
  assetRequired: (flag, value, message) => {
@@ -1,3 +0,0 @@
1
- import { BlockValidator } from '@sanity/types';
2
- export declare const blockValidator: BlockValidator;
3
- //# sourceMappingURL=blockValidator.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"blockValidator.d.ts","sourceRoot":"","sources":["../../../src/validators/blockValidator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,cAAc,EAAC,MAAM,eAAe,CAAA;AAG9D,eAAO,MAAM,cAAc,EAAE,cAkD5B,CAAA"}
@@ -1,65 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.blockValidator = void 0;
7
-
8
- var _validateDocument = require("../validateDocument");
9
-
10
- function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
11
-
12
- function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
13
-
14
- var blockValidator = /*#__PURE__*/function () {
15
- var _ref = _asyncToGenerator(function* (value, context) {
16
- var _type$fields, _type$fields$find;
17
-
18
- if (value.markDefs.length === 0) return [];
19
- var type = context.type;
20
-
21
- if (!type) {
22
- throw new Error("Schema `type` was not provided in validation context");
23
- }
24
-
25
- if (type.jsonType !== 'object') {
26
- throw new Error("Expected schema type with jsonType `object` but found `".concat(type.jsonType, "` instead."));
27
- }
28
-
29
- var childrenType = (_type$fields = type.fields) === null || _type$fields === void 0 ? void 0 : (_type$fields$find = _type$fields.find(field => field.name === 'children')) === null || _type$fields$find === void 0 ? void 0 : _type$fields$find.type;
30
- var spanType = (childrenType === null || childrenType === void 0 ? void 0 : childrenType.jsonType) === 'array' ? childrenType === null || childrenType === void 0 ? void 0 : childrenType.of.find(ofType => ofType.name === 'span') : null; // Validate every markDef (annotation) value
31
- // eslint-disable-next-line no-warning-comments
32
- // @ts-expect-error TODO (eventually): fix these types
33
-
34
- var activeAnnotationTypes = spanType === null || spanType === void 0 ? void 0 : spanType.annotations.filter(annotation => // eslint-disable-next-line no-warning-comments
35
- // @ts-expect-error TODO (eventually): fix these types
36
- value.markDefs.map(def => def._type).includes(annotation.name));
37
- var annotationValidations = [];
38
- value.markDefs.forEach(markDef => {
39
- var annotationType = activeAnnotationTypes.find(aType => aType.name === markDef._type);
40
- var validations = (0, _validateDocument.validateItem)({
41
- document: context.document,
42
- value: markDef,
43
- type: annotationType,
44
- path: (context.path || []).concat(['markDefs', {
45
- _key: markDef._key
46
- }]),
47
- parent: value
48
- });
49
- annotationValidations.push(validations);
50
- });
51
- var results = yield Promise.all(annotationValidations);
52
- var flattened = results.flat();
53
- if (!flattened.length) return [];
54
- return flattened.map(res => {
55
- res.item.paths = [res.path];
56
- return res.item;
57
- });
58
- });
59
-
60
- return function blockValidator(_x, _x2) {
61
- return _ref.apply(this, arguments);
62
- };
63
- }();
64
-
65
- exports.blockValidator = blockValidator;
@@ -1,54 +0,0 @@
1
- import {ValidationMarker, BlockValidator} from '@sanity/types'
2
- import {validateItem} from '../validateDocument'
3
-
4
- export const blockValidator: BlockValidator = async (value, context) => {
5
- if (value.markDefs.length === 0) return []
6
-
7
- const {type} = context
8
-
9
- if (!type) {
10
- throw new Error(`Schema \`type\` was not provided in validation context`)
11
- }
12
- if (type.jsonType !== 'object') {
13
- throw new Error(
14
- `Expected schema type with jsonType \`object\` but found \`${type.jsonType}\` instead.`
15
- )
16
- }
17
-
18
- const childrenType = type.fields?.find((field) => field.name === 'children')?.type
19
- const spanType =
20
- childrenType?.jsonType === 'array'
21
- ? childrenType?.of.find((ofType) => ofType.name === 'span')
22
- : null
23
-
24
- // Validate every markDef (annotation) value
25
- // eslint-disable-next-line no-warning-comments
26
- // @ts-expect-error TODO (eventually): fix these types
27
- const activeAnnotationTypes: any[] = spanType?.annotations.filter((annotation) =>
28
- // eslint-disable-next-line no-warning-comments
29
- // @ts-expect-error TODO (eventually): fix these types
30
- value.markDefs.map((def) => def._type).includes(annotation.name)
31
- )
32
- const annotationValidations: Promise<ValidationMarker[]>[] = []
33
- value.markDefs.forEach((markDef: any) => {
34
- const annotationType = activeAnnotationTypes.find((aType) => aType.name === markDef._type)
35
- const validations = validateItem({
36
- document: context.document,
37
- value: markDef,
38
- type: annotationType,
39
- path: (context.path || []).concat(['markDefs', {_key: markDef._key}]),
40
- parent: value,
41
- })
42
- annotationValidations.push(validations)
43
- })
44
-
45
- const results = await Promise.all(annotationValidations)
46
- const flattened = results.flat()
47
-
48
- if (!flattened.length) return []
49
-
50
- return flattened.map((res) => {
51
- res.item.paths = [res.path]
52
- return res.item
53
- })
54
- }