diff --git a/.github/runs-on.yml b/.github/runs-on.yml index adec414..b1bfde5 100644 --- a/.github/runs-on.yml +++ b/.github/runs-on.yml @@ -1 +1 @@ -_extends: .github-private \ No newline at end of file +_extends: .github-private diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a456e7..7e8e27d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ name: CI (build + client samples) - main env: - LD_API_KEY: ${{ secrets.LD_API_KEY }} + LD_API_KEY: ${{ secrets.LD_API_KEY }} permissions: contents: read @@ -82,33 +82,9 @@ jobs: - name: Run Go sample env: GO111MODULE: 'on' - run: make - test-javascript: - runs-on: ubuntu-latest - needs: build - steps: - - uses: actions/checkout@v4 - - name: Download targets - uses: actions/download-artifact@v4 - with: - name: targets - path: targets - - uses: actions/setup-node@v4 - with: - node-version: '20' - - name: Build JS client - working-directory: targets/api-client-javascript run: | - rm -rf node_modules dist - npm install - npx --yes babel src -d dist --plugins @babel/plugin-transform-modules-commonjs - # TODO: Fix this test - # - name: Run JS sample - # run: | - # cd samples/javascript - # # Install the local package directly - # npm install - # node index.js + cd samples/go + make test-python: runs-on: ubuntu-latest needs: build @@ -210,37 +186,16 @@ jobs: npm install npm run build npm start - test-php: - runs-on: ubuntu-latest - needs: build - steps: - - uses: actions/checkout@v4 - - name: Download targets - uses: actions/download-artifact@v4 - with: - name: targets - path: targets - - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - - name: Run PHP sample - working-directory: samples/php - run: | - echo '{"require":{"launchdarkly/api-client-php":"@dev","guzzlehttp/guzzle":"*"},"repositories":[{"type":"path","url":"../../targets/api-client-php","options":{"symlink":true}}]}' > composer.json - composer update - php index.php check-success: name: Check Success needs: - build - test-go - - test-javascript - test-python - test-ruby - test-java - test-typescript - - test-php if: always() runs-on: ubuntu-latest steps: @@ -249,4 +204,4 @@ jobs: if printf '${{ toJSON(needs) }}' | grep --quiet --extended-regexp --ignore-case '"result": "(failure|cancelled)"'; then printf "Tests failed or workflow cancelled:\n\n${{ toJSON(needs) }}" exit 1 - fi \ No newline at end of file + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 50f6ab2..3187ddb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,7 +51,7 @@ jobs: s3_path_pairs: 'launchdarkly-releaser/java/code-signing-keyring.gpg = /home/runner/code-signing-keyring.gpg' - name: Prepare secrets run: | - ./scripts/release/prepare.sh + ./scripts/release/prepare.sh - name: Build clients run: | export PATH=/opt/gradle/bin:/usr/local/go/bin:$HOME/.rubygems/bin:$PATH @@ -59,7 +59,7 @@ jobs: java -XX:+PrintFlagsFinal -version | grep ThreadStackSize make # Verify that the generated client code can be built - make BUILD_TARGETS="go java javascript python ruby typescript-axios" build_clients + make BUILD_TARGETS="go java python ruby typescript-axios" build_clients # This step will: # * Push built clients to their respective Github repos # * Tag those commits with the release version @@ -77,11 +77,11 @@ jobs: needs: release strategy: matrix: - repo: ['api-client-go', 'api-client-java', 'api-client-javascript', 'api-client-php', 'api-client-python', 'api-client-ruby', 'api-client-typescript'] + repo: ['api-client-go', 'api-client-java', 'api-client-python', 'api-client-ruby', 'api-client-typescript'] steps: - uses: ncipollo/release-action@v1.14.0 with: repo: ${{ matrix.repo }} token: ${{ secrets.BOT_TOKEN }} - tag: v${{ inputs.releaseVersion }} + tag: v${{ inputs.releaseVersion }} body: ${{ inputs.changeLog }} diff --git a/.ldrelease/build.sh b/.ldrelease/build.sh deleted file mode 100755 index d5c3ae1..0000000 --- a/.ldrelease/build.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -set -eu - -make - -# Copy the build products into $LD_RELEASE_ARTIFACTS_DIR so that in an actual -# release they will be automatically attached as assets to the GitHub release; -# in a dry run they will be downloadable from Releaser. - -# Add spec artifacts to be picked up by Releaser and added to the ld-openapi GitHub release -echo Generating artifacts... -cp targets/openapi.json ${LD_RELEASE_ARTIFACTS_DIR} - -# Add client artifacts to be picked up by Releaser and added to the ld-openapi GitHub release -cd targets -tar cvfz api-clients-${LD_RELEASE_VERSION}.tgz api-client-* -cd .. -cp targets/api-clients-${LD_RELEASE_VERSION}.tgz ${LD_RELEASE_ARTIFACTS_DIR} - -# Verify that the generated client code can be built -make BUILD_TARGETS="go java javascript python ruby typescript-axios" build_clients diff --git a/.ldrelease/config.yml b/.ldrelease/config.yml deleted file mode 100644 index f92b35e..0000000 --- a/.ldrelease/config.yml +++ /dev/null @@ -1,24 +0,0 @@ -version: 2 - -repo: - public: ld-openapi - private: ld-openapi-private - -publications: - - url: https://oss.sonatype.org/content/groups/public/com/launchdarkly/api-client/ - description: Java client (Sonatype) - - url: https://www.javadoc.io/doc/com.launchdarkly/api-client - description: Java docs (Javadoc) - - url: https://www.npmjs.com/package/launchdarkly-api - description: JavaScript client (NPM) - - url: https://pypi.org/project/launchdarkly-api/ - description: Python client (PyPi) - - url: https://rubygems.org/gems/launchdarkly_api - description: Ruby client (RubyGems) - - url: https://www.rubydoc.info/gems/launchdarkly_api - description: Ruby docs (RubyDoc) - -jobs: - - docker: - image: ldcircleci/openapi-release:1 # defined in https://github.com/launchdarkly/sdks-ci-docker - copyGitHistory: true diff --git a/.ldrelease/prepare.sh b/.ldrelease/prepare.sh deleted file mode 100755 index e15cc75..0000000 --- a/.ldrelease/prepare.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash - -set -eu - -# Set up credentials needed for client package releases. These have been downloaded -# by Releaser into $LD_RELEASE_SECRETS_DIR as specified by our secrets.properties file. -# We'll put them into the standard locations used by the various build/release tools - -# in some cases we could put them in environment variables instead, but then it would -# be harder to use the release scripts locally outside of Releaser. - -# Gradle properties and signing key -java_sonatype_username="$(cat "${LD_RELEASE_SECRETS_DIR}/java_sonatype_username")" -java_sonatype_password="$(cat "${LD_RELEASE_SECRETS_DIR}/java_sonatype_password")" -mkdir -p ~/.gradle -cat >~/.gradle/gradle.properties < ~/.npmrc - -# PyPI token -python_pypi_token="$(cat "${LD_RELEASE_SECRETS_DIR}/python_pypi_token")" -echo -e "[pypi]\nusername=__token__\npassword=${python_pypi_token}" > ~/.pypirc - -# RubyGems API key -rubygems_api_key="$(cat "${LD_RELEASE_SECRETS_DIR}/ruby_gems_api_key")" -mkdir -p ~/.gem -echo -e "---\r\n:rubygems_api_key: ${rubygems_api_key}" > ~/.gem/credentials -chmod 0600 ~/.gem/credentials diff --git a/.ldrelease/publish-dry-run.sh b/.ldrelease/publish-dry-run.sh deleted file mode 100755 index 4e3670a..0000000 --- a/.ldrelease/publish-dry-run.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -eu - -# Publish updates to client repositories -echo Dry run of updates to client repositories... -make TAG=${LD_RELEASE_VERSION} push_dry_run - -# Publish client artifacts to registries -echo Dry run of publishing client artifacts to registries... -make PUBLISH_TARGETS="javascript python ruby typescript-axios java" publish_dry_run diff --git a/.ldrelease/publish.sh b/.ldrelease/publish.sh deleted file mode 100755 index 4b42d56..0000000 --- a/.ldrelease/publish.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -set -eu - -# Configure GitHub access token for pushing to client repositories -# (We're not doing this in prepare.sh because we want to make sure there's no way the -# build/test scripts can accidentally push to GitHub) -echo >>~/.netrc "machine github.com login LaunchDarklyReleaseBot password $(cat $LD_RELEASE_SECRETS_DIR/github_token)" -git config --global user.name LaunchDarklyReleaseBot -git config --global user.email launchdarklyreleasebot@launchdarkly.com - -# Publish updates to client repositories -echo Publishing updates to client repositories... -make RELEASE_TARGETS="go java javascript php python ruby typescript-axios" TAG=${LD_RELEASE_VERSION} push - -# Publish client artifacts to registries -echo Publishing client artifacts to registries... -make PUBLISH_TARGETS="javascript python ruby typescript-axios java" publish diff --git a/.ldrelease/secrets.properties b/.ldrelease/secrets.properties deleted file mode 100644 index 23a0972..0000000 --- a/.ldrelease/secrets.properties +++ /dev/null @@ -1,17 +0,0 @@ - -# GitHub access token for pushing to client repos with LaunchDarklyReleaseBot account -github_token=param:/staging/common/services/github/releaser_access_token - -# Python - Pypi -python_pypi_token=param:/production/common/releasing/pypi/token - -# Ruby - Gems -ruby_gems_api_key=param:/production/common/releasing/rubygems/api_key - -# Node - NPM -npm_auth_token=param:/production/common/releasing/npm/token - -# Java - Sonatype -java_sonatype_username=param:/production/common/releasing/sonatype/username -java_sonatype_password=param:/production/common/releasing/sonatype/password -java_code_signing_keyring=blob:/java/code-signing-keyring.gpg diff --git a/CODEOWNERS b/CODEOWNERS index d9060b7..23f95b4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @launchdarkly/team-enterprise +* @launchdarkly/team-fm-next diff --git a/Makefile b/Makefile index 88aa95d..aa7b417 100644 --- a/Makefile +++ b/Makefile @@ -2,17 +2,23 @@ SHELL = /bin/bash LD_RELEASE_VERSION ?= 0.0.1-SNAPSHOT -GENERATOR_VERSION=7.16.0 +# IMPORTANT: Whenever updating this generator version, check if the ApiClient mustache files need corresponding updates. +# We have copied of these into the swagger-codegen-templates directory. Look for code blocks notated by CUSTOM-START and CUSTOM-END. +# - Go: https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/go/client.mustache +# - Java: https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/ApiClient.mustache +# - Python: https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/python/api_client.mustache +# - Ruby: https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/ruby-client/api_client.mustache +# - Typescript: https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/typescript-axios/apiInner.mustache +GENERATOR_VERSION=7.18.0 GENERATOR_JAR=openapi-generator-cli-${GENERATOR_VERSION}.jar GENERATOR_DOWNLOAD_URL=https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${GENERATOR_VERSION}/${GENERATOR_JAR} OPENAPI_JSON_URL=https://app.launchdarkly.com/api/v2/openapi.json +LATEST_API_VERSION=20240415 # the API version (for the LD-API-Version header value) corresponding to what is specified in this OpenAPI spec API_TARGETS ?= \ go \ java \ - javascript \ - php \ python \ ruby \ typescript-axios @@ -41,7 +47,9 @@ SAMPLES_PATH ?= ./samples # The following variables define any special command-line parameters that need to be passed # to openapi-generator for each language/platform. -CODEGEN_PARAMS_go = --additional-properties=packageName=ldapi \ +CODEGEN_PARAMS_go = \ + -t $(TEMPLATES_PATH)/go \ + --additional-properties=packageName=ldapi \ --additional-properties=disallowAdditionalPropertiesIfNotPresent=false \ --additional-properties=generateInterfaces=true \ --additional-properties=apiNameSuffix=Api \ @@ -51,8 +59,7 @@ CODEGEN_PARAMS_go = --additional-properties=packageName=ldapi \ --additional-properties=developerOrganization=LaunchDarkly \ --additional-properties=developerOrganizationUrl=https://launchdarkly.com \ --additional-properties=packageVersion=$(firstword $(subst ., ,$(TAG))) \ - -t $(TEMPLATES_PATH)/go - + --additional-properties=launchDarklyApiVersion=${LATEST_API_VERSION} CODEGEN_PARAMS_java = \ -t $(TEMPLATES_PATH)/java \ --group-id com.launchdarkly \ @@ -70,42 +77,32 @@ CODEGEN_PARAMS_java = \ --additional-properties=scmUrl="https://github.com/launchdarkly/api-client-java" \ --additional-properties=scmConnection='scm:git:git://github.com/launchdarkly/api-client-java.git' \ --additional-properties=scmDeveloperConnection='scm:git:ssh:git@github.com:launchdarkly/api-client-java.git' \ - --additional-properties=gradleProperties=systemProp.org.gradle.internal.http.connectionTimeout=300000$$'\n'systemProp.org.gradle.internal.http.socketTimeout=300000$$'\n'org.gradle.jvmargs=-Xss2m -CODEGEN_PARAMS_javascript = \ - -t $(TEMPLATES_PATH)/javascript \ - --additional-properties=projectName=launchdarkly-api \ - --additional-properties=projectVersion=$(TAG) \ - --additional-properties=projectDescription="Build custom integrations with the LaunchDarkly REST API" \ - --additional-properties=moduleName=LaunchDarklyApi -CODEGEN_PARAMS_php = \ - --additional-properties=packagePath=LaunchDarklyApi \ - --additional-properties=composerVendorName=launchdarkly \ - --additional-properties=composerProjectName=api-client-php \ - --additional-properties=invokerPackage=LaunchDarklyApi \ - --git-user-id=launchdarkly \ - --git-repo-id=api-client-php + --additional-properties=gradleProperties=systemProp.org.gradle.internal.http.connectionTimeout=300000$$'\n'systemProp.org.gradle.internal.http.socketTimeout=300000$$'\n'org.gradle.jvmargs=-Xss2m \ + --additional-properties=launchDarklyApiVersion=${LATEST_API_VERSION} CODEGEN_PARAMS_python = \ --additional-properties=packageName=launchdarkly_api \ --additional-properties=packageVersion=$(TAG) \ - + --additional-properties=launchDarklyApiVersion=${LATEST_API_VERSION} CODEGEN_PARAMS_typescript-axios = \ + -t $(TEMPLATES_PATH)/typescript \ --additional-properties=npmName=launchdarkly-api-typescript \ --additional-properties=npmVersion=$(TAG) \ --additional-properties=supportsES6=true \ - --additional-properties=axiosVersion=^1.13.1 + --additional-properties=axiosVersion=^1.13.1 \ + --additional-properties=launchDarklyApiVersion=${LATEST_API_VERSION} CODEGEN_PARAMS_ruby = \ - --additional-properties=moduleName=LaunchDarklyApi \ - --additional-properties=gemName=launchdarkly_api \ - --additional-properties=gemVersion=$(TAG) \ + -t $(TEMPLATES_PATH)/ruby \ + --additional-properties=moduleName=LaunchDarklyApi \ + --additional-properties=gemName=launchdarkly_api \ + --additional-properties=gemVersion=$(TAG) \ + --additional-properties=launchDarklyApiVersion=${LATEST_API_VERSION} SAMPLE_FILE_go = main.go -SAMPLE_FILE_javascript = index.js SAMPLE_FILE_python = main.py SAMPLE_FILE_ruby = main.rb SAMPLE_FILE_typescript-axios = index.ts SAMPLE_FORMAT_go = go -SAMPLE_FORMAT_javascript = js SAMPLE_FORMAT_python = python SAMPLE_FORMAT_ruby = ruby SAMPLE_FORMAT_typescript-axios = ts diff --git a/README.md b/README.md index 00917c9..640ea19 100644 --- a/README.md +++ b/README.md @@ -16,18 +16,4 @@ Server/client code for the API can be automatically generated. To generate the c ## How releases work -This project is set up (via the files in `.ldrelease`) to work with LaunchDarkly's internal Releaser tool. Running a release via Releaser will execute the required `make` targets in the right order, sandboxed in a Docker container that contains all the necessary build tools. You can also do a "dry run" in Releaser, which does all of the _build_ steps and provides you with the build products, but does not push anything to GitHub or to package managers. - -That is the preferred way of doing releases, but the `make` targets can also be run locally if necessary, as long as your local environment has all of the necessary tools installed (as defined by the Docker image selected in `.ldrelease/config.yml` and anything else that's installed in `.ldrelease/prepare.sh`). Here are more details about what they do: - -* `make load_prior_targets`: Sets up the source repos for each client repository (such as `api-client-go`) as git submodules of the `targets` directory. -* `make all`: Generates the API spec, runs the OpenAPI code generator to create the code for each API client, and copies the generated code into the submodules in `targets`. -* `make build_clients`: Verifies that the generated code for each client can actually be built as a package. This runs the script for each client that is in `scripts/build`. -* `make push`: Pushes the changes in the submodules to the client repositories. -* `make push_dry_run`: Simulates what the result of `make push` would be. -* `make publish`: Pushes the client code to package managers, for platforms where this is applicable (such as NPM for the JavaScript clients). This runs the script for each client that is in `scripts/release`, if any. -* `make publish_dry_run`: Simulates what the result of `make publish` would be.This runs the script for each client that is in `scripts/build`, if any. - -Or, if you just want to look at the generated code locally, run `make targets_docker` which is equivalent to `make all` but uses a Docker container, so that the only tool you need to have installed locally is Docker. The output will appear in `./targets`. This is a convenient way to validate any local changes that relate to code generation. If you are also making changes that affect how the client code is packaged or published, it is better to use the slower but more comprehensive method of a Releaser dry run. - -When running any `make` targets locally, set the environment variable `$LD_RELEASE_VERSION` to the version you are releasing, such as "6.0.0". It is set automatically when releases are run through Releaser. +This project is set up to use [Github Actions](https://github.com/launchdarkly/ld-openapi-private/actions/workflows/release.yml) to release new versions. \ No newline at end of file diff --git a/samples/go/main.go b/samples/go/main.go index bb514da..c5b118c 100644 --- a/samples/go/main.go +++ b/samples/go/main.go @@ -41,14 +41,14 @@ func main() { flag, resp, err := client.FeatureFlagsApi.PostFeatureFlag(ctx, "openapi").FeatureFlagBody(body).Execute() if err != nil { if resp.StatusCode != 409 { - panic(fmt.Errorf("create failed: %s", err)) + panic(fmt.Errorf("create failed: %s", resp)) } else { if _, err := client.FeatureFlagsApi.DeleteFeatureFlag(ctx, "openapi", body.Key).Execute(); err != nil { - panic(fmt.Errorf("delete failed: %s", err)) + panic(fmt.Errorf("delete failed: %s", resp)) } flag, _, err = client.FeatureFlagsApi.PostFeatureFlag(ctx, "openapi").FeatureFlagBody(body).Execute() if err != nil { - panic(fmt.Errorf("create failed: %s", err)) + panic(fmt.Errorf("create failed: %s", resp)) } } } @@ -56,7 +56,7 @@ func main() { // Clean up new flag defer func() { if _, err := client.FeatureFlagsApi.DeleteFeatureFlag(ctx, "openapi", body.Key).Execute(); err != nil { - panic(fmt.Errorf("delete failed: %s", err)) + panic(fmt.Errorf("delete failed: %s", resp)) } }() } diff --git a/samples/javascript/index.js b/samples/javascript/index.js deleted file mode 100644 index 94e7bb9..0000000 --- a/samples/javascript/index.js +++ /dev/null @@ -1,36 +0,0 @@ -const LaunchDarklyApi = require('launchdarkly-api'); - -const defaultClient = LaunchDarklyApi.ApiClient.instance; - -const Token = defaultClient.authentications['ApiKey']; -Token.apiKey = process.env.LD_API_KEY; - -const apiInstance = new LaunchDarklyApi.FeatureFlagsApi(); - -const projectName = "openapi"; -const keyName = "test-javascript"; - -const callback = function(error, data) { - if (error) { - console.error(error); - process.exit(1); - } else { - console.log('API called successfully. Returned data: ' + JSON.stringify(data)); - } -}; - -const postCallback = function(error, data) { - callback(error, data); - - if (!error) { - // Clean up - apiInstance.deleteFeatureFlag(projectName, keyName, callback); - } -}; - -apiInstance.postFeatureFlag(projectName, - { - name: "Test Flag Javascript", - key: keyName, - variations: [{value: [1, 2]}, {value: [3, 4]}, {value: [5]}] - }, {}, postCallback); diff --git a/samples/php/.gitignore b/samples/php/.gitignore deleted file mode 100644 index 7579f74..0000000 --- a/samples/php/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -vendor -composer.lock diff --git a/samples/php/README.md b/samples/php/README.md deleted file mode 100644 index a142f4a..0000000 --- a/samples/php/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Overview - -This sample application will create and delete a feature flag. - -# Running sample - -Get dependencies - -``` -composer update -``` - -``` -LD_API_KEY= composer test -``` - -# Testing with a local package - -Update `composer.json` to link a local version of the package: - -```json -{ - "require": { - "launchdarkly/api-client-php": "@dev", - "guzzlehttp/guzzle": "*" - }, - "repositories": [ - { - "type": "path", - "url": "path/to/api-client-php", - "options": { - "symlink": true - } - } - ] -} -``` - -Then update dependencies - -``` -composer update -``` diff --git a/samples/php/composer.json b/samples/php/composer.json deleted file mode 100644 index 8a68264..0000000 --- a/samples/php/composer.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "require": { - "launchdarkly/api-client-php": "*", - "guzzlehttp/guzzle": "*" - }, - "scripts": { - "test": "php index.php" - } -} diff --git a/samples/php/index.php b/samples/php/index.php deleted file mode 100644 index 12bff7f..0000000 --- a/samples/php/index.php +++ /dev/null @@ -1,40 +0,0 @@ -setApiKey('Authorization', getenv('LD_API_KEY')); - - -$apiInstance = new LaunchDarklyApi\Api\FeatureFlagsApi( - new GuzzleHttp\Client(), - $config -); - -$feature_flag_body = (new \LaunchDarklyApi\Model\FeatureFlagBody()) - ->setKey($FLAG_KEY) - ->setName($FLAG_KEY) - ->setVariations( - [ - (new \LaunchDarklyApi\Model\Variation()) - ->setValue([1,2]), - (new \LaunchDarklyApi\Model\Variation()) - ->setValue([3,4]), - (new \LaunchDarklyApi\Model\Variation()) - ->setValue([4,5]), - ] - ); - -try { - $result = $apiInstance->postFeatureFlag($PROJECT_KEY, $feature_flag_body); - print_r($result); -} catch (Exception $e) { - echo 'Exception creating flag: ', $e->getMessage(), PHP_EOL; -} - -try { - $result = $apiInstance->deleteFeatureFlag($PROJECT_KEY, $FLAG_KEY); -} catch (Exception $e) { - echo 'Exception deleting flag: ', $e->getMessage(), PHP_EOL; -} diff --git a/samples/python/main.py b/samples/python/main.py index 2a2c377..c541c04 100644 --- a/samples/python/main.py +++ b/samples/python/main.py @@ -1,5 +1,6 @@ from __future__ import print_function import os +import sys from pprint import pprint import launchdarkly_api @@ -38,6 +39,7 @@ pprint(api_response) except ApiException as e: print("Exception creating flag: %s\n" % e) + sys.exit("Failed to create the flag") # Clean up the flag try: @@ -45,3 +47,4 @@ pprint(api_response) except ApiException as e: print("Exception deleting flag: %s\n" % e) + sys.exit("Failed to delete the flag") diff --git a/samples/ruby/main.rb b/samples/ruby/main.rb index 412ac7c..30c29ea 100644 --- a/samples/ruby/main.rb +++ b/samples/ruby/main.rb @@ -4,7 +4,7 @@ # Setup authorization LaunchDarklyApi.configure do |config| - config.api_key['ApiKey'] = ENV['LD_API_KEY'] + config.api_key['Authorization'] = ENV['LD_API_KEY'] config.debugging = true end @@ -28,6 +28,7 @@ p result rescue LaunchDarklyApi::ApiError => e puts "Exception creating feature flag: #{e}" + exit("Failed to create the flag") end # Clean up new flag @@ -36,4 +37,5 @@ p result rescue LaunchDarklyApi::ApiError => e puts "Exception deleting feature flag: #{e}" + exit("Failed to delete the flag") end diff --git a/scripts/build/javascript.sh b/scripts/build/javascript.sh deleted file mode 100755 index 9d47e71..0000000 --- a/scripts/build/javascript.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -set -ex - -path=$1 -name=$2 -version=$3 - -cd ${path} - -# Build the package (or at least, verify that the dependencies work) -npm install diff --git a/scripts/release-dry-run/javascript.sh b/scripts/release-dry-run/javascript.sh deleted file mode 100755 index 09ee6c7..0000000 --- a/scripts/release-dry-run/javascript.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -set -ex - -path=$1 -name=$2 -version=$3 - -cd ${path} - -# Simulate publishing to NPM -npm publish --dry-run - -# Copy the package to the output -npm pack -mkdir -p ${LD_RELEASE_ARTIFACTS_DIR}/javascript -cp *.tgz ${LD_RELEASE_ARTIFACTS_DIR}/javascript diff --git a/scripts/release/javascript.sh b/scripts/release/javascript.sh deleted file mode 100755 index 261f355..0000000 --- a/scripts/release/javascript.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -set -ex - -path=$1 -name=$2 -version=$3 - -cd ${path} - -# Publish to NPM -PUBLISH_FAILED= -npm publish || PUBLISH_FAILED=1 - -if [ -n "${PUBLISH_FAILED}" ]; then - # If publication failed because this version of this package already exists, - # then we can assume that we're rerunning a partially successful release, so - # we'll ignore the error and proceed with releasing the other packages. - echo "Publish failed; checking if version ${LD_RELEASE_VERSION} was already published" - PACKAGE_NAME=launchdarkly-api - PACKAGE_EXISTS= - npm view "${PACKAGE_NAME}@${LD_RELEASE_VERSION}" >/dev/null 2>/dev/null && PACKAGE_EXISTS=1 - if [ -n "${PACKAGE_EXISTS}" ]; then - echo "Version ${LD_RELEASE_VERSION} already exists in NPM; proceeding with rest of release" - else - echo "Version ${LD_RELEASE_VERSION} was not found in NPM; must assume the release failed" - exit 1 - fi -fi diff --git a/scripts/release/publish.sh b/scripts/release/publish.sh index e734a1b..7adb8cd 100755 --- a/scripts/release/publish.sh +++ b/scripts/release/publish.sh @@ -11,8 +11,8 @@ git config --global user.email launchdarklyreleasebot@launchdarkly.com # Publish updates to client repositories echo Publishing updates to client repositories... -make RELEASE_TARGETS="go java javascript php python ruby typescript-axios" TAG=${LD_RELEASE_VERSION} push +make RELEASE_TARGETS="go java python ruby typescript-axios" TAG=${LD_RELEASE_VERSION} push # Publish client artifacts to registries echo Publishing client artifacts to registries... -make PUBLISH_TARGETS="javascript python ruby typescript-axios java" publish +make PUBLISH_TARGETS="python ruby typescript-axios java" publish diff --git a/swagger-codegen-templates/go/client.mustache b/swagger-codegen-templates/go/client.mustache new file mode 100644 index 0000000..7186a8f --- /dev/null +++ b/swagger-codegen-templates/go/client.mustache @@ -0,0 +1,765 @@ +{{>partial_header}} +package {{packageName}} + +import ( + "bytes" + "context" + "encoding/json" + "encoding/xml" + "errors" + "fmt" + "io" + "log" + "mime/multipart" + "net/http" + "net/http/httputil" + "net/url" + "os" + "path/filepath" + "reflect" + "regexp" + "strconv" + "strings" + "time" + "unicode/utf8" + + {{#hasOAuthMethods}} + "golang.org/x/oauth2" + {{/hasOAuthMethods}} + {{#withAWSV4Signature}} + awsv4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" + awscredentials "github.com/aws/aws-sdk-go-v2/credentials" + "crypto/sha256" + "encoding/hex" + {{/withAWSV4Signature}} +) + +var ( + JsonCheck = regexp.MustCompile(`(?i:(?:application|text)/(?:[^;]+\+)?json)`) + XmlCheck = regexp.MustCompile(`(?i:(?:application|text)/(?:[^;]+\+)?xml)`) + queryParamSplit = regexp.MustCompile(`(^|&)([^&]+)`) + queryDescape = strings.NewReplacer( "%5B", "[", "%5D", "]" ) +) + +// APIClient manages communication with the {{appName}} API v{{version}} +// In most cases there should be only one, shared, APIClient. +type APIClient struct { + cfg *Configuration + common service // Reuse a single struct instead of allocating one for each service on the heap. + + // API Services +{{#apiInfo}} +{{#apis}} +{{#operations}} + + {{classname}} {{#generateInterfaces}}{{classname}}{{/generateInterfaces}}{{^generateInterfaces}}*{{classname}}Service{{/generateInterfaces}} +{{/operations}} +{{/apis}} +{{/apiInfo}} +} + +type service struct { + client *APIClient +} + +// NewAPIClient creates a new API client. Requires a userAgent string describing your application. +// optionally a custom http.Client to allow for advanced features such as caching. +func NewAPIClient(cfg *Configuration) *APIClient { + if cfg.HTTPClient == nil { + cfg.HTTPClient = http.DefaultClient + } + + c := &APIClient{} + c.cfg = cfg + c.common.client = c + +{{#apiInfo}} + // API Services +{{#apis}} +{{#operations}} + c.{{classname}} = (*{{classname}}Service)(&c.common) +{{/operations}} +{{/apis}} +{{/apiInfo}} + + return c +} + +func atoi(in string) (int, error) { + return strconv.Atoi(in) +} + +// selectHeaderContentType select a content type from the available list. +func selectHeaderContentType(contentTypes []string) string { + if len(contentTypes) == 0 { + return "" + } + if contains(contentTypes, "application/json") { + return "application/json" + } + return contentTypes[0] // use the first content type specified in 'consumes' +} + +// selectHeaderAccept join all accept types and return +func selectHeaderAccept(accepts []string) string { + if len(accepts) == 0 { + return "" + } + + if contains(accepts, "application/json") { + return "application/json" + } + + return strings.Join(accepts, ",") +} + +// contains is a case insensitive match, finding needle in a haystack +func contains(haystack []string, needle string) bool { + for _, a := range haystack { + if strings.EqualFold(a, needle) { + return true + } + } + return false +} + +// Verify optional parameters are of the correct type. +func typeCheckParameter(obj interface{}, expected string, name string) error { + // Make sure there is an object. + if obj == nil { + return nil + } + + // Check the type is as expected. + if reflect.TypeOf(obj).String() != expected { + return fmt.Errorf("expected %s to be of type %s but received %s", name, expected, reflect.TypeOf(obj).String()) + } + return nil +} + +func parameterValueToString( obj interface{}, key string ) string { + if reflect.TypeOf(obj).Kind() != reflect.Ptr { + if actualObj, ok := obj.(interface{ GetActualInstanceValue() interface{} }); ok { + return fmt.Sprintf("%v", actualObj.GetActualInstanceValue()) + } + + return fmt.Sprintf("%v", obj) + } + var param,ok = obj.(MappedNullable) + if !ok { + return "" + } + dataMap,err := param.ToMap() + if err != nil { + return "" + } + return fmt.Sprintf("%v", dataMap[key]) +} + +// parameterAddToHeaderOrQuery adds the provided object to the request header or url query +// supporting deep object syntax +func parameterAddToHeaderOrQuery(headerOrQueryParams interface{}, keyPrefix string, obj interface{}, style string, collectionType string) { + var v = reflect.ValueOf(obj) + var value = "" + if v == reflect.ValueOf(nil) { + value = "null" + } else { + switch v.Kind() { + case reflect.Invalid: + value = "invalid" + + case reflect.Struct: + if t,ok := obj.(MappedNullable); ok { + dataMap,err := t.ToMap() + if err != nil { + return + } + parameterAddToHeaderOrQuery(headerOrQueryParams, keyPrefix, dataMap, style, collectionType) + return + } + if t, ok := obj.(time.Time); ok { + parameterAddToHeaderOrQuery(headerOrQueryParams, keyPrefix, t.Format(time.RFC3339Nano), style, collectionType) + return + } + value = v.Type().String() + " value" + case reflect.Slice: + var indValue = reflect.ValueOf(obj) + if indValue == reflect.ValueOf(nil) { + return + } + var lenIndValue = indValue.Len() + for i:=0;i 0 || (len(formFiles) > 0) { + if body != nil { + return nil, errors.New("Cannot specify postBody and multipart form at the same time.") + } + body = &bytes.Buffer{} + w := multipart.NewWriter(body) + + for k, v := range formParams { + for _, iv := range v { + if strings.HasPrefix(k, "@") { // file + err = addFile(w, k[1:], iv) + if err != nil { + return nil, err + } + } else { // form value + w.WriteField(k, iv) + } + } + } + for _, formFile := range formFiles { + if len(formFile.fileBytes) > 0 && formFile.fileName != "" { + w.Boundary() + part, err := w.CreateFormFile(formFile.formFileName, filepath.Base(formFile.fileName)) + if err != nil { + return nil, err + } + _, err = part.Write(formFile.fileBytes) + if err != nil { + return nil, err + } + } + } + + // Set the Boundary in the Content-Type + headerParams["Content-Type"] = w.FormDataContentType() + + // Set Content-Length + headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len()) + w.Close() + } + + if strings.HasPrefix(headerParams["Content-Type"], "application/x-www-form-urlencoded") && len(formParams) > 0 { + if body != nil { + return nil, errors.New("Cannot specify postBody and x-www-form-urlencoded form at the same time.") + } + body = &bytes.Buffer{} + body.WriteString(formParams.Encode()) + // Set Content-Length + headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len()) + } + + // Setup path and query parameters + url, err := url.Parse(path) + if err != nil { + return nil, err + } + + // Override request host, if applicable + if c.cfg.Host != "" { + url.Host = c.cfg.Host + } + + // Override request scheme, if applicable + if c.cfg.Scheme != "" { + url.Scheme = c.cfg.Scheme + } + + // Adding Query Param + query := url.Query() + for k, v := range queryParams { + for _, iv := range v { + query.Add(k, iv) + } + } + + // Encode the parameters. + url.RawQuery = queryParamSplit.ReplaceAllStringFunc(query.Encode(), func(s string) string { + pieces := strings.Split(s, "=") + pieces[0] = queryDescape.Replace(pieces[0]) + return strings.Join(pieces, "=") + }) + + // Generate a new request + if body != nil { + localVarRequest, err = http.NewRequest(method, url.String(), body) + } else { + localVarRequest, err = http.NewRequest(method, url.String(), nil) + } + if err != nil { + return nil, err + } + + // add header parameters, if any + if len(headerParams) > 0 { + headers := http.Header{} + for h, v := range headerParams { + headers[h] = []string{v} + } + localVarRequest.Header = headers + } + + // CUSTOM-START + // Add the default API version. + if _, exists := localVarRequest.Header["LD-API-Version"]; !exists { + localVarRequest.Header.Add("LD-API-Version", "{{launchDarklyApiVersion}}") + } + // CUSTOM-END + + // Add the user agent to the request. + localVarRequest.Header.Add("User-Agent", c.cfg.UserAgent) + + if ctx != nil { + // add context to the request + localVarRequest = localVarRequest.WithContext(ctx) + + // Walk through any authentication. + + {{#hasOAuthMethods}} + // OAuth2 authentication + if tok, ok := ctx.Value(ContextOAuth2).(oauth2.TokenSource); ok { + // We were able to grab an oauth2 token from the context + var latestToken *oauth2.Token + if latestToken, err = tok.Token(); err != nil { + return nil, err + } + + latestToken.SetAuthHeader(localVarRequest) + } + + {{/hasOAuthMethods}} + {{#hasHttpBasicMethods}} + // Basic HTTP Authentication + if auth, ok := ctx.Value(ContextBasicAuth).(BasicAuth); ok { + localVarRequest.SetBasicAuth(auth.UserName, auth.Password) + } + + {{/hasHttpBasicMethods}} + {{#hasHttpBearerMethods}} + // AccessToken Authentication + if auth, ok := ctx.Value(ContextAccessToken).(string); ok { + localVarRequest.Header.Add("Authorization", "Bearer "+auth) + } + + {{/hasHttpBearerMethods}} + {{#withAWSV4Signature}} + // AWS Signature v4 Authentication + if auth, ok := ctx.Value(ContextAWSv4).(AWSv4); ok { + credsProvider := awscredentials.NewStaticCredentialsProvider(auth.AccessKey, auth.SecretKey, auth.SessionToken) + creds, err := credsProvider.Retrieve(ctx) + if err != nil { + return nil, err + } + + // Define default values for region and service to maintain backward compatibility + region := auth.Region + if region == "" { + region = "eu-west-2" + } + service := auth.Service + if service == "" { + service = "oapi" + } + + // Compute payload hash from the request body + var payloadHash string + if body == nil { + // Empty body + hash := sha256.Sum256([]byte("")) + payloadHash = hex.EncodeToString(hash[:]) + } else { + // Hash the actual body content + bodyBytes := []byte(body.String()) + hash := sha256.Sum256(bodyBytes) + payloadHash = hex.EncodeToString(hash[:]) + } + + // Sign the request with the computed payload hash + signer := awsv4.NewSigner() + err = signer.SignHTTP(ctx, creds, localVarRequest, payloadHash, service, region, time.Now()) + if err != nil { + return nil, err + } + } + {{/withAWSV4Signature}} + } + + for header, value := range c.cfg.DefaultHeader { + localVarRequest.Header.Add(header, value) + } +{{#withCustomMiddlewareFunction}} + + if c.cfg.Middleware != nil { + c.cfg.Middleware(localVarRequest) + } + + if c.cfg.MiddlewareWithError != nil { + err = c.cfg.MiddlewareWithError(localVarRequest) + if err != nil { + return nil, err + } + } + +{{/withCustomMiddlewareFunction}} +{{#hasHttpSignatureMethods}} + if ctx != nil { + // HTTP Signature Authentication. All request headers must be set (including default headers) + // because the headers may be included in the signature. + if auth, ok := ctx.Value(ContextHttpSignatureAuth).(HttpSignatureAuth); ok { + err = SignRequest(ctx, localVarRequest, auth) + if err != nil { + return nil, err + } + } + } +{{/hasHttpSignatureMethods}} + return localVarRequest, nil +} + +func (c *APIClient) decode(v interface{}, b []byte, contentType string) (err error) { + if len(b) == 0 { + return nil + } + if s, ok := v.(*string); ok { + *s = string(b) + return nil + } + if f, ok := v.(*os.File); ok { + f, err = os.CreateTemp("", "HttpClientFile") + if err != nil { + return + } + _, err = f.Write(b) + if err != nil { + return + } + _, err = f.Seek(0, io.SeekStart) + return + } + if f, ok := v.(**os.File); ok { + *f, err = os.CreateTemp("", "HttpClientFile") + if err != nil { + return + } + _, err = (*f).Write(b) + if err != nil { + return + } + _, err = (*f).Seek(0, io.SeekStart) + return + } + if XmlCheck.MatchString(contentType) { + if err = xml.Unmarshal(b, v); err != nil { + return err + } + return nil + } + if JsonCheck.MatchString(contentType) { + if actualObj, ok := v.(interface{ GetActualInstance() interface{} }); ok { // oneOf, anyOf schemas + if unmarshalObj, ok := actualObj.(interface{ UnmarshalJSON([]byte) error }); ok { // make sure it has UnmarshalJSON defined + if err = unmarshalObj.UnmarshalJSON(b); err != nil { + return err + } + } else { + return errors.New("Unknown type with GetActualInstance but no unmarshalObj.UnmarshalJSON defined") + } + } else if err = json.Unmarshal(b, v); err != nil { // simple model + return err + } + return nil + } + return errors.New("undefined response type") +} + +// Add a file to the multipart request +func addFile(w *multipart.Writer, fieldName, path string) error { + file, err := os.Open(filepath.Clean(path)) + if err != nil { + return err + } + defer file.Close() + + part, err := w.CreateFormFile(fieldName, filepath.Base(path)) + if err != nil { + return err + } + _, err = io.Copy(part, file) + + return err +} + +// Set request body from an interface{} +func setBody(body interface{}, contentType string) (bodyBuf *bytes.Buffer, err error) { + if bodyBuf == nil { + bodyBuf = &bytes.Buffer{} + } + + if reader, ok := body.(io.Reader); ok { + _, err = bodyBuf.ReadFrom(reader) + } else if fp, ok := body.(*os.File); ok { + _, err = bodyBuf.ReadFrom(fp) + } else if b, ok := body.([]byte); ok { + _, err = bodyBuf.Write(b) + } else if s, ok := body.(string); ok { + _, err = bodyBuf.WriteString(s) + } else if s, ok := body.(*string); ok { + _, err = bodyBuf.WriteString(*s) + } else if JsonCheck.MatchString(contentType) { + err = json.NewEncoder(bodyBuf).Encode(body) + } else if XmlCheck.MatchString(contentType) { + var bs []byte + bs, err = xml.Marshal(body) + if err == nil { + bodyBuf.Write(bs) + } + } + + if err != nil { + return nil, err + } + + if bodyBuf.Len() == 0 { + err = fmt.Errorf("invalid body type %s\n", contentType) + return nil, err + } + return bodyBuf, nil +} + +// detectContentType method is used to figure out `Request.Body` content type for request header +func detectContentType(body interface{}) string { + contentType := "text/plain; charset=utf-8" + kind := reflect.TypeOf(body).Kind() + + switch kind { + case reflect.Struct, reflect.Map, reflect.Ptr: + contentType = "application/json; charset=utf-8" + case reflect.String: + contentType = "text/plain; charset=utf-8" + default: + if b, ok := body.([]byte); ok { + contentType = http.DetectContentType(b) + } else if kind == reflect.Slice { + contentType = "application/json; charset=utf-8" + } + } + + return contentType +} + +// Ripped from https://github.com/gregjones/httpcache/blob/master/httpcache.go +type cacheControl map[string]string + +func parseCacheControl(headers http.Header) cacheControl { + cc := cacheControl{} + ccHeader := headers.Get("Cache-Control") + for _, part := range strings.Split(ccHeader, ",") { + part = strings.Trim(part, " ") + if part == "" { + continue + } + if strings.ContainsRune(part, '=') { + keyval := strings.Split(part, "=") + cc[strings.Trim(keyval[0], " ")] = strings.Trim(keyval[1], ",") + } else { + cc[part] = "" + } + } + return cc +} + +// CacheExpires helper function to determine remaining time before repeating a request. +func CacheExpires(r *http.Response) time.Time { + // Figure out when the cache expires. + var expires time.Time + now, err := time.Parse(time.RFC1123, r.Header.Get("date")) + if err != nil { + return time.Now() + } + respCacheControl := parseCacheControl(r.Header) + + if maxAge, ok := respCacheControl["max-age"]; ok { + lifetime, err := time.ParseDuration(maxAge + "s") + if err != nil { + expires = now + } else { + expires = now.Add(lifetime) + } + } else { + expiresHeader := r.Header.Get("Expires") + if expiresHeader != "" { + expires, err = time.Parse(time.RFC1123, expiresHeader) + if err != nil { + expires = now + } + } + } + return expires +} + +func strlen(s string) int { + return utf8.RuneCountInString(s) +} + +// GenericOpenAPIError Provides access to the body, error and model on returned errors. +type GenericOpenAPIError struct { + body []byte + error string + model interface{} +} + +// Error returns non-empty string if there was an error. +func (e GenericOpenAPIError) Error() string { + return e.error +} + +// Body returns the raw bytes of the response +func (e GenericOpenAPIError) Body() []byte { + return e.body +} + +// Model returns the unpacked model of the error +func (e GenericOpenAPIError) Model() interface{} { + return e.model +} + +// format error message using title and detail when model implements rfc7807 +func formatErrorMessage(status string, v interface{}) string { + str := "" + metaValue := reflect.ValueOf(v).Elem() + + if metaValue.Kind() == reflect.Struct { + field := metaValue.FieldByName("Title") + if field != (reflect.Value{}) { + str = fmt.Sprintf("%s", field.Interface()) + } + + field = metaValue.FieldByName("Detail") + if field != (reflect.Value{}) { + str = fmt.Sprintf("%s (%s)", str, field.Interface()) + } + } + + return strings.TrimSpace(fmt.Sprintf("%s %s", status, str)) +} \ No newline at end of file diff --git a/swagger-codegen-templates/java/ApiClient.mustache b/swagger-codegen-templates/java/ApiClient.mustache new file mode 100644 index 0000000..4797163 --- /dev/null +++ b/swagger-codegen-templates/java/ApiClient.mustache @@ -0,0 +1,1988 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +{{#dynamicOperations}} +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.parameters.Parameter.StyleEnum; +import io.swagger.v3.parser.OpenAPIV3Parser; +{{/dynamicOperations}} +import okhttp3.*; +import okhttp3.internal.http.HttpMethod; +import okhttp3.internal.tls.OkHostnameVerifier; +import okhttp3.logging.HttpLoggingInterceptor; +import okhttp3.logging.HttpLoggingInterceptor.Level; +import okio.Buffer; +import okio.BufferedSink; +import okio.Okio; +{{#joda}} +import org.joda.time.DateTime; +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormatter; +{{/joda}} +{{#hasOAuthMethods}} +import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder; +import org.apache.oltu.oauth2.common.message.types.GrantType; +{{/hasOAuthMethods}} + +import javax.net.ssl.*; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Type; +import java.net.URI; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.text.DateFormat; +{{#jsr310}} +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +{{/jsr310}} +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import {{invokerPackage}}.auth.Authentication; +import {{invokerPackage}}.auth.HttpBasicAuth; +import {{invokerPackage}}.auth.HttpBearerAuth; +import {{invokerPackage}}.auth.ApiKeyAuth; +{{#hasOAuthMethods}} +import {{invokerPackage}}.auth.OAuth; +import {{invokerPackage}}.auth.RetryingOAuth; +import {{invokerPackage}}.auth.OAuthFlow; +{{/hasOAuthMethods}} +{{#withAWSV4Signature}} +import {{invokerPackage}}.auth.AWS4Auth; +{{/withAWSV4Signature}} + +/** + *

