zapier-platform-schema 18.3.0 → 18.4.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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "18.3.0",
2
+ "version": "18.4.0",
3
3
  "schemas": {
4
4
  "AppSchema": {
5
5
  "id": "/AppSchema",
@@ -202,7 +202,10 @@
202
202
  "oneOf": [
203
203
  {
204
204
  "type": "object",
205
- "minProperties": 1
205
+ "minProperties": 1,
206
+ "not": {
207
+ "required": ["perform"]
208
+ }
206
209
  },
207
210
  {
208
211
  "type": "array",
@@ -252,7 +255,8 @@
252
255
  "file",
253
256
  "password",
254
257
  "copy",
255
- "code"
258
+ "code",
259
+ "json"
256
260
  ]
257
261
  },
258
262
  "required": {
@@ -698,6 +702,12 @@
698
702
  "minLength": 2,
699
703
  "pattern": "^[a-zA-Z]+[a-zA-Z0-9_]*$"
700
704
  },
705
+ "JsonSchemaSchema": {
706
+ "id": "/JsonSchemaSchema",
707
+ "description": "A JSON Schema object that describes the expected structure of a JSON value. Validated against JSON Schema Draft 4, 6, or 7 meta-schema (based on the `$schema` field, defaulting to Draft 7) via the validateJsonFieldSchema functional constraint.",
708
+ "type": "object",
709
+ "additionalProperties": true
710
+ },
701
711
  "PlainInputFieldSchema": {
702
712
  "description": "Field schema specialized for input fields. In addition to the requirements below, the following keys are mutually exclusive:\n\n* `children` & `list`\n* `children` & `dict`\n* `children` & `type`\n* `children` & `placeholder`\n* `children` & `helpText`\n* `children` & `default`\n* `dict` & `list`\n* `dynamic` & `dict`\n* `dynamic` & `choices`",
703
713
  "id": "/PlainInputFieldSchema",
@@ -727,7 +737,8 @@
727
737
  "file",
728
738
  "password",
729
739
  "copy",
730
- "code"
740
+ "code",
741
+ "json"
731
742
  ]
732
743
  },
733
744
  "required": {
@@ -818,6 +829,10 @@
818
829
  "group": {
819
830
  "description": "References a group key from the operation's inputFieldGroups to organize this field with others.",
820
831
  "$ref": "/KeySchema"
832
+ },
833
+ "schema": {
834
+ "description": "A JSON Schema object that describes the expected structure of the JSON value. Only valid when `type` is `json`.",
835
+ "$ref": "/JsonSchemaSchema"
821
836
  }
822
837
  },
823
838
  "additionalProperties": false
@@ -2192,7 +2207,7 @@
2192
2207
  "type": "boolean"
2193
2208
  },
2194
2209
  "skipThrowForStatus": {
2195
- "description": "Starting in `core` version `10.0.0`, `response.throwForStatus()` was called by default. We introduced a per-request way to opt-out of this behavior. This flag takes that a step further and controls that behavior integration-wide **for requests made using `z.request()`**. Unless they specify otherwise (per-request, or via middleware), [Shorthand requests](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#shorthand-http-requests) _always_ call `throwForStatus()`. `z.request()` calls can also ignore this flag if they set `skipThrowForStatus` directly",
2210
+ "description": "Starting in `core` version `10.0.0`, `response.throwForStatus()` was called by default. We introduced a per-request way to opt-out of this behavior. This flag takes that a step further and controls that behavior integration-wide **for requests made using `z.request()`**. Unless they specify otherwise (per-request, or via middleware), [Shorthand requests](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#shorthand-http-requests) _always_ call `throwForStatus()`. `z.request()` calls can also ignore this flag if they set `skipThrowForStatus` directly. It is important to note that for oauth2 or session auths with `authRefresh:true`, `401` status codes will throw a `RefreshAuthError` regardless of `skipThrowForStatus`, and will need to be handled manually if intervention is required.",
2196
2211
  "type": "boolean"
2197
2212
  },
