Skip to content

Commit

Permalink
[scala][client]: scala-http4s: minor improvement (enum companion meth…
Browse files Browse the repository at this point in the history
…od, remove implicit, error handling) (#19901)

* [scala][http4s][client]: add enum method; remove implicit

* improve error handling

* more enum enhancement

* remove unused

* update samle

* avoid breaking change

---------

Co-authored-by: Jenny Leahy <[email protected]>
  • Loading branch information
JennyLeahy and Jenny Leahy authored Nov 8, 2024
1 parent 3a36882 commit 961c2e4
Show file tree
Hide file tree
Showing 19 changed files with 112 additions and 100 deletions.
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

0 comments on commit 961c2e4

Please sign in to comment.