zapier-platform-schema 12.1.0 → 12.2.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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "12.1.0",
2
+ "version": "12.2.1",
3
3
  "schemas": {
4
4
  "AppSchema": {
5
5
  "id": "/AppSchema",
@@ -67,6 +67,10 @@
67
67
  "description": "All the search-or-create combos for your app. You can create your own here, or Zapier will automatically register any from resources that define a search, a create, and a get (or define a searchOrCreate directly). Register non-resource search-or-creates here as well.",
68
68
  "$ref": "/SearchOrCreatesSchema"
69
69
  },
70
+ "searchAndCreates": {
71
+ "description": "An alias for \"searchOrCreates\".",
72
+ "$ref": "/SearchAndCreatesSchema"
73
+ },
70
74
  "flags": {
71
75
  "description": "Top-level app options",
72
76
  "$ref": "/AppFlagsSchema"
@@ -164,7 +168,7 @@
164
168
  "maxLength": 1000
165
169
  },
166
170
  "type": {
167
- "description": "The type of this value. Use `string` for basic text input, `text` for a large, `<textarea>` style box, and `code` for a `<textarea>` with a fixed-width font.",
171
+ "description": "The type of this value. Use `string` for basic text input, `text` for a large, `<textarea>` style box, and `code` for a `<textarea>` with a fixed-width font. Field type of `file` will accept either a file object or a string. If a URL is provided in the string, Zapier will automatically make a GET for that file. Otherwise, a .txt file will be generated.",
168
172
  "type": "string",
169
173
  "enum": [
170
174
  "string",
@@ -1319,10 +1323,33 @@
1319
1323
  "create": {
1320
1324
  "description": "The key of the create that powers this search-or-create",
1321
1325
  "$ref": "/KeySchema"
1326
+ },
1327
+ "update": {
1328
+ "description": "EXPERIMENTAL: The key of the update action (in `creates`) that will be used if a search succeeds.",
1329
+ "$ref": "/KeySchema"
1330
+ },
1331
+ "updateInputFromSearchOutput": {
1332
+ "description": "EXPERIMENTAL: A mapping where the key represents the input field for the update action, and the value represents the field from the search action's output that should be mapped to the update action's input field.",
1333
+ "$ref": "/FlatObjectSchema"
1334
+ },
1335
+ "searchUniqueInputToOutputConstraint": {
1336
+ "description": "EXPERIMENTAL: A mapping where the key represents an input field for the search action, and the value represents how that field's value will be used to filter down the search output for an exact match.",
1337
+ "type": "object"
1322
1338
  }
1323
1339
  },
1324
1340
  "additionalProperties": false
1325
1341
  },
1342
+ "SearchOrCreatesSchema": {
1343
+ "id": "/SearchOrCreatesSchema",
1344
+ "description": "Enumerates the search-or-creates your app has available for users.",
1345
+ "type": "object",
1346
+ "patternProperties": {
1347
+ "^[a-zA-Z]+[a-zA-Z0-9_]*$": {
1348
+ "description": "Any unique key can be used and its values will be validated against the SearchOrCreateSchema.",
1349
+ "$ref": "/SearchOrCreateSchema"
1350
+ }
1351
+ }
1352
+ },
1326
1353
  "AuthenticationSchema": {
1327
1354
  "id": "/AuthenticationSchema",
1328
1355
  "description": "Represents authentication schemes.",
@@ -1442,9 +1469,9 @@
1442
1469
  }
1443
1470
  }
1444
1471
  },
