From 1620ad5071a39f48e05239c39c805861585b0e4e Mon Sep 17 00:00:00 2001 From: Aleksandr Nekrasov Date: Mon, 24 Feb 2020 00:04:58 +0700 Subject: [PATCH 1/9] scala-sttp-client template --- bin/openapi3/scala-sttp-petstore.sh | 32 +++ .../languages/ScalaSttpClientCodegen.java | 68 +++++ .../org.openapitools.codegen.CodegenConfig | 1 + .../scala-sttp-client/README.mustache | 107 ++++++++ .../resources/scala-sttp-client/api.mustache | 65 +++++ .../scala-sttp-client/apiInvoker.mustache | 98 +++++++ .../scala-sttp-client/build.sbt.mustache | 29 ++ .../enumsSerializers.mustache | 42 +++ .../scala-sttp-client/javadoc.mustache | 25 ++ .../scala-sttp-client/licenseInfo.mustache | 11 + .../methodParameters.mustache | 1 + .../scala-sttp-client/model.mustache | 42 +++ .../operationReturnType.mustache | 1 + .../scala-sttp-client/paramCreation.mustache | 1 + .../paramFormCreation.mustache | 1 + .../paramQueryCreation.mustache | 1 + .../scala-sttp-client/requests.mustache | 195 +++++++++++++ .../scala-sttp-client/responseState.mustache | 1 + .../scala-sttp/.openapi-generator-ignore | 23 ++ .../scala-sttp/.openapi-generator/VERSION | 1 + samples/client/petstore/scala-sttp/README.md | 121 ++++++++ samples/client/petstore/scala-sttp/build.sbt | 29 ++ samples/client/petstore/scala-sttp/pom.xml | 259 ++++++++++++++++++ .../scala-sttp/project/build.properties | 1 + .../src/main/resources/reference.conf | 24 ++ .../org/openapitools/client/Client.scala | 27 ++ .../client/api/EnumsSerializers.scala | 51 ++++ .../org/openapitools/client/api/PetApi.scala | 186 +++++++++++++ .../openapitools/client/api/StoreApi.scala | 109 ++++++++ .../org/openapitools/client/api/UserApi.scala | 190 +++++++++++++ .../openapitools/client/core/ApiInvoker.scala | 108 ++++++++ .../openapitools/client/core/ApiRequest.scala | 65 +++++ .../openapitools/client/core/requests.scala | 205 ++++++++++++++ .../client/model/ApiResponse.scala | 22 ++ .../openapitools/client/model/Category.scala | 21 ++ .../client/model/InlineObject.scala | 23 ++ .../client/model/InlineObject1.scala | 24 ++ .../org/openapitools/client/model/Order.scala | 37 +++ .../org/openapitools/client/model/Pet.scala | 36 +++ .../org/openapitools/client/model/Tag.scala | 21 ++ .../org/openapitools/client/model/User.scala | 28 ++ 41 files changed, 2332 insertions(+) create mode 100644 bin/openapi3/scala-sttp-petstore.sh create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/README.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/enumsSerializers.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/javadoc.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/licenseInfo.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/methodParameters.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/model.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/operationReturnType.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/paramCreation.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/paramFormCreation.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/paramQueryCreation.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/requests.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/responseState.mustache create mode 100644 samples/client/petstore/scala-sttp/.openapi-generator-ignore create mode 100644 samples/client/petstore/scala-sttp/.openapi-generator/VERSION create mode 100644 samples/client/petstore/scala-sttp/README.md create mode 100644 samples/client/petstore/scala-sttp/build.sbt create mode 100644 samples/client/petstore/scala-sttp/pom.xml create mode 100644 samples/client/petstore/scala-sttp/project/build.properties create mode 100644 samples/client/petstore/scala-sttp/src/main/resources/reference.conf create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/Client.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/EnumsSerializers.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/StoreApi.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/UserApi.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiRequest.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/requests.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/ApiResponse.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Category.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject1.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Order.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Pet.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Tag.scala create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/User.scala diff --git a/bin/openapi3/scala-sttp-petstore.sh b/bin/openapi3/scala-sttp-petstore.sh new file mode 100644 index 000000000000..94bb4192804b --- /dev/null +++ b/bin/openapi3/scala-sttp-petstore.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +SCRIPT="$0" +echo "# START SCRIPT: $SCRIPT" + +while [ -h "$SCRIPT" ] ; do + ls=`ls -ld "$SCRIPT"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + SCRIPT="$link" + else + SCRIPT=`dirname "$SCRIPT"`/"$link" + fi +done + +if [ ! -d "${APP_DIR}" ]; then + APP_DIR=`dirname "$SCRIPT"`/.. + APP_DIR=`cd "${APP_DIR}"; pwd` +fi + +executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar" + +if [ ! -f "$executable" ] +then + mvn clean package +fi + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="generate --artifact-id "scala-akka-petstore-client" -t modules/openapi-generator/src/main/resources/scala-sttp-client -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -g scala-sttp -o samples/client/petstore/scala-sttp $@" + +java $JAVA_OPTS -jar $executable $ags diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java new file mode 100644 index 000000000000..188581fdb62b --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java @@ -0,0 +1,68 @@ +package org.openapitools.codegen.languages; + +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.servers.Server; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.SupportingFile; + +import java.io.File; +import java.util.List; + +public class ScalaSttpClientCodegen extends ScalaAkkaClientCodegen implements CodegenConfig { + protected String mainPackage = "org.openapitools.client"; + + public ScalaSttpClientCodegen() { + super(); + } + + + @Override + public void processOpts() { + super.processOpts(); + if (additionalProperties.containsKey("mainPackage")) { + setMainPackage((String) additionalProperties.get("mainPackage")); + additionalProperties.replace("configKeyPath", this.configKeyPath); + apiPackage = mainPackage + ".api"; + modelPackage = mainPackage + ".model"; + invokerPackage = mainPackage + ".core"; + additionalProperties.put("apiPackage", apiPackage); + additionalProperties.put("modelPackage", modelPackage); + } + + supportingFiles.clear(); + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("build.sbt.mustache", "", "build.sbt")); + final String invokerFolder = (sourceFolder + File.separator + invokerPackage).replace(".", File.separator); + supportingFiles.add(new SupportingFile("requests.mustache", invokerFolder, "requests.scala")); + supportingFiles.add(new SupportingFile("apiInvoker.mustache", invokerFolder, "ApiInvoker.scala")); + final String apiFolder = (sourceFolder + File.separator + apiPackage).replace(".", File.separator); + supportingFiles.add(new SupportingFile("enumsSerializers.mustache", apiFolder, "EnumsSerializers.scala")); + } + + @Override + public String getName() { + return "scala-sttp"; + } + + @Override + public String getHelp() { + return "Generates a Scala client library base on Sttp."; + } + + @Override + public String encodePath(String input) { + String result = super.encodePath(input); + return result.replace("{","${"); + } + + @Override + public CodegenOperation fromOperation(String path, + String httpMethod, + Operation operation, + List servers) { + CodegenOperation op = super.fromOperation(path, httpMethod, operation, servers); + op.path = encodePath(path); + return op; + } +} diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig index c48f628a3aac..89cbc16b8965 100644 --- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -126,3 +126,4 @@ org.openapitools.codegen.languages.AsciidocDocumentationCodegen org.openapitools.codegen.languages.FsharpFunctionsServerCodegen org.openapitools.codegen.languages.MarkdownDocumentationCodegen +org.openapitools.codegen.languages.ScalaSttpClientCodegen diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/README.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/README.mustache new file mode 100644 index 000000000000..0a61209568da --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/README.mustache @@ -0,0 +1,107 @@ +# {{artifactId}} + +{{appName}} +- API version: {{appVersion}} +{{^hideGenerationTimestamp}} + - Build date: {{generatedDate}} +{{/hideGenerationTimestamp}} + +{{#appDescriptionWithNewLines}}{{{appDescriptionWithNewLines}}}{{/appDescriptionWithNewLines}} + +{{#infoUrl}} + For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + +*Automatically generated by the [OpenAPI Generator](https://openapi-generator.tech)* + +## Requirements + +Building the API client library requires: +1. Java 1.7+ +2. Maven/Gradle/SBT + +## Installation + +To install the API client library to your local Maven repository, simply execute: + +```shell +mvn clean install +``` + +To deploy it to a remote Maven repository instead, configure the settings of the repository and execute: + +```shell +mvn clean deploy +``` + +Refer to the [OSSRH Guide](http://central.sonatype.org/pages/ossrh-guide.html) for more information. + +### Maven users + +Add this dependency to your project's POM: + +```xml + + {{{groupId}}} + {{{artifactId}}} + {{{artifactVersion}}} + compile + +``` + +### Gradle users + +Add this dependency to your project's build file: + +```groovy +compile "{{{groupId}}}:{{{artifactId}}}:{{{artifactVersion}}}" +``` + +### SBT users + +```scala +libraryDependencies += "{{{groupId}}}" % "{{{artifactId}}}" % "{{{artifactVersion}}}" +``` + +## Getting Started + +## Documentation for API Endpoints + +All URIs are relative to *{{basePath}}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | **{{operationId}}** | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} + +## Documentation for Models + +{{#models}}{{#model}} - [{{classname}}]({{modelDocPath}}{{classname}}.md) +{{/model}}{{/models}} + +## Documentation for Authorization + +{{^authMethods}}All endpoints do not require authorization. +{{/authMethods}}Authentication schemes defined for the API: +{{#authMethods}}### {{name}} + +{{#isApiKey}}- **Type**: API key +- **API key parameter name**: {{keyParamName}} +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} +{{/isApiKey}} +{{#isBasic}}- **Type**: HTTP basic authentication +{{/isBasic}} +{{#isOAuth}}- **Type**: OAuth +- **Flow**: {{flow}} +- **Authorization URL**: {{authorizationUrl}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - {{scope}}: {{description}} +{{/scopes}} +{{/isOAuth}} + +{{/authMethods}} + +## Author + +{{#apiInfo}}{{#apis}}{{^hasMore}}{{infoEmail}} +{{/hasMore}}{{/apis}}{{/apiInfo}} diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache new file mode 100644 index 000000000000..897108313ee4 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache @@ -0,0 +1,65 @@ +{{>licenseInfo}} +package {{package}} + +{{#imports}} +import {{import}} +{{/imports}} +import {{mainPackage}}.core._ +import {{mainPackage}}.core.CollectionFormats._ +import sttp.client._ +import sttp.model.Method +import sttp.client.json4s.SttpJson4sApi + +{{#operations}} +object {{classname}} { + + def apply(baseUrl: String = "{{{basePath}}}")(implicit serializer: SttpSerializer) = new {{classname}}(baseUrl) +} + +class {{classname}}(baseUrl: String)(implicit serializer: SttpSerializer) { + + import serializer._ + + /* + * Helper to handle Optional header parameters + **/ + implicit class optionalParams(request: RequestT[Identity, Either[String, String], Nothing]) { + def header( header: String, optValue: Option[Any]): RequestT[Identity, Either[String, String], Nothing] = { + optValue.map( value => request.header(header, value.toString)).getOrElse(request) + } + } + +{{#operation}} +{{#javadocRenderer}} +{{>javadoc}} +{{/javadocRenderer}} + def {{operationId}}({{>methodParameters}}): RequestT[Identity, Either[ResponseError[Exception], {{>operationReturnType}}], Nothing] = + basicRequest + .method(Method.{{httpMethod.toUpperCase}}, uri"$baseUrl{{{path}}}{{#queryParams.0}}?{{#queryParams}}{{baseName}}=${{{paramName}}}{{^-last}}&{{/-last}}{{/queryParams}}{{/queryParams.0}}") + .contentType({{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}}{{^consumes}}"application/json"{{/consumes}}){{#headerParams}} + .header({{>paramCreation}}){{/headerParams}}{{#formParams.0}} + .body(Map({{#formParams}} + {{>paramFormCreation}},{{/formParams}} + )){{/formParams.0}}{{#bodyParam}} + .body({{paramName}}){{/bodyParam}} + .response(asJson[{{>operationReturnType}}]) + + +{{#x-skip-this}} + + + {{#authMethods}}{{#isApiKey}}.withApiKey(apiKey, "{{keyParamName}}", {{#isKeyInQuery}}QUERY{{/isKeyInQuery}}{{#isKeyInHeader}}HEADER{{/isKeyInHeader}}{{#isKeyInCookie}}COOKIE{{/isKeyInCookie}}) + {{/isApiKey}}{{#isBasic}}{{#isBasicBasic}}.withCredentials(basicAuth){{/isBasicBasic}}{{#isBasicBearer}}.withCredentials(bearerToken){{/isBasicBearer}}{{/isBasic}}{{/authMethods}} + +{{#responses}}{{^isWildcard}}{{#dataType}}.with{{>responseState}}Response[{{dataType}}]({{code}}) + {{/dataType}}{{^dataType}}.with{{>responseState}}Response[Unit]({{code}}) + {{/dataType}}{{/isWildcard}}{{/responses}}{{#responses}}{{#isWildcard}}{{#dataType}}.withDefault{{>responseState}}Response[{{dataType}}] + {{/dataType}}{{^dataType}}.withDefault{{>responseState}}Response[Unit] + {{/dataType}}{{/isWildcard}}{{/responses}} +{{/x-skip-this}} + +{{/operation}} + +} + +{{/operations}} diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache new file mode 100644 index 000000000000..902b2ad1d981 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache @@ -0,0 +1,98 @@ +{{>licenseInfo}} +package {{{mainPackage}}}.core + +import java.io.File + +import org.joda.time.DateTime +import org.joda.time.format.ISODateTimeFormat +import org.json4s.JsonAST.JString +import org.json4s._ +import org.json4s.jackson.JsonMethods._ +import org.json4s.jackson.Serialization + +import scala.collection.immutable +import scala.concurrent.{ ExecutionContext, ExecutionContextExecutor, Future } +import scala.reflect.ClassTag +import sttp.client.json4s.SttpJson4sApi + +class SttpSerializer(implicit val serialization: org.json4s.Serialization) extends SttpJson4sApi + +object ApiInvoker { + + def apply(): ApiInvoker = + apply(DefaultFormats + DateTimeSerializer) + + def apply(serializers: Iterable[Serializer[_]]): ApiInvoker = + apply(DefaultFormats + DateTimeSerializer ++ serializers) + + def apply(formats: Formats): ApiInvoker = new ApiInvoker(formats) + + + /** + * Allows request execution without calling apiInvoker.execute(request) + * request.response can be used to get a future of the ApiResponse generated. + * request.result can be used to get a future of the expected ApiResponse content. If content doesn't match, a + * Future will failed with a ClassCastException + * + * @param request the apiRequest to be executed + */ + implicit class ApiRequestImprovements[T: Manifest](request: ApiRequest[T]) { + + def response(invoker: ApiInvoker)(implicit ec: ExecutionContext): Future[ApiResponse[T]] = + response(ec, invoker) + + def response(implicit ec: ExecutionContext, invoker: ApiInvoker): Future[ApiResponse[T]] = + invoker.execute(request) + + def result[U <: T](implicit c: ClassTag[U], ec: ExecutionContext, invoker: ApiInvoker): Future[U] = + invoker.execute(request).map(_.content).mapTo[U] + + } + + case object DateTimeSerializer extends CustomSerializer[DateTime](_ => ( { + case JString(s) => + ISODateTimeFormat.dateOptionalTimeParser().parseDateTime(s) + }, { + case d: DateTime => + JString(ISODateTimeFormat.dateTime().print(d)) + }) + ) + +} + + +class ApiInvoker(formats: Formats) { +/* + import {{{mainPackage}}}.core.ApiInvoker._ + import {{{mainPackage}}}.core.ParametersMap._ + + implicit val jsonFormats: Formats = formats + + private implicit val serialization: Serialization = jackson.Serialization +*/ + def execute[T: Manifest](r: ApiRequest[T]): Future[ApiResponse[T]] = { +// implicit val timeout: Timeout = settings.connectionTimeout +/* + val request = createRequest(makeUri(r), r) + + http + .singleRequest(request) + .map { response => + val decoder: Coder with StreamDecoder = response.encoding match { + case HttpEncodings.gzip ⇒ + Gzip + case HttpEncodings.deflate ⇒ + Deflate + case HttpEncodings.identity ⇒ + NoCoding + case HttpEncoding(encoding) => + throw new IllegalArgumentException(s"Unsupported encoding: $encoding") + } + + decoder.decodeMessage(response) + } + .flatMap(unmarshallApiResponse(r)) + */ + null + } +} diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache new file mode 100644 index 000000000000..555021a2466b --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache @@ -0,0 +1,29 @@ +version := "{{artifactVersion}}" +name := "{{artifactId}}" +organization := "{{groupId}}" +scalaVersion := "2.12.8" + +val sttp = "2.0.0-RC11" + +libraryDependencies ++= Seq( + "com.softwaremill.sttp.client" %% "core" % sttp, + "com.softwaremill.sttp.client" %% "json4s" % sttp, + + "joda-time" % "joda-time" % "2.10.1", + "org.json4s" %% "json4s-jackson" % "3.6.5", + "org.json4s" %% "json4s-ext" % "3.6.5", + + // test dependencies + "org.scalatest" %% "scalatest" % "3.0.5" % "test", + "junit" % "junit" % "4.13" % "test" +) + +resolvers ++= Seq(Resolver.mavenLocal) + +scalacOptions := Seq( + "-unchecked", + "-deprecation", + "-feature" +) + +publishArtifact in (Compile, packageDoc) := false \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/enumsSerializers.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/enumsSerializers.mustache new file mode 100644 index 000000000000..8c7e6f2e41e5 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/enumsSerializers.mustache @@ -0,0 +1,42 @@ +{{>licenseInfo}} +package {{apiPackage}} + +{{#models.0}} +import {{modelPackage}}._ +{{/models.0}} +import org.json4s._ +import scala.reflect.ClassTag + +object EnumsSerializers { + + def all: Seq[Serializer[_]] = Seq[Serializer[_]](){{#models}}{{#model}}{{#hasEnums}}{{#vars}}{{#isEnum}} :+ + new EnumNameSerializer({{classname}}Enums.{{datatypeWithEnum}}){{/isEnum}}{{/vars}}{{/hasEnums}}{{/model}}{{/models}} + + private class EnumNameSerializer[E <: Enumeration: ClassTag](enum: E) + extends Serializer[E#Value] { + import JsonDSL._ + + val EnumerationClass: Class[E#Value] = classOf[E#Value] + + def deserialize(implicit format: Formats): + PartialFunction[(TypeInfo, JValue), E#Value] = { + case (t @ TypeInfo(EnumerationClass, _), json) if isValid(json) => + json match { + case JString(value) => + enum.withName(value) + case value => + throw new MappingException(s"Can't convert $value to $EnumerationClass") + } + } + + private[this] def isValid(json: JValue) = json match { + case JString(value) if enum.values.exists(_.toString == value) => true + case _ => false + } + + def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { + case i: E#Value => i.toString + } + } + +} diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/javadoc.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/javadoc.mustache new file mode 100644 index 000000000000..e42fa1dcdcd7 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/javadoc.mustache @@ -0,0 +1,25 @@ +{{#notes}} +{{{notes}}} + +{{/notes}} +Expected answers: +{{#responses}} + code {{code}} : {{{dataType}}} {{#message}}({{{message}}}){{/message}} + {{#headers}} + {{#-first}} + Headers : + {{/-first}} + {{{baseName}}} - {{{description}}} + {{/headers}} +{{/responses}} +{{#authMethods.0}} + +Available security schemes: +{{#authMethods}} + {{name}} ({{type}}) +{{/authMethods}} +{{/authMethods.0}} + +{{#allParams}} +@param {{{paramName}}} {{{description}}} +{{/allParams}} diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/licenseInfo.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/licenseInfo.mustache new file mode 100644 index 000000000000..835764cfc729 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/licenseInfo.mustache @@ -0,0 +1,11 @@ +/** + * {{{appName}}} + * {{{appDescription}}} + * + * {{#version}}The version of the OpenAPI document: {{{version}}}{{/version}} + * {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}} + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/methodParameters.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/methodParameters.mustache new file mode 100644 index 000000000000..54dc2f92a51f --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/methodParameters.mustache @@ -0,0 +1 @@ +{{#allParams}}{{paramName}}: {{#required}}{{dataType}}{{/required}}{{^required}}{{#isContainer}}{{dataType}}{{/isContainer}}{{^isContainer}}Option[{{dataType}}]{{/isContainer}}{{/required}}{{^defaultValue}}{{^required}}{{^isContainer}} = None{{/isContainer}}{{/required}}{{/defaultValue}}{{#hasMore}}, {{/hasMore}}{{/allParams}}{{#authMethods.0}})(implicit {{#authMethods}}{{#isApiKey}}apiKey: ApiKeyValue{{/isApiKey}}{{#isBasic}}{{#isBasicBasic}}basicAuth: BasicCredentials{{/isBasicBasic}}{{#isBasicBearer}}bearerToken: BearerToken{{/isBasicBearer}}{{/isBasic}}{{#hasMore}}, {{/hasMore}}{{/authMethods}}{{/authMethods.0}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/model.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/model.mustache new file mode 100644 index 000000000000..d5e9b9d3876b --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/model.mustache @@ -0,0 +1,42 @@ +{{>licenseInfo}} +package {{package}} + +{{#imports}} +import {{import}} +{{/imports}} +import {{mainPackage}}.core.ApiModel + +{{#models}} +{{#model}} +case class {{classname}} ( + {{#vars}} + {{#description}} + /* {{{description}}} */ + {{/description}} + {{{name}}}: {{^required}}Option[{{/required}}{{^isEnum}}{{dataType}}{{/isEnum}}{{#isEnum}}{{classname}}Enums.{{datatypeWithEnum}}{{/isEnum}}{{^required}}] = None{{/required}}{{#hasMore}},{{/hasMore}} + {{/vars}} +) extends ApiModel + +{{#hasEnums}} +object {{classname}}Enums { + + {{#vars}} + {{#isEnum}} + type {{datatypeWithEnum}} = {{datatypeWithEnum}}.Value + {{/isEnum}} + {{/vars}} + {{#vars}} + {{#isEnum}} + object {{datatypeWithEnum}} extends Enumeration { +{{#_enum}} + val {{#fnEnumEntry}}{{.}}{{/fnEnumEntry}} = Value("{{.}}") +{{/_enum}} + } + + {{/isEnum}} + {{/vars}} +} +{{/hasEnums}} +{{/model}} +{{/models}} + diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/operationReturnType.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/operationReturnType.mustache new file mode 100644 index 000000000000..a8917911853a --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/operationReturnType.mustache @@ -0,0 +1 @@ +{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Unit{{/returnType}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/paramCreation.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/paramCreation.mustache new file mode 100644 index 000000000000..68280bd9a36b --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/paramCreation.mustache @@ -0,0 +1 @@ +"{{baseName}}", {{#isContainer}}ArrayValues({{{paramName}}}{{#collectionFormat}}, {{collectionFormat.toUpperCase}}{{/collectionFormat}}){{/isContainer}}{{^isContainer}}{{{paramName}}}{{/isContainer}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/paramFormCreation.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/paramFormCreation.mustache new file mode 100644 index 000000000000..a0f1a65c0746 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/paramFormCreation.mustache @@ -0,0 +1 @@ +"{{baseName}}" -> {{#isContainer}}ArrayValues({{{paramName}}}{{#collectionFormat}}, {{collectionFormat.toUpperCase}}{{/collectionFormat}}){{/isContainer}}{{^isContainer}}{{{paramName}}}{{/isContainer}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/paramQueryCreation.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/paramQueryCreation.mustache new file mode 100644 index 000000000000..8a1ea8cfcbf7 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/paramQueryCreation.mustache @@ -0,0 +1 @@ +{{#isContainer}}${ formatQueryArray("{{{baseName}}}",{{{paramName}}}{{#collectionFormat}}, {{collectionFormat.toUpperCase}}{{/collectionFormat}}) }{{/isContainer}}{{^isContainer}}{{baseName}}=${ {{{paramName}}} }{{/isContainer}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/requests.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/requests.mustache new file mode 100644 index 000000000000..271a1a3e67ff --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/requests.mustache @@ -0,0 +1,195 @@ +{{>licenseInfo}} +package {{mainPackage}}.core + +import java.io.File +import java.net.URLEncoder + +import scala.util.Try + +sealed trait ApiReturnWithHeaders { + def headers: Map[String, String] + + def header(name: String): Option[String] = headers.get(name) + + def getStringHeader(name: String): Option[String] = header(name) + + // workaround: return date time header in string instead of datetime object + def getDateTimeHeader(name: String): Option[String] = header(name) + + def getIntHeader(name: String): Option[Int] = castedHeader(name, java.lang.Integer.parseInt) + + def getLongHeader(name: String): Option[Long] = castedHeader(name, java.lang.Long.parseLong) + + def getFloatHeader(name: String): Option[Float] = castedHeader(name, java.lang.Float.parseFloat) + + def getDoubleHeader(name: String): Option[Double] = castedHeader(name, java.lang.Double.parseDouble) + + def getBooleanHeader(name: String): Option[Boolean] = castedHeader(name, java.lang.Boolean.parseBoolean) + + private def castedHeader[U](name: String, conversion: String => U): Option[U] = { + Try { + header(name).map(conversion) + }.get + } +} + +sealed case class ApiResponse[T](code: Int, content: T, headers: Map[String, String] = Map.empty) + extends ApiReturnWithHeaders + +sealed case class ApiError[T](code: Int, message: String, responseContent: Option[T], cause: Throwable = null, headers: Map[String, String] = Map.empty) + extends Throwable(s"($code) $message.${responseContent.map(s => s" Content : $s").getOrElse("")}", cause) + with ApiReturnWithHeaders + +sealed case class ApiMethod(value: String) + +object ApiMethods { + val CONNECT = ApiMethod("CONNECT") + val DELETE = ApiMethod("DELETE") + val GET = ApiMethod("GET") + val HEAD = ApiMethod("HEAD") + val OPTIONS = ApiMethod("OPTIONS") + val PATCH = ApiMethod("PATCH") + val POST = ApiMethod("POST") + val PUT = ApiMethod("PUT") + val TRACE = ApiMethod("TRACE") +} + +/** + * This trait needs to be added to any model defined by the api. + */ +trait ApiModel + +/** + * Single trait defining a credential that can be transformed to a paramName / paramValue tupple + */ +sealed trait Credentials { + def asQueryParam: Option[(String, String)] = None +} + +sealed case class BasicCredentials(user: String, password: String) extends Credentials + +sealed case class BearerToken(token: String) extends Credentials + +sealed case class ApiKeyCredentials(key: ApiKeyValue, keyName: String, location: ApiKeyLocation) extends Credentials { + override def asQueryParam: Option[(String, String)] = location match { + case ApiKeyLocations.QUERY => Some((keyName, key.value)) + case _ => None + } +} + +sealed case class ApiKeyValue(value: String) + +sealed trait ApiKeyLocation + +object ApiKeyLocations { + + case object QUERY extends ApiKeyLocation + + case object HEADER extends ApiKeyLocation + + case object COOKIE extends ApiKeyLocation + +} + + +/** + * Case class used to unapply numeric values only in pattern matching + * + * @param value the string representation of the numeric value + */ +sealed case class NumericValue(value: String) { + override def toString: String = value +} + +object NumericValue { + def unapply(n: Any): Option[NumericValue] = n match { + case (_: Int | _: Long | _: Float | _: Double | _: Boolean | _: Byte) => Some(NumericValue(String.valueOf(n))) + case _ => None + } +} + +/** + * Used for params being arrays + */ +sealed case class ArrayValues(values: Seq[Any], format: CollectionFormat = CollectionFormats.CSV) + +object ArrayValues { + def apply(values: Option[Seq[Any]], format: CollectionFormat): ArrayValues = + ArrayValues(values.getOrElse(Seq.empty), format) + + def apply(values: Option[Seq[Any]]): ArrayValues = ArrayValues(values, CollectionFormats.CSV) +} + +case class QueryValues(name: String, value: Any) { + override def toString = { + ParametersMap.ParametersMapImprovements( Map(name -> value)).asFormattedParams.mapValues(_.toString) + .foldRight[String]("") { + case ((name, value), acc) => (if(acc.isEmpty) "" else ",") + s"$name=$value" + } + } +} + +/** + * Defines how arrays should be rendered in query strings. + */ +sealed trait CollectionFormat + +trait MergedArrayFormat extends CollectionFormat { + def separator: String +} + +object CollectionFormats { + + case object CSV extends MergedArrayFormat { + override val separator = "," + } + + case object TSV extends MergedArrayFormat { + override val separator = "\t" + } + + case object SSV extends MergedArrayFormat { + override val separator = " " + } + + case object PIPES extends MergedArrayFormat { + override val separator = "|" + } + + case object MULTI extends CollectionFormat + +} + +object ParametersMap { + + /** + * Pimp parameters maps (Map[String, Any]) in order to transform them in a sequence of String -> Any tupples, + * with valid url-encoding, arrays handling, files preservation, ... + */ + implicit class ParametersMapImprovements(val m: Map[String, Any]) { + + def asFormattedParamsList: List[(String, Any)] = m.toList.flatMap(formattedParams) + + def asFormattedParams: Map[String, Any] = m.flatMap(formattedParams) + + private def urlEncode(v: Any) = URLEncoder.encode(String.valueOf(v), "utf-8").replaceAll("\\+", "%20") + + private def formattedParams(tuple: (String, Any)): Seq[(String, Any)] = formattedParams(tuple._1, tuple._2) + + private def formattedParams(name: String, value: Any): Seq[(String, Any)] = value match { + case arr: ArrayValues => + arr.format match { + case CollectionFormats.MULTI => arr.values.flatMap(formattedParams(name, _)) + case format: MergedArrayFormat => Seq((name, arr.values.mkString(format.separator))) + } + case None => Seq.empty + case Some(opt) => formattedParams(name, opt) + case s: Seq[Any] => formattedParams(name, ArrayValues(s)) + case v: String => Seq((name, urlEncode(v))) + case NumericValue(v) => Seq((name, urlEncode(v))) + case f: File => Seq((name, f)) + case m: ApiModel => Seq((name, m)) + } + } + +} diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/responseState.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/responseState.mustache new file mode 100644 index 000000000000..d1b3798e6de1 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/responseState.mustache @@ -0,0 +1 @@ +{{#isDefault}}Success{{/isDefault}}{{^isDefault}}Error{{/isDefault}} \ No newline at end of file diff --git a/samples/client/petstore/scala-sttp/.openapi-generator-ignore b/samples/client/petstore/scala-sttp/.openapi-generator-ignore new file mode 100644 index 000000000000..7484ee590a38 --- /dev/null +++ b/samples/client/petstore/scala-sttp/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/samples/client/petstore/scala-sttp/.openapi-generator/VERSION b/samples/client/petstore/scala-sttp/.openapi-generator/VERSION new file mode 100644 index 000000000000..bfbf77eb7fad --- /dev/null +++ b/samples/client/petstore/scala-sttp/.openapi-generator/VERSION @@ -0,0 +1 @@ +4.3.0-SNAPSHOT \ No newline at end of file diff --git a/samples/client/petstore/scala-sttp/README.md b/samples/client/petstore/scala-sttp/README.md new file mode 100644 index 000000000000..15c1a0ca9ef3 --- /dev/null +++ b/samples/client/petstore/scala-sttp/README.md @@ -0,0 +1,121 @@ +# scala-akka-petstore-client + +OpenAPI Petstore +- API version: 1.0.0 + +This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + +*Automatically generated by the [OpenAPI Generator](https://openapi-generator.tech)* + +## Requirements + +Building the API client library requires: +1. Java 1.7+ +2. Maven/Gradle/SBT + +## Installation + +To install the API client library to your local Maven repository, simply execute: + +```shell +mvn clean install +``` + +To deploy it to a remote Maven repository instead, configure the settings of the repository and execute: + +```shell +mvn clean deploy +``` + +Refer to the [OSSRH Guide](http://central.sonatype.org/pages/ossrh-guide.html) for more information. + +### Maven users + +Add this dependency to your project's POM: + +```xml + + org.openapitools + scala-akka-petstore-client + 1.0.0 + compile + +``` + +### Gradle users + +Add this dependency to your project's build file: + +```groovy +compile "org.openapitools:scala-akka-petstore-client:1.0.0" +``` + +### SBT users + +```scala +libraryDependencies += "org.openapitools" % "scala-akka-petstore-client" % "1.0.0" +``` + +## Getting Started + +## Documentation for API Endpoints + +All URIs are relative to *http://petstore.swagger.io/v2* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +*PetApi* | **addPet** | **POST** /pet | Add a new pet to the store +*PetApi* | **deletePet** | **DELETE** /pet/${petId} | Deletes a pet +*PetApi* | **findPetsByStatus** | **GET** /pet/findByStatus | Finds Pets by status +*PetApi* | **findPetsByTags** | **GET** /pet/findByTags | Finds Pets by tags +*PetApi* | **getPetById** | **GET** /pet/${petId} | Find pet by ID +*PetApi* | **updatePet** | **PUT** /pet | Update an existing pet +*PetApi* | **updatePetWithForm** | **POST** /pet/${petId} | Updates a pet in the store with form data +*PetApi* | **uploadFile** | **POST** /pet/${petId}/uploadImage | uploads an image +*StoreApi* | **deleteOrder** | **DELETE** /store/order/${orderId} | Delete purchase order by ID +*StoreApi* | **getInventory** | **GET** /store/inventory | Returns pet inventories by status +*StoreApi* | **getOrderById** | **GET** /store/order/${orderId} | Find purchase order by ID +*StoreApi* | **placeOrder** | **POST** /store/order | Place an order for a pet +*UserApi* | **createUser** | **POST** /user | Create user +*UserApi* | **createUsersWithArrayInput** | **POST** /user/createWithArray | Creates list of users with given input array +*UserApi* | **createUsersWithListInput** | **POST** /user/createWithList | Creates list of users with given input array +*UserApi* | **deleteUser** | **DELETE** /user/${username} | Delete user +*UserApi* | **getUserByName** | **GET** /user/${username} | Get user by user name +*UserApi* | **loginUser** | **GET** /user/login | Logs user into the system +*UserApi* | **logoutUser** | **GET** /user/logout | Logs out current logged in user session +*UserApi* | **updateUser** | **PUT** /user/${username} | Updated user + + +## Documentation for Models + + - [ApiResponse](ApiResponse.md) + - [Category](Category.md) + - [InlineObject](InlineObject.md) + - [InlineObject1](InlineObject1.md) + - [Order](Order.md) + - [Pet](Pet.md) + - [Tag](Tag.md) + - [User](User.md) + + +## Documentation for Authorization + +Authentication schemes defined for the API: +### api_key + +- **Type**: API key +- **API key parameter name**: api_key +- **Location**: HTTP header + +### auth_cookie + +- **Type**: API key +- **API key parameter name**: AUTH_KEY +- **Location**: + + +## Author + + + diff --git a/samples/client/petstore/scala-sttp/build.sbt b/samples/client/petstore/scala-sttp/build.sbt new file mode 100644 index 000000000000..806503dc4317 --- /dev/null +++ b/samples/client/petstore/scala-sttp/build.sbt @@ -0,0 +1,29 @@ +version := "1.0.0" +name := "scala-akka-petstore-client" +organization := "org.openapitools" +scalaVersion := "2.12.8" + +val sttp = "2.0.0-RC11" + +libraryDependencies ++= Seq( + "com.softwaremill.sttp.client" %% "core" % sttp, + "com.softwaremill.sttp.client" %% "json4s" % sttp, + + "joda-time" % "joda-time" % "2.10.1", + "org.json4s" %% "json4s-jackson" % "3.6.5", + "org.json4s" %% "json4s-ext" % "3.6.5", + + // test dependencies + "org.scalatest" %% "scalatest" % "3.0.5" % "test", + "junit" % "junit" % "4.13" % "test" +) + +resolvers ++= Seq(Resolver.mavenLocal) + +scalacOptions := Seq( + "-unchecked", + "-deprecation", + "-feature" +) + +publishArtifact in (Compile, packageDoc) := false \ No newline at end of file diff --git a/samples/client/petstore/scala-sttp/pom.xml b/samples/client/petstore/scala-sttp/pom.xml new file mode 100644 index 000000000000..33f89264546a --- /dev/null +++ b/samples/client/petstore/scala-sttp/pom.xml @@ -0,0 +1,259 @@ + + 4.0.0 + + scala-akka-petstore-client + + org.openapitools + scala-akka-petstore-client + 1.0.0 + + jar + + + UTF-8 + UTF-8 + + 1.8 + 2.12.8 + 3.5.3 + 3.2.11 + 2.5.21 + 10.1.7 + 2.10.1 + 1.3.3 + 1.25.2 + 4.13 + 3.0.5 + + 3.3.1 + + + + + org.scala-lang + scala-library + ${scala.version} + provided + + + joda-time + joda-time + ${joda.time.version} + + + com.typesafe + config + ${typesafeconfig.version} + + + com.typesafe.akka + akka-actor_2.12 + ${akka.version} + + + com.typesafe.akka + akka-stream_2.12 + ${akka.version} + + + com.typesafe.akka + akka-http_2.12 + ${akka.http.version} + + + org.json4s + json4s-jackson_2.12 + ${json4s.jackson.version} + + + org.json4s + json4s-ext_2.12 + ${json4s.jackson.version} + + + de.heikoseeberger + akka-http-json4s_2.12 + ${akka.http.json4s.version} + + + + + org.scalatest + scalatest_2.12 + ${scala.test.version} + test + + + junit + junit + ${junit.version} + test + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.0 + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.0.0-M1 + + + enforce-maven + + enforce + + + + + 2.2.0 + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.20.1 + + + + loggerPath + conf/log4j.properties + + + -Xms512m -Xmx1500m + methods + pertest + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.0.2 + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + + jar + test-jar + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.0.0 + + + add_sources + generate-sources + + add-source + + + + + src/main/java + + + + + + add_test_sources + generate-test-sources + + add-test-source + + + + + src/test/java + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + ${java.version} + ${java.version} + + + + net.alchim31.maven + scala-maven-plugin + ${scala.maven.plugin.version} + + + scala-compile-first + process-resources + + add-source + compile + + + + scala-test-compile + process-test-resources + + testCompile + + + + + + -feature + + + -Xms128m + -Xmx1500m + + + + + + \ No newline at end of file diff --git a/samples/client/petstore/scala-sttp/project/build.properties b/samples/client/petstore/scala-sttp/project/build.properties new file mode 100644 index 000000000000..c0bab04941d7 --- /dev/null +++ b/samples/client/petstore/scala-sttp/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.8 diff --git a/samples/client/petstore/scala-sttp/src/main/resources/reference.conf b/samples/client/petstore/scala-sttp/src/main/resources/reference.conf new file mode 100644 index 000000000000..6d419f988eba --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/resources/reference.conf @@ -0,0 +1,24 @@ +org.openapitools.client { + + apiRequest { + + compression { + enabled: false + size-threshold: 0 + } + + trust-certificates: true + + connection-timeout: 5000ms + + default-headers { + "userAgent": "scala-akka-petstore-client_1.0.0" + } + + // let you define custom http status code, as in : + // { code: 601, reason: "some custom http status code", success: false } + custom-codes : [] + } +} + +spray.can.host-connector.max-redirects = 10 \ No newline at end of file diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/Client.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/Client.scala new file mode 100644 index 000000000000..161a79d41105 --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/Client.scala @@ -0,0 +1,27 @@ +package org.openapitools.client + +import org.openapitools.client.api.PetApi +import org.openapitools.client.core.{ApiKeyValue, SttpSerializer} +import sttp.client.HttpURLConnectionBackend + +object Client extends App { + + implicit val serializer = org.json4s.jackson.Serialization + + implicit val sttpSerializer = new SttpSerializer + + val api = new PetApi("https://petstore3.swagger.io/api/v3") + implicit val backend = HttpURLConnectionBackend() + implicit val apiKey = ApiKeyValue("api-key") + //val response = api.getPetById(5) + val response = api.findPetsByStatus(status = Seq("sold","pending")) + + println(response.toCurl) + + val result = response.send() + + result.body match { + case Right(r) => println(r) + case Left(l) => println(l.getMessage) + } +} diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/EnumsSerializers.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/EnumsSerializers.scala new file mode 100644 index 000000000000..71ad618e31fb --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/EnumsSerializers.scala @@ -0,0 +1,51 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.api + +import org.openapitools.client.model._ +import org.json4s._ +import scala.reflect.ClassTag + +object EnumsSerializers { + + def all: Seq[Serializer[_]] = Seq[Serializer[_]]() :+ + new EnumNameSerializer(OrderEnums.Status) :+ + new EnumNameSerializer(PetEnums.Status) + + private class EnumNameSerializer[E <: Enumeration: ClassTag](enum: E) + extends Serializer[E#Value] { + import JsonDSL._ + + val EnumerationClass: Class[E#Value] = classOf[E#Value] + + def deserialize(implicit format: Formats): + PartialFunction[(TypeInfo, JValue), E#Value] = { + case (t @ TypeInfo(EnumerationClass, _), json) if isValid(json) => + json match { + case JString(value) => + enum.withName(value) + case value => + throw new MappingException(s"Can't convert $value to $EnumerationClass") + } + } + + private[this] def isValid(json: JValue) = json match { + case JString(value) if enum.values.exists(_.toString == value) => true + case _ => false + } + + def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { + case i: E#Value => i.toString + } + } + +} diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala new file mode 100644 index 000000000000..a16c8ed6b8b4 --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala @@ -0,0 +1,186 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.api + +import org.openapitools.client.model.ApiResponse +import java.io.File +import org.openapitools.client.model.Pet +import org.openapitools.client.core._ +import sttp.client._ +import sttp.model.Method + +object PetApi { + + def apply(baseUrl: String = "http://petstore.swagger.io/v2")(implicit serializer: SttpSerializer) = new PetApi(baseUrl) +} + +class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { + + import serializer._ + + /* + * Helper to handle Optional header parameters + **/ + implicit class optionalParams(request: RequestT[Identity, Either[String, String], Nothing]) { + def header( header: String, optValue: Option[Any]): RequestT[Identity, Either[String, String], Nothing] = { + optValue.map( value => request.header(header, value.toString)).getOrElse(request) + } + } + + /** + * Expected answers: + * code 200 : Pet (successful operation) + * code 405 : (Invalid input) + * + * @param pet Pet object that needs to be added to the store + */ + def addPet(pet: Pet): RequestT[Identity, Either[ResponseError[Exception], Pet], Nothing] = + basicRequest + .method(Method.POST, uri"$baseUrl/pet") + .contentType("application/json") + .body(pet) + .response(asJson[Pet]) + + + + /** + * Expected answers: + * code 400 : (Invalid pet value) + * + * @param petId Pet id to delete + * @param apiKey + */ + def deletePet(petId: Long, apiKey: Option[String] = None): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + basicRequest + .method(Method.DELETE, uri"$baseUrl/pet/${petId}") + .contentType("application/json") + .header("api_key", apiKey) + .response(asJson[Unit]) + + + + /** + * Multiple status values can be provided with comma separated strings + * + * Expected answers: + * code 200 : Seq[Pet] (successful operation) + * code 400 : (Invalid status value) + * + * @param status Status values that need to be considered for filter + */ + def findPetsByStatus(status: Seq[String]): RequestT[Identity, Either[ResponseError[Exception], Seq[Pet]], Nothing] = + basicRequest + .method(Method.GET, uri"$baseUrl/pet/findByStatus?status=$status") + .contentType("application/json") + .response(asJson[Seq[Pet]]) + + + + /** + * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. + * + * Expected answers: + * code 200 : Seq[Pet] (successful operation) + * code 400 : (Invalid tag value) + * + * @param tags Tags to filter by + */ + def findPetsByTags(tags: Seq[String]): RequestT[Identity, Either[ResponseError[Exception], Seq[Pet]], Nothing] = + basicRequest + .method(Method.GET, uri"$baseUrl/pet/findByTags?tags=$tags") + .contentType("application/json") + .response(asJson[Seq[Pet]]) + + + + /** + * Returns a single pet + * + * Expected answers: + * code 200 : Pet (successful operation) + * code 400 : (Invalid ID supplied) + * code 404 : (Pet not found) + * + * Available security schemes: + * api_key (apiKey) + * + * @param petId ID of pet to return + */ + def getPetById(petId: Long)(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Pet], Nothing] = + basicRequest + .method(Method.GET, uri"$baseUrl/pet/${petId}") + .contentType("application/json") + .response(asJson[Pet]) + + + + /** + * Expected answers: + * code 200 : Pet (successful operation) + * code 400 : (Invalid ID supplied) + * code 404 : (Pet not found) + * code 405 : (Validation exception) + * + * @param pet Pet object that needs to be added to the store + */ + def updatePet(pet: Pet): RequestT[Identity, Either[ResponseError[Exception], Pet], Nothing] = + basicRequest + .method(Method.PUT, uri"$baseUrl/pet") + .contentType("application/json") + .body(pet) + .response(asJson[Pet]) + + + + /** + * Expected answers: + * code 405 : (Invalid input) + * + * @param petId ID of pet that needs to be updated + * @param name Updated name of the pet + * @param status Updated status of the pet + */ + def updatePetWithForm(petId: Long, name: Option[String] = None, status: Option[String] = None): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + basicRequest + .method(Method.POST, uri"$baseUrl/pet/${petId}") + .contentType("application/x-www-form-urlencoded") + .body(Map( + "name" -> name, + "status" -> status, + )) + .response(asJson[Unit]) + + + + /** + * Expected answers: + * code 200 : ApiResponse (successful operation) + * + * @param petId ID of pet to update + * @param additionalMetadata Additional data to pass to server + * @param file file to upload + */ + def uploadFile(petId: Long, additionalMetadata: Option[String] = None, file: Option[File] = None): RequestT[Identity, Either[ResponseError[Exception], ApiResponse], Nothing] = + basicRequest + .method(Method.POST, uri"$baseUrl/pet/${petId}/uploadImage") + .contentType("multipart/form-data") + .body(Map( + "additionalMetadata" -> additionalMetadata, + "file" -> file, + )) + .response(asJson[ApiResponse]) + + + + +} + diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/StoreApi.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/StoreApi.scala new file mode 100644 index 000000000000..58ff84323213 --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/StoreApi.scala @@ -0,0 +1,109 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.api + +import org.openapitools.client.model.Order +import org.openapitools.client.core._ +import org.openapitools.client.core.CollectionFormats._ +import sttp.client._ +import sttp.model.Method +import sttp.client.json4s.SttpJson4sApi + +object StoreApi { + + def apply(baseUrl: String = "http://petstore.swagger.io/v2")(implicit serializer: SttpSerializer) = new StoreApi(baseUrl) +} + +class StoreApi(baseUrl: String)(implicit serializer: SttpSerializer) { + + import serializer._ + + /* + * Helper to handle Optional header parameters + **/ + implicit class optionalParams(request: RequestT[Identity, Either[String, String], Nothing]) { + def header( header: String, optValue: Option[Any]): RequestT[Identity, Either[String, String], Nothing] = { + optValue.map( value => request.header(header, value.toString)).getOrElse(request) + } + } + + /** + * For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors + * + * Expected answers: + * code 400 : (Invalid ID supplied) + * code 404 : (Order not found) + * + * @param orderId ID of the order that needs to be deleted + */ + def deleteOrder(orderId: String): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + basicRequest + .method(Method.DELETE, uri"$baseUrl/store/order/${orderId}") + .contentType("application/json") + .response(asJson[Unit]) + + + + /** + * Returns a map of status codes to quantities + * + * Expected answers: + * code 200 : Map[String, Int] (successful operation) + * + * Available security schemes: + * api_key (apiKey) + */ + def getInventory()(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Map[String, Int]], Nothing] = + basicRequest + .method(Method.GET, uri"$baseUrl/store/inventory") + .contentType("application/json") + .response(asJson[Map[String, Int]]) + + + + /** + * For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions + * + * Expected answers: + * code 200 : Order (successful operation) + * code 400 : (Invalid ID supplied) + * code 404 : (Order not found) + * + * @param orderId ID of pet that needs to be fetched + */ + def getOrderById(orderId: Long): RequestT[Identity, Either[ResponseError[Exception], Order], Nothing] = + basicRequest + .method(Method.GET, uri"$baseUrl/store/order/${orderId}") + .contentType("application/json") + .response(asJson[Order]) + + + + /** + * Expected answers: + * code 200 : Order (successful operation) + * code 400 : (Invalid Order) + * + * @param order order placed for purchasing the pet + */ + def placeOrder(order: Order): RequestT[Identity, Either[ResponseError[Exception], Order], Nothing] = + basicRequest + .method(Method.POST, uri"$baseUrl/store/order") + .contentType("application/json") + .body(order) + .response(asJson[Order]) + + + + +} + diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/UserApi.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/UserApi.scala new file mode 100644 index 000000000000..f9b8e35407a7 --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/UserApi.scala @@ -0,0 +1,190 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.api + +import org.openapitools.client.model.User +import org.openapitools.client.core._ +import org.openapitools.client.core.CollectionFormats._ +import sttp.client._ +import sttp.model.Method +import sttp.client.json4s.SttpJson4sApi + +object UserApi { + + def apply(baseUrl: String = "http://petstore.swagger.io/v2")(implicit serializer: SttpSerializer) = new UserApi(baseUrl) +} + +class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { + + import serializer._ + + /* + * Helper to handle Optional header parameters + **/ + implicit class optionalParams(request: RequestT[Identity, Either[String, String], Nothing]) { + def header( header: String, optValue: Option[Any]): RequestT[Identity, Either[String, String], Nothing] = { + optValue.map( value => request.header(header, value.toString)).getOrElse(request) + } + } + + /** + * This can only be done by the logged in user. + * + * Expected answers: + * code 0 : (successful operation) + * + * Available security schemes: + * auth_cookie (apiKey) + * + * @param user Created user object + */ + def createUser(user: User)(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + basicRequest + .method(Method.POST, uri"$baseUrl/user") + .contentType("application/json") + .body(user) + .response(asJson[Unit]) + + + + /** + * Expected answers: + * code 0 : (successful operation) + * + * Available security schemes: + * auth_cookie (apiKey) + * + * @param user List of user object + */ + def createUsersWithArrayInput(user: Seq[User])(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + basicRequest + .method(Method.POST, uri"$baseUrl/user/createWithArray") + .contentType("application/json") + .body(user) + .response(asJson[Unit]) + + + + /** + * Expected answers: + * code 0 : (successful operation) + * + * Available security schemes: + * auth_cookie (apiKey) + * + * @param user List of user object + */ + def createUsersWithListInput(user: Seq[User])(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + basicRequest + .method(Method.POST, uri"$baseUrl/user/createWithList") + .contentType("application/json") + .body(user) + .response(asJson[Unit]) + + + + /** + * This can only be done by the logged in user. + * + * Expected answers: + * code 400 : (Invalid username supplied) + * code 404 : (User not found) + * + * Available security schemes: + * auth_cookie (apiKey) + * + * @param username The name that needs to be deleted + */ + def deleteUser(username: String)(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + basicRequest + .method(Method.DELETE, uri"$baseUrl/user/${username}") + .contentType("application/json") + .response(asJson[Unit]) + + + + /** + * Expected answers: + * code 200 : User (successful operation) + * code 400 : (Invalid username supplied) + * code 404 : (User not found) + * + * @param username The name that needs to be fetched. Use user1 for testing. + */ + def getUserByName(username: String): RequestT[Identity, Either[ResponseError[Exception], User], Nothing] = + basicRequest + .method(Method.GET, uri"$baseUrl/user/${username}") + .contentType("application/json") + .response(asJson[User]) + + + + /** + * Expected answers: + * code 200 : String (successful operation) + * Headers : + * Set-Cookie - Cookie authentication key for use with the `auth_cookie` apiKey authentication. + * X-Rate-Limit - calls per hour allowed by the user + * X-Expires-After - date in UTC when toekn expires + * code 400 : (Invalid username/password supplied) + * + * @param username The user name for login + * @param password The password for login in clear text + */ + def loginUser(username: String, password: String): RequestT[Identity, Either[ResponseError[Exception], String], Nothing] = + basicRequest + .method(Method.GET, uri"$baseUrl/user/login?username=$username&password=$password") + .contentType("application/json") + .response(asJson[String]) + + + + /** + * Expected answers: + * code 0 : (successful operation) + * + * Available security schemes: + * auth_cookie (apiKey) + */ + def logoutUser()(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + basicRequest + .method(Method.GET, uri"$baseUrl/user/logout") + .contentType("application/json") + .response(asJson[Unit]) + + + + /** + * This can only be done by the logged in user. + * + * Expected answers: + * code 400 : (Invalid user supplied) + * code 404 : (User not found) + * + * Available security schemes: + * auth_cookie (apiKey) + * + * @param username name that need to be deleted + * @param user Updated user object + */ + def updateUser(username: String, user: User)(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + basicRequest + .method(Method.PUT, uri"$baseUrl/user/${username}") + .contentType("application/json") + .body(user) + .response(asJson[Unit]) + + + + +} + diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala new file mode 100644 index 000000000000..d4a2efc8475a --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala @@ -0,0 +1,108 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.core + +import java.io.File + +import org.joda.time.DateTime +import org.joda.time.format.ISODateTimeFormat +import org.json4s.JsonAST.JString +import org.json4s._ +import org.json4s.jackson.JsonMethods._ +import org.json4s.jackson.Serialization + +import scala.collection.immutable +import scala.concurrent.{ ExecutionContext, ExecutionContextExecutor, Future } +import scala.reflect.ClassTag +import sttp.client.json4s.SttpJson4sApi + +class SttpSerializer(implicit val serialization: org.json4s.Serialization) extends SttpJson4sApi + +object ApiInvoker { + + def apply(): ApiInvoker = + apply(DefaultFormats + DateTimeSerializer) + + def apply(serializers: Iterable[Serializer[_]]): ApiInvoker = + apply(DefaultFormats + DateTimeSerializer ++ serializers) + + def apply(formats: Formats): ApiInvoker = new ApiInvoker(formats) + + + /** + * Allows request execution without calling apiInvoker.execute(request) + * request.response can be used to get a future of the ApiResponse generated. + * request.result can be used to get a future of the expected ApiResponse content. If content doesn't match, a + * Future will failed with a ClassCastException + * + * @param request the apiRequest to be executed + */ + implicit class ApiRequestImprovements[T: Manifest](request: ApiRequest[T]) { + + def response(invoker: ApiInvoker)(implicit ec: ExecutionContext): Future[ApiResponse[T]] = + response(ec, invoker) + + def response(implicit ec: ExecutionContext, invoker: ApiInvoker): Future[ApiResponse[T]] = + invoker.execute(request) + + def result[U <: T](implicit c: ClassTag[U], ec: ExecutionContext, invoker: ApiInvoker): Future[U] = + invoker.execute(request).map(_.content).mapTo[U] + + } + + case object DateTimeSerializer extends CustomSerializer[DateTime](_ => ( { + case JString(s) => + ISODateTimeFormat.dateOptionalTimeParser().parseDateTime(s) + }, { + case d: DateTime => + JString(ISODateTimeFormat.dateTime().print(d)) + }) + ) + +} + + +class ApiInvoker(formats: Formats) { +/* + import org.openapitools.client.core.ApiInvoker._ + import org.openapitools.client.core.ParametersMap._ + + implicit val jsonFormats: Formats = formats + + private implicit val serialization: Serialization = jackson.Serialization +*/ + def execute[T: Manifest](r: ApiRequest[T]): Future[ApiResponse[T]] = { +// implicit val timeout: Timeout = settings.connectionTimeout +/* + val request = createRequest(makeUri(r), r) + + http + .singleRequest(request) + .map { response => + val decoder: Coder with StreamDecoder = response.encoding match { + case HttpEncodings.gzip ⇒ + Gzip + case HttpEncodings.deflate ⇒ + Deflate + case HttpEncodings.identity ⇒ + NoCoding + case HttpEncoding(encoding) => + throw new IllegalArgumentException(s"Unsupported encoding: $encoding") + } + + decoder.decodeMessage(response) + } + .flatMap(unmarshallApiResponse(r)) + */ + null + } +} diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiRequest.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiRequest.scala new file mode 100644 index 000000000000..3dfa61094de0 --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiRequest.scala @@ -0,0 +1,65 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.core + +sealed trait ResponseState + +object ResponseState { + + case object Success extends ResponseState + + case object Error extends ResponseState + +} + +case class ApiRequest[U]( + // required fields + method: ApiMethod, + basePath: String, + operationPath: String, + contentType: String, + + // optional fields + responses: Map[Int, (Manifest[_], ResponseState)] = Map.empty, + bodyParam: Option[Any] = None, + formParams: Map[String, Any] = Map.empty, + pathParams: Map[String, Any] = Map.empty, + queryParams: Map[String, Any] = Map.empty, + headerParams: Map[String, Any] = Map.empty, + credentials: Seq[Credentials] = List.empty) { + + def withCredentials(cred: Credentials): ApiRequest[U] = copy[U](credentials = credentials :+ cred) + + def withApiKey(key: ApiKeyValue, keyName: String, location: ApiKeyLocation): ApiRequest[U] = withCredentials(ApiKeyCredentials(key, keyName, location)) + + def withSuccessResponse[T](code: Int)(implicit m: Manifest[T]): ApiRequest[U] = copy[U](responses = responses + (code -> (m, ResponseState.Success))) + + def withErrorResponse[T](code: Int)(implicit m: Manifest[T]): ApiRequest[U] = copy[U](responses = responses + (code -> (m, ResponseState.Error))) + + def withDefaultSuccessResponse[T](implicit m: Manifest[T]): ApiRequest[U] = withSuccessResponse[T](0) + + def withDefaultErrorResponse[T](implicit m: Manifest[T]): ApiRequest[U] = withErrorResponse[T](0) + + def responseForCode(statusCode: Int): Option[(Manifest[_], ResponseState)] = responses.get(statusCode) orElse responses.get(0) + + def withoutBody(): ApiRequest[U] = copy[U](bodyParam = None) + + def withBody(body: Any): ApiRequest[U] = copy[U](bodyParam = Some(body)) + + def withFormParam(name: String, value: Any): ApiRequest[U] = copy[U](formParams = formParams + (name -> value)) + + def withPathParam(name: String, value: Any): ApiRequest[U] = copy[U](pathParams = pathParams + (name -> value)) + + def withQueryParam(name: String, value: Any): ApiRequest[U] = copy[U](queryParams = queryParams + (name -> value)) + + def withHeaderParam(name: String, value: Any): ApiRequest[U] = copy[U](headerParams = headerParams + (name -> value)) +} diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/requests.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/requests.scala new file mode 100644 index 000000000000..29e5b8222d05 --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/requests.scala @@ -0,0 +1,205 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.core + +import java.io.File +import java.net.URLEncoder + +import scala.util.Try + +sealed trait ApiReturnWithHeaders { + def headers: Map[String, String] + + def header(name: String): Option[String] = headers.get(name) + + def getStringHeader(name: String): Option[String] = header(name) + + // workaround: return date time header in string instead of datetime object + def getDateTimeHeader(name: String): Option[String] = header(name) + + def getIntHeader(name: String): Option[Int] = castedHeader(name, java.lang.Integer.parseInt) + + def getLongHeader(name: String): Option[Long] = castedHeader(name, java.lang.Long.parseLong) + + def getFloatHeader(name: String): Option[Float] = castedHeader(name, java.lang.Float.parseFloat) + + def getDoubleHeader(name: String): Option[Double] = castedHeader(name, java.lang.Double.parseDouble) + + def getBooleanHeader(name: String): Option[Boolean] = castedHeader(name, java.lang.Boolean.parseBoolean) + + private def castedHeader[U](name: String, conversion: String => U): Option[U] = { + Try { + header(name).map(conversion) + }.get + } +} + +sealed case class ApiResponse[T](code: Int, content: T, headers: Map[String, String] = Map.empty) + extends ApiReturnWithHeaders + +sealed case class ApiError[T](code: Int, message: String, responseContent: Option[T], cause: Throwable = null, headers: Map[String, String] = Map.empty) + extends Throwable(s"($code) $message.${responseContent.map(s => s" Content : $s").getOrElse("")}", cause) + with ApiReturnWithHeaders + +sealed case class ApiMethod(value: String) + +object ApiMethods { + val CONNECT = ApiMethod("CONNECT") + val DELETE = ApiMethod("DELETE") + val GET = ApiMethod("GET") + val HEAD = ApiMethod("HEAD") + val OPTIONS = ApiMethod("OPTIONS") + val PATCH = ApiMethod("PATCH") + val POST = ApiMethod("POST") + val PUT = ApiMethod("PUT") + val TRACE = ApiMethod("TRACE") +} + +/** + * This trait needs to be added to any model defined by the api. + */ +trait ApiModel + +/** + * Single trait defining a credential that can be transformed to a paramName / paramValue tupple + */ +sealed trait Credentials { + def asQueryParam: Option[(String, String)] = None +} + +sealed case class BasicCredentials(user: String, password: String) extends Credentials + +sealed case class BearerToken(token: String) extends Credentials + +sealed case class ApiKeyCredentials(key: ApiKeyValue, keyName: String, location: ApiKeyLocation) extends Credentials { + override def asQueryParam: Option[(String, String)] = location match { + case ApiKeyLocations.QUERY => Some((keyName, key.value)) + case _ => None + } +} + +sealed case class ApiKeyValue(value: String) + +sealed trait ApiKeyLocation + +object ApiKeyLocations { + + case object QUERY extends ApiKeyLocation + + case object HEADER extends ApiKeyLocation + + case object COOKIE extends ApiKeyLocation + +} + + +/** + * Case class used to unapply numeric values only in pattern matching + * + * @param value the string representation of the numeric value + */ +sealed case class NumericValue(value: String) { + override def toString: String = value +} + +object NumericValue { + def unapply(n: Any): Option[NumericValue] = n match { + case (_: Int | _: Long | _: Float | _: Double | _: Boolean | _: Byte) => Some(NumericValue(String.valueOf(n))) + case _ => None + } +} + +/** + * Used for params being arrays + */ +sealed case class ArrayValues(values: Seq[Any], format: CollectionFormat = CollectionFormats.CSV) + +object ArrayValues { + def apply(values: Option[Seq[Any]], format: CollectionFormat): ArrayValues = + ArrayValues(values.getOrElse(Seq.empty), format) + + def apply(values: Option[Seq[Any]]): ArrayValues = ArrayValues(values, CollectionFormats.CSV) +} + +case class QueryValues(name: String, value: Any) { + override def toString = { + ParametersMap.ParametersMapImprovements( Map(name -> value)).asFormattedParams.mapValues(_.toString) + .foldRight[String]("") { + case ((name, value), acc) => (if(acc.isEmpty) "" else ",") + s"$name=$value" + } + } +} + +/** + * Defines how arrays should be rendered in query strings. + */ +sealed trait CollectionFormat + +trait MergedArrayFormat extends CollectionFormat { + def separator: String +} + +object CollectionFormats { + + case object CSV extends MergedArrayFormat { + override val separator = "," + } + + case object TSV extends MergedArrayFormat { + override val separator = "\t" + } + + case object SSV extends MergedArrayFormat { + override val separator = " " + } + + case object PIPES extends MergedArrayFormat { + override val separator = "|" + } + + case object MULTI extends CollectionFormat + +} + +object ParametersMap { + + /** + * Pimp parameters maps (Map[String, Any]) in order to transform them in a sequence of String -> Any tupples, + * with valid url-encoding, arrays handling, files preservation, ... + */ + implicit class ParametersMapImprovements(val m: Map[String, Any]) { + + def asFormattedParamsList: List[(String, Any)] = m.toList.flatMap(formattedParams) + + def asFormattedParams: Map[String, Any] = m.flatMap(formattedParams) + + private def urlEncode(v: Any) = URLEncoder.encode(String.valueOf(v), "utf-8").replaceAll("\\+", "%20") + + private def formattedParams(tuple: (String, Any)): Seq[(String, Any)] = formattedParams(tuple._1, tuple._2) + + private def formattedParams(name: String, value: Any): Seq[(String, Any)] = value match { + case arr: ArrayValues => + arr.format match { + case CollectionFormats.MULTI => arr.values.flatMap(formattedParams(name, _)) + case format: MergedArrayFormat => Seq((name, arr.values.mkString(format.separator))) + } + case None => Seq.empty + case Some(opt) => formattedParams(name, opt) + case s: Seq[Any] => formattedParams(name, ArrayValues(s)) + case v: String => Seq((name, urlEncode(v))) + case NumericValue(v) => Seq((name, urlEncode(v))) + case f: File => Seq((name, f)) + case m: ApiModel => Seq((name, m)) + } + } + +} diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/ApiResponse.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/ApiResponse.scala new file mode 100644 index 000000000000..3ddf32c9bda7 --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/ApiResponse.scala @@ -0,0 +1,22 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.model + +import org.openapitools.client.core.ApiModel + +case class ApiResponse ( + code: Option[Int] = None, + `type`: Option[String] = None, + message: Option[String] = None +) extends ApiModel + + diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Category.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Category.scala new file mode 100644 index 000000000000..e62645a38b2a --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Category.scala @@ -0,0 +1,21 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.model + +import org.openapitools.client.core.ApiModel + +case class Category ( + id: Option[Long] = None, + name: Option[String] = None +) extends ApiModel + + diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject.scala new file mode 100644 index 000000000000..3d9ec200c9fc --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject.scala @@ -0,0 +1,23 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.model + +import org.openapitools.client.core.ApiModel + +case class InlineObject ( + /* Updated name of the pet */ + name: Option[String] = None, + /* Updated status of the pet */ + status: Option[String] = None +) extends ApiModel + + diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject1.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject1.scala new file mode 100644 index 000000000000..c41794c43d43 --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject1.scala @@ -0,0 +1,24 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.model + +import java.io.File +import org.openapitools.client.core.ApiModel + +case class InlineObject1 ( + /* Additional data to pass to server */ + additionalMetadata: Option[String] = None, + /* file to upload */ + file: Option[File] = None +) extends ApiModel + + diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Order.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Order.scala new file mode 100644 index 000000000000..6a9fdc141f08 --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Order.scala @@ -0,0 +1,37 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.model + +import org.joda.time.DateTime +import org.openapitools.client.core.ApiModel + +case class Order ( + id: Option[Long] = None, + petId: Option[Long] = None, + quantity: Option[Int] = None, + shipDate: Option[DateTime] = None, + /* Order Status */ + status: Option[OrderEnums.Status] = None, + complete: Option[Boolean] = None +) extends ApiModel + +object OrderEnums { + + type Status = Status.Value + object Status extends Enumeration { + val Placed = Value("placed") + val Approved = Value("approved") + val Delivered = Value("delivered") + } + +} + diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Pet.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Pet.scala new file mode 100644 index 000000000000..3a78a7c3f183 --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Pet.scala @@ -0,0 +1,36 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.model + +import org.openapitools.client.core.ApiModel + +case class Pet ( + id: Option[Long] = None, + category: Option[Category] = None, + name: String, + photoUrls: Seq[String], + tags: Option[Seq[Tag]] = None, + /* pet status in the store */ + status: Option[PetEnums.Status] = None +) extends ApiModel + +object PetEnums { + + type Status = Status.Value + object Status extends Enumeration { + val Available = Value("available") + val Pending = Value("pending") + val Sold = Value("sold") + } + +} + diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Tag.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Tag.scala new file mode 100644 index 000000000000..ac0c7763720c --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Tag.scala @@ -0,0 +1,21 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.model + +import org.openapitools.client.core.ApiModel + +case class Tag ( + id: Option[Long] = None, + name: Option[String] = None +) extends ApiModel + + diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/User.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/User.scala new file mode 100644 index 000000000000..aad2117b16d5 --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/User.scala @@ -0,0 +1,28 @@ +/** + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +package org.openapitools.client.model + +import org.openapitools.client.core.ApiModel + +case class User ( + id: Option[Long] = None, + username: Option[String] = None, + firstName: Option[String] = None, + lastName: Option[String] = None, + email: Option[String] = None, + password: Option[String] = None, + phone: Option[String] = None, + /* User Status */ + userStatus: Option[Int] = None +) extends ApiModel + + From 35d120d2f44a2230c57c85550f8f2c476e45590b Mon Sep 17 00:00:00 2001 From: Aleksandr Nekrasov Date: Tue, 25 Feb 2020 17:12:42 +0700 Subject: [PATCH 2/9] invoker for sttp fixed and tests added --- .../scala-sttp-client/apiInvoker.mustache | 90 ++++--------- .../scala-sttp-client/build.sbt.mustache | 2 +- samples/client/petstore/scala-sttp/build.sbt | 2 +- .../org/openapitools/client/Client.scala | 27 ---- .../org/openapitools/client/api/PetApi.scala | 2 + .../openapitools/client/core/ApiInvoker.scala | 89 ++++--------- .../openapitools/client/core/ApiRequest.scala | 65 --------- .../openapitools/client/core/requests.scala | 48 ------- .../src/test/scala/PetApiTest.scala | 123 ++++++++++++++++++ 9 files changed, 173 insertions(+), 275 deletions(-) delete mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/Client.scala delete mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiRequest.scala create mode 100644 samples/client/petstore/scala-sttp/src/test/scala/PetApiTest.scala diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache index 902b2ad1d981..ce1bf6a0fa25 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache @@ -7,46 +7,39 @@ import org.joda.time.DateTime import org.joda.time.format.ISODateTimeFormat import org.json4s.JsonAST.JString import org.json4s._ -import org.json4s.jackson.JsonMethods._ -import org.json4s.jackson.Serialization - -import scala.collection.immutable -import scala.concurrent.{ ExecutionContext, ExecutionContextExecutor, Future } -import scala.reflect.ClassTag +import sttp.client._ +import org.openapitools.client.api.EnumsSerializers +import org.openapitools.client.core.ApiInvoker.DateTimeSerializer import sttp.client.json4s.SttpJson4sApi +import sttp.client.monad.MonadError -class SttpSerializer(implicit val serialization: org.json4s.Serialization) extends SttpJson4sApi - -object ApiInvoker { - - def apply(): ApiInvoker = - apply(DefaultFormats + DateTimeSerializer) - - def apply(serializers: Iterable[Serializer[_]]): ApiInvoker = - apply(DefaultFormats + DateTimeSerializer ++ serializers) +class SttpSerializer(implicit val format: Formats = DefaultFormats ++ EnumsSerializers.all + DateTimeSerializer, + implicit val serialization: org.json4s.Serialization = org.json4s.jackson.Serialization) extends SttpJson4sApi - def apply(formats: Formats): ApiInvoker = new ApiInvoker(formats) +class HttpException(val statusCode: Int, val statusText: String, val message: String) extends Exception(s"[$statusCode] $statusText: $message") +object ApiInvoker { /** * Allows request execution without calling apiInvoker.execute(request) - * request.response can be used to get a future of the ApiResponse generated. - * request.result can be used to get a future of the expected ApiResponse content. If content doesn't match, a - * Future will failed with a ClassCastException + * request.result can be used to get a monad wrapped content. * * @param request the apiRequest to be executed */ - implicit class ApiRequestImprovements[T: Manifest](request: ApiRequest[T]) { - - def response(invoker: ApiInvoker)(implicit ec: ExecutionContext): Future[ApiResponse[T]] = - response(ec, invoker) - - def response(implicit ec: ExecutionContext, invoker: ApiInvoker): Future[ApiResponse[T]] = - invoker.execute(request) - - def result[U <: T](implicit c: ClassTag[U], ec: ExecutionContext, invoker: ApiInvoker): Future[U] = - invoker.execute(request).map(_.content).mapTo[U] - + implicit class ApiRequestImprovements[R[_], RE, T](request: RequestT[Identity, Either[ResponseError[Exception], T], Nothing]) + (implicit backend: SttpBackend[R, Nothing, Nothing]) { + + def result: R[T] = { + val responseT = request.send() + val ME: MonadError[R] = backend.responseMonad + ME.flatMap(responseT) { + response => + response.body match { + case Left(ex) => ME.error[T](new HttpException(response.code.code, response.statusText, ex.body)) + case Right(value) => ME.unit(value) + } + } + } } case object DateTimeSerializer extends CustomSerializer[DateTime](_ => ( { @@ -59,40 +52,3 @@ object ApiInvoker { ) } - - -class ApiInvoker(formats: Formats) { -/* - import {{{mainPackage}}}.core.ApiInvoker._ - import {{{mainPackage}}}.core.ParametersMap._ - - implicit val jsonFormats: Formats = formats - - private implicit val serialization: Serialization = jackson.Serialization -*/ - def execute[T: Manifest](r: ApiRequest[T]): Future[ApiResponse[T]] = { -// implicit val timeout: Timeout = settings.connectionTimeout -/* - val request = createRequest(makeUri(r), r) - - http - .singleRequest(request) - .map { response => - val decoder: Coder with StreamDecoder = response.encoding match { - case HttpEncodings.gzip ⇒ - Gzip - case HttpEncodings.deflate ⇒ - Deflate - case HttpEncodings.identity ⇒ - NoCoding - case HttpEncoding(encoding) => - throw new IllegalArgumentException(s"Unsupported encoding: $encoding") - } - - decoder.decodeMessage(response) - } - .flatMap(unmarshallApiResponse(r)) - */ - null - } -} diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache index 555021a2466b..f2de98b7ffa2 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache @@ -3,7 +3,7 @@ name := "{{artifactId}}" organization := "{{groupId}}" scalaVersion := "2.12.8" -val sttp = "2.0.0-RC11" +val sttp = "2.0.0" libraryDependencies ++= Seq( "com.softwaremill.sttp.client" %% "core" % sttp, diff --git a/samples/client/petstore/scala-sttp/build.sbt b/samples/client/petstore/scala-sttp/build.sbt index 806503dc4317..ec019b0dc375 100644 --- a/samples/client/petstore/scala-sttp/build.sbt +++ b/samples/client/petstore/scala-sttp/build.sbt @@ -3,7 +3,7 @@ name := "scala-akka-petstore-client" organization := "org.openapitools" scalaVersion := "2.12.8" -val sttp = "2.0.0-RC11" +val sttp = "2.0.0" libraryDependencies ++= Seq( "com.softwaremill.sttp.client" %% "core" % sttp, diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/Client.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/Client.scala deleted file mode 100644 index 161a79d41105..000000000000 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/Client.scala +++ /dev/null @@ -1,27 +0,0 @@ -package org.openapitools.client - -import org.openapitools.client.api.PetApi -import org.openapitools.client.core.{ApiKeyValue, SttpSerializer} -import sttp.client.HttpURLConnectionBackend - -object Client extends App { - - implicit val serializer = org.json4s.jackson.Serialization - - implicit val sttpSerializer = new SttpSerializer - - val api = new PetApi("https://petstore3.swagger.io/api/v3") - implicit val backend = HttpURLConnectionBackend() - implicit val apiKey = ApiKeyValue("api-key") - //val response = api.getPetById(5) - val response = api.findPetsByStatus(status = Seq("sold","pending")) - - println(response.toCurl) - - val result = response.send() - - result.body match { - case Right(r) => println(r) - case Left(l) => println(l.getMessage) - } -} diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala index a16c8ed6b8b4..42528a30ba5e 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala @@ -15,8 +15,10 @@ import org.openapitools.client.model.ApiResponse import java.io.File import org.openapitools.client.model.Pet import org.openapitools.client.core._ +import org.openapitools.client.core.CollectionFormats._ import sttp.client._ import sttp.model.Method +import sttp.client.json4s.SttpJson4sApi object PetApi { diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala index d4a2efc8475a..2d8285f1816b 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala @@ -17,46 +17,40 @@ import org.joda.time.DateTime import org.joda.time.format.ISODateTimeFormat import org.json4s.JsonAST.JString import org.json4s._ -import org.json4s.jackson.JsonMethods._ -import org.json4s.jackson.Serialization - -import scala.collection.immutable -import scala.concurrent.{ ExecutionContext, ExecutionContextExecutor, Future } -import scala.reflect.ClassTag +import org.openapitools.client.api.EnumsSerializers +import org.openapitools.client.core.ApiInvoker.DateTimeSerializer +import sttp.client._ import sttp.client.json4s.SttpJson4sApi +import sttp.client.monad.MonadError -class SttpSerializer(implicit val serialization: org.json4s.Serialization) extends SttpJson4sApi - -object ApiInvoker { +class SttpSerializer(implicit val format: Formats = DefaultFormats ++ EnumsSerializers.all + DateTimeSerializer, + implicit val serialization: org.json4s.Serialization = org.json4s.jackson.Serialization) extends SttpJson4sApi - def apply(): ApiInvoker = - apply(DefaultFormats + DateTimeSerializer) +class HttpException(val statusCode: Int, val statusText: String, val message: String) extends Exception(s"[$statusCode] $statusText: $message") - def apply(serializers: Iterable[Serializer[_]]): ApiInvoker = - apply(DefaultFormats + DateTimeSerializer ++ serializers) - - def apply(formats: Formats): ApiInvoker = new ApiInvoker(formats) +object ApiInvoker { /** * Allows request execution without calling apiInvoker.execute(request) - * request.response can be used to get a future of the ApiResponse generated. - * request.result can be used to get a future of the expected ApiResponse content. If content doesn't match, a - * Future will failed with a ClassCastException + * request.result can be used to get a monad wrapped content. * * @param request the apiRequest to be executed */ - implicit class ApiRequestImprovements[T: Manifest](request: ApiRequest[T]) { - - def response(invoker: ApiInvoker)(implicit ec: ExecutionContext): Future[ApiResponse[T]] = - response(ec, invoker) - - def response(implicit ec: ExecutionContext, invoker: ApiInvoker): Future[ApiResponse[T]] = - invoker.execute(request) - - def result[U <: T](implicit c: ClassTag[U], ec: ExecutionContext, invoker: ApiInvoker): Future[U] = - invoker.execute(request).map(_.content).mapTo[U] - + implicit class ApiRequestImprovements[R[_], RE, T](request: RequestT[Identity, Either[ResponseError[Exception], T], Nothing]) + (implicit backend: SttpBackend[R, Nothing, Nothing]) { + + def result: R[T] = { + val responseT = request.send() + val ME: MonadError[R] = backend.responseMonad + ME.flatMap(responseT) { + response => + response.body match { + case Left(ex) => ME.error[T](new HttpException(response.code.code, response.statusText, ex.body)) + case Right(value) => ME.unit(value) + } + } + } } case object DateTimeSerializer extends CustomSerializer[DateTime](_ => ( { @@ -69,40 +63,3 @@ object ApiInvoker { ) } - - -class ApiInvoker(formats: Formats) { -/* - import org.openapitools.client.core.ApiInvoker._ - import org.openapitools.client.core.ParametersMap._ - - implicit val jsonFormats: Formats = formats - - private implicit val serialization: Serialization = jackson.Serialization -*/ - def execute[T: Manifest](r: ApiRequest[T]): Future[ApiResponse[T]] = { -// implicit val timeout: Timeout = settings.connectionTimeout -/* - val request = createRequest(makeUri(r), r) - - http - .singleRequest(request) - .map { response => - val decoder: Coder with StreamDecoder = response.encoding match { - case HttpEncodings.gzip ⇒ - Gzip - case HttpEncodings.deflate ⇒ - Deflate - case HttpEncodings.identity ⇒ - NoCoding - case HttpEncoding(encoding) => - throw new IllegalArgumentException(s"Unsupported encoding: $encoding") - } - - decoder.decodeMessage(response) - } - .flatMap(unmarshallApiResponse(r)) - */ - null - } -} diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiRequest.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiRequest.scala deleted file mode 100644 index 3dfa61094de0..000000000000 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiRequest.scala +++ /dev/null @@ -1,65 +0,0 @@ -/** - * OpenAPI Petstore - * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. - * - * The version of the OpenAPI document: 1.0.0 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ -package org.openapitools.client.core - -sealed trait ResponseState - -object ResponseState { - - case object Success extends ResponseState - - case object Error extends ResponseState - -} - -case class ApiRequest[U]( - // required fields - method: ApiMethod, - basePath: String, - operationPath: String, - contentType: String, - - // optional fields - responses: Map[Int, (Manifest[_], ResponseState)] = Map.empty, - bodyParam: Option[Any] = None, - formParams: Map[String, Any] = Map.empty, - pathParams: Map[String, Any] = Map.empty, - queryParams: Map[String, Any] = Map.empty, - headerParams: Map[String, Any] = Map.empty, - credentials: Seq[Credentials] = List.empty) { - - def withCredentials(cred: Credentials): ApiRequest[U] = copy[U](credentials = credentials :+ cred) - - def withApiKey(key: ApiKeyValue, keyName: String, location: ApiKeyLocation): ApiRequest[U] = withCredentials(ApiKeyCredentials(key, keyName, location)) - - def withSuccessResponse[T](code: Int)(implicit m: Manifest[T]): ApiRequest[U] = copy[U](responses = responses + (code -> (m, ResponseState.Success))) - - def withErrorResponse[T](code: Int)(implicit m: Manifest[T]): ApiRequest[U] = copy[U](responses = responses + (code -> (m, ResponseState.Error))) - - def withDefaultSuccessResponse[T](implicit m: Manifest[T]): ApiRequest[U] = withSuccessResponse[T](0) - - def withDefaultErrorResponse[T](implicit m: Manifest[T]): ApiRequest[U] = withErrorResponse[T](0) - - def responseForCode(statusCode: Int): Option[(Manifest[_], ResponseState)] = responses.get(statusCode) orElse responses.get(0) - - def withoutBody(): ApiRequest[U] = copy[U](bodyParam = None) - - def withBody(body: Any): ApiRequest[U] = copy[U](bodyParam = Some(body)) - - def withFormParam(name: String, value: Any): ApiRequest[U] = copy[U](formParams = formParams + (name -> value)) - - def withPathParam(name: String, value: Any): ApiRequest[U] = copy[U](pathParams = pathParams + (name -> value)) - - def withQueryParam(name: String, value: Any): ApiRequest[U] = copy[U](queryParams = queryParams + (name -> value)) - - def withHeaderParam(name: String, value: Any): ApiRequest[U] = copy[U](headerParams = headerParams + (name -> value)) -} diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/requests.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/requests.scala index 29e5b8222d05..bb54c3f088a8 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/requests.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/requests.scala @@ -16,54 +16,6 @@ import java.net.URLEncoder import scala.util.Try -sealed trait ApiReturnWithHeaders { - def headers: Map[String, String] - - def header(name: String): Option[String] = headers.get(name) - - def getStringHeader(name: String): Option[String] = header(name) - - // workaround: return date time header in string instead of datetime object - def getDateTimeHeader(name: String): Option[String] = header(name) - - def getIntHeader(name: String): Option[Int] = castedHeader(name, java.lang.Integer.parseInt) - - def getLongHeader(name: String): Option[Long] = castedHeader(name, java.lang.Long.parseLong) - - def getFloatHeader(name: String): Option[Float] = castedHeader(name, java.lang.Float.parseFloat) - - def getDoubleHeader(name: String): Option[Double] = castedHeader(name, java.lang.Double.parseDouble) - - def getBooleanHeader(name: String): Option[Boolean] = castedHeader(name, java.lang.Boolean.parseBoolean) - - private def castedHeader[U](name: String, conversion: String => U): Option[U] = { - Try { - header(name).map(conversion) - }.get - } -} - -sealed case class ApiResponse[T](code: Int, content: T, headers: Map[String, String] = Map.empty) - extends ApiReturnWithHeaders - -sealed case class ApiError[T](code: Int, message: String, responseContent: Option[T], cause: Throwable = null, headers: Map[String, String] = Map.empty) - extends Throwable(s"($code) $message.${responseContent.map(s => s" Content : $s").getOrElse("")}", cause) - with ApiReturnWithHeaders - -sealed case class ApiMethod(value: String) - -object ApiMethods { - val CONNECT = ApiMethod("CONNECT") - val DELETE = ApiMethod("DELETE") - val GET = ApiMethod("GET") - val HEAD = ApiMethod("HEAD") - val OPTIONS = ApiMethod("OPTIONS") - val PATCH = ApiMethod("PATCH") - val POST = ApiMethod("POST") - val PUT = ApiMethod("PUT") - val TRACE = ApiMethod("TRACE") -} - /** * This trait needs to be added to any model defined by the api. */ diff --git a/samples/client/petstore/scala-sttp/src/test/scala/PetApiTest.scala b/samples/client/petstore/scala-sttp/src/test/scala/PetApiTest.scala new file mode 100644 index 000000000000..7c2eb09bf594 --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/test/scala/PetApiTest.scala @@ -0,0 +1,123 @@ +import org.json4s.DefaultFormats +import org.junit.runner.RunWith +import org.openapitools.client._ +import org.openapitools.client.api._ +import org.openapitools.client.core.{ApiInvoker, ApiKeyValue, SttpSerializer} +import org.openapitools.client.model._ +import org.scalatest.Inspectors._ +import org.scalatest._ +import org.scalatest.junit.JUnitRunner +import sttp.client.HttpURLConnectionBackend + +// +//implicit class ApiRequestImprovements[R[_], T](request: RequestT[Identity, Either[ResponseError[Exception], T], Nothing]) { +// +// // def response(invoker: ApiInvoker)(implicit ec: ExecutionContext): Future[ApiResponse[T]] = +// // response(ec, invoker) +// +// def result[U <: T]()(implicit backend: SttpBackend[R, ResponseError[Exception], Nothing]): R[U] = { +// val res = request.send() +// val ME: MonadError[R] = backend.responseMonad +// ME.flatMap(res) { +// resp => +// resp.body match { +// case Left(exc) => ME.error[U](exc) +// case Right(value) => ME.unit(value) +// } +// } +// } +// +//} + +@RunWith(classOf[JUnitRunner]) +class PetApiTest extends AsyncFlatSpec with Matchers { + + implicit val sttpSerializer = new SttpSerializer + implicit val backend = HttpURLConnectionBackend() + + val api = new PetApi("https://petstore3.swagger.io/api/v3") + + implicit val apiKey = ApiKeyValue("api-key") + + import ApiInvoker._ + + behavior of "PetApi" + + it should "add and fetch a pet" in { + val petId = 1000 + val createdPet = Pet( + Some(petId), + Some(Category(Some(1), Some("sold"))), + "dragon", + (for (i <- 1 to 10) yield "http://foo.com/photo/" + i).toList, + Some((for (i <- 1 to 5) yield org.openapitools.client.model.Tag(Some(i), Some("tag-" + i))).toList), + Some(PetEnums.Status.Sold) + ) + + val addPetRequest = api.addPet(createdPet) + val getPetRequest = api.getPetById(petId) + + backend + addPetRequest.result + val pet = getPetRequest.result + + pet should have( + 'id(createdPet.id), + 'status(createdPet.status), + 'category(createdPet.category), + 'name(createdPet.name) + ) + pet.tags should not be empty + pet.tags.get should contain theSameElementsInOrderAs createdPet.tags.get + pet.photoUrls should contain theSameElementsInOrderAs createdPet.photoUrls + } + + it should "update a pet" in { + val petId = (Math.random() * 1000000000).toLong + val createdPetObj = Pet( + Some(petId), + Some(Category(Some(1), Some("sold"))), + "programmer", + (for (i <- 1 to 10) yield "http://foo.com/photo/" + i).toList, + Some((for (i <- 1 to 5) yield org.openapitools.client.model.Tag(Some(i), Some("tag-" + i))).toList), + Some(PetEnums.Status.Available) + ) + + val createdPet = api.addPet(createdPetObj).result + val pet = api.getPetById(createdPet.id.get).result + val updatedPetObj = pet.copy(status = Some(PetEnums.Status.Sold), name = "developer") + val updatedPet = api.updatePet(updatedPetObj).result + val updatedRequested = api.getPetById(createdPet.id.get).result + + pet.name should be("programmer") + pet.status should be(Some(PetEnums.Status.Available)) + + updatedPet.name should be("developer") + updatedPet.status should be(Some(PetEnums.Status.Sold)) + + updatedRequested.name should be("developer") + updatedRequested.status should be(Some(PetEnums.Status.Sold)) + + } + + it should "find pets by status" in { + val pets = api.findPetsByStatus(List("available")).result + pets should not be empty + + + forAll(pets.toList) { pet => + pet.status should contain(PetEnums.Status.Available) + } + } + + it should "find pets by tag" in { + val pets = api.findPetsByTags(List("tag1", "tag2")).result + pets should not be empty + + forAll(pets.toList) { pet => + val tagNames = pet.tags.toList.flatten.map(_.name).collect { case Some(name) => name } + tagNames should contain atLeastOneOf("tag1", "tag2") + } + } + +} \ No newline at end of file From b20fdb17f7637f4ba5aad62a345b28c7e89f4da4 Mon Sep 17 00:00:00 2001 From: Aleksandr Nekrasov Date: Tue, 25 Feb 2020 17:39:00 +0700 Subject: [PATCH 3/9] clean up pet api test from redunant comments --- .../src/test/scala/PetApiTest.scala | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/samples/client/petstore/scala-sttp/src/test/scala/PetApiTest.scala b/samples/client/petstore/scala-sttp/src/test/scala/PetApiTest.scala index 7c2eb09bf594..f81b816f582c 100644 --- a/samples/client/petstore/scala-sttp/src/test/scala/PetApiTest.scala +++ b/samples/client/petstore/scala-sttp/src/test/scala/PetApiTest.scala @@ -1,6 +1,4 @@ -import org.json4s.DefaultFormats import org.junit.runner.RunWith -import org.openapitools.client._ import org.openapitools.client.api._ import org.openapitools.client.core.{ApiInvoker, ApiKeyValue, SttpSerializer} import org.openapitools.client.model._ @@ -9,26 +7,6 @@ import org.scalatest._ import org.scalatest.junit.JUnitRunner import sttp.client.HttpURLConnectionBackend -// -//implicit class ApiRequestImprovements[R[_], T](request: RequestT[Identity, Either[ResponseError[Exception], T], Nothing]) { -// -// // def response(invoker: ApiInvoker)(implicit ec: ExecutionContext): Future[ApiResponse[T]] = -// // response(ec, invoker) -// -// def result[U <: T]()(implicit backend: SttpBackend[R, ResponseError[Exception], Nothing]): R[U] = { -// val res = request.send() -// val ME: MonadError[R] = backend.responseMonad -// ME.flatMap(res) { -// resp => -// resp.body match { -// case Left(exc) => ME.error[U](exc) -// case Right(value) => ME.unit(value) -// } -// } -// } -// -//} - @RunWith(classOf[JUnitRunner]) class PetApiTest extends AsyncFlatSpec with Matchers { From 262d0890094d600ed4a37f12a9745fdd87f4633b Mon Sep 17 00:00:00 2001 From: Aleksandr Nekrasov Date: Wed, 26 Feb 2020 12:00:34 +0700 Subject: [PATCH 4/9] docs updated --- docs/generators/scala-sttp.md | 216 ++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 docs/generators/scala-sttp.md diff --git a/docs/generators/scala-sttp.md b/docs/generators/scala-sttp.md new file mode 100644 index 000000000000..bcf665f875b7 --- /dev/null +++ b/docs/generators/scala-sttp.md @@ -0,0 +1,216 @@ +--- +title: Config Options for scala-sttp +sidebar_label: scala-sttp +--- + +| Option | Description | Values | Default | +| ------ | ----------- | ------ | ------- | +|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false| +|apiPackage|package for generated api classes| |null| +|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true| +|mainPackage|Top-level package name, which defines 'apiPackage', 'modelPackage', 'invokerPackage'| |org.openapitools.client| +|modelPackage|package for generated models| |null| +|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false| +|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true| +|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| +|sourceFolder|source folder for generated code| |null| + +## IMPORT MAPPING + +| Type/Alias | Imports | +| ---------- | ------- | +|Array|java.util.List| +|ArrayList|java.util.ArrayList| +|BigDecimal|java.math.BigDecimal| +|Date|java.util.Date| +|DateTime|org.joda.time.DateTime| +|File|java.io.File| +|HashMap|java.util.HashMap| +|ListBuffer|scala.collection.mutable.ListBuffer| +|ListSet|scala.collection.immutable.ListSet| +|LocalDate|org.joda.time.*| +|LocalDateTime|org.joda.time.*| +|LocalTime|org.joda.time.*| +|Timestamp|java.sql.Timestamp| +|URI|java.net.URI| +|UUID|java.util.UUID| + + +## INSTANTIATION TYPES + +| Type/Alias | Instantiated By | +| ---------- | --------------- | +|array|ListBuffer| +|map|Map| +|set|Set| + + +## LANGUAGE PRIMITIVES + +
    +
  • Any
  • +
  • Array
  • +
  • Boolean
  • +
  • Double
  • +
  • Float
  • +
  • Int
  • +
  • List
  • +
  • Long
  • +
  • Map
  • +
  • Object
  • +
  • Seq
  • +
  • String
  • +
  • boolean
  • +
+ +## RESERVED WORDS + +
    +
  • abstract
  • +
  • case
  • +
  • catch
  • +
  • class
  • +
  • def
  • +
  • do
  • +
  • else
  • +
  • extends
  • +
  • false
  • +
  • final
  • +
  • finally
  • +
  • for
  • +
  • forsome
  • +
  • if
  • +
  • implicit
  • +
  • import
  • +
  • lazy
  • +
  • match
  • +
  • new
  • +
  • null
  • +
  • object
  • +
  • override
  • +
  • package
  • +
  • private
  • +
  • protected
  • +
  • return
  • +
  • sealed
  • +
  • super
  • +
  • this
  • +
  • throw
  • +
  • trait
  • +
  • true
  • +
  • try
  • +
  • type
  • +
  • val
  • +
  • var
  • +
  • while
  • +
  • with
  • +
  • yield
  • +
+ +## FEATURE SET + + +### Client Modification Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|BasePath|✓|ToolingExtension +|Authorizations|✗|ToolingExtension +|UserAgent|✓|ToolingExtension + +### Data Type Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Custom|✗|OAS2,OAS3 +|Int32|✓|OAS2,OAS3 +|Int64|✓|OAS2,OAS3 +|Float|✓|OAS2,OAS3 +|Double|✓|OAS2,OAS3 +|Decimal|✓|ToolingExtension +|String|✓|OAS2,OAS3 +|Byte|✓|OAS2,OAS3 +|Binary|✓|OAS2,OAS3 +|Boolean|✓|OAS2,OAS3 +|Date|✓|OAS2,OAS3 +|DateTime|✓|OAS2,OAS3 +|Password|✓|OAS2,OAS3 +|File|✓|OAS2 +|Array|✓|OAS2,OAS3 +|Maps|✓|ToolingExtension +|CollectionFormat|✓|OAS2 +|CollectionFormatMulti|✓|OAS2 +|Enum|✓|OAS2,OAS3 +|ArrayOfEnum|✓|ToolingExtension +|ArrayOfModel|✓|ToolingExtension +|ArrayOfCollectionOfPrimitives|✓|ToolingExtension +|ArrayOfCollectionOfModel|✓|ToolingExtension +|ArrayOfCollectionOfEnum|✓|ToolingExtension +|MapOfEnum|✓|ToolingExtension +|MapOfModel|✓|ToolingExtension +|MapOfCollectionOfPrimitives|✓|ToolingExtension +|MapOfCollectionOfModel|✓|ToolingExtension +|MapOfCollectionOfEnum|✓|ToolingExtension + +### Documentation Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Readme|✓|ToolingExtension +|Model|✓|ToolingExtension +|Api|✓|ToolingExtension + +### Global Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Host|✓|OAS2,OAS3 +|BasePath|✓|OAS2,OAS3 +|Info|✓|OAS2,OAS3 +|Schemes|✗|OAS2,OAS3 +|PartialSchemes|✓|OAS2,OAS3 +|Consumes|✓|OAS2 +|Produces|✓|OAS2 +|ExternalDocumentation|✓|OAS2,OAS3 +|Examples|✓|OAS2,OAS3 +|XMLStructureDefinitions|✗|OAS2,OAS3 +|MultiServer|✗|OAS3 +|ParameterizedServer|✗|OAS3 +|ParameterStyling|✗|OAS3 +|Callbacks|✗|OAS3 +|LinkObjects|✗|OAS3 + +### Parameter Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Path|✓|OAS2,OAS3 +|Query|✓|OAS2,OAS3 +|Header|✓|OAS2,OAS3 +|Body|✓|OAS2 +|FormUnencoded|✓|OAS2 +|FormMultipart|✓|OAS2 +|Cookie|✗|OAS3 + +### Schema Support Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Simple|✓|OAS2,OAS3 +|Composite|✓|OAS2,OAS3 +|Polymorphism|✗|OAS2,OAS3 +|Union|✗|OAS3 + +### Security Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|BasicAuth|✓|OAS2,OAS3 +|ApiKey|✓|OAS2,OAS3 +|OpenIDConnect|✗|OAS3 +|BearerToken|✓|OAS3 +|OAuth2_Implicit|✗|OAS2,OAS3 +|OAuth2_Password|✗|OAS2,OAS3 +|OAuth2_ClientCredentials|✗|OAS2,OAS3 +|OAuth2_AuthorizationCode|✗|OAS2,OAS3 + +### Wire Format Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|JSON|✓|OAS2,OAS3 +|XML|✓|OAS2,OAS3 +|PROTOBUF|✗|ToolingExtension +|Custom|✓|OAS2,OAS3 From f7c8b80c19ccb2721d0a1bf3a60e1dddc7eae4fb Mon Sep 17 00:00:00 2001 From: Aleksandr Nekrasov Date: Wed, 26 Feb 2020 16:43:00 +0700 Subject: [PATCH 5/9] fix artefact name, model comments and redunant generic --- bin/openapi3/scala-sttp-petstore.sh | 2 +- docs/generators.md | 1 + .../scala-sttp-client/apiInvoker.mustache | 5 +- .../scala-sttp-client/model.mustache | 9 + .../scala-sttp-client/requests.mustache | 48 ---- samples/client/petstore/scala-sttp/README.md | 8 +- samples/client/petstore/scala-sttp/build.sbt | 2 +- samples/client/petstore/scala-sttp/pom.xml | 259 ------------------ .../openapitools/client/core/ApiInvoker.scala | 8 +- .../client/model/ApiResponse.scala | 5 + .../openapitools/client/model/Category.scala | 5 + .../org/openapitools/client/model/Order.scala | 5 + .../org/openapitools/client/model/Pet.scala | 5 + .../org/openapitools/client/model/Tag.scala | 5 + .../org/openapitools/client/model/User.scala | 5 + 15 files changed, 51 insertions(+), 321 deletions(-) delete mode 100644 samples/client/petstore/scala-sttp/pom.xml diff --git a/bin/openapi3/scala-sttp-petstore.sh b/bin/openapi3/scala-sttp-petstore.sh index 94bb4192804b..2a9753df2956 100644 --- a/bin/openapi3/scala-sttp-petstore.sh +++ b/bin/openapi3/scala-sttp-petstore.sh @@ -27,6 +27,6 @@ fi # if you've executed sbt assembly previously it will use that instead. export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties" -ags="generate --artifact-id "scala-akka-petstore-client" -t modules/openapi-generator/src/main/resources/scala-sttp-client -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -g scala-sttp -o samples/client/petstore/scala-sttp $@" +ags="generate --artifact-id "scala-sttp-petstore-client" -t modules/openapi-generator/src/main/resources/scala-sttp-client -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -g scala-sttp -o samples/client/petstore/scala-sttp $@" java $JAVA_OPTS -jar $executable $ags diff --git a/docs/generators.md b/docs/generators.md index c8dd81b0cc7c..ab157a3e4607 100644 --- a/docs/generators.md +++ b/docs/generators.md @@ -54,6 +54,7 @@ The following generators are available: * [scala-akka](generators/scala-akka.md) * [scala-gatling](generators/scala-gatling.md) * [scala-httpclient-deprecated (deprecated)](generators/scala-httpclient-deprecated.md) +* [scala-sttp](generators/scala-sttp.md) * [scalaz](generators/scalaz.md) * [swift2-deprecated (deprecated)](generators/swift2-deprecated.md) * [swift3-deprecated (deprecated)](generators/swift3-deprecated.md) diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache index ce1bf6a0fa25..e5b75a591ebb 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache @@ -26,10 +26,9 @@ object ApiInvoker { * * @param request the apiRequest to be executed */ - implicit class ApiRequestImprovements[R[_], RE, T](request: RequestT[Identity, Either[ResponseError[Exception], T], Nothing]) - (implicit backend: SttpBackend[R, Nothing, Nothing]) { + implicit class ApiRequestImprovements[R[_], T](request: RequestT[Identity, Either[ResponseError[Exception], T], Nothing]) { - def result: R[T] = { + def result: R[T](implicit backend: SttpBackend[R, Nothing, Nothing]) = { val responseT = request.send() val ME: MonadError[R] = backend.responseMonad ME.flatMap(responseT) { diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/model.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/model.mustache index d5e9b9d3876b..c5a991d58eb3 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/model.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/model.mustache @@ -8,6 +8,15 @@ import {{mainPackage}}.core.ApiModel {{#models}} {{#model}} +{{#description}} +{{#javadocRenderer}} +{{#title}} +{{{title}}} + +{{/title}} +{{{description}}} +{{/javadocRenderer}} +{{/description}} case class {{classname}} ( {{#vars}} {{#description}} diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/requests.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/requests.mustache index 271a1a3e67ff..3886f149b7d5 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/requests.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/requests.mustache @@ -6,54 +6,6 @@ import java.net.URLEncoder import scala.util.Try -sealed trait ApiReturnWithHeaders { - def headers: Map[String, String] - - def header(name: String): Option[String] = headers.get(name) - - def getStringHeader(name: String): Option[String] = header(name) - - // workaround: return date time header in string instead of datetime object - def getDateTimeHeader(name: String): Option[String] = header(name) - - def getIntHeader(name: String): Option[Int] = castedHeader(name, java.lang.Integer.parseInt) - - def getLongHeader(name: String): Option[Long] = castedHeader(name, java.lang.Long.parseLong) - - def getFloatHeader(name: String): Option[Float] = castedHeader(name, java.lang.Float.parseFloat) - - def getDoubleHeader(name: String): Option[Double] = castedHeader(name, java.lang.Double.parseDouble) - - def getBooleanHeader(name: String): Option[Boolean] = castedHeader(name, java.lang.Boolean.parseBoolean) - - private def castedHeader[U](name: String, conversion: String => U): Option[U] = { - Try { - header(name).map(conversion) - }.get - } -} - -sealed case class ApiResponse[T](code: Int, content: T, headers: Map[String, String] = Map.empty) - extends ApiReturnWithHeaders - -sealed case class ApiError[T](code: Int, message: String, responseContent: Option[T], cause: Throwable = null, headers: Map[String, String] = Map.empty) - extends Throwable(s"($code) $message.${responseContent.map(s => s" Content : $s").getOrElse("")}", cause) - with ApiReturnWithHeaders - -sealed case class ApiMethod(value: String) - -object ApiMethods { - val CONNECT = ApiMethod("CONNECT") - val DELETE = ApiMethod("DELETE") - val GET = ApiMethod("GET") - val HEAD = ApiMethod("HEAD") - val OPTIONS = ApiMethod("OPTIONS") - val PATCH = ApiMethod("PATCH") - val POST = ApiMethod("POST") - val PUT = ApiMethod("PUT") - val TRACE = ApiMethod("TRACE") -} - /** * This trait needs to be added to any model defined by the api. */ diff --git a/samples/client/petstore/scala-sttp/README.md b/samples/client/petstore/scala-sttp/README.md index 15c1a0ca9ef3..b3d417a26fe5 100644 --- a/samples/client/petstore/scala-sttp/README.md +++ b/samples/client/petstore/scala-sttp/README.md @@ -1,4 +1,4 @@ -# scala-akka-petstore-client +# scala-sttp-petstore-client OpenAPI Petstore - API version: 1.0.0 @@ -37,7 +37,7 @@ Add this dependency to your project's POM: ```xml org.openapitools - scala-akka-petstore-client + scala-sttp-petstore-client 1.0.0 compile @@ -48,13 +48,13 @@ Add this dependency to your project's POM: Add this dependency to your project's build file: ```groovy -compile "org.openapitools:scala-akka-petstore-client:1.0.0" +compile "org.openapitools:scala-sttp-petstore-client:1.0.0" ``` ### SBT users ```scala -libraryDependencies += "org.openapitools" % "scala-akka-petstore-client" % "1.0.0" +libraryDependencies += "org.openapitools" % "scala-sttp-petstore-client" % "1.0.0" ``` ## Getting Started diff --git a/samples/client/petstore/scala-sttp/build.sbt b/samples/client/petstore/scala-sttp/build.sbt index ec019b0dc375..96a54a0748e3 100644 --- a/samples/client/petstore/scala-sttp/build.sbt +++ b/samples/client/petstore/scala-sttp/build.sbt @@ -1,5 +1,5 @@ version := "1.0.0" -name := "scala-akka-petstore-client" +name := "scala-sttp-petstore-client" organization := "org.openapitools" scalaVersion := "2.12.8" diff --git a/samples/client/petstore/scala-sttp/pom.xml b/samples/client/petstore/scala-sttp/pom.xml deleted file mode 100644 index 33f89264546a..000000000000 --- a/samples/client/petstore/scala-sttp/pom.xml +++ /dev/null @@ -1,259 +0,0 @@ - - 4.0.0 - - scala-akka-petstore-client - - org.openapitools - scala-akka-petstore-client - 1.0.0 - - jar - - - UTF-8 - UTF-8 - - 1.8 - 2.12.8 - 3.5.3 - 3.2.11 - 2.5.21 - 10.1.7 - 2.10.1 - 1.3.3 - 1.25.2 - 4.13 - 3.0.5 - - 3.3.1 - - - - - org.scala-lang - scala-library - ${scala.version} - provided - - - joda-time - joda-time - ${joda.time.version} - - - com.typesafe - config - ${typesafeconfig.version} - - - com.typesafe.akka - akka-actor_2.12 - ${akka.version} - - - com.typesafe.akka - akka-stream_2.12 - ${akka.version} - - - com.typesafe.akka - akka-http_2.12 - ${akka.http.version} - - - org.json4s - json4s-jackson_2.12 - ${json4s.jackson.version} - - - org.json4s - json4s-ext_2.12 - ${json4s.jackson.version} - - - de.heikoseeberger - akka-http-json4s_2.12 - ${akka.http.json4s.version} - - - - - org.scalatest - scalatest_2.12 - ${scala.test.version} - test - - - junit - junit - ${junit.version} - test - - - - - - - org.apache.maven.plugins - maven-deploy-plugin - 2.8.2 - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.0.0 - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - org.apache.maven.plugins - maven-enforcer-plugin - 3.0.0-M1 - - - enforce-maven - - enforce - - - - - 2.2.0 - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.20.1 - - - - loggerPath - conf/log4j.properties - - - -Xms512m -Xmx1500m - methods - pertest - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.0.2 - - - package - - copy-dependencies - - - ${project.build.directory}/lib - - - - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.6 - - - - jar - test-jar - - - - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.0.0 - - - add_sources - generate-sources - - add-source - - - - - src/main/java - - - - - - add_test_sources - generate-test-sources - - add-test-source - - - - - src/test/java - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.7.0 - - ${java.version} - ${java.version} - - - - net.alchim31.maven - scala-maven-plugin - ${scala.maven.plugin.version} - - - scala-compile-first - process-resources - - add-source - compile - - - - scala-test-compile - process-test-resources - - testCompile - - - - - - -feature - - - -Xms128m - -Xmx1500m - - - - - - \ No newline at end of file diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala index 2d8285f1816b..b3665b340750 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala @@ -17,9 +17,9 @@ import org.joda.time.DateTime import org.joda.time.format.ISODateTimeFormat import org.json4s.JsonAST.JString import org.json4s._ +import sttp.client._ import org.openapitools.client.api.EnumsSerializers import org.openapitools.client.core.ApiInvoker.DateTimeSerializer -import sttp.client._ import sttp.client.json4s.SttpJson4sApi import sttp.client.monad.MonadError @@ -30,17 +30,15 @@ class HttpException(val statusCode: Int, val statusText: String, val message: St object ApiInvoker { - /** * Allows request execution without calling apiInvoker.execute(request) * request.result can be used to get a monad wrapped content. * * @param request the apiRequest to be executed */ - implicit class ApiRequestImprovements[R[_], RE, T](request: RequestT[Identity, Either[ResponseError[Exception], T], Nothing]) - (implicit backend: SttpBackend[R, Nothing, Nothing]) { + implicit class ApiRequestImprovements[R[_], T](request: RequestT[Identity, Either[ResponseError[Exception], T], Nothing]) { - def result: R[T] = { + def result: R[T](implicit backend: SttpBackend[R, Nothing, Nothing]) = { val responseT = request.send() val ME: MonadError[R] = backend.responseMonad ME.flatMap(responseT) { diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/ApiResponse.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/ApiResponse.scala index 3ddf32c9bda7..947a449a1de4 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/ApiResponse.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/ApiResponse.scala @@ -13,6 +13,11 @@ package org.openapitools.client.model import org.openapitools.client.core.ApiModel + /** + * An uploaded response + * + * Describes the result of uploading an image resource + */ case class ApiResponse ( code: Option[Int] = None, `type`: Option[String] = None, diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Category.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Category.scala index e62645a38b2a..1e3284207f7c 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Category.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Category.scala @@ -13,6 +13,11 @@ package org.openapitools.client.model import org.openapitools.client.core.ApiModel + /** + * Pet category + * + * A category for a pet + */ case class Category ( id: Option[Long] = None, name: Option[String] = None diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Order.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Order.scala index 6a9fdc141f08..e25fe060d4f9 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Order.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Order.scala @@ -14,6 +14,11 @@ package org.openapitools.client.model import org.joda.time.DateTime import org.openapitools.client.core.ApiModel + /** + * Pet Order + * + * An order for a pets from the pet store + */ case class Order ( id: Option[Long] = None, petId: Option[Long] = None, diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Pet.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Pet.scala index 3a78a7c3f183..dca755b339d2 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Pet.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Pet.scala @@ -13,6 +13,11 @@ package org.openapitools.client.model import org.openapitools.client.core.ApiModel + /** + * a Pet + * + * A pet for sale in the pet store + */ case class Pet ( id: Option[Long] = None, category: Option[Category] = None, diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Tag.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Tag.scala index ac0c7763720c..76d11034c631 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Tag.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Tag.scala @@ -13,6 +13,11 @@ package org.openapitools.client.model import org.openapitools.client.core.ApiModel + /** + * Pet Tag + * + * A tag for a pet + */ case class Tag ( id: Option[Long] = None, name: Option[String] = None diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/User.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/User.scala index aad2117b16d5..fe13ca8d7347 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/User.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/User.scala @@ -13,6 +13,11 @@ package org.openapitools.client.model import org.openapitools.client.core.ApiModel + /** + * a User + * + * A User who is purchasing from the pet store + */ case class User ( id: Option[Long] = None, username: Option[String] = None, From 897b077239dd8c15132a5b8c707faa1cb814da59 Mon Sep 17 00:00:00 2001 From: Aleksandr Nekrasov Date: Wed, 26 Feb 2020 19:52:05 +0700 Subject: [PATCH 6/9] code optimization --- .../resources/scala-sttp-client/api.mustache | 18 +-- .../scala-sttp-client/apiInvoker.mustache | 15 ++- .../scala-sttp-client/build.sbt.mustache | 8 +- .../scala-sttp-client/requests.mustache | 115 ++---------------- samples/client/petstore/scala-sttp/build.sbt | 8 +- .../src/main/resources/reference.conf | 24 ---- .../org/openapitools/client/api/PetApi.scala | 30 ++--- .../openapitools/client/api/StoreApi.scala | 22 ++-- .../org/openapitools/client/api/UserApi.scala | 35 +++--- .../openapitools/client/core/ApiInvoker.scala | 15 ++- .../openapitools/client/core/requests.scala | 115 ++---------------- .../src/test/scala/PetApiTest.scala | 10 +- 12 files changed, 89 insertions(+), 326 deletions(-) delete mode 100644 samples/client/petstore/scala-sttp/src/main/resources/reference.conf diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache index 897108313ee4..f856defc9231 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache @@ -5,10 +5,9 @@ package {{package}} import {{import}} {{/imports}} import {{mainPackage}}.core._ -import {{mainPackage}}.core.CollectionFormats._ +import alias._ import sttp.client._ import sttp.model.Method -import sttp.client.json4s.SttpJson4sApi {{#operations}} object {{classname}} { @@ -18,26 +17,19 @@ object {{classname}} { class {{classname}}(baseUrl: String)(implicit serializer: SttpSerializer) { + import Helpers._ import serializer._ - /* - * Helper to handle Optional header parameters - **/ - implicit class optionalParams(request: RequestT[Identity, Either[String, String], Nothing]) { - def header( header: String, optValue: Option[Any]): RequestT[Identity, Either[String, String], Nothing] = { - optValue.map( value => request.header(header, value.toString)).getOrElse(request) - } - } - {{#operation}} {{#javadocRenderer}} {{>javadoc}} {{/javadocRenderer}} - def {{operationId}}({{>methodParameters}}): RequestT[Identity, Either[ResponseError[Exception], {{>operationReturnType}}], Nothing] = + def {{operationId}}({{>methodParameters}}): ApiRequestT[{{>operationReturnType}}] = basicRequest .method(Method.{{httpMethod.toUpperCase}}, uri"$baseUrl{{{path}}}{{#queryParams.0}}?{{#queryParams}}{{baseName}}=${{{paramName}}}{{^-last}}&{{/-last}}{{/queryParams}}{{/queryParams.0}}") .contentType({{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}}{{^consumes}}"application/json"{{/consumes}}){{#headerParams}} - .header({{>paramCreation}}){{/headerParams}}{{#formParams.0}} + .header({{>paramCreation}}){{/headerParams}}{{#authMethods}}{{#isApiKey}} + {{#isKeyInQuery}}{{/isKeyInQuery}}{{#isKeyInHeader}}.header("{{keyParamName}}", apiKey.value){{/isKeyInHeader}}{{#isKeyInCookie}}.cookie("{{keyParamName}}", apiKey.value){{/isKeyInCookie}}{{/isApiKey}}{{/authMethods}}{{#formParams.0}} .body(Map({{#formParams}} {{>paramFormCreation}},{{/formParams}} )){{/formParams.0}}{{#bodyParam}} diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache index e5b75a591ebb..af8ce93125c4 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache @@ -1,8 +1,6 @@ {{>licenseInfo}} package {{{mainPackage}}}.core -import java.io.File - import org.joda.time.DateTime import org.joda.time.format.ISODateTimeFormat import org.json4s.JsonAST.JString @@ -18,6 +16,17 @@ class SttpSerializer(implicit val format: Formats = DefaultFormats ++ EnumsSeria class HttpException(val statusCode: Int, val statusText: String, val message: String) extends Exception(s"[$statusCode] $statusText: $message") +object Helpers { + + // Helper to handle Optional header parameters + implicit class optionalParams(val request: RequestT[Identity, Either[String, String], Nothing]) extends AnyVal { + def header( header: String, optValue: Option[Any]): RequestT[Identity, Either[String, String], Nothing] = { + optValue.map( value => request.header(header, value.toString)).getOrElse(request) + } + } + +} + object ApiInvoker { /** @@ -28,7 +37,7 @@ object ApiInvoker { */ implicit class ApiRequestImprovements[R[_], T](request: RequestT[Identity, Either[ResponseError[Exception], T], Nothing]) { - def result: R[T](implicit backend: SttpBackend[R, Nothing, Nothing]) = { + def result(implicit backend: SttpBackend[R, Nothing, Nothing]): R[T] = { val responseT = request.send() val ME: MonadError[R] = backend.responseMonad ME.flatMap(responseT) { diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache index f2de98b7ffa2..b0ef9395a32d 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache @@ -3,18 +3,16 @@ name := "{{artifactId}}" organization := "{{groupId}}" scalaVersion := "2.12.8" -val sttp = "2.0.0" - libraryDependencies ++= Seq( - "com.softwaremill.sttp.client" %% "core" % sttp, - "com.softwaremill.sttp.client" %% "json4s" % sttp, + "com.softwaremill.sttp.client" %% "core" % "2.0.0", + "com.softwaremill.sttp.client" %% "json4s" % "2.0.0", "joda-time" % "joda-time" % "2.10.1", "org.json4s" %% "json4s-jackson" % "3.6.5", "org.json4s" %% "json4s-ext" % "3.6.5", // test dependencies - "org.scalatest" %% "scalatest" % "3.0.5" % "test", + "org.scalatest" %% "scalatest" % "3.0.5" % Test, "junit" % "junit" % "4.13" % "test" ) diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/requests.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/requests.mustache index 3886f149b7d5..31158fd67cbf 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/requests.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/requests.mustache @@ -1,16 +1,20 @@ {{>licenseInfo}} package {{mainPackage}}.core -import java.io.File -import java.net.URLEncoder - -import scala.util.Try +import sttp.client.{Identity, RequestT, ResponseError} /** * This trait needs to be added to any model defined by the api. */ trait ApiModel +/** + * Sttp type aliases + */ +object alias { + type ApiRequestT[T] = RequestT[Identity, Either[ResponseError[Exception], T], Nothing] +} + /** * Single trait defining a credential that can be transformed to a paramName / paramValue tupple */ @@ -42,106 +46,3 @@ object ApiKeyLocations { case object COOKIE extends ApiKeyLocation } - - -/** - * Case class used to unapply numeric values only in pattern matching - * - * @param value the string representation of the numeric value - */ -sealed case class NumericValue(value: String) { - override def toString: String = value -} - -object NumericValue { - def unapply(n: Any): Option[NumericValue] = n match { - case (_: Int | _: Long | _: Float | _: Double | _: Boolean | _: Byte) => Some(NumericValue(String.valueOf(n))) - case _ => None - } -} - -/** - * Used for params being arrays - */ -sealed case class ArrayValues(values: Seq[Any], format: CollectionFormat = CollectionFormats.CSV) - -object ArrayValues { - def apply(values: Option[Seq[Any]], format: CollectionFormat): ArrayValues = - ArrayValues(values.getOrElse(Seq.empty), format) - - def apply(values: Option[Seq[Any]]): ArrayValues = ArrayValues(values, CollectionFormats.CSV) -} - -case class QueryValues(name: String, value: Any) { - override def toString = { - ParametersMap.ParametersMapImprovements( Map(name -> value)).asFormattedParams.mapValues(_.toString) - .foldRight[String]("") { - case ((name, value), acc) => (if(acc.isEmpty) "" else ",") + s"$name=$value" - } - } -} - -/** - * Defines how arrays should be rendered in query strings. - */ -sealed trait CollectionFormat - -trait MergedArrayFormat extends CollectionFormat { - def separator: String -} - -object CollectionFormats { - - case object CSV extends MergedArrayFormat { - override val separator = "," - } - - case object TSV extends MergedArrayFormat { - override val separator = "\t" - } - - case object SSV extends MergedArrayFormat { - override val separator = " " - } - - case object PIPES extends MergedArrayFormat { - override val separator = "|" - } - - case object MULTI extends CollectionFormat - -} - -object ParametersMap { - - /** - * Pimp parameters maps (Map[String, Any]) in order to transform them in a sequence of String -> Any tupples, - * with valid url-encoding, arrays handling, files preservation, ... - */ - implicit class ParametersMapImprovements(val m: Map[String, Any]) { - - def asFormattedParamsList: List[(String, Any)] = m.toList.flatMap(formattedParams) - - def asFormattedParams: Map[String, Any] = m.flatMap(formattedParams) - - private def urlEncode(v: Any) = URLEncoder.encode(String.valueOf(v), "utf-8").replaceAll("\\+", "%20") - - private def formattedParams(tuple: (String, Any)): Seq[(String, Any)] = formattedParams(tuple._1, tuple._2) - - private def formattedParams(name: String, value: Any): Seq[(String, Any)] = value match { - case arr: ArrayValues => - arr.format match { - case CollectionFormats.MULTI => arr.values.flatMap(formattedParams(name, _)) - case format: MergedArrayFormat => Seq((name, arr.values.mkString(format.separator))) - } - case None => Seq.empty - case Some(opt) => formattedParams(name, opt) - case s: Seq[Any] => formattedParams(name, ArrayValues(s)) - case v: String => Seq((name, urlEncode(v))) - case NumericValue(v) => Seq((name, urlEncode(v))) - case f: File => Seq((name, f)) - case m: ApiModel => Seq((name, m)) - } - } - -} diff --git a/samples/client/petstore/scala-sttp/build.sbt b/samples/client/petstore/scala-sttp/build.sbt index 96a54a0748e3..fab4e1b6047b 100644 --- a/samples/client/petstore/scala-sttp/build.sbt +++ b/samples/client/petstore/scala-sttp/build.sbt @@ -3,18 +3,16 @@ name := "scala-sttp-petstore-client" organization := "org.openapitools" scalaVersion := "2.12.8" -val sttp = "2.0.0" - libraryDependencies ++= Seq( - "com.softwaremill.sttp.client" %% "core" % sttp, - "com.softwaremill.sttp.client" %% "json4s" % sttp, + "com.softwaremill.sttp.client" %% "core" % "2.0.0", + "com.softwaremill.sttp.client" %% "json4s" % "2.0.0", "joda-time" % "joda-time" % "2.10.1", "org.json4s" %% "json4s-jackson" % "3.6.5", "org.json4s" %% "json4s-ext" % "3.6.5", // test dependencies - "org.scalatest" %% "scalatest" % "3.0.5" % "test", + "org.scalatest" %% "scalatest" % "3.0.5" % Test, "junit" % "junit" % "4.13" % "test" ) diff --git a/samples/client/petstore/scala-sttp/src/main/resources/reference.conf b/samples/client/petstore/scala-sttp/src/main/resources/reference.conf deleted file mode 100644 index 6d419f988eba..000000000000 --- a/samples/client/petstore/scala-sttp/src/main/resources/reference.conf +++ /dev/null @@ -1,24 +0,0 @@ -org.openapitools.client { - - apiRequest { - - compression { - enabled: false - size-threshold: 0 - } - - trust-certificates: true - - connection-timeout: 5000ms - - default-headers { - "userAgent": "scala-akka-petstore-client_1.0.0" - } - - // let you define custom http status code, as in : - // { code: 601, reason: "some custom http status code", success: false } - custom-codes : [] - } -} - -spray.can.host-connector.max-redirects = 10 \ No newline at end of file diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala index 42528a30ba5e..0e44b02e221b 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala @@ -15,10 +15,9 @@ import org.openapitools.client.model.ApiResponse import java.io.File import org.openapitools.client.model.Pet import org.openapitools.client.core._ -import org.openapitools.client.core.CollectionFormats._ +import alias._ import sttp.client._ import sttp.model.Method -import sttp.client.json4s.SttpJson4sApi object PetApi { @@ -27,17 +26,9 @@ object PetApi { class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { + import Helpers._ import serializer._ - /* - * Helper to handle Optional header parameters - **/ - implicit class optionalParams(request: RequestT[Identity, Either[String, String], Nothing]) { - def header( header: String, optValue: Option[Any]): RequestT[Identity, Either[String, String], Nothing] = { - optValue.map( value => request.header(header, value.toString)).getOrElse(request) - } - } - /** * Expected answers: * code 200 : Pet (successful operation) @@ -45,7 +36,7 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { * * @param pet Pet object that needs to be added to the store */ - def addPet(pet: Pet): RequestT[Identity, Either[ResponseError[Exception], Pet], Nothing] = + def addPet(pet: Pet): ApiRequestT[Pet] = basicRequest .method(Method.POST, uri"$baseUrl/pet") .contentType("application/json") @@ -61,7 +52,7 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { * @param petId Pet id to delete * @param apiKey */ - def deletePet(petId: Long, apiKey: Option[String] = None): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + def deletePet(petId: Long, apiKey: Option[String] = None): ApiRequestT[Unit] = basicRequest .method(Method.DELETE, uri"$baseUrl/pet/${petId}") .contentType("application/json") @@ -79,7 +70,7 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { * * @param status Status values that need to be considered for filter */ - def findPetsByStatus(status: Seq[String]): RequestT[Identity, Either[ResponseError[Exception], Seq[Pet]], Nothing] = + def findPetsByStatus(status: Seq[String]): ApiRequestT[Seq[Pet]] = basicRequest .method(Method.GET, uri"$baseUrl/pet/findByStatus?status=$status") .contentType("application/json") @@ -96,7 +87,7 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { * * @param tags Tags to filter by */ - def findPetsByTags(tags: Seq[String]): RequestT[Identity, Either[ResponseError[Exception], Seq[Pet]], Nothing] = + def findPetsByTags(tags: Seq[String]): ApiRequestT[Seq[Pet]] = basicRequest .method(Method.GET, uri"$baseUrl/pet/findByTags?tags=$tags") .contentType("application/json") @@ -117,10 +108,11 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { * * @param petId ID of pet to return */ - def getPetById(petId: Long)(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Pet], Nothing] = + def getPetById(petId: Long)(implicit apiKey: ApiKeyValue): ApiRequestT[Pet] = basicRequest .method(Method.GET, uri"$baseUrl/pet/${petId}") .contentType("application/json") + .header("api_key", apiKey.value) .response(asJson[Pet]) @@ -134,7 +126,7 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { * * @param pet Pet object that needs to be added to the store */ - def updatePet(pet: Pet): RequestT[Identity, Either[ResponseError[Exception], Pet], Nothing] = + def updatePet(pet: Pet): ApiRequestT[Pet] = basicRequest .method(Method.PUT, uri"$baseUrl/pet") .contentType("application/json") @@ -151,7 +143,7 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { * @param name Updated name of the pet * @param status Updated status of the pet */ - def updatePetWithForm(petId: Long, name: Option[String] = None, status: Option[String] = None): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + def updatePetWithForm(petId: Long, name: Option[String] = None, status: Option[String] = None): ApiRequestT[Unit] = basicRequest .method(Method.POST, uri"$baseUrl/pet/${petId}") .contentType("application/x-www-form-urlencoded") @@ -171,7 +163,7 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { * @param additionalMetadata Additional data to pass to server * @param file file to upload */ - def uploadFile(petId: Long, additionalMetadata: Option[String] = None, file: Option[File] = None): RequestT[Identity, Either[ResponseError[Exception], ApiResponse], Nothing] = + def uploadFile(petId: Long, additionalMetadata: Option[String] = None, file: Option[File] = None): ApiRequestT[ApiResponse] = basicRequest .method(Method.POST, uri"$baseUrl/pet/${petId}/uploadImage") .contentType("multipart/form-data") diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/StoreApi.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/StoreApi.scala index 58ff84323213..7a434e5b80d3 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/StoreApi.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/StoreApi.scala @@ -13,10 +13,9 @@ package org.openapitools.client.api import org.openapitools.client.model.Order import org.openapitools.client.core._ -import org.openapitools.client.core.CollectionFormats._ +import alias._ import sttp.client._ import sttp.model.Method -import sttp.client.json4s.SttpJson4sApi object StoreApi { @@ -25,17 +24,9 @@ object StoreApi { class StoreApi(baseUrl: String)(implicit serializer: SttpSerializer) { + import Helpers._ import serializer._ - /* - * Helper to handle Optional header parameters - **/ - implicit class optionalParams(request: RequestT[Identity, Either[String, String], Nothing]) { - def header( header: String, optValue: Option[Any]): RequestT[Identity, Either[String, String], Nothing] = { - optValue.map( value => request.header(header, value.toString)).getOrElse(request) - } - } - /** * For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors * @@ -45,7 +36,7 @@ class StoreApi(baseUrl: String)(implicit serializer: SttpSerializer) { * * @param orderId ID of the order that needs to be deleted */ - def deleteOrder(orderId: String): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + def deleteOrder(orderId: String): ApiRequestT[Unit] = basicRequest .method(Method.DELETE, uri"$baseUrl/store/order/${orderId}") .contentType("application/json") @@ -62,10 +53,11 @@ class StoreApi(baseUrl: String)(implicit serializer: SttpSerializer) { * Available security schemes: * api_key (apiKey) */ - def getInventory()(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Map[String, Int]], Nothing] = + def getInventory()(implicit apiKey: ApiKeyValue): ApiRequestT[Map[String, Int]] = basicRequest .method(Method.GET, uri"$baseUrl/store/inventory") .contentType("application/json") + .header("api_key", apiKey.value) .response(asJson[Map[String, Int]]) @@ -80,7 +72,7 @@ class StoreApi(baseUrl: String)(implicit serializer: SttpSerializer) { * * @param orderId ID of pet that needs to be fetched */ - def getOrderById(orderId: Long): RequestT[Identity, Either[ResponseError[Exception], Order], Nothing] = + def getOrderById(orderId: Long): ApiRequestT[Order] = basicRequest .method(Method.GET, uri"$baseUrl/store/order/${orderId}") .contentType("application/json") @@ -95,7 +87,7 @@ class StoreApi(baseUrl: String)(implicit serializer: SttpSerializer) { * * @param order order placed for purchasing the pet */ - def placeOrder(order: Order): RequestT[Identity, Either[ResponseError[Exception], Order], Nothing] = + def placeOrder(order: Order): ApiRequestT[Order] = basicRequest .method(Method.POST, uri"$baseUrl/store/order") .contentType("application/json") diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/UserApi.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/UserApi.scala index f9b8e35407a7..046713880703 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/UserApi.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/UserApi.scala @@ -13,10 +13,9 @@ package org.openapitools.client.api import org.openapitools.client.model.User import org.openapitools.client.core._ -import org.openapitools.client.core.CollectionFormats._ +import alias._ import sttp.client._ import sttp.model.Method -import sttp.client.json4s.SttpJson4sApi object UserApi { @@ -25,17 +24,9 @@ object UserApi { class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { + import Helpers._ import serializer._ - /* - * Helper to handle Optional header parameters - **/ - implicit class optionalParams(request: RequestT[Identity, Either[String, String], Nothing]) { - def header( header: String, optValue: Option[Any]): RequestT[Identity, Either[String, String], Nothing] = { - optValue.map( value => request.header(header, value.toString)).getOrElse(request) - } - } - /** * This can only be done by the logged in user. * @@ -47,10 +38,11 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { * * @param user Created user object */ - def createUser(user: User)(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + def createUser(user: User)(implicit apiKey: ApiKeyValue): ApiRequestT[Unit] = basicRequest .method(Method.POST, uri"$baseUrl/user") .contentType("application/json") + .cookie("AUTH_KEY", apiKey.value) .body(user) .response(asJson[Unit]) @@ -65,10 +57,11 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { * * @param user List of user object */ - def createUsersWithArrayInput(user: Seq[User])(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + def createUsersWithArrayInput(user: Seq[User])(implicit apiKey: ApiKeyValue): ApiRequestT[Unit] = basicRequest .method(Method.POST, uri"$baseUrl/user/createWithArray") .contentType("application/json") + .cookie("AUTH_KEY", apiKey.value) .body(user) .response(asJson[Unit]) @@ -83,10 +76,11 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { * * @param user List of user object */ - def createUsersWithListInput(user: Seq[User])(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + def createUsersWithListInput(user: Seq[User])(implicit apiKey: ApiKeyValue): ApiRequestT[Unit] = basicRequest .method(Method.POST, uri"$baseUrl/user/createWithList") .contentType("application/json") + .cookie("AUTH_KEY", apiKey.value) .body(user) .response(asJson[Unit]) @@ -104,10 +98,11 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { * * @param username The name that needs to be deleted */ - def deleteUser(username: String)(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + def deleteUser(username: String)(implicit apiKey: ApiKeyValue): ApiRequestT[Unit] = basicRequest .method(Method.DELETE, uri"$baseUrl/user/${username}") .contentType("application/json") + .cookie("AUTH_KEY", apiKey.value) .response(asJson[Unit]) @@ -120,7 +115,7 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { * * @param username The name that needs to be fetched. Use user1 for testing. */ - def getUserByName(username: String): RequestT[Identity, Either[ResponseError[Exception], User], Nothing] = + def getUserByName(username: String): ApiRequestT[User] = basicRequest .method(Method.GET, uri"$baseUrl/user/${username}") .contentType("application/json") @@ -140,7 +135,7 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { * @param username The user name for login * @param password The password for login in clear text */ - def loginUser(username: String, password: String): RequestT[Identity, Either[ResponseError[Exception], String], Nothing] = + def loginUser(username: String, password: String): ApiRequestT[String] = basicRequest .method(Method.GET, uri"$baseUrl/user/login?username=$username&password=$password") .contentType("application/json") @@ -155,10 +150,11 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { * Available security schemes: * auth_cookie (apiKey) */ - def logoutUser()(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + def logoutUser()(implicit apiKey: ApiKeyValue): ApiRequestT[Unit] = basicRequest .method(Method.GET, uri"$baseUrl/user/logout") .contentType("application/json") + .cookie("AUTH_KEY", apiKey.value) .response(asJson[Unit]) @@ -176,10 +172,11 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { * @param username name that need to be deleted * @param user Updated user object */ - def updateUser(username: String, user: User)(implicit apiKey: ApiKeyValue): RequestT[Identity, Either[ResponseError[Exception], Unit], Nothing] = + def updateUser(username: String, user: User)(implicit apiKey: ApiKeyValue): ApiRequestT[Unit] = basicRequest .method(Method.PUT, uri"$baseUrl/user/${username}") .contentType("application/json") + .cookie("AUTH_KEY", apiKey.value) .body(user) .response(asJson[Unit]) diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala index b3665b340750..2263eab5e866 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala @@ -11,8 +11,6 @@ */ package org.openapitools.client.core -import java.io.File - import org.joda.time.DateTime import org.joda.time.format.ISODateTimeFormat import org.json4s.JsonAST.JString @@ -28,6 +26,17 @@ class SttpSerializer(implicit val format: Formats = DefaultFormats ++ EnumsSeria class HttpException(val statusCode: Int, val statusText: String, val message: String) extends Exception(s"[$statusCode] $statusText: $message") +object Helpers { + + // Helper to handle Optional header parameters + implicit class optionalParams(val request: RequestT[Identity, Either[String, String], Nothing]) extends AnyVal { + def header( header: String, optValue: Option[Any]): RequestT[Identity, Either[String, String], Nothing] = { + optValue.map( value => request.header(header, value.toString)).getOrElse(request) + } + } + +} + object ApiInvoker { /** @@ -38,7 +47,7 @@ object ApiInvoker { */ implicit class ApiRequestImprovements[R[_], T](request: RequestT[Identity, Either[ResponseError[Exception], T], Nothing]) { - def result: R[T](implicit backend: SttpBackend[R, Nothing, Nothing]) = { + def result(implicit backend: SttpBackend[R, Nothing, Nothing]): R[T] = { val responseT = request.send() val ME: MonadError[R] = backend.responseMonad ME.flatMap(responseT) { diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/requests.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/requests.scala index bb54c3f088a8..1f45be8103e1 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/requests.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/requests.scala @@ -11,16 +11,20 @@ */ package org.openapitools.client.core -import java.io.File -import java.net.URLEncoder - -import scala.util.Try +import sttp.client.{Identity, RequestT, ResponseError} /** * This trait needs to be added to any model defined by the api. */ trait ApiModel +/** + * Sttp type aliases + */ +object alias { + type ApiRequestT[T] = RequestT[Identity, Either[ResponseError[Exception], T], Nothing] +} + /** * Single trait defining a credential that can be transformed to a paramName / paramValue tupple */ @@ -52,106 +56,3 @@ object ApiKeyLocations { case object COOKIE extends ApiKeyLocation } - - -/** - * Case class used to unapply numeric values only in pattern matching - * - * @param value the string representation of the numeric value - */ -sealed case class NumericValue(value: String) { - override def toString: String = value -} - -object NumericValue { - def unapply(n: Any): Option[NumericValue] = n match { - case (_: Int | _: Long | _: Float | _: Double | _: Boolean | _: Byte) => Some(NumericValue(String.valueOf(n))) - case _ => None - } -} - -/** - * Used for params being arrays - */ -sealed case class ArrayValues(values: Seq[Any], format: CollectionFormat = CollectionFormats.CSV) - -object ArrayValues { - def apply(values: Option[Seq[Any]], format: CollectionFormat): ArrayValues = - ArrayValues(values.getOrElse(Seq.empty), format) - - def apply(values: Option[Seq[Any]]): ArrayValues = ArrayValues(values, CollectionFormats.CSV) -} - -case class QueryValues(name: String, value: Any) { - override def toString = { - ParametersMap.ParametersMapImprovements( Map(name -> value)).asFormattedParams.mapValues(_.toString) - .foldRight[String]("") { - case ((name, value), acc) => (if(acc.isEmpty) "" else ",") + s"$name=$value" - } - } -} - -/** - * Defines how arrays should be rendered in query strings. - */ -sealed trait CollectionFormat - -trait MergedArrayFormat extends CollectionFormat { - def separator: String -} - -object CollectionFormats { - - case object CSV extends MergedArrayFormat { - override val separator = "," - } - - case object TSV extends MergedArrayFormat { - override val separator = "\t" - } - - case object SSV extends MergedArrayFormat { - override val separator = " " - } - - case object PIPES extends MergedArrayFormat { - override val separator = "|" - } - - case object MULTI extends CollectionFormat - -} - -object ParametersMap { - - /** - * Pimp parameters maps (Map[String, Any]) in order to transform them in a sequence of String -> Any tupples, - * with valid url-encoding, arrays handling, files preservation, ... - */ - implicit class ParametersMapImprovements(val m: Map[String, Any]) { - - def asFormattedParamsList: List[(String, Any)] = m.toList.flatMap(formattedParams) - - def asFormattedParams: Map[String, Any] = m.flatMap(formattedParams) - - private def urlEncode(v: Any) = URLEncoder.encode(String.valueOf(v), "utf-8").replaceAll("\\+", "%20") - - private def formattedParams(tuple: (String, Any)): Seq[(String, Any)] = formattedParams(tuple._1, tuple._2) - - private def formattedParams(name: String, value: Any): Seq[(String, Any)] = value match { - case arr: ArrayValues => - arr.format match { - case CollectionFormats.MULTI => arr.values.flatMap(formattedParams(name, _)) - case format: MergedArrayFormat => Seq((name, arr.values.mkString(format.separator))) - } - case None => Seq.empty - case Some(opt) => formattedParams(name, opt) - case s: Seq[Any] => formattedParams(name, ArrayValues(s)) - case v: String => Seq((name, urlEncode(v))) - case NumericValue(v) => Seq((name, urlEncode(v))) - case f: File => Seq((name, f)) - case m: ApiModel => Seq((name, m)) - } - } - -} diff --git a/samples/client/petstore/scala-sttp/src/test/scala/PetApiTest.scala b/samples/client/petstore/scala-sttp/src/test/scala/PetApiTest.scala index f81b816f582c..f03ae223b51a 100644 --- a/samples/client/petstore/scala-sttp/src/test/scala/PetApiTest.scala +++ b/samples/client/petstore/scala-sttp/src/test/scala/PetApiTest.scala @@ -5,17 +5,16 @@ import org.openapitools.client.model._ import org.scalatest.Inspectors._ import org.scalatest._ import org.scalatest.junit.JUnitRunner -import sttp.client.HttpURLConnectionBackend +import sttp.client.{HttpURLConnectionBackend, Identity, NothingT, SttpBackend} @RunWith(classOf[JUnitRunner]) class PetApiTest extends AsyncFlatSpec with Matchers { - implicit val sttpSerializer = new SttpSerializer - implicit val backend = HttpURLConnectionBackend() - + implicit val sttpSerializer: SttpSerializer = new SttpSerializer + implicit val backend: SttpBackend[Identity, Nothing, NothingT] = HttpURLConnectionBackend() val api = new PetApi("https://petstore3.swagger.io/api/v3") - implicit val apiKey = ApiKeyValue("api-key") + implicit val apiKey: ApiKeyValue = ApiKeyValue("api-key") import ApiInvoker._ @@ -35,7 +34,6 @@ class PetApiTest extends AsyncFlatSpec with Matchers { val addPetRequest = api.addPet(createdPet) val getPetRequest = api.getPetById(petId) - backend addPetRequest.result val pet = getPetRequest.result From 1cab8e99659e46db72ec7b0d13d4b685815d3fee Mon Sep 17 00:00:00 2001 From: Aleksandr Nekrasov Date: Wed, 26 Feb 2020 20:07:47 +0700 Subject: [PATCH 7/9] cross scala versions 2.11 2.12 2.13 --- .../resources/scala-sttp-client/build.sbt.mustache | 11 +++++++---- samples/client/petstore/scala-sttp/build.sbt | 10 ++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache index b0ef9395a32d..9a1b29b67412 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache @@ -1,18 +1,21 @@ version := "{{artifactVersion}}" name := "{{artifactId}}" organization := "{{groupId}}" -scalaVersion := "2.12.8" + +scalaVersion := "2.13.0" + +crossScalaVersions := Seq(scalaVersion.value, "2.12.10", "2.11.12") libraryDependencies ++= Seq( "com.softwaremill.sttp.client" %% "core" % "2.0.0", "com.softwaremill.sttp.client" %% "json4s" % "2.0.0", "joda-time" % "joda-time" % "2.10.1", - "org.json4s" %% "json4s-jackson" % "3.6.5", - "org.json4s" %% "json4s-ext" % "3.6.5", + "org.json4s" %% "json4s-jackson" % "3.6.7", + "org.json4s" %% "json4s-ext" % "3.6.7", // test dependencies - "org.scalatest" %% "scalatest" % "3.0.5" % Test, + "org.scalatest" %% "scalatest" % "3.1.1" % Test, "junit" % "junit" % "4.13" % "test" ) diff --git a/samples/client/petstore/scala-sttp/build.sbt b/samples/client/petstore/scala-sttp/build.sbt index fab4e1b6047b..90d67f9dcaaa 100644 --- a/samples/client/petstore/scala-sttp/build.sbt +++ b/samples/client/petstore/scala-sttp/build.sbt @@ -1,18 +1,20 @@ version := "1.0.0" name := "scala-sttp-petstore-client" organization := "org.openapitools" -scalaVersion := "2.12.8" +scalaVersion := "2.13.0" + +crossScalaVersions := Seq(scalaVersion.value, "2.12.10", "2.11.12") libraryDependencies ++= Seq( "com.softwaremill.sttp.client" %% "core" % "2.0.0", "com.softwaremill.sttp.client" %% "json4s" % "2.0.0", "joda-time" % "joda-time" % "2.10.1", - "org.json4s" %% "json4s-jackson" % "3.6.5", - "org.json4s" %% "json4s-ext" % "3.6.5", + "org.json4s" %% "json4s-jackson" % "3.6.7", + "org.json4s" %% "json4s-ext" % "3.6.7", // test dependencies - "org.scalatest" %% "scalatest" % "3.0.5" % Test, + "org.scalatest" %% "scalatest" % "3.1.1" % Test, "junit" % "junit" % "4.13" % "test" ) From 66903ef5182b60ffe7e37ba92b20df226b997bf5 Mon Sep 17 00:00:00 2001 From: Aleksandr Nekrasov Date: Thu, 27 Feb 2020 18:25:24 +0700 Subject: [PATCH 8/9] date serializers extracted and joda enabled as default --- .../languages/ScalaSttpClientCodegen.java | 5 ++ .../resources/scala-sttp-client/api.mustache | 15 ----- .../scala-sttp-client/apiInvoker.mustache | 20 ++----- .../scala-sttp-client/build.sbt.mustache | 9 +-- .../scala-sttp-client/model.mustache | 3 +- .../scala-sttp-client/serializers.mustache | 57 +++++++++++++++++++ samples/client/petstore/scala-sttp/build.sbt | 8 +-- .../org/openapitools/client/api/PetApi.scala | 17 ------ .../openapitools/client/api/StoreApi.scala | 9 --- .../org/openapitools/client/api/UserApi.scala | 17 ------ .../openapitools/client/core/ApiInvoker.scala | 20 ++----- .../client/core/Serializers.scala | 29 ++++++++++ .../client/model/ApiResponse.scala | 3 +- .../openapitools/client/model/Category.scala | 3 +- .../client/model/InlineObject.scala | 2 +- .../client/model/InlineObject1.scala | 2 +- .../org/openapitools/client/model/Order.scala | 3 +- .../org/openapitools/client/model/Pet.scala | 3 +- .../org/openapitools/client/model/Tag.scala | 3 +- .../org/openapitools/client/model/User.scala | 3 +- 20 files changed, 113 insertions(+), 118 deletions(-) create mode 100644 modules/openapi-generator/src/main/resources/scala-sttp-client/serializers.mustache create mode 100644 samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/Serializers.scala diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java index 188581fdb62b..a00f57263a37 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java @@ -30,6 +30,10 @@ public void processOpts() { additionalProperties.put("modelPackage", modelPackage); } + if (!additionalProperties.containsKey("java8")) { + additionalProperties.put("joda", "true"); + } + supportingFiles.clear(); supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); supportingFiles.add(new SupportingFile("build.sbt.mustache", "", "build.sbt")); @@ -38,6 +42,7 @@ public void processOpts() { supportingFiles.add(new SupportingFile("apiInvoker.mustache", invokerFolder, "ApiInvoker.scala")); final String apiFolder = (sourceFolder + File.separator + apiPackage).replace(".", File.separator); supportingFiles.add(new SupportingFile("enumsSerializers.mustache", apiFolder, "EnumsSerializers.scala")); + supportingFiles.add(new SupportingFile("serializers.mustache", invokerFolder, "Serializers.scala")); } @Override diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache index f856defc9231..2418be173ada 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache @@ -36,22 +36,7 @@ class {{classname}}(baseUrl: String)(implicit serializer: SttpSerializer) { .body({{paramName}}){{/bodyParam}} .response(asJson[{{>operationReturnType}}]) - -{{#x-skip-this}} - - - {{#authMethods}}{{#isApiKey}}.withApiKey(apiKey, "{{keyParamName}}", {{#isKeyInQuery}}QUERY{{/isKeyInQuery}}{{#isKeyInHeader}}HEADER{{/isKeyInHeader}}{{#isKeyInCookie}}COOKIE{{/isKeyInCookie}}) - {{/isApiKey}}{{#isBasic}}{{#isBasicBasic}}.withCredentials(basicAuth){{/isBasicBasic}}{{#isBasicBearer}}.withCredentials(bearerToken){{/isBasicBearer}}{{/isBasic}}{{/authMethods}} - -{{#responses}}{{^isWildcard}}{{#dataType}}.with{{>responseState}}Response[{{dataType}}]({{code}}) - {{/dataType}}{{^dataType}}.with{{>responseState}}Response[Unit]({{code}}) - {{/dataType}}{{/isWildcard}}{{/responses}}{{#responses}}{{#isWildcard}}{{#dataType}}.withDefault{{>responseState}}Response[{{dataType}}] - {{/dataType}}{{^dataType}}.withDefault{{>responseState}}Response[Unit] - {{/dataType}}{{/isWildcard}}{{/responses}} -{{/x-skip-this}} - {{/operation}} - } {{/operations}} diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache index af8ce93125c4..254e92c74aac 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/apiInvoker.mustache @@ -1,20 +1,17 @@ {{>licenseInfo}} package {{{mainPackage}}}.core -import org.joda.time.DateTime -import org.joda.time.format.ISODateTimeFormat -import org.json4s.JsonAST.JString import org.json4s._ import sttp.client._ +import sttp.model.StatusCode import org.openapitools.client.api.EnumsSerializers -import org.openapitools.client.core.ApiInvoker.DateTimeSerializer import sttp.client.json4s.SttpJson4sApi import sttp.client.monad.MonadError -class SttpSerializer(implicit val format: Formats = DefaultFormats ++ EnumsSerializers.all + DateTimeSerializer, +class SttpSerializer(implicit val format: Formats = DefaultFormats ++ EnumsSerializers.all ++ Serializers.all, implicit val serialization: org.json4s.Serialization = org.json4s.jackson.Serialization) extends SttpJson4sApi -class HttpException(val statusCode: Int, val statusText: String, val message: String) extends Exception(s"[$statusCode] $statusText: $message") +class HttpException(val statusCode: StatusCode, val statusText: String, val message: String) extends Exception(s"[$statusCode] $statusText: $message") object Helpers { @@ -43,20 +40,11 @@ object ApiInvoker { ME.flatMap(responseT) { response => response.body match { - case Left(ex) => ME.error[T](new HttpException(response.code.code, response.statusText, ex.body)) + case Left(ex) => ME.error[T](new HttpException(response.code, response.statusText, ex.body)) case Right(value) => ME.unit(value) } } } } - case object DateTimeSerializer extends CustomSerializer[DateTime](_ => ( { - case JString(s) => - ISODateTimeFormat.dateOptionalTimeParser().parseDateTime(s) - }, { - case d: DateTime => - JString(ISODateTimeFormat.dateTime().print(d)) - }) - ) - } diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache index 9a1b29b67412..00fe48b731da 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/build.sbt.mustache @@ -9,18 +9,15 @@ crossScalaVersions := Seq(scalaVersion.value, "2.12.10", "2.11.12") libraryDependencies ++= Seq( "com.softwaremill.sttp.client" %% "core" % "2.0.0", "com.softwaremill.sttp.client" %% "json4s" % "2.0.0", - +{{#joda}} "joda-time" % "joda-time" % "2.10.1", +{{/joda}} "org.json4s" %% "json4s-jackson" % "3.6.7", - "org.json4s" %% "json4s-ext" % "3.6.7", - // test dependencies - "org.scalatest" %% "scalatest" % "3.1.1" % Test, + "org.scalatest" %% "scalatest" % "3.0.8" % Test, "junit" % "junit" % "4.13" % "test" ) -resolvers ++= Seq(Resolver.mavenLocal) - scalacOptions := Seq( "-unchecked", "-deprecation", diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/model.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/model.mustache index c5a991d58eb3..941266f3306e 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/model.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/model.mustache @@ -12,12 +12,11 @@ import {{mainPackage}}.core.ApiModel {{#javadocRenderer}} {{#title}} {{{title}}} - {{/title}} {{{description}}} {{/javadocRenderer}} {{/description}} -case class {{classname}} ( +case class {{classname}}( {{#vars}} {{#description}} /* {{{description}}} */ diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/serializers.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/serializers.mustache new file mode 100644 index 000000000000..a17d77069157 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/serializers.mustache @@ -0,0 +1,57 @@ +package {{invokerPackage}} + +{{#java8}} +import java.time.{LocalDate, LocalDateTime, OffsetDateTime, ZoneId} +import java.time.format.DateTimeFormatter +import scala.util.Try +{{/java8}} +{{#joda}} +import org.joda.time.DateTime +import org.joda.time.format.ISODateTimeFormat +{{/joda}} +import org.json4s.{Serializer, CustomSerializer, JNull} +import org.json4s.JsonAST.JString + +object Serializers { + +{{#java8}} + case object DateTimeSerializer extends CustomSerializer[OffsetDateTime](_ => ( { + case JString(s) => + Try(OffsetDateTime.parse(s, DateTimeFormatter.ISO_OFFSET_DATE_TIME)) orElse + Try(LocalDateTime.parse(s).atZone(ZoneId.systemDefault()).toOffsetDateTime) getOrElse (null) + case JNull => null + }, { + case d: OffsetDateTime => + JString(d.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) + })) + + case object LocalDateSerializer extends CustomSerializer[LocalDate]( _ => ( { + case JString(s) => LocalDate.parse(s) + case JNull => null + }, { + case d: LocalDate => + JString(d.format(DateTimeFormatter.ISO_LOCAL_DATE)) + })) +{{/java8}} +{{#joda}} + case object DateTimeSerializer extends CustomSerializer[DateTime](_ => ( { + case JString(s) => + ISODateTimeFormat.dateOptionalTimeParser().parseDateTime(s) + case JNull => null + }, { + case d: org.joda.time.DateTime => + JString(ISODateTimeFormat.dateTime().print(d)) + }) + ) + + case object LocalDateSerializer extends CustomSerializer[org.joda.time.LocalDate](_ => ( { + case JString(s) => org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd").parseLocalDate(s) + case JNull => null + }, { + case d: org.joda.time.LocalDate => JString(d.toString("yyyy-MM-dd")) + })) +{{/joda}} + + def all: Seq[Serializer[_]] = Seq[Serializer[_]]() :+ LocalDateSerializer :+ DateTimeSerializer + +} diff --git a/samples/client/petstore/scala-sttp/build.sbt b/samples/client/petstore/scala-sttp/build.sbt index 90d67f9dcaaa..6964f664a1d4 100644 --- a/samples/client/petstore/scala-sttp/build.sbt +++ b/samples/client/petstore/scala-sttp/build.sbt @@ -1,6 +1,7 @@ version := "1.0.0" name := "scala-sttp-petstore-client" organization := "org.openapitools" + scalaVersion := "2.13.0" crossScalaVersions := Seq(scalaVersion.value, "2.12.10", "2.11.12") @@ -8,18 +9,13 @@ crossScalaVersions := Seq(scalaVersion.value, "2.12.10", "2.11.12") libraryDependencies ++= Seq( "com.softwaremill.sttp.client" %% "core" % "2.0.0", "com.softwaremill.sttp.client" %% "json4s" % "2.0.0", - "joda-time" % "joda-time" % "2.10.1", "org.json4s" %% "json4s-jackson" % "3.6.7", - "org.json4s" %% "json4s-ext" % "3.6.7", - // test dependencies - "org.scalatest" %% "scalatest" % "3.1.1" % Test, + "org.scalatest" %% "scalatest" % "3.0.8" % Test, "junit" % "junit" % "4.13" % "test" ) -resolvers ++= Seq(Resolver.mavenLocal) - scalacOptions := Seq( "-unchecked", "-deprecation", diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala index 0e44b02e221b..5211d09c9c5c 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala @@ -43,8 +43,6 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { .body(pet) .response(asJson[Pet]) - - /** * Expected answers: * code 400 : (Invalid pet value) @@ -59,8 +57,6 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { .header("api_key", apiKey) .response(asJson[Unit]) - - /** * Multiple status values can be provided with comma separated strings * @@ -76,8 +72,6 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { .contentType("application/json") .response(asJson[Seq[Pet]]) - - /** * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. * @@ -93,8 +87,6 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { .contentType("application/json") .response(asJson[Seq[Pet]]) - - /** * Returns a single pet * @@ -115,8 +107,6 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { .header("api_key", apiKey.value) .response(asJson[Pet]) - - /** * Expected answers: * code 200 : Pet (successful operation) @@ -133,8 +123,6 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { .body(pet) .response(asJson[Pet]) - - /** * Expected answers: * code 405 : (Invalid input) @@ -153,8 +141,6 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { )) .response(asJson[Unit]) - - /** * Expected answers: * code 200 : ApiResponse (successful operation) @@ -173,8 +159,5 @@ class PetApi(baseUrl: String)(implicit serializer: SttpSerializer) { )) .response(asJson[ApiResponse]) - - - } diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/StoreApi.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/StoreApi.scala index 7a434e5b80d3..907cc9f42f0f 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/StoreApi.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/StoreApi.scala @@ -42,8 +42,6 @@ class StoreApi(baseUrl: String)(implicit serializer: SttpSerializer) { .contentType("application/json") .response(asJson[Unit]) - - /** * Returns a map of status codes to quantities * @@ -60,8 +58,6 @@ class StoreApi(baseUrl: String)(implicit serializer: SttpSerializer) { .header("api_key", apiKey.value) .response(asJson[Map[String, Int]]) - - /** * For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions * @@ -78,8 +74,6 @@ class StoreApi(baseUrl: String)(implicit serializer: SttpSerializer) { .contentType("application/json") .response(asJson[Order]) - - /** * Expected answers: * code 200 : Order (successful operation) @@ -94,8 +88,5 @@ class StoreApi(baseUrl: String)(implicit serializer: SttpSerializer) { .body(order) .response(asJson[Order]) - - - } diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/UserApi.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/UserApi.scala index 046713880703..7b7df3b8e149 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/UserApi.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/UserApi.scala @@ -46,8 +46,6 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { .body(user) .response(asJson[Unit]) - - /** * Expected answers: * code 0 : (successful operation) @@ -65,8 +63,6 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { .body(user) .response(asJson[Unit]) - - /** * Expected answers: * code 0 : (successful operation) @@ -84,8 +80,6 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { .body(user) .response(asJson[Unit]) - - /** * This can only be done by the logged in user. * @@ -105,8 +99,6 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { .cookie("AUTH_KEY", apiKey.value) .response(asJson[Unit]) - - /** * Expected answers: * code 200 : User (successful operation) @@ -121,8 +113,6 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { .contentType("application/json") .response(asJson[User]) - - /** * Expected answers: * code 200 : String (successful operation) @@ -141,8 +131,6 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { .contentType("application/json") .response(asJson[String]) - - /** * Expected answers: * code 0 : (successful operation) @@ -157,8 +145,6 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { .cookie("AUTH_KEY", apiKey.value) .response(asJson[Unit]) - - /** * This can only be done by the logged in user. * @@ -180,8 +166,5 @@ class UserApi(baseUrl: String)(implicit serializer: SttpSerializer) { .body(user) .response(asJson[Unit]) - - - } diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala index 2263eab5e866..dc98ff4d1365 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/ApiInvoker.scala @@ -11,20 +11,17 @@ */ package org.openapitools.client.core -import org.joda.time.DateTime -import org.joda.time.format.ISODateTimeFormat -import org.json4s.JsonAST.JString import org.json4s._ import sttp.client._ +import sttp.model.StatusCode import org.openapitools.client.api.EnumsSerializers -import org.openapitools.client.core.ApiInvoker.DateTimeSerializer import sttp.client.json4s.SttpJson4sApi import sttp.client.monad.MonadError -class SttpSerializer(implicit val format: Formats = DefaultFormats ++ EnumsSerializers.all + DateTimeSerializer, +class SttpSerializer(implicit val format: Formats = DefaultFormats ++ EnumsSerializers.all ++ Serializers.all, implicit val serialization: org.json4s.Serialization = org.json4s.jackson.Serialization) extends SttpJson4sApi -class HttpException(val statusCode: Int, val statusText: String, val message: String) extends Exception(s"[$statusCode] $statusText: $message") +class HttpException(val statusCode: StatusCode, val statusText: String, val message: String) extends Exception(s"[$statusCode] $statusText: $message") object Helpers { @@ -53,20 +50,11 @@ object ApiInvoker { ME.flatMap(responseT) { response => response.body match { - case Left(ex) => ME.error[T](new HttpException(response.code.code, response.statusText, ex.body)) + case Left(ex) => ME.error[T](new HttpException(response.code, response.statusText, ex.body)) case Right(value) => ME.unit(value) } } } } - case object DateTimeSerializer extends CustomSerializer[DateTime](_ => ( { - case JString(s) => - ISODateTimeFormat.dateOptionalTimeParser().parseDateTime(s) - }, { - case d: DateTime => - JString(ISODateTimeFormat.dateTime().print(d)) - }) - ) - } diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/Serializers.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/Serializers.scala new file mode 100644 index 000000000000..80188ba5e6a1 --- /dev/null +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/Serializers.scala @@ -0,0 +1,29 @@ +package org.openapitools.client.core + +import org.joda.time.DateTime +import org.joda.time.format.ISODateTimeFormat +import org.json4s.{Serializer, CustomSerializer, JNull} +import org.json4s.JsonAST.JString + +object Serializers { + + case object DateTimeSerializer extends CustomSerializer[DateTime](_ => ( { + case JString(s) => + ISODateTimeFormat.dateOptionalTimeParser().parseDateTime(s) + case JNull => null + }, { + case d: org.joda.time.DateTime => + JString(ISODateTimeFormat.dateTime().print(d)) + }) + ) + + case object LocalDateSerializer extends CustomSerializer[org.joda.time.LocalDate](_ => ( { + case JString(s) => org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd").parseLocalDate(s) + case JNull => null + }, { + case d: org.joda.time.LocalDate => JString(d.toString("yyyy-MM-dd")) + })) + + def all: Seq[Serializer[_]] = Seq[Serializer[_]]() :+ LocalDateSerializer :+ DateTimeSerializer + +} diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/ApiResponse.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/ApiResponse.scala index 947a449a1de4..3a3b6d6f499a 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/ApiResponse.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/ApiResponse.scala @@ -15,10 +15,9 @@ import org.openapitools.client.core.ApiModel /** * An uploaded response - * * Describes the result of uploading an image resource */ -case class ApiResponse ( +case class ApiResponse( code: Option[Int] = None, `type`: Option[String] = None, message: Option[String] = None diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Category.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Category.scala index 1e3284207f7c..011164617cfb 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Category.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Category.scala @@ -15,10 +15,9 @@ import org.openapitools.client.core.ApiModel /** * Pet category - * * A category for a pet */ -case class Category ( +case class Category( id: Option[Long] = None, name: Option[String] = None ) extends ApiModel diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject.scala index 3d9ec200c9fc..a8c5493161a1 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject.scala @@ -13,7 +13,7 @@ package org.openapitools.client.model import org.openapitools.client.core.ApiModel -case class InlineObject ( +case class InlineObject( /* Updated name of the pet */ name: Option[String] = None, /* Updated status of the pet */ diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject1.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject1.scala index c41794c43d43..480cf8c2e10a 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject1.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/InlineObject1.scala @@ -14,7 +14,7 @@ package org.openapitools.client.model import java.io.File import org.openapitools.client.core.ApiModel -case class InlineObject1 ( +case class InlineObject1( /* Additional data to pass to server */ additionalMetadata: Option[String] = None, /* file to upload */ diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Order.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Order.scala index e25fe060d4f9..b8f11b0b3c38 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Order.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Order.scala @@ -16,10 +16,9 @@ import org.openapitools.client.core.ApiModel /** * Pet Order - * * An order for a pets from the pet store */ -case class Order ( +case class Order( id: Option[Long] = None, petId: Option[Long] = None, quantity: Option[Int] = None, diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Pet.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Pet.scala index dca755b339d2..75b528c3c0a1 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Pet.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Pet.scala @@ -15,10 +15,9 @@ import org.openapitools.client.core.ApiModel /** * a Pet - * * A pet for sale in the pet store */ -case class Pet ( +case class Pet( id: Option[Long] = None, category: Option[Category] = None, name: String, diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Tag.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Tag.scala index 76d11034c631..299ee5161a8b 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Tag.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/Tag.scala @@ -15,10 +15,9 @@ import org.openapitools.client.core.ApiModel /** * Pet Tag - * * A tag for a pet */ -case class Tag ( +case class Tag( id: Option[Long] = None, name: Option[String] = None ) extends ApiModel diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/User.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/User.scala index fe13ca8d7347..bd2e6c3ba2a7 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/User.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/model/User.scala @@ -15,10 +15,9 @@ import org.openapitools.client.core.ApiModel /** * a User - * * A User who is purchasing from the pet store */ -case class User ( +case class User( id: Option[Long] = None, username: Option[String] = None, firstName: Option[String] = None, From 07a58b0269326653b18ccad04390cff7d97623da Mon Sep 17 00:00:00 2001 From: Aleksandr Nekrasov Date: Thu, 27 Feb 2020 19:05:35 +0700 Subject: [PATCH 9/9] basic and bearer authorization added, apikey in query supported --- .../src/main/resources/scala-sttp-client/api.mustache | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache b/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache index 2418be173ada..412bfca507d4 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp-client/api.mustache @@ -26,10 +26,13 @@ class {{classname}}(baseUrl: String)(implicit serializer: SttpSerializer) { {{/javadocRenderer}} def {{operationId}}({{>methodParameters}}): ApiRequestT[{{>operationReturnType}}] = basicRequest - .method(Method.{{httpMethod.toUpperCase}}, uri"$baseUrl{{{path}}}{{#queryParams.0}}?{{#queryParams}}{{baseName}}=${{{paramName}}}{{^-last}}&{{/-last}}{{/queryParams}}{{/queryParams.0}}") + .method(Method.{{httpMethod.toUpperCase}}, uri"$baseUrl{{{path}}}{{#queryParams.0}}?{{#queryParams}}{{baseName}}=${{{paramName}}}{{^-last}}&{{/-last}}{{/queryParams}}{{/queryParams.0}}{{#isApiKey}}{{#isKeyInQuery}}{{^queryParams.0}}?{{/queryParams.0}}{{#queryParams.0}}&{{/queryParams.0}}{{keyParamName}}=${apiKey.value}&{{/isKeyInQuery}}{{/isApiKey}}") .contentType({{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}}{{^consumes}}"application/json"{{/consumes}}){{#headerParams}} - .header({{>paramCreation}}){{/headerParams}}{{#authMethods}}{{#isApiKey}} - {{#isKeyInQuery}}{{/isKeyInQuery}}{{#isKeyInHeader}}.header("{{keyParamName}}", apiKey.value){{/isKeyInHeader}}{{#isKeyInCookie}}.cookie("{{keyParamName}}", apiKey.value){{/isKeyInCookie}}{{/isApiKey}}{{/authMethods}}{{#formParams.0}} + .header({{>paramCreation}}){{/headerParams}}{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} + .auth.withCredentials(basicAuth.user, basicAuth.password){{/isBasicBasic}}{{#isBasicBearer}} + .auth.bearer(bearerToken.token){{/isBasicBearer}}{{/isBasic}}{{#isApiKey}}{{#isKeyInHeader}} + .header("{{keyParamName}}", apiKey.value){{/isKeyInHeader}}{{#isKeyInCookie}} + .cookie("{{keyParamName}}", apiKey.value){{/isKeyInCookie}}{{/isApiKey}}{{/authMethods}}{{#formParams.0}} .body(Map({{#formParams}} {{>paramFormCreation}},{{/formParams}} )){{/formParams.0}}{{#bodyParam}}