From 0deeef8afb8b6fd0d9be45ce76e8c15bff90c318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=9A=80=20Jack?= Date: Wed, 30 Oct 2024 12:13:58 +1100 Subject: [PATCH] fix(type-safe-api): remove imports for self-referential models Models which referenced themselves would previously attempt to import themselves too which produced code which did not compile. --- .../type-safe-api/generators/generate-next.ts | 2 +- packages/type-safe-api/src/project/types.ts | 2 +- .../__snapshots__/java.test.ts.snap | 5242 ++++++++++++++++- .../__snapshots__/python.test.ts.snap | 3127 +++++++++- .../__snapshots__/typescript.test.ts.snap | 1320 ++++- .../test/scripts/generators/java.test.ts | 1 + .../test/scripts/generators/python.test.ts | 1 + .../scripts/generators/typescript.test.ts | 1 + 8 files changed, 9685 insertions(+), 11 deletions(-) diff --git a/packages/type-safe-api/scripts/type-safe-api/generators/generate-next.ts b/packages/type-safe-api/scripts/type-safe-api/generators/generate-next.ts index 423529e86..b4fcba49b 100755 --- a/packages/type-safe-api/scripts/type-safe-api/generators/generate-next.ts +++ b/packages/type-safe-api/scripts/type-safe-api/generators/generate-next.ts @@ -958,7 +958,7 @@ const buildData = async (inSpec: OpenAPIV3.Document, metadata: any) => { ...model.imports, // Include property imports, if any ...model.properties.filter(p => p.export === "reference").map(p => p.type), - ])); + ])).filter(modelImport => modelImport !== model.name); // Filter out self for recursive model references // Add deprecated flag if present (model as any).deprecated = specModel.deprecated || false; diff --git a/packages/type-safe-api/src/project/types.ts b/packages/type-safe-api/src/project/types.ts index 36d0da695..58df0c14e 100644 --- a/packages/type-safe-api/src/project/types.ts +++ b/packages/type-safe-api/src/project/types.ts @@ -10,9 +10,9 @@ import { OpenApiAsyncModelProject } from "./model/openapi/open-api-async-model-p import { OpenApiModelProject } from "./model/openapi/open-api-model-project"; import { SmithyAsyncModelProject } from "./model/smithy/smithy-async-model-project"; import { SmithyModelProject } from "./model/smithy/smithy-model-project"; +import { SmithyProjectDefinitionOptions } from "./model/smithy/smithy-project-definition"; import { TypeSpecAsyncModelProject } from "./model/type-spec/type-spec-async-model-project"; import { TypeSpecModelProject } from "./model/type-spec/type-spec-model-project"; -import { SmithyProjectDefinitionOptions } from "./model/smithy/smithy-project-definition"; import { PythonProjectOptions } from "./python-project-options"; import { TypeScriptProjectOptions } from "./typescript-project-options"; diff --git a/packages/type-safe-api/test/scripts/generators/__snapshots__/java.test.ts.snap b/packages/type-safe-api/test/scripts/generators/__snapshots__/java.test.ts.snap index 31879aaa5..5d196168a 100644 --- a/packages/type-safe-api/test/scripts/generators/__snapshots__/java.test.ts.snap +++ b/packages/type-safe-api/test/scripts/generators/__snapshots__/java.test.ts.snap @@ -49842,7 +49842,6 @@ package test.test.runtime.model; import java.util.Objects; import java.util.Arrays; -import test.test.runtime.model.HelloResponse; import com.google.gson.TypeAdapter; import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; @@ -50094,6 +50093,5247 @@ public class HelloResponse { } `; +exports[`Java Client Code Generation Script Unit Tests Generates With recursive.yaml 1`] = ` +{ + ".tsapi-manifest": "src/main/java/test/test/runtime/api/handlers/Handlers.java +src/main/java/test/test/runtime/api/handlers/Response.java +src/main/java/test/test/runtime/api/handlers/ApiResponse.java +src/main/java/test/test/runtime/api/handlers/Interceptor.java +src/main/java/test/test/runtime/api/handlers/Interceptors.java +src/main/java/test/test/runtime/api/handlers/HandlerChain.java +src/main/java/test/test/runtime/api/handlers/RequestInput.java +src/main/java/test/test/runtime/api/handlers/ChainedRequestInput.java +src/main/java/test/test/runtime/api/handlers/InterceptorWarmupChainedRequestInput.java +src/main/java/test/test/runtime/api/handlers/InterceptorWithWarmup.java +src/main/java/test/test/runtime/api/handlers/get_tree/GetTreeResponse.java +src/main/java/test/test/runtime/api/handlers/get_tree/GetTree200Response.java +src/main/java/test/test/runtime/api/handlers/get_tree/GetTreeRequestParameters.java +src/main/java/test/test/runtime/api/handlers/get_tree/GetTreeInput.java +src/main/java/test/test/runtime/api/handlers/get_tree/GetTreeRequestInput.java +src/main/java/test/test/runtime/api/handlers/get_tree/GetTree.java +src/main/java/test/test/runtime/api/handlers/HandlerRouter.java +src/main/java/test/test/runtime/api/interceptors/TryCatchInterceptor.java +src/main/java/test/test/runtime/api/interceptors/ResponseHeadersInterceptor.java +src/main/java/test/test/runtime/api/interceptors/powertools/LoggingInterceptor.java +src/main/java/test/test/runtime/api/interceptors/powertools/TracingInterceptor.java +src/main/java/test/test/runtime/api/interceptors/powertools/MetricsInterceptor.java +src/main/java/test/test/runtime/api/interceptors/DefaultInterceptors.java +src/main/java/test/test/runtime/api/operation_config/OperationConfig.java +src/main/java/test/test/runtime/api/operation_config/OperationLookup.java +src/main/java/test/test/runtime/api/operation_config/Operations.java +src/main/java/test/test/runtime/api/DefaultApi.java +src/main/java/test/test/runtime/auth/ApiKeyAuth.java +src/main/java/test/test/runtime/auth/Authentication.java +src/main/java/test/test/runtime/auth/HttpBasicAuth.java +src/main/java/test/test/runtime/auth/HttpBearerAuth.java +src/main/java/test/test/runtime/ApiCallback.java +src/main/java/test/test/runtime/ApiClient.java +src/main/java/test/test/runtime/ApiException.java +src/main/java/test/test/runtime/ApiResponse.java +src/main/java/test/test/runtime/Configuration.java +src/main/java/test/test/runtime/GzipRequestInterceptor.java +src/main/java/test/test/runtime/JSON.java +src/main/java/test/test/runtime/Pair.java +src/main/java/test/test/runtime/ProgressRequestBody.java +src/main/java/test/test/runtime/ProgressResponseBody.java +src/main/java/test/test/runtime/ServerConfiguration.java +src/main/java/test/test/runtime/ServerVariable.java +src/main/java/test/test/runtime/StringUtil.java +src/main/java/test/test/runtime/model/AbstractOpenApiSchema.java +src/main/java/test/test/runtime/model/TreeNode.java", + "src/main/java/test/test/runtime/ApiCallback.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime; + +import java.io.IOException; + +import java.util.Map; +import java.util.List; + +/** + * Callback for asynchronous API call. + * + * @param The return type + */ +public interface ApiCallback { + /** + * This is called when the API call fails. + * + * @param e The exception causing the failure + * @param statusCode Status code of the response if available, otherwise it would be 0 + * @param responseHeaders Headers of the response if available, otherwise it would be null + */ + void onFailure(ApiException e, int statusCode, Map> responseHeaders); + + /** + * This is called when the API call succeeded. + * + * @param result The result deserialized from response + * @param statusCode Status code of the response + * @param responseHeaders Headers of the response + */ + void onSuccess(T result, int statusCode, Map> responseHeaders); + + /** + * This is called when the API upload processing. + * + * @param bytesWritten bytes Written + * @param contentLength content length of request body + * @param done write end + */ + void onUploadProgress(long bytesWritten, long contentLength, boolean done); + + /** + * This is called when the API download processing. + * + * @param bytesRead bytes Read + * @param contentLength content length of the response + * @param done Read end + */ + void onDownloadProgress(long bytesRead, long contentLength, boolean done); +} +", + "src/main/java/test/test/runtime/ApiClient.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime; + +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; + +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; +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import test.test.runtime.auth.Authentication; +import test.test.runtime.auth.HttpBasicAuth; +import test.test.runtime.auth.HttpBearerAuth; +import test.test.runtime.auth.ApiKeyAuth; + +/** + *

ApiClient class.

+ */ +public class ApiClient { + + private String basePath = "http://localhost"; + protected List servers = new ArrayList(Arrays.asList( + new ServerConfiguration( + "", + "No description provided", + new HashMap() + ) + )); + protected Integer serverIndex = 0; + protected Map serverVariables = null; + private boolean debugging = false; + private Map defaultHeaderMap = new HashMap(); + private Map defaultCookieMap = new HashMap(); + private String tempFolderPath = null; + + private Map authentications; + + private DateFormat dateFormat; + private DateFormat datetimeFormat; + private boolean lenientDatetimeFormat; + private int dateLength; + + private InputStream sslCaCert; + private boolean verifyingSsl; + private KeyManager[] keyManagers; + + private OkHttpClient httpClient; + private JSON json; + + private HttpLoggingInterceptor loggingInterceptor; + + /** + * Basic constructor for ApiClient + */ + public ApiClient() { + init(); + initHttpClient(); + + // Setup authentications (key: authentication name, value: authentication). + // 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). + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + } + + private void initHttpClient() { + initHttpClient(Collections.emptyList()); + } + + private void initHttpClient(List interceptors) { + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.addNetworkInterceptor(getProgressInterceptor()); + for (Interceptor interceptor: interceptors) { + builder.addInterceptor(interceptor); + } + + httpClient = builder.build(); + } + + private void init() { + verifyingSsl = true; + + json = new JSON(); + + // Set default User-Agent. + setUserAgent("OpenAPI-Generator/0.0.0/java"); + + authentications = new HashMap(); + } + + /** + * Get base path + * + * @return Base path + */ + public String getBasePath() { + return basePath; + } + + /** + * Set base path + * + * @param basePath Base path of the URL (e.g http://localhost + * @return An instance of OkHttpClient + */ + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + 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 Api Client + * @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; + } + + /** + *

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 test.test.runtime.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 test.test.runtime.ApiClient} object + */ + public ApiClient setSqlDateFormat(DateFormat dateFormat) { + JSON.setSqlDateFormat(dateFormat); + return this; + } + + /** + *

Set OffsetDateTimeFormat.

+ * + * @param dateFormat a {@link java.time.format.DateTimeFormatter} object + * @return a {@link test.test.runtime.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 test.test.runtime.ApiClient} object + */ + public ApiClient setLocalDateFormat(DateTimeFormatter dateFormat) { + JSON.setLocalDateFormat(dateFormat); + return this; + } + + /** + *

Set LenientOnJson.

+ * + * @param lenientOnJson a boolean + * @return a {@link test.test.runtime.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); + } + + + /** + * 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) { + 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) { + 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; + } + + + /** + * 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 || param instanceof OffsetDateTime || param instanceof LocalDate) { + //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; + } + + /** + * 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; + } + + /** + * 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.replaceAll(".*[/\\\\\\\\]", ""); + } + + /** + * 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 test.test.runtime.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); + } + + String respBody; + try { + if (response.body() != null) + respBody = response.body().string(); + else + respBody = null; + } catch (IOException e) { + throw new ApiException(e); + } + + if (respBody == null || "".equals(respBody)) { + return null; + } + + String contentType = response.headers().get("Content-Type"); + if (contentType == null) { + // ensuring a default content type + contentType = "application/json"; + } + if (isJsonMime(contentType)) { + return JSON.deserialize(respBody, returnType); + } else if (returnType.equals(String.class)) { + // Expecting string, return the raw response body. + return (T) respBody; + } else { + throw new ApiException( + "Content type \\"" + contentType + "\\" is not supported for type: " + returnType, + response.code(), + response.headers().toMultimap(), + respBody); + } + } + + /** + * 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 test.test.runtime.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 test.test.runtime.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 test.test.runtime.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 test.test.runtime.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); + } + } + + /** + * {@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 test.test.runtime.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 test.test.runtime.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 test.test.runtime.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 { + // aggregate queryParams (non-collection) and collectionQueryParams into allQueryParams + List allQueryParams = new ArrayList(queryParams); + allQueryParams.addAll(collectionQueryParams); + + final String url = buildUrl(baseUrl, path, queryParams, collectionQueryParams); + + // prepare HTTP request body + RequestBody reqBody; + String contentType = headerParams.get("Content-Type"); + + if (!HttpMethod.permitsRequestBody(method)) { + reqBody = null; + } else if ("application/x-www-form-urlencoded".equals(contentType)) { + reqBody = buildRequestBodyFormEncoding(formParams); + } else if ("multipart/form-data".equals(contentType)) { + 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); + } + + // update parameters with authentication settings + updateParamsForAuth(authNames, allQueryParams, headerParams, cookieParams, requestBodyToString(reqBody), method, URI.create(url)); + + final Request.Builder reqBuilder = new Request.Builder().url(url); + 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( + "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("%s=%s", param.getKey(), param.getValue())); + } + for (Entry param : defaultCookieMap.entrySet()) { + if (!cookieParams.containsKey(param.getKey())) { + reqBuilder.addHeader("Cookie", String.format("%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 test.test.runtime.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 + */ + private 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 + */ + private 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. + */ + private 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. + */ + private 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(); + 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); + } + } + + private 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); + } + } + + /** + * Convert the HTTP request body to a string. + * + * @param requestBody The HTTP request object + * @return The string representation of the HTTP request body + * @throws test.test.runtime.ApiException If fail to serialize the request body object into a string + */ + private 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 ""; + } +} +", + "src/main/java/test/test/runtime/ApiException.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime; + +import java.util.Map; +import java.util.List; + +import javax.ws.rs.core.GenericType; + +/** + *

ApiException class.

+ */ +@SuppressWarnings("serial") +public class ApiException extends Exception { + private int code = 0; + private Map> responseHeaders = null; + private String responseBody = null; + + /** + *

Constructor for ApiException.

+ */ + public ApiException() {} + + /** + *

Constructor for ApiException.

+ * + * @param throwable a {@link java.lang.Throwable} object + */ + public ApiException(Throwable throwable) { + super(throwable); + } + + /** + *

Constructor for ApiException.

+ * + * @param message the error message + */ + public ApiException(String message) { + super(message); + } + + /** + *

Constructor for ApiException.

+ * + * @param message the error message + * @param throwable a {@link java.lang.Throwable} object + * @param code HTTP status code + * @param responseHeaders a {@link java.util.Map} of HTTP response headers + * @param responseBody the response body + */ + public ApiException(String message, Throwable throwable, int code, Map> responseHeaders, String responseBody) { + super(message, throwable); + this.code = code; + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + /** + *

Constructor for ApiException.

+ * + * @param message the error message + * @param code HTTP status code + * @param responseHeaders a {@link java.util.Map} of HTTP response headers + * @param responseBody the response body + */ + public ApiException(String message, int code, Map> responseHeaders, String responseBody) { + this(message, (Throwable) null, code, responseHeaders, responseBody); + } + + /** + *

Constructor for ApiException.

+ * + * @param message the error message + * @param throwable a {@link java.lang.Throwable} object + * @param code HTTP status code + * @param responseHeaders a {@link java.util.Map} of HTTP response headers + */ + public ApiException(String message, Throwable throwable, int code, Map> responseHeaders) { + this(message, throwable, code, responseHeaders, null); + } + + /** + *

Constructor for ApiException.

+ * + * @param code HTTP status code + * @param responseHeaders a {@link java.util.Map} of HTTP response headers + * @param responseBody the response body + */ + public ApiException(int code, Map> responseHeaders, String responseBody) { + this("Response Code: " + code + " Response Body: " + responseBody, (Throwable) null, code, responseHeaders, responseBody); + } + + /** + *

Constructor for ApiException.

+ * + * @param code HTTP status code + * @param message a {@link java.lang.String} object + */ + public ApiException(int code, String message) { + super(message); + this.code = code; + } + + /** + *

Constructor for ApiException.

+ * + * @param code HTTP status code + * @param message the error message + * @param responseHeaders a {@link java.util.Map} of HTTP response headers + * @param responseBody the response body + */ + public ApiException(int code, String message, Map> responseHeaders, String responseBody) { + this(code, message); + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + /** + * Get the HTTP status code. + * + * @return HTTP status code + */ + public int getCode() { + return code; + } + + /** + * Get the HTTP response headers. + * + * @return A map of list of string + */ + public Map> getResponseHeaders() { + return responseHeaders; + } + + /** + * Get the HTTP response body. + * + * @return Response body in the form of string + */ + public String getResponseBody() { + return responseBody; + } + + /** + * Get the exception message including HTTP response data. + * + * @return The exception message + */ + public String getMessage() { + return String.format("Message: %s%nHTTP response code: %s%nHTTP response body: %s%nHTTP response headers: %s", + super.getMessage(), this.getCode(), this.getResponseBody(), this.getResponseHeaders()); + } +} +", + "src/main/java/test/test/runtime/ApiResponse.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime; + +import java.util.List; +import java.util.Map; + +/** + * API response returned by API call. + */ +public class ApiResponse { + final private int statusCode; + final private Map> headers; + final private T data; + + /** + *

Constructor for ApiResponse.

+ * + * @param statusCode The status code of HTTP response + * @param headers The headers of HTTP response + */ + public ApiResponse(int statusCode, Map> headers) { + this(statusCode, headers, null); + } + + /** + *

Constructor for ApiResponse.

+ * + * @param statusCode The status code of HTTP response + * @param headers The headers of HTTP response + * @param data The object deserialized from response bod + */ + public ApiResponse(int statusCode, Map> headers, T data) { + this.statusCode = statusCode; + this.headers = headers; + this.data = data; + } + + /** + *

Get the status code.

+ * + * @return the status code + */ + public int getStatusCode() { + return statusCode; + } + + /** + *

Get the headers.

+ * + * @return a {@link java.util.Map} of headers + */ + public Map> getHeaders() { + return headers; + } + + /** + *

Get the data.

+ * + * @return the data + */ + public T getData() { + return data; + } +} +", + "src/main/java/test/test/runtime/Configuration.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime; + +public class Configuration { + private static ApiClient defaultApiClient = new ApiClient(); + + /** + * Get the default API client, which would be used when creating API + * instances without providing an API client. + * + * @return Default API client + */ + public static ApiClient getDefaultApiClient() { + return defaultApiClient; + } + + /** + * Set the default API client, which would be used when creating API + * instances without providing an API client. + * + * @param apiClient API client + */ + public static void setDefaultApiClient(ApiClient apiClient) { + defaultApiClient = apiClient; + } +} +", + "src/main/java/test/test/runtime/GzipRequestInterceptor.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime; + +import okhttp3.*; +import okio.Buffer; +import okio.BufferedSink; +import okio.GzipSink; +import okio.Okio; + +import java.io.IOException; + +/** + * Encodes request bodies using gzip. + * + * Taken from https://github.com/square/okhttp/issues/350 + */ +class GzipRequestInterceptor implements Interceptor { + @Override + public Response intercept(Chain chain) throws IOException { + Request originalRequest = chain.request(); + if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) { + return chain.proceed(originalRequest); + } + + Request compressedRequest = originalRequest.newBuilder() + .header("Content-Encoding", "gzip") + .method(originalRequest.method(), forceContentLength(gzip(originalRequest.body()))) + .build(); + return chain.proceed(compressedRequest); + } + + private RequestBody forceContentLength(final RequestBody requestBody) throws IOException { + final Buffer buffer = new Buffer(); + requestBody.writeTo(buffer); + return new RequestBody() { + @Override + public MediaType contentType() { + return requestBody.contentType(); + } + + @Override + public long contentLength() { + return buffer.size(); + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + sink.write(buffer.snapshot()); + } + }; + } + + private RequestBody gzip(final RequestBody body) { + return new RequestBody() { + @Override + public MediaType contentType() { + return body.contentType(); + } + + @Override + public long contentLength() { + return -1; // We don't know the compressed length in advance! + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + BufferedSink gzipSink = Okio.buffer(new GzipSink(sink)); + body.writeTo(gzipSink); + gzipSink.close(); + } + }; + } +} +", + "src/main/java/test/test/runtime/JSON.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapter; +import com.google.gson.internal.bind.util.ISO8601Utils; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.google.gson.JsonElement; +import io.gsonfire.GsonFireBuilder; +import io.gsonfire.TypeSelector; + +import okio.ByteString; + +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.Type; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.Locale; +import java.util.Map; +import java.util.HashMap; + +/* + * A JSON utility class + * + * NOTE: in the future, this class may be converted to static, which may break + * backward-compatibility + */ +public class JSON { + private static Gson gson; + private static boolean isLenientOnJson = false; + private static DateTypeAdapter dateTypeAdapter = new DateTypeAdapter(); + private static SqlDateTypeAdapter sqlDateTypeAdapter = new SqlDateTypeAdapter(); + private static OffsetDateTimeTypeAdapter offsetDateTimeTypeAdapter = new OffsetDateTimeTypeAdapter(); + private static LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter(); + private static ByteArrayAdapter byteArrayAdapter = new ByteArrayAdapter(); + + @SuppressWarnings("unchecked") + public static GsonBuilder createGson() { + GsonFireBuilder fireBuilder = new GsonFireBuilder() + ; + GsonBuilder builder = fireBuilder.createGsonBuilder(); + return builder; + } + + private static String getDiscriminatorValue(JsonElement readElement, String discriminatorField) { + JsonElement element = readElement.getAsJsonObject().get(discriminatorField); + if (null == element) { + throw new IllegalArgumentException("missing discriminator field: <" + discriminatorField + ">"); + } + return element.getAsString(); + } + + /** + * Returns the Java class that implements the OpenAPI schema for the specified discriminator value. + * + * @param classByDiscriminatorValue The map of discriminator values to Java classes. + * @param discriminatorValue The value of the OpenAPI discriminator in the input data. + * @return The Java class that implements the OpenAPI schema + */ + private static Class getClassByDiscriminator(Map classByDiscriminatorValue, String discriminatorValue) { + Class clazz = (Class) classByDiscriminatorValue.get(discriminatorValue); + if (null == clazz) { + throw new IllegalArgumentException("cannot determine model class of name: <" + discriminatorValue + ">"); + } + return clazz; + } + + { + GsonBuilder gsonBuilder = createGson(); + gsonBuilder.registerTypeAdapter(Date.class, dateTypeAdapter); + gsonBuilder.registerTypeAdapter(java.sql.Date.class, sqlDateTypeAdapter); + gsonBuilder.registerTypeAdapter(OffsetDateTime.class, offsetDateTimeTypeAdapter); + gsonBuilder.registerTypeAdapter(LocalDate.class, localDateTypeAdapter); + gsonBuilder.registerTypeAdapter(byte[].class, byteArrayAdapter); + gsonBuilder.registerTypeAdapterFactory(new test.test.runtime.model.TreeNode.CustomTypeAdapterFactory()); + gson = gsonBuilder.create(); + } + + /** + * Get Gson. + * + * @return Gson + */ + public static Gson getGson() { + return gson; + } + + /** + * Set Gson. + * + * @param gson Gson + */ + public static void setGson(Gson gson) { + JSON.gson = gson; + } + + public static void setLenientOnJson(boolean lenientOnJson) { + isLenientOnJson = lenientOnJson; + } + + /** + * Serialize the given Java object into JSON string. + * + * @param obj Object + * @return String representation of the JSON + */ + public static String serialize(Object obj) { + return gson.toJson(obj); + } + + /** + * Deserialize the given JSON string to Java object. + * + * @param Type + * @param body The JSON string + * @param returnType The type to deserialize into + * @return The deserialized Java object + */ + @SuppressWarnings("unchecked") + public static T deserialize(String body, Type returnType) { + try { + if (isLenientOnJson) { + JsonReader jsonReader = new JsonReader(new StringReader(body)); + // see https://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/stream/JsonReader.html#setLenient(boolean) + jsonReader.setLenient(true); + return gson.fromJson(jsonReader, returnType); + } else { + return gson.fromJson(body, returnType); + } + } catch (JsonParseException e) { + // Fallback processing when failed to parse JSON form response body: + // return the response body string directly for the String return type; + if (returnType.equals(String.class)) { + return (T) body; + } else { + throw (e); + } + } + } + + /** + * Gson TypeAdapter for Byte Array type + */ + public static class ByteArrayAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, byte[] value) throws IOException { + if (value == null) { + out.nullValue(); + } else { + out.value(ByteString.of(value).base64()); + } + } + + @Override + public byte[] read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String bytesAsBase64 = in.nextString(); + ByteString byteString = ByteString.decodeBase64(bytesAsBase64); + return byteString.toByteArray(); + } + } + } + + /** + * Gson TypeAdapter for JSR310 OffsetDateTime type + */ + public static class OffsetDateTimeTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public OffsetDateTimeTypeAdapter() { + this(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + } + + public OffsetDateTimeTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, OffsetDateTime date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.format(date)); + } + } + + @Override + public OffsetDateTime read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + if (date.endsWith("+0000")) { + date = date.substring(0, date.length()-5) + "Z"; + } + return OffsetDateTime.parse(date, formatter); + } + } + } + + /** + * Gson TypeAdapter for JSR310 LocalDate type + */ + public static class LocalDateTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public LocalDateTypeAdapter() { + this(DateTimeFormatter.ISO_LOCAL_DATE); + } + + public LocalDateTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, LocalDate date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.format(date)); + } + } + + @Override + public LocalDate read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + return LocalDate.parse(date, formatter); + } + } + } + + public static void setOffsetDateTimeFormat(DateTimeFormatter dateFormat) { + offsetDateTimeTypeAdapter.setFormat(dateFormat); + } + + public static void setLocalDateFormat(DateTimeFormatter dateFormat) { + localDateTypeAdapter.setFormat(dateFormat); + } + + /** + * Gson TypeAdapter for java.sql.Date type + * If the dateFormat is null, a simple "yyyy-MM-dd" format will be used + * (more efficient than SimpleDateFormat). + */ + public static class SqlDateTypeAdapter extends TypeAdapter { + + private DateFormat dateFormat; + + public SqlDateTypeAdapter() {} + + public SqlDateTypeAdapter(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + public void setFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + @Override + public void write(JsonWriter out, java.sql.Date date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + String value; + if (dateFormat != null) { + value = dateFormat.format(date); + } else { + value = date.toString(); + } + out.value(value); + } + } + + @Override + public java.sql.Date read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + try { + if (dateFormat != null) { + return new java.sql.Date(dateFormat.parse(date).getTime()); + } + return new java.sql.Date(ISO8601Utils.parse(date, new ParsePosition(0)).getTime()); + } catch (ParseException e) { + throw new JsonParseException(e); + } + } + } + } + + /** + * Gson TypeAdapter for java.util.Date type + * If the dateFormat is null, ISO8601Utils will be used. + */ + public static class DateTypeAdapter extends TypeAdapter { + + private DateFormat dateFormat; + + public DateTypeAdapter() {} + + public DateTypeAdapter(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + public void setFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + @Override + public void write(JsonWriter out, Date date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + String value; + if (dateFormat != null) { + value = dateFormat.format(date); + } else { + value = ISO8601Utils.format(date, true); + } + out.value(value); + } + } + + @Override + public Date read(JsonReader in) throws IOException { + try { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + try { + if (dateFormat != null) { + return dateFormat.parse(date); + } + return ISO8601Utils.parse(date, new ParsePosition(0)); + } catch (ParseException e) { + throw new JsonParseException(e); + } + } + } catch (IllegalArgumentException e) { + throw new JsonParseException(e); + } + } + } + + public static void setDateFormat(DateFormat dateFormat) { + dateTypeAdapter.setFormat(dateFormat); + } + + public static void setSqlDateFormat(DateFormat dateFormat) { + sqlDateTypeAdapter.setFormat(dateFormat); + } +} +", + "src/main/java/test/test/runtime/Pair.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime; + +public class Pair { + private String name = ""; + private String value = ""; + + public Pair (String name, String value) { + setName(name); + setValue(value); + } + + private void setName(String name) { + if (!isValidString(name)) { + return; + } + + this.name = name; + } + + private void setValue(String value) { + if (!isValidString(value)) { + return; + } + + this.value = value; + } + + public String getName() { + return this.name; + } + + public String getValue() { + return this.value; + } + + private boolean isValidString(String arg) { + if (arg == null) { + return false; + } + + return true; + } +} +", + "src/main/java/test/test/runtime/ProgressRequestBody.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime; + +import okhttp3.MediaType; +import okhttp3.RequestBody; + +import java.io.IOException; + +import okio.Buffer; +import okio.BufferedSink; +import okio.ForwardingSink; +import okio.Okio; +import okio.Sink; + +public class ProgressRequestBody extends RequestBody { + + private final RequestBody requestBody; + + private final ApiCallback callback; + + public ProgressRequestBody(RequestBody requestBody, ApiCallback callback) { + this.requestBody = requestBody; + this.callback = callback; + } + + @Override + public MediaType contentType() { + return requestBody.contentType(); + } + + @Override + public long contentLength() throws IOException { + return requestBody.contentLength(); + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + BufferedSink bufferedSink = Okio.buffer(sink(sink)); + requestBody.writeTo(bufferedSink); + bufferedSink.flush(); + } + + private Sink sink(Sink sink) { + return new ForwardingSink(sink) { + + long bytesWritten = 0L; + long contentLength = 0L; + + @Override + public void write(Buffer source, long byteCount) throws IOException { + super.write(source, byteCount); + if (contentLength == 0) { + contentLength = contentLength(); + } + + bytesWritten += byteCount; + callback.onUploadProgress(bytesWritten, contentLength, bytesWritten == contentLength); + } + }; + } +} +", + "src/main/java/test/test/runtime/ProgressResponseBody.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime; + +import okhttp3.MediaType; +import okhttp3.ResponseBody; + +import java.io.IOException; + +import okio.Buffer; +import okio.BufferedSource; +import okio.ForwardingSource; +import okio.Okio; +import okio.Source; + +public class ProgressResponseBody extends ResponseBody { + + private final ResponseBody responseBody; + private final ApiCallback callback; + private BufferedSource bufferedSource; + + public ProgressResponseBody(ResponseBody responseBody, ApiCallback callback) { + this.responseBody = responseBody; + this.callback = callback; + } + + @Override + public MediaType contentType() { + return responseBody.contentType(); + } + + @Override + public long contentLength() { + return responseBody.contentLength(); + } + + @Override + public BufferedSource source() { + if (bufferedSource == null) { + bufferedSource = Okio.buffer(source(responseBody.source())); + } + return bufferedSource; + } + + private Source source(Source source) { + return new ForwardingSource(source) { + long totalBytesRead = 0L; + + @Override + public long read(Buffer sink, long byteCount) throws IOException { + long bytesRead = super.read(sink, byteCount); + // read() returns the number of bytes read, or -1 if this source is exhausted. + totalBytesRead += bytesRead != -1 ? bytesRead : 0; + callback.onDownloadProgress(totalBytesRead, responseBody.contentLength(), bytesRead == -1); + return bytesRead; + } + }; + } +} +", + "src/main/java/test/test/runtime/ServerConfiguration.java": "package test.test.runtime; + +import java.util.Map; + +/** + * Representing a Server configuration. + */ +public class ServerConfiguration { + public String URL; + public String description; + public Map variables; + + /** + * @param URL A URL to the target host. + * @param description A description of the host designated by the URL. + * @param variables A map between a variable name and its value. The value is used for substitution in the server's URL template. + */ + public ServerConfiguration(String URL, String description, Map variables) { + this.URL = URL; + this.description = description; + this.variables = variables; + } + + /** + * Format URL template using given variables. + * + * @param variables A map between a variable name and its value. + * @return Formatted URL. + */ + public String URL(Map variables) { + String url = this.URL; + + // go through variables and replace placeholders + for (Map.Entry variable: this.variables.entrySet()) { + String name = variable.getKey(); + ServerVariable serverVariable = variable.getValue(); + String value = serverVariable.defaultValue; + + if (variables != null && variables.containsKey(name)) { + value = variables.get(name); + if (serverVariable.enumValues.size() > 0 && !serverVariable.enumValues.contains(value)) { + throw new IllegalArgumentException("The variable " + name + " in the server URL has invalid value " + value + "."); + } + } + url = url.replace("{" + name + "}", value); + } + return url; + } + + /** + * Format URL template using default server variables. + * + * @return Formatted URL. + */ + public String URL() { + return URL(null); + } +} +", + "src/main/java/test/test/runtime/ServerVariable.java": "package test.test.runtime; + +import java.util.HashSet; + +/** + * Representing a Server Variable for server URL template substitution. + */ +public class ServerVariable { + public String description; + public String defaultValue; + public HashSet enumValues = null; + + /** + * @param description A description for the server variable. + * @param defaultValue The default value to use for substitution. + * @param enumValues An enumeration of string values to be used if the substitution options are from a limited set. + */ + public ServerVariable(String description, String defaultValue, HashSet enumValues) { + this.description = description; + this.defaultValue = defaultValue; + this.enumValues = enumValues; + } +} +", + "src/main/java/test/test/runtime/StringUtil.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime; + +import java.util.Collection; +import java.util.Iterator; + +public class StringUtil { + /** + * Check if the given array contains the given value (with case-insensitive comparison). + * + * @param array The array + * @param value The value to search + * @return true if the array contains the value + */ + public static boolean containsIgnoreCase(String[] array, String value) { + for (String str : array) { + if (value == null && str == null) { + return true; + } + if (value != null && value.equalsIgnoreCase(str)) { + return true; + } + } + return false; + } + + /** + * Join an array of strings with the given separator. + *

+ * Note: This might be replaced by utility method from commons-lang or guava someday + * if one of those libraries is added as dependency. + *

+ * + * @param array The array of strings + * @param separator The separator + * @return the resulting string + */ + public static String join(String[] array, String separator) { + int len = array.length; + if (len == 0) { + return ""; + } + + StringBuilder out = new StringBuilder(); + out.append(array[0]); + for (int i = 1; i < len; i++) { + out.append(separator).append(array[i]); + } + return out.toString(); + } + + /** + * Join a list of strings with the given separator. + * + * @param list The list of strings + * @param separator The separator + * @return the resulting string + */ + public static String join(Collection list, String separator) { + Iterator iterator = list.iterator(); + StringBuilder out = new StringBuilder(); + if (iterator.hasNext()) { + out.append(iterator.next()); + } + while (iterator.hasNext()) { + out.append(separator).append(iterator.next()); + } + return out.toString(); + } +} +", + "src/main/java/test/test/runtime/api/DefaultApi.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime.api; + +import test.test.runtime.ApiCallback; +import test.test.runtime.ApiClient; +import test.test.runtime.ApiException; +import test.test.runtime.ApiResponse; +import test.test.runtime.Configuration; +import test.test.runtime.Pair; +import test.test.runtime.ProgressRequestBody; +import test.test.runtime.ProgressResponseBody; + +import com.google.gson.reflect.TypeToken; + +import java.io.IOException; + + +import java.math.BigDecimal; +import java.io.File; +import test.test.runtime.model.TreeNode; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.ws.rs.core.GenericType; + +public class DefaultApi { + private ApiClient localVarApiClient; + private int localHostIndex; + private String localCustomBaseUrl; + + public DefaultApi() { + this(Configuration.getDefaultApiClient()); + } + + public DefaultApi(ApiClient apiClient) { + this.localVarApiClient = apiClient; + } + + public ApiClient getApiClient() { + return localVarApiClient; + } + + public void setApiClient(ApiClient apiClient) { + this.localVarApiClient = apiClient; + } + + public int getHostIndex() { + return localHostIndex; + } + + public void setHostIndex(int hostIndex) { + this.localHostIndex = hostIndex; + } + + public String getCustomBaseUrl() { + return localCustomBaseUrl; + } + + public void setCustomBaseUrl(String customBaseUrl) { + this.localCustomBaseUrl = customBaseUrl; + } + + private okhttp3.Call getTreeCall(final ApiCallback _callback) throws ApiException { + String basePath = null; + // Operation Servers + String[] localBasePaths = new String[] { }; + + // Determine Base Path to Use + if (localCustomBaseUrl != null){ + basePath = localCustomBaseUrl; + } else if ( localBasePaths.length > 0 ) { + basePath = localBasePaths[localHostIndex]; + } else { + basePath = null; + } + + Object localVarPostBody = null; + + // create path and map variables + String localVarPath = "/tree"; + + List localVarQueryParams = new ArrayList(); + List localVarCollectionQueryParams = new ArrayList(); + Map localVarHeaderParams = new HashMap(); + Map localVarCookieParams = new HashMap(); + Map localVarFormParams = new HashMap(); + + final String[] localVarAccepts = { + "application/json" + }; + final String localVarAccept = localVarApiClient.selectHeaderAccept(localVarAccepts); + if (localVarAccept != null) { + localVarHeaderParams.put("Accept", localVarAccept); + } + + final String[] localVarContentTypes = { + }; + final String localVarContentType = localVarApiClient.selectHeaderContentType(localVarContentTypes); + if (localVarContentType != null) { + localVarHeaderParams.put("Content-Type", localVarContentType); + } + + String[] localVarAuthNames = new String[] { }; + return localVarApiClient.buildCall(basePath, localVarPath, "GET", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAuthNames, _callback); + } + + + @SuppressWarnings("rawtypes") + private okhttp3.Call getTreeValidateBeforeCall(final ApiCallback _callback) throws ApiException { + return getTreeCall(_callback); + + } + + private ApiResponse getTreeWithHttpInfo() throws ApiException { + okhttp3.Call localVarCall = getTreeValidateBeforeCall(null); + Type localVarReturnType = new TypeToken(){}.getType(); + return localVarApiClient.execute(localVarCall, localVarReturnType); + } + + + private okhttp3.Call getTreeAsync(final ApiCallback _callback) throws ApiException { + + okhttp3.Call localVarCall = getTreeValidateBeforeCall(_callback); + Type localVarReturnType = new TypeToken(){}.getType(); + localVarApiClient.executeAsync(localVarCall, localVarReturnType, _callback); + return localVarCall; + } + + public class APIgetTreeRequest { + + private APIgetTreeRequest() { + } + + /** + * Build call for getTree + * @param _callback ApiCallback API callback + * @return Call to execute + * @throws ApiException If fail to serialize the request body object + * @http.response.details + + + +
Status Code Description Response Headers
200 Ok -
+ */ + public okhttp3.Call buildCall(final ApiCallback _callback) throws ApiException { + return getTreeCall(_callback); + } + + /** + * Execute getTree request + * @return TreeNode + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + * @http.response.details + + + +
Status Code Description Response Headers
200 Ok -
+ */ + public TreeNode execute() throws ApiException { + ApiResponse localVarResp = getTreeWithHttpInfo(); + return localVarResp.getData(); + } + + /** + * Execute getTree request with HTTP info returned + * @return ApiResponse<TreeNode> + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + * @http.response.details + + + +
Status Code Description Response Headers
200 Ok -
+ */ + public ApiResponse executeWithHttpInfo() throws ApiException { + return getTreeWithHttpInfo(); + } + + /** + * Execute getTree request (asynchronously) + * @param _callback The callback to be executed when the API call finishes + * @return The request call + * @throws ApiException If fail to process the API call, e.g. serializing the request body object + * @http.response.details + + + +
Status Code Description Response Headers
200 Ok -
+ */ + public okhttp3.Call executeAsync(final ApiCallback _callback) throws ApiException { + return getTreeAsync(_callback); + } + } + + /** + * + * + * @return APIgetTreeRequest + * @http.response.details + + + +
Status Code Description Response Headers
200 Ok -
+ */ + + public APIgetTreeRequest getTree() { + return new APIgetTreeRequest(); + } +} + +", + "src/main/java/test/test/runtime/api/handlers/ApiResponse.java": " +package test.test.runtime.api.handlers; + +import java.util.Map; +import java.util.List; + +@lombok.experimental.SuperBuilder +@lombok.AllArgsConstructor +@lombok.Getter +public class ApiResponse implements Response { + private String body; + private int statusCode; + private Map headers; + private Map> multiValueHeaders; +} +", + "src/main/java/test/test/runtime/api/handlers/ChainedRequestInput.java": " +package test.test.runtime.api.handlers; + +/** + * Reqeust input with a handler chain + */ +public interface ChainedRequestInput extends RequestInput { + /** + * The chain for handling requests + */ + HandlerChain getChain(); +} +", + "src/main/java/test/test/runtime/api/handlers/HandlerChain.java": " +package test.test.runtime.api.handlers; +/** + * A handler chain represents a series of interceptors, which may or may not delegate to following interceptors. + * The lambda handler is always the last method in the chain. + */ +public interface HandlerChain { + /** + * Delegate to the remainder of the handler chain + */ + Response next(ChainedRequestInput input); +} +", + "src/main/java/test/test/runtime/api/handlers/HandlerRouter.java": " +package test.test.runtime.api.handlers; + +import test.test.runtime.api.handlers.get_tree.*; + +import test.test.runtime.api.handlers.Handlers; +import test.test.runtime.api.handlers.*; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; + +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; + + +public abstract class HandlerRouter implements RequestHandler { + private static final String getTreeMethodAndPath = Handlers.concatMethodAndPath("GET", "/tree"); + + private final GetTree constructedGetTree; + + /** + * This method must return your implementation of the GetTree operation + */ + public abstract GetTree getTree(); + + private static enum Route { + getTreeRoute, + } + + /** + * Map of method and path to the route to map to + */ + private final Map routes = new HashMap<>(); + + public HandlerRouter() { + this.routes.put(getTreeMethodAndPath, Route.getTreeRoute); + // Handlers are all constructed in the router's constructor such that lambda behaviour remains consistent; + // ie resources created in the constructor remain in memory between invocations. + // https://docs.aws.amazon.com/lambda/latest/dg/java-handler.html + this.constructedGetTree = this.getTree(); + } + + /** + * For more complex interceptors that require instantiation with parameters, you may override this method to + * return a list of instantiated interceptors. For simple interceptors with no need for constructor arguments, + * prefer the @Interceptors annotation. + */ + public List> getInterceptors() { + return Collections.emptyList(); + } + + @Override + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent event, final Context context) { + String method = event.getRequestContext().getHttpMethod(); + String path = event.getRequestContext().getResourcePath(); + String methodAndPath = Handlers.concatMethodAndPath(method, path); + Route route = this.routes.get(methodAndPath); + + switch (route) { + case getTreeRoute: + List> getTreeInterceptors = Handlers.getAnnotationInterceptors(this.getClass()); + getTreeInterceptors.addAll(this.getInterceptors()); + return this.constructedGetTree.handleRequestWithAdditionalInterceptors(event, context, getTreeInterceptors); + default: + throw new RuntimeException(String.format("No registered handler for method {} and path {}", method, path)); + } + } +}", + "src/main/java/test/test/runtime/api/handlers/Handlers.java": " +package test.test.runtime.api.handlers; + +import test.test.runtime.model.*; +import test.test.runtime.api.interceptors.ResponseHeadersInterceptor; + +import java.util.Arrays; +import java.util.Optional; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; +import java.util.stream.Collectors; +import java.io.UnsupportedEncodingException; +import java.io.IOException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.time.OffsetDateTime; +import java.time.DateTimeException; +import java.math.BigDecimal; +import java.math.BigInteger; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; + +import test.test.runtime.JSON; + +public class Handlers { + + static { + // JSON has a static instance of Gson which is instantiated lazily the first time it is initialised. + // Create an instance here if required to ensure that the static Gson instance is always available. + if (JSON.getGson() == null) { + new JSON(); + } + } + + private static String decodeParameter(final String parameter) { + try { + return URLDecoder.decode(parameter, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + public static Map decodeRequestParameters(Map parameters) { + Map decodedParameters = new HashMap<>(); + for(Map.Entry parameter : parameters.entrySet()) { + decodedParameters.put(parameter.getKey(), decodeParameter(parameter.getValue())); + } + return decodedParameters; + } + + public static Map> decodeRequestArrayParameters(Map> parameters) { + Map> decodedParameters = new HashMap<>(); + for(Map.Entry> parameter : parameters.entrySet()) { + decodedParameters.put(parameter.getKey(), parameter.getValue().stream().map(Handlers::decodeParameter).collect(Collectors.toList())); + } + return decodedParameters; + } + + public static void assertRequired(final Boolean required, final String baseName, final Map parameters) { + if (required && parameters.get(baseName) == null) { + throw new RuntimeException("Missing required request parameter '" + baseName + "'"); + } + } + + public static String coerceStringParameter(final String baseName, final boolean required, final Map parameters) { + Handlers.assertRequired(required, baseName, parameters); + return parameters.get(baseName); + } + + public static List coerceStringArrayParameter(final String baseName, final boolean required, final Map> parameters) { + Handlers.assertRequired(required, baseName, parameters); + return parameters.get(baseName); + } + + public static Double coerceDouble(final String baseName, final String s) { + try { + return Double.valueOf(s); + } catch (NumberFormatException e) { + throw new RuntimeException("Expected a number for request parameter '" + baseName + "'"); + } + } + + public static Double coerceDoubleParameter(final String baseName, final boolean required, final Map parameters) { + Handlers.assertRequired(required, baseName, parameters); + String s = parameters.get(baseName); + return s == null ? null : coerceDouble(baseName, s); + } + + public static List coerceDoubleArrayParameter(final String baseName, final boolean required, final Map> parameters) { + Handlers.assertRequired(required, baseName, parameters); + List ss = parameters.get(baseName); + if (ss == null) { + return null; + } + List res = new ArrayList<>(); + for (String s : ss) { + res.add(coerceDouble(baseName, s)); + } + return res; + } + + public static BigDecimal coerceBigDecimal(final String baseName, final String s) { + try { + return new BigDecimal(s); + } catch (NumberFormatException e) { + throw new RuntimeException("Expected a number for request parameter '" + baseName + "'"); + } + } + + public static BigDecimal coerceBigDecimalParameter(final String baseName, final boolean required, final Map parameters) { + Handlers.assertRequired(required, baseName, parameters); + String s = parameters.get(baseName); + return s == null ? null : coerceBigDecimal(baseName, s); + } + + public static List coerceBigDecimalArrayParameter(final String baseName, final boolean required, final Map> parameters) { + Handlers.assertRequired(required, baseName, parameters); + List ss = parameters.get(baseName); + if (ss == null) { + return null; + } + List res = new ArrayList<>(); + for (String s : ss) { + res.add(coerceBigDecimal(baseName, s)); + } + return res; + } + + public static BigInteger coerceBigInteger(final String baseName, final String s) { + try { + return new BigInteger(s); + } catch (NumberFormatException e) { + throw new RuntimeException("Expected a number for request parameter '" + baseName + "'"); + } + } + + public static BigInteger coerceBigIntegerParameter(final String baseName, final boolean required, final Map parameters) { + Handlers.assertRequired(required, baseName, parameters); + String s = parameters.get(baseName); + return s == null ? null : coerceBigInteger(baseName, s); + } + + public static List coerceBigIntegerArrayParameter(final String baseName, final boolean required, final Map> parameters) { + Handlers.assertRequired(required, baseName, parameters); + List ss = parameters.get(baseName); + if (ss == null) { + return null; + } + List res = new ArrayList<>(); + for (String s : ss) { + res.add(coerceBigInteger(baseName, s)); + } + return res; + } + + public static Float coerceFloat(final String baseName, final String s) { + try { + return Float.valueOf(s); + } catch (NumberFormatException e) { + throw new RuntimeException("Expected a float for request parameter '" + baseName + "'"); + } + } + + public static Float coerceFloatParameter(final String baseName, final boolean required, final Map parameters) { + Handlers.assertRequired(required, baseName, parameters); + String s = parameters.get(baseName); + return s == null ? null : coerceFloat(baseName, s); + } + + public static List coerceFloatArrayParameter(final String baseName, final boolean required, final Map> parameters) { + Handlers.assertRequired(required, baseName, parameters); + List ss = parameters.get(baseName); + if (ss == null) { + return null; + } + List res = new ArrayList<>(); + for (String s : ss) { + res.add(coerceFloat(baseName, s)); + } + return res; + } + + public static Integer coerceInteger(final String baseName, final String s) { + try { + return Integer.valueOf(s); + } catch (NumberFormatException e) { + throw new RuntimeException("Expected an integer for request parameter '" + baseName + "'"); + } + } + + public static Integer coerceIntegerParameter(final String baseName, final boolean required, final Map parameters) { + Handlers.assertRequired(required, baseName, parameters); + String s = parameters.get(baseName); + return s == null ? null : coerceInteger(baseName, s); + } + + public static List coerceIntegerArrayParameter(final String baseName, final boolean required, final Map> parameters) { + Handlers.assertRequired(required, baseName, parameters); + List ss = parameters.get(baseName); + if (ss == null) { + return null; + } + List res = new ArrayList<>(); + for (String s : ss) { + res.add(coerceInteger(baseName, s)); + } + return res; + } + + public static Long coerceLong(final String baseName, final String s) { + try { + return Long.valueOf(s); + } catch (NumberFormatException e) { + throw new RuntimeException("Expected a long for request parameter '" + baseName + "'"); + } + } + + public static Long coerceLongParameter(final String baseName, final boolean required, final Map parameters) { + Handlers.assertRequired(required, baseName, parameters); + String s = parameters.get(baseName); + return s == null ? null : coerceLong(baseName, s); + } + + public static List coerceLongArrayParameter(final String baseName, final boolean required, final Map> parameters) { + Handlers.assertRequired(required, baseName, parameters); + List ss = parameters.get(baseName); + if (ss == null) { + return null; + } + List res = new ArrayList<>(); + for (String s : ss) { + res.add(coerceLong(baseName, s)); + } + return res; + } + + public static Short coerceShort(final String baseName, final String s) { + try { + return Short.valueOf(s); + } catch (NumberFormatException e) { + throw new RuntimeException("Expected a short for request parameter '" + baseName + "'"); + } + } + + public static Short coerceShortParameter(final String baseName, final boolean required, final Map parameters) { + Handlers.assertRequired(required, baseName, parameters); + String s = parameters.get(baseName); + return s == null ? null : coerceShort(baseName, s); + } + + public static List coerceShortArrayParameter(final String baseName, final boolean required, final Map> parameters) { + Handlers.assertRequired(required, baseName, parameters); + List ss = parameters.get(baseName); + if (ss == null) { + return null; + } + List res = new ArrayList<>(); + for (String s : ss) { + res.add(coerceShort(baseName, s)); + } + return res; + } + + public static Boolean coerceBoolean(final String baseName, final String s) { + if ("true".equals(s)) { + return true; + } else if ("false".equals(s)) { + return false; + } + throw new RuntimeException("Expected a boolean (true or false) for request parameter '" + baseName + "'"); + } + + public static Boolean coerceBooleanParameter(final String baseName, final boolean required, final Map parameters) { + Handlers.assertRequired(required, baseName, parameters); + String s = parameters.get(baseName); + return s == null ? null : coerceBoolean(baseName, s); + } + + public static List coerceBooleanArrayParameter(final String baseName, final boolean required, final Map> parameters) { + Handlers.assertRequired(required, baseName, parameters); + List ss = parameters.get(baseName); + if (ss == null) { + return null; + } + List res = new ArrayList<>(); + for (String s : ss) { + res.add(coerceBoolean(baseName, s)); + } + return res; + } + + public static OffsetDateTime coerceOffsetDateTime(final String baseName, final String s) { + try { + return OffsetDateTime.parse(s); + } catch (DateTimeException e) { + throw new RuntimeException("Expected a valid date (iso format) for request parameter '" + baseName + "'"); + } + } + + public static OffsetDateTime coerceOffsetDateTimeParameter(final String baseName, final boolean required, final Map parameters) { + Handlers.assertRequired(required, baseName, parameters); + String s = parameters.get(baseName); + return s == null ? null : coerceOffsetDateTime(baseName, s); + } + + public static List coerceOffsetDateTimeArrayParameter(final String baseName, final boolean required, final Map> parameters) { + Handlers.assertRequired(required, baseName, parameters); + List ss = parameters.get(baseName); + if (ss == null) { + return null; + } + List res = new ArrayList<>(); + for (String s : ss) { + res.add(coerceOffsetDateTime(baseName, s)); + } + return res; + } + + public static void putAllFromNullableMap(Map source, Map destination) { + if (source != null) { + destination.putAll(source); + } + } + + public static String concatMethodAndPath(final String method, final String path) { + return String.format("%s||%s", method.toLowerCase(), path); + } + + public static Map extractResponseHeadersFromInterceptors(final List> interceptors) { + Map headers = new HashMap<>(); + for (Interceptor interceptor : interceptors) { + if (interceptor instanceof ResponseHeadersInterceptor) { + headers.putAll(((ResponseHeadersInterceptor) interceptor).getAdditionalHeaders()); + } + } + return headers; + } + + public static List> getAnnotationInterceptors(Class clazz) { + // Support specifying simple interceptors via the @Interceptors({ MyInterceptor.class, MyOtherInterceptor.class }) format + return clazz.isAnnotationPresent(Interceptors.class) + ? Arrays.stream(clazz.getAnnotation(Interceptors.class).value()).map(c -> { + try { + return (Interceptor) c.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(String.format( + "Cannot create instance of interceptor %s. Please ensure it has a public constructor " + + "with no arguments, or override the getInterceptors method instead of using the annotation", c.getSimpleName()), e); + } + }).collect(Collectors.toList()) + : new ArrayList<>(); + } + + public static HandlerChain buildHandlerChain(final List> interceptors, final HandlerChain baseChain) { + if (interceptors.isEmpty()) { + return baseChain; + } else { + Interceptor interceptor = interceptors.get(0); + HandlerChain remainingChain = buildHandlerChain(interceptors.subList(1, interceptors.size()), baseChain); + return new HandlerChain() { + @Override + public Response next(ChainedRequestInput input) { + return interceptor.handle(new ChainedRequestInput() { + @Override + public APIGatewayProxyRequestEvent getEvent() { + return input.getEvent(); + } + + @Override + public Context getContext() { + return input.getContext(); + } + + @Override + public TInput getInput() { + return input.getInput(); + } + + @Override + public HandlerChain getChain() { + return remainingChain; + } + + @Override + public Map getInterceptorContext() { + return input.getInterceptorContext(); + } + }); + } + }; + } + } +} +", + "src/main/java/test/test/runtime/api/handlers/Interceptor.java": " +package test.test.runtime.api.handlers; + +/** + * Interceptors can perform generic operations on requests and/or responses, optionally delegating to the remainder + * of the request chain. + */ +public interface Interceptor { + /** + * Handle a request. Usually the response from \`input.getChain().next(input)\` is returned to delegate to the + * remainder of the chain, however you may wish to return an alternative Response. + */ + Response handle(ChainedRequestInput input); +} +", + "src/main/java/test/test/runtime/api/handlers/InterceptorWarmupChainedRequestInput.java": " +package test.test.runtime.api.handlers; + +import com.amazonaws.services.lambda.runtime.ClientContext; +import com.amazonaws.services.lambda.runtime.CognitoIdentity; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.LambdaLogger; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.List; + +/** + * An "empty" chained request input used to warm up interceptors which extend the InterceptorWithWarmup + */ +public class InterceptorWarmupChainedRequestInput implements ChainedRequestInput { + + @Override + public HandlerChain getChain() { + return new HandlerChain() { + @Override + public Response next(ChainedRequestInput input) { + return new Response() { + @Override + public String getBody() { + return ""; + } + + @Override + public int getStatusCode() { + return 0; + } + + @Override + public Map getHeaders() { + return new HashMap<>(); + } + + @Override + public Map> getMultiValueHeaders() { + return new HashMap<>(); + } + }; + } + }; + } + + @Override + public Context getContext() { + return new Context() { + @Override + public String getAwsRequestId() { + return ""; + } + + @Override + public String getLogGroupName() { + return ""; + } + + @Override + public String getLogStreamName() { + return ""; + } + + @Override + public String getFunctionName() { + return ""; + } + + @Override + public String getFunctionVersion() { + return ""; + } + + @Override + public String getInvokedFunctionArn() { + return ""; + } + + @Override + public CognitoIdentity getIdentity() { + return null; + } + + @Override + public ClientContext getClientContext() { + return null; + } + + @Override + public int getRemainingTimeInMillis() { + return 0; + } + + @Override + public int getMemoryLimitInMB() { + return 0; + } + + @Override + public LambdaLogger getLogger() { + return null; + } + }; + } + + @Override + public APIGatewayProxyRequestEvent getEvent() { + return new APIGatewayProxyRequestEvent(); + } + + @Override + public T getInput() { + return null; + } + + @Override + public Map getInterceptorContext() { + Map context = new HashMap<>(); + context.put("operationId", "__tsapi_interceptor_warmup"); + return context; + } +} +", + "src/main/java/test/test/runtime/api/handlers/InterceptorWithWarmup.java": " +package test.test.runtime.api.handlers; + +import org.crac.Resource; +import org.crac.Core; +import org.crac.Context; + +/** + * An interceptor with a "warmUp" method with default snap-start warmup behaviour, which can be overridden if desired. + */ +public abstract class InterceptorWithWarmup implements Interceptor, Resource { + { + Core.getGlobalContext().register(this); + } + + @Override + public void beforeCheckpoint(Context context) { + this.warmUp(); + } + + @Override + public void afterRestore(Context context) { + + } + + /** + * Called prior to the lambda snap-start snapshot. + * Override this to change the default behaviour, which is to call the interceptor's handle method with an empty + * chained request. + */ + public void warmUp() { + this.handle(new InterceptorWarmupChainedRequestInput<>()); + } +} +", + "src/main/java/test/test/runtime/api/handlers/Interceptors.java": " +package test.test.runtime.api.handlers; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Use this annotation to add interceptors to the request handler. Interceptors used in the annotation must have a + * constructor with no arguments. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Interceptors { + public Class[] value() default {}; +} +", + "src/main/java/test/test/runtime/api/handlers/RequestInput.java": " +package test.test.runtime.api.handlers; + +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.Context; +import java.util.Map; + +/** + * Defines the input for a request. + */ +public interface RequestInput { + /** + * The raw event from API Gateway + */ + APIGatewayProxyRequestEvent getEvent(); + /** + * Lambda execution context + */ + Context getContext(); + /** + * Demarshalled request input + */ + TInput getInput(); + /** + * Storage for arbitrary interceptor context for the lifetime of the request. Set and get values to pass state + * between interceptors or to the final handler. + */ + Map getInterceptorContext(); +} +", + "src/main/java/test/test/runtime/api/handlers/Response.java": " +package test.test.runtime.api.handlers; + +import java.util.Map; +import java.util.List; + +/** + * Represents an HTTP response from an api operation + */ +public interface Response { + /** + * Returns the response body + */ + String getBody(); + /** + * Returns the response status code + */ + int getStatusCode(); + /** + * Returns the response headers + */ + Map getHeaders(); + /** + * Returns the multi-value response headers + */ + Map> getMultiValueHeaders(); +} +", + "src/main/java/test/test/runtime/api/handlers/get_tree/GetTree.java": " +package test.test.runtime.api.handlers.get_tree; + +import test.test.runtime.model.*; +import test.test.runtime.JSON; +import test.test.runtime.api.handlers.Interceptor; +import test.test.runtime.api.handlers.Handlers; +import test.test.runtime.api.handlers.*; + +import java.util.List; +import java.util.ArrayList; +import java.util.Optional; +import java.util.Map; +import java.util.HashMap; +import java.util.Collections; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import java.io.IOException; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.crac.Core; +import org.crac.Resource; + + +/** + * Lambda handler wrapper for the getTree operation + */ +public abstract class GetTree implements RequestHandler, Resource { + { + Core.getGlobalContext().register(this); + } + + /** + * Handle the request for the getTree operation + */ + public abstract GetTreeResponse handle(final GetTreeRequestInput request); + + /** + * Interceptors that the handler class has been decorated with + */ + private List> annotationInterceptors = Handlers.getAnnotationInterceptors(GetTree.class); + + /** + * For more complex interceptors that require instantiation with parameters, you may override this method to + * return a list of instantiated interceptors. For simple interceptors with no need for constructor arguments, + * prefer the @Interceptors annotation. + */ + public List> getInterceptors() { + return Collections.emptyList(); + } + + private List> getHandlerInterceptors() { + List> interceptors = new ArrayList<>(); + interceptors.addAll(annotationInterceptors); + interceptors.addAll(this.getInterceptors()); + return interceptors; + } + + private HandlerChain buildChain(List> interceptors) { + return Handlers.buildHandlerChain(interceptors, new HandlerChain() { + @Override + public Response next(ChainedRequestInput input) { + return handle(new GetTreeRequestInput(input.getEvent(), input.getContext(), input.getInterceptorContext(), input.getInput())); + } + }); + } + + private ChainedRequestInput buildChainedRequestInput(final APIGatewayProxyRequestEvent event, final Context context, final GetTreeInput input, final Map interceptorContext) { + return new ChainedRequestInput() { + @Override + public HandlerChain getChain() { + // The chain's next method ignores the chain given as input, and is pre-built to follow the remaining + // chain. + return null; + } + + @Override + public APIGatewayProxyRequestEvent getEvent() { + return event; + } + + @Override + public Context getContext() { + return context; + } + + @Override + public GetTreeInput getInput() { + return input; + } + + @Override + public Map getInterceptorContext() { + return interceptorContext; + } + }; + } + + @Override + public void beforeCheckpoint(org.crac.Context context) { + // Prime building the handler chain which can take a few 100ms to JIT. + this.buildChain(this.getHandlerInterceptors()); + this.buildChainedRequestInput(null, null, null, null); + + // Initialise instance of Gson and prime serialisation and deserialisation + new JSON(); + JSON.getGson().fromJson(JSON.getGson().toJson(new ApiResponse("", 0, new HashMap<>(), new HashMap<>())), ApiResponse.class); + + try { + // Prime input validation - this will likely fail for the fake event but ensures the code path is optimised + // ready for a real invocation + new GetTreeInput(new APIGatewayProxyRequestEvent() + .withBody("{}") + .withPathParameters(new HashMap<>()) + .withQueryStringParameters(new HashMap<>()) + .withMultiValueQueryStringParameters(new HashMap<>()) + .withHeaders(new HashMap<>()) + .withMultiValueHeaders(new HashMap<>()) + ); + } catch (Exception e) { + + } + + this.warmUp(); + } + + @Override + public void afterRestore(org.crac.Context context) { + + } + + /** + * Override this method to perform any warmup activities which will be executed prior to the snap-start snapshot. + */ + public void warmUp() { + + } + + @Override + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent event, final Context context) { + return this.handleRequestWithAdditionalInterceptors(event, context, new ArrayList<>()); + } + + private Map getErrorResponseHeaders(final int statusCode) { + Map headers = new HashMap<>(); + return headers; + } + + public APIGatewayProxyResponseEvent handleRequestWithAdditionalInterceptors(final APIGatewayProxyRequestEvent event, final Context context, final List> additionalInterceptors) { + final Map interceptorContext = new HashMap<>(); + interceptorContext.put("operationId", "getTree"); + + List> interceptors = new ArrayList<>(); + interceptors.addAll(additionalInterceptors); + interceptors.addAll(this.getHandlerInterceptors()); + + final HandlerChain chain = this.buildChain(interceptors); + + GetTreeInput input; + + try { + input = new GetTreeInput(event); + } catch (RuntimeException e) { + Map headers = new HashMap<>(); + headers.putAll(Handlers.extractResponseHeadersFromInterceptors(interceptors)); + headers.putAll(this.getErrorResponseHeaders(400)); + return new APIGatewayProxyResponseEvent() + .withStatusCode(400) + .withHeaders(headers) + .withBody("{\\"message\\": \\"" + e.getMessage() + "\\"}"); + } + + final Response response = chain.next(this.buildChainedRequestInput(event, context, input, interceptorContext)); + + Map responseHeaders = new HashMap<>(); + responseHeaders.putAll(this.getErrorResponseHeaders(response.getStatusCode())); + responseHeaders.putAll(response.getHeaders()); + + return new APIGatewayProxyResponseEvent() + .withStatusCode(response.getStatusCode()) + .withHeaders(responseHeaders) + .withMultiValueHeaders(response.getMultiValueHeaders()) + .withBody(response.getBody()); + } +} +", + "src/main/java/test/test/runtime/api/handlers/get_tree/GetTree200Response.java": " +package test.test.runtime.api.handlers.get_tree; + +import test.test.runtime.model.*; +import test.test.runtime.JSON; +import java.util.Map; +import java.util.HashMap; +import java.util.List; + +/** + * Response with status code 200 for the getTree operation + */ +public class GetTree200Response extends RuntimeException implements GetTreeResponse { + static { + // JSON has a static instance of Gson which is instantiated lazily the first time it is initialised. + // Create an instance here if required to ensure that the static Gson instance is always available. + if (JSON.getGson() == null) { + new JSON(); + } + } + + private final String body; + private final TreeNode typedBody; + private final Map headers; + private final Map> multiValueHeaders; + + private GetTree200Response(final TreeNode body, final Map headers, final Map> multiValueHeaders) { + this.typedBody = body; + this.body = body.toJson(); + this.headers = headers; + this.multiValueHeaders = multiValueHeaders; + } + + @Override + public int getStatusCode() { + return 200; + } + + @Override + public String getBody() { + return this.body; + } + + public TreeNode getTypedBody() { + return this.typedBody; + } + + @Override + public Map getHeaders() { + return this.headers; + } + + @Override + public Map> getMultiValueHeaders() { + return this.multiValueHeaders; + } + + /** + * Create a GetTree200Response with a body + */ + public static GetTree200Response of(final TreeNode body) { + return new GetTree200Response(body, new HashMap<>(), new HashMap<>()); + } + + /** + * Create a GetTree200Response with a body and headers + */ + public static GetTree200Response of(final TreeNode body, final Map headers) { + return new GetTree200Response(body, headers, new HashMap<>()); + } + + /** + * Create a GetTree200Response with a body, headers and multi-value headers + */ + public static GetTree200Response of(final TreeNode body, final Map headers, final Map> multiValueHeaders) { + return new GetTree200Response(body, headers, multiValueHeaders); + } +} +", + "src/main/java/test/test/runtime/api/handlers/get_tree/GetTreeInput.java": " +package test.test.runtime.api.handlers.get_tree; + +import test.test.runtime.model.*; +import test.test.runtime.JSON; +import java.util.List; +import java.util.ArrayList; +import java.util.Optional; +import java.util.Map; +import java.util.HashMap; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import java.io.IOException; + +/** + * Input for the getTree operation + */ +@lombok.Builder +@lombok.AllArgsConstructor +public class GetTreeInput { + static { + // JSON has a static instance of Gson which is instantiated lazily the first time it is initialised. + // Create an instance here if required to ensure that the static Gson instance is always available. + if (JSON.getGson() == null) { + new JSON(); + } + } + + private final GetTreeRequestParameters requestParameters; + + public GetTreeInput(final APIGatewayProxyRequestEvent event) { + this.requestParameters = new GetTreeRequestParameters(event); + } + + public GetTreeRequestParameters getRequestParameters() { + return this.requestParameters; + } + +} +", + "src/main/java/test/test/runtime/api/handlers/get_tree/GetTreeRequestInput.java": " +package test.test.runtime.api.handlers.get_tree; + +import test.test.runtime.model.*; +import test.test.runtime.api.handlers.RequestInput; +import java.util.List; +import java.util.Optional; +import java.util.Map; +import java.util.HashMap; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import java.io.IOException; +import com.amazonaws.services.lambda.runtime.Context; + +/** + * Full request input for the getTree operation, including the raw API Gateway event + */ +@lombok.Builder +@lombok.AllArgsConstructor +public class GetTreeRequestInput implements RequestInput { + private final APIGatewayProxyRequestEvent event; + private final Context context; + private final Map interceptorContext; + private final GetTreeInput input; + + /** + * Returns the typed request input, with path, query and body parameters + */ + public GetTreeInput getInput() { + return this.input; + } + + /** + * Returns the raw API Gateway event + */ + public APIGatewayProxyRequestEvent getEvent() { + return this.event; + } + + /** + * Returns the lambda context + */ + public Context getContext() { + return this.context; + } + + /** + * Returns the interceptor context, which may contain values set by request interceptors + */ + public Map getInterceptorContext() { + return this.interceptorContext; + } +} +", + "src/main/java/test/test/runtime/api/handlers/get_tree/GetTreeRequestParameters.java": " +package test.test.runtime.api.handlers.get_tree; + +import test.test.runtime.api.handlers.Handlers; +import java.util.Optional; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; +import java.util.HashMap; +import java.time.OffsetDateTime; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.stream.Collectors; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; + +import test.test.runtime.model.*; + +/** + * Query, path and header parameters for the GetTree operation + */ +@lombok.Builder +@lombok.AllArgsConstructor +public class GetTreeRequestParameters { + + public GetTreeRequestParameters(final APIGatewayProxyRequestEvent event) { + Map rawStringParameters = new HashMap<>(); + Handlers.putAllFromNullableMap(event.getPathParameters(), rawStringParameters); + Handlers.putAllFromNullableMap(event.getQueryStringParameters(), rawStringParameters); + Handlers.putAllFromNullableMap(event.getHeaders(), rawStringParameters); + Map decodedStringParameters = Handlers.decodeRequestParameters(rawStringParameters); + + Map> rawStringArrayParameters = new HashMap<>(); + Handlers.putAllFromNullableMap(event.getMultiValueQueryStringParameters(), rawStringArrayParameters); + Handlers.putAllFromNullableMap(event.getMultiValueHeaders(), rawStringArrayParameters); + Map> decodedStringArrayParameters = Handlers.decodeRequestArrayParameters(rawStringArrayParameters); + + } + +} +", + "src/main/java/test/test/runtime/api/handlers/get_tree/GetTreeResponse.java": " +package test.test.runtime.api.handlers.get_tree; + +import test.test.runtime.api.handlers.Response; + +/** + * Response for the getTree operation + */ +public interface GetTreeResponse extends Response {} +", + "src/main/java/test/test/runtime/api/interceptors/DefaultInterceptors.java": "package test.test.runtime.api.interceptors; + +import test.test.runtime.api.interceptors.powertools.LoggingInterceptor; +import test.test.runtime.api.interceptors.powertools.MetricsInterceptor; +import test.test.runtime.api.interceptors.powertools.TracingInterceptor; +import test.test.runtime.api.handlers.Interceptor; + +import java.util.Arrays; +import java.util.List; + +public class DefaultInterceptors { + public static List> all() { + return Arrays.asList( + new ResponseHeadersInterceptor<>(), + new LoggingInterceptor<>(), + new TryCatchInterceptor<>(), + new TracingInterceptor<>(), + new MetricsInterceptor<>() + ); + } +}", + "src/main/java/test/test/runtime/api/interceptors/ResponseHeadersInterceptor.java": "package test.test.runtime.api.interceptors; + +import test.test.runtime.api.handlers.ChainedRequestInput; +import test.test.runtime.api.handlers.Response; +import test.test.runtime.api.handlers.Interceptor; +import test.test.runtime.api.handlers.InterceptorWithWarmup; +import java.util.Map; +import java.util.HashMap; + +/** + * An interceptor for adding cross-origin resource sharing (CORS) headers to the response. + * Allows all origins and headers. + */ +public class ResponseHeadersInterceptor extends InterceptorWithWarmup { + private final Map additionalHeaders; + + public ResponseHeadersInterceptor() { + this.additionalHeaders = new HashMap<>(); + this.additionalHeaders.put("Access-Control-Allow-Origin", "*"); + this.additionalHeaders.put("Access-Control-Allow-Headers", "*"); + } + + public ResponseHeadersInterceptor(final Map headers) { + this.additionalHeaders = headers; + } + + @Override + public Response handle(ChainedRequestInput input) { + Response res = input.getChain().next(input); + res.getHeaders().putAll(this.additionalHeaders); + return res; + } + + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } +} +", + "src/main/java/test/test/runtime/api/interceptors/TryCatchInterceptor.java": "package test.test.runtime.api.interceptors; + +import test.test.runtime.api.handlers.ApiResponse; +import test.test.runtime.api.handlers.ChainedRequestInput; +import test.test.runtime.api.handlers.Response; +import test.test.runtime.api.handlers.Interceptor; +import test.test.runtime.api.handlers.InterceptorWithWarmup; +import org.apache.logging.log4j.Logger; + +/** + * Interceptor for handling uncaught exceptions and responding with a default error response + */ +public class TryCatchInterceptor extends InterceptorWithWarmup { + private final int statusCode; + private final String errorResponseBody; + + public TryCatchInterceptor() { + this(500, "{\\"message\\": \\"Internal Error\\"}"); + } + + public TryCatchInterceptor(final int statusCode, final String errorResponseBody) { + this.statusCode = statusCode; + this.errorResponseBody = errorResponseBody; + } + + @Override + public Response handle(final ChainedRequestInput input) { + try { + return input.getChain().next(input); + } catch (Throwable e) { + if (e instanceof Response) { + return (Response) e; + } + + Object logger = input.getInterceptorContext().get("logger"); + if (logger instanceof Logger) { + ((Logger) logger).error("Interceptor caught exception", e); + } else { + System.err.println("Interceptor caught exception"); + e.printStackTrace(); + } + + return ApiResponse.builder() + .statusCode(this.statusCode) + .body(this.errorResponseBody) + .build(); + } + } +} +", + "src/main/java/test/test/runtime/api/interceptors/powertools/LoggingInterceptor.java": "package test.test.runtime.api.interceptors.powertools; + +import test.test.runtime.api.handlers.ChainedRequestInput; +import test.test.runtime.api.handlers.RequestInput; +import test.test.runtime.api.handlers.Response; +import test.test.runtime.api.handlers.Interceptor; +import test.test.runtime.api.handlers.InterceptorWithWarmup; +import com.amazonaws.services.lambda.runtime.Context; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.logging.LoggingUtils; + +/** + * An interceptor which adds an aws lambda powertools logger to the interceptor context, + * and adds the lambda context. + * See https://docs.powertools.aws.dev/lambda/java/latest/core/logging/ + */ +public class LoggingInterceptor extends InterceptorWithWarmup { + private Logger logger = LogManager.getLogger(LoggingInterceptor.class); + + @Override + public void warmUp() { + super.warmUp(); + logger.info("LoggingInterceptor: init"); + } + + /** + * Return the instance of the logger from the interceptor context + */ + public static Logger getLogger(final RequestInput request) { + Object logger = request.getInterceptorContext().get("logger"); + if (logger == null) { + throw new RuntimeException("No logger found. Did you configure the LoggingInterceptor?"); + } + return (Logger) logger; + } + + private void addContext(final Context context) { + LoggingUtils.appendKey("functionName", context.getFunctionName()); + LoggingUtils.appendKey("functionVersion", context.getFunctionVersion()); + LoggingUtils.appendKey("functionArn", context.getInvokedFunctionArn()); + LoggingUtils.appendKey("functionMemorySize", String.valueOf(context.getMemoryLimitInMB())); + // Same casing as powertools aspect implementation + LoggingUtils.appendKey("function_request_id", String.valueOf(context.getAwsRequestId())); + } + + @Override + public Response handle(final ChainedRequestInput input) { + // Add lambda context fields + this.addContext(input.getContext()); + + // Add service, cold start and tracing + LoggingUtils.appendKey("service", LambdaHandlerProcessor.serviceName()); + LoggingUtils.appendKey("coldStart", LambdaHandlerProcessor.isColdStart() ? "true" : "false"); + LambdaHandlerProcessor.getXrayTraceId().ifPresent((xRayTraceId) -> { + LoggingUtils.appendKey("xray_trace_id", xRayTraceId); + }); + + // Add the operation id + String operationId = (String) input.getInterceptorContext().get("operationId"); + LoggingUtils.appendKey("operationId", operationId); + + // Add the logger to the interceptor context + input.getInterceptorContext().put("logger", logger); + + Response response = input.getChain().next(input); + + // Mark cold start done + LambdaHandlerProcessor.coldStartDone(); + + // Clear the logger keys + ThreadContext.clearMap(); + + return response; + } +} +", + "src/main/java/test/test/runtime/api/interceptors/powertools/MetricsInterceptor.java": "package test.test.runtime.api.interceptors.powertools; + +import test.test.runtime.api.handlers.ChainedRequestInput; +import test.test.runtime.api.handlers.RequestInput; +import test.test.runtime.api.handlers.Response; +import test.test.runtime.api.handlers.Interceptor; +import test.test.runtime.api.handlers.InterceptorWithWarmup; +import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger; +import software.amazon.cloudwatchlogs.emf.model.DimensionSet; +import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.metrics.MetricsUtils; + +/** + * Interceptor which adds an instance of aws lambda powertools metrics to the interceptor context (under the key "metrics"), + * and ensures metrics are flushed prior to finishing the lambda execution + * See: https://docs.powertools.aws.dev/lambda/typescript/latest/core/metrics + */ +public class MetricsInterceptor extends InterceptorWithWarmup { + private MetricsLogger metrics = MetricsUtils.metricsLogger(); + + /** + * Return the instance of the metrics logger from the interceptor context + */ + public static MetricsLogger getMetrics(final RequestInput request) { + Object metrics = request.getInterceptorContext().get("metrics"); + if (metrics == null) { + throw new RuntimeException("No metrics logger found. Did you configure the MetricsInterceptor?"); + } + return (MetricsLogger) metrics; + } + + @Override + public Response handle(final ChainedRequestInput input) { + metrics.putDimensions(DimensionSet.of("operationId", (String) input.getInterceptorContext().get("operationId"))); + + input.getInterceptorContext().put("metrics", metrics); + + metrics.putProperty("function_request_id", input.getContext().getAwsRequestId()); + LambdaHandlerProcessor.getXrayTraceId().ifPresent((traceId) -> { + metrics.putProperty("xray_trace_id", traceId); + }); + + try { + Response response = input.getChain().next(input); + + // Mark cold start done + LambdaHandlerProcessor.coldStartDone(); + + return response; + } finally { + metrics.flush(); + } + } +} +", + "src/main/java/test/test/runtime/api/interceptors/powertools/TracingInterceptor.java": "package test.test.runtime.api.interceptors.powertools; + +import test.test.runtime.api.handlers.ChainedRequestInput; +import test.test.runtime.api.handlers.Response; +import test.test.runtime.api.handlers.Interceptor; +import test.test.runtime.api.handlers.InterceptorWithWarmup; +import com.amazonaws.xray.AWSXRay; +import com.amazonaws.xray.AWSXRayRecorderBuilder; +import com.amazonaws.xray.entities.Subsegment; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.apache.logging.log4j.Logger; +import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.tracing.TracingUtils; + +/** + * Interceptor which adds an aws lambda powertools tracer to the interceptor context, + * creating the appropriate segment for the handler execution and annotating with recommended + * details. + * See: https://docs.powertools.aws.dev/lambda/java/latest/core/tracing/ + */ +public class TracingInterceptor extends InterceptorWithWarmup { + + static { + AWSXRayRecorderBuilder builder = AWSXRayRecorderBuilder.standard(); + AWSXRay.setGlobalRecorder(builder.build()); + } + + private final boolean captureResponse; + + public TracingInterceptor(final boolean captureResponse) { + this.captureResponse = captureResponse; + } + + public TracingInterceptor() { + this(false); + } + + @Override + public void warmUp() { + try { + // Set a dummy trace header to ensure the regular subsegment code path is followed and warmed. + // The segment is not actually recorded by xray. + System.setProperty("com.amazonaws.xray.traceHeader", "Root=1-xxx;Parent=yyy;Sampled=1"); + super.warmUp(); + } finally { + System.clearProperty("com.amazonaws.xray.traceHeader"); + } + } + + private void logError(final String message, final ChainedRequestInput input, final Throwable e) { + Object logger = input.getInterceptorContext().get("logger"); + if (logger instanceof Logger) { + ((Logger) logger).error(message, e); + } else { + System.err.println(message); + e.printStackTrace(); + } + } + + @Override + public Response handle(final ChainedRequestInput input) { + String operationId = (String) input.getInterceptorContext().get("operationId"); + Subsegment segment = AWSXRay.beginSubsegment("## " + operationId); + + segment.setNamespace(operationId); + segment.putAnnotation("ColdStart", LambdaHandlerProcessor.isColdStart()); + segment.putAnnotation("Service", LambdaHandlerProcessor.serviceName()); + + try { + Response response = input.getChain().next(input); + + try { + if (this.captureResponse) { + segment.putMetadata(operationId + " response", TracingUtils.objectMapper() != null ? TracingUtils.objectMapper().writeValueAsString(response) : response); + } + } catch (JsonProcessingException e) { + this.logError("Failed to add response to trace", input, e); + } + + // Mark cold start done + LambdaHandlerProcessor.coldStartDone(); + + return response; + } catch (Throwable e) { + try { + segment.putMetadata(operationId + " error", TracingUtils.objectMapper() != null ? TracingUtils.objectMapper().writeValueAsString(e) : e); + } catch (JsonProcessingException ex) { + this.logError("Failed to add error to trace", input, e); + } + throw e; + } finally { + if (!LambdaHandlerProcessor.isSamLocal()) { + AWSXRay.endSubsegment(); + } + } + } +} +", + "src/main/java/test/test/runtime/api/operation_config/OperationConfig.java": "package test.test.runtime.api.operation_config; + +import test.test.runtime.model.*; + +import java.util.HashMap; +import java.util.Map; + +// Generic type for object "keyed" by operation names +@lombok.Builder @lombok.Getter +public class OperationConfig { + private T getTree; + + public Map asMap() { + Map map = new HashMap<>(); + map.put("getTree", this.getTree); + return map; + } +} +", + "src/main/java/test/test/runtime/api/operation_config/OperationLookup.java": "package test.test.runtime.api.operation_config; + +import test.test.runtime.model.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import java.util.Arrays; + + +// Look up path and http method for a given operation name +public class OperationLookup { + @lombok.Builder @lombok.Getter + public static class OperationLookupEntry { + private String method; + private String path; + private List contentTypes; + } + + /** + * Returns the operation lookup information for the TypeSafeRestApi construct + */ + public static Map getOperationLookup() { + final Map config = new HashMap<>(); + + config.put("getTree", OperationLookupEntry.builder() + .path("/tree") + .method("GET") + .contentTypes(Arrays.asList("application/json")) + .build()); + + return config; + } +} +", + "src/main/java/test/test/runtime/api/operation_config/Operations.java": "package test.test.runtime.api.operation_config; + +public class Operations { + /** + * Returns an OperationConfig Builder with all values populated with the given value. + * You can override specific values on the builder if you like. + * Make sure you call \`.build()\` at the end to construct the OperationConfig. + */ + public static OperationConfig.OperationConfigBuilder all(final T value) { + return OperationConfig.builder() + .getTree(value) + ; + } +} +", + "src/main/java/test/test/runtime/auth/ApiKeyAuth.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime.auth; + +import test.test.runtime.ApiException; +import test.test.runtime.Pair; + +import java.net.URI; +import java.util.Map; +import java.util.List; + +public class ApiKeyAuth implements Authentication { + private final String location; + private final String paramName; + + private String apiKey; + private String apiKeyPrefix; + + public ApiKeyAuth(String location, String paramName) { + this.location = location; + this.paramName = paramName; + } + + public String getLocation() { + return location; + } + + public String getParamName() { + return paramName; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public String getApiKeyPrefix() { + return apiKeyPrefix; + } + + public void setApiKeyPrefix(String apiKeyPrefix) { + this.apiKeyPrefix = apiKeyPrefix; + } + + @Override + public void applyToParams(List queryParams, Map headerParams, Map cookieParams, + String payload, String method, URI uri) throws ApiException { + if (apiKey == null) { + return; + } + String value; + if (apiKeyPrefix != null) { + value = apiKeyPrefix + " " + apiKey; + } else { + value = apiKey; + } + if ("query".equals(location)) { + queryParams.add(new Pair(paramName, value)); + } else if ("header".equals(location)) { + headerParams.put(paramName, value); + } else if ("cookie".equals(location)) { + cookieParams.put(paramName, value); + } + } +} +", + "src/main/java/test/test/runtime/auth/Authentication.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime.auth; + +import test.test.runtime.Pair; +import test.test.runtime.ApiException; + +import java.net.URI; +import java.util.Map; +import java.util.List; + +public interface Authentication { + /** + * Apply authentication settings to header and query params. + * + * @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 ApiException if failed to update the parameters + */ + void applyToParams(List queryParams, Map headerParams, Map cookieParams, String payload, String method, URI uri) throws ApiException; +} +", + "src/main/java/test/test/runtime/auth/HttpBasicAuth.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime.auth; + +import test.test.runtime.Pair; +import test.test.runtime.ApiException; + +import okhttp3.Credentials; + +import java.net.URI; +import java.util.Map; +import java.util.List; + +import java.io.UnsupportedEncodingException; + +public class HttpBasicAuth implements Authentication { + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public void applyToParams(List queryParams, Map headerParams, Map cookieParams, + String payload, String method, URI uri) throws ApiException { + if (username == null && password == null) { + return; + } + headerParams.put("Authorization", Credentials.basic( + username == null ? "" : username, + password == null ? "" : password)); + } +} +", + "src/main/java/test/test/runtime/auth/HttpBearerAuth.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime.auth; + +import test.test.runtime.ApiException; +import test.test.runtime.Pair; + +import java.net.URI; +import java.util.Map; +import java.util.List; + +public class HttpBearerAuth implements Authentication { + private final String scheme; + private String bearerToken; + + public HttpBearerAuth(String scheme) { + this.scheme = scheme; + } + + /** + * Gets the token, which together with the scheme, will be sent as the value of the Authorization header. + * + * @return The bearer token + */ + public String getBearerToken() { + return bearerToken; + } + + /** + * Sets the token, which together with the scheme, will be sent as the value of the Authorization header. + * + * @param bearerToken The bearer token to send in the Authorization header + */ + public void setBearerToken(String bearerToken) { + this.bearerToken = bearerToken; + } + + @Override + public void applyToParams(List queryParams, Map headerParams, Map cookieParams, + String payload, String method, URI uri) throws ApiException { + if (bearerToken == null) { + return; + } + + headerParams.put("Authorization", (scheme != null ? upperCaseBearer(scheme) + " " : "") + bearerToken); + } + + private static String upperCaseBearer(String scheme) { + return ("bearer".equalsIgnoreCase(scheme)) ? "Bearer" : scheme; + } +} +", + "src/main/java/test/test/runtime/model/AbstractOpenApiSchema.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime.model; + +import test.test.runtime.ApiException; +import java.util.Objects; +import java.lang.reflect.Type; +import java.util.Map; +import javax.ws.rs.core.GenericType; + +//import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Abstract class for oneOf,anyOf schemas defined in OpenAPI spec + */ +@lombok.AllArgsConstructor @lombok.experimental.SuperBuilder +public abstract class AbstractOpenApiSchema { + + // store the actual instance of the schema/object + private Object instance; + + // is nullable + private Boolean isNullable; + + // schema type (e.g. oneOf, anyOf) + private final String schemaType; + + public AbstractOpenApiSchema(String schemaType, Boolean isNullable) { + this.schemaType = schemaType; + this.isNullable = isNullable; + } + + /** + * Get the list of oneOf/anyOf composed schemas allowed to be stored in this object + * + * @return an instance of the actual schema/object + */ + public abstract Map getSchemas(); + + /** + * Get the actual instance + * + * @return an instance of the actual schema/object + */ + //@JsonValue + public Object getActualInstance() {return instance;} + + /** + * Set the actual instance + * + * @param instance the actual instance of the schema/object + */ + public void setActualInstance(Object instance) {this.instance = instance;} + + /** + * Get the instant recursively when the schemas defined in oneOf/anyof happen to be oneOf/anyOf schema as well + * + * @return an instance of the actual schema/object + */ + public Object getActualInstanceRecursively() { + return getActualInstanceRecursively(this); + } + + private Object getActualInstanceRecursively(AbstractOpenApiSchema object) { + if (object.getActualInstance() == null) { + return null; + } else if (object.getActualInstance() instanceof AbstractOpenApiSchema) { + return getActualInstanceRecursively((AbstractOpenApiSchema)object.getActualInstance()); + } else { + return object.getActualInstance(); + } + } + + /** + * Get the schema type (e.g. anyOf, oneOf) + * + * @return the schema type + */ + public String getSchemaType() { + return schemaType; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ").append(getClass()).append(" {\\n"); + sb.append(" instance: ").append(toIndentedString(instance)).append("\\n"); + sb.append(" isNullable: ").append(toIndentedString(isNullable)).append("\\n"); + sb.append(" schemaType: ").append(toIndentedString(schemaType)).append("\\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\\n", "\\n "); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AbstractOpenApiSchema a = (AbstractOpenApiSchema) o; + return Objects.equals(this.instance, a.instance) && + Objects.equals(this.isNullable, a.isNullable) && + Objects.equals(this.schemaType, a.schemaType); + } + + @Override + public int hashCode() { + return Objects.hash(instance, isNullable, schemaType); + } + + /** + * Is nullable + * + * @return true if it's nullable + */ + public Boolean isNullable() { + if (Boolean.TRUE.equals(isNullable)) { + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } + + + +} +", + "src/main/java/test/test/runtime/model/TreeNode.java": "/* + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + + +package test.test.runtime.model; + +import java.util.Objects; +import java.util.Arrays; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; + +import javax.ws.rs.core.GenericType; + +import java.io.IOException; +import java.io.File; +import java.math.BigDecimal; +import java.net.URI; +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.util.UUID; +import java.lang.reflect.Type; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.List; +import java.util.Set; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.JsonPrimitive; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import test.test.runtime.JSON; + +/** + * TreeNode + */ +@lombok.AllArgsConstructor @lombok.experimental.SuperBuilder +public class TreeNode { + public static final String SERIALIZED_NAME_LEFT = "left"; + @SerializedName(SERIALIZED_NAME_LEFT) + private TreeNode left; + + public static final String SERIALIZED_NAME_RIGHT = "right"; + @SerializedName(SERIALIZED_NAME_RIGHT) + private TreeNode right; + + public TreeNode() { + } + + public TreeNode left(TreeNode left) { + + this.left = left; + return this; + } + + /** + * Get left + * @return left + **/ + @javax.annotation.Nullable + public TreeNode getLeft() { + return left; + } + + + public void setLeft(TreeNode left) { + this.left = left; + } + + public TreeNode right(TreeNode right) { + + this.right = right; + return this; + } + + /** + * Get right + * @return right + **/ + @javax.annotation.Nullable + public TreeNode getRight() { + return right; + } + + + public void setRight(TreeNode right) { + this.right = right; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TreeNode treeNode = (TreeNode) o; + return Objects.equals(this.left, treeNode.left) && + Objects.equals(this.right, treeNode.right); + + } + + @Override + public int hashCode() { + return Objects.hash(left, right); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class TreeNode {\\n"); + sb.append(" left: ").append(toIndentedString(left)).append("\\n"); + sb.append(" right: ").append(toIndentedString(right)).append("\\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\\n", "\\n "); + } + + + public static HashSet openapiFields; + public static HashSet openapiRequiredFields; + + static { + // a set of all properties/fields (JSON key names) + openapiFields = new HashSet(); + openapiFields.add("left"); + openapiFields.add("right"); + + // a set of required properties/fields (JSON key names) + openapiRequiredFields = new HashSet(); + } + + /** + * Validates the JSON Object and throws an exception if issues found + * + * @param jsonObj JSON Object + * @throws IOException if the JSON Object is invalid with respect to TreeNode + */ + public static void validateJsonObject(JsonObject jsonObj) throws IOException { + if (jsonObj == null) { + if (!TreeNode.openapiRequiredFields.isEmpty()) { // has required fields but JSON object is null + throw new IllegalArgumentException(String.format("The required field(s) %s in TreeNode is not found in the empty JSON string", TreeNode.openapiRequiredFields.toString())); + } + } + + Set> entries = jsonObj.entrySet(); + // check to see if the JSON string contains additional fields + for (Entry entry : entries) { + if (!TreeNode.openapiFields.contains(entry.getKey())) { + throw new IllegalArgumentException(String.format("The field \`%s\` in the JSON string is not defined in the \`TreeNode\` properties. JSON: %s", entry.getKey(), jsonObj.toString())); + } + } + // validate the optional field \`left\` + if (jsonObj.get("left") != null && !jsonObj.get("left").isJsonNull()) { + TreeNode.validateJsonObject(jsonObj.getAsJsonObject("left")); + } + // validate the optional field \`right\` + if (jsonObj.get("right") != null && !jsonObj.get("right").isJsonNull()) { + TreeNode.validateJsonObject(jsonObj.getAsJsonObject("right")); + } + } + + public static class CustomTypeAdapterFactory implements TypeAdapterFactory { + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (!TreeNode.class.isAssignableFrom(type.getRawType())) { + return null; // this class only serializes 'TreeNode' and its subtypes + } + final TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class); + final TypeAdapter thisAdapter + = gson.getDelegateAdapter(this, TypeToken.get(TreeNode.class)); + + return (TypeAdapter) new TypeAdapter() { + @Override + public void write(JsonWriter out, TreeNode value) throws IOException { + JsonObject obj = thisAdapter.toJsonTree(value).getAsJsonObject(); + elementAdapter.write(out, obj); + } + + @Override + public TreeNode read(JsonReader in) throws IOException { + JsonObject jsonObj = elementAdapter.read(in).getAsJsonObject(); + validateJsonObject(jsonObj); + return thisAdapter.fromJsonTree(jsonObj); + } + + }.nullSafe(); + } + } + + /** + * Create an instance of TreeNode given an JSON string + * + * @param jsonString JSON string + * @return An instance of TreeNode + * @throws IOException if the JSON string is invalid with respect to TreeNode + */ + public static TreeNode fromJson(String jsonString) throws IOException { + return JSON.getGson().fromJson(jsonString, TreeNode.class); + } + + /** + * Convert an instance of TreeNode to an JSON string + * + * @return JSON string + */ + public String toJson() { + return JSON.getGson().toJson(this); + } +} +", +} +`; + exports[`Java Client Code Generation Script Unit Tests Generates With single.yaml 1`] = ` { ".tsapi-manifest": "src/main/java/test/test/runtime/api/handlers/Handlers.java diff --git a/packages/type-safe-api/test/scripts/generators/__snapshots__/python.test.ts.snap b/packages/type-safe-api/test/scripts/generators/__snapshots__/python.test.ts.snap index 2ce5932c8..76f476dfd 100644 --- a/packages/type-safe-api/test/scripts/generators/__snapshots__/python.test.ts.snap +++ b/packages/type-safe-api/test/scripts/generators/__snapshots__/python.test.ts.snap @@ -28976,7 +28976,6 @@ from typing import Any, List, Union, ClassVar, Dict, Optional, TYPE_CHECKING from pydantic import Field, StrictStr, ValidationError, field_validator, BaseModel, SecretStr, StrictFloat, StrictInt, StrictBytes, StrictBool from decimal import Decimal from typing_extensions import Annotated, Literal -from test_project.models.hello_response import HelloResponse try: from typing import Self except ImportError: @@ -29329,6 +29328,3132 @@ class RESTClientObject: } `; +exports[`Python Client Code Generation Script Unit Tests Generates With recursive.yaml 1`] = ` +{ + ".gitattributes": "# ~~ Generated by projen. To modify, edit .projenrc.py and run "npx projen". + +/.gitattributes linguist-generated +/.github/workflows/pull-request-lint.yml linguist-generated +/.gitignore linguist-generated +/.projen/** linguist-generated +/.projen/deps.json linguist-generated +/.projen/files.json linguist-generated +/.projen/tasks.json linguist-generated +/pyproject.toml linguist-generated", + ".github/workflows/pull-request-lint.yml": "# ~~ Generated by projen. To modify, edit .projenrc.py and run "npx projen". + +name: pull-request-lint +on: + pull_request_target: + types: + - labeled + - opened + - synchronize + - reopened + - ready_for_review + - edited +jobs: + validate: + name: Validate PR title + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: amannn/action-semantic-pull-request@v5.4.0 + env: + GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} + with: + types: |- + feat + fix + chore + requireScope: false +", + ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.py and run "npx projen". +node_modules/ +!/.gitattributes +!/.projen/tasks.json +!/.projen/deps.json +!/.projen/files.json +!/.github/workflows/pull-request-lint.yml +!/pyproject.toml +/poetry.toml +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +*.manifest +*.spec +pip-log.txt +pip-delete-this-directory.txt +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ +*.mo +*.pot +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal +instance/ +.webassets-cache +.scrapy +docs/_build/ +.pybuilder/ +target/ +.ipynb_checkpoints +profile_default/ +ipython_config.py +__pypackages__/ +celerybeat-schedule +celerybeat.pid +*.sage.py +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +.spyderproject +.spyproject +.ropeproject +/site +.mypy_cache/ +.dmypy.json +dmypy.json +.pyre/ +.pytype/ +cython_debug/ +test_project +docs +README.md +.openapi-generator +.tsapi-manifest +!/.projenrc.py +", + ".projen/deps.json": { + "//": "~~ Generated by projen. To modify, edit .projenrc.py and run "npx projen".", + "dependencies": [ + { + "name": "projen", + "type": "devenv", + "version": "99.99.99", + }, + { + "name": "aenum", + "type": "runtime", + "version": "^3.1.11", + }, + { + "name": "aws-lambda-powertools", + "type": "runtime", + "version": "{extras=["tracer", "aws-sdk"],version="^2.28.0"}", + }, + { + "name": "pydantic", + "type": "runtime", + "version": "^2.5.2", + }, + { + "name": "python-dateutil", + "type": "runtime", + "version": "~2.8.2", + }, + { + "name": "python", + "type": "runtime", + "version": "^3.9", + }, + { + "name": "urllib3", + "type": "runtime", + "version": "~1.26.7", + }, + ], + }, + ".projen/files.json": { + "//": "~~ Generated by projen. To modify, edit .projenrc.py and run "npx projen".", + "files": [ + ".gitattributes", + ".github/workflows/pull-request-lint.yml", + ".gitignore", + ".projen/deps.json", + ".projen/files.json", + ".projen/tasks.json", + "poetry.toml", + "pyproject.toml", + ], + }, + ".projen/tasks.json": { + "//": "~~ Generated by projen. To modify, edit .projenrc.py and run "npx projen".", + "env": { + "AWS_PDK_VERSION": "0.0.0", + "PATH": "$(echo $(poetry env info -p)/bin:$PATH)", + "VIRTUAL_ENV": "$(poetry env info -p || poetry run poetry env info -p)", + }, + "tasks": { + "build": { + "description": "Full release build", + "name": "build", + "steps": [ + { + "spawn": "default", + }, + { + "spawn": "pre-compile", + }, + { + "spawn": "compile", + }, + { + "spawn": "post-compile", + }, + { + "spawn": "test", + }, + { + "spawn": "package", + }, + ], + }, + "clobber": { + "condition": "git diff --exit-code > /dev/null", + "description": "hard resets to HEAD of origin and cleans the local repo", + "env": { + "BRANCH": "$(git branch --show-current)", + }, + "name": "clobber", + "steps": [ + { + "exec": "git checkout -b scratch", + "name": "save current HEAD in "scratch" branch", + }, + { + "exec": "git checkout $BRANCH", + }, + { + "exec": "git fetch origin", + "name": "fetch latest changes from origin", + }, + { + "exec": "git reset --hard origin/$BRANCH", + "name": "hard reset to origin commit", + }, + { + "exec": "git clean -fdx", + "name": "clean all untracked files", + }, + { + "say": "ready to rock! (unpushed commits are under the "scratch" branch)", + }, + ], + }, + "compile": { + "description": "Only compile", + "name": "compile", + }, + "default": { + "description": "Synthesize project files", + "name": "default", + "steps": [ + { + "exec": "python .projenrc.py", + }, + ], + }, + "eject": { + "description": "Remove projen from the project", + "env": { + "PROJEN_EJECTING": "true", + }, + "name": "eject", + "steps": [ + { + "spawn": "default", + }, + ], + }, + "generate": { + "name": "generate", + "steps": [ + { + "exec": "npx --yes -p @aws/pdk@$AWS_PDK_VERSION type-safe-api generate --specPath spec.yaml --outputPath . --templateDirs "python" --metadata '{"srcDir":"test_project","moduleName":"test_project","projectName":"test_project"}'", + }, + ], + }, + "install": { + "description": "Install dependencies and update lockfile", + "name": "install", + "steps": [ + { + "exec": "mkdir -p test_project && touch test_project/__init__.py README.md", + }, + { + "exec": "poetry update", + }, + ], + }, + "install:ci": { + "description": "Install dependencies with frozen lockfile", + "name": "install:ci", + "steps": [ + { + "exec": "mkdir -p test_project && touch test_project/__init__.py README.md", + }, + { + "exec": "poetry check --lock && poetry install", + }, + ], + }, + "package": { + "description": "Creates the distribution package", + "name": "package", + "steps": [ + { + "exec": "poetry build", + }, + ], + }, + "post-compile": { + "description": "Runs after successful compilation", + "name": "post-compile", + }, + "pre-compile": { + "description": "Prepare the project for compilation", + "name": "pre-compile", + "steps": [ + { + "spawn": "generate", + }, + ], + }, + "publish": { + "description": "Uploads the package to PyPI.", + "name": "publish", + "steps": [ + { + "exec": "poetry publish", + }, + ], + }, + "publish:test": { + "description": "Uploads the package against a test PyPI endpoint.", + "name": "publish:test", + "steps": [ + { + "exec": "poetry publish -r testpypi", + }, + ], + }, + "test": { + "description": "Run tests", + "name": "test", + }, + }, + }, + ".tsapi-manifest": "test_project/api_client.py +test_project/api_response.py +test_project/configuration.py +test_project/exceptions.py +test_project/__init__.py +test_project/py.typed +test_project/rest.py +docs/DefaultApi.md +docs/TreeNode.md +README.md +test_project/interceptors/try_catch.py +test_project/interceptors/response_headers.py +test_project/interceptors/powertools/logger.py +test_project/interceptors/powertools/tracer.py +test_project/interceptors/powertools/metrics.py +test_project/interceptors/__init__.py +test_project/api/operation_config.py +test_project/response.py +test_project/api/default_api.py +test_project/api/__init__.py +test_project/models/__init__.py +test_project/models/tree_node.py", + "README.md": "# Recursive schema + + +This Python package is automatically generated. + +- API version: 1.0.0 + +## Requirements. + +Python 3.7+ + +## Getting Started + +See the following example for usage: + +\`\`\`python +import time +import test_project +from test_project.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to http://localhost +# See configuration.py for a list of all supported configuration parameters. +configuration = test_project.Configuration( + host = "http://localhost" +) + +# Enter a context with an instance of the API client +with test_project.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = test_project.DefaultApi(api_client) + + try: + api_response = api_instance.get_tree() + print("The response of DefaultApi->get_tree:\\n") + pprint(api_response) + except ApiException as e: + print("Exception when calling DefaultApi->get_tree: %s\\n" % e) +\`\`\` + +## Documentation for API Endpoints + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +*DefaultApi* | [**get_tree**](docs/DefaultApi.md#get_tree) | **GET** /tree | + +## Documentation For Models + + - [TreeNode](docs/TreeNode.md) +", + "docs/DefaultApi.md": "# test_project.DefaultApi + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**get_tree**](DefaultApi.md#get_tree) | **GET** /tree | + +# **get_tree** +> TreeNode get_tree() + + +### Example + +\`\`\`python +import time +import test_project +from test_project.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to http://localhost +# See configuration.py for a list of all supported configuration parameters. +configuration = test_project.Configuration( + host = "http://localhost" +) + +# Enter a context with an instance of the API client +with test_project.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = test_project.DefaultApi(api_client) + + try: + api_response = api_instance.get_tree() + print("The response of DefaultApi->get_tree:\\n") + pprint(api_response) + except ApiException as e: + print("Exception when calling DefaultApi->get_tree: %s\\n" % e) +\`\`\` + +### Parameters +This endpoint does not need any parameters. + +### Return type + +[**TreeNode**](TreeNode.md) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**200** | Ok | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +", + "docs/TreeNode.md": "# TreeNode + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**left** | [**TreeNode**](TreeNode.md) | | [optional] +**right** | [**TreeNode**](TreeNode.md) | | [optional] + +## Example + +\`\`\`python +from test_project.models.tree_node import TreeNode + +# TODO update the JSON string below +json = "{}" +# create an instance of TreeNode from a JSON string +tree_node_instance = TreeNode.from_json(json) +# print the JSON string representation of the object +print(TreeNode.to_json()) + +# convert the object into a dict +tree_node_dict = tree_node_instance.to_dict() +# create an instance of TreeNode from a dict +tree_node_form_dict = tree_node.from_dict(tree_node_dict) +\`\`\` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + +", + "poetry.toml": "# ~~ Generated by projen. To modify, edit .projenrc.py and run "npx projen". + +[repositories.testpypi] +url = "https://test.pypi.org/legacy/" +", + "pyproject.toml": "# ~~ Generated by projen. To modify, edit .projenrc.py and run "npx projen". + +[tool.poetry] +name = "test_project" +version = "1.0.0" +description = "" +authors = [ "test " ] +readme = "README.md" +include = [ "test_project", "test_project/**/*.py" ] + + [[tool.poetry.packages]] + include = "test_project" + + [tool.poetry.dependencies] + aenum = "^3.1.11" + pydantic = "^2.5.2" + python-dateutil = "~2.8.2" + python = "^3.9" + urllib3 = "~1.26.7" + + [tool.poetry.dependencies.aws-lambda-powertools] + extras = [ "tracer", "aws-sdk" ] + version = "^2.28.0" + +[tool.poetry.group.dev.dependencies] +projen = "99.99.99" + +[build-system] +requires = [ "poetry-core" ] +build-backend = "poetry.core.masonry.api" +", + "test_project/__init__.py": "# coding: utf-8 + +# flake8: noqa + +""" + Recursive schema + + No description provided + + The version of the OpenAPI document: 1.0.0 + + NOTE: This class is auto generated. + Do not edit the class manually. +""" # noqa: E501 + +__version__ = "1.0.0" + +# import apis into sdk package +from test_project.api.default_api import DefaultApi + +# import ApiClient +from test_project.api_response import ApiResponse +from test_project.api_client import ApiClient +from test_project.configuration import Configuration +from test_project.exceptions import OpenApiException +from test_project.exceptions import ApiTypeError +from test_project.exceptions import ApiValueError +from test_project.exceptions import ApiKeyError +from test_project.exceptions import ApiAttributeError +from test_project.exceptions import ApiException + +# import models into sdk package +from test_project.models.tree_node import TreeNode +", + "test_project/api/__init__.py": "# flake8: noqa + +# import apis into api package +from test_project.api.default_api import DefaultApi +", + "test_project/api/default_api.py": "# coding: utf-8 + +""" + Recursive schema + + No description provided + + The version of the OpenAPI document: 1.0.0 + + NOTE: This class is auto generated. + Do not edit the class manually. +""" # noqa: E501 + +import io +import warnings + +from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt +from typing import Dict, List, Optional, Tuple, Union, Any + +try: + from typing import Annotated +except ImportError: + from typing_extensions import Annotated + +from test_project.models.tree_node import TreeNode + +from test_project.api_client import ApiClient +from test_project.api_response import ApiResponse +from test_project.rest import RESTResponseType + + +class DefaultApi: + """NOTE: This class is auto generated + + Do not edit the class manually. + """ + + def __init__(self, api_client=None) -> None: + if api_client is None: + api_client = ApiClient.get_default() + self.api_client = api_client + + @validate_call + def get_tree( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> TreeNode: + """get_tree + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :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. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_tree_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "TreeNode" + } + + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def get_tree_with_http_info( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[TreeNode]: + """get_tree + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :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. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_tree_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "TreeNode" + } + + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def get_tree_without_preload_content( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """get_tree + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :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. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_tree_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "TreeNode" + } + + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _get_tree_serialize( + self, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> Tuple: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[str, str] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + + + # set the HTTP header \`Accept\` + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + ] + + return self.api_client.param_serialize( + method='GET', + resource_path='/tree', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + +", + "test_project/api/operation_config.py": "from __future__ import annotations +import urllib.parse +import json +from typing import Callable, Any, Dict, List, NamedTuple, TypeVar, Generic, Union, TypedDict, Protocol, Optional, Literal, Annotated +from functools import wraps +from dataclasses import dataclass, fields +from datetime import datetime +import dateutil.parser +from pydantic import BaseModel, Field, StrictStr, conlist, StrictBool, StrictInt, StrictFloat + +from test_project.models import * + +T = TypeVar('T') + +# Generic type for object keyed by operation names +@dataclass +class OperationConfig(Generic[T]): + get_tree: T + ... + +# Look up path and http method for a given operation name +OperationLookup = { + "get_tree": { + "path": "/tree", + "method": "GET", + "contentTypes": ["application/json"] + }, +} + +class Operations: + @staticmethod + def all(value: T) -> OperationConfig[T]: + """ + Returns an OperationConfig with the same value for every operation + """ + return OperationConfig(**{ operation_id: value for operation_id, _ in OperationLookup.items() }) + +def uri_decode(value): + """ + URI decode a value or list of values + """ + if isinstance(value, list): + return [urllib.parse.unquote(v) for v in value] + return urllib.parse.unquote(value) + +def decode_request_parameters(parameters): + """ + URI decode api request parameters (path, query or multi-value query) + """ + return { key: uri_decode(parameters[key]) if parameters[key] is not None else parameters[key] for key in parameters.keys() } + +def parse_body(body, content_types, model): + """ + Parse the body of an api request into the given model if present + """ + if len([c for c in content_types if c != 'application/json']) == 0: + if model != Any: + body = model.model_validate(json.loads(body)) + else: + body = json.loads(body or '{}') + return body + +def assert_required(required, base_name, parameters): + if required and parameters.get(base_name) is None: + raise Exception(f"Missing required request parameter '{base_name}'") + +def coerce_float(base_name, s): + try: + return float(s) + except Exception as e: + raise Exception(f"Expected a number for request parameter '{base_name}'") + +def coerce_int(base_name, s): + try: + return int(s) + except Exception as e: + raise Exception(f"Expected an integer for request parameter '{base_name}'") + +def coerce_datetime(base_name, s): + try: + return dateutil.parser.parse(s) + except Exception as e: + raise Exception(f"Expected a valid date (iso format) for request parameter '{base_name}'") + +def coerce_bool(base_name, s): + if s == "true": + return True + elif s == "false": + return False + raise Exception(f"Expected a boolean (true or false) for request parameter '{base_name}'") + +def coerce_parameter(base_name, data_type, raw_string_parameters, raw_string_array_parameters, required): + if data_type == "float": + assert_required(required, base_name, raw_string_parameters) + param = raw_string_parameters.get(base_name) + return None if param is None else coerce_float(base_name, param) + elif data_type == "int": + assert_required(required, base_name, raw_string_parameters) + param = raw_string_parameters.get(base_name) + return None if param is None else coerce_int(base_name, param) + elif data_type == "bool": + assert_required(required, base_name, raw_string_parameters) + param = raw_string_parameters.get(base_name) + return None if param is None else coerce_bool(base_name, param) + elif data_type == "datetime": + assert_required(required, base_name, raw_string_parameters) + param = raw_string_parameters.get(base_name) + return None if param is None else coerce_datetime(base_name, param) + elif data_type == "List[float]": + assert_required(required, base_name, raw_string_array_parameters) + param = raw_string_array_parameters.get(base_name) + return None if param is None else [coerce_float(base_name, p) for p in param] + elif data_type == "List[int]": + assert_required(required, base_name, raw_string_array_parameters) + param = raw_string_array_parameters.get(base_name) + return None if param is None else [coerce_int(base_name, p) for p in param] + elif data_type == "List[bool]": + assert_required(required, base_name, raw_string_array_parameters) + param = raw_string_array_parameters.get(base_name) + return None if param is None else [coerce_bool(base_name, p) for p in param] + elif data_type == "List[datetime]": + assert_required(required, base_name, raw_string_array_parameters) + param = raw_string_array_parameters.get(base_name) + return None if param is None else [coerce_datetime(base_name, p) for p in param] + elif data_type == "List[str]": + assert_required(required, base_name, raw_string_array_parameters) + return raw_string_array_parameters.get(base_name) + else: # data_type == "str" + assert_required(required, base_name, raw_string_parameters) + return raw_string_parameters.get(base_name) + + +def extract_response_headers_from_interceptors(interceptors): + headers = {} + for interceptor in interceptors: + additional_headers = getattr(interceptor, "__type_safe_api_response_headers", None) + headers = {**headers, **(additional_headers or {})} + return headers + + +RequestParameters = TypeVar('RequestParameters') +RequestBody = TypeVar('RequestBody') +ResponseBody = TypeVar('ResponseBody') +StatusCode = TypeVar('StatusCode') + +@dataclass +class ApiRequest(Generic[RequestParameters, RequestBody]): + request_parameters: RequestParameters + body: RequestBody + event: Any + context: Any + interceptor_context: Dict[str, Any] + +@dataclass +class ChainedApiRequest(ApiRequest[RequestParameters, RequestBody], + Generic[RequestParameters, RequestBody]): + + chain: 'HandlerChain' + +@dataclass +class ApiResponse(Exception, Generic[StatusCode, ResponseBody]): + status_code: StatusCode + headers: Dict[str, str] + body: ResponseBody + multi_value_headers: Optional[Dict[str, List[str]]] = None + +class HandlerChain(Generic[RequestParameters, RequestBody, StatusCode, ResponseBody]): + def next(self, request: ChainedApiRequest[RequestParameters, RequestBody]) -> ApiResponse[StatusCode, ResponseBody]: + raise Exception("Not implemented!") + +def _build_handler_chain(_interceptors, handler) -> HandlerChain: + if len(_interceptors) == 0: + class BaseHandlerChain(HandlerChain[RequestParameters, RequestBody, StatusCode, ResponseBody]): + def next(self, request: ApiRequest[RequestParameters, RequestBody]) -> ApiResponse[StatusCode, ResponseBody]: + return handler(request) + return BaseHandlerChain() + else: + interceptor = _interceptors[0] + + class RemainingHandlerChain(HandlerChain[RequestParameters, RequestBody, StatusCode, ResponseBody]): + def next(self, request: ChainedApiRequest[RequestParameters, RequestBody]) -> ApiResponse[StatusCode, ResponseBody]: + return interceptor(ChainedApiRequest( + request_parameters = request.request_parameters, + body = request.body, + event = request.event, + context = request.context, + interceptor_context = request.interceptor_context, + chain = _build_handler_chain(_interceptors[1:len(_interceptors)], handler), + )) + return RemainingHandlerChain() + + +class GetTreeRequestParameters(BaseModel): + """ + Query, path and header parameters for the GetTree operation + """ + + class Config: + """Pydantic configuration""" + populate_by_name = True + validate_assignment = True + + def to_json(self) -> str: + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> GetTreeRequestParameters: + return cls.from_dict(json.loads(json_str)) + + def to_dict(self): + return self.model_dump(exclude={}, exclude_none=True) + + @classmethod + def from_dict(cls, obj: dict) -> GetTreeRequestParameters: + if obj is None: + return None + return GetTreeRequestParameters.model_validate(obj) + + +# Request body type (default to Any when no body parameters exist, or leave unchanged as str if it's a primitive type) +GetTreeRequestBody = Any + +GetTree200OperationResponse = ApiResponse[Literal[200], TreeNode] + +GetTreeOperationResponses = Union[GetTree200OperationResponse, ] + +# Request type for get_tree +GetTreeRequest = ApiRequest[GetTreeRequestParameters, GetTreeRequestBody] +GetTreeChainedRequest = ChainedApiRequest[GetTreeRequestParameters, GetTreeRequestBody] + +class GetTreeHandlerFunction(Protocol): + def __call__(self, input: GetTreeRequest, **kwargs) -> GetTreeOperationResponses: + ... + +GetTreeInterceptor = Callable[[GetTreeChainedRequest], GetTreeOperationResponses] + +def get_tree_handler(_handler: GetTreeHandlerFunction = None, interceptors: List[GetTreeInterceptor] = []): + """ + Decorator for an api handler for the get_tree operation, providing a typed interface for inputs and outputs + """ + def _handler_wrapper(handler: GetTreeHandlerFunction): + @wraps(handler) + def wrapper(event, context, additional_interceptors = [], **kwargs): + all_interceptors = additional_interceptors + interceptors + + raw_string_parameters = decode_request_parameters({ + **(event.get('pathParameters', {}) or {}), + **(event.get('queryStringParameters', {}) or {}), + **(event.get('headers', {}) or {}), + }) + raw_string_array_parameters = decode_request_parameters({ + **(event.get('multiValueQueryStringParameters', {}) or {}), + **(event.get('multiValueHeaders', {}) or {}), + }) + + def response_headers_for_status_code(status_code): + headers_for_status = {} + return headers_for_status + + request_parameters = None + try: + request_parameters = GetTreeRequestParameters.from_dict({ + }) + except Exception as e: + return { + 'statusCode': 400, + 'headers': {**response_headers_for_status_code(400), **extract_response_headers_from_interceptors(all_interceptors)}, + 'body': '{"message": "' + str(e) + '"}', + } + + body = {} + interceptor_context = { + "operationId": "get_tree", + } + + chain = _build_handler_chain(all_interceptors, handler) + response = chain.next(ApiRequest( + request_parameters, + body, + event, + context, + interceptor_context, + ), **kwargs) + + response_headers = {** (response.headers or {}), **response_headers_for_status_code(response.status_code)} + response_body = '' + if response.body is None: + pass + elif response.status_code == 200: + response_body = response.body.to_json() + + return { + 'statusCode': response.status_code, + 'headers': response_headers, + 'multiValueHeaders': response.multi_value_headers or {}, + 'body': response_body, + } + return wrapper + + # Support use as a decorator with no arguments, or with interceptor arguments + if callable(_handler): + return _handler_wrapper(_handler) + elif _handler is None: + return _handler_wrapper + else: + raise Exception("Positional arguments are not supported by get_tree_handler.") + +Interceptor = Callable[[ChainedApiRequest[RequestParameters, RequestBody]], ApiResponse[StatusCode, ResponseBody]] + +def concat_method_and_path(method: str, path: str): + return "{}||{}".format(method.lower(), path) + +OperationIdByMethodAndPath = { concat_method_and_path(method_and_path["method"], method_and_path["path"]): operation for operation, method_and_path in OperationLookup.items() } + +@dataclass +class HandlerRouterHandlers: + get_tree: Callable[[Dict, Any], Dict] + +def handler_router(handlers: HandlerRouterHandlers, interceptors: List[Interceptor] = []): + """ + Returns a lambda handler which can be used to route requests to the appropriate typed lambda handler function. + """ + _handlers = { field.name: getattr(handlers, field.name) for field in fields(handlers) } + + def handler_wrapper(event, context): + operation_id = OperationIdByMethodAndPath[concat_method_and_path(event['requestContext']['httpMethod'], event['requestContext']['resourcePath'])] + handler = _handlers[operation_id] + return handler(event, context, additional_interceptors=interceptors) + return handler_wrapper +", + "test_project/api_client.py": "# coding: utf-8 + +""" + Recursive schema + + No description provided + + The version of the OpenAPI document: 1.0.0 + + NOTE: This class is auto generated. + Do not edit the class manually. +""" # noqa: E501 + + +import atexit +import datetime +from dateutil.parser import parse +import json +import mimetypes +import os +import re +import tempfile + +from urllib.parse import quote +from typing import Tuple, Optional, List + +from test_project.configuration import Configuration +from test_project.api_response import ApiResponse +import test_project.models +from test_project import rest +from test_project.exceptions import ( + ApiValueError, + ApiException, + BadRequestException, + UnauthorizedException, + ForbiddenException, + NotFoundException, + ServiceException +) + + +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, + '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 + self.cookie = cookie + # Set default User-Agent. + self.user_agent = 'OpenAPI-Generator/1.0.0/python' + self.client_side_validation = configuration.client_side_validation + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @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 + ) -> Tuple: + + """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 + ) + 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: + 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 + + + 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 = self.rest_client.request( + method, url, + headers=header_params, + body=body, post_params=post_params, + _request_timeout=_request_timeout + ) + + except ApiException as e: + if e.body: + e.body = e.body.decode('utf-8') + raise e + + return response_data + + def response_deserialize( + self, + response_data=None, + response_types_map=None + ) -> ApiResponse: + """Deserializes response into an object. + :param response_data: RESTResponse object to be deserialized. + :param response_types_map: dict of response types. + :return: ApiResponse + """ + + + 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) + + if not 200 <= response_data.status <= 299: + if response_data.status == 400: + raise BadRequestException(http_resp=response_data) + + if response_data.status == 401: + raise UnauthorizedException(http_resp=response_data) + + if response_data.status == 403: + raise ForbiddenException(http_resp=response_data) + + if response_data.status == 404: + raise NotFoundException(http_resp=response_data) + + if 500 <= response_data.status <= 599: + raise ServiceException(http_resp=response_data) + raise ApiException(http_resp=response_data) + + # deserialize response data + + if response_type == "bytearray": + return_data = response_data.data + elif response_type is None: + return_data = None + elif response_type == "file": + return_data = self.__deserialize_file(response_data) + else: + match = None + content_type = response_data.getheader('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) + + return ApiResponse( + status_code = response_data.status, + data = return_data, + headers = response_data.getheaders(), + 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 str, int, long, float, bool, return directly. + If obj is datetime.datetime, datetime.date + convert to string in iso8601 format. + 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, self.PRIMITIVE_TYPES): + return 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, 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. + obj_dict = obj.to_dict() + + return { + key: self.sanitize_for_serialization(val) + for key, val in obj_dict.items() + } + + def deserialize(self, response_text, response_type): + """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. + + :return: deserialized object. + """ + + # fetch data from response object + try: + data = json.loads(response_text) + except ValueError: + data = response_text + + 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['): + sub_kls = re.match(r'List\\[(.*)]', klass).group(1) + return [self.__deserialize(sub_data, sub_kls) + for sub_data in data] + + if klass.startswith('Dict['): + sub_kls = re.match(r'Dict\\[([^,]*), (.*)]', klass).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(test_project.models, klass) + + if klass in self.PRIMITIVE_TYPES: + return self.__deserialize_primitive(data, klass) + elif klass == object: + return self.__deserialize_object(data) + elif klass == datetime.date: + return self.__deserialize_date(data) + elif klass == datetime.datetime: + return self.__deserialize_datetime(data) + 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 = [] + 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 = [] + 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, 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(item) for item in new_params]) + + def files_parameters(self, files=None): + """Builds form parameters. + + :param files: File parameters. + :return: Form parameters with files. + """ + params = [] + + if files: + for k, v in files.items(): + if not v: + continue + file_names = v if type(v) is list else [v] + for n in file_names: + with open(n, 'rb') as f: + filename = os.path.basename(f.name) + filedata = f.read() + 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'] + 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.getheader("Content-Disposition") + if content_disposition: + filename = re.search( + r'filename=[\\'"]?([^\\'"\\s]+)[\\'"]?', + content_disposition + ).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_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) +", + "test_project/api_response.py": """"API response object.""" + +from __future__ import annotations +from typing import Any, Dict, Optional, Generic, TypeVar +from pydantic import Field, StrictInt, StrictStr, StrictBytes, BaseModel + +T = TypeVar("T") + +class ApiResponse(BaseModel, Generic[T]): + """ + API response object + """ + + status_code: StrictInt = Field(description="HTTP status code") + headers: Optional[Dict[StrictStr, StrictStr]] = Field(None, description="HTTP headers") + data: T = Field(description="Deserialized data given the data type") + raw_data: StrictBytes = Field(description="Raw data (HTTP response body)") + + model_config = { + "arbitrary_types_allowed": True + } +", + "test_project/configuration.py": "# coding: utf-8 + +""" + Recursive schema + + No description provided + + The version of the OpenAPI document: 1.0.0 + + NOTE: This class is auto generated. + Do not edit the class manually. +""" # noqa: E501 + + +import copy +import logging +import sys +import urllib3 + +import http.client as httplib + +JSON_SCHEMA_VALIDATION_KEYWORDS = { + 'multipleOf', 'maximum', 'exclusiveMaximum', + 'minimum', 'exclusiveMinimum', 'maxLength', + 'minLength', 'pattern', 'maxItems', 'minItems' +} + +class Configuration: + """This class contains various settings of the API client. + + :param host: Base url. + :param api_key: Dict to store API key(s). + Each entry in the dict specifies an API key. + The dict key is the name of the security scheme in the OAS specification. + The dict value is the API key secret. + :param api_key_prefix: Dict to store API prefix (e.g. Bearer). + The dict key is the name of the security scheme in the OAS specification. + The dict value is an API key prefix when generating the auth data. + :param username: Username for HTTP basic authentication. + :param password: Password for HTTP basic authentication. + :param access_token: Access token. + :param server_index: Index to servers configuration. + :param server_variables: Mapping with string values to replace variables in + templated server configuration. The validation of enums is performed for + variables with defined enum values before. + :param server_operation_index: Mapping from operation ID to an index to server + configuration. + :param server_operation_variables: Mapping from operation ID to a mapping with + string values to replace variables in templated server configuration. + The validation of enums is performed for variables with defined enum + values before. + :param ssl_ca_cert: str - the path to a file of concatenated CA certificates + in PEM format. + + """ + + _default = None + + def __init__(self, host=None, + api_key=None, api_key_prefix=None, + username=None, password=None, + access_token=None, + server_index=None, server_variables=None, + server_operation_index=None, server_operation_variables=None, + ssl_ca_cert=None, + ) -> None: + """Constructor + """ + self._base_path = "http://localhost" if host is None else host + """Default Base url + """ + self.server_index = 0 if server_index is None and host is None else server_index + self.server_operation_index = server_operation_index or {} + """Default server index + """ + self.server_variables = server_variables or {} + self.server_operation_variables = server_operation_variables or {} + """Default server variables + """ + self.temp_folder_path = None + """Temp file folder for downloading files + """ + # Authentication Settings + self.api_key = {} + if api_key: + self.api_key = api_key + """dict to store API key(s) + """ + self.api_key_prefix = {} + if api_key_prefix: + self.api_key_prefix = api_key_prefix + """dict to store API prefix (e.g. Bearer) + """ + self.refresh_api_key_hook = None + """function hook to refresh API key if expired + """ + self.username = username + """Username for HTTP basic authentication + """ + self.password = password + """Password for HTTP basic authentication + """ + self.access_token = access_token + """Access token + """ + self.logger = {} + """Logging Settings + """ + self.logger["package_logger"] = logging.getLogger("test_project") + self.logger["urllib3_logger"] = logging.getLogger("urllib3") + self.logger_format = '%(asctime)s %(levelname)s %(message)s' + """Log format + """ + self.logger_stream_handler = None + """Log stream handler + """ + self.logger_file_handler = None + """Log file handler + """ + self.logger_file = None + """Debug file location + """ + self.debug = False + """Debug switch + """ + + self.verify_ssl = True + """SSL/TLS verification + Set this to false to skip verifying SSL certificate when calling API + from https server. + """ + self.ssl_ca_cert = ssl_ca_cert + """Set this to customize the certificate file to verify the peer. + """ + self.cert_file = None + """client certificate file + """ + self.key_file = None + """client key file + """ + self.assert_hostname = None + """Set this to True/False to enable/disable SSL hostname verification. + """ + self.tls_server_name = None + """SSL/TLS Server Name Indication (SNI) + Set this to the SNI value expected by the server. + """ + + + self.proxy = None + """Proxy URL + """ + self.proxy_headers = None + """Proxy headers + """ + self.safe_chars_for_path_param = '' + """Safe chars for path_param + """ + self.retries = None + """Adding retries to override urllib3 default value 3 + """ + # Enable client side validation + self.client_side_validation = True + + self.socket_options = None + """Options to pass down to the underlying urllib3 socket + """ + + self.datetime_format = "%Y-%m-%dT%H:%M:%S.%f%z" + """datetime format + """ + + self.date_format = "%Y-%m-%d" + """date format + """ + + def __deepcopy__(self, memo): + cls = self.__class__ + result = cls.__new__(cls) + memo[id(self)] = result + for k, v in self.__dict__.items(): + if k not in ('logger', 'logger_file_handler'): + setattr(result, k, copy.deepcopy(v, memo)) + # shallow copy of loggers + result.logger = copy.copy(self.logger) + # use setters to configure loggers + result.logger_file = self.logger_file + result.debug = self.debug + return result + + def __setattr__(self, name, value): + object.__setattr__(self, name, value) + + @classmethod + def set_default(cls, default): + """Set default instance of configuration. + + It stores default configuration, which can be + returned by get_default_copy method. + + :param default: object of Configuration + """ + cls._default = default + + @classmethod + def get_default_copy(cls): + """Deprecated. Please use \`get_default\` instead. + + Deprecated. Please use \`get_default\` instead. + + :return: The configuration object. + """ + return cls.get_default() + + @classmethod + def get_default(cls): + """Return the default configuration. + + This method returns newly created, based on default constructor, + object of Configuration class or returns a copy of default + configuration. + + :return: The configuration object. + """ + if cls._default is None: + cls._default = Configuration() + return cls._default + + @property + def logger_file(self): + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + return self.__logger_file + + @logger_file.setter + def logger_file(self, value): + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + self.__logger_file = value + if self.__logger_file: + # If set logging file, + # then add file handler and remove stream handler. + self.logger_file_handler = logging.FileHandler(self.__logger_file) + self.logger_file_handler.setFormatter(self.logger_formatter) + for _, logger in self.logger.items(): + logger.addHandler(self.logger_file_handler) + + @property + def debug(self): + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + return self.__debug + + @debug.setter + def debug(self, value): + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + self.__debug = value + if self.__debug: + # if debug status is True, turn on debug logging + for _, logger in self.logger.items(): + logger.setLevel(logging.DEBUG) + # turn on httplib debug + httplib.HTTPConnection.debuglevel = 1 + else: + # if debug status is False, turn off debug logging, + # setting log level to default \`logging.WARNING\` + for _, logger in self.logger.items(): + logger.setLevel(logging.WARNING) + # turn off httplib debug + httplib.HTTPConnection.debuglevel = 0 + + @property + def logger_format(self): + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + return self.__logger_format + + @logger_format.setter + def logger_format(self, value): + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + self.__logger_format = value + self.logger_formatter = logging.Formatter(self.__logger_format) + + def get_api_key_with_prefix(self, identifier, alias=None): + """Gets API key (with prefix if set). + + :param identifier: The identifier of apiKey. + :param alias: The alternative identifier of apiKey. + :return: The token for api key authentication. + """ + if self.refresh_api_key_hook is not None: + self.refresh_api_key_hook(self) + key = self.api_key.get(identifier, self.api_key.get(alias) if alias is not None else None) + if key: + prefix = self.api_key_prefix.get(identifier) + if prefix: + return "%s %s" % (prefix, key) + else: + return key + + def get_basic_auth_token(self): + """Gets HTTP basic authentication header (string). + + :return: The token for basic HTTP authentication. + """ + username = "" + if self.username is not None: + username = self.username + password = "" + if self.password is not None: + password = self.password + return urllib3.util.make_headers( + basic_auth=username + ':' + password + ).get('authorization') + + def auth_settings(self): + """Gets Auth Settings dict for api client. + + :return: The Auth Settings information dict. + """ + auth = {} + return auth + + def to_debug_report(self): + """Gets the essential information for debugging. + + :return: The report for debugging. + """ + return "Python SDK Debug Report:\\n"\\ + "OS: {env}\\n"\\ + "Python Version: {pyversion}\\n"\\ + "Version of the API: 1.0.0\\n"\\ + "SDK Package Version: 1.0.0".\\ + format(env=sys.platform, pyversion=sys.version) + + def get_host_settings(self): + """Gets an array of host settings + + :return: An array of host settings + """ + return [ + { + 'url': "", + 'description': "No description provided", + } + ] + + def get_host_from_settings(self, index, variables=None, servers=None): + """Gets host URL based on the index and variables + :param index: array index of the host settings + :param variables: hash of variable and the corresponding value + :param servers: an array of host settings or None + :return: URL based on host settings + """ + if index is None: + return self._base_path + + variables = {} if variables is None else variables + servers = self.get_host_settings() if servers is None else servers + + try: + server = servers[index] + except IndexError: + raise ValueError( + "Invalid index {0} when selecting the host settings. " + "Must be less than {1}".format(index, len(servers))) + + url = server['url'] + + # go through variables and replace placeholders + for variable_name, variable in server.get('variables', {}).items(): + used_value = variables.get( + variable_name, variable['default_value']) + + if 'enum_values' in variable \\ + and used_value not in variable['enum_values']: + raise ValueError( + "The variable \`{0}\` in the host URL has invalid value " + "{1}. Must be {2}.".format( + variable_name, variables[variable_name], + variable['enum_values'])) + + url = url.replace("{" + variable_name + "}", used_value) + + return url + + @property + def host(self): + """Return generated host.""" + return self.get_host_from_settings(self.server_index, variables=self.server_variables) + + @host.setter + def host(self, value): + """Fix base path.""" + self._base_path = value + self.server_index = None +", + "test_project/exceptions.py": "# coding: utf-8 + +""" + Recursive schema + + No description provided + + The version of the OpenAPI document: 1.0.0 + + NOTE: This class is auto generated. + Do not edit the class manually. +""" # noqa: E501 + +class OpenApiException(Exception): + """The base exception class for all OpenAPIExceptions""" + + +class ApiTypeError(OpenApiException, TypeError): + def __init__(self, msg, path_to_item=None, valid_classes=None, + key_type=None) -> None: + """ Raises an exception for TypeErrors + + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (list): a list of keys an indices to get to the + current_item + None if unset + valid_classes (tuple): the primitive classes that current item + should be an instance of + None if unset + key_type (bool): False if our value is a value in a dict + True if it is a key in a dict + False if our item is an item in a list + None if unset + """ + self.path_to_item = path_to_item + self.valid_classes = valid_classes + self.key_type = key_type + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiTypeError, self).__init__(full_msg) + + +class ApiValueError(OpenApiException, ValueError): + def __init__(self, msg, path_to_item=None) -> None: + """ + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (list) the path to the exception in the + received_data dict. None if unset + """ + + self.path_to_item = path_to_item + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiValueError, self).__init__(full_msg) + + +class ApiAttributeError(OpenApiException, AttributeError): + def __init__(self, msg, path_to_item=None) -> None: + """ + Raised when an attribute reference or assignment fails. + + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (None/list) the path to the exception in the + received_data dict + """ + self.path_to_item = path_to_item + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiAttributeError, self).__init__(full_msg) + + +class ApiKeyError(OpenApiException, KeyError): + def __init__(self, msg, path_to_item=None) -> None: + """ + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (None/list) the path to the exception in the + received_data dict + """ + self.path_to_item = path_to_item + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiKeyError, self).__init__(full_msg) + + +class ApiException(OpenApiException): + + def __init__(self, status=None, reason=None, http_resp=None) -> None: + if http_resp: + self.status = http_resp.status + self.reason = http_resp.reason + self.body = http_resp.data.decode('utf-8') + self.headers = http_resp.getheaders() + else: + self.status = status + self.reason = reason + self.body = None + self.headers = None + + def __str__(self): + """Custom error messages for exception""" + error_message = "({0})\\n"\\ + "Reason: {1}\\n".format(self.status, self.reason) + if self.headers: + error_message += "HTTP response headers: {0}\\n".format( + self.headers) + + if self.body: + error_message += "HTTP response body: {0}\\n".format(self.body) + + return error_message + +class BadRequestException(ApiException): + + def __init__(self, status=None, reason=None, http_resp=None) -> None: + super(BadRequestException, self).__init__(status, reason, http_resp) + +class NotFoundException(ApiException): + + def __init__(self, status=None, reason=None, http_resp=None) -> None: + super(NotFoundException, self).__init__(status, reason, http_resp) + + +class UnauthorizedException(ApiException): + + def __init__(self, status=None, reason=None, http_resp=None) -> None: + super(UnauthorizedException, self).__init__(status, reason, http_resp) + + +class ForbiddenException(ApiException): + + def __init__(self, status=None, reason=None, http_resp=None) -> None: + super(ForbiddenException, self).__init__(status, reason, http_resp) + + +class ServiceException(ApiException): + + def __init__(self, status=None, reason=None, http_resp=None) -> None: + super(ServiceException, self).__init__(status, reason, http_resp) + + +def render_path(path_to_item): + """Returns a string representation of a path""" + result = "" + for pth in path_to_item: + if isinstance(pth, int): + result += "[{0}]".format(pth) + else: + result += "['{0}']".format(pth) + return result +", + "test_project/interceptors/__init__.py": "from test_project.interceptors.response_headers import cors_interceptor +from test_project.interceptors.try_catch import try_catch_interceptor +from test_project.interceptors.powertools.logger import LoggingInterceptor +from test_project.interceptors.powertools.tracer import TracingInterceptor +from test_project.interceptors.powertools.metrics import MetricsInterceptor + +# All default interceptors, for logging, tracing, metrics, cors headers and error handling +INTERCEPTORS = [ + cors_interceptor, + LoggingInterceptor().intercept, + try_catch_interceptor, + TracingInterceptor().intercept, + MetricsInterceptor().intercept, +] +", + "test_project/interceptors/powertools/logger.py": "from aws_lambda_powertools import Logger +from aws_lambda_powertools.logging.logger import _is_cold_start +from test_project.api.operation_config import ApiResponse, ChainedApiRequest + +logger = Logger() + +class LoggingInterceptor: + + def intercept(self, request: ChainedApiRequest) -> ApiResponse: + """ + An interceptor for adding an aws powertools logger to the interceptor context + See: https://docs.powertools.aws.dev/lambda/python/latest/core/logger/ + """ + request.interceptor_context["logger"] = logger + + # Add the operation id, lambda context and cold start + logger.append_keys( + operationId=request.interceptor_context["operationId"], + **request.context.__dict__, + cold_start=_is_cold_start() + ) + response = request.chain.next(request) + logger.remove_keys(["operationId"]) + + return response + + @staticmethod + def get_logger(request: ChainedApiRequest) -> Logger: + if request.interceptor_context.get("logger") is None: + raise Exception("No logger found. Did you configure the LoggingInterceptor?") + return request.interceptor_context["logger"] +", + "test_project/interceptors/powertools/metrics.py": "from aws_lambda_powertools import Metrics +from test_project.api.operation_config import ApiResponse, ChainedApiRequest + +metrics = Metrics() + +class MetricsInterceptor: + + def intercept(self, request: ChainedApiRequest) -> ApiResponse: + """ + An interceptor for adding an aws powertools metrics instance to the interceptor context + See: https://docs.powertools.aws.dev/lambda/python/latest/core/metrics/ + """ + operation_id = request.interceptor_context["operationId"] + + # Set the namespace if not set via environment variables + if metrics.namespace is None: + metrics.namespace = operation_id + + request.interceptor_context["metrics"] = metrics + + try: + metrics.add_dimension(name="operationId", value=operation_id) + return request.chain.next(request) + finally: + metrics.flush_metrics() + + @staticmethod + def get_metrics(request: ChainedApiRequest) -> Metrics: + """ + Retrieve the metrics logger from the request + """ + if request.interceptor_context.get("metrics") is None: + raise Exception("No metrics found. Did you configure the MetricsInterceptor?") + return request.interceptor_context["metrics"] +", + "test_project/interceptors/powertools/tracer.py": "from aws_lambda_powertools import Tracer +from test_project.api.operation_config import ApiResponse, ChainedApiRequest + +tracer = Tracer() +is_cold_start = True + +class TracingInterceptor: + def __init__(self, add_response_as_metadata: bool = False): + self._add_response_as_metadata = add_response_as_metadata + + def intercept(self, request: ChainedApiRequest) -> ApiResponse: + """ + An interceptor for adding an aws powertools tracer to the interceptor context + See: https://docs.powertools.aws.dev/lambda/python/latest/core/tracer/ + """ + request.interceptor_context["tracer"] = tracer + + operation_id = request.interceptor_context["operationId"] + + with tracer.provider.in_subsegment(name=f"## {operation_id}") as subsegment: + try: + result = request.chain.next(request) + tracer._add_response_as_metadata( + method_name=operation_id, + data=result, + subsegment=subsegment, + capture_response=self._add_response_as_metadata + ) + return result + except Exception as e: + tracer._add_full_exception_as_metadata( + method_name=operation_id, + error=e, + subsegment=subsegment, + capture_error=True + ) + raise + finally: + global is_cold_start + subsegment.put_annotation(key="ColdStart", value=is_cold_start) + is_cold_start = False + + @staticmethod + def get_tracer(request: ChainedApiRequest) -> Tracer: + """ + Retrieve the metrics logger from the request + """ + if request.interceptor_context.get("tracer") is None: + raise Exception("No tracer found. Did you configure the TracingInterceptor?") + return request.interceptor_context["tracer"] +", + "test_project/interceptors/response_headers.py": "from test_project.api.operation_config import ApiResponse, ChainedApiRequest +from typing import Dict + +CORS_HEADERS = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": "*", +} + +def build_response_headers_interceptor(headers: Dict[str, str]): + """ + Build an interceptor for adding headers to the response. + """ + def response_headers_interceptor(request: ChainedApiRequest) -> ApiResponse: + result = request.chain.next(request) + result.headers = { **headers, **(result.headers or {}) } + return result + + # Any error responses returned during request validation will include the headers + response_headers_interceptor.__type_safe_api_response_headers = headers + + return response_headers_interceptor + +# Cors interceptor allows all origins and headers. Use build_response_headers_interceptors to customise +cors_interceptor = build_response_headers_interceptor(CORS_HEADERS) + +", + "test_project/interceptors/try_catch.py": "from test_project.api.operation_config import ApiResponse, ChainedApiRequest +from test_project.response import Response + + +def try_catch_interceptor(request: ChainedApiRequest) -> ApiResponse: + """ + Interceptor for catching unhandled exceptions and returning a 500 error. + Uncaught exceptions which are ApiResponses will be returned, such that deeply nested code may return error + responses, eg: \`throw Response.not_found(...)\` + """ + try: + return request.chain.next(request) + except ApiResponse as response: + # If the error is a response, return it as the response + return response + except Exception as e: + if request.interceptor_context.get("logger") is not None: + request.interceptor_context.get("logger").exception("Interceptor caught exception") + else: + print("Interceptor caught exception") + print(e) + + return Response.internal_failure({ "message": "Internal Error" }) +", + "test_project/models/__init__.py": "# coding: utf-8 + +# flake8: noqa +""" + Recursive schema + + No description provided + + The version of the OpenAPI document: 1.0.0 + + NOTE: This class is auto generated. + Do not edit the class manually. +""" # noqa: E501 + +# import models into model package +from test_project.models.tree_node import TreeNode +", + "test_project/models/tree_node.py": "# coding: utf-8 + +""" + Recursive schema + + No description provided + + The version of the OpenAPI document: 1.0.0 + + NOTE: This class is auto generated. + Do not edit the class manually. +""" # noqa: E501 + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json +from enum import Enum +from datetime import date, datetime +from typing import Any, List, Union, ClassVar, Dict, Optional, TYPE_CHECKING +from pydantic import Field, StrictStr, ValidationError, field_validator, BaseModel, SecretStr, StrictFloat, StrictInt, StrictBytes, StrictBool +from decimal import Decimal +from typing_extensions import Annotated, Literal +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +class TreeNode(BaseModel): + """ + TreeNode + """ # noqa: E501 + left: Optional[TreeNode] = None + right: Optional[TreeNode] = None + __properties: ClassVar[List[str]] = ["left", "right"] + + + model_config = { + "populate_by_name": True, + "validate_assignment": True + } + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of TreeNode from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + \`self.model_dump(by_alias=True)\`: + + * \`None\` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value \`None\` + are ignored. + """ + _dict = self.model_dump( + by_alias=True, + exclude={ + }, + exclude_none=True, + ) + # override the default output from pydantic by calling \`to_dict()\` of left + if self.left: + _dict['left'] = self.left.to_dict() + # override the default output from pydantic by calling \`to_dict()\` of right + if self.right: + _dict['right'] = self.right.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of TreeNode from a dict""" + + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "left": TreeNode.from_dict(obj.get("left")) if obj.get("left") is not None else None, + "right": TreeNode.from_dict(obj.get("right")) if obj.get("right") is not None else None + }) + return _obj + +", + "test_project/py.typed": "", + "test_project/response.py": "from typing import TypeVar, Generic, Dict, List +from test_project.api.operation_config import ApiResponse + +ResponseBody = TypeVar("ResponseBody") + +class Response(Generic[ResponseBody]): + """ + Helpers for constructing api responses + """ + + @staticmethod + def success(body: ResponseBody, headers: Dict[str, str] = {}, multi_value_headers: Dict[str, List[str]] = {}) -> ApiResponse[200, ResponseBody]: + """ + A successful response + """ + return ApiResponse(status_code=200, body=body, headers={**headers}, multi_value_headers={**multi_value_headers}) + + @staticmethod + def bad_request(body: ResponseBody, headers: Dict[str, str] = {}, multi_value_headers: Dict[str, List[str]] = {}) -> ApiResponse[400, ResponseBody]: + """ + A response which indicates a client error + """ + return ApiResponse(status_code=400, body=body, headers={**headers}, multi_value_headers={**multi_value_headers}) + + @staticmethod + def not_found(body: ResponseBody, headers: Dict[str, str] = {}, multi_value_headers: Dict[str, List[str]] = {}) -> ApiResponse[404, ResponseBody]: + """ + A response which indicates the requested resource was not found + """ + return ApiResponse(status_code=404, body=body, headers={**headers}, multi_value_headers={**multi_value_headers}) + + @staticmethod + def not_authorized(body: ResponseBody, headers: Dict[str, str] = {}, multi_value_headers: Dict[str, List[str]] = {}) -> ApiResponse[403, ResponseBody]: + """ + A response which indicates the caller is not authorised to perform the operation or access the resource + """ + return ApiResponse(status_code=403, body=body, headers={**headers}, multi_value_headers={**multi_value_headers}) + + @staticmethod + def internal_failure(body: ResponseBody, headers: Dict[str, str] = {}, multi_value_headers: Dict[str, List[str]] = {}) -> ApiResponse[500, ResponseBody]: + """ + A response to indicate a server error + """ + return ApiResponse(status_code=500, body=body, headers={**headers}, multi_value_headers={**multi_value_headers}) +", + "test_project/rest.py": "# coding: utf-8 + +""" + Recursive schema + + No description provided + + The version of the OpenAPI document: 1.0.0 + + NOTE: This class is auto generated. + Do not edit the class manually. +""" # noqa: E501 + + +import io +import json +import re +import ssl + +import urllib3 + +from test_project.exceptions import ApiException, ApiValueError + +RESTResponseType = urllib3.HTTPResponse + +class RESTResponse(io.IOBase): + + def __init__(self, resp) -> None: + self.response = resp + self.status = resp.status + self.reason = resp.reason + self.data = None + + def read(self): + if self.data is None: + self.data = self.response.data + return self.data + + def getheaders(self): + """Returns a dictionary of the response headers.""" + return self.response.headers + + def getheader(self, name, default=None): + """Returns a given response header.""" + return self.response.headers.get(name, default) + + +class RESTClientObject: + + def __init__(self, configuration) -> None: + # urllib3.PoolManager will pass all kw parameters to connectionpool + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501 + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680 # noqa: E501 + # Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html # noqa: E501 + + # cert_reqs + if configuration.verify_ssl: + cert_reqs = ssl.CERT_REQUIRED + else: + cert_reqs = ssl.CERT_NONE + + addition_pool_args = {} + if configuration.assert_hostname is not None: + addition_pool_args['assert_hostname'] = ( + configuration.assert_hostname + ) + + if configuration.retries is not None: + addition_pool_args['retries'] = configuration.retries + + if configuration.tls_server_name: + addition_pool_args['server_hostname'] = configuration.tls_server_name + + + if configuration.socket_options is not None: + addition_pool_args['socket_options'] = configuration.socket_options + + # https pool manager + if configuration.proxy: + self.pool_manager = urllib3.ProxyManager( + cert_reqs=cert_reqs, + ca_certs=configuration.ssl_ca_cert, + cert_file=configuration.cert_file, + key_file=configuration.key_file, + proxy_url=configuration.proxy, + proxy_headers=configuration.proxy_headers, + **addition_pool_args + ) + else: + self.pool_manager = urllib3.PoolManager( + cert_reqs=cert_reqs, + ca_certs=configuration.ssl_ca_cert, + cert_file=configuration.cert_file, + key_file=configuration.key_file, + **addition_pool_args + ) + + def request( + self, + method, + url, + headers=None, + body=None, + post_params=None, + _request_timeout=None + ): + """Perform requests. + + :param method: http request method + :param url: http request url + :param headers: http request headers + :param body: request json body, for \`application/json\` + :param post_params: request post parameters, + \`application/x-www-form-urlencoded\` + and \`multipart/form-data\` + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in [ + 'GET', + 'HEAD', + 'DELETE', + 'POST', + 'PUT', + 'PATCH', + 'OPTIONS' + ] + + if post_params and body: + raise ApiValueError( + "body parameter cannot be used with post_params parameter." + ) + + post_params = post_params or {} + headers = headers or {} + + timeout = None + if _request_timeout: + if isinstance(_request_timeout, (int, float)): + timeout = urllib3.Timeout(total=_request_timeout) + elif ( + isinstance(_request_timeout, tuple) + and len(_request_timeout) == 2 + ): + timeout = urllib3.Timeout( + connect=_request_timeout[0], + read=_request_timeout[1] + ) + + try: + # For \`POST\`, \`PUT\`, \`PATCH\`, \`OPTIONS\`, \`DELETE\` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + + # no content type provided or payload is json + content_type = headers.get('Content-Type') + if ( + not content_type + or re.search('json', content_type, re.IGNORECASE) + ): + request_body = None + if body is not None: + request_body = json.dumps(body) + r = self.pool_manager.request( + method, + url, + body=request_body, + timeout=timeout, + headers=headers, + preload_content=False + ) + elif content_type == 'application/x-www-form-urlencoded': + r = self.pool_manager.request( + method, + url, + fields=post_params, + encode_multipart=False, + timeout=timeout, + headers=headers, + preload_content=False + ) + elif content_type == 'multipart/form-data': + # must del headers['Content-Type'], or the correct + # Content-Type which generated by urllib3 will be + # overwritten. + del headers['Content-Type'] + r = self.pool_manager.request( + method, + url, + fields=post_params, + encode_multipart=True, + timeout=timeout, + headers=headers, + preload_content=False + ) + # Pass a \`string\` parameter directly in the body to support + # other content types than Json when \`body\` argument is + # provided in serialized form + elif isinstance(body, str) or isinstance(body, bytes): + request_body = body + r = self.pool_manager.request( + method, + url, + body=request_body, + timeout=timeout, + headers=headers, + preload_content=False + ) + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + # For \`GET\`, \`HEAD\` + else: + r = self.pool_manager.request( + method, + url, + fields={}, + timeout=timeout, + headers=headers, + preload_content=False + ) + except urllib3.exceptions.SSLError as e: + msg = "\\n".join([type(e).__name__, str(e)]) + raise ApiException(status=0, reason=msg) + + return RESTResponse(r) +", +} +`; + exports[`Python Client Code Generation Script Unit Tests Generates With single.yaml 1`] = ` { ".gitattributes": "# ~~ Generated by projen. To modify, edit .projenrc.py and run "npx projen". diff --git a/packages/type-safe-api/test/scripts/generators/__snapshots__/typescript.test.ts.snap b/packages/type-safe-api/test/scripts/generators/__snapshots__/typescript.test.ts.snap index 0369e72a1..82ef4cbab 100644 --- a/packages/type-safe-api/test/scripts/generators/__snapshots__/typescript.test.ts.snap +++ b/packages/type-safe-api/test/scripts/generators/__snapshots__/typescript.test.ts.snap @@ -12705,13 +12705,6 @@ export const tryCatchInterceptor = buildTryCatchInterceptor(500, { message: 'Int * Do not edit the class manually. */ import { exists, mapValues } from './model-utils'; -import type { HelloResponse } from './HelloResponse'; -import { - HelloResponseFromJSON, - HelloResponseFromJSONTyped, - HelloResponseToJSON, - instanceOfHelloResponse, -} from './HelloResponse'; /** * @@ -13259,6 +13252,1319 @@ export class TextApiResponse { } `; +exports[`Typescript Client Code Generation Script Unit Tests Generates With recursive.yaml 1`] = ` +{ + ".tsapi-manifest": "src/index.ts +src/runtime.ts +src/interceptors/try-catch.ts +src/interceptors/cors.ts +src/interceptors/powertools/logger.ts +src/interceptors/powertools/tracer.ts +src/interceptors/powertools/metrics.ts +src/interceptors/index.ts +src/apis/DefaultApi/OperationConfig.ts +src/response/response.ts +src/apis/DefaultApi.ts +src/apis/index.ts +src/models/index.ts +src/models/model-utils.ts +src/models/TreeNode.ts", + "src/apis/DefaultApi.ts": "/* tslint:disable */ +/* eslint-disable */ +/** + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + +import * as runtime from '../runtime'; +import type { + TreeNode, +} from '../models'; +import { + TreeNodeFromJSON, + TreeNodeToJSON, +} from '../models'; + + +/** + * + */ +export class DefaultApi extends runtime.BaseAPI { + /** + * + */ + async getTreeRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const queryParameters: any = {}; + + + const headerParameters: runtime.HTTPHeaders = {}; + + + + const response = await this.request({ + path: \`/tree\`, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => TreeNodeFromJSON(jsonValue)); + } + + /** + * + */ + async getTree(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.getTreeRaw(initOverrides); + return await response.value(); + } + +} + +", + "src/apis/DefaultApi/OperationConfig.ts": "// Import models +import { + TreeNode, + TreeNodeFromJSON, + TreeNodeToJSON, +} from '../../models'; +// Import request parameter interfaces +import { +} from '..'; + +// API Gateway Types +import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda"; + +// Generic type for object keyed by operation names +export interface OperationConfig { + getTree: T; +} + +// Look up path and http method for a given operation name +export const OperationLookup = { + getTree: { + path: '/tree', + method: 'GET', + contentTypes: ['application/json'], + }, +}; + +export class Operations { + /** + * Return an OperationConfig with the same value for every operation + */ + public static all = (value: T): OperationConfig => Object.fromEntries( + Object.keys(OperationLookup).map((operationId) => [operationId, value]) + ) as unknown as OperationConfig; +} + +// Standard apigateway request parameters (query parameters or path parameters, multi or single value) +type ApiGatewayRequestParameters = { [key: string]: string | string[] | undefined }; + +/** + * URI decode for a string or array of strings + */ +const uriDecode = (value: string | string[]): string | string[] => + typeof value === 'string' ? decodeURIComponent(value) : value.map((v) => decodeURIComponent(v)); + +/** + * URI decodes apigateway request parameters (query or path parameters) + */ +const decodeRequestParameters = (parameters: ApiGatewayRequestParameters): ApiGatewayRequestParameters => { + const decodedParameters = {}; + Object.keys(parameters || {}).forEach((key) => { + decodedParameters[key] = parameters[key] ? uriDecode(parameters[key]) : parameters[key]; + }); + return decodedParameters; +}; + +/** + * Parse the body if the content type is json, otherwise leave as a raw string + */ +const parseBody = (body: string, demarshal: (body: string) => any, contentTypes: string[]): any => contentTypes.filter((contentType) => contentType !== 'application/json').length === 0 ? demarshal(body || '{}') : body; + +const assertRequired = (required: boolean, baseName: string, parameters: any) => { + if(required && parameters[baseName] === undefined) { + throw new Error(\`Missing required request parameter '\${baseName}'\`); + } +}; + +const coerceNumber = (baseName: string, s: string, isInteger: boolean): number => { + const n = Number(s); + if (isNaN(n)) { + throw new Error(\`Expected a number for request parameter '\${baseName}'\`); + } + if (isInteger && !Number.isInteger(n)) { + throw new Error(\`Expected an integer for request parameter '\${baseName}'\`); + } + return n; +}; + +const coerceBoolean = (baseName: string, s: string): boolean => { + switch (s) { + case "true": + return true; + case "false": + return false; + default: + throw new Error(\`Expected a boolean (true or false) for request parameter '\${baseName}'\`); + } +}; + +const coerceDate = (baseName: string, s: string): Date => { + const d = new Date(s); + if (isNaN(d as any)) { + throw new Error(\`Expected a valid date (iso format) for request parameter '\${baseName}'\`); + } + return d; +}; + +const coerceParameter = ( + baseName: string, + dataType: string, + isInteger: boolean, + rawStringParameters: { [key: string]: string | undefined }, + rawStringArrayParameters: { [key: string]: string[] | undefined }, + required: boolean, +) => { + switch (dataType) { + case "number": + assertRequired(required, baseName, rawStringParameters); + return rawStringParameters[baseName] !== undefined ? coerceNumber(baseName, rawStringParameters[baseName], isInteger) : undefined; + case "boolean": + assertRequired(required, baseName, rawStringParameters); + return rawStringParameters[baseName] !== undefined ? coerceBoolean(baseName, rawStringParameters[baseName]) : undefined; + case "Date": + assertRequired(required, baseName, rawStringParameters); + return rawStringParameters[baseName] !== undefined ? coerceDate(baseName, rawStringParameters[baseName]) : undefined; + case "Array": + assertRequired(required, baseName, rawStringArrayParameters); + return rawStringArrayParameters[baseName] !== undefined ? rawStringArrayParameters[baseName].map(n => coerceNumber(baseName, n, isInteger)) : undefined; + case "Array": + assertRequired(required, baseName, rawStringArrayParameters); + return rawStringArrayParameters[baseName] !== undefined ? rawStringArrayParameters[baseName].map(n => coerceBoolean(baseName, n)) : undefined; + case "Array": + assertRequired(required, baseName, rawStringArrayParameters); + return rawStringArrayParameters[baseName] !== undefined ? rawStringArrayParameters[baseName].map(n => coerceDate(baseName, n)) : undefined; + case "Array": + assertRequired(required, baseName, rawStringArrayParameters); + return rawStringArrayParameters[baseName]; + case "string": + default: + assertRequired(required, baseName, rawStringParameters); + return rawStringParameters[baseName]; + } +}; + +const extractResponseHeadersFromInterceptors = (interceptors: any[]): { [key: string]: string } => { + return (interceptors ?? []).reduce((interceptor: any, headers: { [key: string]: string }) => ({ + ...headers, + ...(interceptor?.__type_safe_api_response_headers ?? {}), + }), {} as { [key: string]: string }); +}; + +export type OperationIds = | 'getTree'; +export type OperationApiGatewayProxyResult = APIGatewayProxyResult & { __operationId?: T }; + +// Api gateway lambda handler type +export type OperationApiGatewayLambdaHandler = (event: APIGatewayProxyEvent, context: Context) => Promise>; + +// Type of the response to be returned by an operation lambda handler +export interface OperationResponse { + statusCode: StatusCode; + headers?: { [key: string]: string }; + multiValueHeaders?: { [key: string]: string[] }; + body: Body; +} + +// Input for a lambda handler for an operation +export type LambdaRequestParameters = { + requestParameters: RequestParameters, + body: RequestBody, +}; + +export type InterceptorContext = { [key: string]: any }; + +export interface RequestInput { + input: LambdaRequestParameters; + event: APIGatewayProxyEvent; + context: Context; + interceptorContext: InterceptorContext; +} + +export interface ChainedRequestInput extends RequestInput { + chain: LambdaHandlerChain; +} + +/** + * A lambda handler function which is part of a chain. It may invoke the remainder of the chain via the given chain input + */ +export type ChainedLambdaHandlerFunction = ( + input: ChainedRequestInput, +) => Promise; + +// Type for a lambda handler function to be wrapped +export type LambdaHandlerFunction = ( + input: RequestInput, +) => Promise; + +export interface LambdaHandlerChain { + next: LambdaHandlerFunction; +} + +// Interceptor is a type alias for ChainedLambdaHandlerFunction +export type Interceptor = ChainedLambdaHandlerFunction; + +/** + * Build a chain from the given array of chained lambda handlers + */ +const buildHandlerChain = ( + ...handlers: ChainedLambdaHandlerFunction[] +): LambdaHandlerChain => { + if (handlers.length === 0) { + return { + next: () => { + throw new Error("No more handlers remain in the chain! The last handler should not call next."); + } + }; + } + const [currentHandler, ...remainingHandlers] = handlers; + return { + next: (input) => { + return currentHandler({ + ...input, + chain: buildHandlerChain(...remainingHandlers), + }); + }, + }; +}; + +/** + * Path, Query and Header parameters for GetTree + */ +export interface GetTreeRequestParameters { +} + +/** + * Request body parameter for GetTree + */ +export type GetTreeRequestBody = never; + +export type GetTree200OperationResponse = OperationResponse<200, TreeNode>; + +export type GetTreeOperationResponses = | GetTree200OperationResponse ; + +// Type that the handler function provided to the wrapper must conform to +export type GetTreeHandlerFunction = LambdaHandlerFunction; +export type GetTreeChainedHandlerFunction = ChainedLambdaHandlerFunction; +export type GetTreeChainedRequestInput = ChainedRequestInput; + +/** + * Lambda handler wrapper to provide typed interface for the implementation of getTree + */ +export const getTreeHandler = ( + ...handlers: [GetTreeChainedHandlerFunction, ...GetTreeChainedHandlerFunction[]] +): OperationApiGatewayLambdaHandler<'getTree'> => async (event: any, context: any, _callback?: any, additionalInterceptors: GetTreeChainedHandlerFunction[] = []): Promise => { + const operationId = "getTree"; + + const rawSingleValueParameters = decodeRequestParameters({ + ...(event.pathParameters || {}), + ...(event.queryStringParameters || {}), + ...(event.headers || {}), + }) as { [key: string]: string | undefined }; + const rawMultiValueParameters = decodeRequestParameters({ + ...(event.multiValueQueryStringParameters || {}), + ...(event.multiValueHeaders || {}), + }) as { [key: string]: string[] | undefined }; + + const marshal = (statusCode: number, responseBody: any): string => { + let marshalledBody = responseBody; + switch(statusCode) { + case 200: + marshalledBody = JSON.stringify(TreeNodeToJSON(marshalledBody)); + break; + default: + break; + } + + return marshalledBody; + }; + + const errorHeaders = (statusCode: number): { [key: string]: string } => { + let headers = {}; + + switch(statusCode) { + default: + break; + } + + return headers; + }; + + let requestParameters: GetTreeRequestParameters | undefined = undefined; + + try { + requestParameters = { + + }; + } catch (e: any) { + const res = { + statusCode: 400, + body: { message: e.message }, + headers: extractResponseHeadersFromInterceptors(handlers), + }; + return { + ...res, + headers: { + ...errorHeaders(res.statusCode), + ...res.headers, + }, + body: res.body ? marshal(res.statusCode, res.body) : '', + }; + } + + const demarshal = (bodyString: string): any => { + return {}; + }; + const body = parseBody(event.body, demarshal, ['application/json']) as GetTreeRequestBody; + + const chain = buildHandlerChain(...additionalInterceptors, ...handlers); + const response = await chain.next({ + input: { + requestParameters, + body, + }, + event, + context, + interceptorContext: { operationId }, + }); + + return { + ...response, + headers: { + ...errorHeaders(response.statusCode), + ...response.headers, + }, + body: response.body ? marshal(response.statusCode, response.body) : '', + }; +}; + +export interface HandlerRouterHandlers { + readonly getTree: OperationApiGatewayLambdaHandler<'getTree'>; +} + +export type AnyOperationRequestParameters = | GetTreeRequestParameters; +export type AnyOperationRequestBodies = | GetTreeRequestBody; +export type AnyOperationResponses = | GetTreeOperationResponses; + +export interface HandlerRouterProps< + RequestParameters, + RequestBody, + Response extends AnyOperationResponses +> { + /** + * Interceptors to apply to all handlers + */ + readonly interceptors?: ChainedLambdaHandlerFunction< + RequestParameters, + RequestBody, + Response + >[]; + + /** + * Handlers to register for each operation + */ + readonly handlers: HandlerRouterHandlers; +} + +const concatMethodAndPath = (method: string, path: string) => \`\${method.toLowerCase()}||\${path}\`; + +const OperationIdByMethodAndPath = Object.fromEntries(Object.entries(OperationLookup).map( + ([operationId, methodAndPath]) => [concatMethodAndPath(methodAndPath.method, methodAndPath.path), operationId] +)); + +/** + * Returns a lambda handler which can be used to route requests to the appropriate typed lambda handler function. + */ +export const handlerRouter = (props: HandlerRouterProps< + AnyOperationRequestParameters, + AnyOperationRequestBodies, + AnyOperationResponses +>): OperationApiGatewayLambdaHandler => async (event, context) => { + const operationId = OperationIdByMethodAndPath[concatMethodAndPath(event.requestContext.httpMethod, event.requestContext.resourcePath)]; + const handler = props.handlers[operationId]; + return handler(event, context, undefined, props.interceptors); +}; +", + "src/apis/index.ts": "/* tslint:disable */ +/* eslint-disable */ +export * from './DefaultApi'; +", + "src/index.ts": "/* tslint:disable */ +/* eslint-disable */ +export * from './runtime'; +export * from './apis'; +export * from './models'; +export * from './apis/DefaultApi/OperationConfig'; +export * from './response/response'; +export * from './interceptors' +", + "src/interceptors/cors.ts": "import { ChainedRequestInput, OperationResponse } from '..'; + +// By default, allow all origins and headers +const DEFAULT_CORS_HEADERS: { [key: string]: string } = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': '*', +}; + +/** + * Create an interceptor for adding headers to the response + * @param additionalHeaders headers to add to the response + */ +export const buildResponseHeaderInterceptor = (additionalHeaders: { [key: string]: string }) => { + const interceptor = async < + RequestParameters, + RequestBody, + Response extends OperationResponse + >( + request: ChainedRequestInput, + ): Promise => { + const result = await request.chain.next(request); + return { + ...result, + headers: { + ...additionalHeaders, + ...result.headers, + }, + }; + }; + + // Any error responses returned during request validation will include the headers + (interceptor as any).__type_safe_api_response_headers = additionalHeaders; + + return interceptor; +}; + +/** + * An interceptor for adding cross-origin resource sharing (CORS) headers to the response. + * Allows all origins and headers. Use buildResponseHeaderInterceptor to customise. + */ +export const corsInterceptor = buildResponseHeaderInterceptor(DEFAULT_CORS_HEADERS); +", + "src/interceptors/index.ts": "import { corsInterceptor } from './cors'; +import { LoggingInterceptor } from './powertools/logger'; +import { MetricsInterceptor } from './powertools/metrics'; +import { TracingInterceptor } from './powertools/tracer'; +import { tryCatchInterceptor } from './try-catch'; + +export * from './cors'; +export * from './try-catch'; +export * from './powertools/tracer'; +export * from './powertools/metrics'; +export * from './powertools/logger'; + +/** + * All default interceptors, for logging, tracing, metrics, cors headers and error handling + */ +export const INTERCEPTORS = [ + corsInterceptor, + LoggingInterceptor.intercept, + tryCatchInterceptor, + TracingInterceptor.intercept, + MetricsInterceptor.intercept, +] as const; +", + "src/interceptors/powertools/logger.ts": "import { Logger } from '@aws-lambda-powertools/logger'; +import { ChainedRequestInput, OperationResponse } from '../..'; + +const logger = new Logger(); + +export class LoggingInterceptor { + /** + * Interceptor which adds an aws lambda powertools logger to the interceptor context, + * and adds the lambda context + * @see https://docs.powertools.aws.dev/lambda/typescript/latest/core/logger/ + */ + public static intercept = async < + RequestParameters, + RequestArrayParameters, + RequestBody, + Response extends OperationResponse + >( + request: ChainedRequestInput, + ): Promise => { + logger.addContext(request.context); + logger.appendKeys({ operationId: request.interceptorContext.operationId }); + request.interceptorContext.logger = logger; + const response = await request.chain.next(request); + logger.removeKeys(['operationId']); + return response; + }; + + /** + * Retrieve the logger from the interceptor context + */ + public static getLogger = < + RequestParameters, + RequestArrayParameters, + RequestBody, + Response extends OperationResponse + >(request: ChainedRequestInput): Logger => { + if (!request.interceptorContext.logger) { + throw new Error('No logger found, did you configure the LoggingInterceptor?'); + } + return request.interceptorContext.logger; + }; +} +", + "src/interceptors/powertools/metrics.ts": "import { Metrics } from '@aws-lambda-powertools/metrics'; +import { ChainedRequestInput, OperationResponse } from '../..'; + +const metrics = new Metrics(); + +export class MetricsInterceptor { + /** + * Interceptor which adds an instance of aws lambda powertools metrics to the interceptor context, + * and ensures metrics are flushed prior to finishing the lambda execution + * @see https://docs.powertools.aws.dev/lambda/typescript/latest/core/metrics/ + */ + public static intercept = async < + RequestParameters, + RequestArrayParameters, + RequestBody, + Response extends OperationResponse + >( + request: ChainedRequestInput, + ): Promise => { + metrics.addDimension("operationId", request.interceptorContext.operationId); + request.interceptorContext.metrics = metrics; + try { + return await request.chain.next(request); + } finally { + // Flush metrics + metrics.publishStoredMetrics(); + } + }; + + /** + * Retrieve the metrics logger from the request + */ + public static getMetrics = < + RequestParameters, + RequestArrayParameters, + RequestBody, + Response extends OperationResponse + >( + request: ChainedRequestInput, + ): Metrics => { + if (!request.interceptorContext.metrics) { + throw new Error('No metrics logger found, did you configure the MetricsInterceptor?'); + } + return request.interceptorContext.metrics; + }; +} +", + "src/interceptors/powertools/tracer.ts": "import { Tracer } from '@aws-lambda-powertools/tracer'; +import { ChainedRequestInput, OperationResponse } from '../..'; + +const tracer = new Tracer(); + +export interface TracingInterceptorOptions { + /** + * Whether to add the response as metadata to the trace + */ + readonly addResponseAsMetadata?: boolean; +} + +/** + * Create an interceptor which adds an aws lambda powertools tracer to the interceptor context, + * creating the appropriate segment for the handler execution and annotating with recommended + * details. + * @see https://docs.powertools.aws.dev/lambda/typescript/latest/core/tracer/#lambda-handler + */ +export const buildTracingInterceptor = (options?: TracingInterceptorOptions) => async < + RequestParameters, + RequestBody, + Response extends OperationResponse +>( + request: ChainedRequestInput, +): Promise => { + const handler = request.interceptorContext.operationId ?? process.env._HANDLER ?? 'index.handler'; + const segment = tracer.getSegment(); + let subsegment; + if (segment) { + subsegment = segment.addNewSubsegment(handler); + tracer.setSegment(subsegment); + } + + tracer.annotateColdStart(); + tracer.addServiceNameAnnotation(); + + if (request.interceptorContext.logger) { + tracer.provider.setLogger(request.interceptorContext.logger); + } + + request.interceptorContext.tracer = tracer; + + try { + const result = await request.chain.next(request); + if (options?.addResponseAsMetadata) { + tracer.addResponseAsMetadata(result, handler); + } + return result; + } catch (e) { + tracer.addErrorAsMetadata(e as Error); + throw e; + } finally { + if (segment && subsegment) { + subsegment.close(); + tracer.setSegment(segment); + } + } +}; + +export class TracingInterceptor { + /** + * Interceptor which adds an aws lambda powertools tracer to the interceptor context, + * creating the appropriate segment for the handler execution and annotating with recommended + * details. + */ + public static intercept = buildTracingInterceptor(); + + /** + * Get the tracer from the interceptor context + */ + public static getTracer = < + RequestParameters, + RequestArrayParameters, + RequestBody, + Response extends OperationResponse + >( + request: ChainedRequestInput, + ): Tracer => { + if (!request.interceptorContext.tracer) { + throw new Error('No tracer found, did you configure the TracingInterceptor?'); + } + return request.interceptorContext.tracer; + }; +} +", + "src/interceptors/try-catch.ts": "import { + ChainedRequestInput, + OperationResponse, +} from '..'; + +/** + * Create an interceptor which returns the given error response and status should an error occur + * @param statusCode the status code to return when an error is thrown + * @param errorResponseBody the body to return when an error occurs + */ +export const buildTryCatchInterceptor = ( + statusCode: TStatus, + errorResponseBody: ErrorResponseBody, +) => async < + RequestParameters, + RequestBody, + Response extends OperationResponse, +>( + request: ChainedRequestInput< + RequestParameters, + RequestBody, + Response + >, +): Promise> => { + try { + return await request.chain.next(request); + } catch (e: any) { + // If the error looks like a response, return it as the response + if ('statusCode' in e) { + return e; + } + + // Log the error if the logger is present + if (request.interceptorContext.logger && request.interceptorContext.logger.error) { + request.interceptorContext.logger.error('Interceptor caught exception', e as Error); + } else { + console.error('Interceptor caught exception', e); + } + + // Return the default error message + return { statusCode, body: errorResponseBody }; + } +}; + +/** + * Interceptor for catching unhandled exceptions and returning a 500 error. + * Uncaught exceptions which look like OperationResponses will be returned, such that deeply nested code may return error + * responses, eg: \`throw ApiResponse.notFound({ message: 'Not found!' })\` + */ +export const tryCatchInterceptor = buildTryCatchInterceptor(500, { message: 'Internal Error' }); +", + "src/models/TreeNode.ts": "/* tslint:disable */ +/* eslint-disable */ +/** + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ +import { exists, mapValues } from './model-utils'; + +/** + * + * @export + * @interface TreeNode + */ +export interface TreeNode { + /** + * + * @type {TreeNode} + * @memberof TreeNode + */ + left?: TreeNode; + /** + * + * @type {TreeNode} + * @memberof TreeNode + */ + right?: TreeNode; +} + + +/** + * Check if a given object implements the TreeNode interface. + */ +export function instanceOfTreeNode(value: object): boolean { + let isInstance = true; + return isInstance; +} + +export function TreeNodeFromJSON(json: any): TreeNode { + return TreeNodeFromJSONTyped(json, false); +} + +export function TreeNodeFromJSONTyped(json: any, ignoreDiscriminator: boolean): TreeNode { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'left': !exists(json, 'left') ? undefined : TreeNodeFromJSON(json['left']), + 'right': !exists(json, 'right') ? undefined : TreeNodeFromJSON(json['right']), + }; +} + +export function TreeNodeToJSON(value?: TreeNode | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'left': TreeNodeToJSON(value.left), + 'right': TreeNodeToJSON(value.right), + }; +} + +", + "src/models/index.ts": "/* tslint:disable */ +/* eslint-disable */ +export * from './TreeNode'; +", + "src/models/model-utils.ts": "/* tslint:disable */ +/* eslint-disable */ + +export function mapValues(data: any, fn: (item: any) => any) { + return Object.keys(data).reduce( + (acc, key) => ({ ...acc, [key]: fn(data[key]) }), + {} + ); +} + +export function exists(json: any, key: string) { + const value = json[key]; + return value !== null && value !== undefined; +} +", + "src/response/response.ts": "import { OperationResponse } from '..'; + + +/** + * Helpers for constructing api responses + */ +export class Response { + /** + * A successful response + */ + public static success = ( + body: T + ): OperationResponse<200, T> => ({ + statusCode: 200, + body, + }); + + /** + * A response which indicates a client error + */ + public static badRequest = ( + body: T + ): OperationResponse<400, T> => ({ + statusCode: 400, + body, + }); + + /** + * A response which indicates the requested resource was not found + */ + public static notFound = ( + body: T + ): OperationResponse<404, T> => ({ + statusCode: 404, + body, + }); + + /** + * A response which indicates the caller is not authorised to perform the operation or access the resource + */ + public static notAuthorized = ( + body: T + ): OperationResponse<403, T> => ({ + statusCode: 403, + body, + }); + + /** + * A response to indicate a server error + */ + public static internalFailure = ( + body: T + ): OperationResponse<500, T> => ({ + statusCode: 500, + body, + }); +} +", + "src/runtime.ts": "/* tslint:disable */ +/* eslint-disable */ +/** + * Recursive schema + * + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated. + * Do not edit the class manually. + */ + +export const BASE_PATH = "http://localhost".replace(/\\/+$/, ""); + +export interface ConfigurationParameters { + basePath?: string; // override base path + fetchApi?: FetchAPI; // override for fetch implementation + middleware?: Middleware[]; // middleware to apply before/after fetch requests + queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings + username?: string; // parameter for basic security + password?: string; // parameter for basic security + apiKey?: string | ((name: string) => string); // parameter for apiKey security + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string | Promise); // parameter for oauth2 security + headers?: HTTPHeaders; //header params we want to use on every request + credentials?: RequestCredentials; //value for the credentials param we want to use on each request +} + +export class Configuration { + constructor(private configuration: ConfigurationParameters = {}) {} + + set config(configuration: Configuration) { + this.configuration = configuration; + } + + get basePath(): string { + return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH; + } + + get fetchApi(): FetchAPI | undefined { + return this.configuration.fetchApi; + } + + get middleware(): Middleware[] { + return this.configuration.middleware || []; + } + + get queryParamsStringify(): (params: HTTPQuery) => string { + return this.configuration.queryParamsStringify || querystring; + } + + get username(): string | undefined { + return this.configuration.username; + } + + get password(): string | undefined { + return this.configuration.password; + } + + get apiKey(): ((name: string) => string) | undefined { + const apiKey = this.configuration.apiKey; + if (apiKey) { + return typeof apiKey === 'function' ? apiKey : () => apiKey; + } + return undefined; + } + + get accessToken(): ((name?: string, scopes?: string[]) => string | Promise) | undefined { + const accessToken = this.configuration.accessToken; + if (accessToken) { + return typeof accessToken === 'function' ? accessToken : async () => accessToken; + } + return undefined; + } + + get headers(): HTTPHeaders | undefined { + return this.configuration.headers; + } + + get credentials(): RequestCredentials | undefined { + return this.configuration.credentials; + } +} + +export const DefaultConfig = new Configuration(); + +/** + * This is the base class for all generated API classes. + */ +export class BaseAPI { + + private middleware: Middleware[]; + + constructor(protected configuration = DefaultConfig) { + this.middleware = configuration.middleware; + } + + withMiddleware(this: T, ...middlewares: Middleware[]) { + const next = this.clone(); + next.middleware = next.middleware.concat(...middlewares); + return next; + } + + withPreMiddleware(this: T, ...preMiddlewares: Array) { + const middlewares = preMiddlewares.map((pre) => ({ pre })); + return this.withMiddleware(...middlewares); + } + + withPostMiddleware(this: T, ...postMiddlewares: Array) { + const middlewares = postMiddlewares.map((post) => ({ post })); + return this.withMiddleware(...middlewares); + } + + protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise { + const { url, init } = await this.createFetchParams(context, initOverrides); + const response = await this.fetchApi(url, init); + if (response && (response.status >= 200 && response.status < 300)) { + return response; + } + throw new ResponseError(response, 'Response returned an error code'); + } + + private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) { + let url = this.configuration.basePath + context.path; + if (context.query !== undefined && Object.keys(context.query).length !== 0) { + // only add the querystring to the URL if there are query parameters. + // this is done to avoid urls ending with a "?" character which buggy webservers + // do not handle correctly sometimes. + url += '?' + this.configuration.queryParamsStringify(context.query); + } + + const headers = Object.assign({}, this.configuration.headers, context.headers); + Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {}); + + const initOverrideFn = + typeof initOverrides === "function" + ? initOverrides + : async () => initOverrides; + + const initParams = { + method: context.method, + headers, + body: context.body, + credentials: this.configuration.credentials, + }; + + const overriddenInit: RequestInit = { + ...initParams, + ...(await initOverrideFn({ + init: initParams, + context, + })) + }; + + const init: RequestInit = { + ...overriddenInit, + body: + isFormData(overriddenInit.body) || + overriddenInit.body instanceof URLSearchParams || + isBlob(overriddenInit.body) + ? overriddenInit.body + : JSON.stringify(overriddenInit.body), + }; + + return { url, init }; + } + + private fetchApi = async (url: string, init: RequestInit) => { + let fetchParams = { url, init }; + for (const middleware of this.middleware) { + if (middleware.pre) { + fetchParams = await middleware.pre({ + fetch: this.fetchApi, + ...fetchParams, + }) || fetchParams; + } + } + let response: Response | undefined = undefined; + try { + response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init); + } catch (e) { + for (const middleware of this.middleware) { + if (middleware.onError) { + response = await middleware.onError({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + error: e, + response: response ? response.clone() : undefined, + }) || response; + } + } + if (response === undefined) { + if (e instanceof Error) { + throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response'); + } else { + throw e; + } + } + } + for (const middleware of this.middleware) { + if (middleware.post) { + response = await middleware.post({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + response: response.clone(), + }) || response; + } + } + return response; + } + + /** + * Create a shallow clone of \`this\` by constructing a new instance + * and then shallow cloning data members. + */ + private clone(this: T): T { + const constructor = this.constructor as any; + const next = new constructor(this.configuration); + next.middleware = this.middleware.slice(); + return next; + } +}; + +function isBlob(value: any): value is Blob { + return typeof Blob !== 'undefined' && value instanceof Blob; +} + +function isFormData(value: any): value is FormData { + return typeof FormData !== "undefined" && value instanceof FormData; +} + +export class ResponseError extends Error { + override name: "ResponseError" = "ResponseError"; + constructor(public response: Response, msg?: string) { + super(msg); + } +} + +export class FetchError extends Error { + override name: "FetchError" = "FetchError"; + constructor(public cause: Error, msg?: string) { + super(msg); + } +} + +export class RequiredError extends Error { + override name: "RequiredError" = "RequiredError"; + constructor(public field: string, msg?: string) { + super(msg); + } +} + +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\\t", + pipes: "|", +}; + +export type FetchAPI = WindowOrWorkerGlobalScope['fetch']; + +export type Json = any; +export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; +export type HTTPHeaders = { [key: string]: string }; +export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; +export type HTTPBody = Json | FormData | URLSearchParams; +export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody }; +export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; + +export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise + +export interface FetchParams { + url: string; + init: RequestInit; +} + +export interface RequestOpts { + path: string; + method: HTTPMethod; + headers: HTTPHeaders; + query?: HTTPQuery; + body?: HTTPBody; +} + +export function exists(json: any, key: string) { + const value = json[key]; + return value !== null && value !== undefined; +} + +export function querystring(params: HTTPQuery, prefix: string = ''): string { + return Object.keys(params) + .map(key => querystringSingleKey(key, params[key], prefix)) + .filter(part => part.length > 0) + .join('&'); +} + +function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array | Set | HTTPQuery, keyPrefix: string = ''): string { + const fullKey = keyPrefix + (keyPrefix.length ? \`[\${key}]\` : key); + if (value instanceof Array) { + const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue))) + .join(\`&\${encodeURIComponent(fullKey)}=\`); + return \`\${encodeURIComponent(fullKey)}=\${multiValue}\`; + } + if (value instanceof Set) { + const valueAsArray = Array.from(value); + return querystringSingleKey(key, valueAsArray, keyPrefix); + } + if (value instanceof Date) { + return \`\${encodeURIComponent(fullKey)}=\${encodeURIComponent(value.toISOString())}\`; + } + if (value instanceof Object) { + return querystring(value as HTTPQuery, fullKey); + } + return \`\${encodeURIComponent(fullKey)}=\${encodeURIComponent(String(value))}\`; +} + +export function mapValues(data: any, fn: (item: any) => any) { + return Object.keys(data).reduce( + (acc, key) => ({ ...acc, [key]: fn(data[key]) }), + {} + ); +} + +export function canConsumeForm(consumes: Consume[]): boolean { + for (const consume of consumes) { + if ('multipart/form-data' === consume.contentType) { + return true; + } + } + return false; +} + +export interface Consume { + contentType: string; +} + +export interface RequestContext { + fetch: FetchAPI; + url: string; + init: RequestInit; +} + +export interface ResponseContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + response: Response; +} + +export interface ErrorContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + error: unknown; + response?: Response; +} + +export interface Middleware { + pre?(context: RequestContext): Promise; + post?(context: ResponseContext): Promise; + onError?(context: ErrorContext): Promise; +} + +export interface ApiResponse { + raw: Response; + value(): Promise; +} + +export interface ResponseTransformer { + (json: any): T; +} + +export class JSONApiResponse { + constructor(public raw: Response, private transformer: ResponseTransformer = (jsonValue: any) => jsonValue) {} + + async value(): Promise { + return this.transformer(await this.raw.json()); + } +} + +export class VoidApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return undefined; + } +} + +export class BlobApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return await this.raw.blob(); + }; +} + +export class TextApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return await this.raw.text(); + }; +} +", +} +`; + exports[`Typescript Client Code Generation Script Unit Tests Generates With single.yaml 1`] = ` { ".tsapi-manifest": "src/index.ts diff --git a/packages/type-safe-api/test/scripts/generators/java.test.ts b/packages/type-safe-api/test/scripts/generators/java.test.ts index 073c5dae6..8d50fd6ac 100644 --- a/packages/type-safe-api/test/scripts/generators/java.test.ts +++ b/packages/type-safe-api/test/scripts/generators/java.test.ts @@ -16,6 +16,7 @@ describe("Java Client Code Generation Script Unit Tests", () => { "default-response.yaml", "allof-model.yaml", "composite-models.yaml", + "recursive.yaml", ])("Generates With %s", (spec) => { const specPath = path.resolve(__dirname, `../../resources/specs/${spec}`); diff --git a/packages/type-safe-api/test/scripts/generators/python.test.ts b/packages/type-safe-api/test/scripts/generators/python.test.ts index 0ccf3b19d..2a67ba900 100644 --- a/packages/type-safe-api/test/scripts/generators/python.test.ts +++ b/packages/type-safe-api/test/scripts/generators/python.test.ts @@ -16,6 +16,7 @@ describe("Python Client Code Generation Script Unit Tests", () => { "default-response.yaml", "allof-model.yaml", "composite-models.yaml", + "recursive.yaml", ])("Generates With %s", (spec) => { const specPath = path.resolve(__dirname, `../../resources/specs/${spec}`); diff --git a/packages/type-safe-api/test/scripts/generators/typescript.test.ts b/packages/type-safe-api/test/scripts/generators/typescript.test.ts index f7d163290..6f66f4e4c 100644 --- a/packages/type-safe-api/test/scripts/generators/typescript.test.ts +++ b/packages/type-safe-api/test/scripts/generators/typescript.test.ts @@ -16,6 +16,7 @@ describe("Typescript Client Code Generation Script Unit Tests", () => { "default-response.yaml", "allof-model.yaml", "composite-models.yaml", + "recursive.yaml", ])("Generates With %s", (spec) => { const specPath = path.resolve(__dirname, `../../resources/specs/${spec}`);