1445
- "SearchOrCreatesSchema": {
1446
- "id": "/SearchOrCreatesSchema",
1447
- "description": "Enumerates the search-or-creates your app has available for users.",
1472
+ "SearchAndCreatesSchema": {
1473
+ "id": "/SearchAndCreatesSchema",
1474
+ "description": "Alias for /SearchOrCreatesSchema",
1448
1475
  "type": "object",
1449
1476
  "patternProperties": {
1450
1477
  "^[a-zA-Z]+[a-zA-Z0-9_]*$": {
@@ -3,7 +3,7 @@
3
3
  /* Each check below is expected to return a list of ValidationSchema errors. An error is defined by:
4
4
  * new jsonschema.ValidationError(
5
5
  * message, // string that explains the problem, like 'must not have a URL that points to AWS'
6
- * instance, // the snippet of the app defintion that is invalid
6
+ * instance, // the snippet of the app definition that is invalid
7
7
  * schema, // name of the schema that failed, like '/TriggerSchema'
8
8
  * propertyPath, // stringified path to problematic snippet, like 'instance.triggers.find_contact'
9
9
  * name, // optional, the validation type that failed. Can make something up like 'invalidUrl'
@@ -18,6 +18,7 @@ const checks = [
18
18
  require('./matchingKeys'),
19
19
  require('./labelWhenVisible'),
20
20
  require('./uniqueInputFieldKeys'),
21
+ require('./searchAndCreatesAlias'),
21
22
  ];
22
23
 
23
24
  const runFunctionalConstraints = (definition, mainSchema) => {
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const jsonschema = require('jsonschema');
4
+
5
+ const searchAndCreatesAliasConstraint = (definition) => {
6
+ // Ensure that 'searchOrCreates' and 'searchAndCreates' don't appear together, since one is an alias of the other
7
+ const errors = [];
8
+ if (definition.searchOrCreates && definition.searchAndCreates) {
9
+ errors.push(
10
+ new jsonschema.ValidationError(
11
+ `should not be used at the same time as its alias, searchAndCreates`,
12
+ definition,
13
+ `/AppSchema`,
14
+ `instance.searchOrCreates`,
15
+ 'invalid',
16
+ 'key'
17
+ )
18
+ );
19
+ }
20
+
21
+ return errors;
22
+ };
23
+
24
+ module.exports = searchAndCreatesAliasConstraint;
@@ -3,20 +3,78 @@
3
3
  const _ = require('lodash');
4
4
  const jsonschema = require('jsonschema');
5
5
 
6
+ const getFieldKeys = (definition, path) => {
7
+ const fields = _.get(definition, path, []);
8
+ // Filter out any `undefined` values using .filter(), which may happen due to incoming inputFields
9
+ // containing functions instead of plain Objects.
10
+ return fields.map((field) => field.key).filter((key) => key);
11
+ };
12
+
13
+ // This method differs from 'getFieldKeys' since here we obtain the actual object keys with Object.keys()
14
+ const getSearchOutputSampleKeys = (definition, searchKey) => {
15
+ const searchOutputSampleFields = _.get(
16
+ definition.searches,
17
+ `${searchKey}.operation.sample`,
18
+ {}
19
+ );
20
+
21
+ return Object.keys(searchOutputSampleFields);
22
+ };
23
+
6
24
  const validateSearchOrCreateKeys = (definition) => {
7
- if (!definition.searchOrCreates) {
25
+ // searchAndCreates is an alias for searchOrCreates. Another functional constraint makes sure only one of them is defined.
26
+ const searchCreatesKey = definition.searchAndCreates
27
+ ? 'searchAndCreates'
28
+ : 'searchOrCreates';
29
+ const searchCreates = definition[searchCreatesKey];
30
+
31
+ if (!searchCreates) {
8
32
  return [];
9
33
  }
10
34
 
11
35
  const errors = [];
12
36
 
13
- const searchKeys = _.keys(definition.searches);
14
- const createKeys = _.keys(definition.creates);
37
+ const searchKeys = Object.keys(definition.searches);
38
+ const createKeys = Object.keys(definition.creates);
15
39
 
16
- _.each(definition.searchOrCreates, (searchOrCreateDef, key) => {
40
+ _.each(searchCreates, (searchOrCreateDef, key) => {
17
41
  const searchOrCreateKey = searchOrCreateDef.key;
18
42
  const searchKey = searchOrCreateDef.search;
19
43
  const createKey = searchOrCreateDef.create;
44
+ const updateKey = searchOrCreateDef.update;
45
+
46
+ const updateInputKeys = getFieldKeys(
47
+ definition.creates,
48
+ `${updateKey}.operation.inputFields`
49
+ );
50
+ const searchInputKeys = getFieldKeys(
51
+ definition.searches,
52
+ `${searchKey}.operation.inputFields`
53
+ );
54
+ const searchOutputKeys = getFieldKeys(
55
+ definition.searches,
56
+ `${searchKey}.operation.outputFields`
57
+ );
58
+ const searchOutputSampleKeys = getSearchOutputSampleKeys(
59
+ definition,
60
+ searchKey
61
+ );
62
+
63
+ // There are constraints where we check for keys in either outputFields or sample, so combining them is a shortcut
64
+ const allSearchOutputKeys = new Set([
65
+ ...searchOutputKeys,
66
+ ...searchOutputSampleKeys,
67
+ ]);
68
+
69
+ // For some constraints, there is a difference between not "having" a key defined versus having one but with empty values
70
+ const hasSearchOutputFields = _.has(
71
+ definition.searches,
72
+ `${searchKey}.operation.outputFields`
73
+ );
74
+ const hasSearchOutputSample = _.has(
75
+ definition.searches,
76
+ `${searchKey}.operation.sample`
77
+ );
20
78
 
21
79
  // Confirm searchOrCreate.key matches a searches.key (current Zapier editor limitation)
22
80
  if (!definition.searches[searchOrCreateKey]) {
@@ -25,7 +83,7 @@ const validateSearchOrCreateKeys = (definition) => {
25
83
  `must match a "key" from a search (options: ${searchKeys})`,
26
84
  searchOrCreateDef,
27
85
  '/SearchOrCreateSchema',
28
- `instance.searchOrCreates.${key}.key`,
86
+ `instance.${searchCreatesKey}.${key}.key`,
29
87
  'invalidKey',
30
88
  'key'
31
89
  )
@@ -39,7 +97,7 @@ const validateSearchOrCreateKeys = (definition) => {
39
97
  `must match a "key" from a search (options: ${searchKeys})`,
40
98
  searchOrCreateDef,
41
99
  '/SearchOrCreateSchema',
42
- `instance.searchOrCreates.${key}.search`,
100
+ `instance.${searchCreatesKey}.${key}.search`,
43
101
  'invalidKey',
44
102
  'search'
45
103
  )
@@ -53,12 +111,144 @@ const validateSearchOrCreateKeys = (definition) => {
53
111
  `must match a "key" from a create (options: ${createKeys})`,
54
112
  searchOrCreateDef,
55
113
  '/SearchOrCreateSchema',
56
- `instance.searchOrCreates.${key}.create`,
114
+ `instance.${searchCreatesKey}.${key}.create`,
57
115
  'invalidKey',
58
116
  'create'
59
117
  )
60
118
  );
61
119
  }
120
+
121
+ // Confirm searchOrCreate.update matches a creates.key, if it is defined
122
+ if (updateKey && !definition.creates[updateKey]) {
123
+ errors.push(
124
+ new jsonschema.ValidationError(
125
+ `must match a "key" from a create (options: ${createKeys})`,
126
+ searchOrCreateDef,
127
+ '/SearchOrCreateSchema',
128
+ `instance.${searchCreatesKey}.${key}.update`,
129
+ 'invalidKey'
130
+ )
131
+ );
132
+ }
133
+
134
+ // Confirm searchOrCreate.updateInputFromSearchOutput existing implies searchOrCreate.update is defined
135
+ if (searchOrCreateDef.updateInputFromSearchOutput && !updateKey) {
136
+ errors.push(
137
+ new jsonschema.ValidationError(
138
+ `requires searchOrCreates.${key}.update to be defined`,
139
+ searchOrCreateDef,
140
+ '/SearchOrCreateSchema',
141
+ `instance.${searchCreatesKey}.${key}.updateInputFromSearchOutput`,
142
+ 'invalid'
143
+ )
144
+ );
145
+ }
146
+
147
+ // Confirm searchOrCreate.searchUniqueInputToOutputConstraint existing implies searchOrCreate.update is defined
148
+ if (searchOrCreateDef.searchUniqueInputToOutputConstraint && !updateKey) {
149
+ errors.push(
150
+ new jsonschema.ValidationError(
151
+ `requires searchOrCreates.${key}.update to be defined`,
152
+ searchOrCreateDef,
153
+ '/SearchOrCreateSchema',
154
+ `instance.${searchCreatesKey}.${key}.searchUniqueInputToOutputConstraint`,
155
+ 'invalid'
156
+ )
157
+ );
158
+ }
159
+
160
+ // Confirm searchOrCreate.updateInputFromSearchOutput contains objects with:
161
+ // keys existing in creates[update].operation.inputFields.key
162
+ // values existing in searches[search].operation.(outputFields.key|sample keys), if they are defined
163
+ if (
164
+ updateKey &&
165
+ _.isPlainObject(searchOrCreateDef.updateInputFromSearchOutput)
166
+ ) {
167
+ const updateInputOptionHint = _.isEmpty(updateInputKeys)
168
+ ? '(no "key" found in inputFields)'
169
+ : `(options: ${updateInputKeys})`;
170
+
171
+ const searchOutputOptionHint = `(options: ${[...allSearchOutputKeys]})`;
172
+
173
+ for (const [updateInputField, searchOutputField] of Object.entries(
174
+ searchOrCreateDef.updateInputFromSearchOutput
175
+ )) {
176
+ if (!updateInputKeys.includes(updateInputField)) {
177
+ errors.push(
178
+ new jsonschema.ValidationError(
179
+ `object key must match a "key" from a creates.${updateKey}.operation.inputFields ${updateInputOptionHint}`,
180
+ searchOrCreateDef,
181
+ '/SearchOrCreateSchema',
182
+ `instance.${searchCreatesKey}.${key}.updateInputFromSearchOutput`,
183
+ 'invalidKey'
184
+ )
185
+ );
186
+ }
187
+
188
+ if (
189
+ (hasSearchOutputFields || hasSearchOutputSample) &&
190
+ !allSearchOutputKeys.has(searchOutputField)
191
+ ) {
192
+ errors.push(
193
+ new jsonschema.ValidationError(
194
+ `object value must match a "key" from searches.${searchKey}.operation.(outputFields|sample) ${searchOutputOptionHint}`,
195
+ searchOrCreateDef,
196
+ '/SearchOrCreateSchema',
197
+ `instance.${searchCreatesKey}.${key}.updateInputFromSearchOutput`,
198
+ 'invalidKey'
199
+ )
200
+ );
201
+ }
202
+ }
203
+ }
204
+
205
+ // Confirm searchOrCreate.searchUniqueInputToOutputConstraint contains objects with:
206
+ // keys existing in searches[search].operation.inputFields.key
207
+ // values existing in searches[search].operation.(outputFields.key|sample keys), if they are defined
208
+ if (
209
+ updateKey &&
210
+ _.isPlainObject(searchOrCreateDef.searchUniqueInputToOutputConstraint)
211
+ ) {
212
+ const searchInputOptionHint = _.isEmpty(searchInputKeys)
213
+ ? '(no "key" found in inputFields)'
214
+ : `(options: ${searchInputKeys})`;
215
+
216
+ const searchOutputOptionHint = `(options: ${[...allSearchOutputKeys]})`;
217
+
218
+ for (const [searchInputField, searchOutputField] of Object.entries(
219
+ searchOrCreateDef.searchUniqueInputToOutputConstraint
220
+ )) {
221
+ if (!searchInputKeys.includes(searchInputField)) {
222
+ errors.push(
223
+ new jsonschema.ValidationError(
224
+ `object key must match a "key" from a searches.${searchKey}.operation.inputFields ${searchInputOptionHint}`,
225
+ searchOrCreateDef,
226
+ '/SearchOrCreateSchema',
227
+ `instance.${searchCreatesKey}.${key}.searchUniqueInputToOutputConstraint`,
228
+ 'invalidKey'
229
+ )
230
+ );
231
+ }
232
+
233
+ if (
234
+ (hasSearchOutputFields || hasSearchOutputSample) &&
235
+ typeof searchOutputField === 'string' &&
236
+ !allSearchOutputKeys.has(searchOutputField)
237
+ ) {
238
+ errors.push(
239
+ new jsonschema.ValidationError(
240
+ `object value must match a "key" from searches.${searchKey}.operation.(outputFields|sample) ${searchOutputOptionHint}`,
241
+ searchOrCreateDef,
242
+ '/SearchOrCreateSchema',
243
+ `instance.${searchCreatesKey}.${key}.searchUniqueInputToOutputConstraint`,
244
+ 'invalidKey'
245
+ )
246
+ );
247
+ }
248
+ }
249
+ }
250
+
251
+ return errors;
62
252
  });
63
253
 
64
254
  return errors;
@@ -10,6 +10,7 @@ const ReadBulksSchema = require('./BulkReadsSchema');
10
10
  const SearchesSchema = require('./SearchesSchema');
11
11
  const CreatesSchema = require('./CreatesSchema');
12
12
  const SearchOrCreatesSchema = require('./SearchOrCreatesSchema');
13
+ const SearchAndCreatesSchema = require('./SearchAndCreatesSchema');
13
14
  const RequestSchema = require('./RequestSchema');
14
15
  const VersionSchema = require('./VersionSchema');
15
16
  const MiddlewaresSchema = require('./MiddlewaresSchema');
@@ -96,6 +97,10 @@ module.exports = makeSchema(
96
97
  'All the search-or-create combos for your app. You can create your own here, or Zapier will automatically register any from resources that define a search, a create, and a get (or define a searchOrCreate directly). Register non-resource search-or-creates here as well.',
97
98
  $ref: SearchOrCreatesSchema.id,
98
99
  },
100
+ searchAndCreates: {
101
+ description: 'An alias for "searchOrCreates".',
102
+ $ref: SearchAndCreatesSchema.id,
103
+ },
99
104
  flags: {
100
105
  description: 'Top-level app options',
101
106
  $ref: AppFlagsSchema.id,
@@ -139,6 +144,7 @@ module.exports = makeSchema(
139
144
  SearchesSchema,
140
145
  CreatesSchema,
141
146
  SearchOrCreatesSchema,
147
+ SearchAndCreatesSchema,
142
148
  RequestSchema,
143
149
  VersionSchema,
144
150
  MiddlewaresSchema,
@@ -45,7 +45,7 @@ module.exports = makeSchema(
45
45
  },
46
46
  type: {
47
47
  description:
48
- 'The type of this value. Use `string` for basic text input, `text` for a large, `<textarea>` style box, and `code` for a `<textarea>` with a fixed-width font.',
48
+ 'The type of this value. Use `string` for basic text input, `text` for a large, `<textarea>` style box, and `code` for a `<textarea>` with a fixed-width font. Field type of `file` will accept either a file object or a string. If a URL is provided in the string, Zapier will automatically make a GET for that file. Otherwise, a .txt file will be generated.',
49
49
  type: 'string',
50
50
  // string == unicode
51
51
  // text == a long textarea string
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ const makeSchema = require('../utils/makeSchema');
4
+
5
+ const SearchOrCreatesSchema = require('./SearchOrCreatesSchema');
6
+
7
+ module.exports = makeSchema(
8
+ {
9
+ ...SearchOrCreatesSchema.schema,
10
+ id: '/SearchAndCreatesSchema',
11
+ description: 'Alias for /SearchOrCreatesSchema',
12
+ },
13
+ [SearchOrCreatesSchema]
14
+ );
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
 
3
+ const { SKIP_KEY } = require('../constants');
3
4
  const makeSchema = require('../utils/makeSchema');
4
5
 
5
6
  const BasicDisplaySchema = require('./BasicDisplaySchema');
7
+ const FlatObjectSchema = require('./FlatObjectSchema');
6
8
  const KeySchema = require('./KeySchema');
7
9
 
8
10
  module.exports = makeSchema(
@@ -30,6 +32,21 @@ module.exports = makeSchema(
30
32
  description: 'The key of the create that powers this search-or-create',
31
33
  $ref: KeySchema.id,
32
34
  },
35
+ update: {
36
+ description:
37
+ 'EXPERIMENTAL: The key of the update action (in `creates`) that will be used if a search succeeds.',
38
+ $ref: KeySchema.id,
39
+ },
40
+ updateInputFromSearchOutput: {
41
+ description:
42
+ "EXPERIMENTAL: A mapping where the key represents the input field for the update action, and the value represents the field from the search action's output that should be mapped to the update action's input field.",
43
+ $ref: FlatObjectSchema.id,
44
+ },
45
+ searchUniqueInputToOutputConstraint: {
46
+ description:
47
+ "EXPERIMENTAL: A mapping where the key represents an input field for the search action, and the value represents how that field's value will be used to filter down the search output for an exact match.",
48
+ type: 'object',
49
+ },
33
50
  },
34
51
  additionalProperties: false,
35
52
  examples: [
@@ -45,6 +62,25 @@ module.exports = makeSchema(
45
62
  search: 'searchWidgets',
46
63
  create: 'createWidget',
47
64
  },
65
+ {
66
+ key: 'upsertWidgets',
67
+ display: {
68
+ label: 'Upsert Widgets',
69
+ description:
70
+ 'Searches for a widget matching the provided query and updates it if found, or creates one if it does not exist.',
71
+ important: true,
72
+ hidden: false,
73
+ },
74
+ search: 'searchWidgets',
75
+ create: 'createWidget',
76
+ update: 'updateExistingWidget',
77
+ updateInputFromSearchOutput: {
78
+ widget_id: 'id',
79
+ },
80
+ searchUniqueInputToOutputConstraint: {
81
+ widget_name: 'name',
82
+ },
83
+ },
48
84
  ],
49
85
  antiExamples: [
50
86
  {
@@ -78,7 +114,30 @@ module.exports = makeSchema(
78
114
  reason:
79
115
  'Invalid values for keys: search and create (must be a string that matches the key of a registered search or create)',
80
116
  },
117
+ {
118
+ [SKIP_KEY]: true, // Cannot validate field dependency between updateInputFromSearchOutput / searchUniqueInputToOutputConstraint and update
119
+ example: {
120
+ key: 'upsertWidgets',
121
+ display: {
122
+ label: 'Upsert Widgets',
123
+ description:
124
+ 'Searches for a widget matching the provided query and updates it if found, or creates one if it does not exist.',
125
+ important: true,
126
+ hidden: false,
127
+ },
128
+ search: 'searchWidgets',
129
+ create: 'createWidget',
130
+ updateInputFromSearchOutput: {
131
+ widget_id: 'id',
132
+ },
133
+ searchUniqueInputToOutputConstraint: {
134
+ widget_name: 'name',
135
+ },
136
+ },
137
+ reason:
138
+ 'If either the updateInputFromSearchOutput or searchUniqueInputToOutputConstraint keys are present, then the update key must be present as well.',
139
+ },
81
140
  ],
82
141
  },
83
- [BasicDisplaySchema, KeySchema]
142
+ [BasicDisplaySchema, KeySchema, FlatObjectSchema]
84
143
  );
@@ -33,6 +33,21 @@ module.exports = makeSchema(
33
33
  create: 'createWidget',
34
34
  },
35
35
  },
36
+ {
37
+ searchAndCreateWidgets: {
38
+ key: 'searchAndCreateWidgets',
39
+ display: {
40
+ label: 'Search and Create Widgets',
41
+ description:
42
+ 'Searches for a widget matching the provided query, creates one if it does not exist or updates existing one if found.',
43
+ important: true,
44
+ hidden: false,
45
+ },
46
+ search: 'searchWidgets',
47
+ create: 'createWidget',
48
+ update: 'updateWidget',
49
+ },
50
+ },
36
51
  ],
37
52
  antiExamples: [
38
53
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapier-platform-schema",
3
- "version": "12.1.0",
3
+ "version": "12.2.1",
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/",