From 9baf4988f347f59b0bc00cff1a274313faa92654 Mon Sep 17 00:00:00 2001 From: Emanuele Saccomandi <48432054+emajo@users.noreply.github.com> Date: Sun, 9 Jul 2023 15:52:04 +0200 Subject: [PATCH] Zapier generator (#15997) * First version of Zapier Generator * fixed zapier codegen * added zapier templates * added zapier sample * added zapier doc * added zapier generator form data management * added samples generation * updated docs * fixed zapier api template * fixed zapier samples export * added zapier readme template * fixed zapier readme template * added petstore readme * cleaned zapier generator * updated samples * fixed zapier enum label * cleaned code * updated samples * improved zapier search actions * updated samples --------- Co-authored-by: Mauro Valota Co-authored-by: William Cheng --- bin/configs/zapier-petstore-new.yaml | 7 + docs/generators.md | 1 + docs/generators/zapier.md | 178 +++++++++ .../languages/ZapierClientCodegen.java | 199 ++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + .../src/main/resources/zapier/README.mustache | 51 +++ .../main/resources/zapier/actions.mustache | 23 ++ .../src/main/resources/zapier/api.mustache | 106 +++++ .../resources/zapier/authentication.mustache | 4 + .../src/main/resources/zapier/index.mustache | 10 + .../src/main/resources/zapier/model.mustache | 93 +++++ .../main/resources/zapier/package.mustache | 27 ++ .../src/main/resources/zapier/sample.mustache | 18 + .../src/main/resources/zapier/utils.mustache | 34 ++ .../src/Org.OpenAPITools/Api/FakeApi.cs | 9 +- .../src/Org.OpenAPITools/Api/FakeApi.cs | 4 + .../petstore/zapier/.openapi-generator-ignore | 23 ++ .../petstore/zapier/.openapi-generator/FILES | 15 + .../zapier/.openapi-generator/VERSION | 1 + samples/client/petstore/zapier/README.md | 51 +++ samples/client/petstore/zapier/apis/PetApi.js | 370 ++++++++++++++++++ .../client/petstore/zapier/apis/StoreApi.js | 170 ++++++++ .../client/petstore/zapier/apis/UserApi.js | 343 ++++++++++++++++ .../client/petstore/zapier/authentication.js | 4 + samples/client/petstore/zapier/index.js | 10 + .../petstore/zapier/models/ApiResponse.js | 32 ++ .../client/petstore/zapier/models/Category.js | 26 ++ .../client/petstore/zapier/models/Order.js | 55 +++ samples/client/petstore/zapier/models/Pet.js | 54 +++ samples/client/petstore/zapier/models/Tag.js | 26 ++ samples/client/petstore/zapier/models/User.js | 62 +++ .../petstore/zapier/operations/actions.js | 32 ++ samples/client/petstore/zapier/package.json | 27 ++ .../client/petstore/zapier/samples/PetApi.js | 14 + .../petstore/zapier/samples/StoreApi.js | 6 + .../client/petstore/zapier/samples/UserApi.js | 4 + samples/client/petstore/zapier/utils/utils.js | 34 ++ 37 files changed, 2123 insertions(+), 1 deletion(-) create mode 100644 bin/configs/zapier-petstore-new.yaml create mode 100644 docs/generators/zapier.md create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ZapierClientCodegen.java create mode 100644 modules/openapi-generator/src/main/resources/zapier/README.mustache create mode 100644 modules/openapi-generator/src/main/resources/zapier/actions.mustache create mode 100644 modules/openapi-generator/src/main/resources/zapier/api.mustache create mode 100644 modules/openapi-generator/src/main/resources/zapier/authentication.mustache create mode 100644 modules/openapi-generator/src/main/resources/zapier/index.mustache create mode 100644 modules/openapi-generator/src/main/resources/zapier/model.mustache create mode 100644 modules/openapi-generator/src/main/resources/zapier/package.mustache create mode 100644 modules/openapi-generator/src/main/resources/zapier/sample.mustache create mode 100644 modules/openapi-generator/src/main/resources/zapier/utils.mustache create mode 100644 samples/client/petstore/zapier/.openapi-generator-ignore create mode 100644 samples/client/petstore/zapier/.openapi-generator/FILES create mode 100644 samples/client/petstore/zapier/.openapi-generator/VERSION create mode 100644 samples/client/petstore/zapier/README.md create mode 100644 samples/client/petstore/zapier/apis/PetApi.js create mode 100644 samples/client/petstore/zapier/apis/StoreApi.js create mode 100644 samples/client/petstore/zapier/apis/UserApi.js create mode 100644 samples/client/petstore/zapier/authentication.js create mode 100644 samples/client/petstore/zapier/index.js create mode 100644 samples/client/petstore/zapier/models/ApiResponse.js create mode 100644 samples/client/petstore/zapier/models/Category.js create mode 100644 samples/client/petstore/zapier/models/Order.js create mode 100644 samples/client/petstore/zapier/models/Pet.js create mode 100644 samples/client/petstore/zapier/models/Tag.js create mode 100644 samples/client/petstore/zapier/models/User.js create mode 100644 samples/client/petstore/zapier/operations/actions.js create mode 100644 samples/client/petstore/zapier/package.json create mode 100644 samples/client/petstore/zapier/samples/PetApi.js create mode 100644 samples/client/petstore/zapier/samples/StoreApi.js create mode 100644 samples/client/petstore/zapier/samples/UserApi.js create mode 100644 samples/client/petstore/zapier/utils/utils.js diff --git a/bin/configs/zapier-petstore-new.yaml b/bin/configs/zapier-petstore-new.yaml new file mode 100644 index 00000000000..b57c9594850 --- /dev/null +++ b/bin/configs/zapier-petstore-new.yaml @@ -0,0 +1,7 @@ +generatorName: zapier +outputDir: samples/client/petstore/zapier +inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml +templateDir: modules/openapi-generator/src/main/resources/zapier +additionalProperties: + npmVersion: 0.0.1 + npmName: '@openapitools/zapier' \ No newline at end of file diff --git a/docs/generators.md b/docs/generators.md index c1adb82b6d6..6b175338702 100644 --- a/docs/generators.md +++ b/docs/generators.md @@ -74,6 +74,7 @@ The following generators are available: * [typescript-redux-query](generators/typescript-redux-query.md) * [typescript-rxjs](generators/typescript-rxjs.md) * [xojo-client](generators/xojo-client.md) +* [zapier](generators/zapier.md) ## SERVER generators diff --git a/docs/generators/zapier.md b/docs/generators/zapier.md new file mode 100644 index 00000000000..f054dcd0a6c --- /dev/null +++ b/docs/generators/zapier.md @@ -0,0 +1,178 @@ +--- +title: Documentation for the zapier Generator +--- + +## METADATA + +| Property | Value | Notes | +| -------- | ----- | ----- | +| generator name | zapier | pass this to the generate command after -g | +| generator stability | STABLE | | +| generator type | CLIENT | | +| generator default templating engine | mustache | | +| helpTxt | Generates a zapier client. | | + +## CONFIG OPTIONS +These options may be applied as additional-properties (cli) or configOptions (plugins). Refer to [configuration docs](https://openapi-generator.tech/docs/configuration) for more details. + +| Option | Description | Values | Default | +| ------ | ----------- | ------ | ------- | +|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false| +|disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|
**false**
The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.
**true**
Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.
|true| +|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true| +|enumUnknownDefaultCase|If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response.With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.|
**false**
No changes to the enum's are made, this is the default option.
**true**
With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the enum case sent by the server is not known by the client/spec, can safely be decoded to this case.
|false| +|legacyDiscriminatorBehavior|Set to false for generators with better support for discriminators. (Python, Java, Go, PowerShell, C# have this enabled by default).|
**true**
The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.
**false**
The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.
|true| +|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false| +|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true| +|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| + +## IMPORT MAPPING + +| Type/Alias | Imports | +| ---------- | ------- | + + +## INSTANTIATION TYPES + +| Type/Alias | Instantiated By | +| ---------- | --------------- | +|array|array| +|list|array| +|map|object| +|set|array| + + +## LANGUAGE PRIMITIVES + + + +## RESERVED WORDS + + + +## FEATURE SET + + +### Client Modification Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|BasePath|✗|ToolingExtension +|Authorizations|✗|ToolingExtension +|UserAgent|✗|ToolingExtension +|MockServer|✗|ToolingExtension + +### Data Type Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Custom|✗|OAS2,OAS3 +|Int32|✓|OAS2,OAS3 +|Int64|✓|OAS2,OAS3 +|Float|✓|OAS2,OAS3 +|Double|✓|OAS2,OAS3 +|Decimal|✓|ToolingExtension +|String|✓|OAS2,OAS3 +|Byte|✓|OAS2,OAS3 +|Binary|✓|OAS2,OAS3 +|Boolean|✓|OAS2,OAS3 +|Date|✓|OAS2,OAS3 +|DateTime|✓|OAS2,OAS3 +|Password|✓|OAS2,OAS3 +|File|✓|OAS2 +|Uuid|✗| +|Array|✓|OAS2,OAS3 +|Null|✗|OAS3 +|AnyType|✗|OAS2,OAS3 +|Object|✓|OAS2,OAS3 +|Maps|✓|ToolingExtension +|CollectionFormat|✓|OAS2 +|CollectionFormatMulti|✓|OAS2 +|Enum|✓|OAS2,OAS3 +|ArrayOfEnum|✓|ToolingExtension +|ArrayOfModel|✓|ToolingExtension +|ArrayOfCollectionOfPrimitives|✓|ToolingExtension +|ArrayOfCollectionOfModel|✓|ToolingExtension +|ArrayOfCollectionOfEnum|✓|ToolingExtension +|MapOfEnum|✓|ToolingExtension +|MapOfModel|✓|ToolingExtension +|MapOfCollectionOfPrimitives|✓|ToolingExtension +|MapOfCollectionOfModel|✓|ToolingExtension +|MapOfCollectionOfEnum|✓|ToolingExtension + +### Documentation Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Readme|✗|ToolingExtension +|Model|✓|ToolingExtension +|Api|✓|ToolingExtension + +### Global Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Host|✓|OAS2,OAS3 +|BasePath|✓|OAS2,OAS3 +|Info|✓|OAS2,OAS3 +|Schemes|✗|OAS2,OAS3 +|PartialSchemes|✓|OAS2,OAS3 +|Consumes|✓|OAS2 +|Produces|✓|OAS2 +|ExternalDocumentation|✓|OAS2,OAS3 +|Examples|✓|OAS2,OAS3 +|XMLStructureDefinitions|✗|OAS2,OAS3 +|MultiServer|✗|OAS3 +|ParameterizedServer|✗|OAS3 +|ParameterStyling|✗|OAS3 +|Callbacks|✓|OAS3 +|LinkObjects|✗|OAS3 + +### Parameter Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Path|✓|OAS2,OAS3 +|Query|✓|OAS2,OAS3 +|Header|✓|OAS2,OAS3 +|Body|✓|OAS2 +|FormUnencoded|✓|OAS2 +|FormMultipart|✓|OAS2 +|Cookie|✓|OAS3 + +### Schema Support Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Simple|✓|OAS2,OAS3 +|Composite|✓|OAS2,OAS3 +|Polymorphism|✓|OAS2,OAS3 +|Union|✗|OAS3 +|allOf|✗|OAS2,OAS3 +|anyOf|✗|OAS3 +|oneOf|✗|OAS3 +|not|✗|OAS3 + +### Security Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|BasicAuth|✓|OAS2,OAS3 +|ApiKey|✓|OAS2,OAS3 +|OpenIDConnect|✗|OAS3 +|BearerToken|✓|OAS3 +|OAuth2_Implicit|✓|OAS2,OAS3 +|OAuth2_Password|✓|OAS2,OAS3 +|OAuth2_ClientCredentials|✓|OAS2,OAS3 +|OAuth2_AuthorizationCode|✓|OAS2,OAS3 +|SignatureAuth|✗|OAS3 + +### Wire Format Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|JSON|✓|OAS2,OAS3 +|XML|✓|OAS2,OAS3 +|PROTOBUF|✗|ToolingExtension +|Custom|✗|OAS2,OAS3 diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ZapierClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ZapierClientCodegen.java new file mode 100644 index 00000000000..52bb93222e2 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ZapierClientCodegen.java @@ -0,0 +1,199 @@ +package org.openapitools.codegen.languages; + +import com.fasterxml.jackson.core.JsonProcessingException; +import io.swagger.v3.core.util.Json; +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.responses.ApiResponse; +import org.openapitools.codegen.*; +import org.openapitools.codegen.utils.ModelUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.*; + +import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER; +import static org.openapitools.codegen.utils.StringUtils.camelize; +import static org.openapitools.codegen.utils.StringUtils.escape; + +public class ZapierClientCodegen extends DefaultCodegen implements CodegenConfig { + public static final String PROJECT_NAME = "projectName"; + + private final Logger LOGGER = LoggerFactory.getLogger(ZapierClientCodegen.class); + + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + public String getName() { + return "zapier"; + } + + public String getHelp() { + return "Generates a zapier client."; + } + + public ZapierClientCodegen() { + super(); + + outputFolder = "generated-code" + File.separator + "zapier"; + modelTemplateFiles.put("model.mustache", ".js"); + apiTemplateFiles.put("api.mustache", ".js"); + embeddedTemplateDir = templateDir = "zapier"; + apiPackage = "apis"; + testPackage = "samples"; + modelPackage = "models"; + apiTestTemplateFiles.put("sample.mustache", ".js"); + supportingFiles.add(new SupportingFile("actions.mustache", "operations", "actions.js")); + supportingFiles.add(new SupportingFile("utils.mustache", "utils", "utils.js")); + supportingFiles.add(new SupportingFile("index.mustache", "", "index.js")); + supportingFiles.add(new SupportingFile("authentication.mustache", "", "authentication.js")); + supportingFiles.add(new SupportingFile("package.mustache", "", "package.json")); + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + + languageSpecificPrimitives.clear(); + languageSpecificPrimitives = new HashSet<>( + Arrays.asList("number", "integer", "string", "boolean", "array", "file", "object") + ); + + instantiationTypes.put("array", "array"); + instantiationTypes.put("set", "array"); + instantiationTypes.put("list", "array"); + instantiationTypes.put("map", "object"); + typeMapping = new HashMap<>(); + typeMapping.put("array", "array"); + typeMapping.put("set", "array"); + typeMapping.put("map", "object"); + typeMapping.put("List", "array"); + typeMapping.put("boolean", "boolean"); + typeMapping.put("string", "string"); + typeMapping.put("int", "integer"); + typeMapping.put("float", "number"); + typeMapping.put("number", "number"); + typeMapping.put("decimal", "number"); + typeMapping.put("DateTime", "string"); + typeMapping.put("date", "string"); + typeMapping.put("long", "number"); + typeMapping.put("short", "number"); + typeMapping.put("char", "string"); + typeMapping.put("double", "number"); + typeMapping.put("object", "object"); + typeMapping.put("integer", "integer"); + typeMapping.put("binary", "file"); + typeMapping.put("file", "file"); + typeMapping.put("UUID", "string"); + typeMapping.put("URI", "string"); + } + + @Override + protected void initializeSpecialCharacterMapping() { + super.initializeSpecialCharacterMapping(); + specialCharReplacements.remove("_"); + } + + /** + * Works identically to {@link DefaultCodegen#toParamName(String)} but doesn't camelize. + * + * @param name Codegen property object + * @return the sanitized parameter name + */ + @Override + public String toParamName(String name) { + if (reservedWords.contains(name)) { + return escapeReservedWord(name); + } else if (((CharSequence) name).chars().anyMatch(character -> specialCharReplacements.keySet().contains(String.valueOf((char) character)))) { + return escape(name, specialCharReplacements, null, null); + } + return name; + } + + @Override + public String toModelName(final String name) { + return name; + } + + @Override + public String toModelImport(String name) { + return "const " + name + " = " + "require('../" + modelPackage() + "/" + name + "');"; + } + + @Override + public String getSchemaType(Schema p) { + String openAPIType = super.getSchemaType(p); + String type = null; + if (typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); + if (!needToImport(type)) { + return type; + } + } else { + type = openAPIType; + } + if (null == type) { + LOGGER.error("No Type defined for Schema {}", p); + } + return toModelName(type); + } + + @Override + public String toModelFilename(String name) { + return name; + } + + @Override + public String toApiTestFilename(String name) { + return toApiName(name); + } + + @Override + public GeneratorLanguage generatorLanguage() { return null; } + + @Override + public String escapeUnsafeCharacters(String input) { + return input; + } + + @Override + public String escapeQuotationMark(String input) { + return input; + } + + @Override + public CodegenResponse fromResponse(String responseCode, ApiResponse response) { + CodegenResponse r = super.fromResponse(responseCode, response); + try { + Map>> map = Json.mapper().readerFor(Map.class).readValue(Json.pretty(response.getContent())); + Map.Entry>> entry = map.entrySet().stream().findFirst().get(); + Map> example = entry.getValue(); + r.examples = toExamples(example.get("examples")); + } catch (Exception e) { + LOGGER.error(e.toString()); + } + return r; + } + + @Override + protected List> toExamples(Map examples) { + if (examples == null) { + return null; + } + + final List> output = new ArrayList<>(examples.size()); + for (Map.Entry entry : examples.entrySet()) { + final Map kv = new HashMap<>(); + @SuppressWarnings("unchecked") + Map map = (Map) entry.getValue(); + String example = ""; + try{ + example = Json.mapper().writeValueAsString(map.getOrDefault("value", map)); + } catch(Exception ignored) {} + + kv.put("example", example); + output.add(kv); + } + + return output; + } + +} diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig index 2f9d1f0907d..2667bc37c42 100644 --- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -140,3 +140,4 @@ org.openapitools.codegen.languages.TypeScriptReduxQueryClientCodegen org.openapitools.codegen.languages.TypeScriptRxjsClientCodegen org.openapitools.codegen.languages.WsdlSchemaCodegen org.openapitools.codegen.languages.XojoClientCodegen +org.openapitools.codegen.languages.ZapierClientCodegen diff --git a/modules/openapi-generator/src/main/resources/zapier/README.mustache b/modules/openapi-generator/src/main/resources/zapier/README.mustache new file mode 100644 index 00000000000..d12e0e96be2 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/zapier/README.mustache @@ -0,0 +1,51 @@ +# Documentation for zapier generator + +This generator generates a partial implementation of a zapier integration from an openapi specification. + +## Why is it partial? + +It's a partial integration because you have to put in some code for it to be 100% complete, code that depends only on how your api is structured. + +Here there are all the parts you need to complete by yourself: + +### 1) **utils/utils.js** isSearchAction method +This method has to return either true or false if a method is a [zapier search action](https://platform.zapier.com/docs/search-create). +``` +const isSearchAction = (key) => { + // TODO: custom logic + return false +} +``` + +### 2) **utils/utils.js** searchMiddleware method +This method has to return an array of resources (searches must return an array), if you have pagination or you api returns an array inside nested fields, here you have to extract the array. + +``` +const searchMiddleware = (action) => { + // TODO: custom logic + return action +} +``` + +### 3) **authentication.js** +This file must be written completely according to your api authentication, you can get it generated automatically by creating the integration on the zapier website and filling the requested data, or you can build it yourself following [this guide](https://platform.zapier.com/cli_tutorials/getting-started#adding-authentication). + +## Samples +To get your app made public on zapier you must provide a sample (json example response) for each of your actions, these samples get automatically generated by this generator but you have to have actual response examples in you openapi file. + +For example: + +``` +CreateUserResponse: + description: Example response + content: + application/json: + schema: + $ref: ./models/responses/CreateUserResponse.yaml + examples: + example-1: + value: + data: + id: 12345 + name: user1 +``` diff --git a/modules/openapi-generator/src/main/resources/zapier/actions.mustache b/modules/openapi-generator/src/main/resources/zapier/actions.mustache new file mode 100644 index 00000000000..d398927b6d8 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/zapier/actions.mustache @@ -0,0 +1,23 @@ +{{#apiInfo}} +{{#apis}} +const {{classname}} = require('../{{apiPackage}}/{{classname}}'); +{{/apis}} +{{/apiInfo}} +const { searchMiddleware, hasSearchRequisites, isSearchAction } = require('../utils/utils'); + +const actions = { + {{#apiInfo}} + {{#apis}} + {{#operations}} + {{#operation}} + [{{classname}}.{{operationId}}.key]: {{classname}}.{{operationId}}, + {{/operation}} + {{/operations}} + {{/apis}} + {{/apiInfo}} +} + +module.exports = { + searchActions: () => Object.entries(actions).reduce((actions, [key, value]) => isSearchAction(key) && hasSearchRequisites(value) ? {...actions, [key]: searchMiddleware(value)} : actions, {}), + createActions: () => Object.entries(actions).reduce((actions, [key, value]) => !isSearchAction(key) ? {...actions, [key]: value} : actions, {}), +} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/zapier/api.mustache b/modules/openapi-generator/src/main/resources/zapier/api.mustache new file mode 100644 index 00000000000..8c1c7d5e34d --- /dev/null +++ b/modules/openapi-generator/src/main/resources/zapier/api.mustache @@ -0,0 +1,106 @@ +const samples = require('../samples/{{classFilename}}'); +{{#imports}}{{{import}}} +{{/imports}} +const utils = require('../utils/utils'); +{{#operations}} +{{#operation}} +{{#isMultipart}} +const FormData = require('form-data'); +{{/isMultipart}} +{{/operation}} +{{/operations}} +{{#operations}} + +module.exports = { +{{#operation}} + {{operationId}}: { + key: '{{operationId}}', + noun: '{{baseName}}', + display: { + label: '{{operationId}}', + description: '{{#notes}}{{.}}{{/notes}}', + hidden: false, + }, + operation: { + inputFields: [ + {{#allParams}} + {{#isPrimitiveType}} + { + key: '{{baseName}}', + label: '{{description}}', + type: '{{#isInteger}}integer{{/isInteger}}{{^isInteger}}{{#isNumeric}}number{{/isNumeric}}{{/isInteger}}{{#isString}}string{{/isString}}{{#isBoolean}}boolean{{/isBoolean}}{{#isDateTime}}datetime{{/isDateTime}}{{#isDate}}datetime{{/isDate}}{{#isFile}}file{{/isFile}}', + {{#required}} + required: true, + {{/required}} + {{#isEnum}} + choices: [ + {{#_enum}} + '{{.}}', + {{/_enum}} + ], + {{/isEnum}} + }, + {{/isPrimitiveType}} + {{^isPrimitiveType}} + ...{{baseType}}.fields(), + {{/isPrimitiveType}} + {{/allParams}} + ], + outputFields: [ + {{#returnType}} + {{^returnTypeIsPrimitive}} + ...{{returnType}}.fields('', false), + {{/returnTypeIsPrimitive}} + {{/returnType}} + ], + perform: async (z, bundle) => { + {{#isMultipart}} + const formData = new FormData() + {{#allParams}} + {{#isFormParam}} + {{^isFile}} + formData.append('{{baseName}}', bundle.inputData?.['{{baseName}}']) + {{/isFile}} + {{#isFile}} + const filename = bundle.inputData?.['filename'] || bundle.inputData?.['{{baseName}}'].split('/').slice(-1)[0] + formData.append('{{baseName}}', (await (await z.request({url: bundle.inputData?.['{{baseName}}'], method: 'GET', raw: true})).buffer()), { filename: filename }) + {{/isFile}} + {{/isFormParam}} + {{/allParams}} + {{/isMultipart}} + const options = { + url: utils.replacePathParameters('{{basePath}}{{path}}'), + method: '{{httpMethod}}', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{=<% %>=}}{{bundle.authData.access_token}}<%={{ }}=%>', + {{^isMultipart}}'Content-Type': '{{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}',{{/isMultipart}} + 'Accept': '{{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}', + }, + params: { + {{#allParams}} + {{#isQueryParam}} + '{{baseName}}': bundle.inputData?.['{{baseName}}'], + {{/isQueryParam}} + {{/allParams}} + }, + body: {{#isMultipart}}formData,{{/isMultipart}}{{^isMultipart}}{ + {{#allParams}} + {{#isBodyParam}} + {{#isPrimitiveType}}'{{baseName}}': bundle.inputData?.['{{baseName}}']{{/isPrimitiveType}}{{^isPrimitiveType}}...{{baseName}}.mapping(bundle){{/isPrimitiveType}}, + {{/isBodyParam}} + {{/allParams}} + },{{/isMultipart}} + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return {{#returnType}}{{#returnTypeIsPrimitive}}{ data: results }{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}results{{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}results{{/returnType}}; + }) + }, + sample: {{#returnType}}{{^returnTypeIsPrimitive}}{{#responses}}{{#is2xx}}{{#baseType}}samples['{{baseType}}Sample']{{/baseType}}{{/is2xx}}{{/responses}}{{/returnTypeIsPrimitive}}{{#returnTypeIsPrimitive}}{ data: {} }{{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}{ data: {} }{{/returnType}} + } + }, +{{/operation}} +} +{{/operations}} diff --git a/modules/openapi-generator/src/main/resources/zapier/authentication.mustache b/modules/openapi-generator/src/main/resources/zapier/authentication.mustache new file mode 100644 index 00000000000..8294e5fc99a --- /dev/null +++ b/modules/openapi-generator/src/main/resources/zapier/authentication.mustache @@ -0,0 +1,4 @@ +module.exports = { + // TODO: autentication logic + // https://platform.zapier.com/cli_tutorials/getting-started#adding-authentication +}; \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/zapier/index.mustache b/modules/openapi-generator/src/main/resources/zapier/index.mustache new file mode 100644 index 00000000000..8f60b0c7b1e --- /dev/null +++ b/modules/openapi-generator/src/main/resources/zapier/index.mustache @@ -0,0 +1,10 @@ +const authentication = require('./authentication'); +const { searchActions, createActions } = require('./operations/actions'); + +module.exports = { + version: require('./package.json').version, + platformVersion: require('zapier-platform-core').version, + authentication: authentication, + searches: searchActions(), + creates: createActions(), +}; diff --git a/modules/openapi-generator/src/main/resources/zapier/model.mustache b/modules/openapi-generator/src/main/resources/zapier/model.mustache new file mode 100644 index 00000000000..fd27ed85584 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/zapier/model.mustache @@ -0,0 +1,93 @@ +const utils = require('../utils/utils'); +{{#imports}}{{{import}}} +{{/imports}} +{{#models}} +{{#model}} + +{{#isEnum}} +module.exports = { + fields: (key) => ( + { + label: `{{#description}}{{{.}}} - {{/description}}[${key.replaceAll('__', '.')}]`, + choices: [ + {{#allowableValues}} + {{#values}} + '{{.}}', + {{/values}} + {{/allowableValues}} + ], + } + ) + } +{{/isEnum}} +{{^isEnum}} +module.exports = { + fields: (prefix = '', isInput = true, isArrayChild = false) => { + const {keyPrefix, labelPrefix} = utils.buildKeyAndLabel(prefix, isInput, isArrayChild) + return [ + {{#vars}} + {{#isPrimitiveType}} + { + key: `${keyPrefix}{{baseName}}`, + label: `{{#description}}{{{.}}} - {{/description}}[${labelPrefix}{{baseName}}]`, + {{#isArray}} + list: true, + type: '{{#items}}{{baseType}}{{/items}}', + {{/isArray}} + {{^isArray}} + {{#isFreeFormObject}} + dict: true, + {{/isFreeFormObject}} + {{^isFreeFormObject}} + type: '{{baseType}}', + {{/isFreeFormObject}} + {{/isArray}} + {{#isEnum}} + choices: [ + {{#_enum}} + '{{.}}', + {{/_enum}} + ], + {{/isEnum}} + }, + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{#isArray}} + { + key: `${keyPrefix}{{baseName}}`,{{#items}}{{^isEnumRef}} + label: `{{#description}}{{{.}}} - {{/description}}[${labelPrefix}{{baseName}}]`, + children: {{complexType}}.fields(`${keyPrefix}{{baseName}}${!isInput ? '[]' : ''}`, isInput, true), {{/isEnumRef}}{{#isEnumRef}} + list: true, + type: 'string', + ...{{complexType}}.fields(`${keyPrefix}{{baseName}}`, isInput),{{/isEnumRef}}{{/items}} + }, + {{/isArray}} + {{^isArray}} + {{^allowableValues}} + {{^isFreeFormObject}} + ...{{complexType}}.fields(`${keyPrefix}{{baseName}}`, isInput), + {{/isFreeFormObject}} + {{/allowableValues}} + {{#allowableValues}} + { + key: `${keyPrefix}{{baseName}}`, + ...{{complexType}}.fields(`${keyPrefix}{{baseName}}`, isInput), + }, + {{/allowableValues}} + {{/isArray}} + {{/isPrimitiveType}} + {{/vars}} + ] + }, + mapping: (bundle, prefix = '') => { + const {keyPrefix} = utils.buildKeyAndLabel(prefix) + return { + {{#vars}} + '{{baseName}}': {{#isPrimitiveType}}bundle.inputData?.[`${keyPrefix}{{baseName}}`]{{/isPrimitiveType}}{{^isPrimitiveType}}{{^allowableValues}}{{^isArray}}utils.removeIfEmpty({{complexType}}.mapping(bundle, `${keyPrefix}{{baseName}}`)){{/isArray}}{{#isArray}}utils.removeKeyPrefixes(bundle.inputData?.[`${keyPrefix}{{baseName}}`]){{/isArray}}{{/allowableValues}}{{#allowableValues}}bundle.inputData?.[`${keyPrefix}{{baseName}}`]{{/allowableValues}}{{/isPrimitiveType}}, + {{/vars}} + } + }, +} +{{/isEnum}} +{{/model}} +{{/models}} diff --git a/modules/openapi-generator/src/main/resources/zapier/package.mustache b/modules/openapi-generator/src/main/resources/zapier/package.mustache new file mode 100644 index 00000000000..95d261febc0 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/zapier/package.mustache @@ -0,0 +1,27 @@ +{ + "name": "{{npmName}}", + "version": "{{npmVersion}}", + "description": "OpenAPI client for {{npmName}}", + "author": "OpenAPI-Generator", + "main": "index.js", + "scripts": { + "test": "mocha --recursive -t 10000" + }, + "engines": { + "node": ">=v16", + "npm": ">=5.6.0" + }, + "dependencies": { + "lodash": "^4.17.21", + "zapier-platform-core": "14.1.1", + "form-data": "2.1.4" + }, + "devDependencies": { + "mocha": "^10.2.0", + "should": "^13.2.0" + }, + "private": true, + "zapier": { + "convertedByCLIVersion": "14.1.1" + } +} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/zapier/sample.mustache b/modules/openapi-generator/src/main/resources/zapier/sample.mustache new file mode 100644 index 00000000000..a7f3757c38c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/zapier/sample.mustache @@ -0,0 +1,18 @@ +{{#operations}} +module.exports = { +{{#operation}} +{{#returnType}} +{{^returnTypeIsPrimitive}} +{{#responses}} +{{#is2xx}} + {{#baseType}} + "{{baseType}}Sample": + {{#examples}}{{#-last}}{{{example}}}{{/-last}}{{/examples}}{{^examples}}{ data: {} }{{/examples}}, + {{/baseType}} +{{/is2xx}} +{{/responses}} +{{/returnTypeIsPrimitive}} +{{/returnType}} +{{/operation}} +} +{{/operations}} diff --git a/modules/openapi-generator/src/main/resources/zapier/utils.mustache b/modules/openapi-generator/src/main/resources/zapier/utils.mustache new file mode 100644 index 00000000000..89690f26657 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/zapier/utils.mustache @@ -0,0 +1,34 @@ +const _ = require('lodash') + +const replacePathParameters = (url) => url.replace(/{([^{}]+)}/g, (keyExpr, key) => `{{bundle.inputData.${key}}}`) +const removeKeyPrefixes = (objectsArray) => objectsArray == undefined || typeof objectsArray[0] != 'object' ? objectsArray : objectsArray.map((obj) => Object.keys(obj).reduce((res, key) => (res[(key.split('.')).slice(-1)] = obj[key], res), {})) +const removeIfEmpty = (obj) => _.isEmpty(JSON.parse(JSON.stringify(obj))) ? undefined : obj +const buildKeyAndLabel = (prefix, isInput = true, isArrayChild = false) => { + const keyPrefix = !_.isEmpty(prefix) && (!isArrayChild || isInput) ? `${prefix}${isInput ? '.' : '__'}` : prefix + const labelPrefix = !_.isEmpty(keyPrefix) ? keyPrefix.replaceAll('__', '.') : '' + return { + keyPrefix: keyPrefix, + labelPrefix:labelPrefix, + } +} +const isSearchAction = (key) => { + // TODO: custom logic + return false +} +const hasASearchField = action => action.operation.inputFields.length > 0 +const returnsObjectsArray = action => !!action.operation.outputFields.find(field => 'children' in field) +const hasSearchRequisites = action => hasASearchField(action) && returnsObjectsArray(action) +const searchMiddleware = (action) => { + // TODO: custom logic + return action +} + +module.exports = { + replacePathParameters: replacePathParameters, + removeKeyPrefixes: removeKeyPrefixes, + removeIfEmpty: removeIfEmpty, + buildKeyAndLabel: buildKeyAndLabel, + hasSearchRequisites: hasSearchRequisites, + isSearchAction: isSearchAction, + searchMiddleware: searchMiddleware, +} \ No newline at end of file diff --git a/samples/client/petstore/csharp/OpenAPIClient-generichost-net6.0-nrt/src/Org.OpenAPITools/Api/FakeApi.cs b/samples/client/petstore/csharp/OpenAPIClient-generichost-net6.0-nrt/src/Org.OpenAPITools/Api/FakeApi.cs index 7868e9af673..3de82a2c9f0 100644 --- a/samples/client/petstore/csharp/OpenAPIClient-generichost-net6.0-nrt/src/Org.OpenAPITools/Api/FakeApi.cs +++ b/samples/client/petstore/csharp/OpenAPIClient-generichost-net6.0-nrt/src/Org.OpenAPITools/Api/FakeApi.cs @@ -2023,15 +2023,19 @@ namespace Org.OpenAPITools.Api /// /// Validates the request parameters /// + /// /// /// /// /// + /// /// /// - /// /// private void ValidateTestEnumParameters(Option> enumHeaderStringArray, Option> enumQueryStringArray, Option> enumFormStringArray, Option enumHeaderString, Option enumQueryString, Option enumFormString) { + if (enumHeaderString.IsSet && enumHeaderString.Value == null) + throw new ArgumentNullException(nameof(enumHeaderString)); + if (enumHeaderStringArray.IsSet && enumHeaderStringArray.Value == null) throw new ArgumentNullException(nameof(enumHeaderStringArray)); @@ -2049,6 +2053,9 @@ namespace Org.OpenAPITools.Api if (enumFormString.IsSet && enumFormString.Value == null) throw new ArgumentNullException(nameof(enumFormString)); + + if (enumQueryString.IsSet && enumQueryString.Value == null) + throw new ArgumentNullException(nameof(enumQueryString)); } /// diff --git a/samples/client/petstore/csharp/OpenAPIClient-generichost-net6.0/src/Org.OpenAPITools/Api/FakeApi.cs b/samples/client/petstore/csharp/OpenAPIClient-generichost-net6.0/src/Org.OpenAPITools/Api/FakeApi.cs index 95491ea8c68..249beb0176f 100644 --- a/samples/client/petstore/csharp/OpenAPIClient-generichost-net6.0/src/Org.OpenAPITools/Api/FakeApi.cs +++ b/samples/client/petstore/csharp/OpenAPIClient-generichost-net6.0/src/Org.OpenAPITools/Api/FakeApi.cs @@ -2042,11 +2042,15 @@ namespace Org.OpenAPITools.Api if (enumHeaderString.IsSet && enumHeaderString.Value == null) throw new ArgumentNullException(nameof(enumHeaderString)); + if (enumQueryString.IsSet && enumQueryString.Value == null) throw new ArgumentNullException(nameof(enumQueryString)); if (enumFormString.IsSet && enumFormString.Value == null) throw new ArgumentNullException(nameof(enumFormString)); + + if (enumQueryString.IsSet && enumQueryString.Value == null) + throw new ArgumentNullException(nameof(enumQueryString)); } /// diff --git a/samples/client/petstore/zapier/.openapi-generator-ignore b/samples/client/petstore/zapier/.openapi-generator-ignore new file mode 100644 index 00000000000..7484ee590a3 --- /dev/null +++ b/samples/client/petstore/zapier/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/samples/client/petstore/zapier/.openapi-generator/FILES b/samples/client/petstore/zapier/.openapi-generator/FILES new file mode 100644 index 00000000000..2dcb04d6086 --- /dev/null +++ b/samples/client/petstore/zapier/.openapi-generator/FILES @@ -0,0 +1,15 @@ +README.md +apis/PetApi.js +apis/StoreApi.js +apis/UserApi.js +authentication.js +index.js +models/ApiResponse.js +models/Category.js +models/Order.js +models/Pet.js +models/Tag.js +models/User.js +operations/actions.js +package.json +utils/utils.js diff --git a/samples/client/petstore/zapier/.openapi-generator/VERSION b/samples/client/petstore/zapier/.openapi-generator/VERSION new file mode 100644 index 00000000000..757e6740040 --- /dev/null +++ b/samples/client/petstore/zapier/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.0.0-SNAPSHOT \ No newline at end of file diff --git a/samples/client/petstore/zapier/README.md b/samples/client/petstore/zapier/README.md new file mode 100644 index 00000000000..d12e0e96be2 --- /dev/null +++ b/samples/client/petstore/zapier/README.md @@ -0,0 +1,51 @@ +# Documentation for zapier generator + +This generator generates a partial implementation of a zapier integration from an openapi specification. + +## Why is it partial? + +It's a partial integration because you have to put in some code for it to be 100% complete, code that depends only on how your api is structured. + +Here there are all the parts you need to complete by yourself: + +### 1) **utils/utils.js** isSearchAction method +This method has to return either true or false if a method is a [zapier search action](https://platform.zapier.com/docs/search-create). +``` +const isSearchAction = (key) => { + // TODO: custom logic + return false +} +``` + +### 2) **utils/utils.js** searchMiddleware method +This method has to return an array of resources (searches must return an array), if you have pagination or you api returns an array inside nested fields, here you have to extract the array. + +``` +const searchMiddleware = (action) => { + // TODO: custom logic + return action +} +``` + +### 3) **authentication.js** +This file must be written completely according to your api authentication, you can get it generated automatically by creating the integration on the zapier website and filling the requested data, or you can build it yourself following [this guide](https://platform.zapier.com/cli_tutorials/getting-started#adding-authentication). + +## Samples +To get your app made public on zapier you must provide a sample (json example response) for each of your actions, these samples get automatically generated by this generator but you have to have actual response examples in you openapi file. + +For example: + +``` +CreateUserResponse: + description: Example response + content: + application/json: + schema: + $ref: ./models/responses/CreateUserResponse.yaml + examples: + example-1: + value: + data: + id: 12345 + name: user1 +``` diff --git a/samples/client/petstore/zapier/apis/PetApi.js b/samples/client/petstore/zapier/apis/PetApi.js new file mode 100644 index 00000000000..473dcea31c1 --- /dev/null +++ b/samples/client/petstore/zapier/apis/PetApi.js @@ -0,0 +1,370 @@ +const samples = require('../samples/PetApi'); +const ApiResponse = require('../models/ApiResponse'); +const Pet = require('../models/Pet'); +const utils = require('../utils/utils'); +const FormData = require('form-data'); + +module.exports = { + addPet: { + key: 'addPet', + noun: 'Pet', + display: { + label: 'addPet', + description: '', + hidden: false, + }, + operation: { + inputFields: [ + ...Pet.fields(), + ], + outputFields: [ + ...Pet.fields('', false), + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/pet'), + method: 'POST', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': 'application/json, application/xml', + 'Accept': 'application/xml, application/json', + }, + params: { + }, + body: { + ...Pet.mapping(bundle), + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: samples['PetSample'] + } + }, + deletePet: { + key: 'deletePet', + noun: 'Pet', + display: { + label: 'deletePet', + description: '', + hidden: false, + }, + operation: { + inputFields: [ + { + key: 'petId', + label: 'Pet id to delete', + type: '', + required: true, + }, + { + key: 'api_key', + label: '', + type: 'string', + }, + ], + outputFields: [ + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/pet/{petId}'), + method: 'DELETE', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': '', + 'Accept': '', + }, + params: { + }, + body: { + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: { data: {} } + } + }, + findPetsByStatus: { + key: 'findPetsByStatus', + noun: 'Pet', + display: { + label: 'findPetsByStatus', + description: 'Multiple status values can be provided with comma separated strings', + hidden: false, + }, + operation: { + inputFields: [ + ...string.fields(), + ], + outputFields: [ + ...array.fields('', false), + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/pet/findByStatus'), + method: 'GET', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': '', + 'Accept': 'application/xml, application/json', + }, + params: { + 'status': bundle.inputData?.['status'], + }, + body: { + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: samples['PetSample'] + } + }, + findPetsByTags: { + key: 'findPetsByTags', + noun: 'Pet', + display: { + label: 'findPetsByTags', + description: 'Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.', + hidden: false, + }, + operation: { + inputFields: [ + ...string.fields(), + ], + outputFields: [ + ...array.fields('', false), + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/pet/findByTags'), + method: 'GET', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': '', + 'Accept': 'application/xml, application/json', + }, + params: { + 'tags': bundle.inputData?.['tags'], + }, + body: { + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: samples['PetSample'] + } + }, + getPetById: { + key: 'getPetById', + noun: 'Pet', + display: { + label: 'getPetById', + description: 'Returns a single pet', + hidden: false, + }, + operation: { + inputFields: [ + { + key: 'petId', + label: 'ID of pet to return', + type: '', + required: true, + }, + ], + outputFields: [ + ...Pet.fields('', false), + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/pet/{petId}'), + method: 'GET', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': '', + 'Accept': 'application/xml, application/json', + }, + params: { + }, + body: { + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: samples['PetSample'] + } + }, + updatePet: { + key: 'updatePet', + noun: 'Pet', + display: { + label: 'updatePet', + description: '', + hidden: false, + }, + operation: { + inputFields: [ + ...Pet.fields(), + ], + outputFields: [ + ...Pet.fields('', false), + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/pet'), + method: 'PUT', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': 'application/json, application/xml', + 'Accept': 'application/xml, application/json', + }, + params: { + }, + body: { + ...Pet.mapping(bundle), + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: samples['PetSample'] + } + }, + updatePetWithForm: { + key: 'updatePetWithForm', + noun: 'Pet', + display: { + label: 'updatePetWithForm', + description: '', + hidden: false, + }, + operation: { + inputFields: [ + { + key: 'petId', + label: 'ID of pet that needs to be updated', + type: '', + required: true, + }, + { + key: 'name', + label: 'Updated name of the pet', + type: 'string', + }, + { + key: 'status', + label: 'Updated status of the pet', + type: 'string', + }, + ], + outputFields: [ + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/pet/{petId}'), + method: 'POST', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': '', + }, + params: { + }, + body: { + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: { data: {} } + } + }, + uploadFile: { + key: 'uploadFile', + noun: 'Pet', + display: { + label: 'uploadFile', + description: '', + hidden: false, + }, + operation: { + inputFields: [ + { + key: 'petId', + label: 'ID of pet to update', + type: '', + required: true, + }, + { + key: 'additionalMetadata', + label: 'Additional data to pass to server', + type: 'string', + }, + { + key: 'file', + label: 'file to upload', + type: 'file', + }, + ], + outputFields: [ + ...ApiResponse.fields('', false), + ], + perform: async (z, bundle) => { + const formData = new FormData() + formData.append('additionalMetadata', bundle.inputData?.['additionalMetadata']) + const filename = bundle.inputData?.['filename'] || bundle.inputData?.['file'].split('/').slice(-1)[0] + formData.append('file', (await (await z.request({url: bundle.inputData?.['file'], method: 'GET', raw: true})).buffer()), { filename: filename }) + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/pet/{petId}/uploadImage'), + method: 'POST', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + + 'Accept': 'application/json', + }, + params: { + }, + body: formData, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: samples['ApiResponseSample'] + } + }, +} diff --git a/samples/client/petstore/zapier/apis/StoreApi.js b/samples/client/petstore/zapier/apis/StoreApi.js new file mode 100644 index 00000000000..0292fa8fb3b --- /dev/null +++ b/samples/client/petstore/zapier/apis/StoreApi.js @@ -0,0 +1,170 @@ +const samples = require('../samples/StoreApi'); +const Order = require('../models/Order'); +const utils = require('../utils/utils'); + +module.exports = { + deleteOrder: { + key: 'deleteOrder', + noun: 'Store', + display: { + label: 'deleteOrder', + description: 'For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors', + hidden: false, + }, + operation: { + inputFields: [ + { + key: 'orderId', + label: 'ID of the order that needs to be deleted', + type: 'string', + required: true, + }, + ], + outputFields: [ + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/store/order/{orderId}'), + method: 'DELETE', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': '', + 'Accept': '', + }, + params: { + }, + body: { + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: { data: {} } + } + }, + getInventory: { + key: 'getInventory', + noun: 'Store', + display: { + label: 'getInventory', + description: 'Returns a map of status codes to quantities', + hidden: false, + }, + operation: { + inputFields: [ + ], + outputFields: [ + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/store/inventory'), + method: 'GET', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': '', + 'Accept': 'application/json', + }, + params: { + }, + body: { + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return { data: results }; + }) + }, + sample: { data: {} } + } + }, + getOrderById: { + key: 'getOrderById', + noun: 'Store', + display: { + label: 'getOrderById', + description: 'For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions', + hidden: false, + }, + operation: { + inputFields: [ + { + key: 'orderId', + label: 'ID of pet that needs to be fetched', + type: '', + required: true, + }, + ], + outputFields: [ + ...Order.fields('', false), + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/store/order/{orderId}'), + method: 'GET', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': '', + 'Accept': 'application/xml, application/json', + }, + params: { + }, + body: { + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: samples['OrderSample'] + } + }, + placeOrder: { + key: 'placeOrder', + noun: 'Store', + display: { + label: 'placeOrder', + description: '', + hidden: false, + }, + operation: { + inputFields: [ + ...Order.fields(), + ], + outputFields: [ + ...Order.fields('', false), + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/store/order'), + method: 'POST', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': 'application/json', + 'Accept': 'application/xml, application/json', + }, + params: { + }, + body: { + ...Order.mapping(bundle), + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: samples['OrderSample'] + } + }, +} diff --git a/samples/client/petstore/zapier/apis/UserApi.js b/samples/client/petstore/zapier/apis/UserApi.js new file mode 100644 index 00000000000..47b16423539 --- /dev/null +++ b/samples/client/petstore/zapier/apis/UserApi.js @@ -0,0 +1,343 @@ +const samples = require('../samples/UserApi'); +const User = require('../models/User'); +const utils = require('../utils/utils'); + +module.exports = { + createUser: { + key: 'createUser', + noun: 'User', + display: { + label: 'createUser', + description: 'This can only be done by the logged in user.', + hidden: false, + }, + operation: { + inputFields: [ + ...User.fields(), + ], + outputFields: [ + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/user'), + method: 'POST', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': 'application/json', + 'Accept': '', + }, + params: { + }, + body: { + ...User.mapping(bundle), + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: { data: {} } + } + }, + createUsersWithArrayInput: { + key: 'createUsersWithArrayInput', + noun: 'User', + display: { + label: 'createUsersWithArrayInput', + description: '', + hidden: false, + }, + operation: { + inputFields: [ + ...User.fields(), + ], + outputFields: [ + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/user/createWithArray'), + method: 'POST', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': 'application/json', + 'Accept': '', + }, + params: { + }, + body: { + ...User.mapping(bundle), + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: { data: {} } + } + }, + createUsersWithListInput: { + key: 'createUsersWithListInput', + noun: 'User', + display: { + label: 'createUsersWithListInput', + description: '', + hidden: false, + }, + operation: { + inputFields: [ + ...User.fields(), + ], + outputFields: [ + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/user/createWithList'), + method: 'POST', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': 'application/json', + 'Accept': '', + }, + params: { + }, + body: { + ...User.mapping(bundle), + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: { data: {} } + } + }, + deleteUser: { + key: 'deleteUser', + noun: 'User', + display: { + label: 'deleteUser', + description: 'This can only be done by the logged in user.', + hidden: false, + }, + operation: { + inputFields: [ + { + key: 'username', + label: 'The name that needs to be deleted', + type: 'string', + required: true, + }, + ], + outputFields: [ + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/user/{username}'), + method: 'DELETE', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': '', + 'Accept': '', + }, + params: { + }, + body: { + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: { data: {} } + } + }, + getUserByName: { + key: 'getUserByName', + noun: 'User', + display: { + label: 'getUserByName', + description: '', + hidden: false, + }, + operation: { + inputFields: [ + { + key: 'username', + label: 'The name that needs to be fetched. Use user1 for testing.', + type: 'string', + required: true, + }, + ], + outputFields: [ + ...User.fields('', false), + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/user/{username}'), + method: 'GET', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': '', + 'Accept': 'application/xml, application/json', + }, + params: { + }, + body: { + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: samples['UserSample'] + } + }, + loginUser: { + key: 'loginUser', + noun: 'User', + display: { + label: 'loginUser', + description: '', + hidden: false, + }, + operation: { + inputFields: [ + { + key: 'username', + label: 'The user name for login', + type: 'string', + required: true, + }, + { + key: 'password', + label: 'The password for login in clear text', + type: 'string', + required: true, + }, + ], + outputFields: [ + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/user/login'), + method: 'GET', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': '', + 'Accept': 'application/xml, application/json', + }, + params: { + 'username': bundle.inputData?.['username'], + 'password': bundle.inputData?.['password'], + }, + body: { + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return { data: results }; + }) + }, + sample: { data: {} } + } + }, + logoutUser: { + key: 'logoutUser', + noun: 'User', + display: { + label: 'logoutUser', + description: '', + hidden: false, + }, + operation: { + inputFields: [ + ], + outputFields: [ + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/user/logout'), + method: 'GET', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': '', + 'Accept': '', + }, + params: { + }, + body: { + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: { data: {} } + } + }, + updateUser: { + key: 'updateUser', + noun: 'User', + display: { + label: 'updateUser', + description: 'This can only be done by the logged in user.', + hidden: false, + }, + operation: { + inputFields: [ + { + key: 'username', + label: 'name that need to be deleted', + type: 'string', + required: true, + }, + ...User.fields(), + ], + outputFields: [ + ], + perform: async (z, bundle) => { + const options = { + url: utils.replacePathParameters('http://petstore.swagger.io/v2/user/{username}'), + method: 'PUT', + removeMissingValuesFrom: { params: true, body: true }, + headers: { + 'Authorization': 'Bearer {{bundle.authData.access_token}}', + 'Content-Type': 'application/json', + 'Accept': '', + }, + params: { + }, + body: { + ...User.mapping(bundle), + }, + } + return z.request(options).then((response) => { + response.throwForStatus(); + const results = response.json; + return results; + }) + }, + sample: { data: {} } + } + }, +} diff --git a/samples/client/petstore/zapier/authentication.js b/samples/client/petstore/zapier/authentication.js new file mode 100644 index 00000000000..8294e5fc99a --- /dev/null +++ b/samples/client/petstore/zapier/authentication.js @@ -0,0 +1,4 @@ +module.exports = { + // TODO: autentication logic + // https://platform.zapier.com/cli_tutorials/getting-started#adding-authentication +}; \ No newline at end of file diff --git a/samples/client/petstore/zapier/index.js b/samples/client/petstore/zapier/index.js new file mode 100644 index 00000000000..8f60b0c7b1e --- /dev/null +++ b/samples/client/petstore/zapier/index.js @@ -0,0 +1,10 @@ +const authentication = require('./authentication'); +const { searchActions, createActions } = require('./operations/actions'); + +module.exports = { + version: require('./package.json').version, + platformVersion: require('zapier-platform-core').version, + authentication: authentication, + searches: searchActions(), + creates: createActions(), +}; diff --git a/samples/client/petstore/zapier/models/ApiResponse.js b/samples/client/petstore/zapier/models/ApiResponse.js new file mode 100644 index 00000000000..c5bd2eba697 --- /dev/null +++ b/samples/client/petstore/zapier/models/ApiResponse.js @@ -0,0 +1,32 @@ +const utils = require('../utils/utils'); + +module.exports = { + fields: (prefix = '', isInput = true, isArrayChild = false) => { + const {keyPrefix, labelPrefix} = utils.buildKeyAndLabel(prefix, isInput, isArrayChild) + return [ + { + key: `${keyPrefix}code`, + label: `[${labelPrefix}code]`, + type: 'integer', + }, + { + key: `${keyPrefix}type`, + label: `[${labelPrefix}type]`, + type: 'string', + }, + { + key: `${keyPrefix}message`, + label: `[${labelPrefix}message]`, + type: 'string', + }, + ] + }, + mapping: (bundle, prefix = '') => { + const {keyPrefix} = utils.buildKeyAndLabel(prefix) + return { + 'code': bundle.inputData?.[`${keyPrefix}code`], + 'type': bundle.inputData?.[`${keyPrefix}type`], + 'message': bundle.inputData?.[`${keyPrefix}message`], + } + }, +} diff --git a/samples/client/petstore/zapier/models/Category.js b/samples/client/petstore/zapier/models/Category.js new file mode 100644 index 00000000000..1c543e14767 --- /dev/null +++ b/samples/client/petstore/zapier/models/Category.js @@ -0,0 +1,26 @@ +const utils = require('../utils/utils'); + +module.exports = { + fields: (prefix = '', isInput = true, isArrayChild = false) => { + const {keyPrefix, labelPrefix} = utils.buildKeyAndLabel(prefix, isInput, isArrayChild) + return [ + { + key: `${keyPrefix}id`, + label: `[${labelPrefix}id]`, + type: 'number', + }, + { + key: `${keyPrefix}name`, + label: `[${labelPrefix}name]`, + type: 'string', + }, + ] + }, + mapping: (bundle, prefix = '') => { + const {keyPrefix} = utils.buildKeyAndLabel(prefix) + return { + 'id': bundle.inputData?.[`${keyPrefix}id`], + 'name': bundle.inputData?.[`${keyPrefix}name`], + } + }, +} diff --git a/samples/client/petstore/zapier/models/Order.js b/samples/client/petstore/zapier/models/Order.js new file mode 100644 index 00000000000..d8478ef9004 --- /dev/null +++ b/samples/client/petstore/zapier/models/Order.js @@ -0,0 +1,55 @@ +const utils = require('../utils/utils'); + +module.exports = { + fields: (prefix = '', isInput = true, isArrayChild = false) => { + const {keyPrefix, labelPrefix} = utils.buildKeyAndLabel(prefix, isInput, isArrayChild) + return [ + { + key: `${keyPrefix}id`, + label: `[${labelPrefix}id]`, + type: 'number', + }, + { + key: `${keyPrefix}petId`, + label: `[${labelPrefix}petId]`, + type: 'number', + }, + { + key: `${keyPrefix}quantity`, + label: `[${labelPrefix}quantity]`, + type: 'integer', + }, + { + key: `${keyPrefix}shipDate`, + label: `[${labelPrefix}shipDate]`, + type: 'string', + }, + { + key: `${keyPrefix}status`, + label: `Order Status - [${labelPrefix}status]`, + type: 'string', + choices: [ + 'placed', + 'approved', + 'delivered', + ], + }, + { + key: `${keyPrefix}complete`, + label: `[${labelPrefix}complete]`, + type: 'boolean', + }, + ] + }, + mapping: (bundle, prefix = '') => { + const {keyPrefix} = utils.buildKeyAndLabel(prefix) + return { + 'id': bundle.inputData?.[`${keyPrefix}id`], + 'petId': bundle.inputData?.[`${keyPrefix}petId`], + 'quantity': bundle.inputData?.[`${keyPrefix}quantity`], + 'shipDate': bundle.inputData?.[`${keyPrefix}shipDate`], + 'status': bundle.inputData?.[`${keyPrefix}status`], + 'complete': bundle.inputData?.[`${keyPrefix}complete`], + } + }, +} diff --git a/samples/client/petstore/zapier/models/Pet.js b/samples/client/petstore/zapier/models/Pet.js new file mode 100644 index 00000000000..b0aa8bceec4 --- /dev/null +++ b/samples/client/petstore/zapier/models/Pet.js @@ -0,0 +1,54 @@ +const utils = require('../utils/utils'); +const Category = require('../models/Category'); +const Tag = require('../models/Tag'); + +module.exports = { + fields: (prefix = '', isInput = true, isArrayChild = false) => { + const {keyPrefix, labelPrefix} = utils.buildKeyAndLabel(prefix, isInput, isArrayChild) + return [ + { + key: `${keyPrefix}id`, + label: `[${labelPrefix}id]`, + type: 'number', + }, + ...Category.fields(`${keyPrefix}category`, isInput), + { + key: `${keyPrefix}name`, + label: `[${labelPrefix}name]`, + type: 'string', + }, + { + key: `${keyPrefix}photoUrls`, + label: `[${labelPrefix}photoUrls]`, + list: true, + type: 'string', + }, + { + key: `${keyPrefix}tags`, + label: `[${labelPrefix}tags]`, + children: Tag.fields(`${keyPrefix}tags${!isInput ? '[]' : ''}`, isInput, true), + }, + { + key: `${keyPrefix}status`, + label: `pet status in the store - [${labelPrefix}status]`, + type: 'string', + choices: [ + 'available', + 'pending', + 'sold', + ], + }, + ] + }, + mapping: (bundle, prefix = '') => { + const {keyPrefix} = utils.buildKeyAndLabel(prefix) + return { + 'id': bundle.inputData?.[`${keyPrefix}id`], + 'category': utils.removeIfEmpty(Category.mapping(bundle, `${keyPrefix}category`)), + 'name': bundle.inputData?.[`${keyPrefix}name`], + 'photoUrls': bundle.inputData?.[`${keyPrefix}photoUrls`], + 'tags': utils.removeKeyPrefixes(bundle.inputData?.[`${keyPrefix}tags`]), + 'status': bundle.inputData?.[`${keyPrefix}status`], + } + }, +} diff --git a/samples/client/petstore/zapier/models/Tag.js b/samples/client/petstore/zapier/models/Tag.js new file mode 100644 index 00000000000..1c543e14767 --- /dev/null +++ b/samples/client/petstore/zapier/models/Tag.js @@ -0,0 +1,26 @@ +const utils = require('../utils/utils'); + +module.exports = { + fields: (prefix = '', isInput = true, isArrayChild = false) => { + const {keyPrefix, labelPrefix} = utils.buildKeyAndLabel(prefix, isInput, isArrayChild) + return [ + { + key: `${keyPrefix}id`, + label: `[${labelPrefix}id]`, + type: 'number', + }, + { + key: `${keyPrefix}name`, + label: `[${labelPrefix}name]`, + type: 'string', + }, + ] + }, + mapping: (bundle, prefix = '') => { + const {keyPrefix} = utils.buildKeyAndLabel(prefix) + return { + 'id': bundle.inputData?.[`${keyPrefix}id`], + 'name': bundle.inputData?.[`${keyPrefix}name`], + } + }, +} diff --git a/samples/client/petstore/zapier/models/User.js b/samples/client/petstore/zapier/models/User.js new file mode 100644 index 00000000000..7ea88814d6e --- /dev/null +++ b/samples/client/petstore/zapier/models/User.js @@ -0,0 +1,62 @@ +const utils = require('../utils/utils'); + +module.exports = { + fields: (prefix = '', isInput = true, isArrayChild = false) => { + const {keyPrefix, labelPrefix} = utils.buildKeyAndLabel(prefix, isInput, isArrayChild) + return [ + { + key: `${keyPrefix}id`, + label: `[${labelPrefix}id]`, + type: 'number', + }, + { + key: `${keyPrefix}username`, + label: `[${labelPrefix}username]`, + type: 'string', + }, + { + key: `${keyPrefix}firstName`, + label: `[${labelPrefix}firstName]`, + type: 'string', + }, + { + key: `${keyPrefix}lastName`, + label: `[${labelPrefix}lastName]`, + type: 'string', + }, + { + key: `${keyPrefix}email`, + label: `[${labelPrefix}email]`, + type: 'string', + }, + { + key: `${keyPrefix}password`, + label: `[${labelPrefix}password]`, + type: 'string', + }, + { + key: `${keyPrefix}phone`, + label: `[${labelPrefix}phone]`, + type: 'string', + }, + { + key: `${keyPrefix}userStatus`, + label: `User Status - [${labelPrefix}userStatus]`, + type: 'integer', + }, + ] + }, + mapping: (bundle, prefix = '') => { + const {keyPrefix} = utils.buildKeyAndLabel(prefix) + return { + 'id': bundle.inputData?.[`${keyPrefix}id`], + 'username': bundle.inputData?.[`${keyPrefix}username`], + 'firstName': bundle.inputData?.[`${keyPrefix}firstName`], + 'lastName': bundle.inputData?.[`${keyPrefix}lastName`], + 'email': bundle.inputData?.[`${keyPrefix}email`], + 'password': bundle.inputData?.[`${keyPrefix}password`], + 'phone': bundle.inputData?.[`${keyPrefix}phone`], + 'userStatus': bundle.inputData?.[`${keyPrefix}userStatus`], + } + }, +} diff --git a/samples/client/petstore/zapier/operations/actions.js b/samples/client/petstore/zapier/operations/actions.js new file mode 100644 index 00000000000..d9a6a9afbba --- /dev/null +++ b/samples/client/petstore/zapier/operations/actions.js @@ -0,0 +1,32 @@ +const PetApi = require('../apis/PetApi'); +const StoreApi = require('../apis/StoreApi'); +const UserApi = require('../apis/UserApi'); +const { searchMiddleware, hasSearchRequisites, isSearchAction } = require('../utils/utils'); + +const actions = { + [PetApi.addPet.key]: PetApi.addPet, + [PetApi.deletePet.key]: PetApi.deletePet, + [PetApi.findPetsByStatus.key]: PetApi.findPetsByStatus, + [PetApi.findPetsByTags.key]: PetApi.findPetsByTags, + [PetApi.getPetById.key]: PetApi.getPetById, + [PetApi.updatePet.key]: PetApi.updatePet, + [PetApi.updatePetWithForm.key]: PetApi.updatePetWithForm, + [PetApi.uploadFile.key]: PetApi.uploadFile, + [StoreApi.deleteOrder.key]: StoreApi.deleteOrder, + [StoreApi.getInventory.key]: StoreApi.getInventory, + [StoreApi.getOrderById.key]: StoreApi.getOrderById, + [StoreApi.placeOrder.key]: StoreApi.placeOrder, + [UserApi.createUser.key]: UserApi.createUser, + [UserApi.createUsersWithArrayInput.key]: UserApi.createUsersWithArrayInput, + [UserApi.createUsersWithListInput.key]: UserApi.createUsersWithListInput, + [UserApi.deleteUser.key]: UserApi.deleteUser, + [UserApi.getUserByName.key]: UserApi.getUserByName, + [UserApi.loginUser.key]: UserApi.loginUser, + [UserApi.logoutUser.key]: UserApi.logoutUser, + [UserApi.updateUser.key]: UserApi.updateUser, +} + +module.exports = { + searchActions: () => Object.entries(actions).reduce((actions, [key, value]) => isSearchAction(key) && hasSearchRequisites(value) ? {...actions, [key]: searchMiddleware(value)} : actions, {}), + createActions: () => Object.entries(actions).reduce((actions, [key, value]) => !isSearchAction(key) ? {...actions, [key]: value} : actions, {}), +} \ No newline at end of file diff --git a/samples/client/petstore/zapier/package.json b/samples/client/petstore/zapier/package.json new file mode 100644 index 00000000000..f4d4102ceac --- /dev/null +++ b/samples/client/petstore/zapier/package.json @@ -0,0 +1,27 @@ +{ + "name": "@openapitools/zapier", + "version": "0.0.1", + "description": "OpenAPI client for @openapitools/zapier", + "author": "OpenAPI-Generator", + "main": "index.js", + "scripts": { + "test": "mocha --recursive -t 10000" + }, + "engines": { + "node": ">=v16", + "npm": ">=5.6.0" + }, + "dependencies": { + "lodash": "^4.17.21", + "zapier-platform-core": "14.1.1", + "form-data": "2.1.4" + }, + "devDependencies": { + "mocha": "^10.2.0", + "should": "^13.2.0" + }, + "private": true, + "zapier": { + "convertedByCLIVersion": "14.1.1" + } +} \ No newline at end of file diff --git a/samples/client/petstore/zapier/samples/PetApi.js b/samples/client/petstore/zapier/samples/PetApi.js new file mode 100644 index 00000000000..3ef6e6adfab --- /dev/null +++ b/samples/client/petstore/zapier/samples/PetApi.js @@ -0,0 +1,14 @@ +module.export = { + "PetSample": + { data: {} }, + "PetSample": + { data: {} }, + "PetSample": + { data: {} }, + "PetSample": + { data: {} }, + "PetSample": + { data: {} }, + "ApiResponseSample": + { data: {} }, +} diff --git a/samples/client/petstore/zapier/samples/StoreApi.js b/samples/client/petstore/zapier/samples/StoreApi.js new file mode 100644 index 00000000000..c948e814e30 --- /dev/null +++ b/samples/client/petstore/zapier/samples/StoreApi.js @@ -0,0 +1,6 @@ +module.export = { + "OrderSample": + { data: {} }, + "OrderSample": + { data: {} }, +} diff --git a/samples/client/petstore/zapier/samples/UserApi.js b/samples/client/petstore/zapier/samples/UserApi.js new file mode 100644 index 00000000000..8053c81b88d --- /dev/null +++ b/samples/client/petstore/zapier/samples/UserApi.js @@ -0,0 +1,4 @@ +module.export = { + "UserSample": + { data: {} }, +} diff --git a/samples/client/petstore/zapier/utils/utils.js b/samples/client/petstore/zapier/utils/utils.js new file mode 100644 index 00000000000..89690f26657 --- /dev/null +++ b/samples/client/petstore/zapier/utils/utils.js @@ -0,0 +1,34 @@ +const _ = require('lodash') + +const replacePathParameters = (url) => url.replace(/{([^{}]+)}/g, (keyExpr, key) => `{{bundle.inputData.${key}}}`) +const removeKeyPrefixes = (objectsArray) => objectsArray == undefined || typeof objectsArray[0] != 'object' ? objectsArray : objectsArray.map((obj) => Object.keys(obj).reduce((res, key) => (res[(key.split('.')).slice(-1)] = obj[key], res), {})) +const removeIfEmpty = (obj) => _.isEmpty(JSON.parse(JSON.stringify(obj))) ? undefined : obj +const buildKeyAndLabel = (prefix, isInput = true, isArrayChild = false) => { + const keyPrefix = !_.isEmpty(prefix) && (!isArrayChild || isInput) ? `${prefix}${isInput ? '.' : '__'}` : prefix + const labelPrefix = !_.isEmpty(keyPrefix) ? keyPrefix.replaceAll('__', '.') : '' + return { + keyPrefix: keyPrefix, + labelPrefix:labelPrefix, + } +} +const isSearchAction = (key) => { + // TODO: custom logic + return false +} +const hasASearchField = action => action.operation.inputFields.length > 0 +const returnsObjectsArray = action => !!action.operation.outputFields.find(field => 'children' in field) +const hasSearchRequisites = action => hasASearchField(action) && returnsObjectsArray(action) +const searchMiddleware = (action) => { + // TODO: custom logic + return action +} + +module.exports = { + replacePathParameters: replacePathParameters, + removeKeyPrefixes: removeKeyPrefixes, + removeIfEmpty: removeIfEmpty, + buildKeyAndLabel: buildKeyAndLabel, + hasSearchRequisites: hasSearchRequisites, + isSearchAction: isSearchAction, + searchMiddleware: searchMiddleware, +} \ No newline at end of file