├── .nvmrc
├── .prettierignore
├── .gitignore
├── CHANGELOG.md
├── smoke-test
├── test-export
├── test-validate
└── smoke-test.js
├── .github
└── ISSUE_TEMPLATE.md
├── bin
├── export.js
└── docs.js
├── lib
├── schemas
│ ├── KeySchema.js
│ ├── FieldsSchema.js
│ ├── ResultsSchema.js
│ ├── AuthenticationBasicConfigSchema.js
│ ├── AuthenticationDigestConfigSchema.js
│ ├── AuthenticationCustomConfigSchema.js
│ ├── FunctionRequireSchema.js
│ ├── MiddlewaresSchema.js
│ ├── RefResourceSchema.js
│ ├── BundleSchema.js
│ ├── VersionSchema.js
│ ├── HydratorsSchema.js
│ ├── CreatesSchema.js
│ ├── SearchesSchema.js
│ ├── TriggersSchema.js
│ ├── SearchOrCreatesSchema.js
│ ├── DynamicFieldsSchema.js
│ ├── AppFlagsSchema.js
│ ├── AuthenticationSessionConfigSchema.js
│ ├── ResourcesSchema.js
│ ├── FieldOrFunctionSchema.js
│ ├── BasicCreateActionOperationSchema.js
│ ├── FunctionSourceSchema.js
│ ├── FieldChoicesSchema.js
│ ├── FlatObjectSchema.js
│ ├── RedirectRequestSchema.js
│ ├── FunctionSchema.js
│ ├── SearchOrCreateSchema.js
│ ├── FieldChoiceWithLabelSchema.js
│ ├── BasicPollingOperationSchema.js
│ ├── AuthenticationOAuth1ConfigSchema.js
│ ├── BasicActionOperationSchema.js
│ ├── AuthenticationOAuth2ConfigSchema.js
│ ├── ResourceMethodCreateSchema.js
│ ├── ResourceMethodSearchSchema.js
│ ├── ResourceMethodGetSchema.js
│ ├── BasicDisplaySchema.js
│ ├── ResourceMethodHookSchema.js
│ ├── ResourceMethodListSchema.js
│ ├── SearchSchema.js
│ ├── BasicOperationSchema.js
│ ├── TriggerSchema.js
│ ├── CreateSchema.js
│ ├── RequestSchema.js
│ ├── BasicHookOperationSchema.js
│ ├── AuthenticationSchema.js
│ ├── AppSchema.js
│ ├── FieldSchema.js
│ └── ResourceSchema.js
├── utils
│ ├── exportSchema.js
│ ├── links.js
│ ├── makeSchema.js
│ ├── makeValidator.js
│ └── buildDocs.js
├── constants.js
└── functional-constraints
│ ├── matchingKeys.js
│ ├── index.js
│ ├── requiredSamples.js
│ ├── mutuallyExclusiveFields.js
│ ├── deepNestedFields.js
│ └── searchOrCreateKeys.js
├── schema.js
├── .travis.yml
├── docs
└── build
│ └── schema.html
├── README.md
├── test
├── utils.js
├── functional-constraints
│ ├── matchingKeys.js
│ ├── deepNestedFields.js
│ └── mutuallyExclusiveFields.js
├── readability.js
└── index.js
├── package.json
├── .eslintrc
├── examples
└── definition.json
└── exported-schema.json
/.nvmrc:
--------------------------------------------------------------------------------
1 | v8.10.0
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | package.json
2 | package-lock.json
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.zip
3 | *.log
4 | coverage
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Read docs: https://zapier.github.io/zapier-platform-cli/.
2 |
3 | Changelog: https://github.com/zapier/zapier-platform-cli/blob/master/CHANGELOG.md
4 |
--------------------------------------------------------------------------------
/smoke-test/test-export:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const zapierSchema = require('zapier-platform-schema');
4 | console.log(JSON.stringify(zapierSchema.exportSchema()));
5 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Please open your issues on https://github.com/zapier/zapier-platform-cli/issues instead.
2 |
3 | Also, we have a Slack room at https://zapier-platform-slack.herokuapp.com/ set up if you have any other questions.
4 |
--------------------------------------------------------------------------------
/bin/export.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 |
5 | const schema = require('../schema');
6 | const exportedSchema = schema.exportSchema();
7 |
8 | fs.writeFileSync(
9 | './exported-schema.json',
10 | JSON.stringify(exportedSchema, null, ' ')
11 | );
12 |
--------------------------------------------------------------------------------
/lib/schemas/KeySchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | module.exports = makeSchema({
6 | id: '/KeySchema',
7 | description: 'A unique identifier for this item.',
8 | type: 'string',
9 | minLength: 2,
10 | pattern: '^[a-zA-Z]+[a-zA-Z0-9_]*$'
11 | });
12 |
--------------------------------------------------------------------------------
/bin/docs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 |
5 | const constants = require('../lib/constants');
6 |
7 | const AppSchema = require('../lib/schemas/AppSchema');
8 | const buildDocs = require('../lib/utils/buildDocs');
9 |
10 | fs.writeFileSync(`./${constants.DOCS_PATH}`, buildDocs(AppSchema));
11 |
--------------------------------------------------------------------------------
/schema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const exportSchema = require('./lib/utils/exportSchema');
4 |
5 | const AppSchema = require('./lib/schemas/AppSchema');
6 |
7 | const validateAppDefinition = AppSchema.validate;
8 |
9 | module.exports = {
10 | AppSchema,
11 | validateAppDefinition,
12 | exportSchema: () => exportSchema(AppSchema)
13 | };
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "8.10.0"
4 | - "10"
5 | script:
6 | - npm run coverage
7 | - npm run smoke-test
8 | notifications:
9 | email: false
10 | deploy:
11 | provider: npm
12 | email: engineering@zapier.com
13 | api_key: $NPM_TOKEN
14 | on:
15 | tags: true
16 | node: "8.10.0"
17 | skip_cleanup: true
18 |
--------------------------------------------------------------------------------
/lib/schemas/FieldsSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const FieldSchema = require('./FieldSchema');
6 |
7 | module.exports = makeSchema(
8 | {
9 | id: '/FieldsSchema',
10 | description: 'An array or collection of fields.',
11 | type: 'array',
12 | items: { $ref: FieldSchema.id }
13 | },
14 | [FieldSchema]
15 | );
16 |
--------------------------------------------------------------------------------
/lib/schemas/ResultsSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | module.exports = makeSchema({
6 | id: '/ResultsSchema',
7 | description: 'An array of objects suitable for returning in perform calls.',
8 | type: 'array',
9 | items: {
10 | type: 'object',
11 | // TODO: require id, ID, Id property?
12 | minProperties: 1
13 | }
14 | });
15 |
--------------------------------------------------------------------------------
/lib/schemas/AuthenticationBasicConfigSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | module.exports = makeSchema({
6 | id: '/AuthenticationBasicConfigSchema',
7 | description:
8 | 'Config for Basic Authentication. No extra properties are required to setup Basic Auth, so you can leave this empty if your app uses Basic Auth.',
9 | type: 'object',
10 | properties: {},
11 | additionalProperties: false
12 | });
13 |
--------------------------------------------------------------------------------
/smoke-test/test-validate:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const zapierSchema = require('zapier-platform-schema');
4 |
5 | const validApp = {
6 | version: '1.0.0',
7 | platformVersion: '7.0.0'
8 | };
9 |
10 | const invalidApp = {};
11 |
12 | const testApps = [validApp, invalidApp];
13 |
14 | const results = testApps.map(app =>
15 | zapierSchema.validateAppDefinition(app).errors.map(err => err.message)
16 | );
17 |
18 | console.log(JSON.stringify(results));
19 |
--------------------------------------------------------------------------------
/lib/schemas/AuthenticationDigestConfigSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | module.exports = makeSchema({
6 | id: '/AuthenticationDigestConfigSchema',
7 | description:
8 | 'Config for Digest Authentication. No extra properties are required to setup Digest Auth, so you can leave this empty if your app uses Digets Auth.',
9 | type: 'object',
10 | properties: {},
11 | additionalProperties: false
12 | });
13 |
--------------------------------------------------------------------------------
/docs/build/schema.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/lib/schemas/AuthenticationCustomConfigSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | module.exports = makeSchema({
6 | id: '/AuthenticationCustomConfigSchema',
7 | description:
8 | 'Config for custom authentication (like API keys). No extra properties are required to setup this auth type, so you can leave this empty if your app uses a custom auth method.',
9 | type: 'object',
10 | properties: {},
11 | additionalProperties: false
12 | });
13 |
--------------------------------------------------------------------------------
/lib/schemas/FunctionRequireSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | module.exports = makeSchema({
6 | id: '/FunctionRequireSchema',
7 | description:
8 | 'A path to a file that might have content like `module.exports = (z, bundle) => [{id: 123}];`.',
9 | examples: [{ require: 'some/path/to/file.js' }],
10 | type: 'object',
11 | additionalProperties: false,
12 | required: ['require'],
13 | properties: {
14 | require: { type: 'string' }
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/lib/schemas/MiddlewaresSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 | const FunctionSchema = require('./FunctionSchema');
5 |
6 | module.exports = makeSchema({
7 | id: '/MiddlewaresSchema',
8 | description:
9 | 'List of before or after middlewares. Can be an array of functions or a single function',
10 | oneOf: [
11 | {
12 | type: 'array',
13 | items: { $ref: FunctionSchema.id }
14 | },
15 | { $ref: FunctionSchema.id }
16 | ],
17 | additionalProperties: false
18 | });
19 |
--------------------------------------------------------------------------------
/lib/schemas/RefResourceSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | module.exports = makeSchema({
6 | id: '/RefResourceSchema',
7 | description:
8 | 'Reference a resource by key and the data it returns. In the format of: `{resource_key}.{foreign_key}(.{human_label_key})`.',
9 | type: 'string',
10 | examples: ['contact', 'contact.id', 'contact.id.full_name'],
11 | antiExamples: ['Contact.list.id.full_name'],
12 | pattern: '^[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)?(\\.[a-zA-Z0-9_]+)?$'
13 | });
14 |
--------------------------------------------------------------------------------
/lib/schemas/BundleSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | module.exports = makeSchema({
6 | id: '/BundleSchema',
7 | description: 'Given as the "arguments" or input to a perform call.',
8 | type: 'object',
9 | examples: [{}, { authData: {}, inputData: {}, inputDataRaw: {} }],
10 | antiExamples: [{ random: true }],
11 | properties: {
12 | authData: { type: 'object' },
13 | inputData: { type: 'object' },
14 | inputDataRaw: { type: 'object' }
15 | },
16 | additionalProperties: false
17 | });
18 |
--------------------------------------------------------------------------------
/lib/schemas/VersionSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | module.exports = makeSchema({
6 | id: '/VersionSchema',
7 | description:
8 | 'Represents a simplified semver string, from `0.0.0` to `999.999.999`.',
9 | examples: ['1.0.0', '2.11.3', '999.999.999'],
10 | antiExamples: [
11 | '1.0.0.0',
12 | '1000.0.0',
13 | 'v1.0.0',
14 | '1.0.0-beta',
15 | '1.0.0-beta.x.y.z',
16 | '1.0.0-beta+12487'
17 | ],
18 | type: 'string',
19 | pattern: '^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$'
20 | });
21 |
--------------------------------------------------------------------------------
/lib/schemas/HydratorsSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 | const FunctionSchema = require('./FunctionSchema');
5 |
6 | module.exports = makeSchema({
7 | id: '/HydratorsSchema',
8 | description:
9 | "A bank of named functions that you can use in `z.hydrate('someName')` to lazily load data.",
10 | type: 'object',
11 | patternProperties: {
12 | '^[a-zA-Z]+[a-zA-Z0-9]*$': {
13 | description:
14 | "Any unique key can be used in `z.hydrate('uniqueKeyHere')`.",
15 | $ref: FunctionSchema.id
16 | }
17 | },
18 | additionalProperties: false
19 | });
20 |
--------------------------------------------------------------------------------
/lib/schemas/CreatesSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const CreateSchema = require('./CreateSchema');
6 |
7 | module.exports = makeSchema(
8 | {
9 | id: '/CreatesSchema',
10 | description: 'Enumerates the creates your app has available for users.',
11 | type: 'object',
12 | patternProperties: {
13 | '^[a-zA-Z]+[a-zA-Z0-9_]*$': {
14 | description:
15 | 'Any unique key can be used and its values will be validated against the CreateSchema.',
16 | $ref: CreateSchema.id
17 | }
18 | }
19 | },
20 | [CreateSchema]
21 | );
22 |
--------------------------------------------------------------------------------
/lib/schemas/SearchesSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const SearchSchema = require('./SearchSchema');
6 |
7 | module.exports = makeSchema(
8 | {
9 | id: '/SearchesSchema',
10 | description: 'Enumerates the searches your app has available for users.',
11 | type: 'object',
12 | patternProperties: {
13 | '^[a-zA-Z]+[a-zA-Z0-9_]*$': {
14 | description:
15 | 'Any unique key can be used and its values will be validated against the SearchSchema.',
16 | $ref: SearchSchema.id
17 | }
18 | }
19 | },
20 | [SearchSchema]
21 | );
22 |
--------------------------------------------------------------------------------
/lib/utils/exportSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const _ = require('lodash');
4 | const packageJson = require('../../package.json');
5 |
6 | const exportSchema = InitSchema => {
7 | const exportedSchema = {
8 | version: packageJson.version,
9 | schemas: {}
10 | };
11 | const addAndRecurse = Schema => {
12 | exportedSchema.schemas[Schema.id.replace('/', '')] = _.omit(
13 | Schema.schema,
14 | 'examples',
15 | 'antiExamples'
16 | );
17 | Schema.dependencies.map(addAndRecurse);
18 | };
19 | addAndRecurse(InitSchema);
20 | return exportedSchema;
21 | };
22 |
23 | module.exports = exportSchema;
24 |
--------------------------------------------------------------------------------
/lib/schemas/TriggersSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const TriggerSchema = require('./TriggerSchema');
6 |
7 | module.exports = makeSchema(
8 | {
9 | id: '/TriggersSchema',
10 | description: 'Enumerates the triggers your app has available for users.',
11 | type: 'object',
12 | patternProperties: {
13 | '^[a-zA-Z]+[a-zA-Z0-9_]*$': {
14 | description:
15 | 'Any unique key can be used and its values will be validated against the TriggerSchema.',
16 | $ref: TriggerSchema.id
17 | }
18 | },
19 | additionalProperties: false
20 | },
21 | [TriggerSchema]
22 | );
23 |
--------------------------------------------------------------------------------
/lib/schemas/SearchOrCreatesSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const SearchOrCreateSchema = require('./SearchOrCreateSchema');
6 |
7 | module.exports = makeSchema(
8 | {
9 | id: '/SearchOrCreatesSchema',
10 | description:
11 | 'Enumerates the search-or-creates your app has available for users.',
12 | type: 'object',
13 | patternProperties: {
14 | '^[a-zA-Z]+[a-zA-Z0-9_]*$': {
15 | description:
16 | 'Any unique key can be used and its values will be validated against the SearchOrCreateSchema.',
17 | $ref: SearchOrCreateSchema.id
18 | }
19 | }
20 | },
21 | [SearchOrCreateSchema]
22 | );
23 |
--------------------------------------------------------------------------------
/lib/schemas/DynamicFieldsSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const FieldOrFunctionSchema = require('./FieldOrFunctionSchema');
6 |
7 | module.exports = makeSchema(
8 | {
9 | id: '/DynamicFieldsSchema',
10 | description:
11 | 'Like [/FieldsSchema](#fieldsschema) but you can provide functions to create dynamic or custom fields.',
12 | examples: [
13 | [],
14 | [{ key: 'abc' }],
15 | [{ key: 'abc' }, '$func$2$f$'],
16 | ['$func$2$f$', '$func$2$f$']
17 | ],
18 | antiExamples: [[{}], [{ key: 'abc', choices: {} }], '$func$2$f$'],
19 | $ref: FieldOrFunctionSchema.id
20 | },
21 | [FieldOrFunctionSchema]
22 | );
23 |
--------------------------------------------------------------------------------
/lib/schemas/AppFlagsSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | module.exports = makeSchema({
6 | id: '/AppFlagsSchema',
7 | description: 'Codifies high-level options for your app.',
8 | type: 'object',
9 | properties: {
10 | skipHttpPatch: {
11 | description:
12 | "By default, Zapier patches the core `http` module so that all requests (including those from 3rd-party SDKs) can be logged. Set this to true if you're seeing issues using an SDK (such as AWS).",
13 | type: 'boolean'
14 | }
15 | },
16 | additionalProperties: false,
17 | examples: [{ skipHttpPatch: true }, { skipHttpPatch: false }, {}],
18 | antiExamples: [{ blah: true }, { skipHttpPatch: 'yes' }]
19 | });
20 |
--------------------------------------------------------------------------------
/lib/schemas/AuthenticationSessionConfigSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const RequestSchema = require('./RequestSchema');
6 | const FunctionSchema = require('./FunctionSchema');
7 |
8 | module.exports = makeSchema(
9 | {
10 | id: '/AuthenticationSessionConfigSchema',
11 | description: 'Config for session authentication.',
12 | type: 'object',
13 | required: ['perform'],
14 | properties: {
15 | perform: {
16 | description:
17 | 'Define how Zapier fetches the additional authData needed to make API calls.',
18 | oneOf: [{ $ref: RequestSchema.id }, { $ref: FunctionSchema.id }]
19 | }
20 | },
21 | additionalProperties: false
22 | },
23 | [FunctionSchema, RequestSchema]
24 | );
25 |
--------------------------------------------------------------------------------
/lib/schemas/ResourcesSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 | const ResourceSchema = require('./ResourceSchema');
5 |
6 | module.exports = makeSchema(
7 | {
8 | id: '/ResourcesSchema',
9 | description:
10 | 'All the resources that underlie common CRUD methods powering automatically handled triggers, creates, and searches for your app. Zapier will break these apart for you.',
11 | type: 'object',
12 | patternProperties: {
13 | '^[a-zA-Z]+[a-zA-Z0-9_]*$': {
14 | description:
15 | 'Any unique key can be used and its values will be validated against the ResourceSchema.',
16 | $ref: ResourceSchema.id
17 | }
18 | },
19 | additionalProperties: false
20 | },
21 | [ResourceSchema]
22 | );
23 |
--------------------------------------------------------------------------------
/lib/utils/links.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const packageJson = require('../../package.json');
4 | const constants = require('../constants.js');
5 |
6 | // From '' to 'SomeSchema'.
7 | const filename = val => _.trim(String(val), '/<>');
8 |
9 | // From '/SomeSchema' to '#someschema'.
10 | const anchor = val => '#' + filename(val.toLowerCase());
11 |
12 | const makeCodeLink = id =>
13 | `${constants.ROOT_GITHUB}/blob/v${packageJson.version}/lib/schemas/${filename(
14 | id
15 | )}.js`;
16 | const makeDocLink = id =>
17 | `${constants.ROOT_GITHUB}/blob/v${packageJson.version}/${
18 | constants.DOCS_PATH
19 | }${anchor(id)}`;
20 |
21 | module.exports = {
22 | filename: filename,
23 | anchor: anchor,
24 | makeCodeLink: makeCodeLink,
25 | makeDocLink: makeDocLink
26 | };
27 |
--------------------------------------------------------------------------------
/lib/schemas/FieldOrFunctionSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const FieldSchema = require('./FieldSchema');
6 | const FunctionSchema = require('./FunctionSchema');
7 |
8 | // This schema was created to improve readability on errors.
9 |
10 | module.exports = makeSchema(
11 | {
12 | id: '/FieldOrFunctionSchema',
13 | description: 'Represents an array of fields or functions.',
14 | examples: [
15 | [],
16 | [{ key: 'abc' }],
17 | [{ key: 'abc' }, '$func$2$f$'],
18 | ['$func$2$f$', '$func$2$f$']
19 | ],
20 | antiExamples: [[{}], [{ key: 'abc', choices: {} }], '$func$2$f$'],
21 | type: 'array',
22 | items: {
23 | oneOf: [{ $ref: FieldSchema.id }, { $ref: FunctionSchema.id }]
24 | }
25 | },
26 | [FieldSchema, FunctionSchema]
27 | );
28 |
--------------------------------------------------------------------------------
/lib/utils/makeSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const _ = require('lodash');
4 |
5 | const makeValidator = require('./makeValidator');
6 |
7 | const getRawSchema = schema => schema.schema;
8 |
9 | const getDependencies = schema => schema.dependencies;
10 |
11 | const flattenDependencies = schemas => {
12 | schemas = schemas || [];
13 | return _.flatten(schemas.map(getDependencies).concat(schemas));
14 | };
15 |
16 | const makeSchema = (schemaDefinition, schemaDependencies) => {
17 | const dependencies = flattenDependencies(schemaDependencies);
18 | const validatorDependencies = dependencies.map(getRawSchema);
19 | return {
20 | dependencies,
21 | id: schemaDefinition.id,
22 | schema: schemaDefinition,
23 | validate: makeValidator(schemaDefinition, validatorDependencies).validate
24 | };
25 | };
26 |
27 | module.exports = makeSchema;
28 |
--------------------------------------------------------------------------------
/lib/constants.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ROOT_GITHUB: 'https://github.com/zapier/zapier-platform-schema',
3 | DOCS_PATH: 'docs/build/schema.md',
4 | SKIP_KEY: '_skipTest',
5 | // the following pairs of keys can't be used together in FieldSchema
6 | // they're stored here because they're used in a few places
7 | INCOMPATIBLE_FIELD_SCHEMA_KEYS: [
8 | ['children', 'list'], // This is actually a Feature Request (https://github.com/zapier/zapier-platform-cli/issues/115)
9 | ['children', 'dict'], // dict is ignored
10 | ['children', 'type'], // type is ignored
11 | ['children', 'placeholder'], // placeholder is ignored
12 | ['children', 'helpText'], // helpText is ignored
13 | ['children', 'default'], // default is ignored
14 | ['dict', 'list'], // Use only one or the other
15 | ['dynamic', 'dict'], // dict is ignored
16 | ['dynamic', 'choices'] // choices are ignored
17 | ]
18 | };
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MOVED
2 |
3 | This code has been moved to: https://github.com/zapier/zapier-platform/tree/master/packages/schema
4 |
5 | ---
6 |
7 | # Schema For Zapier CLI Platform
8 |
9 | [Visit the CLI for basic documentation and instructions on how to use](https://zapier.github.io/zapier-platform-cli).
10 |
11 | [View all the schema definitions](https://zapier.github.io/zapier-platform-schema/build/schema.html).
12 |
13 | ## Development
14 |
15 | - `npm install` for getting started
16 | - `npm test` for running tests
17 | - `npm run export` for updating the exported-schema (even if only the version changes)
18 | - `npm run docs` for updating docs (even if only the version changes)
19 | - `npm run coverage` for running tests and displaying test coverage
20 |
21 | ## Publishing (after merging)
22 |
23 | - `npm version [patch|minor|major]` will pull, test, update exported-schema, update docs, increment version in package.json, push tags, and publish to npm
24 |
--------------------------------------------------------------------------------
/lib/schemas/BasicCreateActionOperationSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const BasicActionOperationSchema = require('./BasicActionOperationSchema');
6 |
7 | // TODO: would be nice to deep merge these instead
8 | // or maybe use allOf which is built into json-schema
9 | const BasicCreateActionOperationSchema = JSON.parse(
10 | JSON.stringify(BasicActionOperationSchema.schema)
11 | );
12 |
13 | BasicCreateActionOperationSchema.id = '/BasicCreateActionOperationSchema';
14 | BasicCreateActionOperationSchema.description =
15 | 'Represents the fundamental mechanics of a create.';
16 |
17 | BasicCreateActionOperationSchema.properties.shouldLock = {
18 | description:
19 | 'Should this action be performed one at a time (avoid concurrency)?',
20 | type: 'boolean'
21 | };
22 |
23 | module.exports = makeSchema(
24 | BasicCreateActionOperationSchema,
25 | BasicActionOperationSchema.dependencies
26 | );
27 |
--------------------------------------------------------------------------------
/lib/schemas/FunctionSourceSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | module.exports = makeSchema({
6 | id: '/FunctionSourceSchema',
7 | description:
8 | 'Source code like `{source: "return 1 + 2"}` which the system will wrap in a function for you.',
9 | examples: [
10 | { source: 'return 1 + 2' },
11 | { args: ['x', 'y'], source: 'return x + y;' }
12 | ],
13 | antiExamples: [{ source: '1 + 2' }],
14 | type: 'object',
15 | additionalProperties: false,
16 | required: ['source'],
17 | properties: {
18 | source: {
19 | type: 'string',
20 | pattern: 'return',
21 | description:
22 | 'JavaScript code for the function body. This must end with a `return` statement.'
23 | },
24 | args: {
25 | type: 'array',
26 | items: { type: 'string' },
27 | description:
28 | "Function signature. Defaults to `['z', 'bundle']` if not specified."
29 | }
30 | }
31 | });
32 |
--------------------------------------------------------------------------------
/lib/functional-constraints/matchingKeys.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const _ = require('lodash');
4 | const jsonschema = require('jsonschema');
5 |
6 | const actionTypes = ['triggers', 'searches', 'creates'];
7 |
8 | const matchingKeys = definition => {
9 | const errors = [];
10 |
11 | // verifies that x.key === x
12 | // otherwise, we double results in the compiled app via core's compileApp
13 |
14 | for (const actionType of actionTypes) {
15 | const group = definition[actionType] || {};
16 | _.each(group, (action, key) => {
17 | if (action.key !== key) {
18 | errors.push(
19 | new jsonschema.ValidationError(
20 | `must have a matching top-level key (found "${key}" and "${
21 | action.key
22 | }")`,
23 | action,
24 | `/${_.capitalize(actionType)}Schema`,
25 | `instance.${key}.key`,
26 | 'invalid',
27 | 'key'
28 | )
29 | );
30 | }
31 | });
32 | }
33 |
34 | return errors;
35 | };
36 |
37 | module.exports = matchingKeys;
38 |
--------------------------------------------------------------------------------
/lib/schemas/FieldChoicesSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const FieldChoiceWithLabelSchema = require('./FieldChoiceWithLabelSchema');
6 |
7 | module.exports = makeSchema(
8 | {
9 | id: '/FieldChoicesSchema',
10 | description:
11 | 'A static dropdown of options. Which you use depends on your order and label requirements:\n\nNeed a Label? | Does Order Matter? | Type to Use\n---|---|---\nYes | No | Object of value -> label\nNo | Yes | Array of Strings\nYes | Yes | Array of [FieldChoiceWithLabel](#fieldchoicewithlabelschema)',
12 | examples: [{ a: '1', b: '2', c: '3' }, ['first', 'second', 'third']],
13 | antiExamples: [[1, 2, 3], [{ a: '1', b: '2', c: '3' }]],
14 | oneOf: [
15 | {
16 | type: 'object',
17 | minProperties: 1
18 | },
19 | {
20 | type: 'array',
21 | minItems: 1,
22 | items: {
23 | oneOf: [{ type: 'string' }, { $ref: FieldChoiceWithLabelSchema.id }]
24 | }
25 | }
26 | ]
27 | },
28 | [FieldChoiceWithLabelSchema]
29 | );
30 |
--------------------------------------------------------------------------------
/lib/schemas/FlatObjectSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | module.exports = makeSchema({
6 | id: '/FlatObjectSchema',
7 | description: 'An object whose values can only be primitives',
8 | type: 'object',
9 | examples: [
10 | { a: 1, b: 2, c: 3 },
11 | { a: 1.2, b: 2.2, c: 3.3 },
12 | { a: 'a', b: 'b', c: 'c' },
13 | { a: true, b: true, c: false },
14 | { a: 'a', b: 2, c: 3.1, d: true, e: false },
15 | { 123: 'hello' }
16 | ],
17 | antiExamples: [
18 | { a: {}, b: 2 },
19 | { a: { aa: 1 }, b: 2 },
20 | { a: [], b: 2 },
21 | { a: [1, 2, 3], b: 2 },
22 | { '': 1 },
23 | { ' ': 1 },
24 | { ' ': 1 }
25 | ],
26 | patternProperties: {
27 | '[^\\s]+': {
28 | description:
29 | 'Any key may exist in this flat object as long as its values are simple.',
30 | anyOf: [
31 | { type: 'null' },
32 | { type: 'string' },
33 | { type: 'integer' },
34 | { type: 'number' },
35 | { type: 'boolean' }
36 | ]
37 | }
38 | },
39 | additionalProperties: false
40 | });
41 |
--------------------------------------------------------------------------------
/test/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('should');
4 | const { SKIP_KEY } = require('../lib/constants');
5 |
6 | const testInlineSchemaExamples = name => {
7 | const Schema = require('../lib/schemas/' + name);
8 | const goods = Schema.schema.examples || [];
9 | const bads = Schema.schema.antiExamples || [];
10 |
11 | describe(name, () => {
12 | it('valid example schemas should pass validation', function() {
13 | if (!goods.length) {
14 | this.skip();
15 | } else {
16 | goods.filter(t => !t[SKIP_KEY]).forEach(good => {
17 | const errors = Schema.validate(good).errors;
18 | errors.should.have.length(0);
19 | });
20 | }
21 | });
22 |
23 | it('invalid example schemas should fail validation', function() {
24 | if (!bads.length) {
25 | this.skip();
26 | } else {
27 | bads.filter(t => !t[SKIP_KEY]).forEach(bad => {
28 | const errors = Schema.validate(bad).errors;
29 | errors.should.not.have.length(0);
30 | });
31 | }
32 | });
33 | });
34 | };
35 |
36 | module.exports = {
37 | testInlineSchemaExamples
38 | };
39 |
--------------------------------------------------------------------------------
/lib/schemas/RedirectRequestSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const FlatObjectSchema = require('./FlatObjectSchema');
6 |
7 | module.exports = makeSchema(
8 | {
9 | id: '/RedirectRequestSchema',
10 | description:
11 | 'A representation of a HTTP redirect - you can use the `{{syntax}}` to inject authentication, field or global variables.',
12 | type: 'object',
13 | properties: {
14 | method: {
15 | description: 'The HTTP method for the request.',
16 | type: 'string',
17 | default: 'GET',
18 | enum: ['GET']
19 | },
20 | url: {
21 | description:
22 | 'A URL for the request (we will parse the querystring and merge with params). Keys and values will not be re-encoded.',
23 | type: 'string'
24 | },
25 | params: {
26 | description:
27 | 'A mapping of the querystring - will get merged with any query params in the URL. Keys and values will be encoded.',
28 | $ref: FlatObjectSchema.id
29 | }
30 | },
31 | additionalProperties: false
32 | },
33 | [FlatObjectSchema]
34 | );
35 |
--------------------------------------------------------------------------------
/lib/schemas/FunctionSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const FunctionRequireSchema = require('./FunctionRequireSchema');
6 | const FunctionSourceSchema = require('./FunctionSourceSchema');
7 |
8 | module.exports = makeSchema(
9 | {
10 | id: '/FunctionSchema',
11 | description:
12 | 'Internal pointer to a function from the original source or the source code itself. Encodes arity and if `arguments` is used in the body. Note - just write normal functions and the system will encode the pointers for you. Or, provide {source: "return 1 + 2"} and the system will wrap in a function for you.',
13 | examples: [
14 | '$func$0$f$',
15 | '$func$2$t$',
16 | { source: 'return 1 + 2' },
17 | { require: 'some/path/to/file.js' }
18 | ],
19 | antiExamples: [
20 | 'funcy',
21 | { source: '1 + 2' },
22 | { source: '1 + 2', require: 'some/path/to/file.js' }
23 | ],
24 | oneOf: [
25 | { type: 'string', pattern: '^\\$func\\$\\d+\\$[tf]\\$$' },
26 | { $ref: FunctionRequireSchema.id },
27 | { $ref: FunctionSourceSchema.id }
28 | ]
29 | },
30 | [FunctionRequireSchema, FunctionSourceSchema]
31 | );
32 |
--------------------------------------------------------------------------------
/lib/schemas/SearchOrCreateSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const BasicDisplaySchema = require('./BasicDisplaySchema');
6 | const KeySchema = require('./KeySchema');
7 |
8 | module.exports = makeSchema(
9 | {
10 | id: '/SearchOrCreateSchema',
11 | description:
12 | 'Pair an existing search and a create to enable "Find or Create" functionality in your app',
13 | type: 'object',
14 | required: ['key', 'display', 'search', 'create'],
15 | properties: {
16 | key: {
17 | description:
18 | 'A key to uniquely identify this search-or-create. Must match the search key.',
19 | $ref: KeySchema.id
20 | },
21 | display: {
22 | description: 'Configures the UI for this search-or-create.',
23 | $ref: BasicDisplaySchema.id
24 | },
25 | search: {
26 | description: 'The key of the search that powers this search-or-create',
27 | $ref: KeySchema.id
28 | },
29 | create: {
30 | description: 'The key of the create that powers this search-or-create',
31 | $ref: KeySchema.id
32 | }
33 | },
34 | additionalProperties: false
35 | },
36 | [BasicDisplaySchema, KeySchema]
37 | );
38 |
--------------------------------------------------------------------------------
/test/functional-constraints/matchingKeys.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('should');
4 | const schema = require('../../schema');
5 |
6 | describe('matchingKeys', () => {
7 | it("should error if the keys don't match", () => {
8 | const definition = {
9 | version: '1.0.0',
10 | platformVersion: '1.0.0',
11 | triggers: {}, // this should be harmlessly skipped
12 | creates: {
13 | foo: {
14 | key: 'bar', // this is different than above, which shouldn't validate
15 | noun: 'Foo',
16 | display: {
17 | label: 'Create Foo',
18 | description: 'Creates a...'
19 | },
20 | operation: {
21 | perform: '$func$2$f$',
22 | sample: { id: 1 },
23 | inputFields: [
24 | { key: 'orderId', type: 'number' },
25 | {
26 | key: 'line_items',
27 | children: [
28 | {
29 | key: 'product',
30 | type: 'string'
31 | }
32 | ]
33 | }
34 | ]
35 | }
36 | }
37 | }
38 | };
39 |
40 | const results = schema.validateAppDefinition(definition);
41 | results.errors.should.have.length(1);
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/lib/functional-constraints/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* Each check below is expected to return a list of ValidationSchema errors. An error is defined by:
4 | * new jsonschema.ValidationError(
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
7 | * schema, // name of the schema that failed, like '/TriggerSchema'
8 | * propertyPath, // stringified path to problematic snippet, like 'instance.triggers.find_contact'
9 | * name, // optional, the validation type that failed. Can make something up like 'invalidUrl'
10 | * argument // optional
11 | * );
12 | */
13 | const checks = [
14 | require('./searchOrCreateKeys'),
15 | require('./deepNestedFields'),
16 | require('./mutuallyExclusiveFields'),
17 | require('./requiredSamples'),
18 | require('./matchingKeys')
19 | ];
20 |
21 | const runFunctionalConstraints = (definition, mainSchema) => {
22 | return checks.reduce((errors, checkFunc) => {
23 | const errorsForCheck = checkFunc(definition, mainSchema);
24 | if (errorsForCheck) {
25 | errors = errors.concat(errorsForCheck);
26 | }
27 | return errors;
28 | }, []);
29 | };
30 |
31 | module.exports = {
32 | run: runFunctionalConstraints
33 | };
34 |
--------------------------------------------------------------------------------
/lib/schemas/FieldChoiceWithLabelSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | module.exports = makeSchema({
6 | id: '/FieldChoiceWithLabelSchema',
7 | description:
8 | "An object describing a labeled choice in a static dropdown. Useful if the value a user picks isn't exactly what the zap uses. For instance, when they click on a nickname, but the zap uses the user's full name ([image](https://cdn.zapier.com/storage/photos/8ed01ac5df3a511ce93ed2dc43c7fbbc.png)).",
9 | examples: [{ label: 'Red', sample: '#f00', value: '#f00' }],
10 | antiExamples: [{ label: 'Red', value: '#f00' }],
11 | type: 'object',
12 | required: ['value', 'sample', 'label'],
13 | properties: {
14 | value: {
15 | description:
16 | 'The actual value that is sent into the Zap. Should match sample exactly.',
17 | type: 'string',
18 | minLength: 1
19 | },
20 | sample: {
21 | description:
22 | "Displayed as light grey text in the editor. It's important that the value match the sample. Otherwise, the actual value won't match what the user picked, which is confusing.",
23 | type: 'string',
24 | minLength: 1
25 | },
26 | label: {
27 | description: 'A human readable label for this value.',
28 | type: 'string',
29 | minLength: 1
30 | }
31 | }
32 | });
33 |
--------------------------------------------------------------------------------
/lib/schemas/BasicPollingOperationSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const BasicOperationSchema = require('./BasicOperationSchema');
6 |
7 | // TODO: would be nice to deep merge these instead
8 | // or maybe use allOf which is built into json-schema
9 | const BasicPollingOperationSchema = JSON.parse(
10 | JSON.stringify(BasicOperationSchema.schema)
11 | );
12 |
13 | BasicPollingOperationSchema.id = '/BasicPollingOperationSchema';
14 | BasicPollingOperationSchema.description =
15 | 'Represents the fundamental mechanics of a trigger.';
16 |
17 | BasicPollingOperationSchema.properties = {
18 | type: {
19 | // TODO: not a fan of this...
20 | description:
21 | 'Clarify how this operation works (polling == pull or hook == push).',
22 | type: 'string',
23 | default: 'polling',
24 | enum: ['polling'] // notification?
25 | },
26 | resource: BasicPollingOperationSchema.properties.resource,
27 | perform: BasicPollingOperationSchema.properties.perform,
28 | canPaginate: {
29 | description: 'Does this endpoint support a page offset?',
30 | type: 'boolean'
31 | },
32 | inputFields: BasicPollingOperationSchema.properties.inputFields,
33 | outputFields: BasicPollingOperationSchema.properties.outputFields,
34 | sample: BasicPollingOperationSchema.properties.sample
35 | };
36 |
37 | module.exports = makeSchema(
38 | BasicPollingOperationSchema,
39 | BasicOperationSchema.dependencies
40 | );
41 |
--------------------------------------------------------------------------------
/lib/schemas/AuthenticationOAuth1ConfigSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const FunctionSchema = require('./FunctionSchema');
6 | const RedirectRequestSchema = require('./RedirectRequestSchema');
7 | const RequestSchema = require('./RequestSchema');
8 |
9 | module.exports = makeSchema(
10 | {
11 | id: '/AuthenticationOAuth1ConfigSchema',
12 | description: 'Config for OAuth1 authentication.',
13 | type: 'object',
14 | required: ['getRequestToken', 'authorizeUrl', 'getAccessToken'],
15 | properties: {
16 | getRequestToken: {
17 | description:
18 | 'Define where Zapier will acquire a request token which is used for the rest of the three legged authentication process.',
19 | oneOf: [{ $ref: RequestSchema.id }, { $ref: FunctionSchema.id }]
20 | },
21 | authorizeUrl: {
22 | description:
23 | 'Define where Zapier will redirect the user to authorize our app. Typically, you should append an `oauth_token` querystring parameter to the request.',
24 | oneOf: [{ $ref: RedirectRequestSchema.id }, { $ref: FunctionSchema.id }]
25 | },
26 | getAccessToken: {
27 | description: 'Define how Zapier fetches an access token from the API',
28 | oneOf: [{ $ref: RequestSchema.id }, { $ref: FunctionSchema.id }]
29 | }
30 | },
31 | additionalProperties: false
32 | },
33 | [FunctionSchema, RedirectRequestSchema, RequestSchema]
34 | );
35 |
--------------------------------------------------------------------------------
/lib/schemas/BasicActionOperationSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const BasicOperationSchema = require('./BasicOperationSchema');
6 | const FunctionSchema = require('./FunctionSchema');
7 | const RequestSchema = require('./RequestSchema');
8 |
9 | // TODO: would be nice to deep merge these instead
10 | // or maybe use allOf which is built into json-schema
11 | const BasicActionOperationSchema = JSON.parse(
12 | JSON.stringify(BasicOperationSchema.schema)
13 | );
14 |
15 | BasicActionOperationSchema.id = '/BasicActionOperationSchema';
16 | BasicActionOperationSchema.description =
17 | 'Represents the fundamental mechanics of a search/create.';
18 |
19 | BasicActionOperationSchema.properties = {
20 | resource: BasicActionOperationSchema.properties.resource,
21 | perform: BasicActionOperationSchema.properties.perform,
22 | performResume: {
23 | description:
24 | 'A function that parses data from a perform + callback to resume this action. For use with callback semantics',
25 | $ref: FunctionSchema.id
26 | },
27 | performGet: {
28 | description:
29 | 'How will Zapier get a single record? If you find yourself reaching for this - consider resources and their built-in get methods.',
30 | oneOf: [{ $ref: RequestSchema.id }, { $ref: FunctionSchema.id }]
31 | },
32 | inputFields: BasicActionOperationSchema.properties.inputFields,
33 | outputFields: BasicActionOperationSchema.properties.outputFields,
34 | sample: BasicActionOperationSchema.properties.sample
35 | };
36 |
37 | module.exports = makeSchema(
38 | BasicActionOperationSchema,
39 | BasicOperationSchema.dependencies
40 | );
41 |
--------------------------------------------------------------------------------
/lib/functional-constraints/requiredSamples.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const _ = require('lodash');
4 | const jsonschema = require('jsonschema');
5 |
6 | // todo: deal with circular dep.
7 | const RESOURCE_ID = '/ResourceSchema';
8 | const RESOURCE_METHODS = ['get', 'hook', 'list', 'search', 'create'];
9 |
10 | const check = definition => {
11 | if (!definition.operation || _.get(definition, 'display.hidden')) {
12 | return null;
13 | }
14 |
15 | const samples = _.get(definition, 'operation.sample', {});
16 | return !_.isEmpty(samples)
17 | ? null
18 | : new jsonschema.ValidationError(
19 | 'requires "sample", because it\'s not hidden',
20 | definition,
21 | definition.id
22 | );
23 | };
24 |
25 | module.exports = (definition, mainSchema) => {
26 | let definitions = [];
27 |
28 | if (mainSchema.id === RESOURCE_ID) {
29 | definitions = RESOURCE_METHODS.map(method => definition[method]).filter(
30 | Boolean
31 | );
32 |
33 | // allow method definitions to inherit the sample
34 | if (definition.sample) {
35 | definitions.forEach(methodDefinition => {
36 | if (methodDefinition.operation && !methodDefinition.operation.sample) {
37 | methodDefinition.operation.sample = definition.sample;
38 | }
39 | });
40 | }
41 |
42 | if (!definitions.length) {
43 | return [
44 | new jsonschema.ValidationError(
45 | 'expected at least one resource operation',
46 | definition,
47 | definition.id
48 | )
49 | ];
50 | }
51 | } else {
52 | definitions = [definition];
53 | }
54 |
55 | return definitions.map(check).filter(Boolean);
56 | };
57 |
--------------------------------------------------------------------------------
/lib/schemas/AuthenticationOAuth2ConfigSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const FunctionSchema = require('./FunctionSchema');
6 | const RedirectRequestSchema = require('./RedirectRequestSchema');
7 | const RequestSchema = require('./RequestSchema');
8 |
9 | module.exports = makeSchema(
10 | {
11 | id: '/AuthenticationOAuth2ConfigSchema',
12 | description: 'Config for OAuth2 authentication.',
13 | type: 'object',
14 | required: ['authorizeUrl', 'getAccessToken'],
15 | properties: {
16 | authorizeUrl: {
17 | description:
18 | 'Define where Zapier will redirect the user to authorize our app. Note: we append the redirect URL and state parameters to return value of this function.',
19 | oneOf: [{ $ref: RedirectRequestSchema.id }, { $ref: FunctionSchema.id }]
20 | },
21 | getAccessToken: {
22 | description: 'Define how Zapier fetches an access token from the API',
23 | oneOf: [{ $ref: RequestSchema.id }, { $ref: FunctionSchema.id }]
24 | },
25 | refreshAccessToken: {
26 | description:
27 | 'Define how Zapier will refresh the access token from the API',
28 | oneOf: [{ $ref: RequestSchema.id }, { $ref: FunctionSchema.id }]
29 | },
30 | scope: {
31 | description: 'What scope should Zapier request?',
32 | type: 'string'
33 | },
34 | autoRefresh: {
35 | description:
36 | 'Should Zapier include a pre-built afterResponse middleware that invokes `refreshAccessToken` when we receive a 401 response?',
37 | type: 'boolean'
38 | }
39 | },
40 | additionalProperties: false
41 | },
42 | [FunctionSchema, RedirectRequestSchema, RequestSchema]
43 | );
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zapier-platform-schema",
3 | "version": "8.2.1",
4 | "description": "Schema definition for CLI apps in the Zapier Developer Platform.",
5 | "repository": "zapier/zapier-platform-schema",
6 | "homepage": "https://zapier.com/",
7 | "author": "Bryan Helmig ",
8 | "license": "UNLICENSED",
9 | "main": "schema.js",
10 | "files": [
11 | "/exported-schema.json",
12 | "/lib/**/*.js",
13 | "/schema.js"
14 | ],
15 | "scripts": {
16 | "preversion": "git pull && npm test",
17 | "version": "npm run build && npm run add",
18 | "postversion": "git push && git push --tags",
19 | "test": "mocha -t 5000 --recursive test",
20 | "test:debug": "mocha -t 5000 --recursive --inspect-brk test",
21 | "posttest": "eslint lib",
22 | "smoke-test": "mocha -t 5000 --recursive smoke-test",
23 | "coverage": "istanbul cover _mocha -- --recursive",
24 | "export": "node bin/export.js && prettier --write exported-schema.json",
25 | "docs": "node bin/docs.js",
26 | "build": "npm run docs && npm run export",
27 | "add": "git add exported-schema.json README.md docs",
28 | "precommit": "npm run build && npm run add && lint-staged"
29 | },
30 | "dependencies": {
31 | "jsonschema": "1.1.1",
32 | "lodash": "4.17.11"
33 | },
34 | "devDependencies": {
35 | "eslint": "4.19.1",
36 | "fs-extra": "7.0.0",
37 | "husky": "0.14.3",
38 | "istanbul": "0.4.5",
39 | "lint-staged": "^6.0.0",
40 | "markdown-toc": "1.2.0",
41 | "mocha": "5.1.1",
42 | "node-fetch": "2.2.0",
43 | "prettier": "1.9.2",
44 | "should": "11.1.0"
45 | },
46 | "prettier": {
47 | "singleQuote": true
48 | },
49 | "lint-staged": {
50 | "*.{js,json}": [
51 | "prettier --write",
52 | "git add"
53 | ]
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/schemas/ResourceMethodCreateSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const BasicDisplaySchema = require('./BasicDisplaySchema');
6 | const BasicActionOperationSchema = require('./BasicActionOperationSchema');
7 |
8 | module.exports = makeSchema(
9 | {
10 | id: '/ResourceMethodCreateSchema',
11 | description:
12 | 'How will we find create a specific object given inputs? Will be turned into a create automatically.',
13 | type: 'object',
14 | required: ['display', 'operation'],
15 | examples: [
16 | {
17 | display: {
18 | label: 'Create Tag',
19 | description: 'Create a new Tag in your account.'
20 | },
21 | operation: {
22 | perform: '$func$2$f$',
23 | sample: {
24 | id: 1
25 | }
26 | }
27 | },
28 | {
29 | display: {
30 | label: 'Create Tag',
31 | description: 'Create a new Tag in your account.',
32 | hidden: true
33 | },
34 | operation: {
35 | perform: '$func$2$f$'
36 | }
37 | }
38 | ],
39 | antiExamples: [
40 | {
41 | display: {
42 | label: 'Create Tag',
43 | description: 'Create a new Tag in your account.'
44 | },
45 | operation: {
46 | perform: '$func$2$f$'
47 | }
48 | }
49 | ],
50 | properties: {
51 | display: {
52 | description: 'Define how this create method will be exposed in the UI.',
53 | $ref: BasicDisplaySchema.id
54 | },
55 | operation: {
56 | description: 'Define how this create method will work.',
57 | $ref: BasicActionOperationSchema.id
58 | }
59 | },
60 | additionalProperties: false
61 | },
62 | [BasicDisplaySchema, BasicActionOperationSchema]
63 | );
64 |
--------------------------------------------------------------------------------
/lib/functional-constraints/mutuallyExclusiveFields.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const _ = require('lodash');
4 | const jsonschema = require('jsonschema');
5 |
6 | // NOTE: While it would be possible to accomplish this with a solution like
7 | // https://stackoverflow.com/questions/28162509/mutually-exclusive-property-groups#28172831
8 | // it was harder to read and understand.
9 |
10 | const { INCOMPATIBLE_FIELD_SCHEMA_KEYS } = require('../constants');
11 |
12 | const verifyIncompatibilities = (inputFields, path) => {
13 | const errors = [];
14 |
15 | _.each(inputFields, (inputField, index) => {
16 | _.each(INCOMPATIBLE_FIELD_SCHEMA_KEYS, ([firstField, secondField]) => {
17 | if (_.has(inputField, firstField) && _.has(inputField, secondField)) {
18 | errors.push(
19 | new jsonschema.ValidationError(
20 | `must not contain ${firstField} and ${secondField}, as they're mutually exclusive.`,
21 | inputField,
22 | '/FieldSchema',
23 | `instance.${path}.inputFields[${index}]`,
24 | 'invalid',
25 | 'inputFields'
26 | )
27 | );
28 | }
29 | });
30 | });
31 |
32 | return errors;
33 | };
34 |
35 | const mutuallyExclusiveFields = definition => {
36 | let errors = [];
37 |
38 | _.each(['triggers', 'searches', 'creates'], typeOf => {
39 | if (definition[typeOf]) {
40 | _.each(definition[typeOf], actionDef => {
41 | if (actionDef.operation && actionDef.operation.inputFields) {
42 | errors = [
43 | ...errors,
44 | ...verifyIncompatibilities(
45 | actionDef.operation.inputFields,
46 | `${typeOf}.${actionDef.key}`
47 | )
48 | ];
49 | }
50 | });
51 | }
52 | });
53 |
54 | return errors;
55 | };
56 |
57 | module.exports = mutuallyExclusiveFields;
58 |
--------------------------------------------------------------------------------
/lib/schemas/ResourceMethodSearchSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const BasicDisplaySchema = require('./BasicDisplaySchema');
6 | const BasicActionOperationSchema = require('./BasicActionOperationSchema');
7 |
8 | module.exports = makeSchema(
9 | {
10 | id: '/ResourceMethodSearchSchema',
11 | description:
12 | 'How will we find a specific object given filters or search terms? Will be turned into a search automatically.',
13 | type: 'object',
14 | required: ['display', 'operation'],
15 | examples: [
16 | {
17 | display: {
18 | label: 'Find a Recipe',
19 | description: 'Search for recipe by cuisine style.'
20 | },
21 | operation: {
22 | perform: '$func$2$f$',
23 | sample: { id: 1 }
24 | }
25 | },
26 | {
27 | display: {
28 | label: 'Find a Recipe',
29 | description: 'Search for recipe by cuisine style.',
30 | hidden: true
31 | },
32 | operation: {
33 | perform: '$func$2$f$'
34 | }
35 | }
36 | ],
37 | antiExamples: [
38 | {
39 | key: 'recipe',
40 | noun: 'Recipe',
41 | display: {
42 | label: 'Find a Recipe',
43 | description: 'Search for recipe by cuisine style.'
44 | },
45 | operation: {
46 | perform: '$func$2$f$'
47 | }
48 | }
49 | ],
50 | properties: {
51 | display: {
52 | description: 'Define how this search method will be exposed in the UI.',
53 | $ref: BasicDisplaySchema.id
54 | },
55 | operation: {
56 | description: 'Define how this search method will work.',
57 | $ref: BasicActionOperationSchema.id
58 | }
59 | },
60 | additionalProperties: false
61 | },
62 | [BasicDisplaySchema, BasicActionOperationSchema]
63 | );
64 |
--------------------------------------------------------------------------------
/lib/schemas/ResourceMethodGetSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const BasicDisplaySchema = require('./BasicDisplaySchema');
6 | const BasicOperationSchema = require('./BasicOperationSchema');
7 |
8 | module.exports = makeSchema(
9 | {
10 | id: '/ResourceMethodGetSchema',
11 | description:
12 | 'How will we get a single object given a unique identifier/id?',
13 | type: 'object',
14 | required: ['display', 'operation'],
15 | examples: [
16 | {
17 | display: {
18 | label: 'Get Tag by ID',
19 | description: 'Grab a specific Tag by ID.'
20 | },
21 | operation: {
22 | perform: {
23 | url: '$func$0$f$'
24 | },
25 | sample: {
26 | id: 385,
27 | name: 'proactive enable ROI'
28 | }
29 | }
30 | },
31 | {
32 | display: {
33 | label: 'Get Tag by ID',
34 | description: 'Grab a specific Tag by ID.',
35 | hidden: true
36 | },
37 | operation: {
38 | perform: {
39 | url: '$func$0$f$'
40 | }
41 | }
42 | }
43 | ],
44 | antiExamples: [
45 | {
46 | display: {
47 | label: 'Get Tag by ID',
48 | description: 'Grab a specific Tag by ID.'
49 | },
50 | operation: {
51 | perform: {
52 | url: '$func$0$f$'
53 | }
54 | }
55 | }
56 | ],
57 | properties: {
58 | display: {
59 | description: 'Define how this get method will be exposed in the UI.',
60 | $ref: BasicDisplaySchema.id
61 | },
62 | operation: {
63 | description: 'Define how this get method will work.',
64 | $ref: BasicOperationSchema.id
65 | }
66 | },
67 | additionalProperties: false
68 | },
69 | [BasicDisplaySchema, BasicOperationSchema]
70 | );
71 |
--------------------------------------------------------------------------------
/lib/schemas/BasicDisplaySchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | module.exports = makeSchema({
6 | id: '/BasicDisplaySchema',
7 | description: 'Represents user information for a trigger, search, or create.',
8 | type: 'object',
9 | examples: [
10 | { label: 'New Thing', description: 'Gets a new thing for you.' },
11 | {
12 | label: 'New Thing',
13 | description: 'Gets a new thing for you.',
14 | directions: 'This is how you use the thing.',
15 | hidden: false,
16 | important: true
17 | }
18 | ],
19 | antiExamples: [
20 | { label: 'New Thing' },
21 | {
22 | label: 'New Thing',
23 | description: 'Gets a new thing for you.',
24 | important: 1
25 | }
26 | ],
27 | required: ['label', 'description'],
28 | properties: {
29 | label: {
30 | description:
31 | 'A short label like "New Record" or "Create Record in Project".',
32 | type: 'string',
33 | minLength: 2,
34 | maxLength: 64
35 | },
36 | description: {
37 | description:
38 | 'A description of what this trigger, search, or create does.',
39 | type: 'string',
40 | minLength: 1,
41 | maxLength: 1000
42 | },
43 | directions: {
44 | description:
45 | 'A short blurb that can explain how to get this working. EG: how and where to copy-paste a static hook URL into your application. Only evaluated for static webhooks.',
46 | type: 'string',
47 | minLength: 12,
48 | maxLength: 1000
49 | },
50 | important: {
51 | description:
52 | 'Affects how prominently this operation is displayed in the UI. Only mark a few of the most popular operations important.',
53 | type: 'boolean'
54 | },
55 | hidden: {
56 | description: 'Should this operation be unselectable by users?',
57 | type: 'boolean'
58 | }
59 | },
60 | additionalProperties: false
61 | });
62 |
--------------------------------------------------------------------------------
/lib/functional-constraints/deepNestedFields.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const _ = require('lodash');
4 | const jsonschema = require('jsonschema');
5 |
6 | const collectErrors = (inputFields, path) => {
7 | const errors = [];
8 |
9 | _.each(inputFields, (inputField, index) => {
10 | if (inputField.children) {
11 | if (inputField.children.length === 0) {
12 | errors.push(
13 | new jsonschema.ValidationError(
14 | 'must not be empty.',
15 | inputField,
16 | '/FieldSchema',
17 | `instance.${path}.inputFields[${index}].children`,
18 | 'empty',
19 | 'inputFields'
20 | )
21 | );
22 | } else {
23 | const hasDeeplyNestedChildren = _.some(
24 | inputField.children,
25 | child => child.children
26 | );
27 |
28 | if (hasDeeplyNestedChildren) {
29 | errors.push(
30 | new jsonschema.ValidationError(
31 | 'must not contain deeply nested child fields. One level max.',
32 | inputField,
33 | '/FieldSchema',
34 | `instance.${path}.inputFields[${index}]`,
35 | 'deepNesting',
36 | 'inputFields'
37 | )
38 | );
39 | }
40 | }
41 | }
42 | });
43 |
44 | return errors;
45 | };
46 |
47 | const validateFieldNesting = definition => {
48 | let errors = [];
49 |
50 | _.each(['triggers', 'searches', 'creates'], typeOf => {
51 | if (definition[typeOf]) {
52 | _.each(definition[typeOf], actionDef => {
53 | if (actionDef.operation && actionDef.operation.inputFields) {
54 | errors = errors.concat(
55 | collectErrors(
56 | actionDef.operation.inputFields,
57 | `${typeOf}.${actionDef.key}`
58 | )
59 | );
60 | }
61 | });
62 | }
63 | });
64 |
65 | return errors;
66 | };
67 |
68 | module.exports = validateFieldNesting;
69 |
--------------------------------------------------------------------------------
/lib/schemas/ResourceMethodHookSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const BasicDisplaySchema = require('./BasicDisplaySchema');
6 | const BasicHookOperationSchema = require('./BasicHookOperationSchema');
7 |
8 | module.exports = makeSchema(
9 | {
10 | id: '/ResourceMethodHookSchema',
11 | description:
12 | 'How will we get notified of new objects? Will be turned into a trigger automatically.',
13 | type: 'object',
14 | required: ['display', 'operation'],
15 | examples: [
16 | {
17 | display: {
18 | label: 'Get Tag by ID',
19 | description: 'Grab a specific Tag by ID.'
20 | },
21 | operation: {
22 | type: 'hook',
23 | perform: '$func$0$f$',
24 | sample: {
25 | id: 385,
26 | name: 'proactive enable ROI'
27 | }
28 | }
29 | },
30 | {
31 | display: {
32 | label: 'Get Tag by ID',
33 | description: 'Grab a specific Tag by ID.',
34 | hidden: true
35 | },
36 | operation: {
37 | type: 'hook',
38 | perform: '$func$0$f$'
39 | }
40 | }
41 | ],
42 | antiExamples: [
43 | {
44 | display: {
45 | label: 'Get Tag by ID',
46 | description: 'Grab a specific Tag by ID.'
47 | },
48 | operation: {
49 | type: 'hook',
50 | perform: '$func$0$f$'
51 | }
52 | }
53 | ],
54 | properties: {
55 | display: {
56 | description:
57 | 'Define how this hook/trigger method will be exposed in the UI.',
58 | $ref: BasicDisplaySchema.id
59 | },
60 | operation: {
61 | description: 'Define how this hook/trigger method will work.',
62 | $ref: BasicHookOperationSchema.id
63 | }
64 | },
65 | additionalProperties: false
66 | },
67 | [BasicDisplaySchema, BasicHookOperationSchema]
68 | );
69 |
--------------------------------------------------------------------------------
/lib/functional-constraints/searchOrCreateKeys.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const _ = require('lodash');
4 | const jsonschema = require('jsonschema');
5 |
6 | const validateSearchOrCreateKeys = definition => {
7 | if (!definition.searchOrCreates) {
8 | return [];
9 | }
10 |
11 | const errors = [];
12 |
13 | const searchKeys = _.keys(definition.searches);
14 | const createKeys = _.keys(definition.creates);
15 |
16 | _.each(definition.searchOrCreates, (searchOrCreateDef, key) => {
17 | const searchOrCreateKey = searchOrCreateDef.key;
18 | const searchKey = searchOrCreateDef.search;
19 | const createKey = searchOrCreateDef.create;
20 |
21 | // Confirm searchOrCreate.key matches a searches.key (current Zapier editor limitation)
22 | if (!definition.searches[searchOrCreateKey]) {
23 | errors.push(
24 | new jsonschema.ValidationError(
25 | `must match a "key" from a search (options: ${searchKeys})`,
26 | searchOrCreateDef,
27 | '/SearchOrCreateSchema',
28 | `instance.searchOrCreates.${key}.key`,
29 | 'invalidKey',
30 | 'key'
31 | )
32 | );
33 | }
34 |
35 | // Confirm searchOrCreate.search matches a searches.key
36 | if (!definition.searches[searchKey]) {
37 | errors.push(
38 | new jsonschema.ValidationError(
39 | `must match a "key" from a search (options: ${searchKeys})`,
40 | searchOrCreateDef,
41 | '/SearchOrCreateSchema',
42 | `instance.searchOrCreates.${key}.search`,
43 | 'invalidKey',
44 | 'search'
45 | )
46 | );
47 | }
48 |
49 | // Confirm searchOrCreate.create matches a creates.key
50 | if (!definition.creates[createKey]) {
51 | errors.push(
52 | new jsonschema.ValidationError(
53 | `must match a "key" from a create (options: ${createKeys})`,
54 | searchOrCreateDef,
55 | '/SearchOrCreateSchema',
56 | `instance.searchOrCreates.${key}.create`,
57 | 'invalidKey',
58 | 'create'
59 | )
60 | );
61 | }
62 | });
63 |
64 | return errors;
65 | };
66 |
67 | module.exports = validateSearchOrCreateKeys;
68 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint:recommended",
3 | "parserOptions": {
4 | "ecmaVersion": 2018
5 | },
6 | "env": {
7 | "node": true,
8 | "es6": true,
9 | "mocha": true
10 | },
11 | "globals": {
12 | "Promise": true
13 | },
14 | "rules": {
15 | "quotes": [2, "single", "avoid-escape"],
16 | "strict": [0, "never"],
17 | "camelcase": 0,
18 | "no-underscore-dangle": 0,
19 | "new-cap": 0,
20 | "comma-dangle": 0,
21 | "comma-spacing": 2,
22 | "consistent-return": 2,
23 | "curly": [2, "all"],
24 | "dot-notation": [2, { "allowKeywords": true }],
25 | "eol-last": 2,
26 | "eqeqeq": [2, "smart"],
27 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
28 | "new-parens": 2,
29 | "no-alert": 2,
30 | "no-array-constructor": 2,
31 | "no-caller": 2,
32 | "no-console": 0,
33 | "no-delete-var": 2,
34 | "no-labels": 2,
35 | "no-eval": 2,
36 | "no-extend-native": 2,
37 | "no-extra-bind": 2,
38 | "no-fallthrough": 2,
39 | "no-implied-eval": 2,
40 | "no-iterator": 2,
41 | "no-label-var": 2,
42 | "no-lone-blocks": 2,
43 | "no-loop-func": 2,
44 | "no-mixed-spaces-and-tabs": [2, false],
45 | "no-multi-spaces": 2,
46 | "no-multi-str": 2,
47 | "no-native-reassign": 2,
48 | "no-new": 2,
49 | "no-new-func": 2,
50 | "no-new-object": 2,
51 | "no-new-wrappers": 2,
52 | "no-octal": 2,
53 | "no-octal-escape": 2,
54 | "no-process-exit": 2,
55 | "no-proto": 2,
56 | "no-redeclare": 2,
57 | "no-return-assign": 2,
58 | "no-script-url": 2,
59 | "no-sequences": 2,
60 | "no-shadow": 2,
61 | "no-shadow-restricted-names": 2,
62 | "no-spaced-func": 2,
63 | "no-trailing-spaces": 2,
64 | "no-undef": 2,
65 | "no-undef-init": 2,
66 | "no-unused-expressions": 2,
67 | "no-unused-vars": [2, { "vars": "all", "args": "after-used" }],
68 | "no-use-before-define": 2,
69 | "no-with": 2,
70 | "semi": 2,
71 | "semi-spacing": [2, { "before": false, "after": true }],
72 | "space-infix-ops": 2,
73 | "space-unary-ops": [2, { "words": true, "nonwords": false }],
74 | "yoda": [2, "never"]
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/lib/schemas/ResourceMethodListSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const BasicDisplaySchema = require('./BasicDisplaySchema');
6 | const BasicPollingOperationSchema = require('./BasicPollingOperationSchema');
7 |
8 | module.exports = makeSchema(
9 | {
10 | id: '/ResourceMethodListSchema',
11 | description:
12 | 'How will we get a list of new objects? Will be turned into a trigger automatically.',
13 | type: 'object',
14 | required: ['display', 'operation'],
15 | examples: [
16 | {
17 | display: {
18 | label: 'New User',
19 | description: 'Trigger when a new User is created in your account.'
20 | },
21 | operation: {
22 | perform: {
23 | url: 'http://fake-crm.getsandbox.com/users'
24 | },
25 | sample: {
26 | id: 49,
27 | name: 'Veronica Kuhn',
28 | email: 'veronica.kuhn@company.com'
29 | }
30 | }
31 | },
32 | {
33 | display: {
34 | label: 'New User',
35 | description: 'Trigger when a new User is created in your account.',
36 | hidden: true
37 | },
38 | operation: {
39 | perform: {
40 | url: 'http://fake-crm.getsandbox.com/users'
41 | }
42 | }
43 | }
44 | ],
45 | antiExamples: [
46 | {
47 | display: {
48 | label: 'New User',
49 | description: 'Trigger when a new User is created in your account.'
50 | },
51 | operation: {
52 | perform: {
53 | url: 'http://fake-crm.getsandbox.com/users'
54 | }
55 | // missing sample
56 | }
57 | }
58 | ],
59 | properties: {
60 | display: {
61 | description:
62 | 'Define how this list/trigger method will be exposed in the UI.',
63 | $ref: BasicDisplaySchema.id
64 | },
65 | operation: {
66 | description: 'Define how this list/trigger method will work.',
67 | $ref: BasicPollingOperationSchema.id
68 | }
69 | },
70 | additionalProperties: false
71 | },
72 | [BasicDisplaySchema, BasicPollingOperationSchema]
73 | );
74 |
--------------------------------------------------------------------------------
/lib/schemas/SearchSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const BasicDisplaySchema = require('./BasicDisplaySchema');
6 | const BasicActionOperationSchema = require('./BasicActionOperationSchema');
7 | const KeySchema = require('./KeySchema');
8 |
9 | module.exports = makeSchema(
10 | {
11 | id: '/SearchSchema',
12 | description: 'How will Zapier search for existing objects?',
13 | type: 'object',
14 | required: ['key', 'noun', 'display', 'operation'],
15 | examples: [
16 | {
17 | key: 'recipe',
18 | noun: 'Recipe',
19 | display: {
20 | label: 'Find a Recipe',
21 | description: 'Search for recipe by cuisine style.'
22 | },
23 | operation: {
24 | perform: '$func$2$f$',
25 | sample: { id: 1 }
26 | }
27 | },
28 | {
29 | key: 'recipe',
30 | noun: 'Recipe',
31 | display: {
32 | label: 'Find a Recipe',
33 | description: 'Search for recipe by cuisine style.',
34 | hidden: true
35 | },
36 | operation: { perform: '$func$2$f$' }
37 | }
38 | ],
39 | antiExamples: [
40 | 'abc',
41 | {
42 | key: 'recipe',
43 | noun: 'Recipe',
44 | display: {
45 | label: 'Find a Recipe',
46 | description: 'Search for recipe by cuisine style.'
47 | },
48 | operation: {
49 | perform: '$func$2$f$'
50 | // missing sample
51 | }
52 | }
53 | ],
54 | properties: {
55 | key: {
56 | description: 'A key to uniquely identify this search.',
57 | $ref: KeySchema.id
58 | },
59 | noun: {
60 | description:
61 | 'A noun for this search that completes the sentence "finds a specific XXX".',
62 | type: 'string',
63 | minLength: 2,
64 | maxLength: 255
65 | },
66 | display: {
67 | description: 'Configures the UI for this search.',
68 | $ref: BasicDisplaySchema.id
69 | },
70 | operation: {
71 | description: 'Powers the functionality for this search.',
72 | $ref: BasicActionOperationSchema.id
73 | }
74 | },
75 | additionalProperties: false
76 | },
77 | [BasicDisplaySchema, BasicActionOperationSchema, KeySchema]
78 | );
79 |
--------------------------------------------------------------------------------
/lib/schemas/BasicOperationSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const DynamicFieldsSchema = require('./DynamicFieldsSchema');
6 | const FunctionSchema = require('./FunctionSchema');
7 | const RefResourceSchema = require('./RefResourceSchema');
8 | const RequestSchema = require('./RequestSchema');
9 | const ResultsSchema = require('./ResultsSchema');
10 |
11 | module.exports = makeSchema(
12 | {
13 | id: '/BasicOperationSchema',
14 | description:
15 | 'Represents the fundamental mechanics of triggers, searches, or creates.',
16 | type: 'object',
17 | required: ['perform'],
18 | properties: {
19 | resource: {
20 | description:
21 | 'Optionally reference and extends a resource. Allows Zapier to automatically tie together samples, lists and hooks, greatly improving the UX. EG: if you had another trigger reusing a resource but filtering the results.',
22 | $ref: RefResourceSchema.id
23 | },
24 | perform: {
25 | description:
26 | "How will Zapier get the data? This can be a function like `(z) => [{id: 123}]` or a request like `{url: 'http...'}`.",
27 | oneOf: [{ $ref: RequestSchema.id }, { $ref: FunctionSchema.id }]
28 | },
29 | inputFields: {
30 | description:
31 | 'What should the form a user sees and configures look like?',
32 | $ref: DynamicFieldsSchema.id
33 | },
34 | outputFields: {
35 | description:
36 | 'What fields of data will this return? Will use resource outputFields if missing, will also use sample if available.',
37 | $ref: DynamicFieldsSchema.id
38 | },
39 | sample: {
40 | description:
41 | 'What does a sample of data look like? Will use resource sample if missing. Requirement waived if `display.hidden` is true or if this belongs to a resource that has a top-level sample',
42 | type: 'object',
43 | // TODO: require id, ID, Id property?
44 | minProperties: 1,
45 | docAnnotation: {
46 | required: {
47 | type: 'replace', // replace or append
48 | value: '**yes** (with exceptions, see description)'
49 | }
50 | }
51 | }
52 | },
53 | additionalProperties: false
54 | },
55 | [
56 | DynamicFieldsSchema,
57 | FunctionSchema,
58 | RefResourceSchema,
59 | RequestSchema,
60 | ResultsSchema
61 | ]
62 | );
63 |
--------------------------------------------------------------------------------
/test/functional-constraints/deepNestedFields.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('should');
4 | const schema = require('../../schema');
5 |
6 | describe('deepNestedFields', () => {
7 | it('should not error on fields nested one level deep', () => {
8 | const definition = {
9 | version: '1.0.0',
10 | platformVersion: '1.0.0',
11 | creates: {
12 | foo: {
13 | key: 'foo',
14 | noun: 'Foo',
15 | display: {
16 | label: 'Create Foo',
17 | description: 'Creates a...'
18 | },
19 | operation: {
20 | perform: '$func$2$f$',
21 | sample: { id: 1 },
22 | inputFields: [
23 | { key: 'orderId', type: 'number' },
24 | {
25 | key: 'line_items',
26 | children: [
27 | {
28 | key: 'product',
29 | type: 'string'
30 | }
31 | ]
32 | }
33 | ]
34 | }
35 | }
36 | }
37 | };
38 |
39 | const results = schema.validateAppDefinition(definition);
40 | results.errors.should.have.length(0);
41 | });
42 |
43 | it('should error on fields nested more than one level deep', () => {
44 | const definition = {
45 | version: '1.0.0',
46 | platformVersion: '1.0.0',
47 | creates: {
48 | foo: {
49 | key: 'foo',
50 | noun: 'Foo',
51 | display: {
52 | label: 'Create Foo',
53 | description: 'Creates a...'
54 | },
55 | operation: {
56 | perform: '$func$2$f$',
57 | sample: { id: 1 },
58 | inputFields: [
59 | { key: 'orderId', type: 'number' },
60 | {
61 | key: 'line_items',
62 | children: [
63 | { key: 'some do not have children' },
64 | {
65 | key: 'product',
66 | children: [{ key: 'name', type: 'string' }]
67 | }
68 | ]
69 | }
70 | ]
71 | }
72 | }
73 | }
74 | };
75 |
76 | const results = schema.validateAppDefinition(definition);
77 | results.errors.should.have.length(1);
78 | results.errors[0].stack.should.eql(
79 | 'instance.creates.foo.inputFields[1] must not contain deeply nested child fields. One level max.'
80 | );
81 | });
82 | });
83 |
--------------------------------------------------------------------------------
/lib/schemas/TriggerSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const BasicDisplaySchema = require('./BasicDisplaySchema');
6 | const BasicHookOperationSchema = require('./BasicHookOperationSchema');
7 | const BasicPollingOperationSchema = require('./BasicPollingOperationSchema');
8 | const KeySchema = require('./KeySchema');
9 |
10 | module.exports = makeSchema(
11 | {
12 | id: '/TriggerSchema',
13 | description: 'How will Zapier get notified of new objects?',
14 | type: 'object',
15 | required: ['key', 'noun', 'display', 'operation'],
16 | examples: [
17 | {
18 | key: 'new_recipe',
19 | noun: 'Recipe',
20 | display: {
21 | label: 'New Recipe',
22 | description: 'Triggers when a new recipe is added.'
23 | },
24 | operation: {
25 | type: 'polling',
26 | perform: '$func$0$f$',
27 | sample: { id: 1 }
28 | }
29 | },
30 | {
31 | key: 'new_recipe',
32 | noun: 'Recipe',
33 | display: {
34 | label: 'New Recipe',
35 | description: 'Triggers when a new recipe is added.',
36 | hidden: true
37 | },
38 | operation: {
39 | type: 'polling',
40 | perform: '$func$0$f$'
41 | }
42 | }
43 | ],
44 | antiExamples: [
45 | {
46 | key: 'new_recipe',
47 | noun: 'Recipe',
48 | display: {
49 | label: 'New Recipe',
50 | description: 'Triggers when a new recipe is added.'
51 | },
52 | operation: {
53 | perform: '$func$0$f$'
54 | }
55 | }
56 | ],
57 | properties: {
58 | key: {
59 | description: 'A key to uniquely identify this trigger.',
60 | $ref: KeySchema.id
61 | },
62 | noun: {
63 | description:
64 | 'A noun for this trigger that completes the sentence "triggers on a new XXX".',
65 | type: 'string',
66 | minLength: 2,
67 | maxLength: 255
68 | },
69 | display: {
70 | description: 'Configures the UI for this trigger.',
71 | $ref: BasicDisplaySchema.id
72 | },
73 | operation: {
74 | description: 'Powers the functionality for this trigger.',
75 | anyOf: [
76 | { $ref: BasicPollingOperationSchema.id },
77 | { $ref: BasicHookOperationSchema.id }
78 | ]
79 | }
80 | },
81 | additionalProperties: false
82 | },
83 | [
84 | KeySchema,
85 | BasicDisplaySchema,
86 | BasicPollingOperationSchema,
87 | BasicHookOperationSchema
88 | ]
89 | );
90 |
--------------------------------------------------------------------------------
/lib/schemas/CreateSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const BasicDisplaySchema = require('./BasicDisplaySchema');
6 | const BasicCreateActionOperationSchema = require('./BasicCreateActionOperationSchema');
7 | const KeySchema = require('./KeySchema');
8 |
9 | module.exports = makeSchema(
10 | {
11 | id: '/CreateSchema',
12 | description: 'How will Zapier create a new object?',
13 | type: 'object',
14 | required: ['key', 'noun', 'display', 'operation'],
15 | examples: [
16 | {
17 | key: 'recipe',
18 | noun: 'Recipe',
19 | display: {
20 | label: 'Create Recipe',
21 | description: 'Creates a new recipe.'
22 | },
23 | operation: { perform: '$func$2$f$', sample: { id: 1 } }
24 | },
25 | {
26 | key: 'recipe',
27 | noun: 'Recipe',
28 | display: {
29 | label: 'Create Recipe',
30 | description: 'Creates a new recipe.'
31 | },
32 | operation: {
33 | perform: '$func$2$f$',
34 | sample: { id: 1 },
35 | shouldLock: true
36 | }
37 | },
38 | {
39 | key: 'recipe',
40 | noun: 'Recipe',
41 | display: {
42 | label: 'Create Recipe',
43 | description: 'Creates a new recipe.',
44 | hidden: true
45 | },
46 | operation: {
47 | perform: '$func$2$f$'
48 | }
49 | }
50 | ],
51 | antiExamples: [
52 | 'abc',
53 | {
54 | key: 'recipe',
55 | noun: 'Recipe',
56 | display: {
57 | label: 'Create Recipe',
58 | description: 'Creates a new recipe.'
59 | },
60 | operation: { perform: '$func$2$f$', shouldLock: 'yes' }
61 | },
62 | {
63 | key: 'recipe',
64 | noun: 'Recipe',
65 | display: {
66 | label: 'Create Recipe',
67 | description: 'Creates a new recipe.'
68 | },
69 | operation: {
70 | perform: '$func$2$f$'
71 | // sample is missing!
72 | }
73 | }
74 | ],
75 | properties: {
76 | key: {
77 | description: 'A key to uniquely identify this create.',
78 | $ref: KeySchema.id
79 | },
80 | noun: {
81 | description:
82 | 'A noun for this create that completes the sentence "creates a new XXX".',
83 | type: 'string',
84 | minLength: 2,
85 | maxLength: 255
86 | },
87 | display: {
88 | description: 'Configures the UI for this create.',
89 | $ref: BasicDisplaySchema.id
90 | },
91 | operation: {
92 | description: 'Powers the functionality for this create.',
93 | $ref: BasicCreateActionOperationSchema.id
94 | }
95 | },
96 | additionalProperties: false
97 | },
98 | [BasicDisplaySchema, BasicCreateActionOperationSchema, KeySchema]
99 | );
100 |
--------------------------------------------------------------------------------
/lib/schemas/RequestSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const FlatObjectSchema = require('./FlatObjectSchema');
6 |
7 | module.exports = makeSchema(
8 | {
9 | id: '/RequestSchema',
10 | description:
11 | 'A representation of a HTTP request - you can use the `{{syntax}}` to inject authentication, field or global variables.',
12 | type: 'object',
13 | properties: {
14 | method: {
15 | description: 'The HTTP method for the request.',
16 | type: 'string',
17 | default: 'GET',
18 | enum: ['GET', 'PUT', 'POST', 'PATCH', 'DELETE', 'HEAD']
19 | },
20 | url: {
21 | description:
22 | 'A URL for the request (we will parse the querystring and merge with params). Keys and values will not be re-encoded.',
23 | type: 'string'
24 | },
25 | body: {
26 | description: 'Can be nothing, a raw string or JSON (object or array).',
27 | oneOf: [
28 | { type: 'null' }, // nothing
29 | { type: 'string' }, // raw body
30 | { type: 'object' }, // json body object
31 | { type: 'array' } // json body array
32 | ]
33 | },
34 | params: {
35 | description:
36 | 'A mapping of the querystring - will get merged with any query params in the URL. Keys and values will be encoded.',
37 | $ref: FlatObjectSchema.id
38 | },
39 | headers: {
40 | description: 'The HTTP headers for the request.',
41 | $ref: FlatObjectSchema.id
42 | },
43 | auth: {
44 | description:
45 | "An object holding the auth parameters for OAuth1 request signing, like `{oauth_token: 'abcd', oauth_token_secret: '1234'}`. Or an array reserved (i.e. not implemented yet) to hold the username and password for Basic Auth. Like `['AzureDiamond', 'hunter2']`.",
46 | oneOf: [
47 | {
48 | type: 'array',
49 | items: {
50 | type: 'string',
51 | minProperties: 2,
52 | maxProperties: 2
53 | }
54 | },
55 | { $ref: FlatObjectSchema.id }
56 | ]
57 | },
58 | removeMissingValuesFrom: {
59 | description:
60 | 'Should missing values be sent? (empty strings, `null`, and `undefined` only — `[]`, `{}`, and `false` will still be sent). Allowed fields are `params` and `body`. The default is `false`, ex: ```removeMissingValuesFrom: { params: false, body: false }```',
61 | type: 'object',
62 | properties: {
63 | params: {
64 | description:
65 | 'Refers to data sent via a requests query params (`req.params`)',
66 | type: 'boolean',
67 | default: false
68 | },
69 | body: {
70 | description:
71 | 'Refers to tokens sent via a requsts body (`req.body`)',
72 | type: 'boolean',
73 | default: false
74 | }
75 | },
76 | additionalProperties: false
77 | }
78 | },
79 | additionalProperties: false
80 | },
81 | [FlatObjectSchema]
82 | );
83 |
--------------------------------------------------------------------------------
/lib/schemas/BasicHookOperationSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const BasicOperationSchema = require('./BasicOperationSchema');
6 | const FunctionSchema = require('./FunctionSchema');
7 | const RequestSchema = require('./RequestSchema');
8 |
9 | // TODO: would be nice to deep merge these instead
10 | // or maybe use allOf which is built into json-schema
11 | const BasicHookOperationSchema = JSON.parse(
12 | JSON.stringify(BasicOperationSchema.schema)
13 | );
14 |
15 | const hookTechnicallyRequired =
16 | 'Note: this is required for public apps to ensure the best UX for the end-user. For private apps, you can ignore warnings about this property with the `--without-style` flag during `zapier push`.';
17 |
18 | BasicHookOperationSchema.id = '/BasicHookOperationSchema';
19 |
20 | BasicHookOperationSchema.description =
21 | 'Represents the inbound mechanics of hooks with optional subscribe/unsubscribe. Defers to list for fields.';
22 |
23 | BasicHookOperationSchema.properties = {
24 | type: {
25 | description:
26 | 'Must be explicitly set to `"hook"` unless this hook is defined as part of a resource, in which case it\'s optional.',
27 | type: 'string',
28 | enum: ['hook'],
29 | docAnnotation: {
30 | required: {
31 | type: 'replace',
32 | value: '**yes** (with exceptions, see description)'
33 | }
34 | }
35 | },
36 | resource: BasicHookOperationSchema.properties.resource,
37 | perform: {
38 | description: 'A function that processes the inbound webhook request.',
39 | $ref: FunctionSchema.id
40 | },
41 | performList: {
42 | description:
43 | 'Can get "live" data on demand instead of waiting for a hook. If you find yourself reaching for this - consider resources and their built-in hook/list methods. ' +
44 | hookTechnicallyRequired,
45 | oneOf: [{ $ref: RequestSchema.id }, { $ref: FunctionSchema.id }],
46 | docAnnotation: {
47 | required: {
48 | type: 'replace',
49 | value: '**yes** (with exceptions, see description)'
50 | }
51 | }
52 | },
53 | performSubscribe: {
54 | description:
55 | 'Takes a URL and any necessary data from the user and subscribes. ' +
56 | hookTechnicallyRequired,
57 | oneOf: [{ $ref: RequestSchema.id }, { $ref: FunctionSchema.id }],
58 | docAnnotation: {
59 | required: {
60 | type: 'replace',
61 | value: '**yes** (with exceptions, see description)'
62 | }
63 | }
64 | },
65 | performUnsubscribe: {
66 | description:
67 | 'Takes a URL and data from a previous subscribe call and unsubscribes. ' +
68 | hookTechnicallyRequired,
69 | oneOf: [{ $ref: RequestSchema.id }, { $ref: FunctionSchema.id }],
70 | docAnnotation: {
71 | required: {
72 | type: 'replace',
73 | value: '**yes** (with exceptions, see description)'
74 | }
75 | }
76 | },
77 | inputFields: BasicHookOperationSchema.properties.inputFields,
78 | outputFields: BasicHookOperationSchema.properties.outputFields,
79 | sample: BasicHookOperationSchema.properties.sample
80 | };
81 |
82 | module.exports = makeSchema(
83 | BasicHookOperationSchema,
84 | BasicOperationSchema.dependencies
85 | );
86 |
--------------------------------------------------------------------------------
/lib/schemas/AuthenticationSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const AuthenticationBasicConfigSchema = require('./AuthenticationBasicConfigSchema.js');
6 | const AuthenticationCustomConfigSchema = require('./AuthenticationCustomConfigSchema.js');
7 | const AuthenticationDigestConfigSchema = require('./AuthenticationDigestConfigSchema.js');
8 | const AuthenticationOAuth1ConfigSchema = require('./AuthenticationOAuth1ConfigSchema.js');
9 | const AuthenticationOAuth2ConfigSchema = require('./AuthenticationOAuth2ConfigSchema.js');
10 | const AuthenticationSessionConfigSchema = require('./AuthenticationSessionConfigSchema.js');
11 | const FieldsSchema = require('./FieldsSchema');
12 | const FunctionSchema = require('./FunctionSchema');
13 | const RequestSchema = require('./RequestSchema');
14 |
15 | module.exports = makeSchema(
16 | {
17 | id: '/AuthenticationSchema',
18 | description: 'Represents authentication schemes.',
19 | examples: [
20 | { type: 'basic', test: '$func$2$f$' },
21 | { type: 'custom', test: '$func$2$f$', fields: [{ key: 'abc' }] },
22 | {
23 | type: 'custom',
24 | test: '$func$2$f$',
25 | connectionLabel: '{{bundle.inputData.abc}}'
26 | },
27 | { type: 'custom', test: '$func$2$f$', connectionLabel: '$func$2$f$' },
28 | { type: 'custom', test: '$func$2$f$', connectionLabel: { url: 'abc' } }
29 | ],
30 | antiExamples: [
31 | {},
32 | '$func$2$f$',
33 | { type: 'unknown', test: '$func$2$f$' },
34 | { type: 'custom', test: '$func$2$f$', fields: '$func$2$f$' },
35 | {
36 | type: 'custom',
37 | test: '$func$2$f$',
38 | fields: [{ key: 'abc' }, '$func$2$f$']
39 | }
40 | ],
41 | type: 'object',
42 | required: ['type', 'test'],
43 | properties: {
44 | type: {
45 | description: 'Choose which scheme you want to use.',
46 | type: 'string',
47 | enum: ['basic', 'custom', 'digest', 'oauth1', 'oauth2', 'session']
48 | },
49 | test: {
50 | description:
51 | 'A function or request that confirms the authentication is working.',
52 | oneOf: [{ $ref: RequestSchema.id }, { $ref: FunctionSchema.id }]
53 | },
54 | fields: {
55 | description:
56 | 'Fields you can request from the user before they connect your app to Zapier.',
57 | $ref: FieldsSchema.id
58 | },
59 | connectionLabel: {
60 | description:
61 | 'A string with variables, function, or request that returns the connection label for the authenticated user.',
62 | anyOf: [
63 | { $ref: RequestSchema.id },
64 | { $ref: FunctionSchema.id },
65 | { type: 'string' }
66 | ]
67 | },
68 | // this is preferred to laying out config: anyOf: [...]
69 | basicConfig: { $ref: AuthenticationBasicConfigSchema.id },
70 | customConfig: { $ref: AuthenticationCustomConfigSchema.id },
71 | digestConfig: { $ref: AuthenticationDigestConfigSchema.id },
72 | oauth1Config: { $ref: AuthenticationOAuth1ConfigSchema.id },
73 | oauth2Config: { $ref: AuthenticationOAuth2ConfigSchema.id },
74 | sessionConfig: { $ref: AuthenticationSessionConfigSchema.id }
75 | },
76 | additionalProperties: false
77 | },
78 | [
79 | FieldsSchema,
80 | FunctionSchema,
81 | RequestSchema,
82 | AuthenticationBasicConfigSchema,
83 | AuthenticationCustomConfigSchema,
84 | AuthenticationDigestConfigSchema,
85 | AuthenticationOAuth1ConfigSchema,
86 | AuthenticationOAuth2ConfigSchema,
87 | AuthenticationSessionConfigSchema
88 | ]
89 | );
90 |
--------------------------------------------------------------------------------
/smoke-test/smoke-test.js:
--------------------------------------------------------------------------------
1 | const { spawnSync } = require('child_process');
2 | const crypto = require('crypto');
3 | const fs = require('fs-extra');
4 | const os = require('os');
5 | const path = require('path');
6 |
7 | require('should');
8 | const fetch = require('node-fetch');
9 |
10 | const npmPack = () => {
11 | let filename;
12 | const proc = spawnSync('npm', ['pack'], { encoding: 'utf8' });
13 | const lines = proc.stdout.split('\n');
14 | for (let i = lines.length - 1; i >= 0; i--) {
15 | const line = lines[i].trim();
16 | if (line) {
17 | filename = line;
18 | break;
19 | }
20 | }
21 | return filename;
22 | };
23 |
24 | const setupTempWorkingDir = () => {
25 | let workdir;
26 | const tmpBaseDir = os.tmpdir();
27 | while (!workdir || fs.existsSync(workdir)) {
28 | workdir = path.join(tmpBaseDir, crypto.randomBytes(20).toString('hex'));
29 | }
30 | fs.mkdirSync(workdir);
31 | return workdir;
32 | };
33 |
34 | const npmInstall = (packagePath, workdir) => {
35 | spawnSync('npm', ['install', packagePath], {
36 | encoding: 'utf8',
37 | cwd: workdir
38 | });
39 | };
40 |
41 | const copyTestScript = (filename, workdir) => {
42 | const dest = path.join(workdir, filename);
43 | fs.copyFileSync(path.join(__dirname, filename), dest);
44 | return dest;
45 | };
46 |
47 | describe('smoke tests - setup will take some time', () => {
48 | const context = {
49 | // Global context that will be available for all test cases in this test suite
50 | package: {
51 | filename: null,
52 | path: null
53 | },
54 | workdir: null,
55 | testScripts: {
56 | validate: null,
57 | export: null
58 | }
59 | };
60 |
61 | before(() => {
62 | context.package.filename = npmPack();
63 | context.package.path = path.join(process.cwd(), context.package.filename);
64 |
65 | context.workdir = setupTempWorkingDir();
66 |
67 | npmInstall(context.package.path, context.workdir);
68 |
69 | context.testScripts.validate = copyTestScript(
70 | 'test-validate',
71 | context.workdir
72 | );
73 | context.testScripts.export = copyTestScript('test-export', context.workdir);
74 | });
75 |
76 | after(() => {
77 | fs.unlinkSync(context.package.path);
78 | fs.removeSync(context.workdir);
79 | });
80 |
81 | it('package size should not change much', async () => {
82 | const baseUrl = 'https://registry.npmjs.org/zapier-platform-schema';
83 | let res = await fetch(baseUrl);
84 | const packageInfo = await res.json();
85 | const latestVersion = packageInfo['dist-tags'].latest;
86 |
87 | res = await fetch(
88 | `${baseUrl}/-/zapier-platform-schema-${latestVersion}.tgz`,
89 | {
90 | method: 'HEAD'
91 | }
92 | );
93 | const baselineSize = res.headers.get('content-length');
94 | const newSize = fs.statSync(context.package.path).size;
95 | newSize.should.be.within(baselineSize * 0.7, baselineSize * 1.3);
96 | });
97 |
98 | it('should to able to validate app definitions', () => {
99 | const proc = spawnSync(context.testScripts.validate, {
100 | encoding: 'utf8',
101 | cwd: context.workdir
102 | });
103 | const results = JSON.parse(proc.stdout);
104 | results.length.should.eql(2);
105 | results[0].length.should.eql(0);
106 | results[1].length.should.eql(2);
107 | results[1][0].should.eql('requires property "version"');
108 | results[1][1].should.eql('requires property "platformVersion"');
109 | });
110 |
111 | it('exported-schema.json should exist and be up-to-date', () => {
112 | const exportedSchemaPath = path.join(
113 | context.workdir,
114 | 'node_modules',
115 | 'zapier-platform-schema',
116 | 'exported-schema.json'
117 | );
118 | fs.existsSync(exportedSchemaPath).should.be.true();
119 |
120 | const content = fs.readFileSync(exportedSchemaPath, { encoding: 'utf8' });
121 | const schemaInPackage = JSON.parse(content);
122 |
123 | const proc = spawnSync(context.testScripts.export, {
124 | encoding: 'utf8',
125 | cwd: context.workdir
126 | });
127 | const expectedSchema = JSON.parse(proc.stdout);
128 |
129 | schemaInPackage.should.eql(
130 | expectedSchema,
131 | 'exported-schema.json is not up-to-date. Try `npm run export`.'
132 | );
133 | });
134 | });
135 |
--------------------------------------------------------------------------------
/lib/schemas/AppSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const AuthenticationSchema = require('./AuthenticationSchema');
6 | const FlatObjectSchema = require('./FlatObjectSchema');
7 | const ResourcesSchema = require('./ResourcesSchema');
8 | const TriggersSchema = require('./TriggersSchema');
9 | const SearchesSchema = require('./SearchesSchema');
10 | const CreatesSchema = require('./CreatesSchema');
11 | const SearchOrCreatesSchema = require('./SearchOrCreatesSchema');
12 | const RequestSchema = require('./RequestSchema');
13 | const VersionSchema = require('./VersionSchema');
14 | const MiddlewaresSchema = require('./MiddlewaresSchema');
15 | const HydratorsSchema = require('./HydratorsSchema');
16 | const AppFlagsSchema = require('./AppFlagsSchema');
17 |
18 | module.exports = makeSchema(
19 | {
20 | id: '/AppSchema',
21 | description: 'Represents a full app.',
22 | type: 'object',
23 | required: ['version', 'platformVersion'],
24 | properties: {
25 | version: {
26 | description: 'A version identifier for your code.',
27 | $ref: VersionSchema.id
28 | },
29 | platformVersion: {
30 | description:
31 | 'A version identifier for the Zapier execution environment.',
32 | $ref: VersionSchema.id
33 | },
34 | beforeApp: {
35 | description:
36 | 'EXPERIMENTAL: Before the perform method is called on your app, you can modify the execution context.',
37 | $ref: MiddlewaresSchema.id
38 | },
39 | afterApp: {
40 | description:
41 | 'EXPERIMENTAL: After the perform method is called on your app, you can modify the response.',
42 | $ref: MiddlewaresSchema.id
43 | },
44 | authentication: {
45 | description: 'Choose what scheme your API uses for authentication.',
46 | $ref: AuthenticationSchema.id
47 | },
48 | requestTemplate: {
49 | description:
50 | 'Define a request mixin, great for setting custom headers, content-types, etc.',
51 | $ref: RequestSchema.id
52 | },
53 | beforeRequest: {
54 | description:
55 | 'Before an HTTP request is sent via our `z.request()` client, you can modify it.',
56 | $ref: MiddlewaresSchema.id
57 | },
58 | afterResponse: {
59 | description:
60 | 'After an HTTP response is recieved via our `z.request()` client, you can modify it.',
61 | $ref: MiddlewaresSchema.id
62 | },
63 | hydrators: {
64 | description:
65 | "An optional bank of named functions that you can use in `z.hydrate('someName')` to lazily load data.",
66 | $ref: HydratorsSchema.id
67 | },
68 | resources: {
69 | description:
70 | 'All the resources for your app. Zapier will take these and generate the relevent triggers/searches/creates automatically.',
71 | $ref: ResourcesSchema.id
72 | },
73 | triggers: {
74 | description:
75 | 'All the triggers for your app. You can add your own here, or Zapier will automatically register any from the list/hook methods on your resources.',
76 | $ref: TriggersSchema.id
77 | },
78 | searches: {
79 | description:
80 | 'All the searches for your app. You can add your own here, or Zapier will automatically register any from the search method on your resources.',
81 | $ref: SearchesSchema.id
82 | },
83 | creates: {
84 | description:
85 | 'All the creates for your app. You can add your own here, or Zapier will automatically register any from the create method on your resources.',
86 | $ref: CreatesSchema.id
87 | },
88 | searchOrCreates: {
89 | description:
90 | '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.',
91 | $ref: SearchOrCreatesSchema.id
92 | },
93 | flags: {
94 | description: 'Top-level app options',
95 | $ref: AppFlagsSchema.id
96 | },
97 | legacy: {
98 | description:
99 | '**INTERNAL USE ONLY**. Zapier uses this to hold properties from a legacy Web Builder app.',
100 | type: 'object',
101 | docAnnotation: {
102 | hide: true
103 | }
104 | }
105 | },
106 | additionalProperties: false
107 | },
108 | [
109 | AuthenticationSchema,
110 | FlatObjectSchema,
111 | ResourcesSchema,
112 | TriggersSchema,
113 | SearchesSchema,
114 | CreatesSchema,
115 | SearchOrCreatesSchema,
116 | RequestSchema,
117 | VersionSchema,
118 | MiddlewaresSchema,
119 | HydratorsSchema,
120 | AppFlagsSchema
121 | ]
122 | );
123 |
--------------------------------------------------------------------------------
/test/readability.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const should = require('should');
4 |
5 | const AuthenticationSchema = require('../lib/schemas/AuthenticationSchema');
6 | const CreateSchema = require('../lib/schemas/CreateSchema');
7 | const TriggerSchema = require('../lib/schemas/TriggerSchema');
8 |
9 | describe('readability', () => {
10 | it('should have decent messages for anyOf mismatches', () => {
11 | const results = AuthenticationSchema.validate({
12 | type: 'oauth2',
13 | test: 'whateverfake!'
14 | });
15 | results.errors.should.have.length(1);
16 | results.errors[0].stack.should.eql('instance is not of a type(s) object');
17 | should(results.errors[0].property.endsWith('instance')).be.false();
18 | });
19 |
20 | it('should have decent messages for minimum length not met', () => {
21 | const results = TriggerSchema.validate({
22 | key: 'recipe',
23 | noun: 'Recipe',
24 | display: {
25 | label: '',
26 | description: 'Creates a new recipe.'
27 | },
28 | operation: {
29 | perform: '$func$2$f$',
30 | sample: { id: 1 }
31 | }
32 | });
33 | results.errors.should.have.length(1);
34 | should(results.errors[0].property.endsWith('instance')).be.false();
35 | results.errors[0].stack.should.eql(
36 | 'instance.display.label does not meet minimum length of 2'
37 | );
38 | });
39 |
40 | it('should have decent messages for value type mismatch', () => {
41 | const results = CreateSchema.validate({
42 | key: 'recipe',
43 | noun: 'Recipe',
44 | display: {
45 | label: 'Create Recipe',
46 | description: 'Creates a new recipe.'
47 | },
48 | operation: {
49 | perform: '$func$2$f$',
50 | sample: { id: 1 },
51 | inputFields: [123]
52 | }
53 | });
54 | results.errors.should.have.length(1);
55 | should(results.errors[0].property.endsWith('instance')).be.false();
56 | results.errors[0].stack.should.eql('instance is not of a type(s) object');
57 | });
58 |
59 | it('should handle falsy values for objects', () => {
60 | const results = CreateSchema.validate({
61 | key: 'recipe',
62 | noun: 'Recipe',
63 | display: {
64 | label: 'Create Recipe',
65 | description: 'Creates a new recipe.'
66 | },
67 | operation: {
68 | perform: '$func$2$f$',
69 | sample: { id: 1 },
70 | inputFields: [0]
71 | }
72 | });
73 | results.errors.should.have.length(1);
74 | should(results.errors[0].property.endsWith('instance')).be.false();
75 | results.errors[0].stack.should.eql('instance is not of a type(s) object');
76 | });
77 |
78 | it('should surface deep issues', () => {
79 | const results = CreateSchema.validate({
80 | key: 'recipe',
81 | noun: 'Recipe',
82 | display: {
83 | label: 'Create Recipe',
84 | description: 'Creates a new recipe.'
85 | },
86 | operation: {
87 | perform: '$func$2$f$',
88 | sample: { id: 1 },
89 | inputFields: [{ key: 'field', type: 'string', default: '' }]
90 | }
91 | });
92 | results.errors.should.have.length(1);
93 | should(results.errors[0].property.endsWith('instance')).be.false();
94 | results.errors[0].property.should.eql(
95 | 'instance.operation.inputFields[0].default'
96 | );
97 | results.errors[0].message.should.eql('does not meet minimum length of 1');
98 | });
99 |
100 | it('should correctly surface subschema types', () => {
101 | const results = CreateSchema.validate({
102 | key: 'recipe',
103 | noun: 'Recipe',
104 | display: {
105 | label: 'Create Recipe',
106 | description: 'Creates a new recipe.'
107 | },
108 | operation: {
109 | perform: {
110 | url: 'https://example.com',
111 | body: 123
112 | },
113 | sample: { id: 1 }
114 | }
115 | });
116 | results.errors.should.have.length(1);
117 | results.errors[0].property.should.eql('instance.operation.perform.body');
118 | should(
119 | results.errors[0].message.includes('null,string,object,array')
120 | ).be.true();
121 | should(results.errors[0].property.endsWith('instance')).be.false();
122 | results.errors[0].docLinks.length.should.eql(0);
123 | });
124 |
125 | it('should be helpful for fieldChoices', () => {
126 | const results = CreateSchema.validate({
127 | key: 'recipe',
128 | noun: 'Recipe',
129 | display: {
130 | label: 'Create Recipe',
131 | description: 'Creates a new recipe.'
132 | },
133 | operation: {
134 | perform: '$func$2$f$',
135 | sample: { id: 1 },
136 | inputFields: [
137 | {
138 | key: 'adsf',
139 | // schema says these should be strings
140 | choices: [1, 2, 3]
141 | }
142 | ]
143 | }
144 | });
145 | results.errors.should.have.length(1);
146 | results.errors[0].property.should.eql(
147 | 'instance.operation.inputFields[0].choices'
148 | );
149 | should(
150 | results.errors[0].docLinks[0].endsWith('schema.md#fieldchoicesschema')
151 | ).be.true();
152 | should(results.errors[0].property.endsWith('instance')).be.false();
153 | });
154 | });
155 |
--------------------------------------------------------------------------------
/lib/schemas/FieldSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const RefResourceSchema = require('./RefResourceSchema');
6 |
7 | const FieldChoicesSchema = require('./FieldChoicesSchema');
8 |
9 | const { SKIP_KEY, INCOMPATIBLE_FIELD_SCHEMA_KEYS } = require('../constants');
10 |
11 | // the following takes an array of string arrays (string[][]) and returns the follwing string:
12 | // * `a` & `b`
13 | // * `c` & `d`
14 | // ... etc
15 | const wrapInBackticks = s => `\`${s}\``;
16 | const formatBullet = f => `* ${f.map(wrapInBackticks).join(' & ')}`;
17 | const incompatibleFieldsList = INCOMPATIBLE_FIELD_SCHEMA_KEYS.map(
18 | formatBullet
19 | ).join('\n');
20 |
21 | module.exports = makeSchema(
22 | {
23 | id: '/FieldSchema',
24 | description: `Defines a field an app either needs as input, or gives as output. In addition to the requirements below, the following keys are mutually exclusive:\n\n${incompatibleFieldsList}`,
25 | type: 'object',
26 | examples: [
27 | { key: 'abc' },
28 | { key: 'abc', choices: { mobile: 'Mobile Phone' } },
29 | { key: 'abc', choices: ['first', 'second', 'third'] },
30 | {
31 | key: 'abc',
32 | choices: [{ label: 'Red', sample: '#f00', value: '#f00' }]
33 | },
34 | { key: 'abc', children: [{ key: 'abc' }] },
35 | { key: 'abc', type: 'integer', helpText: 'neat' }
36 | ],
37 | antiExamples: [
38 | {},
39 | { key: 'abc', choices: {} },
40 | { key: 'abc', choices: [] },
41 | { key: 'abc', choices: [3] },
42 | { key: 'abc', choices: [{ label: 'Red', value: '#f00' }] },
43 | { key: 'abc', choices: 'mobile' },
44 | { key: 'abc', type: 'loltype' },
45 | { key: 'abc', children: [], helpText: '' },
46 | {
47 | key: 'abc',
48 | children: [{ key: 'def', children: [] }]
49 | },
50 | {
51 | key: 'abc',
52 | children: [{ key: 'def', children: [{ key: 'dhi' }] }],
53 | [SKIP_KEY]: true
54 | },
55 | { key: 'abc', children: ['$func$2$f$'] }
56 | ],
57 | required: ['key'],
58 | properties: {
59 | key: {
60 | description:
61 | 'A unique machine readable key for this value (IE: "fname").',
62 | type: 'string',
63 | minLength: 1
64 | },
65 | label: {
66 | description:
67 | 'A human readable label for this value (IE: "First Name").',
68 | type: 'string',
69 | minLength: 1
70 | },
71 | helpText: {
72 | description:
73 | 'A human readable description of this value (IE: "The first part of a full name."). You can use Markdown.',
74 | type: 'string',
75 | minLength: 1,
76 | maxLength: 1000
77 | },
78 | type: {
79 | description: 'The type of this value.',
80 | type: 'string',
81 | // string == unicode
82 | // text == a long textarea string
83 | // integer == int
84 | // number == float
85 | enum: [
86 | 'string',
87 | 'text',
88 | 'integer',
89 | 'number',
90 | 'boolean',
91 | 'datetime',
92 | 'file',
93 | 'password',
94 | 'copy'
95 | ]
96 | },
97 | required: {
98 | description: 'If this value is required or not.',
99 | type: 'boolean'
100 | },
101 | placeholder: {
102 | description: 'An example value that is not saved.',
103 | type: 'string',
104 | minLength: 1
105 | },
106 | default: {
107 | description:
108 | 'A default value that is saved the first time a Zap is created.',
109 | type: 'string',
110 | minLength: 1
111 | },
112 | dynamic: {
113 | description:
114 | 'A reference to a trigger that will power a dynamic dropdown.',
115 | $ref: RefResourceSchema.id
116 | },
117 | search: {
118 | description:
119 | 'A reference to a search that will guide the user to add a search step to populate this field when creating a Zap.',
120 | $ref: RefResourceSchema.id
121 | },
122 | choices: {
123 | description:
124 | 'An object of machine keys and human values to populate a static dropdown.',
125 | $ref: FieldChoicesSchema.id
126 | },
127 | list: {
128 | description: 'Can a user provide multiples of this field?',
129 | type: 'boolean'
130 | },
131 | children: {
132 | type: 'array',
133 | items: { $ref: '/FieldSchema' },
134 | description:
135 | 'An array of child fields that define the structure of a sub-object for this field. Usually used for line items.',
136 | minItems: 1
137 | },
138 | dict: {
139 | description: 'Is this field a key/value input?',
140 | type: 'boolean'
141 | },
142 | computed: {
143 | description:
144 | 'Is this field automatically populated (and hidden from the user)?',
145 | type: 'boolean'
146 | },
147 | altersDynamicFields: {
148 | description:
149 | 'Does the value of this field affect the definitions of other fields in the set?',
150 | type: 'boolean'
151 | },
152 | inputFormat: {
153 | description:
154 | 'Useful when you expect the input to be part of a longer string. Put "{{input}}" in place of the user\'s input (IE: "https://{{input}}.yourdomain.com").',
155 | type: 'string',
156 | // TODO: Check if it contains one and ONLY ONE '{{input}}'
157 | pattern: '^.*{{input}}.*$'
158 | }
159 | },
160 | additionalProperties: false
161 | },
162 | [RefResourceSchema, FieldChoicesSchema]
163 | );
164 |
--------------------------------------------------------------------------------
/lib/utils/makeValidator.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const jsonschema = require('jsonschema');
4 | const links = require('./links');
5 | const functionalConstraints = require('../functional-constraints');
6 | const { flattenDeep, get } = require('lodash');
7 |
8 | const ambiguousTypes = ['anyOf', 'oneOf', 'allOf'];
9 |
10 | const makeLinks = (error, makerFunc) => {
11 | if (typeof error.schema === 'string') {
12 | return [makerFunc(error.schema)];
13 | }
14 | if (
15 | ambiguousTypes.includes(error.name) &&
16 | error.argument &&
17 | error.argument.length
18 | ) {
19 | // no way to know what the subschema was, so don't create links for it
20 | return error.argument
21 | .map(s => (s.includes('subschema') ? '' : makerFunc(s)))
22 | .filter(Boolean);
23 | }
24 | return [];
25 | };
26 |
27 | const removeFirstAndLastChar = s => s.slice(1, -1);
28 | // always return a string
29 | const makePath = (path, newSegment) =>
30 | (path ? [path, newSegment].join('.') : newSegment) || '';
31 |
32 | const processBaseError = (err, path) => {
33 | const completePath = makePath(path, err.property)
34 | .replace(/\.instance\.?/g, '.')
35 | .replace(/\.instance$/, '');
36 |
37 | const subSchemas = err.message.match(/\[subschema \d+\]/g);
38 | if (subSchemas) {
39 | subSchemas.forEach((subschema, idx) => {
40 | // err.schema is either an anonymous schema object or the name of a named schema
41 | if (typeof err.schema === 'string') {
42 | // this is basically only for FieldChoicesSchema and I'm not sure why
43 | err.message += ' Consult the docs below for valid subschemas.';
44 | } else {
45 | // the subschemas have a type property
46 | err.message = err.message.replace(
47 | subschema,
48 | err.schema[err.name][idx].type || 'unknown'
49 | );
50 | }
51 | });
52 | }
53 |
54 | err.property = completePath;
55 | return err;
56 | };
57 |
58 | /**
59 | * We have a lot of `anyOf` schemas that return ambiguous errors. This recurses down the schema until it finds the errors that cause the failures replaces the ambiguity.
60 | * @param {ValidationError} validationError an individual error
61 | * @param {string} path current path in the error chain
62 | * @param {Validator} validator validator object to pass around that has all the schemas
63 | * @param {object} definition the original schema we're defining
64 | */
65 | const cleanError = (validationError, path, validator, definition) => {
66 | if (ambiguousTypes.includes(validationError.name)) {
67 | // flatObjectSchema requires each property to be a type. instead of recursing down, it's more valuable to say "hey, it's not of these types"
68 | if (validationError.argument.every(s => s.includes('subschema'))) {
69 | return processBaseError(validationError, path);
70 | }
71 |
72 | // Try against each of A, B, and C to take a guess as to which it's closed to
73 | // errorGroups will be an array of arrays of errors
74 | const errorGroups = validationError.argument.map((schemaName, idx) => {
75 | // this is what we'll validate against next
76 | let nextSchema;
77 | // schemaName is either "[subschema n]" or "/NamedSchema"
78 | if (schemaName.startsWith('[subschema')) {
79 | const maybeNamedSchema = validator.schemas[validationError.schema];
80 |
81 | if (maybeNamedSchema) {
82 | nextSchema = maybeNamedSchema[validationError.name][idx];
83 | } else {
84 | // hoist the anonymous subschema up
85 | nextSchema = validationError.schema[validationError.name][idx];
86 | }
87 | } else {
88 | nextSchema = validator.schemas[removeFirstAndLastChar(schemaName)];
89 | }
90 |
91 | if (validationError.instance === undefined) {
92 | // Work around a jsonschema bug: When the value being validated is
93 | // falsy, validationError.instance isn't available
94 | // See https://github.com/tdegrunt/jsonschema/issues/263
95 | const fullPath =
96 | path.replace(/^instance\./, '') +
97 | validationError.property.replace(/^instance\./, '');
98 | validationError.instance = get(definition, fullPath);
99 | }
100 |
101 | const res = validator.validate(validationError.instance, nextSchema);
102 |
103 | return res.errors.map(e =>
104 | cleanError(
105 | e,
106 | makePath(path, validationError.property),
107 | validator,
108 | definition
109 | )
110 | );
111 | });
112 |
113 | // find the group with the fewest errors, that's probably the most accurate
114 | // if we're goign to tweak what gets returned, this is where we'll do it
115 | // a possible improvement could be treating a longer path favorably, like the python implementation does
116 | errorGroups.sort((a, b) => a.length - b.length);
117 | return errorGroups[0];
118 | } else {
119 | // base case
120 | return processBaseError(validationError, path);
121 | }
122 | };
123 |
124 | const makeValidator = (mainSchema, subSchemas) => {
125 | const schemas = [mainSchema].concat(subSchemas || []);
126 | const v = new jsonschema.Validator();
127 | schemas.forEach(Schema => {
128 | v.addSchema(Schema, Schema.id);
129 | });
130 | return {
131 | validate: definition => {
132 | const results = v.validate(definition, mainSchema);
133 | const allErrors = results.errors.concat(
134 | functionalConstraints.run(definition, mainSchema)
135 | );
136 | const cleanedErrors = flattenDeep(
137 | allErrors.map(e => cleanError(e, '', v, definition))
138 | );
139 |
140 | results.errors = cleanedErrors.map(error => {
141 | error.codeLinks = makeLinks(error, links.makeCodeLink);
142 | error.docLinks = makeLinks(error, links.makeDocLink);
143 | return error;
144 | });
145 | return results;
146 | }
147 | };
148 | };
149 |
150 | module.exports = makeValidator;
151 |
--------------------------------------------------------------------------------
/test/functional-constraints/mutuallyExclusiveFields.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('should');
4 | const schema = require('../../schema');
5 |
6 | describe('mutuallyExclusiveFields', () => {
7 | it('should not error on fields not mutually exclusive', () => {
8 | const definition = {
9 | version: '1.0.0',
10 | platformVersion: '1.0.0',
11 | creates: {
12 | foo: {
13 | key: 'foo',
14 | noun: 'Foo',
15 | display: {
16 | label: 'Create Foo',
17 | description: 'Creates a...'
18 | },
19 | operation: {
20 | perform: '$func$2$f$',
21 | sample: { id: 1 },
22 | inputFields: [
23 | { key: 'orderId', type: 'number' },
24 | {
25 | key: 'line_items',
26 | children: [
27 | {
28 | key: 'product',
29 | type: 'string'
30 | }
31 | ]
32 | }
33 | ]
34 | }
35 | }
36 | }
37 | };
38 |
39 | const results = schema.validateAppDefinition(definition);
40 | results.errors.should.have.length(0);
41 | });
42 |
43 | it('should error on fields that have children and list', () => {
44 | const definition = {
45 | version: '1.0.0',
46 | platformVersion: '1.0.0',
47 | creates: {
48 | foo: {
49 | key: 'foo',
50 | noun: 'Foo',
51 | display: {
52 | label: 'Create Foo',
53 | description: 'Creates a...'
54 | },
55 | operation: {
56 | perform: '$func$2$f$',
57 | sample: { id: 1 },
58 | inputFields: [
59 | { key: 'orderId', type: 'number' },
60 | {
61 | key: 'line_items',
62 | children: [
63 | {
64 | key: 'product'
65 | }
66 | ],
67 | list: true
68 | }
69 | ]
70 | }
71 | }
72 | }
73 | };
74 |
75 | const results = schema.validateAppDefinition(definition);
76 | results.errors.should.have.length(1);
77 | results.errors[0].stack.should.eql(
78 | "instance.creates.foo.inputFields[1] must not contain children and list, as they're mutually exclusive."
79 | );
80 | });
81 |
82 | it('should error on fields that have list and dict', () => {
83 | const definition = {
84 | version: '1.0.0',
85 | platformVersion: '1.0.0',
86 | creates: {
87 | foo: {
88 | key: 'foo',
89 | noun: 'Foo',
90 | display: {
91 | label: 'Create Foo',
92 | description: 'Creates a...'
93 | },
94 | operation: {
95 | perform: '$func$2$f$',
96 | sample: { id: 1 },
97 | inputFields: [
98 | { key: 'orderId', type: 'number' },
99 | {
100 | key: 'line_items',
101 | dict: true,
102 | list: true
103 | }
104 | ]
105 | }
106 | }
107 | }
108 | };
109 |
110 | const results = schema.validateAppDefinition(definition);
111 | results.errors.should.have.length(1);
112 | results.errors[0].stack.should.eql(
113 | "instance.creates.foo.inputFields[1] must not contain dict and list, as they're mutually exclusive."
114 | );
115 | });
116 |
117 | it('should error on fields that have dynamic and dict', () => {
118 | const definition = {
119 | version: '1.0.0',
120 | platformVersion: '1.0.0',
121 | creates: {
122 | foo: {
123 | key: 'foo',
124 | noun: 'Foo',
125 | display: {
126 | label: 'Create Foo',
127 | description: 'Creates a...'
128 | },
129 | operation: {
130 | perform: '$func$2$f$',
131 | sample: { id: 1 },
132 | inputFields: [
133 | {
134 | key: 'orderId',
135 | type: 'number',
136 | dynamic: 'foo.id.number',
137 | dict: true
138 | },
139 | {
140 | key: 'line_items',
141 | list: true
142 | }
143 | ]
144 | }
145 | }
146 | }
147 | };
148 |
149 | const results = schema.validateAppDefinition(definition);
150 | results.errors.should.have.length(1);
151 | results.errors[0].stack.should.eql(
152 | "instance.creates.foo.inputFields[0] must not contain dynamic and dict, as they're mutually exclusive."
153 | );
154 | });
155 |
156 | it('should error on fields that have dynamic and choices', () => {
157 | const definition = {
158 | version: '1.0.0',
159 | platformVersion: '1.0.0',
160 | creates: {
161 | foo: {
162 | key: 'foo',
163 | noun: 'Foo',
164 | display: {
165 | label: 'Create Foo',
166 | description: 'Creates a...'
167 | },
168 | operation: {
169 | perform: '$func$2$f$',
170 | sample: { id: 1 },
171 | inputFields: [
172 | {
173 | key: 'orderId',
174 | type: 'number',
175 | dynamic: 'foo.id.number',
176 | choices: {
177 | uno: 1,
178 | dos: 2
179 | }
180 | },
181 | {
182 | key: 'line_items',
183 | list: true
184 | }
185 | ]
186 | }
187 | }
188 | }
189 | };
190 |
191 | const results = schema.validateAppDefinition(definition);
192 | results.errors.should.have.length(1);
193 | results.errors[0].stack.should.eql(
194 | "instance.creates.foo.inputFields[0] must not contain dynamic and choices, as they're mutually exclusive."
195 | );
196 | });
197 | });
198 |
--------------------------------------------------------------------------------
/lib/utils/buildDocs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const util = require('util');
4 |
5 | const _ = require('lodash');
6 | const toc = require('markdown-toc');
7 |
8 | const packageJson = require('../../package.json');
9 | const links = require('./links');
10 |
11 | const NO_DESCRIPTION = '_No description given._';
12 | const COMBOS = ['anyOf', 'allOf', 'oneOf'];
13 | const { SKIP_KEY } = require('../constants');
14 |
15 | const walkSchemas = (InitSchema, callback) => {
16 | const recurse = (Schema, parents) => {
17 | parents = parents || [];
18 | callback(Schema, parents);
19 | Schema.dependencies.map(childSchema => {
20 | const newParents = parents.concat([InitSchema]);
21 | recurse(childSchema, newParents);
22 | });
23 | };
24 | recurse(InitSchema);
25 | };
26 |
27 | const collectSchemas = InitSchema => {
28 | const schemas = {};
29 | walkSchemas(InitSchema, Schema => {
30 | schemas[Schema.id] = Schema;
31 | });
32 | return schemas;
33 | };
34 |
35 | const BREAK_LENGTH = 96;
36 | const prepQuote = val => val.replace('`', '');
37 | const quote = (val, triple, indent = '') =>
38 | // either ``` with optional indentation or `
39 | triple && val.length > BREAK_LENGTH
40 | ? '```\n' +
41 | val
42 | .match(/[^\r\n]+/g)
43 | .map(line => indent + line)
44 | .join('\n') +
45 | '\n' +
46 | indent +
47 | '```'
48 | : `\`${prepQuote(val)}\``;
49 | const quoteOrNa = (val, triple = false, indent = '') =>
50 | val ? quote(val, triple, indent) : '_n/a_';
51 |
52 | const formatExample = example => {
53 | const ex = _.isPlainObject(example) ? _.omit(example, SKIP_KEY) : example;
54 | return `* ${quoteOrNa(
55 | util.inspect(ex, { depth: null, breakLength: BREAK_LENGTH }),
56 | true,
57 | ' '
58 | )}`;
59 | };
60 |
61 | // Generate a display of the type (or link to a $ref).
62 | const typeOrLink = schema => {
63 | if (schema.type === 'array' && schema.items) {
64 | return `${quoteOrNa(schema.type)}[${typeOrLink(schema.items)}]`;
65 | }
66 | if (schema.$ref) {
67 | return `[${schema.$ref}](${links.anchor(schema.$ref)})`;
68 | }
69 | for (let i = 0; i < COMBOS.length; i++) {
70 | const key = COMBOS[i];
71 | if (schema[key] && schema[key].length) {
72 | return `${key}(${schema[key].map(typeOrLink).join(', ')})`;
73 | }
74 | }
75 | if (schema.enum && schema.enum.length) {
76 | return `${quoteOrNa(schema.type)} in (${schema.enum
77 | .map(util.inspect)
78 | .map(quoteOrNa)
79 | .join(', ')})`;
80 | }
81 | return quoteOrNa(schema.type);
82 | };
83 |
84 | // Properly quote and display examples.
85 | const makeExampleSection = Schema => {
86 | const examples = Schema.schema.examples || [];
87 | if (!examples.length) {
88 | return '';
89 | }
90 | return `\
91 | #### Examples
92 |
93 | ${examples.map(formatExample).join('\n')}
94 | `;
95 | };
96 |
97 | // Properly quote and display anti-examples.
98 | const makeAntiExampleSection = Schema => {
99 | const examples = Schema.schema.antiExamples || [];
100 | if (!examples.length) {
101 | return '';
102 | }
103 | return `\
104 | #### Anti-Examples
105 |
106 | ${examples.map(formatExample).join('\n')}
107 | `;
108 | };
109 |
110 | const processProperty = (key, property, propIsRequired) => {
111 | let isRequired = propIsRequired ? '**yes**' : 'no';
112 | if (_.get(property, 'docAnnotation.hide')) {
113 | return '';
114 | } else if (_.get(property, 'docAnnotation.required')) {
115 | // can also support keys besides "required"
116 | const annotation = property.docAnnotation.required;
117 | if (annotation.type === 'replace') {
118 | isRequired = annotation.value;
119 | } else if (annotation.type === 'append') {
120 | isRequired += annotation.value;
121 | } else {
122 | throw new Error(`unrecognized docAnnotation type: ${annotation.type}`);
123 | }
124 | }
125 | return `${quoteOrNa(key)} | ${isRequired} | ${typeOrLink(
126 | property
127 | )} | ${property.description || NO_DESCRIPTION}`;
128 | };
129 |
130 | // Enumerate the properties as a table.
131 | const makePropertiesSection = Schema => {
132 | const properties =
133 | Schema.schema.properties || Schema.schema.patternProperties || {};
134 | if (!Object.keys(properties).length) {
135 | return '';
136 | }
137 | const required = Schema.schema.required || [];
138 | return `\
139 | #### Properties
140 |
141 | Key | Required | Type | Description
142 | --- | -------- | ---- | -----------
143 | ${Object.keys(properties)
144 | .map(key => {
145 | const property = properties[key];
146 | return processProperty(key, property, required.includes(key));
147 | })
148 | .join('\n')}
149 | `;
150 | };
151 |
152 | // Given a "root" schema, create some markdown.
153 | const makeMarkdownSection = Schema => {
154 | return `\
155 | ## ${Schema.id}
156 |
157 | ${Schema.schema.description || NO_DESCRIPTION}
158 |
159 | #### Details
160 |
161 | * **Type** - ${typeOrLink(Schema.schema)}
162 | * **Pattern** - ${quoteOrNa(Schema.schema.pattern)}
163 | * **Source Code** - [lib/schemas${Schema.id}.js](${links.makeCodeLink(
164 | Schema.id
165 | )})
166 |
167 | ${makeExampleSection(Schema)}
168 | ${makeAntiExampleSection(Schema)}
169 | ${makePropertiesSection(Schema)}
170 | `.trim();
171 | };
172 |
173 | // Generate the final markdown.
174 | const buildDocs = InitSchema => {
175 | const schemas = collectSchemas(InitSchema);
176 | const markdownSections = _.chain(schemas)
177 | .values()
178 | .sortBy('id')
179 | .map(makeMarkdownSection)
180 | .join('\n\n-----\n\n');
181 | const docs = `\
182 |
183 | # \`zapier-platform-schema\` Generated Documentation
184 |
185 | This is automatically generated by the \`npm run docs\` command in \`zapier-platform-schema\` version ${quoteOrNa(
186 | packageJson.version
187 | )}.
188 |
189 | -----
190 |
191 | ## Index
192 |
193 |
194 | -----
195 |
196 | ${markdownSections}
197 |
198 | `.trim();
199 | return toc.insert(docs, { maxdepth: 2, bullets: '*' });
200 | };
201 |
202 | module.exports = buildDocs;
203 |
--------------------------------------------------------------------------------
/lib/schemas/ResourceSchema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const makeSchema = require('../utils/makeSchema');
4 |
5 | const ResourceMethodGetSchema = require('./ResourceMethodGetSchema');
6 | const ResourceMethodHookSchema = require('./ResourceMethodHookSchema');
7 | const ResourceMethodListSchema = require('./ResourceMethodListSchema');
8 | const ResourceMethodSearchSchema = require('./ResourceMethodSearchSchema');
9 | const ResourceMethodCreateSchema = require('./ResourceMethodCreateSchema');
10 | const DynamicFieldsSchema = require('./DynamicFieldsSchema');
11 | const KeySchema = require('./KeySchema');
12 |
13 | module.exports = makeSchema(
14 | {
15 | id: '/ResourceSchema',
16 | description:
17 | 'Represents a resource, which will in turn power triggers, searches, or creates.',
18 | type: 'object',
19 | required: ['key', 'noun'],
20 | examples: [
21 | {
22 | key: 'tag',
23 | noun: 'Tag',
24 | get: {
25 | display: {
26 | label: 'Get Tag by ID',
27 | description: 'Grab a specific Tag by ID.'
28 | },
29 | operation: {
30 | perform: {
31 | url: 'http://fake-crm.getsandbox.com/tags/{{inputData.id}}'
32 | },
33 | sample: {
34 | id: 385,
35 | name: 'proactive enable ROI'
36 | }
37 | }
38 | }
39 | },
40 | {
41 | key: 'tag',
42 | noun: 'Tag',
43 | sample: {
44 | id: 385,
45 | name: 'proactive enable ROI'
46 | },
47 | get: {
48 | display: {
49 | label: 'Get Tag by ID',
50 | description: 'Grab a specific Tag by ID.'
51 | },
52 | operation: {
53 | perform: {
54 | url: 'http://fake-crm.getsandbox.com/tags/{{inputData.id}}'
55 | }
56 | // resource sample is used
57 | }
58 | }
59 | },
60 | {
61 | key: 'tag',
62 | noun: 'Tag',
63 | get: {
64 | display: {
65 | label: 'Get Tag by ID',
66 | description: 'Grab a specific Tag by ID.',
67 | hidden: true
68 | },
69 | operation: {
70 | perform: {
71 | url: 'http://fake-crm.getsandbox.com/tags/{{inputData.id}}'
72 | }
73 | }
74 | },
75 | list: {
76 | display: {
77 | label: 'New Tag',
78 | description: 'Trigger when a new Tag is created in your account.'
79 | },
80 | operation: {
81 | perform: {
82 | url: 'http://fake-crm.getsandbox.com/tags'
83 | },
84 | sample: {
85 | id: 385,
86 | name: 'proactive enable ROI'
87 | }
88 | }
89 | }
90 | }
91 | ],
92 | antiExamples: [
93 | {
94 | key: 'tag',
95 | noun: 'Tag',
96 | get: {
97 | display: {
98 | label: 'Get Tag by ID',
99 | description: 'Grab a specific Tag by ID.'
100 | },
101 | operation: {
102 | perform: {
103 | url: 'http://fake-crm.getsandbox.com/tags/{{inputData.id}}'
104 | }
105 | // missing sample (and no sample on resource)
106 | }
107 | },
108 | list: {
109 | display: {
110 | label: 'New Tag',
111 | description: 'Trigger when a new Tag is created in your account.'
112 | },
113 | operation: {
114 | perform: {
115 | url: 'http://fake-crm.getsandbox.com/tags'
116 | },
117 | sample: {
118 | id: 385,
119 | name: 'proactive enable ROI'
120 | }
121 | }
122 | }
123 | },
124 | {
125 | key: 'tag',
126 | noun: 'Tag',
127 | get: {
128 | display: {
129 | label: 'Get Tag by ID',
130 | description: 'Grab a specific Tag by ID.'
131 | },
132 | operation: {
133 | perform: {
134 | url: 'http://fake-crm.getsandbox.com/tags/{{inputData.id}}'
135 | }
136 | // missing sample (and no sample on resource)
137 | }
138 | }
139 | }
140 | ],
141 | properties: {
142 | key: {
143 | description: 'A key to uniquely identify this resource.',
144 | $ref: KeySchema.id
145 | },
146 | noun: {
147 | description:
148 | 'A noun for this resource that completes the sentence "create a new XXX".',
149 | type: 'string',
150 | minLength: 2,
151 | maxLength: 255
152 | },
153 | // TODO: do we need to break these all apart too? :-/
154 | get: {
155 | description: ResourceMethodGetSchema.schema.description,
156 | $ref: ResourceMethodGetSchema.id
157 | },
158 | hook: {
159 | description: ResourceMethodHookSchema.schema.description,
160 | $ref: ResourceMethodHookSchema.id
161 | },
162 | list: {
163 | description: ResourceMethodListSchema.schema.description,
164 | $ref: ResourceMethodListSchema.id
165 | },
166 | search: {
167 | description: ResourceMethodSearchSchema.schema.description,
168 | $ref: ResourceMethodSearchSchema.id
169 | },
170 | create: {
171 | description: ResourceMethodCreateSchema.schema.description,
172 | $ref: ResourceMethodCreateSchema.id
173 | },
174 | outputFields: {
175 | description: 'What fields of data will this return?',
176 | $ref: DynamicFieldsSchema.id
177 | },
178 | sample: {
179 | description: 'What does a sample of data look like?',
180 | type: 'object',
181 | // TODO: require id, ID, Id property?
182 | minProperties: 1
183 | }
184 | },
185 | additionalProperties: false
186 | },
187 | [
188 | ResourceMethodGetSchema,
189 | ResourceMethodHookSchema,
190 | ResourceMethodListSchema,
191 | ResourceMethodSearchSchema,
192 | ResourceMethodCreateSchema,
193 | DynamicFieldsSchema,
194 | KeySchema
195 | ]
196 | );
197 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const should = require('should');
4 | const schema = require('../schema');
5 |
6 | const testUtils = require('./utils');
7 |
8 | const appDefinition = require('../examples/definition.json');
9 |
10 | const copy = o => JSON.parse(JSON.stringify(o));
11 |
12 | const NUM_SCHEMAS = 48; // changes regularly as we expand
13 |
14 | describe('app', () => {
15 | describe('validation', () => {
16 | it('should be a valid app', () => {
17 | const results = schema.validateAppDefinition(appDefinition);
18 | results.errors.should.eql([]);
19 | });
20 |
21 | it('should invalidate deep errors', () => {
22 | const appCopy = copy(appDefinition);
23 | delete appCopy.version;
24 | delete appCopy.triggers.contact_by_tag.noun;
25 | delete appCopy.triggers.contact_by_tag.display.label;
26 | appCopy.triggers.contact_by_tag.operation.inputFields[0].type = 'loltype';
27 | const results = schema.validateAppDefinition(appCopy);
28 | results.errors.length.should.eql(4);
29 | });
30 |
31 | it('should invalidate a bad named trigger', () => {
32 | const appCopy = copy(appDefinition);
33 | appCopy.triggers['3contact_by_tag'] = appCopy.triggers.contact_by_tag;
34 | delete appCopy.triggers.contact_by_tag;
35 | const results = schema.validateAppDefinition(appCopy);
36 | results.errors.length.should.eql(2); // invalid name and top-level key doesn't match trigger key
37 | });
38 |
39 | it('should run and pass functional constraints', function() {
40 | const definition = {
41 | version: '1.0.0',
42 | platformVersion: '1.0.0',
43 | searches: {
44 | fooSearch: {
45 | key: 'fooSearch',
46 | noun: 'Foo',
47 | display: {
48 | label: 'Find Foo',
49 | description: 'Find a foo...'
50 | },
51 | operation: {
52 | perform: '$func$2$f$',
53 | sample: { id: 1 }
54 | }
55 | }
56 | },
57 | creates: {
58 | fooCreate: {
59 | key: 'fooCreate',
60 | noun: 'Foo',
61 | display: {
62 | label: 'Create Foo',
63 | description: 'Creates a...'
64 | },
65 | operation: {
66 | perform: '$func$2$f$',
67 | sample: { id: 1 }
68 | }
69 | }
70 | },
71 | searchOrCreates: {
72 | fooSearchOrCreate: {
73 | key: 'fooSearch',
74 | display: {
75 | label: 'Find or Create a...',
76 | description: 'Something Something'
77 | },
78 | search: 'fooSearch',
79 | create: 'fooCreate'
80 | }
81 | }
82 | };
83 | const results = schema.validateAppDefinition(definition);
84 | results.errors.should.have.length(0);
85 | });
86 |
87 | it('should run functional constraints with errors', function() {
88 | const definition = {
89 | version: '1.0.0',
90 | platformVersion: '1.0.0',
91 | searches: {
92 | fooSearch: {
93 | key: 'fooSearch',
94 | noun: 'Foo',
95 | display: {
96 | label: 'Find Foo',
97 | description: 'Find a foo...'
98 | },
99 | operation: {
100 | perform: '$func$2$f$',
101 | sample: { id: 1 }
102 | }
103 | }
104 | },
105 | creates: {
106 | fooCreate: {
107 | key: 'fooCreate',
108 | noun: 'Foo',
109 | display: {
110 | label: 'Create Foo',
111 | description: 'Creates a...'
112 | },
113 | operation: {
114 | perform: '$func$2$f$',
115 | sample: { id: 1 }
116 | }
117 | }
118 | },
119 | searchOrCreates: {
120 | fooSearchOrCreate: {
121 | key: 'fooSearchOrCreate',
122 | display: {
123 | label: 'Find or Create a...',
124 | description: 'Something Something'
125 | },
126 | search: 'fooBad',
127 | create: 'fooBad'
128 | }
129 | }
130 | };
131 | const results = schema.validateAppDefinition(definition);
132 | results.errors.should.have.length(3);
133 | results.errors[0].stack.should.eql(
134 | 'instance.searchOrCreates.fooSearchOrCreate.key must match a "key" from a search (options: fooSearch)'
135 | );
136 | results.errors[1].stack.should.eql(
137 | 'instance.searchOrCreates.fooSearchOrCreate.search must match a "key" from a search (options: fooSearch)'
138 | );
139 | results.errors[2].stack.should.eql(
140 | 'instance.searchOrCreates.fooSearchOrCreate.create must match a "key" from a create (options: fooCreate)'
141 | );
142 | });
143 |
144 | it('should validate inputFormat', () => {
145 | const appCopy = copy(appDefinition);
146 | appCopy.authentication = {
147 | type: 'custom',
148 | test: {
149 | url: 'https://example.com'
150 | },
151 | fields: [
152 | {
153 | key: 'subdomain',
154 | type: 'string',
155 | required: true,
156 | inputFormat: 'https://{{input}}.example.com'
157 | }
158 | ]
159 | };
160 | const results = schema.validateAppDefinition(appCopy);
161 | // this line ensures we're getting a ValidatorResult class, not just an object that looks like one
162 | should(results.valid).eql(true);
163 | results.errors.should.eql([]);
164 | });
165 |
166 | it('should invalidate illegal inputFormat', () => {
167 | const appCopy = copy(appDefinition);
168 | appCopy.authentication = {
169 | type: 'custom',
170 | test: {
171 | url: 'https://example.com'
172 | },
173 | fields: [
174 | {
175 | key: 'subdomain',
176 | type: 'string',
177 | required: true,
178 | inputFormat: 'https://{{input}.example.com'
179 | }
180 | ]
181 | };
182 | const results = schema.validateAppDefinition(appCopy);
183 | results.errors.length.should.eql(1);
184 | should(results.valid).eql(false);
185 |
186 | const error = results.errors[0];
187 | error.name.should.eql('pattern');
188 | error.instance.should.eql('https://{{input}.example.com');
189 | });
190 |
191 | it('should validate legacy properties', () => {
192 | const appCopy = copy(appDefinition);
193 | appCopy.legacy = {
194 | subscribeUrl: 'https://example.com',
195 | triggers: {
196 | contact: {
197 | url: 'https://example.com'
198 | }
199 | }
200 | };
201 | const results = schema.validateAppDefinition(appCopy);
202 | results.errors.should.eql([]);
203 | });
204 | });
205 |
206 | describe('export', () => {
207 | it('should export the full schema', () => {
208 | const exportedSchema = schema.exportSchema();
209 | Object.keys(exportedSchema.schemas).length.should.eql(NUM_SCHEMAS);
210 | });
211 | });
212 | });
213 |
214 | describe('auto test', () => {
215 | const _exportedSchema = schema.exportSchema();
216 | Object.keys(_exportedSchema.schemas).map(id => {
217 | testUtils.testInlineSchemaExamples(id);
218 | });
219 | });
220 |
--------------------------------------------------------------------------------
/examples/definition.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.2.30",
3 | "platformVersion": "1.2.30",
4 | "beforeRequest": "$func$2$f$",
5 | "afterResponse": ["$func$2$f$"],
6 | "requestTemplate": {
7 | "headers": {
8 | "X-MyCustomHeader": "my header value"
9 | }
10 | },
11 | "authentication": {
12 | "type": "oauth2",
13 | "test": {
14 | "url": "http://fake-crm.getsandbox.com/ping",
15 | "method": "GET"
16 | },
17 | "fields": [
18 | {
19 | "key": "access_token",
20 | "computed": true
21 | },
22 | {
23 | "key": "refresh_token",
24 | "computed": true
25 | }
26 | ],
27 | "oauth2Config": {
28 | "authorizeUrl": "$func$2$f$",
29 | "getAccessToken": {
30 | "url": "http://fake-crm.getsandbox.com/oauth/access_token",
31 | "method": "POST"
32 | },
33 | "scope": "tags,users,contacts",
34 | "autoRefresh": false
35 | },
36 | "connectionLabel": "{{inputData.email}}"
37 | },
38 | "resources": {
39 | "tag": {
40 | "key": "tag",
41 | "noun": "Tag",
42 | "sample": {
43 | "id": 1,
44 | "name": "test tag"
45 | },
46 | "get": {
47 | "display": {
48 | "label": "Get Tag by ID",
49 | "description": "Grab a specific Tag by ID."
50 | },
51 | "operation": {
52 | "perform": {
53 | "url": "http://fake-crm.getsandbox.com/tags/{{inputData.id}}"
54 | },
55 | "sample": {
56 | "id": 385,
57 | "name": "proactive enable ROI"
58 | },
59 | "inputFields": [
60 | {
61 | "key": "id",
62 | "required": true
63 | }
64 | ]
65 | }
66 | },
67 | "list": {
68 | "display": {
69 | "label": "New Tag",
70 | "description": "Trigger when a new Tag is created in your account."
71 | },
72 | "operation": {
73 | "perform": {
74 | "url": "http://fake-crm.getsandbox.com/tags"
75 | },
76 | "sample": {
77 | "id": 385,
78 | "name": "proactive enable ROI"
79 | }
80 | }
81 | },
82 | "create": {
83 | "display": {
84 | "label": "Create Tag",
85 | "description": "Create a new Tag in your account."
86 | },
87 | "operation": {
88 | "perform": "$func$2$f$",
89 | "sample": {
90 | "id": 1,
91 | "name": "proactive enable ROI"
92 | }
93 | }
94 | }
95 | },
96 | "user": {
97 | "key": "user",
98 | "noun": "User",
99 | "sample": {
100 | "id": 1,
101 | "name": "Test McTesterson",
102 | "email": "test@mctesterson.com"
103 | },
104 | "get": {
105 | "display": {
106 | "label": "Get User by ID",
107 | "description": "Grab a specific User by ID."
108 | },
109 | "operation": {
110 | "perform": {
111 | "url": "http://fake-crm.getsandbox.com/users/{{inputData.id}}"
112 | },
113 | "sample": {
114 | "id": 1,
115 | "name": "Jalen Bode",
116 | "email": "jalen.bode@company.com"
117 | },
118 | "inputFields": [
119 | {
120 | "key": "id",
121 | "required": true
122 | }
123 | ]
124 | }
125 | },
126 | "list": {
127 | "display": {
128 | "label": "New User",
129 | "description": "Trigger when a new User is created in your account."
130 | },
131 | "operation": {
132 | "perform": {
133 | "url": "http://fake-crm.getsandbox.com/users"
134 | },
135 | "sample": {
136 | "id": 49,
137 | "name": "Veronica Kuhn",
138 | "email": "veronica.kuhn@company.com"
139 | }
140 | }
141 | }
142 | },
143 | "contact": {
144 | "key": "contact",
145 | "noun": "Contact",
146 | "sample": {
147 | "id": 1,
148 | "name": "Test Contact",
149 | "company": "Test Inc",
150 | "email": "test@example.com.com",
151 | "phone": "1-111-555-7000",
152 | "address": "1234 Test Canyon",
153 | "owner_id": 1,
154 | "tag_ids": [1, 2, 3]
155 | },
156 | "get": {
157 | "display": {
158 | "label": "Get Contact by ID",
159 | "description": "Grab a specific Contact by ID."
160 | },
161 | "operation": {
162 | "perform": {
163 | "url": "http://fake-crm.getsandbox.com/contacts/{{inputData.id}}"
164 | },
165 | "sample": {
166 | "id": 1,
167 | "name": "Rosalee Kub",
168 | "company": "Schmidt, O'Reilly and Moen",
169 | "email": "Rosalee_Kub47@hotmail.com",
170 | "phone": "412-916-6798 x3478",
171 | "address": "73375 Jacobson Turnpike",
172 | "owner_id": 9,
173 | "tag_ids": [87]
174 | },
175 | "inputFields": [
176 | {
177 | "key": "id",
178 | "required": true
179 | }
180 | ]
181 | }
182 | },
183 | "list": {
184 | "display": {
185 | "label": "New Contact",
186 | "description":
187 | "Trigger when a new Contact is created in your account."
188 | },
189 | "operation": {
190 | "perform": {
191 | "url": "http://fake-crm.getsandbox.com/contacts"
192 | },
193 | "sample": {
194 | "id": 1,
195 | "name": "Rosalee Kub",
196 | "company": "Schmidt, O'Reilly and Moen",
197 | "email": "Rosalee_Kub47@hotmail.com",
198 | "phone": "412-916-6798 x3478",
199 | "address": "73375 Jacobson Turnpike",
200 | "owner_id": 9,
201 | "tag_ids": [87]
202 | }
203 | }
204 | },
205 | "create": {
206 | "display": {
207 | "label": "Create Contact",
208 | "description": "Create a new Contact in your account."
209 | },
210 | "operation": {
211 | "perform": "$func$2$f$",
212 | "sample": {
213 | "id": 1,
214 | "name": "Rosalee Kub",
215 | "company": "Schmidt, O'Reilly and Moen",
216 | "email": "Rosalee_Kub47@hotmail.com",
217 | "phone": "412-916-6798 x3478",
218 | "address": "73375 Jacobson Turnpike",
219 | "owner_id": 9,
220 | "tag_ids": [87]
221 | },
222 | "inputFields": [
223 | {
224 | "key": "email",
225 | "label": "Email",
226 | "type": "string",
227 | "required": true
228 | },
229 | {
230 | "key": "name",
231 | "label": "Name",
232 | "type": "string"
233 | },
234 | {
235 | "key": "company",
236 | "label": "Company",
237 | "type": "string"
238 | },
239 | {
240 | "key": "phone",
241 | "label": "Phone",
242 | "type": "string"
243 | },
244 | {
245 | "key": "address",
246 | "label": "Address",
247 | "type": "text"
248 | },
249 | {
250 | "key": "owner_id",
251 | "label": "Owner",
252 | "type": "integer",
253 | "dynamic": "user"
254 | },
255 | {
256 | "key": "tag_id",
257 | "label": "Tag",
258 | "type": "integer",
259 | "dynamic": "tag.id.name",
260 | "search": "tag.id"
261 | }
262 | ]
263 | }
264 | }
265 | }
266 | },
267 | "triggers": {
268 | "contact_by_tag": {
269 | "key": "contact_by_tag",
270 | "noun": "Contact",
271 | "display": {
272 | "label": "New Tagged Contact",
273 | "description": "Trigger when a new Contact is tagged in your account."
274 | },
275 | "operation": {
276 | "resource": "contact",
277 | "perform": {
278 | "url":
279 | "http://fake-crm.getsandbox.com/contacts?tag_id={{inputData.tagId}}"
280 | },
281 | "inputFields": [
282 | {
283 | "key": "tagId",
284 | "label": "Tag",
285 | "type": "integer",
286 | "dynamic": "tag"
287 | }
288 | ],
289 | "sample": {
290 | "id": 1,
291 | "name": "Test Contact",
292 | "company": "Test Inc",
293 | "email": "test@example.com.com",
294 | "phone": "1-111-555-7000",
295 | "address": "1234 Test Canyon",
296 | "owner_id": 1,
297 | "tag_ids": [1, 2, 3]
298 | }
299 | }
300 | },
301 | "tag_list": {
302 | "display": {
303 | "label": "New Tag",
304 | "description": "Trigger when a new Tag is created in your account."
305 | },
306 | "operation": {
307 | "perform": {
308 | "url": "http://fake-crm.getsandbox.com/tags"
309 | },
310 | "sample": {
311 | "id": 385,
312 | "name": "proactive enable ROI"
313 | },
314 | "resource": "tag",
315 | "type": "polling"
316 | },
317 | "key": "tag_list",
318 | "noun": "Tag"
319 | },
320 | "user_list": {
321 | "display": {
322 | "label": "New User",
323 | "description": "Trigger when a new User is created in your account."
324 | },
325 | "operation": {
326 | "perform": {
327 | "url": "http://fake-crm.getsandbox.com/users"
328 | },
329 | "sample": {
330 | "id": 49,
331 | "name": "Veronica Kuhn",
332 | "email": "veronica.kuhn@company.com"
333 | },
334 | "resource": "user",
335 | "type": "polling"
336 | },
337 | "key": "user_list",
338 | "noun": "User"
339 | },
340 | "contact_list": {
341 | "display": {
342 | "label": "New Contact",
343 | "description": "Trigger when a new Contact is created in your account."
344 | },
345 | "operation": {
346 | "perform": {
347 | "url": "http://fake-crm.getsandbox.com/contacts"
348 | },
349 | "resource": "contact",
350 | "type": "polling",
351 | "sample": {
352 | "id": 1,
353 | "name": "Test Contact",
354 | "company": "Test Inc",
355 | "email": "test@example.com.com",
356 | "phone": "1-111-555-7000",
357 | "address": "1234 Test Canyon",
358 | "owner_id": 1,
359 | "tag_ids": [1, 2, 3]
360 | }
361 | },
362 | "key": "contact_list",
363 | "noun": "Contact"
364 | }
365 | },
366 | "searches": {
367 | "tag": {
368 | "display": {
369 | "label": "Find Tag",
370 | "description": "Finds a Tag."
371 | },
372 | "operation": {
373 | "perform": {
374 | "url": "http://fake-crm.getsandbox.com/tags?name={{inputData.name}}"
375 | },
376 | "sample": {
377 | "id": 385,
378 | "name": "proactive enable ROI"
379 | },
380 | "resource": "tag",
381 | "inputFields": [
382 | {
383 | "key": "name",
384 | "label": "Tag Name",
385 | "type": "string"
386 | }
387 | ],
388 | "sample": {
389 | "id": 1,
390 | "name": "test tag"
391 | }
392 | },
393 | "key": "tag",
394 | "noun": "Tag"
395 | }
396 | },
397 | "creates": {
398 | "tag_create": {
399 | "display": {
400 | "label": "Create Tag",
401 | "description": "Create a new Tag in your account."
402 | },
403 | "operation": {
404 | "perform": "$func$2$f$",
405 | "resource": "tag",
406 | "sample": {
407 | "id": 385,
408 | "name": "proactive enable ROI"
409 | }
410 | },
411 | "key": "tag_create",
412 | "noun": "Tag"
413 | },
414 | "contact_create": {
415 | "display": {
416 | "label": "Create Contact",
417 | "description": "Create a new Contact in your account."
418 | },
419 | "operation": {
420 | "perform": "$func$2$f$",
421 | "inputFields": [
422 | {
423 | "key": "email",
424 | "label": "Email",
425 | "type": "string",
426 | "required": true
427 | },
428 | {
429 | "key": "name",
430 | "label": "Name",
431 | "type": "string"
432 | },
433 | {
434 | "key": "company",
435 | "label": "Company",
436 | "type": "string"
437 | },
438 | {
439 | "key": "phone",
440 | "label": "Phone",
441 | "type": "string"
442 | },
443 | {
444 | "key": "address",
445 | "label": "Address",
446 | "type": "text"
447 | },
448 | {
449 | "key": "owner_id",
450 | "label": "Owner",
451 | "type": "integer",
452 | "dynamic": "user"
453 | },
454 | {
455 | "key": "tag_id",
456 | "label": "Tag",
457 | "type": "integer",
458 | "dynamic": "tag"
459 | }
460 | ],
461 | "resource": "contact",
462 | "sample": {
463 | "id": 1,
464 | "name": "Test Contact",
465 | "company": "Test Inc",
466 | "email": "test@example.com.com",
467 | "phone": "1-111-555-7000",
468 | "address": "1234 Test Canyon",
469 | "owner_id": 1,
470 | "tag_ids": [1, 2, 3]
471 | }
472 | },
473 | "key": "contact_create",
474 | "noun": "Contact"
475 | }
476 | }
477 | }
478 |
--------------------------------------------------------------------------------
/exported-schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "8.2.1",
3 | "schemas": {
4 | "AppSchema": {
5 | "id": "/AppSchema",
6 | "description": "Represents a full app.",
7 | "type": "object",
8 | "required": ["version", "platformVersion"],
9 | "properties": {
10 | "version": {
11 | "description": "A version identifier for your code.",
12 | "$ref": "/VersionSchema"
13 | },
14 | "platformVersion": {
15 | "description":
16 | "A version identifier for the Zapier execution environment.",
17 | "$ref": "/VersionSchema"
18 | },
19 | "beforeApp": {
20 | "description":
21 | "EXPERIMENTAL: Before the perform method is called on your app, you can modify the execution context.",
22 | "$ref": "/MiddlewaresSchema"
23 | },
24 | "afterApp": {
25 | "description":
26 | "EXPERIMENTAL: After the perform method is called on your app, you can modify the response.",
27 | "$ref": "/MiddlewaresSchema"
28 | },
29 | "authentication": {
30 | "description": "Choose what scheme your API uses for authentication.",
31 | "$ref": "/AuthenticationSchema"
32 | },
33 | "requestTemplate": {
34 | "description":
35 | "Define a request mixin, great for setting custom headers, content-types, etc.",
36 | "$ref": "/RequestSchema"
37 | },
38 | "beforeRequest": {
39 | "description":
40 | "Before an HTTP request is sent via our `z.request()` client, you can modify it.",
41 | "$ref": "/MiddlewaresSchema"
42 | },
43 | "afterResponse": {
44 | "description":
45 | "After an HTTP response is recieved via our `z.request()` client, you can modify it.",
46 | "$ref": "/MiddlewaresSchema"
47 | },
48 | "hydrators": {
49 | "description":
50 | "An optional bank of named functions that you can use in `z.hydrate('someName')` to lazily load data.",
51 | "$ref": "/HydratorsSchema"
52 | },
53 | "resources": {
54 | "description":
55 | "All the resources for your app. Zapier will take these and generate the relevent triggers/searches/creates automatically.",
56 | "$ref": "/ResourcesSchema"
57 | },
58 | "triggers": {
59 | "description":
60 | "All the triggers for your app. You can add your own here, or Zapier will automatically register any from the list/hook methods on your resources.",
61 | "$ref": "/TriggersSchema"
62 | },
63 | "searches": {
64 | "description":
65 | "All the searches for your app. You can add your own here, or Zapier will automatically register any from the search method on your resources.",
66 | "$ref": "/SearchesSchema"
67 | },
68 | "creates": {
69 | "description":
70 | "All the creates for your app. You can add your own here, or Zapier will automatically register any from the create method on your resources.",
71 | "$ref": "/CreatesSchema"
72 | },
73 | "searchOrCreates": {
74 | "description":
75 | "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.",
76 | "$ref": "/SearchOrCreatesSchema"
77 | },
78 | "flags": {
79 | "description": "Top-level app options",
80 | "$ref": "/AppFlagsSchema"
81 | },
82 | "legacy": {
83 | "description":
84 | "**INTERNAL USE ONLY**. Zapier uses this to hold properties from a legacy Web Builder app.",
85 | "type": "object",
86 | "docAnnotation": {
87 | "hide": true
88 | }
89 | }
90 | },
91 | "additionalProperties": false
92 | },
93 | "FieldChoiceWithLabelSchema": {
94 | "id": "/FieldChoiceWithLabelSchema",
95 | "description":
96 | "An object describing a labeled choice in a static dropdown. Useful if the value a user picks isn't exactly what the zap uses. For instance, when they click on a nickname, but the zap uses the user's full name ([image](https://cdn.zapier.com/storage/photos/8ed01ac5df3a511ce93ed2dc43c7fbbc.png)).",
97 | "type": "object",
98 | "required": ["value", "sample", "label"],
99 | "properties": {
100 | "value": {
101 | "description":
102 | "The actual value that is sent into the Zap. Should match sample exactly.",
103 | "type": "string",
104 | "minLength": 1
105 | },
106 | "sample": {
107 | "description":
108 | "Displayed as light grey text in the editor. It's important that the value match the sample. Otherwise, the actual value won't match what the user picked, which is confusing.",
109 | "type": "string",
110 | "minLength": 1
111 | },
112 | "label": {
113 | "description": "A human readable label for this value.",
114 | "type": "string",
115 | "minLength": 1
116 | }
117 | }
118 | },
119 | "RefResourceSchema": {
120 | "id": "/RefResourceSchema",
121 | "description":
122 | "Reference a resource by key and the data it returns. In the format of: `{resource_key}.{foreign_key}(.{human_label_key})`.",
123 | "type": "string",
124 | "pattern": "^[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)?(\\.[a-zA-Z0-9_]+)?$"
125 | },
126 | "FieldChoicesSchema": {
127 | "id": "/FieldChoicesSchema",
128 | "description":
129 | "A static dropdown of options. Which you use depends on your order and label requirements:\n\nNeed a Label? | Does Order Matter? | Type to Use\n---|---|---\nYes | No | Object of value -> label\nNo | Yes | Array of Strings\nYes | Yes | Array of [FieldChoiceWithLabel](#fieldchoicewithlabelschema)",
130 | "oneOf": [
131 | {
132 | "type": "object",
133 | "minProperties": 1
134 | },
135 | {
136 | "type": "array",
137 | "minItems": 1,
138 | "items": {
139 | "oneOf": [
140 | {
141 | "type": "string"
142 | },
143 | {
144 | "$ref": "/FieldChoiceWithLabelSchema"
145 | }
146 | ]
147 | }
148 | }
149 | ]
150 | },
151 | "FieldSchema": {
152 | "id": "/FieldSchema",
153 | "description":
154 | "Defines a field an app either needs as input, or gives as output. 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`",
155 | "type": "object",
156 | "required": ["key"],
157 | "properties": {
158 | "key": {
159 | "description":
160 | "A unique machine readable key for this value (IE: \"fname\").",
161 | "type": "string",
162 | "minLength": 1
163 | },
164 | "label": {
165 | "description":
166 | "A human readable label for this value (IE: \"First Name\").",
167 | "type": "string",
168 | "minLength": 1
169 | },
170 | "helpText": {
171 | "description":
172 | "A human readable description of this value (IE: \"The first part of a full name.\"). You can use Markdown.",
173 | "type": "string",
174 | "minLength": 1,
175 | "maxLength": 1000
176 | },
177 | "type": {
178 | "description": "The type of this value.",
179 | "type": "string",
180 | "enum": [
181 | "string",
182 | "text",
183 | "integer",
184 | "number",
185 | "boolean",
186 | "datetime",
187 | "file",
188 | "password",
189 | "copy"
190 | ]
191 | },
192 | "required": {
193 | "description": "If this value is required or not.",
194 | "type": "boolean"
195 | },
196 | "placeholder": {
197 | "description": "An example value that is not saved.",
198 | "type": "string",
199 | "minLength": 1
200 | },
201 | "default": {
202 | "description":
203 | "A default value that is saved the first time a Zap is created.",
204 | "type": "string",
205 | "minLength": 1
206 | },
207 | "dynamic": {
208 | "description":
209 | "A reference to a trigger that will power a dynamic dropdown.",
210 | "$ref": "/RefResourceSchema"
211 | },
212 | "search": {
213 | "description":
214 | "A reference to a search that will guide the user to add a search step to populate this field when creating a Zap.",
215 | "$ref": "/RefResourceSchema"
216 | },
217 | "choices": {
218 | "description":
219 | "An object of machine keys and human values to populate a static dropdown.",
220 | "$ref": "/FieldChoicesSchema"
221 | },
222 | "list": {
223 | "description": "Can a user provide multiples of this field?",
224 | "type": "boolean"
225 | },
226 | "children": {
227 | "type": "array",
228 | "items": {
229 | "$ref": "/FieldSchema"
230 | },
231 | "description":
232 | "An array of child fields that define the structure of a sub-object for this field. Usually used for line items.",
233 | "minItems": 1
234 | },
235 | "dict": {
236 | "description": "Is this field a key/value input?",
237 | "type": "boolean"
238 | },
239 | "computed": {
240 | "description":
241 | "Is this field automatically populated (and hidden from the user)?",
242 | "type": "boolean"
243 | },
244 | "altersDynamicFields": {
245 | "description":
246 | "Does the value of this field affect the definitions of other fields in the set?",
247 | "type": "boolean"
248 | },
249 | "inputFormat": {
250 | "description":
251 | "Useful when you expect the input to be part of a longer string. Put \"{{input}}\" in place of the user's input (IE: \"https://{{input}}.yourdomain.com\").",
252 | "type": "string",
253 | "pattern": "^.*{{input}}.*$"
254 | }
255 | },
256 | "additionalProperties": false
257 | },
258 | "FunctionRequireSchema": {
259 | "id": "/FunctionRequireSchema",
260 | "description":
261 | "A path to a file that might have content like `module.exports = (z, bundle) => [{id: 123}];`.",
262 | "type": "object",
263 | "additionalProperties": false,
264 | "required": ["require"],
265 | "properties": {
266 | "require": {
267 | "type": "string"
268 | }
269 | }
270 | },
271 | "FunctionSourceSchema": {
272 | "id": "/FunctionSourceSchema",
273 | "description":
274 | "Source code like `{source: \"return 1 + 2\"}` which the system will wrap in a function for you.",
275 | "type": "object",
276 | "additionalProperties": false,
277 | "required": ["source"],
278 | "properties": {
279 | "source": {
280 | "type": "string",
281 | "pattern": "return",
282 | "description":
283 | "JavaScript code for the function body. This must end with a `return` statement."
284 | },
285 | "args": {
286 | "type": "array",
287 | "items": {
288 | "type": "string"
289 | },
290 | "description":
291 | "Function signature. Defaults to `['z', 'bundle']` if not specified."
292 | }
293 | }
294 | },
295 | "FlatObjectSchema": {
296 | "id": "/FlatObjectSchema",
297 | "description": "An object whose values can only be primitives",
298 | "type": "object",
299 | "patternProperties": {
300 | "[^\\s]+": {
301 | "description":
302 | "Any key may exist in this flat object as long as its values are simple.",
303 | "anyOf": [
304 | {
305 | "type": "null"
306 | },
307 | {
308 | "type": "string"
309 | },
310 | {
311 | "type": "integer"
312 | },
313 | {
314 | "type": "number"
315 | },
316 | {
317 | "type": "boolean"
318 | }
319 | ]
320 | }
321 | },
322 | "additionalProperties": false
323 | },
324 | "FunctionSchema": {
325 | "id": "/FunctionSchema",
326 | "description":
327 | "Internal pointer to a function from the original source or the source code itself. Encodes arity and if `arguments` is used in the body. Note - just write normal functions and the system will encode the pointers for you. Or, provide {source: \"return 1 + 2\"} and the system will wrap in a function for you.",
328 | "oneOf": [
329 | {
330 | "type": "string",
331 | "pattern": "^\\$func\\$\\d+\\$[tf]\\$$"
332 | },
333 | {
334 | "$ref": "/FunctionRequireSchema"
335 | },
336 | {
337 | "$ref": "/FunctionSourceSchema"
338 | }
339 | ]
340 | },
341 | "RedirectRequestSchema": {
342 | "id": "/RedirectRequestSchema",
343 | "description":
344 | "A representation of a HTTP redirect - you can use the `{{syntax}}` to inject authentication, field or global variables.",
345 | "type": "object",
346 | "properties": {
347 | "method": {
348 | "description": "The HTTP method for the request.",
349 | "type": "string",
350 | "default": "GET",
351 | "enum": ["GET"]
352 | },
353 | "url": {
354 | "description":
355 | "A URL for the request (we will parse the querystring and merge with params). Keys and values will not be re-encoded.",
356 | "type": "string"
357 | },
358 | "params": {
359 | "description":
360 | "A mapping of the querystring - will get merged with any query params in the URL. Keys and values will be encoded.",
361 | "$ref": "/FlatObjectSchema"
362 | }
363 | },
364 | "additionalProperties": false
365 | },
366 | "RequestSchema": {
367 | "id": "/RequestSchema",
368 | "description":
369 | "A representation of a HTTP request - you can use the `{{syntax}}` to inject authentication, field or global variables.",
370 | "type": "object",
371 | "properties": {
372 | "method": {
373 | "description": "The HTTP method for the request.",
374 | "type": "string",
375 | "default": "GET",
376 | "enum": ["GET", "PUT", "POST", "PATCH", "DELETE", "HEAD"]
377 | },
378 | "url": {
379 | "description":
380 | "A URL for the request (we will parse the querystring and merge with params). Keys and values will not be re-encoded.",
381 | "type": "string"
382 | },
383 | "body": {
384 | "description":
385 | "Can be nothing, a raw string or JSON (object or array).",
386 | "oneOf": [
387 | {
388 | "type": "null"
389 | },
390 | {
391 | "type": "string"
392 | },
393 | {
394 | "type": "object"
395 | },
396 | {
397 | "type": "array"
398 | }
399 | ]
400 | },
401 | "params": {
402 | "description":
403 | "A mapping of the querystring - will get merged with any query params in the URL. Keys and values will be encoded.",
404 | "$ref": "/FlatObjectSchema"
405 | },
406 | "headers": {
407 | "description": "The HTTP headers for the request.",
408 | "$ref": "/FlatObjectSchema"
409 | },
410 | "auth": {
411 | "description":
412 | "An object holding the auth parameters for OAuth1 request signing, like `{oauth_token: 'abcd', oauth_token_secret: '1234'}`. Or an array reserved (i.e. not implemented yet) to hold the username and password for Basic Auth. Like `['AzureDiamond', 'hunter2']`.",
413 | "oneOf": [
414 | {
415 | "type": "array",
416 | "items": {
417 | "type": "string",
418 | "minProperties": 2,
419 | "maxProperties": 2
420 | }
421 | },
422 | {
423 | "$ref": "/FlatObjectSchema"
424 | }
425 | ]
426 | },
427 | "removeMissingValuesFrom": {
428 | "description":
429 | "Should missing values be sent? (empty strings, `null`, and `undefined` only — `[]`, `{}`, and `false` will still be sent). Allowed fields are `params` and `body`. The default is `false`, ex: ```removeMissingValuesFrom: { params: false, body: false }```",
430 | "type": "object",
431 | "properties": {
432 | "params": {
433 | "description":
434 | "Refers to data sent via a requests query params (`req.params`)",
435 | "type": "boolean",
436 | "default": false
437 | },
438 | "body": {
439 | "description":
440 | "Refers to tokens sent via a requsts body (`req.body`)",
441 | "type": "boolean",
442 | "default": false
443 | }
444 | },
445 | "additionalProperties": false
446 | }
447 | },
448 | "additionalProperties": false
449 | },
450 | "FieldsSchema": {
451 | "id": "/FieldsSchema",
452 | "description": "An array or collection of fields.",
453 | "type": "array",
454 | "items": {
455 | "$ref": "/FieldSchema"
456 | }
457 | },
458 | "AuthenticationBasicConfigSchema": {
459 | "id": "/AuthenticationBasicConfigSchema",
460 | "description":
461 | "Config for Basic Authentication. No extra properties are required to setup Basic Auth, so you can leave this empty if your app uses Basic Auth.",
462 | "type": "object",
463 | "properties": {},
464 | "additionalProperties": false
465 | },
466 | "AuthenticationCustomConfigSchema": {
467 | "id": "/AuthenticationCustomConfigSchema",
468 | "description":
469 | "Config for custom authentication (like API keys). No extra properties are required to setup this auth type, so you can leave this empty if your app uses a custom auth method.",
470 | "type": "object",
471 | "properties": {},
472 | "additionalProperties": false
473 | },
474 | "AuthenticationDigestConfigSchema": {
475 | "id": "/AuthenticationDigestConfigSchema",
476 | "description":
477 | "Config for Digest Authentication. No extra properties are required to setup Digest Auth, so you can leave this empty if your app uses Digets Auth.",
478 | "type": "object",
479 | "properties": {},
480 | "additionalProperties": false
481 | },
482 | "AuthenticationOAuth1ConfigSchema": {
483 | "id": "/AuthenticationOAuth1ConfigSchema",
484 | "description": "Config for OAuth1 authentication.",
485 | "type": "object",
486 | "required": ["getRequestToken", "authorizeUrl", "getAccessToken"],
487 | "properties": {
488 | "getRequestToken": {
489 | "description":
490 | "Define where Zapier will acquire a request token which is used for the rest of the three legged authentication process.",
491 | "oneOf": [
492 | {
493 | "$ref": "/RequestSchema"
494 | },
495 | {
496 | "$ref": "/FunctionSchema"
497 | }
498 | ]
499 | },
500 | "authorizeUrl": {
501 | "description":
502 | "Define where Zapier will redirect the user to authorize our app. Typically, you should append an `oauth_token` querystring parameter to the request.",
503 | "oneOf": [
504 | {
505 | "$ref": "/RedirectRequestSchema"
506 | },
507 | {
508 | "$ref": "/FunctionSchema"
509 | }
510 | ]
511 | },
512 | "getAccessToken": {
513 | "description":
514 | "Define how Zapier fetches an access token from the API",
515 | "oneOf": [
516 | {
517 | "$ref": "/RequestSchema"
518 | },
519 | {
520 | "$ref": "/FunctionSchema"
521 | }
522 | ]
523 | }
524 | },
525 | "additionalProperties": false
526 | },
527 | "AuthenticationOAuth2ConfigSchema": {
528 | "id": "/AuthenticationOAuth2ConfigSchema",
529 | "description": "Config for OAuth2 authentication.",
530 | "type": "object",
531 | "required": ["authorizeUrl", "getAccessToken"],
532 | "properties": {
533 | "authorizeUrl": {
534 | "description":
535 | "Define where Zapier will redirect the user to authorize our app. Note: we append the redirect URL and state parameters to return value of this function.",
536 | "oneOf": [
537 | {
538 | "$ref": "/RedirectRequestSchema"
539 | },
540 | {
541 | "$ref": "/FunctionSchema"
542 | }
543 | ]
544 | },
545 | "getAccessToken": {
546 | "description":
547 | "Define how Zapier fetches an access token from the API",
548 | "oneOf": [
549 | {
550 | "$ref": "/RequestSchema"
551 | },
552 | {
553 | "$ref": "/FunctionSchema"
554 | }
555 | ]
556 | },
557 | "refreshAccessToken": {
558 | "description":
559 | "Define how Zapier will refresh the access token from the API",
560 | "oneOf": [
561 | {
562 | "$ref": "/RequestSchema"
563 | },
564 | {
565 | "$ref": "/FunctionSchema"
566 | }
567 | ]
568 | },
569 | "scope": {
570 | "description": "What scope should Zapier request?",
571 | "type": "string"
572 | },
573 | "autoRefresh": {
574 | "description":
575 | "Should Zapier include a pre-built afterResponse middleware that invokes `refreshAccessToken` when we receive a 401 response?",
576 | "type": "boolean"
577 | }
578 | },
579 | "additionalProperties": false
580 | },
581 | "AuthenticationSessionConfigSchema": {
582 | "id": "/AuthenticationSessionConfigSchema",
583 | "description": "Config for session authentication.",
584 | "type": "object",
585 | "required": ["perform"],
586 | "properties": {
587 | "perform": {
588 | "description":
589 | "Define how Zapier fetches the additional authData needed to make API calls.",
590 | "oneOf": [
591 | {
592 | "$ref": "/RequestSchema"
593 | },
594 | {
595 | "$ref": "/FunctionSchema"
596 | }
597 | ]
598 | }
599 | },
600 | "additionalProperties": false
601 | },
602 | "FieldOrFunctionSchema": {
603 | "id": "/FieldOrFunctionSchema",
604 | "description": "Represents an array of fields or functions.",
605 | "type": "array",
606 | "items": {
607 | "oneOf": [
608 | {
609 | "$ref": "/FieldSchema"
610 | },
611 | {
612 | "$ref": "/FunctionSchema"
613 | }
614 | ]
615 | }
616 | },
617 | "DynamicFieldsSchema": {
618 | "id": "/DynamicFieldsSchema",
619 | "description":
620 | "Like [/FieldsSchema](#fieldsschema) but you can provide functions to create dynamic or custom fields.",
621 | "$ref": "/FieldOrFunctionSchema"
622 | },
623 | "ResultsSchema": {
624 | "id": "/ResultsSchema",
625 | "description":
626 | "An array of objects suitable for returning in perform calls.",
627 | "type": "array",
628 | "items": {
629 | "type": "object",
630 | "minProperties": 1
631 | }
632 | },
633 | "BasicDisplaySchema": {
634 | "id": "/BasicDisplaySchema",
635 | "description":
636 | "Represents user information for a trigger, search, or create.",
637 | "type": "object",
638 | "required": ["label", "description"],
639 | "properties": {
640 | "label": {
641 | "description":
642 | "A short label like \"New Record\" or \"Create Record in Project\".",
643 | "type": "string",
644 | "minLength": 2,
645 | "maxLength": 64
646 | },
647 | "description": {
648 | "description":
649 | "A description of what this trigger, search, or create does.",
650 | "type": "string",
651 | "minLength": 1,
652 | "maxLength": 1000
653 | },
654 | "directions": {
655 | "description":
656 | "A short blurb that can explain how to get this working. EG: how and where to copy-paste a static hook URL into your application. Only evaluated for static webhooks.",
657 | "type": "string",
658 | "minLength": 12,
659 | "maxLength": 1000
660 | },
661 | "important": {
662 | "description":
663 | "Affects how prominently this operation is displayed in the UI. Only mark a few of the most popular operations important.",
664 | "type": "boolean"
665 | },
666 | "hidden": {
667 | "description": "Should this operation be unselectable by users?",
668 | "type": "boolean"
669 | }
670 | },
671 | "additionalProperties": false
672 | },
673 | "BasicOperationSchema": {
674 | "id": "/BasicOperationSchema",
675 | "description":
676 | "Represents the fundamental mechanics of triggers, searches, or creates.",
677 | "type": "object",
678 | "required": ["perform"],
679 | "properties": {
680 | "resource": {
681 | "description":
682 | "Optionally reference and extends a resource. Allows Zapier to automatically tie together samples, lists and hooks, greatly improving the UX. EG: if you had another trigger reusing a resource but filtering the results.",
683 | "$ref": "/RefResourceSchema"
684 | },
685 | "perform": {
686 | "description":
687 | "How will Zapier get the data? This can be a function like `(z) => [{id: 123}]` or a request like `{url: 'http...'}`.",
688 | "oneOf": [
689 | {
690 | "$ref": "/RequestSchema"
691 | },
692 | {
693 | "$ref": "/FunctionSchema"
694 | }
695 | ]
696 | },
697 | "inputFields": {
698 | "description":
699 | "What should the form a user sees and configures look like?",
700 | "$ref": "/DynamicFieldsSchema"
701 | },
702 | "outputFields": {
703 | "description":
704 | "What fields of data will this return? Will use resource outputFields if missing, will also use sample if available.",
705 | "$ref": "/DynamicFieldsSchema"
706 | },
707 | "sample": {
708 | "description":
709 | "What does a sample of data look like? Will use resource sample if missing. Requirement waived if `display.hidden` is true or if this belongs to a resource that has a top-level sample",
710 | "type": "object",
711 | "minProperties": 1,
712 | "docAnnotation": {
713 | "required": {
714 | "type": "replace",
715 | "value": "**yes** (with exceptions, see description)"
716 | }
717 | }
718 | }
719 | },
720 | "additionalProperties": false
721 | },
722 | "BasicHookOperationSchema": {
723 | "id": "/BasicHookOperationSchema",
724 | "description":
725 | "Represents the inbound mechanics of hooks with optional subscribe/unsubscribe. Defers to list for fields.",
726 | "type": "object",
727 | "required": ["perform"],
728 | "properties": {
729 | "type": {
730 | "description":
731 | "Must be explicitly set to `\"hook\"` unless this hook is defined as part of a resource, in which case it's optional.",
732 | "type": "string",
733 | "enum": ["hook"],
734 | "docAnnotation": {
735 | "required": {
736 | "type": "replace",
737 | "value": "**yes** (with exceptions, see description)"
738 | }
739 | }
740 | },
741 | "resource": {
742 | "description":
743 | "Optionally reference and extends a resource. Allows Zapier to automatically tie together samples, lists and hooks, greatly improving the UX. EG: if you had another trigger reusing a resource but filtering the results.",
744 | "$ref": "/RefResourceSchema"
745 | },
746 | "perform": {
747 | "description":
748 | "A function that processes the inbound webhook request.",
749 | "$ref": "/FunctionSchema"
750 | },
751 | "performList": {
752 | "description":
753 | "Can get \"live\" data on demand instead of waiting for a hook. If you find yourself reaching for this - consider resources and their built-in hook/list methods. Note: this is required for public apps to ensure the best UX for the end-user. For private apps, you can ignore warnings about this property with the `--without-style` flag during `zapier push`.",
754 | "oneOf": [
755 | {
756 | "$ref": "/RequestSchema"
757 | },
758 | {
759 | "$ref": "/FunctionSchema"
760 | }
761 | ],
762 | "docAnnotation": {
763 | "required": {
764 | "type": "replace",
765 | "value": "**yes** (with exceptions, see description)"
766 | }
767 | }
768 | },
769 | "performSubscribe": {
770 | "description":
771 | "Takes a URL and any necessary data from the user and subscribes. Note: this is required for public apps to ensure the best UX for the end-user. For private apps, you can ignore warnings about this property with the `--without-style` flag during `zapier push`.",
772 | "oneOf": [
773 | {
774 | "$ref": "/RequestSchema"
775 | },
776 | {
777 | "$ref": "/FunctionSchema"
778 | }
779 | ],
780 | "docAnnotation": {
781 | "required": {
782 | "type": "replace",
783 | "value": "**yes** (with exceptions, see description)"
784 | }
785 | }
786 | },
787 | "performUnsubscribe": {
788 | "description":
789 | "Takes a URL and data from a previous subscribe call and unsubscribes. Note: this is required for public apps to ensure the best UX for the end-user. For private apps, you can ignore warnings about this property with the `--without-style` flag during `zapier push`.",
790 | "oneOf": [
791 | {
792 | "$ref": "/RequestSchema"
793 | },
794 | {
795 | "$ref": "/FunctionSchema"
796 | }
797 | ],
798 | "docAnnotation": {
799 | "required": {
800 | "type": "replace",
801 | "value": "**yes** (with exceptions, see description)"
802 | }
803 | }
804 | },
805 | "inputFields": {
806 | "description":
807 | "What should the form a user sees and configures look like?",
808 | "$ref": "/DynamicFieldsSchema"
809 | },
810 | "outputFields": {
811 | "description":
812 | "What fields of data will this return? Will use resource outputFields if missing, will also use sample if available.",
813 | "$ref": "/DynamicFieldsSchema"
814 | },
815 | "sample": {
816 | "description":
817 | "What does a sample of data look like? Will use resource sample if missing. Requirement waived if `display.hidden` is true or if this belongs to a resource that has a top-level sample",
818 | "type": "object",
819 | "minProperties": 1,
820 | "docAnnotation": {
821 | "required": {
822 | "type": "replace",
823 | "value": "**yes** (with exceptions, see description)"
824 | }
825 | }
826 | }
827 | },
828 | "additionalProperties": false
829 | },
830 | "BasicPollingOperationSchema": {
831 | "id": "/BasicPollingOperationSchema",
832 | "description": "Represents the fundamental mechanics of a trigger.",
833 | "type": "object",
834 | "required": ["perform"],
835 | "properties": {
836 | "type": {
837 | "description":
838 | "Clarify how this operation works (polling == pull or hook == push).",
839 | "type": "string",
840 | "default": "polling",
841 | "enum": ["polling"]
842 | },
843 | "resource": {
844 | "description":
845 | "Optionally reference and extends a resource. Allows Zapier to automatically tie together samples, lists and hooks, greatly improving the UX. EG: if you had another trigger reusing a resource but filtering the results.",
846 | "$ref": "/RefResourceSchema"
847 | },
848 | "perform": {
849 | "description":
850 | "How will Zapier get the data? This can be a function like `(z) => [{id: 123}]` or a request like `{url: 'http...'}`.",
851 | "oneOf": [
852 | {
853 | "$ref": "/RequestSchema"
854 | },
855 | {
856 | "$ref": "/FunctionSchema"
857 | }
858 | ]
859 | },
860 | "canPaginate": {
861 | "description": "Does this endpoint support a page offset?",
862 | "type": "boolean"
863 | },
864 | "inputFields": {
865 | "description":
866 | "What should the form a user sees and configures look like?",
867 | "$ref": "/DynamicFieldsSchema"
868 | },
869 | "outputFields": {
870 | "description":
871 | "What fields of data will this return? Will use resource outputFields if missing, will also use sample if available.",
872 | "$ref": "/DynamicFieldsSchema"
873 | },
874 | "sample": {
875 | "description":
876 | "What does a sample of data look like? Will use resource sample if missing. Requirement waived if `display.hidden` is true or if this belongs to a resource that has a top-level sample",
877 | "type": "object",
878 | "minProperties": 1,
879 | "docAnnotation": {
880 | "required": {
881 | "type": "replace",
882 | "value": "**yes** (with exceptions, see description)"
883 | }
884 | }
885 | }
886 | },
887 | "additionalProperties": false
888 | },
889 | "BasicActionOperationSchema": {
890 | "id": "/BasicActionOperationSchema",
891 | "description": "Represents the fundamental mechanics of a search/create.",
892 | "type": "object",
893 | "required": ["perform"],
894 | "properties": {
895 | "resource": {
896 | "description":
897 | "Optionally reference and extends a resource. Allows Zapier to automatically tie together samples, lists and hooks, greatly improving the UX. EG: if you had another trigger reusing a resource but filtering the results.",
898 | "$ref": "/RefResourceSchema"
899 | },
900 | "perform": {
901 | "description":
902 | "How will Zapier get the data? This can be a function like `(z) => [{id: 123}]` or a request like `{url: 'http...'}`.",
903 | "oneOf": [
904 | {
905 | "$ref": "/RequestSchema"
906 | },
907 | {
908 | "$ref": "/FunctionSchema"
909 | }
910 | ]
911 | },
912 | "performResume": {
913 | "description":
914 | "A function that parses data from a perform + callback to resume this action. For use with callback semantics",
915 | "$ref": "/FunctionSchema"
916 | },
917 | "performGet": {
918 | "description":
919 | "How will Zapier get a single record? If you find yourself reaching for this - consider resources and their built-in get methods.",
920 | "oneOf": [
921 | {
922 | "$ref": "/RequestSchema"
923 | },
924 | {
925 | "$ref": "/FunctionSchema"
926 | }
927 | ]
928 | },
929 | "inputFields": {
930 | "description":
931 | "What should the form a user sees and configures look like?",
932 | "$ref": "/DynamicFieldsSchema"
933 | },
934 | "outputFields": {
935 | "description":
936 | "What fields of data will this return? Will use resource outputFields if missing, will also use sample if available.",
937 | "$ref": "/DynamicFieldsSchema"
938 | },
939 | "sample": {
940 | "description":
941 | "What does a sample of data look like? Will use resource sample if missing. Requirement waived if `display.hidden` is true or if this belongs to a resource that has a top-level sample",
942 | "type": "object",
943 | "minProperties": 1,
944 | "docAnnotation": {
945 | "required": {
946 | "type": "replace",
947 | "value": "**yes** (with exceptions, see description)"
948 | }
949 | }
950 | }
951 | },
952 | "additionalProperties": false
953 | },
954 | "ResourceMethodGetSchema": {
955 | "id": "/ResourceMethodGetSchema",
956 | "description":
957 | "How will we get a single object given a unique identifier/id?",
958 | "type": "object",
959 | "required": ["display", "operation"],
960 | "properties": {
961 | "display": {
962 | "description":
963 | "Define how this get method will be exposed in the UI.",
964 | "$ref": "/BasicDisplaySchema"
965 | },
966 | "operation": {
967 | "description": "Define how this get method will work.",
968 | "$ref": "/BasicOperationSchema"
969 | }
970 | },
971 | "additionalProperties": false
972 | },
973 | "ResourceMethodHookSchema": {
974 | "id": "/ResourceMethodHookSchema",
975 | "description":
976 | "How will we get notified of new objects? Will be turned into a trigger automatically.",
977 | "type": "object",
978 | "required": ["display", "operation"],
979 | "properties": {
980 | "display": {
981 | "description":
982 | "Define how this hook/trigger method will be exposed in the UI.",
983 | "$ref": "/BasicDisplaySchema"
984 | },
985 | "operation": {
986 | "description": "Define how this hook/trigger method will work.",
987 | "$ref": "/BasicHookOperationSchema"
988 | }
989 | },
990 | "additionalProperties": false
991 | },
992 | "ResourceMethodListSchema": {
993 | "id": "/ResourceMethodListSchema",
994 | "description":
995 | "How will we get a list of new objects? Will be turned into a trigger automatically.",
996 | "type": "object",
997 | "required": ["display", "operation"],
998 | "properties": {
999 | "display": {
1000 | "description":
1001 | "Define how this list/trigger method will be exposed in the UI.",
1002 | "$ref": "/BasicDisplaySchema"
1003 | },
1004 | "operation": {
1005 | "description": "Define how this list/trigger method will work.",
1006 | "$ref": "/BasicPollingOperationSchema"
1007 | }
1008 | },
1009 | "additionalProperties": false
1010 | },
1011 | "ResourceMethodSearchSchema": {
1012 | "id": "/ResourceMethodSearchSchema",
1013 | "description":
1014 | "How will we find a specific object given filters or search terms? Will be turned into a search automatically.",
1015 | "type": "object",
1016 | "required": ["display", "operation"],
1017 | "properties": {
1018 | "display": {
1019 | "description":
1020 | "Define how this search method will be exposed in the UI.",
1021 | "$ref": "/BasicDisplaySchema"
1022 | },
1023 | "operation": {
1024 | "description": "Define how this search method will work.",
1025 | "$ref": "/BasicActionOperationSchema"
1026 | }
1027 | },
1028 | "additionalProperties": false
1029 | },
1030 | "ResourceMethodCreateSchema": {
1031 | "id": "/ResourceMethodCreateSchema",
1032 | "description":
1033 | "How will we find create a specific object given inputs? Will be turned into a create automatically.",
1034 | "type": "object",
1035 | "required": ["display", "operation"],
1036 | "properties": {
1037 | "display": {
1038 | "description":
1039 | "Define how this create method will be exposed in the UI.",
1040 | "$ref": "/BasicDisplaySchema"
1041 | },
1042 | "operation": {
1043 | "description": "Define how this create method will work.",
1044 | "$ref": "/BasicActionOperationSchema"
1045 | }
1046 | },
1047 | "additionalProperties": false
1048 | },
1049 | "KeySchema": {
1050 | "id": "/KeySchema",
1051 | "description": "A unique identifier for this item.",
1052 | "type": "string",
1053 | "minLength": 2,
1054 | "pattern": "^[a-zA-Z]+[a-zA-Z0-9_]*$"
1055 | },
1056 | "ResourceSchema": {
1057 | "id": "/ResourceSchema",
1058 | "description":
1059 | "Represents a resource, which will in turn power triggers, searches, or creates.",
1060 | "type": "object",
1061 | "required": ["key", "noun"],
1062 | "properties": {
1063 | "key": {
1064 | "description": "A key to uniquely identify this resource.",
1065 | "$ref": "/KeySchema"
1066 | },
1067 | "noun": {
1068 | "description":
1069 | "A noun for this resource that completes the sentence \"create a new XXX\".",
1070 | "type": "string",
1071 | "minLength": 2,
1072 | "maxLength": 255
1073 | },
1074 | "get": {
1075 | "description":
1076 | "How will we get a single object given a unique identifier/id?",
1077 | "$ref": "/ResourceMethodGetSchema"
1078 | },
1079 | "hook": {
1080 | "description":
1081 | "How will we get notified of new objects? Will be turned into a trigger automatically.",
1082 | "$ref": "/ResourceMethodHookSchema"
1083 | },
1084 | "list": {
1085 | "description":
1086 | "How will we get a list of new objects? Will be turned into a trigger automatically.",
1087 | "$ref": "/ResourceMethodListSchema"
1088 | },
1089 | "search": {
1090 | "description":
1091 | "How will we find a specific object given filters or search terms? Will be turned into a search automatically.",
1092 | "$ref": "/ResourceMethodSearchSchema"
1093 | },
1094 | "create": {
1095 | "description":
1096 | "How will we find create a specific object given inputs? Will be turned into a create automatically.",
1097 | "$ref": "/ResourceMethodCreateSchema"
1098 | },
1099 | "outputFields": {
1100 | "description": "What fields of data will this return?",
1101 | "$ref": "/DynamicFieldsSchema"
1102 | },
1103 | "sample": {
1104 | "description": "What does a sample of data look like?",
1105 | "type": "object",
1106 | "minProperties": 1
1107 | }
1108 | },
1109 | "additionalProperties": false
1110 | },
1111 | "TriggerSchema": {
1112 | "id": "/TriggerSchema",
1113 | "description": "How will Zapier get notified of new objects?",
1114 | "type": "object",
1115 | "required": ["key", "noun", "display", "operation"],
1116 | "properties": {
1117 | "key": {
1118 | "description": "A key to uniquely identify this trigger.",
1119 | "$ref": "/KeySchema"
1120 | },
1121 | "noun": {
1122 | "description":
1123 | "A noun for this trigger that completes the sentence \"triggers on a new XXX\".",
1124 | "type": "string",
1125 | "minLength": 2,
1126 | "maxLength": 255
1127 | },
1128 | "display": {
1129 | "description": "Configures the UI for this trigger.",
1130 | "$ref": "/BasicDisplaySchema"
1131 | },
1132 | "operation": {
1133 | "description": "Powers the functionality for this trigger.",
1134 | "anyOf": [
1135 | {
1136 | "$ref": "/BasicPollingOperationSchema"
1137 | },
1138 | {
1139 | "$ref": "/BasicHookOperationSchema"
1140 | }
1141 | ]
1142 | }
1143 | },
1144 | "additionalProperties": false
1145 | },
1146 | "SearchSchema": {
1147 | "id": "/SearchSchema",
1148 | "description": "How will Zapier search for existing objects?",
1149 | "type": "object",
1150 | "required": ["key", "noun", "display", "operation"],
1151 | "properties": {
1152 | "key": {
1153 | "description": "A key to uniquely identify this search.",
1154 | "$ref": "/KeySchema"
1155 | },
1156 | "noun": {
1157 | "description":
1158 | "A noun for this search that completes the sentence \"finds a specific XXX\".",
1159 | "type": "string",
1160 | "minLength": 2,
1161 | "maxLength": 255
1162 | },
1163 | "display": {
1164 | "description": "Configures the UI for this search.",
1165 | "$ref": "/BasicDisplaySchema"
1166 | },
1167 | "operation": {
1168 | "description": "Powers the functionality for this search.",
1169 | "$ref": "/BasicActionOperationSchema"
1170 | }
1171 | },
1172 | "additionalProperties": false
1173 | },
1174 | "BasicCreateActionOperationSchema": {
1175 | "id": "/BasicCreateActionOperationSchema",
1176 | "description": "Represents the fundamental mechanics of a create.",
1177 | "type": "object",
1178 | "required": ["perform"],
1179 | "properties": {
1180 | "resource": {
1181 | "description":
1182 | "Optionally reference and extends a resource. Allows Zapier to automatically tie together samples, lists and hooks, greatly improving the UX. EG: if you had another trigger reusing a resource but filtering the results.",
1183 | "$ref": "/RefResourceSchema"
1184 | },
1185 | "perform": {
1186 | "description":
1187 | "How will Zapier get the data? This can be a function like `(z) => [{id: 123}]` or a request like `{url: 'http...'}`.",
1188 | "oneOf": [
1189 | {
1190 | "$ref": "/RequestSchema"
1191 | },
1192 | {
1193 | "$ref": "/FunctionSchema"
1194 | }
1195 | ]
1196 | },
1197 | "performResume": {
1198 | "description":
1199 | "A function that parses data from a perform + callback to resume this action. For use with callback semantics",
1200 | "$ref": "/FunctionSchema"
1201 | },
1202 | "performGet": {
1203 | "description":
1204 | "How will Zapier get a single record? If you find yourself reaching for this - consider resources and their built-in get methods.",
1205 | "oneOf": [
1206 | {
1207 | "$ref": "/RequestSchema"
1208 | },
1209 | {
1210 | "$ref": "/FunctionSchema"
1211 | }
1212 | ]
1213 | },
1214 | "inputFields": {
1215 | "description":
1216 | "What should the form a user sees and configures look like?",
1217 | "$ref": "/DynamicFieldsSchema"
1218 | },
1219 | "outputFields": {
1220 | "description":
1221 | "What fields of data will this return? Will use resource outputFields if missing, will also use sample if available.",
1222 | "$ref": "/DynamicFieldsSchema"
1223 | },
1224 | "sample": {
1225 | "description":
1226 | "What does a sample of data look like? Will use resource sample if missing. Requirement waived if `display.hidden` is true or if this belongs to a resource that has a top-level sample",
1227 | "type": "object",
1228 | "minProperties": 1,
1229 | "docAnnotation": {
1230 | "required": {
1231 | "type": "replace",
1232 | "value": "**yes** (with exceptions, see description)"
1233 | }
1234 | }
1235 | },
1236 | "shouldLock": {
1237 | "description":
1238 | "Should this action be performed one at a time (avoid concurrency)?",
1239 | "type": "boolean"
1240 | }
1241 | },
1242 | "additionalProperties": false
1243 | },
1244 | "CreateSchema": {
1245 | "id": "/CreateSchema",
1246 | "description": "How will Zapier create a new object?",
1247 | "type": "object",
1248 | "required": ["key", "noun", "display", "operation"],
1249 | "properties": {
1250 | "key": {
1251 | "description": "A key to uniquely identify this create.",
1252 | "$ref": "/KeySchema"
1253 | },
1254 | "noun": {
1255 | "description":
1256 | "A noun for this create that completes the sentence \"creates a new XXX\".",
1257 | "type": "string",
1258 | "minLength": 2,
1259 | "maxLength": 255
1260 | },
1261 | "display": {
1262 | "description": "Configures the UI for this create.",
1263 | "$ref": "/BasicDisplaySchema"
1264 | },
1265 | "operation": {
1266 | "description": "Powers the functionality for this create.",
1267 | "$ref": "/BasicCreateActionOperationSchema"
1268 | }
1269 | },
1270 | "additionalProperties": false
1271 | },
1272 | "SearchOrCreateSchema": {
1273 | "id": "/SearchOrCreateSchema",
1274 | "description":
1275 | "Pair an existing search and a create to enable \"Find or Create\" functionality in your app",
1276 | "type": "object",
1277 | "required": ["key", "display", "search", "create"],
1278 | "properties": {
1279 | "key": {
1280 | "description":
1281 | "A key to uniquely identify this search-or-create. Must match the search key.",
1282 | "$ref": "/KeySchema"
1283 | },
1284 | "display": {
1285 | "description": "Configures the UI for this search-or-create.",
1286 | "$ref": "/BasicDisplaySchema"
1287 | },
1288 | "search": {
1289 | "description":
1290 | "The key of the search that powers this search-or-create",
1291 | "$ref": "/KeySchema"
1292 | },
1293 | "create": {
1294 | "description":
1295 | "The key of the create that powers this search-or-create",
1296 | "$ref": "/KeySchema"
1297 | }
1298 | },
1299 | "additionalProperties": false
1300 | },
1301 | "AuthenticationSchema": {
1302 | "id": "/AuthenticationSchema",
1303 | "description": "Represents authentication schemes.",
1304 | "type": "object",
1305 | "required": ["type", "test"],
1306 | "properties": {
1307 | "type": {
1308 | "description": "Choose which scheme you want to use.",
1309 | "type": "string",
1310 | "enum": ["basic", "custom", "digest", "oauth1", "oauth2", "session"]
1311 | },
1312 | "test": {
1313 | "description":
1314 | "A function or request that confirms the authentication is working.",
1315 | "oneOf": [
1316 | {
1317 | "$ref": "/RequestSchema"
1318 | },
1319 | {
1320 | "$ref": "/FunctionSchema"
1321 | }
1322 | ]
1323 | },
1324 | "fields": {
1325 | "description":
1326 | "Fields you can request from the user before they connect your app to Zapier.",
1327 | "$ref": "/FieldsSchema"
1328 | },
1329 | "connectionLabel": {
1330 | "description":
1331 | "A string with variables, function, or request that returns the connection label for the authenticated user.",
1332 | "anyOf": [
1333 | {
1334 | "$ref": "/RequestSchema"
1335 | },
1336 | {
1337 | "$ref": "/FunctionSchema"
1338 | },
1339 | {
1340 | "type": "string"
1341 | }
1342 | ]
1343 | },
1344 | "basicConfig": {
1345 | "$ref": "/AuthenticationBasicConfigSchema"
1346 | },
1347 | "customConfig": {
1348 | "$ref": "/AuthenticationCustomConfigSchema"
1349 | },
1350 | "digestConfig": {
1351 | "$ref": "/AuthenticationDigestConfigSchema"
1352 | },
1353 | "oauth1Config": {
1354 | "$ref": "/AuthenticationOAuth1ConfigSchema"
1355 | },
1356 | "oauth2Config": {
1357 | "$ref": "/AuthenticationOAuth2ConfigSchema"
1358 | },
1359 | "sessionConfig": {
1360 | "$ref": "/AuthenticationSessionConfigSchema"
1361 | }
1362 | },
1363 | "additionalProperties": false
1364 | },
1365 | "ResourcesSchema": {
1366 | "id": "/ResourcesSchema",
1367 | "description":
1368 | "All the resources that underlie common CRUD methods powering automatically handled triggers, creates, and searches for your app. Zapier will break these apart for you.",
1369 | "type": "object",
1370 | "patternProperties": {
1371 | "^[a-zA-Z]+[a-zA-Z0-9_]*$": {
1372 | "description":
1373 | "Any unique key can be used and its values will be validated against the ResourceSchema.",
1374 | "$ref": "/ResourceSchema"
1375 | }
1376 | },
1377 | "additionalProperties": false
1378 | },
1379 | "TriggersSchema": {
1380 | "id": "/TriggersSchema",
1381 | "description":
1382 | "Enumerates the triggers your app has available for users.",
1383 | "type": "object",
1384 | "patternProperties": {
1385 | "^[a-zA-Z]+[a-zA-Z0-9_]*$": {
1386 | "description":
1387 | "Any unique key can be used and its values will be validated against the TriggerSchema.",
1388 | "$ref": "/TriggerSchema"
1389 | }
1390 | },
1391 | "additionalProperties": false
1392 | },
1393 | "SearchesSchema": {
1394 | "id": "/SearchesSchema",
1395 | "description":
1396 | "Enumerates the searches your app has available for users.",
1397 | "type": "object",
1398 | "patternProperties": {
1399 | "^[a-zA-Z]+[a-zA-Z0-9_]*$": {
1400 | "description":
1401 | "Any unique key can be used and its values will be validated against the SearchSchema.",
1402 | "$ref": "/SearchSchema"
1403 | }
1404 | }
1405 | },
1406 | "CreatesSchema": {
1407 | "id": "/CreatesSchema",
1408 | "description": "Enumerates the creates your app has available for users.",
1409 | "type": "object",
1410 | "patternProperties": {
1411 | "^[a-zA-Z]+[a-zA-Z0-9_]*$": {
1412 | "description":
1413 | "Any unique key can be used and its values will be validated against the CreateSchema.",
1414 | "$ref": "/CreateSchema"
1415 | }
1416 | }
1417 | },
1418 | "SearchOrCreatesSchema": {
1419 | "id": "/SearchOrCreatesSchema",
1420 | "description":
1421 | "Enumerates the search-or-creates your app has available for users.",
1422 | "type": "object",
1423 | "patternProperties": {
1424 | "^[a-zA-Z]+[a-zA-Z0-9_]*$": {
1425 | "description":
1426 | "Any unique key can be used and its values will be validated against the SearchOrCreateSchema.",
1427 | "$ref": "/SearchOrCreateSchema"
1428 | }
1429 | }
1430 | },
1431 | "VersionSchema": {
1432 | "id": "/VersionSchema",
1433 | "description":
1434 | "Represents a simplified semver string, from `0.0.0` to `999.999.999`.",
1435 | "type": "string",
1436 | "pattern": "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$"
1437 | },
1438 | "MiddlewaresSchema": {
1439 | "id": "/MiddlewaresSchema",
1440 | "description":
1441 | "List of before or after middlewares. Can be an array of functions or a single function",
1442 | "oneOf": [
1443 | {
1444 | "type": "array",
1445 | "items": {
1446 | "$ref": "/FunctionSchema"
1447 | }
1448 | },
1449 | {
1450 | "$ref": "/FunctionSchema"
1451 | }
1452 | ],
1453 | "additionalProperties": false
1454 | },
1455 | "HydratorsSchema": {
1456 | "id": "/HydratorsSchema",
1457 | "description":
1458 | "A bank of named functions that you can use in `z.hydrate('someName')` to lazily load data.",
1459 | "type": "object",
1460 | "patternProperties": {
1461 | "^[a-zA-Z]+[a-zA-Z0-9]*$": {
1462 | "description":
1463 | "Any unique key can be used in `z.hydrate('uniqueKeyHere')`.",
1464 | "$ref": "/FunctionSchema"
1465 | }
1466 | },
1467 | "additionalProperties": false
1468 | },
1469 | "AppFlagsSchema": {
1470 | "id": "/AppFlagsSchema",
1471 | "description": "Codifies high-level options for your app.",
1472 | "type": "object",
1473 | "properties": {
1474 | "skipHttpPatch": {
1475 | "description":
1476 | "By default, Zapier patches the core `http` module so that all requests (including those from 3rd-party SDKs) can be logged. Set this to true if you're seeing issues using an SDK (such as AWS).",
1477 | "type": "boolean"
1478 | }
1479 | },
1480 | "additionalProperties": false
1481 | }
1482 | }
1483 | }
1484 |
--------------------------------------------------------------------------------