ApiClient class.

+ */ +public class ApiClient { + + protected String basePath = "{{{basePath}}}"; + protected List servers = new ArrayList({{#servers}}{{#-first}}Arrays.asList( +{{/-first}} new ServerConfiguration( + "{{{url}}}", + "{{{description}}}{{^description}}No description provided{{/description}}", + new HashMap(){{#variables}}{{#-first}} {{ +{{/-first}} put("{{{name}}}", new ServerVariable( + "{{{description}}}{{^description}}No description provided{{/description}}", + "{{{defaultValue}}}", + new HashSet( + {{#enumValues}} + {{#-first}} + Arrays.asList( + {{/-first}} + "{{{.}}}"{{^-last}},{{/-last}} + {{#-last}} + ) + {{/-last}} + {{/enumValues}} + ) + )); + {{#-last}} + }}{{/-last}}{{/variables}} + ){{^-last}},{{/-last}} + {{#-last}} + ){{/-last}}{{/servers}}); + protected Integer serverIndex = 0; + protected Map serverVariables = null; + protected boolean debugging = false; + protected Map defaultHeaderMap = new HashMap(); + protected Map defaultCookieMap = new HashMap(); + protected String tempFolderPath = null; + + protected Map authentications; + + protected DateFormat dateFormat; + protected DateFormat datetimeFormat; + protected boolean lenientDatetimeFormat; + protected int dateLength; + + protected InputStream sslCaCert; + protected boolean verifyingSsl; + protected KeyManager[] keyManagers; + protected String tlsServerName; + + protected OkHttpClient httpClient; + protected JSON json; + + protected HttpLoggingInterceptor loggingInterceptor; + + {{#dynamicOperations}} + protected Map operationLookupMap = new HashMap<>(); + + {{/dynamicOperations}} + /** + * Basic constructor for ApiClient + */ + public ApiClient() { + init(); + initHttpClient(); + + // Setup authentications (key: authentication name, value: authentication).{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} + authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isBasicBearer}} + authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} + authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} + authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}}{{#withAWSV4Signature}} + authentications.put("AWS4Auth", new AWS4Auth());{{/withAWSV4Signature}} + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + } + + /** + * Basic constructor with custom OkHttpClient + * + * @param client a {@link okhttp3.OkHttpClient} object + */ + public ApiClient(OkHttpClient client) { + init(); + + httpClient = client; + + // Setup authentications (key: authentication name, value: authentication).{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} + authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isBasicBearer}} + authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} + authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} + authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}}{{#withAWSV4Signature}} + authentications.put("AWS4Auth", new AWS4Auth());{{/withAWSV4Signature}} + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + } + + {{#hasOAuthMethods}} + {{#oauthMethods}} + {{#-first}} + /** + * Constructor for ApiClient to support access token retry on 401/403 configured with client ID + * + * @param clientId client ID + */ + public ApiClient(String clientId) { + this(clientId, null, null); + } + + /** + * Constructor for ApiClient to support access token retry on 401/403 configured with client ID and additional parameters + * + * @param clientId client ID + * @param parameters a {@link java.util.Map} of parameters + */ + public ApiClient(String clientId, Map parameters) { + this(clientId, null, parameters); + } + + /** + * Constructor for ApiClient to support access token retry on 401/403 configured with client ID, secret, and additional parameters + * + * @param clientId client ID + * @param clientSecret client secret + * @param parameters a {@link java.util.Map} of parameters + */ + public ApiClient(String clientId, String clientSecret, Map parameters) { + this(null, clientId, clientSecret, parameters); + } + + /** + * Constructor for ApiClient to support access token retry on 401/403 configured with base path, client ID, secret, and additional parameters + * + * @param basePath base path + * @param clientId client ID + * @param clientSecret client secret + * @param parameters a {@link java.util.Map} of parameters + */ + public ApiClient(String basePath, String clientId, String clientSecret, Map parameters) { + init(); + if (basePath != null) { + this.basePath = basePath; + } + + String tokenUrl = "{{{tokenUrl}}}"; + if (!"".equals(tokenUrl) && !URI.create(tokenUrl).isAbsolute()) { + URI uri = URI.create(getBasePath()); + tokenUrl = uri.getScheme() + ":" + + (uri.getAuthority() != null ? "//" + uri.getAuthority() : "") + + tokenUrl; + if (!URI.create(tokenUrl).isAbsolute()) { + throw new IllegalArgumentException("OAuth2 token URL must be an absolute URL"); + } + } + RetryingOAuth retryingOAuth = new RetryingOAuth(tokenUrl, clientId, OAuthFlow.{{#lambda.uppercase}}{{#lambda.snakecase}}{{flow}}{{/lambda.snakecase}}{{/lambda.uppercase}}, clientSecret, parameters); + authentications.put( + "{{name}}", + retryingOAuth + ); + initHttpClient(Collections.singletonList(retryingOAuth)); + // Setup authentications (key: authentication name, value: authentication).{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} + authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isBasicBearer}} + authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} + authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{/authMethods}}{{#withAWSV4Signature}} + authentications.put("AWS4Auth", new AWS4Auth());{{/withAWSV4Signature}} + + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + } + + {{/-first}} + {{/oauthMethods}} + {{/hasOAuthMethods}} + protected void initHttpClient() { + initHttpClient(Collections.emptyList()); + } + + protected void initHttpClient(List interceptors) { + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.addNetworkInterceptor(getProgressInterceptor()); + for (Interceptor interceptor: interceptors) { + builder.addInterceptor(interceptor); + } + {{#useGzipFeature}} + // Enable gzip request compression + builder.addInterceptor(new GzipRequestInterceptor()); + {{/useGzipFeature}} + + httpClient = builder.build(); + } + + protected void init() { + verifyingSsl = true; + + json = new JSON(); + + // Set default User-Agent. + setUserAgent("{{{httpUserAgent}}}{{^httpUserAgent}}OpenAPI-Generator/{{{artifactVersion}}}/java{{/httpUserAgent}}"); + + // CUSTOM-START + // Set default API version. + addDefaultHeader("LD-API-Version", "{{launchDarklyApiVersion}}"); + // CUSTOM-END + + authentications = new HashMap(); + {{#dynamicOperations}} + + OpenAPI openAPI = new OpenAPIV3Parser().read("openapi/openapi.yaml"); + createOperationLookupMap(openAPI); + {{/dynamicOperations}} + } + + /** + * Get base path + * + * @return Base path + */ + public String getBasePath() { + return basePath; + } + + /** + * Set base path + * + * @param basePath Base path of the URL (e.g {{{basePath}}}) + * @return An instance of ApiClient + */ + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + this.serverIndex = null; + return this; + } + + public List getServers() { + return servers; + } + + public ApiClient setServers(List servers) { + this.servers = servers; + return this; + } + + public Integer getServerIndex() { + return serverIndex; + } + + public ApiClient setServerIndex(Integer serverIndex) { + this.serverIndex = serverIndex; + return this; + } + + public Map getServerVariables() { + return serverVariables; + } + + public ApiClient setServerVariables(Map serverVariables) { + this.serverVariables = serverVariables; + return this; + } + + /** + * Get HTTP client + * + * @return An instance of OkHttpClient + */ + public OkHttpClient getHttpClient() { + return httpClient; + } + + /** + * Set HTTP client, which must never be null. + * + * @param newHttpClient An instance of OkHttpClient + * @return ApiClient + * @throws java.lang.NullPointerException when newHttpClient is null + */ + public ApiClient setHttpClient(OkHttpClient newHttpClient) { + this.httpClient = Objects.requireNonNull(newHttpClient, "HttpClient must not be null!"); + return this; + } + + /** + * Get JSON + * + * @return JSON object + */ + public JSON getJSON() { + return json; + } + + /** + * Set JSON + * + * @param json JSON object + * @return Api client + */ + public ApiClient setJSON(JSON json) { + this.json = json; + return this; + } + + /** + * True if isVerifyingSsl flag is on + * + * @return True if isVerifySsl flag is on + */ + public boolean isVerifyingSsl() { + return verifyingSsl; + } + + /** + * Configure whether to verify certificate and hostname when making https requests. + * Default to true. + * NOTE: Do NOT set to false in production code, otherwise you would face multiple types of cryptographic attacks. + * + * @param verifyingSsl True to verify TLS/SSL connection + * @return ApiClient + */ + public ApiClient setVerifyingSsl(boolean verifyingSsl) { + this.verifyingSsl = verifyingSsl; + applySslSettings(); + return this; + } + + /** + * Get SSL CA cert. + * + * @return Input stream to the SSL CA cert + */ + public InputStream getSslCaCert() { + return sslCaCert; + } + + /** + * Configure the CA certificate to be trusted when making https requests. + * Use null to reset to default. + * + * @param sslCaCert input stream for SSL CA cert + * @return ApiClient + */ + public ApiClient setSslCaCert(InputStream sslCaCert) { + this.sslCaCert = sslCaCert; + applySslSettings(); + return this; + } + + /** + *

Getter for the field keyManagers.

+ * + * @return an array of {@link javax.net.ssl.KeyManager} objects + */ + public KeyManager[] getKeyManagers() { + return keyManagers; + } + + /** + * Configure client keys to use for authorization in an SSL session. + * Use null to reset to default. + * + * @param managers The KeyManagers to use + * @return ApiClient + */ + public ApiClient setKeyManagers(KeyManager[] managers) { + this.keyManagers = managers; + applySslSettings(); + return this; + } + + /** + * Get TLS server name for SNI (Server Name Indication). + * + * @return The TLS server name + */ + public String getTlsServerName() { + return tlsServerName; + } + + /** + * Set TLS server name for SNI (Server Name Indication). + * This is used to verify the server certificate against a specific hostname + * instead of the hostname in the URL. + * + * @param tlsServerName The TLS server name to use for certificate verification + * @return ApiClient + */ + public ApiClient setTlsServerName(String tlsServerName) { + this.tlsServerName = tlsServerName; + applySslSettings(); + return this; + } + + /** + *

Getter for the field dateFormat.

+ * + * @return a {@link java.text.DateFormat} object + */ + public DateFormat getDateFormat() { + return dateFormat; + } + + /** + *

Setter for the field dateFormat.

+ * + * @param dateFormat a {@link java.text.DateFormat} object + * @return a {@link {{invokerPackage}}.ApiClient} object + */ + public ApiClient setDateFormat(DateFormat dateFormat) { + JSON.setDateFormat(dateFormat); + return this; + } + + /** + *

Set SqlDateFormat.

+ * + * @param dateFormat a {@link java.text.DateFormat} object + * @return a {@link {{invokerPackage}}.ApiClient} object + */ + public ApiClient setSqlDateFormat(DateFormat dateFormat) { + JSON.setSqlDateFormat(dateFormat); + return this; + } + + {{#joda}} + public ApiClient setDateTimeFormat(DateTimeFormatter dateFormat) { + JSON.setDateTimeFormat(dateFormat); + return this; + } + + public ApiClient setLocalDateFormat(DateTimeFormatter dateFormat) { + JSON.setLocalDateFormat(dateFormat); + return this; + } + + {{/joda}} + {{#jsr310}} + /** + *

Set OffsetDateTimeFormat.

+ * + * @param dateFormat a {@link java.time.format.DateTimeFormatter} object + * @return a {@link {{invokerPackage}}.ApiClient} object + */ + public ApiClient setOffsetDateTimeFormat(DateTimeFormatter dateFormat) { + JSON.setOffsetDateTimeFormat(dateFormat); + return this; + } + + /** + *

Set LocalDateFormat.

+ * + * @param dateFormat a {@link java.time.format.DateTimeFormatter} object + * @return a {@link {{invokerPackage}}.ApiClient} object + */ + public ApiClient setLocalDateFormat(DateTimeFormatter dateFormat) { + JSON.setLocalDateFormat(dateFormat); + return this; + } + + {{/jsr310}} + /** + *

Set LenientOnJson.

+ * + * @param lenientOnJson a boolean + * @return a {@link {{invokerPackage}}.ApiClient} object + */ + public ApiClient setLenientOnJson(boolean lenientOnJson) { + JSON.setLenientOnJson(lenientOnJson); + return this; + } + + /** + * Get authentications (key: authentication name, value: authentication). + * + * @return Map of authentication objects + */ + public Map getAuthentications() { + return authentications; + } + + /** + * Get authentication for the given name. + * + * @param authName The authentication name + * @return The authentication, null if not found + */ + public Authentication getAuthentication(String authName) { + return authentications.get(authName); + } + + {{#hasHttpBearerMethods}} + /** + * Helper method to set access token for the first Bearer authentication. + * @param bearerToken Bearer token + */ + public void setBearerToken(String bearerToken) { + setBearerToken(() -> bearerToken); + } + + /** + * Helper method to set the supplier of access tokens for Bearer authentication. + * + * @param tokenSupplier The supplier of bearer tokens + */ + public void setBearerToken(Supplier tokenSupplier) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBearerAuth) { + ((HttpBearerAuth) auth).setBearerToken(tokenSupplier); + return; + } + } + throw new RuntimeException("No Bearer authentication configured!"); + } + {{/hasHttpBearerMethods}} + + /** + * Helper method to set username for the first HTTP basic authentication. + * + * @param username Username + */ + public void setUsername(String username) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setUsername(username); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set password for the first HTTP basic authentication. + * + * @param password Password + */ + public void setPassword(String password) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setPassword(password); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set API key value for the first API key authentication. + * + * @param apiKey API key + */ + public void setApiKey(String apiKey) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKey(apiKey); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set API key prefix for the first API key authentication. + * + * @param apiKeyPrefix API key prefix + */ + public void setApiKeyPrefix(String apiKeyPrefix) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set access token for the first OAuth2 authentication. + * + * @param accessToken Access token + */ + public void setAccessToken(String accessToken) { + {{#hasOAuthMethods}} + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).setAccessToken(accessToken); + return; + } + } + {{/hasOAuthMethods}} + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + /** + * Helper method to set credentials for AWSV4 Signature + * + * @param accessKey Access Key + * @param secretKey Secret Key + * @param region Region + * @param service Service to access to + */ + public void setAWS4Configuration(String accessKey, String secretKey, String region, String service) { + {{#withAWSV4Signature}} + for (Authentication auth : authentications.values()) { + if (auth instanceof AWS4Auth) { + ((AWS4Auth) auth).setCredentials(accessKey, secretKey); + ((AWS4Auth) auth).setRegion(region); + ((AWS4Auth) auth).setService(service); + return; + } + } + {{/withAWSV4Signature}} + throw new RuntimeException("No AWS4 authentication configured!"); + } + + /** + * Helper method to set credentials for AWSV4 Signature + * + * @param accessKey Access Key + * @param secretKey Secret Key + * @param sessionToken Session Token + * @param region Region + * @param service Service to access to + */ + public void setAWS4Configuration(String accessKey, String secretKey, String sessionToken, String region, String service) { + {{#withAWSV4Signature}} + for (Authentication auth : authentications.values()) { + if (auth instanceof AWS4Auth) { + ((AWS4Auth) auth).setCredentials(accessKey, secretKey, sessionToken); + ((AWS4Auth) auth).setRegion(region); + ((AWS4Auth) auth).setService(service); + return; + } + } + {{/withAWSV4Signature}} + throw new RuntimeException("No AWS4 authentication configured!"); + } + + /** + * Set the User-Agent header's value (by adding to the default header map). + * + * @param userAgent HTTP request's user agent + * @return ApiClient + */ + public ApiClient setUserAgent(String userAgent) { + addDefaultHeader("User-Agent", userAgent); + return this; + } + + /** + * Add a default header. + * + * @param key The header's key + * @param value The header's value + * @return ApiClient + */ + public ApiClient addDefaultHeader(String key, String value) { + defaultHeaderMap.put(key, value); + return this; + } + + /** + * Add a default cookie. + * + * @param key The cookie's key + * @param value The cookie's value + * @return ApiClient + */ + public ApiClient addDefaultCookie(String key, String value) { + defaultCookieMap.put(key, value); + return this; + } + + /** + * Check that whether debugging is enabled for this API client. + * + * @return True if debugging is enabled, false otherwise. + */ + public boolean isDebugging() { + return debugging; + } + + /** + * Enable/disable debugging for this API client. + * + * @param debugging To enable (true) or disable (false) debugging + * @return ApiClient + */ + public ApiClient setDebugging(boolean debugging) { + if (debugging != this.debugging) { + if (debugging) { + loggingInterceptor = new HttpLoggingInterceptor(); + loggingInterceptor.setLevel(Level.BODY); + httpClient = httpClient.newBuilder().addInterceptor(loggingInterceptor).build(); + } else { + final OkHttpClient.Builder builder = httpClient.newBuilder(); + builder.interceptors().remove(loggingInterceptor); + httpClient = builder.build(); + loggingInterceptor = null; + } + } + this.debugging = debugging; + return this; + } + + /** + * The path of temporary folder used to store downloaded files from endpoints + * with file response. The default value is null, i.e. using + * the system's default temporary folder. + * + * @see createTempFile + * @return Temporary folder path + */ + public String getTempFolderPath() { + return tempFolderPath; + } + + /** + * Set the temporary folder path (for downloading files) + * + * @param tempFolderPath Temporary folder path + * @return ApiClient + */ + public ApiClient setTempFolderPath(String tempFolderPath) { + this.tempFolderPath = tempFolderPath; + return this; + } + + /** + * Get connection timeout (in milliseconds). + * + * @return Timeout in milliseconds + */ + public int getConnectTimeout() { + return httpClient.connectTimeoutMillis(); + } + + /** + * Sets the connect timeout (in milliseconds). + * A value of 0 means no timeout, otherwise values must be between 1 and + * {@link java.lang.Integer#MAX_VALUE}. + * + * @param connectionTimeout connection timeout in milliseconds + * @return Api client + */ + public ApiClient setConnectTimeout(int connectionTimeout) { + httpClient = httpClient.newBuilder().connectTimeout(connectionTimeout, TimeUnit.MILLISECONDS).build(); + return this; + } + + /** + * Get read timeout (in milliseconds). + * + * @return Timeout in milliseconds + */ + public int getReadTimeout() { + return httpClient.readTimeoutMillis(); + } + + /** + * Sets the read timeout (in milliseconds). + * A value of 0 means no timeout, otherwise values must be between 1 and + * {@link java.lang.Integer#MAX_VALUE}. + * + * @param readTimeout read timeout in milliseconds + * @return Api client + */ + public ApiClient setReadTimeout(int readTimeout) { + httpClient = httpClient.newBuilder().readTimeout(readTimeout, TimeUnit.MILLISECONDS).build(); + return this; + } + + /** + * Get write timeout (in milliseconds). + * + * @return Timeout in milliseconds + */ + public int getWriteTimeout() { + return httpClient.writeTimeoutMillis(); + } + + /** + * Sets the write timeout (in milliseconds). + * A value of 0 means no timeout, otherwise values must be between 1 and + * {@link java.lang.Integer#MAX_VALUE}. + * + * @param writeTimeout connection timeout in milliseconds + * @return Api client + */ + public ApiClient setWriteTimeout(int writeTimeout) { + httpClient = httpClient.newBuilder().writeTimeout(writeTimeout, TimeUnit.MILLISECONDS).build(); + return this; + } + + {{#hasOAuthMethods}} + /** + * Helper method to configure the token endpoint of the first oauth found in the apiAuthorizations (there should be only one) + * + * @return Token request builder + */ + public TokenRequestBuilder getTokenEndPoint() { + for (Authentication apiAuth : authentications.values()) { + if (apiAuth instanceof RetryingOAuth) { + RetryingOAuth retryingOAuth = (RetryingOAuth) apiAuth; + return retryingOAuth.getTokenRequestBuilder(); + } + } + return null; + } + {{/hasOAuthMethods}} + + /** + * Format the given parameter object into string. + * + * @param param Parameter + * @return String representation of the parameter + */ + public String parameterToString(Object param) { + if (param == null) { + return ""; + } else if (param instanceof Date {{#joda}}|| param instanceof DateTime || param instanceof LocalDate{{/joda}}{{#jsr310}}|| param instanceof OffsetDateTime || param instanceof LocalDate{{/jsr310}}) { + //Serialize to json string and remove the " enclosing characters + String jsonStr = JSON.serialize(param); + return jsonStr.substring(1, jsonStr.length() - 1); + } else if (param instanceof Collection) { + StringBuilder b = new StringBuilder(); + for (Object o : (Collection) param) { + if (b.length() > 0) { + b.append(","); + } + b.append(o); + } + return b.toString(); + } else { + return String.valueOf(param); + } + } + + /** + * Formats the specified query parameter to a list containing a single {@code Pair} object. + * + * Note that {@code value} must not be a collection. + * + * @param name The name of the parameter. + * @param value The value of the parameter. + * @return A list containing a single {@code Pair} object. + */ + public List parameterToPair(String name, Object value) { + List params = new ArrayList(); + + // preconditions + if (name == null || name.isEmpty() || value == null || value instanceof Collection) { + return params; + } + + params.add(new Pair(name, parameterToString(value))); + return params; + } + + {{^dynamicOperations}} + /** + * Formats the specified collection query parameters to a list of {@code Pair} objects. + * + * Note that the values of each of the returned Pair objects are percent-encoded. + * + * @param collectionFormat The collection format of the parameter. + * @param name The name of the parameter. + * @param value The value of the parameter. + * @return A list of {@code Pair} objects. + */ + public List parameterToPairs(String collectionFormat, String name, Collection value) { + List params = new ArrayList(); + + // preconditions + if (name == null || name.isEmpty() || value == null || value.isEmpty()) { + return params; + } + + // create the params based on the collection format + if ("multi".equals(collectionFormat)) { + for (Object item : value) { + params.add(new Pair(name, escapeString(parameterToString(item)))); + } + return params; + } + + // collectionFormat is assumed to be "csv" by default + String delimiter = ","; + + // escape all delimiters except commas, which are URI reserved + // characters + if ("ssv".equals(collectionFormat)) { + delimiter = escapeString(" "); + } else if ("tsv".equals(collectionFormat)) { + delimiter = escapeString("\t"); + } else if ("pipes".equals(collectionFormat)) { + delimiter = escapeString("|"); + } + + StringBuilder sb = new StringBuilder(); + for (Object item : value) { + sb.append(delimiter); + sb.append(escapeString(parameterToString(item))); + } + + params.add(new Pair(name, sb.substring(delimiter.length()))); + + return params; + } + {{/dynamicOperations}} + {{#dynamicOperations}} + public List parameterToPairs(Parameter param, Collection value) { + List params = new ArrayList(); + + // preconditions + if (param == null || param.getName() == null || param.getName().isEmpty() || value == null || value.isEmpty()) { + return params; + } + + // create the params based on the collection format + if (StyleEnum.FORM.equals(param.getStyle()) && Boolean.TRUE.equals(param.getExplode())) { + for (Object item : value) { + params.add(new Pair(param.getName(), escapeString(parameterToString(item)))); + } + return params; + } + + // collectionFormat is assumed to be "csv" by default + String delimiter = ","; + + // escape all delimiters except commas, which are URI reserved + // characters + if (StyleEnum.SPACEDELIMITED.equals(param.getStyle())) { + delimiter = escapeString(" "); + } else if (StyleEnum.PIPEDELIMITED.equals(param.getStyle())) { + delimiter = escapeString("|"); + } + + StringBuilder sb = new StringBuilder(); + for (Object item : value) { + sb.append(delimiter); + sb.append(escapeString(parameterToString(item))); + } + + params.add(new Pair(param.getName(), sb.substring(delimiter.length()))); + + return params; + } + {{/dynamicOperations}} + + /** + * Formats the specified free-form query parameters to a list of {@code Pair} objects. + * + * @param value The free-form query parameters. + * @return A list of {@code Pair} objects. + */ + public List freeFormParameterToPairs(Object value) { + List params = new ArrayList<>(); + + // preconditions + if (value == null || !(value instanceof Map )) { + return params; + } + + @SuppressWarnings("unchecked") + final Map valuesMap = (Map) value; + + for (Map.Entry entry : valuesMap.entrySet()) { + params.add(new Pair(entry.getKey(), parameterToString(entry.getValue()))); + } + + return params; + } + + + /** + * Formats the specified collection path parameter to a string value. + * + * @param collectionFormat The collection format of the parameter. + * @param value The value of the parameter. + * @return String representation of the parameter + */ + public String collectionPathParameterToString(String collectionFormat, Collection value) { + // create the value based on the collection format + if ("multi".equals(collectionFormat)) { + // not valid for path params + return parameterToString(value); + } + + // collectionFormat is assumed to be "csv" by default + String delimiter = ","; + + if ("ssv".equals(collectionFormat)) { + delimiter = " "; + } else if ("tsv".equals(collectionFormat)) { + delimiter = "\t"; + } else if ("pipes".equals(collectionFormat)) { + delimiter = "|"; + } + + StringBuilder sb = new StringBuilder() ; + for (Object item : value) { + sb.append(delimiter); + sb.append(parameterToString(item)); + } + + return sb.substring(delimiter.length()); + } + + /** + * Sanitize filename by removing path. + * e.g. ../../sun.gif becomes sun.gif + * + * @param filename The filename to be sanitized + * @return The sanitized filename + */ + public String sanitizeFilename(String filename) { + return filename.replaceFirst("^.*[/\\\\]", ""); + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * "* / *" is also default to JSON + * @param mime MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public boolean isJsonMime(String mime) { + String jsonMime = "(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"; + return mime != null && (mime.matches(jsonMime) || mime.equals("*/*")); + } + + /** + * Select the Accept header's value from the given accepts array: + * if JSON exists in the given array, use it; + * otherwise use all of them (joining into a string) + * + * @param accepts The accepts array to select from + * @return The Accept header to use. If the given array is empty, + * null will be returned (not to set the Accept header explicitly). + */ + public String selectHeaderAccept(String[] accepts) { + if (accepts.length == 0) { + return null; + } + for (String accept : accepts) { + if (isJsonMime(accept)) { + return accept; + } + } + return StringUtil.join(accepts, ","); + } + + /** + * Select the Content-Type header's value from the given array: + * if JSON exists in the given array, use it; + * otherwise use the first one of the array. + * + * @param contentTypes The Content-Type array to select from + * @return The Content-Type header to use. If the given array is empty, + * returns null. If it matches "any", JSON will be used. + */ + public String selectHeaderContentType(String[] contentTypes) { + if (contentTypes.length == 0) { + return null; + } + + if (contentTypes[0].equals("*/*")) { + return "application/json"; + } + + for (String contentType : contentTypes) { + if (isJsonMime(contentType)) { + return contentType; + } + } + + return contentTypes[0]; + } + + /** + * Escape the given string to be used as URL query value. + * + * @param str String to be escaped + * @return Escaped string + */ + public String escapeString(String str) { + try { + return URLEncoder.encode(str, "utf8").replaceAll("\\+", "%20"); + } catch (UnsupportedEncodingException e) { + return str; + } + } + + /** + * Deserialize response body to Java object, according to the return type and + * the Content-Type response header. + * + * @param Type + * @param response HTTP response + * @param returnType The type of the Java object + * @return The deserialized Java object + * @throws {{invokerPackage}}.ApiException If fail to deserialize response body, i.e. cannot read response body + * or the Content-Type of the response is not supported. + */ + @SuppressWarnings("unchecked") + public T deserialize(Response response, Type returnType) throws ApiException { + if (response == null || returnType == null) { + return null; + } + + if ("byte[]".equals(returnType.toString())) { + // Handle binary response (byte array). + try { + return (T) response.body().bytes(); + } catch (IOException e) { + throw new ApiException(e); + } + } else if (returnType.equals(File.class)) { + // Handle file downloading. + return (T) downloadFileFromResponse(response); + } + + ResponseBody respBody = response.body(); + if (respBody == null) { + return null; + } + + String contentType = response.headers().get("Content-Type"); + if (contentType == null) { + // ensuring a default content type + contentType = "application/json"; + } + try { + if (isJsonMime(contentType)) { + if (returnType.equals(String.class)) { + String respBodyString = respBody.string(); + if (respBodyString.isEmpty()) { + return null; + } + // Use String-based deserialize for String return type with fallback + return JSON.deserialize(respBodyString, returnType); + } else { + // Use InputStream-based deserialize which supports responses > 2GB + return JSON.deserialize(respBody.byteStream(), returnType); + } + } else if (returnType.equals(String.class)) { + String respBodyString = respBody.string(); + if (respBodyString.isEmpty()) { + return null; + } + // Expecting string, return the raw response body. + return (T) respBodyString; + } else { + throw new ApiException( + "Content type \"" + contentType + "\" is not supported for type: " + returnType, + response.code(), + response.headers().toMultimap(), + response.body().string()); + } + } catch (IOException e) { + throw new ApiException(e); + } + } + + /** + * Serialize the given Java object into request body according to the object's + * class and the request Content-Type. + * + * @param obj The Java object + * @param contentType The request Content-Type + * @return The serialized request body + * @throws {{invokerPackage}}.ApiException If fail to serialize the given object + */ + public RequestBody serialize(Object obj, String contentType) throws ApiException { + if (obj instanceof byte[]) { + // Binary (byte array) body parameter support. + return RequestBody.create((byte[]) obj, MediaType.parse(contentType)); + } else if (obj instanceof File) { + // File body parameter support. + return RequestBody.create((File) obj, MediaType.parse(contentType)); + } else if ("text/plain".equals(contentType) && obj instanceof String) { + return RequestBody.create((String) obj, MediaType.parse(contentType)); + } else if (isJsonMime(contentType)) { + String content; + if (obj != null) { + content = JSON.serialize(obj); + } else { + content = null; + } + return RequestBody.create(content, MediaType.parse(contentType)); + } else if (obj instanceof String) { + return RequestBody.create((String) obj, MediaType.parse(contentType)); + } else { + throw new ApiException("Content type \"" + contentType + "\" is not supported"); + } + } + + /** + * Download file from the given response. + * + * @param response An instance of the Response object + * @throws {{invokerPackage}}.ApiException If fail to read file content from response and write to disk + * @return Downloaded file + */ + public File downloadFileFromResponse(Response response) throws ApiException { + try { + File file = prepareDownloadFile(response); + BufferedSink sink = Okio.buffer(Okio.sink(file)); + sink.writeAll(response.body().source()); + sink.close(); + return file; + } catch (IOException e) { + throw new ApiException(e); + } + } + + /** + * Prepare file for download + * + * @param response An instance of the Response object + * @return Prepared file for the download + * @throws java.io.IOException If fail to prepare file for download + */ + public File prepareDownloadFile(Response response) throws IOException { + String filename = null; + String contentDisposition = response.header("Content-Disposition"); + if (contentDisposition != null && !"".equals(contentDisposition)) { + // Get filename from the Content-Disposition header. + Pattern pattern = Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?"); + Matcher matcher = pattern.matcher(contentDisposition); + if (matcher.find()) { + filename = sanitizeFilename(matcher.group(1)); + } + } + + String prefix = null; + String suffix = null; + if (filename == null) { + prefix = "download-"; + suffix = ""; + } else { + int pos = filename.lastIndexOf("."); + if (pos == -1) { + prefix = filename + "-"; + } else { + prefix = filename.substring(0, pos) + "-"; + suffix = filename.substring(pos); + } + // Files.createTempFile requires the prefix to be at least three characters long + if (prefix.length() < 3) + prefix = "download-"; + } + + if (tempFolderPath == null) + return Files.createTempFile(prefix, suffix).toFile(); + else + return Files.createTempFile(Paths.get(tempFolderPath), prefix, suffix).toFile(); + } + + /** + * {@link #execute(Call, Type)} + * + * @param Type + * @param call An instance of the Call object + * @return ApiResponse<T> + * @throws {{invokerPackage}}.ApiException If fail to execute the call + */ + public ApiResponse execute(Call call) throws ApiException { + return execute(call, null); + } + + /** + * Execute HTTP call and deserialize the HTTP response body into the given return type. + * + * @param returnType The return type used to deserialize HTTP response body + * @param The return type corresponding to (same with) returnType + * @param call Call + * @return ApiResponse object containing response status, headers and + * data, which is a Java object deserialized from response body and would be null + * when returnType is null. + * @throws {{invokerPackage}}.ApiException If fail to execute the call + */ + public ApiResponse execute(Call call, Type returnType) throws ApiException { + try { + Response response = call.execute(); + T data = handleResponse(response, returnType); + return new ApiResponse(response.code(), response.headers().toMultimap(), data); + } catch (IOException e) { + throw new ApiException(e); + } + } + + {{#supportStreaming}} + /** + *

Execute stream.

+ * + * @param call a {@link okhttp3.Call} object + * @param returnType a {@link java.lang.reflect.Type} object + * @return a {@link java.io.InputStream} object + * @throws {{invokerPackage}}.ApiException if any. + */ + public InputStream executeStream(Call call, Type returnType) throws ApiException { + try { + Response response = call.execute(); + if (!response.isSuccessful()) { + throw new ApiException(response.code(), response.message(), response.headers().toMultimap(), null); + } + if (response.body() == null) { + return null; + } + return response.body().byteStream(); + } catch (IOException e) { + throw new ApiException(e); + } + } + + {{/supportStreaming}} + /** + * {@link #executeAsync(Call, Type, ApiCallback)} + * + * @param Type + * @param call An instance of the Call object + * @param callback ApiCallback<T> + */ + public void executeAsync(Call call, ApiCallback callback) { + executeAsync(call, null, callback); + } + + /** + * Execute HTTP call asynchronously. + * + * @param Type + * @param call The callback to be executed when the API call finishes + * @param returnType Return type + * @param callback ApiCallback + * @see #execute(Call, Type) + */ + @SuppressWarnings("unchecked") + public void executeAsync(Call call, final Type returnType, final ApiCallback callback) { + call.enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + callback.onFailure(new ApiException(e), 0, null); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + T result; + try { + result = (T) handleResponse(response, returnType); + } catch (ApiException e) { + callback.onFailure(e, response.code(), response.headers().toMultimap()); + return; + } catch (Exception e) { + callback.onFailure(new ApiException(e), response.code(), response.headers().toMultimap()); + return; + } + callback.onSuccess(result, response.code(), response.headers().toMultimap()); + } + }); + } + + /** + * Handle the given response, return the deserialized object when the response is successful. + * + * @param Type + * @param response Response + * @param returnType Return type + * @return Type + * @throws {{invokerPackage}}.ApiException If the response has an unsuccessful status code or + * fail to deserialize the response body + */ + public T handleResponse(Response response, Type returnType) throws ApiException { + if (response.isSuccessful()) { + if (returnType == null || response.code() == 204) { + // returning null if the returnType is not defined, + // or the status code is 204 (No Content) + if (response.body() != null) { + try { + response.body().close(); + } catch (Exception e) { + throw new ApiException(response.message(), e, response.code(), response.headers().toMultimap()); + } + } + return null; + } else { + return deserialize(response, returnType); + } + } else { + String respBody = null; + if (response.body() != null) { + try { + respBody = response.body().string(); + } catch (IOException e) { + throw new ApiException(response.message(), e, response.code(), response.headers().toMultimap()); + } + } + throw new ApiException(response.message(), response.code(), response.headers().toMultimap(), respBody); + } + } + + /** + * Build HTTP call with the given options. + * + * @param baseUrl The base URL + * @param path The sub-path of the HTTP URL + * @param method The request method, one of "GET", "HEAD", "OPTIONS", "POST", "PUT", "PATCH" and "DELETE" + * @param queryParams The query parameters + * @param collectionQueryParams The collection query parameters + * @param body The request body object + * @param headerParams The header parameters + * @param cookieParams The cookie parameters + * @param formParams The form parameters + * @param authNames The authentications to apply + * @param callback Callback for upload/download progress + * @return The HTTP call + * @throws {{invokerPackage}}.ApiException If fail to serialize the request body object + */ + public Call buildCall(String baseUrl, String path, String method, List queryParams, List collectionQueryParams, Object body, Map headerParams, Map cookieParams, Map formParams, String[] authNames, ApiCallback callback) throws ApiException { + Request request = buildRequest(baseUrl, path, method, queryParams, collectionQueryParams, body, headerParams, cookieParams, formParams, authNames, callback); + + return httpClient.newCall(request); + } + + /** + * Build an HTTP request with the given options. + * + * @param baseUrl The base URL + * @param path The sub-path of the HTTP URL + * @param method The request method, one of "GET", "HEAD", "OPTIONS", "POST", "PUT", "PATCH" and "DELETE" + * @param queryParams The query parameters + * @param collectionQueryParams The collection query parameters + * @param body The request body object + * @param headerParams The header parameters + * @param cookieParams The cookie parameters + * @param formParams The form parameters + * @param authNames The authentications to apply + * @param callback Callback for upload/download progress + * @return The HTTP request + * @throws {{invokerPackage}}.ApiException If fail to serialize the request body object + */ + public Request buildRequest(String baseUrl, String path, String method, List queryParams, List collectionQueryParams, Object body, Map headerParams, Map cookieParams, Map formParams, String[] authNames, ApiCallback callback) throws ApiException { + final String url = buildUrl(baseUrl, path, queryParams, collectionQueryParams); + + // prepare HTTP request body + RequestBody reqBody; + String contentType = headerParams.get("Content-Type"); + String contentTypePure = contentType; + if (contentTypePure != null && contentTypePure.contains(";")) { + contentTypePure = contentType.substring(0, contentType.indexOf(";")); + } + if (!HttpMethod.permitsRequestBody(method)) { + reqBody = null; + } else if ("application/x-www-form-urlencoded".equals(contentTypePure)) { + reqBody = buildRequestBodyFormEncoding(formParams); + } else if ("multipart/form-data".equals(contentTypePure)) { + reqBody = buildRequestBodyMultipart(formParams); + } else if (body == null) { + if ("DELETE".equals(method)) { + // allow calling DELETE without sending a request body + reqBody = null; + } else { + // use an empty request body (for POST, PUT and PATCH) + reqBody = RequestBody.create("", contentType == null ? null : MediaType.parse(contentType)); + } + } else { + reqBody = serialize(body, contentType); + } + + List updatedQueryParams = new ArrayList<>(queryParams); + + // update parameters with authentication settings + updateParamsForAuth(authNames, updatedQueryParams, headerParams, cookieParams, requestBodyToString(reqBody), method, URI.create(url)); + + final Request.Builder reqBuilder = new Request.Builder().url(buildUrl(baseUrl, path, updatedQueryParams, collectionQueryParams)); + processHeaderParams(headerParams, reqBuilder); + processCookieParams(cookieParams, reqBuilder); + + // Associate callback with request (if not null) so interceptor can + // access it when creating ProgressResponseBody + reqBuilder.tag(callback); + + Request request = null; + + if (callback != null && reqBody != null) { + ProgressRequestBody progressRequestBody = new ProgressRequestBody(reqBody, callback); + request = reqBuilder.method(method, progressRequestBody).build(); + } else { + request = reqBuilder.method(method, reqBody).build(); + } + + return request; + } + + /** + * Build full URL by concatenating base path, the given sub path and query parameters. + * + * @param baseUrl The base URL + * @param path The sub path + * @param queryParams The query parameters + * @param collectionQueryParams The collection query parameters + * @return The full URL + */ + public String buildUrl(String baseUrl, String path, List queryParams, List collectionQueryParams) { + final StringBuilder url = new StringBuilder(); + if (baseUrl != null) { + url.append(baseUrl).append(path); + } else { + String baseURL; + if (serverIndex != null) { + if (serverIndex < 0 || serverIndex >= servers.size()) { + throw new ArrayIndexOutOfBoundsException(String.format( + java.util.Locale.ROOT, + "Invalid index %d when selecting the host settings. Must be less than %d", serverIndex, servers.size() + )); + } + baseURL = servers.get(serverIndex).URL(serverVariables); + } else { + baseURL = basePath; + } + url.append(baseURL).append(path); + } + + if (queryParams != null && !queryParams.isEmpty()) { + // support (constant) query string in `path`, e.g. "/posts?draft=1" + String prefix = path.contains("?") ? "&" : "?"; + for (Pair param : queryParams) { + if (param.getValue() != null) { + if (prefix != null) { + url.append(prefix); + prefix = null; + } else { + url.append("&"); + } + String value = parameterToString(param.getValue()); + url.append(escapeString(param.getName())).append("=").append(escapeString(value)); + } + } + } + + if (collectionQueryParams != null && !collectionQueryParams.isEmpty()) { + String prefix = url.toString().contains("?") ? "&" : "?"; + for (Pair param : collectionQueryParams) { + if (param.getValue() != null) { + if (prefix != null) { + url.append(prefix); + prefix = null; + } else { + url.append("&"); + } + String value = parameterToString(param.getValue()); + // collection query parameter value already escaped as part of parameterToPairs + url.append(escapeString(param.getName())).append("=").append(value); + } + } + } + + return url.toString(); + } + + /** + * Set header parameters to the request builder, including default headers. + * + * @param headerParams Header parameters in the form of Map + * @param reqBuilder Request.Builder + */ + public void processHeaderParams(Map headerParams, Request.Builder reqBuilder) { + for (Entry param : headerParams.entrySet()) { + reqBuilder.header(param.getKey(), parameterToString(param.getValue())); + } + for (Entry header : defaultHeaderMap.entrySet()) { + if (!headerParams.containsKey(header.getKey())) { + reqBuilder.header(header.getKey(), parameterToString(header.getValue())); + } + } + } + + /** + * Set cookie parameters to the request builder, including default cookies. + * + * @param cookieParams Cookie parameters in the form of Map + * @param reqBuilder Request.Builder + */ + public void processCookieParams(Map cookieParams, Request.Builder reqBuilder) { + for (Entry param : cookieParams.entrySet()) { + reqBuilder.addHeader("Cookie", String.format(java.util.Locale.ROOT, "%s=%s", param.getKey(), param.getValue())); + } + for (Entry param : defaultCookieMap.entrySet()) { + if (!cookieParams.containsKey(param.getKey())) { + reqBuilder.addHeader("Cookie", String.format(java.util.Locale.ROOT, "%s=%s", param.getKey(), param.getValue())); + } + } + } + + /** + * Update query and header parameters based on authentication settings. + * + * @param authNames The authentications to apply + * @param queryParams List of query parameters + * @param headerParams Map of header parameters + * @param cookieParams Map of cookie parameters + * @param payload HTTP request body + * @param method HTTP method + * @param uri URI + * @throws {{invokerPackage}}.ApiException If fails to update the parameters + */ + public void updateParamsForAuth(String[] authNames, List queryParams, Map headerParams, + Map cookieParams, String payload, String method, URI uri) throws ApiException { + for (String authName : authNames) { + Authentication auth = authentications.get(authName); + if (auth == null) { + throw new RuntimeException("Authentication undefined: " + authName); + } + auth.applyToParams(queryParams, headerParams, cookieParams, payload, method, uri); + } + } + + /** + * Build a form-encoding request body with the given form parameters. + * + * @param formParams Form parameters in the form of Map + * @return RequestBody + */ + public RequestBody buildRequestBodyFormEncoding(Map formParams) { + okhttp3.FormBody.Builder formBuilder = new okhttp3.FormBody.Builder(); + for (Entry param : formParams.entrySet()) { + formBuilder.add(param.getKey(), parameterToString(param.getValue())); + } + return formBuilder.build(); + } + + /** + * Build a multipart (file uploading) request body with the given form parameters, + * which could contain text fields and file fields. + * + * @param formParams Form parameters in the form of Map + * @return RequestBody + */ + public RequestBody buildRequestBodyMultipart(Map formParams) { + MultipartBody.Builder mpBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); + for (Entry param : formParams.entrySet()) { + if (param.getValue() instanceof File) { + File file = (File) param.getValue(); + addPartToMultiPartBuilder(mpBuilder, param.getKey(), file); + } else if (param.getValue() instanceof List) { + List list = (List) param.getValue(); + for (Object item: list) { + if (item instanceof File) { + addPartToMultiPartBuilder(mpBuilder, param.getKey(), (File) item); + } else { + addPartToMultiPartBuilder(mpBuilder, param.getKey(), param.getValue()); + } + } + } else { + addPartToMultiPartBuilder(mpBuilder, param.getKey(), param.getValue()); + } + } + return mpBuilder.build(); + } + + /** + * Guess Content-Type header from the given file (defaults to "application/octet-stream"). + * + * @param file The given file + * @return The guessed Content-Type + */ + public String guessContentTypeFromFile(File file) { + String contentType = URLConnection.guessContentTypeFromName(file.getName()); + if (contentType == null) { + return "application/octet-stream"; + } else { + return contentType; + } + } + + /** + * Add a Content-Disposition Header for the given key and file to the MultipartBody Builder. + * + * @param mpBuilder MultipartBody.Builder + * @param key The key of the Header element + * @param file The file to add to the Header + */ + protected void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, File file) { + Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""); + MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); + mpBuilder.addPart(partHeaders, RequestBody.create(file, mediaType)); + } + + /** + * Add a Content-Disposition Header for the given key and complex object to the MultipartBody Builder. + * + * @param mpBuilder MultipartBody.Builder + * @param key The key of the Header element + * @param obj The complex object to add to the Header + */ + protected void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, Object obj) { + RequestBody requestBody; + if (obj instanceof String) { + requestBody = RequestBody.create((String) obj, MediaType.parse("text/plain")); + } else { + String content; + if (obj != null) { + content = JSON.serialize(obj); + } else { + content = null; + } + requestBody = RequestBody.create(content, MediaType.parse("application/json")); + } + + Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""); + mpBuilder.addPart(partHeaders, requestBody); + } + + /** + * Get network interceptor to add it to the httpClient to track download progress for + * async requests. + */ + protected Interceptor getProgressInterceptor() { + return new Interceptor() { + @Override + public Response intercept(Interceptor.Chain chain) throws IOException { + final Request request = chain.request(); + final Response originalResponse = chain.proceed(request); + if (request.tag() instanceof ApiCallback) { + final ApiCallback callback = (ApiCallback) request.tag(); + return originalResponse.newBuilder() + .body(new ProgressResponseBody(originalResponse.body(), callback)) + .build(); + } + return originalResponse; + } + }; + } + + /** + * Apply SSL related settings to httpClient according to the current values of + * verifyingSsl and sslCaCert. + */ + protected void applySslSettings() { + try { + TrustManager[] trustManagers; + HostnameVerifier hostnameVerifier; + if (!verifyingSsl) { + trustManagers = new TrustManager[]{ + new X509TrustManager() { + @Override + public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[]{}; + } + } + }; + hostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + } else { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + + if (sslCaCert == null) { + trustManagerFactory.init((KeyStore) null); + } else { + char[] password = null; // Any password will work. + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + Collection certificates = certificateFactory.generateCertificates(sslCaCert); + if (certificates.isEmpty()) { + throw new IllegalArgumentException("expected non-empty set of trusted certificates"); + } + KeyStore caKeyStore = newEmptyKeyStore(password); + int index = 0; + for (Certificate certificate : certificates) { + String certificateAlias = "ca" + (index++); + caKeyStore.setCertificateEntry(certificateAlias, certificate); + } + trustManagerFactory.init(caKeyStore); + } + trustManagers = trustManagerFactory.getTrustManagers(); + if (tlsServerName != null && !tlsServerName.isEmpty()) { + hostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + // Verify the certificate against tlsServerName instead of the actual hostname + return OkHostnameVerifier.INSTANCE.verify(tlsServerName, session); + } + }; + } else { + hostnameVerifier = OkHostnameVerifier.INSTANCE; + } + } + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagers, trustManagers, new SecureRandom()); + httpClient = httpClient.newBuilder() + .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagers[0]) + .hostnameVerifier(hostnameVerifier) + .build(); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + protected KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException { + try { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, password); + return keyStore; + } catch (IOException e) { + throw new AssertionError(e); + } + } + {{#dynamicOperations}} + + public ApiClient createOperationLookupMap(OpenAPI openAPI) { + operationLookupMap = new HashMap<>(); + for (Map.Entry pathItemEntry : openAPI.getPaths().entrySet()) { + String path = pathItemEntry.getKey(); + PathItem pathItem = pathItemEntry.getValue(); + addOperationLookupEntry(path, "GET", pathItem.getGet()); + addOperationLookupEntry(path, "PUT", pathItem.getPut()); + addOperationLookupEntry(path, "POST", pathItem.getPost()); + addOperationLookupEntry(path, "DELETE", pathItem.getDelete()); + addOperationLookupEntry(path, "OPTIONS", pathItem.getOptions()); + addOperationLookupEntry(path, "HEAD", pathItem.getHead()); + addOperationLookupEntry(path, "PATCH", pathItem.getPatch()); + addOperationLookupEntry(path, "TRACE", pathItem.getTrace()); + } + return this; + } + + protected void addOperationLookupEntry(String path, String method, Operation operation) { + if ( operation != null && operation.getOperationId() != null) { + operationLookupMap.put( + operation.getOperationId(), + new ApiOperation(path, method, operation)); + } + } + + public Map getOperationLookupMap() { + return operationLookupMap; + } + + public String fillParametersFromOperation( + Operation operation, + Map paramMap, + String path, + List queryParams, + List collectionQueryParams, + Map headerParams, + Map cookieParams + ) { + for (Map.Entry entry : paramMap.entrySet()) { + Object value = entry.getValue(); + for (Parameter param : operation.getParameters()) { + if (entry.getKey().equals(param.getName())) { + switch (param.getIn()) { + case "path": + path = path.replace("{" + param.getName() + "}", escapeString(value.toString())); + break; + case "query": + if (value instanceof Collection) { + collectionQueryParams.addAll(parameterToPairs(param, (Collection) value)); + } else { + queryParams.addAll(parameterToPair(param.getName(), value)); + } + break; + case "header": + headerParams.put(param.getName(), parameterToString(value)); + break; + case "cookie": + cookieParams.put(param.getName(), parameterToString(value)); + break; + default: + throw new IllegalStateException("Unexpected param in: " + param.getIn()); + } + + } + } + } + return path; + } + {{/dynamicOperations}} + + /** + * Convert the HTTP request body to a string. + * + * @param requestBody The HTTP request object + * @return The string representation of the HTTP request body + * @throws {{invokerPackage}}.ApiException If fail to serialize the request body object into a string + */ + protected String requestBodyToString(RequestBody requestBody) throws ApiException { + if (requestBody != null) { + try { + final Buffer buffer = new Buffer(); + requestBody.writeTo(buffer); + return buffer.readUtf8(); + } catch (final IOException e) { + throw new ApiException(e); + } + } + + // empty http request body + return ""; + } +} \ No newline at end of file diff --git a/swagger-codegen-templates/javascript/.gitkeep b/swagger-codegen-templates/javascript/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/swagger-codegen-templates/python/api_client.mustache b/swagger-codegen-templates/python/api_client.mustache new file mode 100644 index 0000000..f71f684 --- /dev/null +++ b/swagger-codegen-templates/python/api_client.mustache @@ -0,0 +1,828 @@ +# coding: utf-8 + +{{>partial_header}} + + +import datetime +from dateutil.parser import parse +from enum import Enum +import decimal +import json +import mimetypes +import os +import re +import tempfile +import uuid + +from urllib.parse import quote +from typing import Tuple, Optional, List, Dict, Union +from pydantic import SecretStr +{{#tornado}} +import tornado.gen +{{/tornado}} + +from {{packageName}}.configuration import Configuration +from {{packageName}}.api_response import ApiResponse, T as ApiResponseT +import {{modelPackage}} +from {{packageName}} import rest +from {{packageName}}.exceptions import ( + ApiValueError, + ApiException, + BadRequestException, + UnauthorizedException, + ForbiddenException, + NotFoundException, + ServiceException +) + +RequestSerialized = Tuple[str, str, Dict[str, str], Optional[str], List[str]] + +class ApiClient: + """Generic API client for OpenAPI client library builds. + + OpenAPI generic API client. This client handles the client- + server communication, and is invariant across implementations. Specifics of + the methods and models for each application are generated from the OpenAPI + templates. + + :param configuration: .Configuration object for this client + :param header_name: a header to pass when making calls to the API. + :param header_value: a header value to pass when making calls to + the API. + :param cookie: a cookie to include in the header when making calls + to the API + """ + + PRIMITIVE_TYPES = (float, bool, bytes, str, int) + NATIVE_TYPES_MAPPING = { + 'int': int, + 'long': int, # TODO remove as only py3 is supported? + 'float': float, + 'str': str, + 'bool': bool, + 'date': datetime.date, + 'datetime': datetime.datetime, + 'decimal': decimal.Decimal, + 'object': object, + } + _pool = None + + def __init__( + self, + configuration=None, + header_name=None, + header_value=None, + cookie=None + ) -> None: + # use default configuration if none is provided + if configuration is None: + configuration = Configuration.get_default() + self.configuration = configuration + + self.rest_client = rest.RESTClientObject(configuration) + self.default_headers = {} + if header_name is not None: + self.default_headers[header_name] = header_value + + # CUSTOM-START + # Set default API version. + self.default_headers['LD-API-Version'] = '{{launchDarklyApiVersion}}' + # CUSTOM-END + + self.cookie = cookie + # Set default User-Agent. + self.user_agent = '{{{httpUserAgent}}}{{^httpUserAgent}}OpenAPI-Generator/{{{packageVersion}}}/python{{/httpUserAgent}}' + self.client_side_validation = configuration.client_side_validation + +{{#async}} + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_value, traceback): + await self.close() + + async def close(self): + await self.rest_client.close() +{{/async}} +{{^async}} + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass +{{/async}} + + @property + def user_agent(self): + """User agent for this API client""" + return self.default_headers['User-Agent'] + + @user_agent.setter + def user_agent(self, value): + self.default_headers['User-Agent'] = value + + def set_default_header(self, header_name, header_value): + self.default_headers[header_name] = header_value + + + _default = None + + @classmethod + def get_default(cls): + """Return new instance of ApiClient. + + This method returns newly created, based on default constructor, + object of ApiClient class or returns a copy of default + ApiClient. + + :return: The ApiClient object. + """ + if cls._default is None: + cls._default = ApiClient() + return cls._default + + @classmethod + def set_default(cls, default): + """Set default instance of ApiClient. + + It stores default ApiClient. + + :param default: object of ApiClient. + """ + cls._default = default + + def param_serialize( + self, + method, + resource_path, + path_params=None, + query_params=None, + header_params=None, + body=None, + post_params=None, + files=None, auth_settings=None, + collection_formats=None, + _host=None, + _request_auth=None + ) -> RequestSerialized: + + """Builds the HTTP request params needed by the request. + :param method: Method to call. + :param resource_path: Path to method endpoint. + :param path_params: Path parameters in the url. + :param query_params: Query parameters in the url. + :param header_params: Header parameters to be + placed in the request header. + :param body: Request body. + :param post_params dict: Request post form parameters, + for `application/x-www-form-urlencoded`, `multipart/form-data`. + :param auth_settings list: Auth Settings names for the request. + :param files dict: key -> filename, value -> filepath, + for `multipart/form-data`. + :param collection_formats: dict of collection formats for path, query, + header, and post parameters. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :return: tuple of form (path, http_method, query_params, header_params, + body, post_params, files) + """ + + config = self.configuration + + # header parameters + header_params = header_params or {} + header_params.update(self.default_headers) + if self.cookie: + header_params['Cookie'] = self.cookie + if header_params: + header_params = self.sanitize_for_serialization(header_params) + header_params = dict( + self.parameters_to_tuples(header_params,collection_formats) + ) + + # path parameters + if path_params: + path_params = self.sanitize_for_serialization(path_params) + path_params = self.parameters_to_tuples( + path_params, + collection_formats + ) + for k, v in path_params: + # specified safe chars, encode everything + resource_path = resource_path.replace( + '{%s}' % k, + quote(str(v), safe=config.safe_chars_for_path_param) + ) + + # post parameters + if post_params or files: + post_params = post_params if post_params else [] + post_params = self.sanitize_for_serialization(post_params) + post_params = self.parameters_to_tuples( + post_params, + collection_formats + ) + if files: + post_params.extend(self.files_parameters(files)) + + # auth setting + self.update_params_for_auth( + header_params, + query_params, + auth_settings, + resource_path, + method, + body, + request_auth=_request_auth + ) + + # body + if body: + body = self.sanitize_for_serialization(body) + + # request url + if _host is None or self.configuration.ignore_operation_servers: + url = self.configuration.host + resource_path + else: + # use server/host defined in path or operation instead + url = _host + resource_path + + # query parameters + if query_params: + query_params = self.sanitize_for_serialization(query_params) + url_query = self.parameters_to_url_query( + query_params, + collection_formats + ) + url += "?" + url_query + + return method, url, header_params, body, post_params + + + {{#tornado}} + @tornado.gen.coroutine + {{/tornado}} + {{#async}}async {{/async}}def call_api( + self, + method, + url, + header_params=None, + body=None, + post_params=None, + _request_timeout=None + ) -> rest.RESTResponse: + """Makes the HTTP request (synchronous) + :param method: Method to call. + :param url: Path to method endpoint. + :param header_params: Header parameters to be + placed in the request header. + :param body: Request body. + :param post_params dict: Request post form parameters, + for `application/x-www-form-urlencoded`, `multipart/form-data`. + :param _request_timeout: timeout setting for this request. + :return: RESTResponse + """ + + try: + # perform request and return response + response_data = {{#async}}await {{/async}}{{#tornado}}yield {{/tornado}}self.rest_client.request( + method, url, + headers=header_params, + body=body, post_params=post_params, + _request_timeout=_request_timeout + ) + + except ApiException as e: + raise e + + return response_data + + def response_deserialize( + self, + response_data: rest.RESTResponse, + response_types_map: Optional[Dict[str, ApiResponseT]]=None + ) -> ApiResponse[ApiResponseT]: + """Deserializes response into an object. + :param response_data: RESTResponse object to be deserialized. + :param response_types_map: dict of response types. + :return: ApiResponse + """ + + msg = "RESTResponse.read() must be called before passing it to response_deserialize()" + assert response_data.data is not None, msg + + response_type = response_types_map.get(str(response_data.status), None) + if not response_type and isinstance(response_data.status, int) and 100 <= response_data.status <= 599: + # if not found, look for '1XX', '2XX', etc. + response_type = response_types_map.get(str(response_data.status)[0] + "XX", None) + + # deserialize response data + response_text = None + return_data = None + try: + if response_type == "bytearray": + return_data = response_data.data + elif response_type == "file": + return_data = self.__deserialize_file(response_data) + elif response_type is not None: + match = None + content_type = response_data.headers.get('content-type') + if content_type is not None: + match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type) + encoding = match.group(1) if match else "utf-8" + response_text = response_data.data.decode(encoding) + return_data = self.deserialize(response_text, response_type, content_type) + finally: + if not 200 <= response_data.status <= 299: + raise ApiException.from_response( + http_resp=response_data, + body=response_text, + data=return_data, + ) + + return ApiResponse( + status_code = response_data.status, + data = return_data, + headers = response_data.headers, + raw_data = response_data.data + ) + + def sanitize_for_serialization(self, obj): + """Builds a JSON POST object. + + If obj is None, return None. + If obj is SecretStr, return obj.get_secret_value() + If obj is str, int, long, float, bool, return directly. + If obj is datetime.datetime, datetime.date + convert to string in iso8601 format. + If obj is decimal.Decimal return string representation. + If obj is list, sanitize each element in the list. + If obj is dict, return the dict. + If obj is OpenAPI model, return the properties dict. + + :param obj: The data to serialize. + :return: The serialized form of data. + """ + if obj is None: + return None + elif isinstance(obj, Enum): + return obj.value + elif isinstance(obj, SecretStr): + return obj.get_secret_value() + elif isinstance(obj, self.PRIMITIVE_TYPES): + return obj + elif isinstance(obj, uuid.UUID): + return str(obj) + elif isinstance(obj, list): + return [ + self.sanitize_for_serialization(sub_obj) for sub_obj in obj + ] + elif isinstance(obj, tuple): + return tuple( + self.sanitize_for_serialization(sub_obj) for sub_obj in obj + ) + elif isinstance(obj, (datetime.datetime, datetime.date)): + return obj.isoformat() + elif isinstance(obj, decimal.Decimal): + return str(obj) + + elif isinstance(obj, dict): + obj_dict = obj + else: + # Convert model obj to dict except + # attributes `openapi_types`, `attribute_map` + # and attributes which value is not None. + # Convert attribute name to json key in + # model definition for request. + if hasattr(obj, 'to_dict') and callable(getattr(obj, 'to_dict')): + obj_dict = obj.to_dict() + else: + obj_dict = obj.__dict__ + + if isinstance(obj_dict, list): + # here we handle instances that can either be a list or something else, and only became a real list by calling to_dict() + return self.sanitize_for_serialization(obj_dict) + + return { + key: self.sanitize_for_serialization(val) + for key, val in obj_dict.items() + } + + def deserialize(self, response_text: str, response_type: str, content_type: Optional[str]): + """Deserializes response into an object. + + :param response: RESTResponse object to be deserialized. + :param response_type: class literal for + deserialized object, or string of class name. + :param content_type: content type of response. + + :return: deserialized object. + """ + + # fetch data from response object + if content_type is None: + try: + data = json.loads(response_text) + except ValueError: + data = response_text + elif re.match(r'^application/(json|[\w!#$&.+\-^_]+\+json)\s*(;|$)', content_type, re.IGNORECASE): + if response_text == "": + data = "" + else: + data = json.loads(response_text) + elif re.match(r'^text\/[a-z.+-]+\s*(;|$)', content_type, re.IGNORECASE): + data = response_text + else: + raise ApiException( + status=0, + reason="Unsupported content type: {0}".format(content_type) + ) + + return self.__deserialize(data, response_type) + + def __deserialize(self, data, klass): + """Deserializes dict, list, str into an object. + + :param data: dict, list or str. + :param klass: class literal, or string of class name. + + :return: object. + """ + if data is None: + return None + + if isinstance(klass, str): + if klass.startswith('List['): + m = re.match(r'List\[(.*)]', klass) + assert m is not None, "Malformed List type definition" + sub_kls = m.group(1) + return [self.__deserialize(sub_data, sub_kls) + for sub_data in data] + + if klass.startswith('Dict['): + m = re.match(r'Dict\[([^,]*), (.*)]', klass) + assert m is not None, "Malformed Dict type definition" + sub_kls = m.group(2) + return {k: self.__deserialize(v, sub_kls) + for k, v in data.items()} + + # convert str to class + if klass in self.NATIVE_TYPES_MAPPING: + klass = self.NATIVE_TYPES_MAPPING[klass] + else: + klass = getattr({{modelPackage}}, klass) + + if klass in self.PRIMITIVE_TYPES: + return self.__deserialize_primitive(data, klass) + elif klass is object: + return self.__deserialize_object(data) + elif klass is datetime.date: + return self.__deserialize_date(data) + elif klass is datetime.datetime: + return self.__deserialize_datetime(data) + elif klass is decimal.Decimal: + return decimal.Decimal(data) + elif issubclass(klass, Enum): + return self.__deserialize_enum(data, klass) + else: + return self.__deserialize_model(data, klass) + + def parameters_to_tuples(self, params, collection_formats): + """Get parameters as list of tuples, formatting collections. + + :param params: Parameters as dict or list of two-tuples + :param dict collection_formats: Parameter collection formats + :return: Parameters as list of tuples, collections formatted + """ + new_params: List[Tuple[str, str]] = [] + if collection_formats is None: + collection_formats = {} + for k, v in params.items() if isinstance(params, dict) else params: + if k in collection_formats: + collection_format = collection_formats[k] + if collection_format == 'multi': + new_params.extend((k, value) for value in v) + else: + if collection_format == 'ssv': + delimiter = ' ' + elif collection_format == 'tsv': + delimiter = '\t' + elif collection_format == 'pipes': + delimiter = '|' + else: # csv is the default + delimiter = ',' + new_params.append( + (k, delimiter.join(str(value) for value in v))) + else: + new_params.append((k, v)) + return new_params + + def parameters_to_url_query(self, params, collection_formats): + """Get parameters as list of tuples, formatting collections. + + :param params: Parameters as dict or list of two-tuples + :param dict collection_formats: Parameter collection formats + :return: URL query string (e.g. a=Hello%20World&b=123) + """ + new_params: List[Tuple[str, str]] = [] + if collection_formats is None: + collection_formats = {} + for k, v in params.items() if isinstance(params, dict) else params: + if isinstance(v, bool): + v = str(v).lower() + if isinstance(v, (int, float)): + v = str(v) + if isinstance(v, dict): + v = json.dumps(v) + + if k in collection_formats: + collection_format = collection_formats[k] + if collection_format == 'multi': + new_params.extend((k, quote(str(value))) for value in v) + else: + if collection_format == 'ssv': + delimiter = ' ' + elif collection_format == 'tsv': + delimiter = '\t' + elif collection_format == 'pipes': + delimiter = '|' + else: # csv is the default + delimiter = ',' + new_params.append( + (k, delimiter.join(quote(str(value)) for value in v)) + ) + else: + new_params.append((k, quote(str(v)))) + + return "&".join(["=".join(map(str, item)) for item in new_params]) + + def files_parameters( + self, + files: Dict[str, Union[str, bytes, List[str], List[bytes], Tuple[str, bytes]]], + ): + """Builds form parameters. + + :param files: File parameters. + :return: Form parameters with files. + """ + params = [] + for k, v in files.items(): + if isinstance(v, str): + with open(v, 'rb') as f: + filename = os.path.basename(f.name) + filedata = f.read() + elif isinstance(v, bytes): + filename = k + filedata = v + elif isinstance(v, tuple): + filename, filedata = v + elif isinstance(v, list): + for file_param in v: + params.extend(self.files_parameters({k: file_param})) + continue + else: + raise ValueError("Unsupported file value") + mimetype = ( + mimetypes.guess_type(filename)[0] + or 'application/octet-stream' + ) + params.append( + tuple([k, tuple([filename, filedata, mimetype])]) + ) + return params + + def select_header_accept(self, accepts: List[str]) -> Optional[str]: + """Returns `Accept` based on an array of accepts provided. + + :param accepts: List of headers. + :return: Accept (e.g. application/json). + """ + if not accepts: + return None + + for accept in accepts: + if re.search('json', accept, re.IGNORECASE): + return accept + + return accepts[0] + + def select_header_content_type(self, content_types): + """Returns `Content-Type` based on an array of content_types provided. + + :param content_types: List of content-types. + :return: Content-Type (e.g. application/json). + """ + if not content_types: + return None + + for content_type in content_types: + if re.search('json', content_type, re.IGNORECASE): + return content_type + + return content_types[0] + + def update_params_for_auth( + self, + headers, + queries, + auth_settings, + resource_path, + method, + body, + request_auth=None + ) -> None: + """Updates header and query params based on authentication setting. + + :param headers: Header parameters dict to be updated. + :param queries: Query parameters tuple list to be updated. + :param auth_settings: Authentication setting identifiers list. + :resource_path: A string representation of the HTTP request resource path. + :method: A string representation of the HTTP request method. + :body: A object representing the body of the HTTP request. + The object type is the return value of sanitize_for_serialization(). + :param request_auth: if set, the provided settings will + override the token in the configuration. + """ + if not auth_settings: + return + + if request_auth: + self._apply_auth_params( + headers, + queries, + resource_path, + method, + body, + request_auth + ) + else: + for auth in auth_settings: + auth_setting = self.configuration.auth_settings().get(auth) + if auth_setting: + self._apply_auth_params( + headers, + queries, + resource_path, + method, + body, + auth_setting + ) + + def _apply_auth_params( + self, + headers, + queries, + resource_path, + method, + body, + auth_setting + ) -> None: + """Updates the request parameters based on a single auth_setting + + :param headers: Header parameters dict to be updated. + :param queries: Query parameters tuple list to be updated. + :resource_path: A string representation of the HTTP request resource path. + :method: A string representation of the HTTP request method. + :body: A object representing the body of the HTTP request. + The object type is the return value of sanitize_for_serialization(). + :param auth_setting: auth settings for the endpoint + """ + if auth_setting['in'] == 'cookie': + headers['Cookie'] = auth_setting['value'] + elif auth_setting['in'] == 'header': + if auth_setting['type'] != 'http-signature': + headers[auth_setting['key']] = auth_setting['value'] + {{#hasHttpSignatureMethods}} + else: + # The HTTP signature scheme requires multiple HTTP headers + # that are calculated dynamically. + signing_info = self.configuration.signing_info + auth_headers = signing_info.get_http_signature_headers( + resource_path, method, headers, body, queries) + headers.update(auth_headers) + {{/hasHttpSignatureMethods}} + elif auth_setting['in'] == 'query': + queries.append((auth_setting['key'], auth_setting['value'])) + else: + raise ApiValueError( + 'Authentication token must be in `query` or `header`' + ) + + def __deserialize_file(self, response): + """Deserializes body to file + + Saves response body into a file in a temporary folder, + using the filename from the `Content-Disposition` header if provided. + + handle file downloading + save response body into a tmp file and return the instance + + :param response: RESTResponse. + :return: file path. + """ + fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path) + os.close(fd) + os.remove(path) + + content_disposition = response.headers.get("Content-Disposition") + if content_disposition: + m = re.search( + r'filename=[\'"]?([^\'"\s]+)[\'"]?', + content_disposition + ) + assert m is not None, "Unexpected 'content-disposition' header value" + filename = m.group(1) + path = os.path.join(os.path.dirname(path), filename) + + with open(path, "wb") as f: + f.write(response.data) + + return path + + def __deserialize_primitive(self, data, klass): + """Deserializes string to primitive type. + + :param data: str. + :param klass: class literal. + + :return: int, long, float, str, bool. + """ + try: + return klass(data) + except UnicodeEncodeError: + return str(data) + except TypeError: + return data + + def __deserialize_object(self, value): + """Return an original value. + + :return: object. + """ + return value + + def __deserialize_date(self, string): + """Deserializes string to date. + + :param string: str. + :return: date. + """ + try: + return parse(string).date() + except ImportError: + return string + except ValueError: + raise rest.ApiException( + status=0, + reason="Failed to parse `{0}` as date object".format(string) + ) + + def __deserialize_datetime(self, string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :return: datetime. + """ + try: + return parse(string) + except ImportError: + return string + except ValueError: + raise rest.ApiException( + status=0, + reason=( + "Failed to parse `{0}` as datetime object" + .format(string) + ) + ) + + def __deserialize_enum(self, data, klass): + """Deserializes primitive type to enum. + + :param data: primitive type. + :param klass: class literal. + :return: enum value. + """ + try: + return klass(data) + except ValueError: + raise rest.ApiException( + status=0, + reason=( + "Failed to parse `{0}` as `{1}`" + .format(data, klass) + ) + ) + + def __deserialize_model(self, data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :param klass: class literal. + :return: model object. + """ + + return klass.from_dict(data) \ No newline at end of file diff --git a/swagger-codegen-templates/ruby/api_client.mustache b/swagger-codegen-templates/ruby/api_client.mustache new file mode 100644 index 0000000..c133925 --- /dev/null +++ b/swagger-codegen-templates/ruby/api_client.mustache @@ -0,0 +1,258 @@ +=begin +{{> api_info}} + +=end + +require 'date' +require 'json' +require 'logger' +require 'tempfile' +require 'time' +{{#isTyphoeus}} +require 'typhoeus' +{{/isTyphoeus}} +{{#isFaraday}} +require 'faraday' +require 'faraday/multipart' if Gem::Version.new(Faraday::VERSION) >= Gem::Version.new('2.0') +require 'marcel' +{{/isFaraday}} +{{#isHttpx}} +require 'httpx' +require 'net/http/status' +{{/isHttpx}} + + +module {{moduleName}} + class ApiClient + # The Configuration object holding settings to be used in the API client. + attr_accessor :config + + # Defines the headers to be used in HTTP requests of all API calls by default. + # + # @return [Hash] + attr_accessor :default_headers + + # Initializes the ApiClient + # @option config [Configuration] Configuration for initializing the object, default to Configuration.default + def initialize(config = Configuration.default) + @config = config + @user_agent = "{{{httpUserAgent}}}{{^httpUserAgent}}OpenAPI-Generator/#{VERSION}/ruby{{/httpUserAgent}}" + @default_headers = { + 'Content-Type' => 'application/json', + # CUSTOM-START + # Set default API version. + 'LD-API-Version' => '{{launchDarklyApiVersion}}', + # CUSTOM-END + 'User-Agent' => @user_agent + } + end + + def self.default + @@default ||= ApiClient.new + end + +{{#isTyphoeus}} +{{> api_client_typhoeus_partial}} + +{{/isTyphoeus}} +{{#isFaraday}} +{{> api_client_faraday_partial}} + +{{/isFaraday}} +{{#isHttpx}} +{{> api_client_httpx_partial}} + +{{/isHttpx}} + # Check if the given MIME is a JSON MIME. + # JSON MIME examples: + # application/json + # application/json; charset=UTF8 + # APPLICATION/JSON + # */* + # @param [String] mime MIME + # @return [Boolean] True if the MIME is application/json + def json_mime?(mime) + (mime == '*/*') || !(mime =~ /^Application\/.*json(?!p)(;.*)?/i).nil? + end + + # Deserialize the response to the given return type. + # + # @param [Response] response HTTP response + # @param [String] return_type some examples: "User", "Array", "Hash" + def deserialize(response, return_type) + body = response.body + return nil if body.nil? || body.empty? + + # return response body directly for String return type + return body.to_s if return_type == 'String' + + # ensuring a default content type + content_type = response.headers['Content-Type'] || 'application/json' + + fail "Content-Type is not supported: #{content_type}" unless json_mime?(content_type) + + begin + data = JSON.parse("[#{body}]", :symbolize_names => true)[0] + rescue JSON::ParserError => e + if %w(String Date Time).include?(return_type) + data = body + else + raise e + end + end + + convert_to_type data, return_type + end + + # Convert data to the given return type. + # @param [Object] data Data to be converted + # @param [String] return_type Return type + # @return [Mixed] Data in a particular type + def convert_to_type(data, return_type) + return nil if data.nil? + case return_type + when 'String' + data.to_s + when 'Integer' + data.to_i + when 'Float' + data.to_f + when 'Boolean' + data == true + when 'Time' + # parse date time (expecting ISO 8601 format) + Time.parse data + when 'Date' + # parse date time (expecting ISO 8601 format) + Date.parse data + when 'Object' + # generic object (usually a Hash), return directly + data + when /\AArray<(.+)>\z/ + # e.g. Array + sub_type = $1 + data.map { |item| convert_to_type(item, sub_type) } + when /\AHash\\z/ + # e.g. Hash + sub_type = $1 + {}.tap do |hash| + data.each { |k, v| hash[k] = convert_to_type(v, sub_type) } + end + else + # models (e.g. Pet) or oneOf/anyOf + klass = {{moduleName}}.const_get(return_type) + if klass.respond_to?(:openapi_one_of) || klass.respond_to?(:openapi_any_of) + klass.build(data) + else + klass.build_from_hash(data) + end + end + end + + # Sanitize filename by removing path. + # e.g. ../../sun.gif becomes sun.gif + # + # @param [String] filename the filename to be sanitized + # @return [String] the sanitized filename + def sanitize_filename(filename) + filename.split(/[\/\\]/).last + end + + def build_request_url(path, opts = {}) + # Add leading and trailing slashes to path + path = "/#{path}".gsub(/\/+/, '/') + @config.base_url(opts[:operation]) + path + end + + # Update header and query params based on authentication settings. + # + # @param [Hash] header_params Header parameters + # @param [Hash] query_params Query parameters + # @param [String] auth_names Authentication scheme name + def update_params_for_auth!(header_params, query_params, auth_names) + Array(auth_names).each do |auth_name| + auth_setting = @config.auth_settings[auth_name] + next unless auth_setting + case auth_setting[:in] + when 'header' then header_params[auth_setting[:key]] = auth_setting[:value] + when 'query' then query_params[auth_setting[:key]] = auth_setting[:value] + else fail ArgumentError, 'Authentication token must be in `query` or `header`' + end + end + end + + # Sets user agent in HTTP header + # + # @param [String] user_agent User agent (e.g. openapi-generator/ruby/1.0.0) + def user_agent=(user_agent) + @user_agent = user_agent + @default_headers['User-Agent'] = @user_agent + end + + # Return Accept header based on an array of accepts provided. + # @param [Array] accepts array for Accept + # @return [String] the Accept header (e.g. application/json) + def select_header_accept(accepts) + return nil if accepts.nil? || accepts.empty? + # use JSON when present, otherwise use all of the provided + json_accept = accepts.find { |s| json_mime?(s) } + json_accept || accepts.join(',') + end + + # Return Content-Type header based on an array of content types provided. + # @param [Array] content_types array for Content-Type + # @return [String] the Content-Type header (e.g. application/json) + def select_header_content_type(content_types) + # return nil by default + return if content_types.nil? || content_types.empty? + # use JSON when present, otherwise use the first one + json_content_type = content_types.find { |s| json_mime?(s) } + json_content_type || content_types.first + end + + # Convert object (array, hash, object, etc) to JSON string. + # @param [Object] model object to be converted into JSON string + # @return [String] JSON string representation of the object + def object_to_http_body(model) + return model if model.nil? || model.is_a?(String) + local_body = nil + if model.is_a?(Array) + local_body = model.map { |m| object_to_hash(m) } + else + local_body = object_to_hash(model) + end + local_body.to_json + end + + # Convert object(non-array) to hash. + # @param [Object] obj object to be converted into JSON string + # @return [String] JSON string representation of the object + def object_to_hash(obj) + if obj.respond_to?(:to_hash) + obj.to_hash + else + obj + end + end + + # Build parameter value according to the given collection format. + # @param [String] collection_format one of :csv, :ssv, :tsv, :pipes and :multi + def build_collection_param(param, collection_format) + case collection_format + when :csv + param.join(',') + when :ssv + param.join(' ') + when :tsv + param.join("\t") + when :pipes + param.join('|') + when :multi + # return the array directly as typhoeus will handle it as expected + param + else + fail "unknown collection format: #{collection_format.inspect}" + end + end + end +end \ No newline at end of file diff --git a/swagger-codegen-templates/typescript/apiInner.mustache b/swagger-codegen-templates/typescript/apiInner.mustache new file mode 100644 index 0000000..55d5590 --- /dev/null +++ b/swagger-codegen-templates/typescript/apiInner.mustache @@ -0,0 +1,470 @@ +{{#withSeparateModelsAndApi}} +/* tslint:disable */ +/* eslint-disable */ +{{>licenseInfo}} + + +import type { Configuration } from '{{apiRelativeToRoot}}configuration{{importFileExtension}}'; +import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +{{#withNodeImports}} +// URLSearchParams not necessarily used +// @ts-ignore +import { URL, URLSearchParams } from 'url'; +{{#multipartFormData}} +import FormData from 'form-data' +{{/multipartFormData}} +{{/withNodeImports}} +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction{{#withAWSV4Signature}}, setAWS4SignatureInterceptor{{/withAWSV4Signature}} } from '{{apiRelativeToRoot}}common{{importFileExtension}}'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError, operationServerMap } from '{{apiRelativeToRoot}}base{{importFileExtension}}'; +{{#imports}} +// @ts-ignore +import type { {{classname}} } from '{{apiRelativeToRoot}}{{tsModelPackage}}{{#importFileExtension}}/index{{importFileExtension}}{{/importFileExtension}}'; +{{/imports}} +{{/withSeparateModelsAndApi}} +{{^withSeparateModelsAndApi}} +{{/withSeparateModelsAndApi}} +{{#operations}} +/** + * {{classname}} - axios parameter creator{{#description}} + * {{&description}}{{/description}} + */ +export const {{classname}}AxiosParamCreator = function (configuration?: Configuration) { + return { + {{#operation}} + /** + * {{¬es}} + {{#summary}} + * @summary {{&summary}} + {{/summary}} + {{#allParams}} + * @param {{=<% %>=}}{<%#isEnum%><%&datatypeWithEnum%><%/isEnum%><%^isEnum%><%&dataType%><%#isNullable%> | null<%/isNullable%><%/isEnum%>}<%={{ }}=%> {{^required}}[{{/required}}{{paramName}}{{^required}}]{{/required}} {{description}} + {{/allParams}} + * @param {*} [options] Override http request option.{{#isDeprecated}} + * @deprecated{{/isDeprecated}} + * @throws {RequiredError} + */ + {{nickname}}: async ({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{#isNullable}} | null{{/isNullable}}{{/isEnum}}, {{/allParams}}options: RawAxiosRequestConfig = {}): Promise => { + {{#allParams}} + {{#required}} + // verify required parameter '{{paramName}}' is not null or undefined + assertParamExists('{{nickname}}', '{{paramName}}', {{paramName}}) + {{/required}} + {{/allParams}} + const localVarPath = `{{{path}}}`{{#pathParams}} + .replace(`{${"{{baseName}}"}}`, encodeURIComponent(String({{paramName}}))){{/pathParams}}; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: '{{httpMethod}}', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any;{{#vendorExtensions}}{{#hasFormParams}} + const localVarFormParams = new {{^multipartFormData}}URLSearchParams(){{/multipartFormData}}{{#multipartFormData}}((configuration && configuration.formDataCtor) || FormData)(){{/multipartFormData}};{{/hasFormParams}}{{/vendorExtensions}} + + {{#authMethods}} + // authentication {{name}} required + {{#isApiKey}} + {{#withAWSV4Signature}} + // aws v4 signature authentication required + await setAWS4SignatureInterceptor(globalAxios, configuration) + {{/withAWSV4Signature}} + {{#isKeyInHeader}} + await setApiKeyToObject(localVarHeaderParameter, "{{keyParamName}}", configuration) + {{/isKeyInHeader}} + {{#isKeyInQuery}} + await setApiKeyToObject(localVarQueryParameter, "{{keyParamName}}", configuration) + {{/isKeyInQuery}} + {{/isApiKey}} + {{#isBasicBasic}} + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + {{/isBasicBasic}} + {{#isBasicBearer}} + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + {{/isBasicBearer}} + {{#isOAuth}} + // oauth required + await setOAuthToObject(localVarHeaderParameter, "{{name}}", [{{#scopes}}"{{{scope}}}"{{^-last}}, {{/-last}}{{/scopes}}], configuration) + {{/isOAuth}} + + {{/authMethods}} + {{#queryParams}} + {{#isArray}} + if ({{paramName}}) { + {{#isCollectionFormatMulti}} + {{#uniqueItems}} + localVarQueryParameter['{{baseName}}'] = Array.from({{paramName}}); + {{/uniqueItems}} + {{^uniqueItems}} + localVarQueryParameter['{{baseName}}'] = {{paramName}}; + {{/uniqueItems}} + {{/isCollectionFormatMulti}} + {{^isCollectionFormatMulti}} + {{#uniqueItems}} + localVarQueryParameter['{{baseName}}'] = Array.from({{paramName}}).join(COLLECTION_FORMATS.{{collectionFormat}}); + {{/uniqueItems}} + {{^uniqueItems}} + localVarQueryParameter['{{baseName}}'] = {{paramName}}.join(COLLECTION_FORMATS.{{collectionFormat}}); + {{/uniqueItems}} + {{/isCollectionFormatMulti}} + } + {{/isArray}} + {{^isArray}} + if ({{paramName}} !== undefined) { + {{#isDateTime}} + localVarQueryParameter['{{baseName}}'] = ({{paramName}} as any instanceof Date) ? + ({{paramName}} as any).toISOString() : + {{paramName}}; + {{/isDateTime}} + {{^isDateTime}} + {{#isDate}} + localVarQueryParameter['{{baseName}}'] = ({{paramName}} as any instanceof Date) ? + ({{paramName}} as any).toISOString().substring(0,10) : + {{paramName}}; + {{/isDate}} + {{^isDate}} + {{#isExplode}} + {{#isPrimitiveType}} + localVarQueryParameter['{{baseName}}'] = {{paramName}}; + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{^isEnumRef}} + {{^isEnum}} + for (const [key, value] of Object.entries({{paramName}})) { + localVarQueryParameter[key] = value; + } + {{/isEnum}} + {{/isEnumRef}} + {{#isEnum}} + localVarQueryParameter['{{baseName}}'] = {{paramName}}; + {{/isEnum}} + {{#isEnumRef}} + localVarQueryParameter['{{baseName}}'] = {{paramName}}; + {{/isEnumRef}} + {{/isPrimitiveType}} + {{/isExplode}} + {{^isExplode}} + localVarQueryParameter['{{baseName}}'] = {{paramName}}; + {{/isExplode}} + {{/isDate}} + {{/isDateTime}} + } + {{/isArray}} + + {{/queryParams}} + {{#vendorExtensions}} + {{#formParams}} + {{#isArray}} + if ({{paramName}}) { + {{#isCollectionFormatMulti}} + {{#contentType}} + localVarFormParams.append('{{baseName}}', new Blob([JSON.stringify({{paramName}})], { type: "{{contentType}}", })); + {{/contentType}} + {{^contentType}} + {{paramName}}.forEach((element) => { + localVarFormParams.{{#multipartFormData}}append{{/multipartFormData}}{{^multipartFormData}}set{{/multipartFormData}}('{{baseName}}{{#useSquareBracketsInArrayNames}}[]{{/useSquareBracketsInArrayNames}}', element as any); + }) + {{/contentType}} + {{/isCollectionFormatMulti}} + {{^isCollectionFormatMulti}} + localVarFormParams.{{#multipartFormData}}append{{/multipartFormData}}{{^multipartFormData}}set{{/multipartFormData}}('{{baseName}}{{#useSquareBracketsInArrayNames}}[]{{/useSquareBracketsInArrayNames}}', {{paramName}}.join(COLLECTION_FORMATS.{{collectionFormat}})); + {{/isCollectionFormatMulti}} + }{{/isArray}} + {{^isArray}} + if ({{paramName}} !== undefined) { {{^multipartFormData}} + localVarFormParams.set('{{baseName}}', {{paramName}} as any);{{/multipartFormData}}{{#multipartFormData}}{{#isPrimitiveType}}{{^isBoolean}} + localVarFormParams.append('{{baseName}}', {{paramName}} as any);{{/isBoolean}}{{/isPrimitiveType}}{{#isPrimitiveType}}{{#isBoolean}} + localVarFormParams.append('{{baseName}}', String({{paramName}}) as any);{{/isBoolean}}{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isEnumRef}} + localVarFormParams.append('{{baseName}}', {{paramName}} as any);{{/isEnumRef}}{{^isEnumRef}} + localVarFormParams.append('{{baseName}}', new Blob([JSON.stringify({{paramName}})], { type: "application/json", }));{{/isEnumRef}}{{/isPrimitiveType}}{{/multipartFormData}} + }{{/isArray}} + {{/formParams}} + {{/vendorExtensions}} + {{#vendorExtensions}} + {{#hasFormParams}} + {{^multipartFormData}} + localVarHeaderParameter['Content-Type'] = 'application/x-www-form-urlencoded'; + {{/multipartFormData}} + {{#multipartFormData}} + localVarHeaderParameter['Content-Type'] = 'multipart/form-data'; + {{/multipartFormData}} + {{/hasFormParams}} + {{/vendorExtensions}} + {{#bodyParam}} + {{^consumes}} + localVarHeaderParameter['Content-Type'] = 'application/json'; + {{/consumes}} + {{#consumes.0}} + localVarHeaderParameter['Content-Type'] = '{{{mediaType}}}'; + {{/consumes.0}} + {{/bodyParam}} + {{#hasProduces}} + localVarHeaderParameter['Accept'] = '{{#produces}}{{{mediaType}}}{{^-last}},{{/-last}}{{/produces}}'; + {{/hasProduces}} + + // CUSTOM-START + // Set default API version. + localVarHeaderParameter['LD-API-Version'] = '{{launchDarklyApiVersion}}'; + // Set a custom LaunchDarkly user agent header. + localVarHeaderParameter['X-LaunchDarkly-User-Agent'] = 'OpenAPI-Generator/{{npmVersion}}/typescript'; + // CUSTOM-END + + {{#headerParams}} + {{#isArray}} + if ({{paramName}}) { + {{#uniqueItems}} + let mapped = Array.from({{paramName}}).map(value => ("{{{dataType}}}" !== "Set") ? JSON.stringify(value) : (value || "")); + {{/uniqueItems}} + {{^uniqueItems}} + let mapped = {{paramName}}.map(value => ("{{{dataType}}}" !== "Array") ? JSON.stringify(value) : (value || "")); + {{/uniqueItems}} + localVarHeaderParameter['{{baseName}}'] = mapped.join(COLLECTION_FORMATS["{{collectionFormat}}"]); + } + {{/isArray}} + {{^isArray}} + {{! `val == null` covers for both `null` and `undefined`}} + if ({{paramName}} != null) { + {{#isString}} + localVarHeaderParameter['{{baseName}}'] = String({{paramName}}); + {{/isString}} + {{^isString}} + {{! isString is falsy also for $ref that defines a string or enum type}} + localVarHeaderParameter['{{baseName}}'] = typeof {{paramName}} === 'string' + ? {{paramName}} + : JSON.stringify({{paramName}}); + {{/isString}} + } + {{/isArray}} + {{/headerParams}} + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions,{{#hasFormParams}}{{#multipartFormData}} ...(localVarFormParams as any).getHeaders?.(),{{/multipartFormData}}{{/hasFormParams}} ...options.headers}; + {{#hasFormParams}} + localVarRequestOptions.data = localVarFormParams{{#vendorExtensions}}{{^multipartFormData}}.toString(){{/multipartFormData}}{{/vendorExtensions}}; + {{/hasFormParams}} + {{#bodyParam}} + localVarRequestOptions.data = serializeDataIfNeeded({{paramName}}, localVarRequestOptions, configuration) + {{/bodyParam}} + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + {{/operation}} + } +}; + +/** + * {{classname}} - functional programming interface{{#description}} + * {{{.}}}{{/description}} + */ +export const {{classname}}Fp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = {{classname}}AxiosParamCreator(configuration) + return { + {{#operation}} + /** + * {{¬es}} + {{#summary}} + * @summary {{&summary}} + {{/summary}} + {{#allParams}} + * @param {{=<% %>=}}{<%#isEnum%><%&datatypeWithEnum%><%/isEnum%><%^isEnum%><%&dataType%><%#isNullable%> | null<%/isNullable%><%/isEnum%>}<%={{ }}=%> {{^required}}[{{/required}}{{paramName}}{{^required}}]{{/required}} {{description}} + {{/allParams}} + * @param {*} [options] Override http request option.{{#isDeprecated}} + * @deprecated{{/isDeprecated}} + * @throws {RequiredError} + */ + async {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{#isNullable}} | null{{/isNullable}}{{/isEnum}}, {{/allParams}}options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<{{{returnType}}}{{^returnType}}void{{/returnType}}>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['{{classname}}.{{nickname}}']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + {{/operation}} + } +}; + +/** + * {{classname}} - factory interface{{#description}} + * {{&description}}{{/description}} + */ +export const {{classname}}Factory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = {{classname}}Fp(configuration) + return { + {{#operation}} + /** + * {{¬es}} + {{#summary}} + * @summary {{&summary}} + {{/summary}} + {{#useSingleRequestParameter}} + {{#allParams.0}} + * @param {{=<% %>=}}{<%& classname %><%& operationIdCamelCase %>Request}<%={{ }}=%> requestParameters Request parameters. + {{/allParams.0}} + {{/useSingleRequestParameter}} + {{^useSingleRequestParameter}} + {{#allParams}} + * @param {{=<% %>=}}{<%#isEnum%><%&datatypeWithEnum%><%/isEnum%><%^isEnum%><%&dataType%><%#isNullable%> | null<%/isNullable%><%/isEnum%>}<%={{ }}=%> {{^required}}[{{/required}}{{paramName}}{{^required}}]{{/required}} {{description}} + {{/allParams}} + {{/useSingleRequestParameter}} + * @param {*} [options] Override http request option.{{#isDeprecated}} + * @deprecated{{/isDeprecated}} + * @throws {RequiredError} + */ + {{#useSingleRequestParameter}} + {{nickname}}({{#allParams.0}}requestParameters: {{classname}}{{operationIdCamelCase}}Request{{^hasRequiredParams}} = {}{{/hasRequiredParams}}, {{/allParams.0}}options?: RawAxiosRequestConfig): AxiosPromise<{{{returnType}}}{{^returnType}}void{{/returnType}}> { + return localVarFp.{{nickname}}({{#allParams.0}}{{#allParams}}requestParameters.{{paramName}}, {{/allParams}}{{/allParams.0}}options).then((request) => request(axios, basePath)); + }, + {{/useSingleRequestParameter}} + {{^useSingleRequestParameter}} + {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{#isNullable}} | null{{/isNullable}}{{/isEnum}}, {{/allParams}}options?: RawAxiosRequestConfig): AxiosPromise<{{{returnType}}}{{^returnType}}void{{/returnType}}> { + return localVarFp.{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}options).then((request) => request(axios, basePath)); + }, + {{/useSingleRequestParameter}} + {{/operation}} + }; +}; + +{{#withInterfaces}} +/** + * {{classname}} - interface{{#description}} + * {{&description}}{{/description}} + */ +export interface {{classname}}Interface { +{{#operation}} + /** + * {{¬es}} + {{#summary}} + * @summary {{&summary}} + {{/summary}} + {{#useSingleRequestParameter}} + {{#allParams.0}} + * @param {{=<% %>=}}{<%& classname %><%& operationIdCamelCase %>Request}<%={{ }}=%> requestParameters Request parameters. + {{/allParams.0}} + {{/useSingleRequestParameter}} + {{^useSingleRequestParameter}} + {{#allParams}} + * @param {{=<% %>=}}{<%#isEnum%><%&datatypeWithEnum%><%/isEnum%><%^isEnum%><%&dataType%><%#isNullable%> | null<%/isNullable%><%/isEnum%>}<%={{ }}=%> {{^required}}[{{/required}}{{paramName}}{{^required}}]{{/required}} {{description}} + {{/allParams}} + {{/useSingleRequestParameter}} + * @param {*} [options] Override http request option.{{#isDeprecated}} + * @deprecated{{/isDeprecated}} + * @throws {RequiredError} + */ + {{#useSingleRequestParameter}} + {{nickname}}({{#allParams.0}}requestParameters{{^hasRequiredParams}}?{{/hasRequiredParams}}: {{classname}}{{operationIdCamelCase}}Request, {{/allParams.0}}options?: RawAxiosRequestConfig): AxiosPromise<{{{returnType}}}{{^returnType}}void{{/returnType}}>; + {{/useSingleRequestParameter}} + {{^useSingleRequestParameter}} + {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{#isNullable}} | null{{/isNullable}}{{/isEnum}}, {{/allParams}}options?: RawAxiosRequestConfig): AxiosPromise<{{{returnType}}}{{^returnType}}void{{/returnType}}>; + {{/useSingleRequestParameter}} + +{{/operation}} +} + +{{/withInterfaces}} +{{#useSingleRequestParameter}} +{{#operation}} +{{#allParams.0}} +/** + * Request parameters for {{nickname}} operation in {{classname}}. + */ +export interface {{classname}}{{operationIdCamelCase}}Request { + {{#allParams}} + {{#description}} + /** + * {{description}} + */ + {{/description}} + readonly {{paramName}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{#isNullable}} | null{{/isNullable}}{{/isEnum}} + {{^-last}} + + {{/-last}} + {{/allParams}} +} + +{{/allParams.0}} +{{/operation}} +{{/useSingleRequestParameter}} +/** + * {{classname}} - object-oriented interface{{#description}} + * {{{.}}}{{/description}} + */ +{{#withInterfaces}} +export class {{classname}} extends BaseAPI implements {{classname}}Interface { +{{/withInterfaces}} +{{^withInterfaces}} +export class {{classname}} extends BaseAPI { +{{/withInterfaces}} + {{#operation}} + /** + * {{¬es}} + {{#summary}} + * @summary {{&summary}} + {{/summary}} + {{#useSingleRequestParameter}} + {{#allParams.0}} + * @param {{=<% %>=}}{<%& classname %><%& operationIdCamelCase %>Request}<%={{ }}=%> requestParameters Request parameters. + {{/allParams.0}} + {{/useSingleRequestParameter}} + {{^useSingleRequestParameter}} + {{#allParams}} + * @param {{=<% %>=}}{<%#isEnum%><%&datatypeWithEnum%><%/isEnum%><%^isEnum%><%&dataType%><%#isNullable%> | null<%/isNullable%><%/isEnum%>}<%={{ }}=%> {{^required}}[{{/required}}{{paramName}}{{^required}}]{{/required}} {{description}} + {{/allParams}} + {{/useSingleRequestParameter}} + * @param {*} [options] Override http request option.{{#isDeprecated}} + * @deprecated{{/isDeprecated}} + * @throws {RequiredError} + */ + {{#useSingleRequestParameter}} + public {{nickname}}({{#allParams.0}}requestParameters: {{classname}}{{operationIdCamelCase}}Request{{^hasRequiredParams}} = {}{{/hasRequiredParams}}, {{/allParams.0}}options?: RawAxiosRequestConfig) { + return {{classname}}Fp(this.configuration).{{nickname}}({{#allParams.0}}{{#allParams}}requestParameters.{{paramName}}, {{/allParams}}{{/allParams.0}}options).then((request) => request(this.axios, this.basePath)); + } + {{/useSingleRequestParameter}} + {{^useSingleRequestParameter}} + public {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{#isNullable}} | null{{/isNullable}}{{/isEnum}}, {{/allParams}}options?: RawAxiosRequestConfig) { + return {{classname}}Fp(this.configuration).{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}options).then((request) => request(this.axios, this.basePath)); + } + {{/useSingleRequestParameter}} + {{^-last}} + + {{/-last}} + {{/operation}} +} +{{/operations}} + +{{#operations}} +{{#operation}} +{{#allParams}} +{{#isEnum}} +{{#stringEnums}} +export enum {{operationIdCamelCase}}{{enumName}} { +{{#allowableValues}} + {{#enumVars}} + {{{name}}} = {{{value}}}{{^-last}},{{/-last}} + {{/enumVars}} +{{/allowableValues}} +} +{{/stringEnums}} +{{^stringEnums}} +export const {{operationIdCamelCase}}{{enumName}} = { +{{#allowableValues}} + {{#enumVars}} + {{{name}}}: {{{value}}}{{^-last}},{{/-last}} + {{/enumVars}} +{{/allowableValues}} +} as const; +export type {{operationIdCamelCase}}{{enumName}} = typeof {{operationIdCamelCase}}{{enumName}}[keyof typeof {{operationIdCamelCase}}{{enumName}}]; +{{/stringEnums}} +{{/isEnum}} +{{/allParams}} +{{/operation}} +{{/operations}} \ No newline at end of file