Skip to content

[scala][client]: scala-http4s: minor improvement (enum companion method, remove implicit, error handling) #19901

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class {{classname}}EndpointsImpl[F[*]: Concurrent](
formParameters = {{^hasFormParams}}None,{{/hasFormParams}}{{#hasFormParams}}formParameters,{{/hasFormParams}}
queryParameters = {{^hasQueryParams}}Nil,{{/hasQueryParams}}{{#hasQueryParams}}queryParameters,{{/hasQueryParams}}
requestHeaders = requestHeaders,
auth = {{#authMethods}}Some(auth){{/authMethods}}{{^authMethods}}None{{/authMethods}}) {
auth = {{#authMethods.0}}Some(auth){{/authMethods.0}}{{^authMethods}}None{{/authMethods}}) {
{{>responseState}}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ abstract class BaseClient[F[*]: Concurrent](
queryParameters: Seq[(String, Any)] = Nil,
requestHeaders: Seq[(String, String)] = Nil,
auth: Option[_Authorization] = None
)(handler: Response[F] => F[U])(implicit encoder: Encoder[T]): F[U] = {
)(handler: Response[F] => F[U])(using Encoder[T]): F[U] = {

val m = Method.fromString(method) match {
case Right(m) => m
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{#dataType}}case r if r.status.code == {{code}} => parseJson[F, {{dataType}}]("{{dataType}}", r).flatMap(res => Concurrent[F].raiseError(_FailedRequest(r.status.code, res.asJson.noSpaces))){{/dataType}}{{^dataType}}case r if r.status.code == {{code}} => Concurrent[F].raiseError(_FailedRequest(r.status.code, r.status.reason)){{/dataType}}
{{#dataType}}case r if r.status.code == {{code}} => parseJson[F, {{dataType}}]("{{dataType}}", r).flatMap(res => Concurrent[F].raiseError(_FailedRequest(r.status.code, r.status.reason, Some(res.asJson)))){{/dataType}}{{^dataType}}case r if r.status.code == {{code}} => Concurrent[F].raiseError(_FailedRequest(r.status.code, r.status.reason)){{/dataType}}
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
{{>licenseInfo}}
package {{modelPackage}}

import io.circe.*
import io.circe.Decoder.*
import io.circe.Encoder.*
import io.circe.{Decoder, Encoder, Json}
import io.circe.syntax.*

case class _FailedRequest(code: Int, message: String) extends Exception(s"Server return status code: $code; message: $message")
case class _FailedRequest(code: Int, message: String, body: Option[Json] = None)
extends Exception(s"Server returned status $code; message: $message; body: ${body.map(_.noSpaces).getOrElse("")}")

object _FailedRequest {

given encoderFailedRequest: Encoder[_FailedRequest] = Encoder.instance { t =>
Json.fromFields{
Seq(
"code" -> t.code.asJson,
"message" -> t.message.asJson
)
Some("code" -> t.code.asJson),
Some("message" -> t.message.asJson),
t.body.map(x => "body" -> x)
).flatten
}
}

given decodeFailedRequest: Decoder[_FailedRequest] = Decoder.instance { c =>
for {
code <- c.downField("code").as[Int]
message <- c.downField("message").as[String]
body <- c.downField("body").as[Option[Json]]
} yield _FailedRequest(
code = code,
message = message
message = message,
body = body
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import {{modelPackage}}.*

object JsonSupports {

implicit def circeJsonEncoder[F[*]: Concurrent, A](implicit encoder: Encoder[A]): EntityEncoder[F, A] =
implicit def circeJsonEncoder[F[*]: Concurrent, A](using Encoder[A]): EntityEncoder[F, A] =
http4sCirce.jsonEncoderOf[F, A]
implicit def circeJsonDecoder[F[*]: Concurrent, A](implicit decoder: Decoder[A]): EntityDecoder[F, A] =
implicit def circeJsonDecoder[F[*]: Concurrent, A](using Decoder[A]): EntityDecoder[F, A] =
http4sCirce.jsonOf[F, A]

def parseJson[F[*]: Concurrent, T](
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{#allParams}}{{paramName}}: {{#required}}{{dataType}}{{/required}}{{^required}}Option[{{dataType}}]{{/required}}{{^defaultValue}}{{^required}} = None{{/required}}{{/defaultValue}}{{^-last}}, {{/-last}}{{/allParams}}{{#authMethods.0}}{{#isApiKey}})(implicit auth: _Authorization.ApiKey{{/isApiKey}}{{#isBasic}}{{#isBasicBasic}})(implicit auth: _Authorization.Basic{{/isBasicBasic}}{{#isBasicBearer}})(implicit auth: _Authorization.Bearer{{/isBasicBearer}}{{/isBasic}}{{/authMethods.0}}
{{#allParams}}{{paramName}}: {{#required}}{{dataType}}{{/required}}{{^required}}Option[{{dataType}}]{{/required}}{{^defaultValue}}{{^required}} = None{{/required}}{{/defaultValue}}{{^-last}}, {{/-last}}{{/allParams}}{{#authMethods.0}}{{#isApiKey}})(using auth: _Authorization.ApiKey{{/isApiKey}}{{#isBasic}}{{#isBasicBasic}})(using auth: _Authorization.Basic{{/isBasicBasic}}{{#isBasicBearer}})(using auth: _Authorization.Bearer{{/isBasicBearer}}{{/isBasic}}{{/authMethods.0}}
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ enum {{classname}}(val value: String) {
}

object {{classname}} {
given decoder{{classname}}: Decoder[{{classname}}] =
Decoder.decodeString.map(str => {{classname}}.values.find(_.value == str)
.getOrElse(throw java.lang.IllegalArgumentException(s"{{classname}} enum case not found: $str"))
)

given encoder{{classname}}: Encoder[{{classname}}] =
Encoder.encodeString.contramap[{{classname}}](_.value)
def withValueOpt(value: String): Option[{{classname}}] = {{classname}}.values.find(_.value == value)
def withValue(value: String): {{classname}} =
withValueOpt(value).getOrElse(throw java.lang.IllegalArgumentException(s"{{classname}} enum case not found: $value"))

given decoder{{classname}}: Decoder[{{classname}}] = Decoder.decodeString.map(withValue)
given encoder{{classname}}: Encoder[{{classname}}] = Encoder.encodeString.contramap[{{classname}}](_.value)

}
{{/isEnum}}
{{^isEnum}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
{{>licenseInfo}}
package {{packageName}}

import io.circe.{Decoder, Encoder}
import io.circe.{Decoder, Encoder, Json}
import java.time.{Instant, LocalDate, OffsetDateTime}
import java.util.UUID

package object models {

given decodeUUID: Decoder[_root_.java.util.UUID] =
Decoder.decodeString.map(str => _root_.java.util.UUID.fromString(str))
given decodeUUID: Decoder[UUID] =
Decoder.decodeString.map(str => UUID.fromString(str))

given encodeUUID: Encoder[_root_.java.util.UUID] =
Encoder.encodeString.contramap[_root_.java.util.UUID](uuid => uuid.toString)
given encodeUUID: Encoder[UUID] =
Encoder.encodeString.contramap[UUID](uuid => uuid.toString)

given decodeInstant: Decoder[_root_.java.time.Instant] =
Decoder.decodeString.map(str => _root_.java.time.OffsetDateTime.parse(str).toInstant)
given decodeInstant: Decoder[Instant] =
Decoder.decodeString.map(str => OffsetDateTime.parse(str).toInstant)

given encodeInstant: Encoder[_root_.java.time.Instant] =
Encoder.encodeString.contramap[_root_.java.time.Instant](_.toString)
given encodeInstant: Encoder[Instant] =
Encoder.encodeString.contramap[Instant](_.toString)

given decodeLocalDate: Decoder[_root_.java.time.LocalDate] =
Decoder.decodeString.map(str => _root_.java.time.LocalDate.parse(str))
given decodeLocalDate: Decoder[LocalDate] =
Decoder.decodeString.map(str => LocalDate.parse(str))

given encodeLocalDate: Encoder[_root_.java.time.LocalDate] =
Encoder.encodeString.contramap[_root_.java.time.LocalDate](_.toString)
given encodeLocalDate: Encoder[LocalDate] =
Encoder.encodeString.contramap[LocalDate](_.toString)

given decodeJson: Decoder[io.circe.Json] =
Decoder.decodeString.map(str => io.circe.Json.fromString(str))
given decodeJson: Decoder[Json] =
Decoder.decodeString.map(str => Json.fromString(str))

given encodeJson: Encoder[io.circe.Json] =
Encoder.encodeString.contramap[io.circe.Json](_.toString)
given encodeJson: Encoder[Json] =
Encoder.encodeString.contramap[Json](_.toString)

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
{{>successResponsePart}}{{/is2xx}}{{^is2xx}}{{#is3xx}}
{{>successResponsePart}}{{/is3xx}}{{^is3xx}}{{^isDefault}}
{{>errorResponsePart}}{{/isDefault}}{{#isDefault}}
{{#dataType}}case r => parseJson[F, {{dataType}}]("{{dataType}}", r).flatMap(res => Concurrent[F].raiseError(_FailedRequest(r.status.code, res.asJson.noSpaces))){{/dataType}}{{^dataType}}case r => Concurrent[F].raiseError(_FailedRequest(r.status.code, r.status.reason)){{/dataType}}{{/isDefault}}{{/is3xx}}{{/is2xx}}{{/responses}}{{/hasOnlyDefaultResponse}}
{{#dataType}}case r => parseJson[F, {{dataType}}]("{{dataType}}", r).flatMap(res => Concurrent[F].raiseError(_FailedRequest(r.status.code, r.status.reason, Some(res.asJson)))){{/dataType}}{{^dataType}}case r => Concurrent[F].raiseError(_FailedRequest(r.status.code, r.status.reason)){{/dataType}}{{/isDefault}}{{/is3xx}}{{/is2xx}}{{/responses}}{{/hasOnlyDefaultResponse}}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ abstract class BaseClient[F[*]: Concurrent](
queryParameters: Seq[(String, Any)] = Nil,
requestHeaders: Seq[(String, String)] = Nil,
auth: Option[_Authorization] = None
)(handler: Response[F] => F[U])(implicit encoder: Encoder[T]): F[U] = {
)(handler: Response[F] => F[U])(using Encoder[T]): F[U] = {

val m = Method.fromString(method) match {
case Right(m) => m
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import org.openapitools.client.models.*

object JsonSupports {

implicit def circeJsonEncoder[F[*]: Concurrent, A](implicit encoder: Encoder[A]): EntityEncoder[F, A] =
implicit def circeJsonEncoder[F[*]: Concurrent, A](using Encoder[A]): EntityEncoder[F, A] =
http4sCirce.jsonEncoderOf[F, A]
implicit def circeJsonDecoder[F[*]: Concurrent, A](implicit decoder: Decoder[A]): EntityDecoder[F, A] =
implicit def circeJsonDecoder[F[*]: Concurrent, A](using Decoder[A]): EntityDecoder[F, A] =
http4sCirce.jsonOf[F, A]

def parseJson[F[*]: Concurrent, T](
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ trait PetApiEndpoints[F[*]] {
def deletePet(petId: Long, apiKey: Option[String] = None): F[Unit]
def findPetsByStatus(status: Seq[FindPetsByStatusStatusParameterInner]): F[Seq[Pet]]
def findPetsByTags(tags: Seq[String]): F[Seq[Pet]]
def getPetById(petId: Long)(implicit auth: _Authorization.ApiKey): F[Pet]
def getPetById(petId: Long)(using auth: _Authorization.ApiKey): F[Pet]
def updatePet(pet: Pet): F[Pet]
def updatePetWithForm(petId: Long, name: Option[String] = None, status: Option[String] = None): F[Unit]
def uploadFile(petId: Long, additionalMetadata: Option[String] = None, file: Option[File] = None): F[ApiResponse]
Expand Down Expand Up @@ -126,7 +126,7 @@ class PetApiEndpointsImpl[F[*]: Concurrent](
}
}

override def getPetById(petId: Long)(implicit auth: _Authorization.ApiKey): F[Pet] = {
override def getPetById(petId: Long)(using auth: _Authorization.ApiKey): F[Pet] = {
val requestHeaders = Seq(
Some("Content-Type" -> "application/json")
).flatten
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import org.openapitools.client.models.*
trait StoreApiEndpoints[F[*]] {

def deleteOrder(orderId: String): F[Unit]
def getInventory()(implicit auth: _Authorization.ApiKey): F[Map[String, Int]]
def getInventory()(using auth: _Authorization.ApiKey): F[Map[String, Int]]
def getOrderById(orderId: Long): F[Order]
def placeOrder(order: Order): F[Order]

Expand Down Expand Up @@ -55,7 +55,7 @@ class StoreApiEndpointsImpl[F[*]: Concurrent](
}
}

override def getInventory()(implicit auth: _Authorization.ApiKey): F[Map[String, Int]] = {
override def getInventory()(using auth: _Authorization.ApiKey): F[Map[String, Int]] = {
val requestHeaders = Seq(
Some("Content-Type" -> "application/json")
).flatten
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ import org.openapitools.client.models.*

trait UserApiEndpoints[F[*]] {

def createUser(user: User)(implicit auth: _Authorization.ApiKey): F[Unit]
def createUsersWithArrayInput(user: Seq[User])(implicit auth: _Authorization.ApiKey): F[Unit]
def createUsersWithListInput(user: Seq[User])(implicit auth: _Authorization.ApiKey): F[Unit]
def deleteUser(username: String)(implicit auth: _Authorization.ApiKey): F[Unit]
def createUser(user: User)(using auth: _Authorization.ApiKey): F[Unit]
def createUsersWithArrayInput(user: Seq[User])(using auth: _Authorization.ApiKey): F[Unit]
def createUsersWithListInput(user: Seq[User])(using auth: _Authorization.ApiKey): F[Unit]
def deleteUser(username: String)(using auth: _Authorization.ApiKey): F[Unit]
def getUserByName(username: String): F[User]
def loginUser(username: String, password: String): F[String]
def logoutUser()(implicit auth: _Authorization.ApiKey): F[Unit]
def updateUser(username: String, user: User)(implicit auth: _Authorization.ApiKey): F[Unit]
def logoutUser()(using auth: _Authorization.ApiKey): F[Unit]
def updateUser(username: String, user: User)(using auth: _Authorization.ApiKey): F[Unit]

}

Expand All @@ -42,7 +42,7 @@ class UserApiEndpointsImpl[F[*]: Concurrent](
import io.circe.syntax.EncoderOps
import cats.implicits.toFlatMapOps

override def createUser(user: User)(implicit auth: _Authorization.ApiKey): F[Unit] = {
override def createUser(user: User)(using auth: _Authorization.ApiKey): F[Unit] = {
val requestHeaders = Seq(
Some("Content-Type" -> "application/json")
).flatten
Expand All @@ -59,7 +59,7 @@ class UserApiEndpointsImpl[F[*]: Concurrent](
}
}

override def createUsersWithArrayInput(user: Seq[User])(implicit auth: _Authorization.ApiKey): F[Unit] = {
override def createUsersWithArrayInput(user: Seq[User])(using auth: _Authorization.ApiKey): F[Unit] = {
val requestHeaders = Seq(
Some("Content-Type" -> "application/json")
).flatten
Expand All @@ -76,7 +76,7 @@ class UserApiEndpointsImpl[F[*]: Concurrent](
}
}

override def createUsersWithListInput(user: Seq[User])(implicit auth: _Authorization.ApiKey): F[Unit] = {
override def createUsersWithListInput(user: Seq[User])(using auth: _Authorization.ApiKey): F[Unit] = {
val requestHeaders = Seq(
Some("Content-Type" -> "application/json")
).flatten
Expand All @@ -93,7 +93,7 @@ class UserApiEndpointsImpl[F[*]: Concurrent](
}
}

override def deleteUser(username: String)(implicit auth: _Authorization.ApiKey): F[Unit] = {
override def deleteUser(username: String)(using auth: _Authorization.ApiKey): F[Unit] = {
val requestHeaders = Seq(
Some("Content-Type" -> "application/json")
).flatten
Expand Down Expand Up @@ -155,7 +155,7 @@ class UserApiEndpointsImpl[F[*]: Concurrent](
}
}

override def logoutUser()(implicit auth: _Authorization.ApiKey): F[Unit] = {
override def logoutUser()(using auth: _Authorization.ApiKey): F[Unit] = {
val requestHeaders = Seq(
Some("Content-Type" -> "application/json")
).flatten
Expand All @@ -172,7 +172,7 @@ class UserApiEndpointsImpl[F[*]: Concurrent](
}
}

override def updateUser(username: String, user: User)(implicit auth: _Authorization.ApiKey): F[Unit] = {
override def updateUser(username: String, user: User)(using auth: _Authorization.ApiKey): F[Unit] = {
val requestHeaders = Seq(
Some("Content-Type" -> "application/json")
).flatten
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ enum FindPetsByStatusStatusParameterInner(val value: String) {
}

object FindPetsByStatusStatusParameterInner {
given decoderFindPetsByStatusStatusParameterInner: Decoder[FindPetsByStatusStatusParameterInner] =
Decoder.decodeString.map(str => FindPetsByStatusStatusParameterInner.values.find(_.value == str)
.getOrElse(throw java.lang.IllegalArgumentException(s"FindPetsByStatusStatusParameterInner enum case not found: $str"))
)

given encoderFindPetsByStatusStatusParameterInner: Encoder[FindPetsByStatusStatusParameterInner] =
Encoder.encodeString.contramap[FindPetsByStatusStatusParameterInner](_.value)
def withValueOpt(value: String): Option[FindPetsByStatusStatusParameterInner] = FindPetsByStatusStatusParameterInner.values.find(_.value == value)
def withValue(value: String): FindPetsByStatusStatusParameterInner =
withValueOpt(value).getOrElse(throw java.lang.IllegalArgumentException(s"FindPetsByStatusStatusParameterInner enum case not found: $value"))

given decoderFindPetsByStatusStatusParameterInner: Decoder[FindPetsByStatusStatusParameterInner] = Decoder.decodeString.map(withValue)
given encoderFindPetsByStatusStatusParameterInner: Encoder[FindPetsByStatusStatusParameterInner] = Encoder.encodeString.contramap[FindPetsByStatusStatusParameterInner](_.value)

}

Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ enum OrderStatus(val value: String) {
}

object OrderStatus {
given decoderOrderStatus: Decoder[OrderStatus] =
Decoder.decodeString.map(str => OrderStatus.values.find(_.value == str)
.getOrElse(throw java.lang.IllegalArgumentException(s"OrderStatus enum case not found: $str"))
)

given encoderOrderStatus: Encoder[OrderStatus] =
Encoder.encodeString.contramap[OrderStatus](_.value)
def withValueOpt(value: String): Option[OrderStatus] = OrderStatus.values.find(_.value == value)
def withValue(value: String): OrderStatus =
withValueOpt(value).getOrElse(throw java.lang.IllegalArgumentException(s"OrderStatus enum case not found: $value"))

given decoderOrderStatus: Decoder[OrderStatus] = Decoder.decodeString.map(withValue)
given encoderOrderStatus: Encoder[OrderStatus] = Encoder.encodeString.contramap[OrderStatus](_.value)

}

Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ enum PetStatus(val value: String) {
}

object PetStatus {
given decoderPetStatus: Decoder[PetStatus] =
Decoder.decodeString.map(str => PetStatus.values.find(_.value == str)
.getOrElse(throw java.lang.IllegalArgumentException(s"PetStatus enum case not found: $str"))
)

given encoderPetStatus: Encoder[PetStatus] =
Encoder.encodeString.contramap[PetStatus](_.value)
def withValueOpt(value: String): Option[PetStatus] = PetStatus.values.find(_.value == value)
def withValue(value: String): PetStatus =
withValueOpt(value).getOrElse(throw java.lang.IllegalArgumentException(s"PetStatus enum case not found: $value"))

given decoderPetStatus: Decoder[PetStatus] = Decoder.decodeString.map(withValue)
given encoderPetStatus: Encoder[PetStatus] = Encoder.encodeString.contramap[PetStatus](_.value)

}

Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,33 @@
*/
package org.openapitools.client.models

import io.circe.*
import io.circe.Decoder.*
import io.circe.Encoder.*
import io.circe.{Decoder, Encoder, Json}
import io.circe.syntax.*

case class _FailedRequest(code: Int, message: String) extends Exception(s"Server return status code: $code; message: $message")
case class _FailedRequest(code: Int, message: String, body: Option[Json] = None)
extends Exception(s"Server returned status $code; message: $message; body: ${body.map(_.noSpaces).getOrElse("")}")

object _FailedRequest {

given encoderFailedRequest: Encoder[_FailedRequest] = Encoder.instance { t =>
Json.fromFields{
Seq(
"code" -> t.code.asJson,
"message" -> t.message.asJson
)
Some("code" -> t.code.asJson),
Some("message" -> t.message.asJson),
t.body.map(x => "body" -> x)
).flatten
}
}

given decodeFailedRequest: Decoder[_FailedRequest] = Decoder.instance { c =>
for {
code <- c.downField("code").as[Int]
message <- c.downField("message").as[String]
body <- c.downField("body").as[Option[Json]]
} yield _FailedRequest(
code = code,
message = message
message = message,
body = body
)
}

Expand Down
Loading
Loading