2198
2213
  "throwForThrottlingEarly": {
@@ -23,6 +23,7 @@ const checks = [
23
23
  require('./pollingThrottle'),
24
24
  require('./AuthFieldisSafe'),
25
25
  require('./inputFieldGroupsConstraints'),
26
+ require('./validateJsonFieldSchema'),
26
27
  ];
27
28
 
28
29
  const runFunctionalConstraints = (definition, mainSchema) => {
@@ -0,0 +1,254 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const jsonschema = require('jsonschema');
5
+
6
+ const JSON_SCHEMA_SCHEMA_ID = '/JsonSchemaSchema';
7
+ const PLAIN_INPUT_FIELD_SCHEMA_ID = '/PlainInputFieldSchema';
8
+
9
+ const actionTypes = ['triggers', 'searches', 'creates', 'bulkReads'];
10
+ const resourceMethods = ['get', 'list', 'hook', 'search', 'create'];
11
+
12
+ // Load supported JSON Schema meta-schemas
13
+ const draft4MetaSchema = require('json-metaschema/draft-04-schema.json');
14
+ const draft6MetaSchema = require('json-metaschema/draft-06-schema.json');
15
+ const draft7MetaSchema = require('json-metaschema/draft-07-schema.json');
16
+
17
+ // Map of supported $schema URIs (without trailing #) to their meta-schemas
18
+ // HTTPS supported but normalized below
19
+ const SUPPORTED_META_SCHEMAS = {
20
+ 'http://json-schema.org/draft-04/schema': draft4MetaSchema,
21
+ 'http://json-schema.org/draft-06/schema': draft6MetaSchema,
22
+ 'http://json-schema.org/draft-07/schema': draft7MetaSchema,
23
+ };
24
+
25
+ const metaValidator = new jsonschema.Validator();
26
+
27
+ // Translates jsonschema ValidationError from meta-schema validation
28
+ // into a human-readable error message.
29
+ const formatMetaSchemaError = (error, rootPath) => {
30
+ const ALLOWED_TYPE_NAMES = [
31
+ 'object',
32
+ 'array',
33
+ 'string',
34
+ 'number',
35
+ 'integer',
36
+ 'boolean',
37
+ 'null',
38
+ ];
39
+ const relativePath = error.property.replace(/^instance\.?/, '');
40
+ const fullPath = relativePath ? `${rootPath}.${relativePath}` : rootPath;
41
+
42
+ // "type" field: invalid JSON Schema type value
43
+ if (/\.type$/.test(error.property) || error.property === 'instance.type') {
44
+ if (error.name === 'anyOf') {
45
+ const value = error.instance;
46
+ if (typeof value === 'string') {
47
+ return `${fullPath}: invalid type "${value}". Must be one of: ${ALLOWED_TYPE_NAMES.join(', ')}`;
48
+ }
49
+ if (Array.isArray(value)) {
50
+ const invalid = value.filter(
51
+ (t) => typeof t !== 'string' || !ALLOWED_TYPE_NAMES.includes(t),
52
+ );
53
+ if (invalid.length > 0) {
54
+ return invalid
55
+ .map(
56
+ (t) =>
57
+ `${fullPath}: invalid type "${t}". Must be one of: ${ALLOWED_TYPE_NAMES.join(', ')}`,
58
+ )
59
+ .join('; ');
60
+ }
61
+ return `${fullPath}: must be a string or array of strings`;
62
+ }
63
+ return `${fullPath}: must be a string or array of strings`;
64
+ }
65
+ }
66
+
67
+ // Non-object where a JSON Schema object is expected
68
+ // Draft 7 allows boolean schemas, so argument may be ['object', 'boolean']
69
+ if (
70
+ error.name === 'type' &&
71
+ Array.isArray(error.argument) &&
72
+ error.argument.includes('object')
73
+ ) {
74
+ return `${fullPath}: must be a valid JSON Schema object`;
75
+ }
76
+
77
+ // Non-array where an array is expected (required, enum, allOf, etc.)
78
+ if (error.name === 'type' && _.isEqual(error.argument, ['array'])) {
79
+ const fieldName = fullPath.split('.').pop();
80
+ return `${fieldName}: must be an array`;
81
+ }
82
+
83
+ // anyOf failure for fields expecting a schema or specific structure
84
+ if (error.name === 'anyOf') {
85
+ const value = error.instance;
86
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
87
+ return `${fullPath}: must be a valid JSON Schema object`;
88
+ }
89
+ return `${fullPath}: must be a valid JSON Schema`;
90
+ }
91
+
92
+ // Default fallback
93
+ return `${fullPath}: ${error.message}`;
94
+ };
95
+
96
+ // Resolves which meta-schema to validate against based on the $schema field.
97
+ // Returns { metaSchema, error } where error is a string if $schema is unsupported.
98
+ const resolveMetaSchema = (schema) => {
99
+ if (!schema.$schema) {
100
+ return { metaSchema: draft7MetaSchema, error: null };
101
+ }
102
+
103
+ if (typeof schema.$schema !== 'string') {
104
+ return { metaSchema: null, error: '`$schema` must be a string' };
105
+ }
106
+
107
+ const normalizedUri = schema.$schema
108
+ .replace(/#$/, '')
109
+ .replace(/^https:/, 'http:');
110
+ const metaSchema = SUPPORTED_META_SCHEMAS[normalizedUri];
111
+
112
+ if (!metaSchema) {
113
+ const supported = Object.keys(SUPPORTED_META_SCHEMAS).join(', ');
114
+ return {
115
+ metaSchema: null,
116
+ error: `unsupported JSON Schema version "${schema.$schema}". Supported versions: ${supported}`,
117
+ };
118
+ }
119
+
120
+ return { metaSchema, error: null };
121
+ };
122
+
123
+ // Validates that an object is a structurally valid JSON schema
124
+ // using the appropriate meta-schema via jsonschema library.
125
+ // Returns an array of error message strings
126
+ const collectSchemaErrors = (schema, rootPath) => {
127
+ if (typeof schema !== 'object' || schema === null || Array.isArray(schema)) {
128
+ return [`${rootPath}: must be a valid JSON Schema object`];
129
+ }
130
+
131
+ const { metaSchema, error } = resolveMetaSchema(schema);
132
+ if (error) {
133
+ return [`${rootPath}: ${error}`];
134
+ }
135
+
136
+ const result = metaValidator.validate(schema, metaSchema);
137
+ return result.errors.map((err) => formatMetaSchemaError(err, rootPath));
138
+ };
139
+
140
+ const checkSchemaField = (field, path) => {
141
+ let errors = [];
142
+
143
+ if (_.has(field, 'schema')) {
144
+ if (field.type !== 'json') {
145
+ errors.push(
146
+ new jsonschema.ValidationError(
147
+ 'must have `type` set to `json` when `schema` is provided.',
148
+ field,
149
+ '/PlainInputFieldSchema',
150
+ path,
151
+ 'invalidSchema',
152
+ 'schema',
153
+ ),
154
+ );
155
+ } else {
156
+ // Root schema type must be object or array, not primitives
157
+ const rootType = field.schema.type;
158
+ if (rootType !== undefined) {
159
+ const types = Array.isArray(rootType) ? rootType : [rootType];
160
+ const invalidTypes = types.filter(
161
+ (t) => t !== 'object' && t !== 'array',
162
+ );
163
+ if (invalidTypes.length > 0) {
164
+ errors.push(
165
+ new jsonschema.ValidationError(
166
+ `has an invalid JSON Schema in \`schema\`: schema: root \`type\` must be "object" or "array", got ${invalidTypes.map((t) => `"${t}"`).join(', ')}`,
167
+ field,
168
+ '/PlainInputFieldSchema',
169
+ path,
170
+ 'invalidJsonSchema',
171
+ 'schema',
172
+ ),
173
+ );
174
+ // Skip meta-schema validation if root type is invalid
175
+ return errors;
176
+ }
177
+ }
178
+
179
+ const schemaErrors = collectSchemaErrors(field.schema, 'schema');
180
+ schemaErrors.forEach((message) => {
181
+ errors.push(
182
+ new jsonschema.ValidationError(
183
+ `has an invalid JSON Schema in \`schema\`: ${message}`,
184
+ field,
185
+ '/PlainInputFieldSchema',
186
+ path,
187
+ 'invalidJsonSchema',
188
+ 'schema',
189
+ ),
190
+ );
191
+ });
192
+ }
193
+ }
194
+
195
+ // Recurse into children (nested PlainInputFieldSchema)
196
+ (field.children || []).forEach((child, childIndex) => {
197
+ errors = errors.concat(
198
+ checkSchemaField(child, `${path}.children[${childIndex}]`),
199
+ );
200
+ });
201
+
202
+ return errors;
203
+ };
204
+
205
+ const validateJsonFieldSchema = (definition, mainSchema) => {
206
+ let errors = [];
207
+
208
+ // Handle individual field validation (for auto-tests of examples/antiExamples)
209
+ if (mainSchema.id === PLAIN_INPUT_FIELD_SCHEMA_ID) {
210
+ return checkSchemaField(definition, 'instance');
211
+ } else if (mainSchema.id === JSON_SCHEMA_SCHEMA_ID) {
212
+ return checkSchemaField(
213
+ // Make a fake PlainInputField object so checkSchemaField detects a json type and `schema` property
214
+ { key: 'placeholder_field_key', type: 'json', schema: definition },
215
+ 'instance',
216
+ );
217
+ }
218
+
219
+ // Handle full app definition validation
220
+ _.each(actionTypes, (actionType) => {
221
+ if (definition[actionType]) {
222
+ _.each(definition[actionType], (actionDef) => {
223
+ if (actionDef.operation && actionDef.operation.inputFields) {
224
+ _.each(actionDef.operation.inputFields, (field, index) => {
225
+ const path = `instance.${actionType}.${actionDef.key}.operation.inputFields[${index}]`;
226
+ errors = errors.concat(checkSchemaField(field, path));
227
+ });
228
+ }
229
+ });
230
+ }
231
+ });
232
+
233
+ // Handle resources if they exist
234
+ if (definition.resources) {
235
+ _.each(definition.resources, (resource, resourceKey) => {
236
+ resourceMethods.forEach((method) => {
237
+ if (
238
+ resource[method] &&
239
+ resource[method].operation &&
240
+ resource[method].operation.inputFields
241
+ ) {
242
+ _.each(resource[method].operation.inputFields, (field, index) => {
243
+ const path = `instance.resources.${resourceKey}.${method}.operation.inputFields[${index}]`;
244
+ errors = errors.concat(checkSchemaField(field, path));
245
+ });
246
+ }
247
+ });
248
+ });
249
+ }
250
+
251
+ return errors;
252
+ };
253
+
254
+ module.exports = validateJsonFieldSchema;
@@ -14,7 +14,7 @@ module.exports = makeSchema({
14
14
  },
15
15
  skipThrowForStatus: {
16
16
  description:
17
- 'Starting in `core` version `10.0.0`, `response.throwForStatus()` was called by default. We introduced a per-request way to opt-out of this behavior. This flag takes that a step further and controls that behavior integration-wide **for requests made using `z.request()`**. Unless they specify otherwise (per-request, or via middleware), [Shorthand requests](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#shorthand-http-requests) _always_ call `throwForStatus()`. `z.request()` calls can also ignore this flag if they set `skipThrowForStatus` directly',
17
+ 'Starting in `core` version `10.0.0`, `response.throwForStatus()` was called by default. We introduced a per-request way to opt-out of this behavior. This flag takes that a step further and controls that behavior integration-wide **for requests made using `z.request()`**. Unless they specify otherwise (per-request, or via middleware), [Shorthand requests](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#shorthand-http-requests) _always_ call `throwForStatus()`. `z.request()` calls can also ignore this flag if they set `skipThrowForStatus` directly. It is important to note that for oauth2 or session auths with `authRefresh:true`, `401` status codes will throw a `RefreshAuthError` regardless of `skipThrowForStatus`, and will need to be handled manually if intervention is required.',
18
18
  type: 'boolean',
19
19
  },
20
20
  throwForThrottlingEarly: {
@@ -13,6 +13,7 @@ module.exports = makeSchema(
13
13
  {
14
14
  type: 'object',
15
15
  minProperties: 1,
16
+ not: { required: ['perform'] },
16
17
  },
17
18
  {
18
19
  type: 'array',
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ const makeSchema = require('../utils/makeSchema');
4
+
5
+ module.exports = makeSchema({
6
+ id: '/JsonSchemaSchema',
7
+ description:
8
+ 'A JSON Schema object that describes the expected structure of a JSON value. Validated against JSON Schema Draft 4, 6, or 7 meta-schema (based on the `$schema` field, defaulting to Draft 7) via the validateJsonFieldSchema functional constraint.',
9
+ type: 'object',
10
+ additionalProperties: true,
11
+ examples: [
12
+ { type: 'object', properties: { name: { type: 'string' } } },
13
+ { type: 'array', items: { type: 'string' } },
14
+ {},
15
+ {
16
+ allOf: [{ type: 'object' }, { properties: { name: { type: 'string' } } }],
17
+ not: { type: 'array' },
18
+ },
19
+ {
20
+ type: 'object',
21
+ properties: {
22
+ name: { type: 'string' },
23
+ age: { type: 'integer' },
24
+ },
25
+ required: ['name'],
26
+ additionalProperties: false,
27
+ },
28
+ {
29
+ $schema: 'http://json-schema.org/draft-07/schema#',
30
+ type: 'object',
31
+ properties: {
32
+ name: { type: 'string' },
33
+ },
34
+ },
35
+ ],
36
+ antiExamples: [
37
+ {
38
+ example: ['not', 'an', 'object'],
39
+ reason: 'JSON Schema must be an object, not an array',
40
+ },
41
+ {
42
+ example: { type: 'string' },
43
+ reason:
44
+ "JSON Schema type should be an object or an array. If a primitive is needed, use `type: 'string'` on the input field directly",
45
+ },
46
+ ],
47
+ });
@@ -50,6 +50,7 @@ module.exports = makeSchema({
50
50
  'password',
51
51
  'copy',
52
52
  'code',
53
+ 'json',
53
54
  ],
54
55
  },
55
56
  required: {
@@ -97,6 +98,9 @@ module.exports = makeSchema({
97
98
 
98
99
  // 6. A field with type=integer
99
100
  { key: 'abc_int', type: 'integer' },
101
+
102
+ // 7. A field with type=json
103
+ { key: 'abc_json', type: 'json' },
100
104
  ],
101
105
  antiExamples: [
102
106
  {
@@ -7,6 +7,7 @@ const FieldDynamicChoicesSchema = require('./FieldDynamicChoicesSchema');
7
7
  const PlainFieldSchema = require('./PlainFieldSchema');
8
8
  const FieldMetaSchema = require('./FieldMetaSchema');
9
9
  const KeySchema = require('./KeySchema');
10
+ const JsonSchemaSchema = require('./JsonSchemaSchema');
10
11
 
11
12
  module.exports = makeSchema(
12
13
  {
@@ -93,6 +94,11 @@ module.exports = makeSchema(
93
94
  "References a group key from the operation's inputFieldGroups to organize this field with others.",
94
95
  $ref: KeySchema.id,
95
96
  },
97
+ schema: {
98
+ description:
99
+ 'A JSON Schema object that describes the expected structure of the JSON value. Only valid when `type` is `json`.',
100
+ $ref: JsonSchemaSchema.id,
101
+ },
96
102
  },
97
103
  examples: [
98
104
  { key: 'abc' },
@@ -125,6 +131,16 @@ module.exports = makeSchema(
125
131
  key: 'email',
126
132
  group: 'contact',
127
133
  },
134
+ {
135
+ key: 'payload',
136
+ type: 'json',
137
+ schema: {
138
+ type: 'object',
139
+ properties: {
140
+ name: { type: 'string' },
141
+ },
142
+ },
143
+ },
128
144
  {
129
145
  key: 'spreadsheet',
130
146
  dependsOn: ['folder'],
@@ -171,7 +187,18 @@ module.exports = makeSchema(
171
187
  reason:
172
188
  'Invalid value for key: choices (must be either object or array)',
173
189
  },
174
-
190
+ {
191
+ example: { key: 'abc', type: 'string', schema: { type: 'object' } },
192
+ reason: 'schema is only valid when type is json',
193
+ },
194
+ {
195
+ example: {
196
+ key: 'abc',
197
+ type: 'json',
198
+ schema: { type: 'foobar' },
199
+ },
200
+ reason: 'schema must contain a valid JSON Schema (invalid type)',
201
+ },
175
202
  {
176
203
  example: { key: 'abc', children: ['$func$2$f$'] },
177
204
  reason:
@@ -187,5 +214,6 @@ module.exports = makeSchema(
187
214
  FieldMetaSchema,
188
215
  PlainFieldSchema,
189
216
  KeySchema,
217
+ JsonSchemaSchema,
190
218
  ],
191
219
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapier-platform-schema",
3
- "version": "18.3.0",
3
+ "version": "18.4.0",
4
4
  "description": "Schema definition for CLI apps in the Zapier Developer Platform.",
5
5
  "repository": "zapier/zapier-platform",
6
6
  "homepage": "https://platform.zapier.com/",
@@ -13,6 +13,7 @@
13
13
  "/schema.js"
14
14
  ],
15
15
  "dependencies": {
16
+ "json-metaschema": "1.3.0",
16
17
  "jsonschema": "1.5.0",
17
18
  "lodash": "4.17.23"
18
19
  },
@@ -26,7 +27,7 @@
26
27
  "preversion": "git pull && pnpm test && pnpm build",
27
28
  "test": "mocha -t 10s --recursive test --exit",
28
29
  "smoke-test": "mocha -t 10s --recursive smoke-test --exit",
29
- "test:debug": "mocha --recursive --inspect-brk test",
30
+ "test:debug": "mocha inspect --recursive test",
30
31
  "lint": "eslint lib",
31
32
  "lint:fix": "eslint --fix lib",
32
33
  "coverage": "istanbul cover _mocha -- --recursive",