-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#8 ♻️ Restructure JSON deserialisation to enable other implementation…
…s than Circe (#351) * #8 ♻️ Restructure JSON (de)serialisation to enable other implementations than Circe. * #8 💚 Add new modules to the CI. * #8 ♻️ Revert the core module and group JSON-related tests in a single trait for simplified implementation per JSON module. * #8 ♻️ Remove unused EntityDecoder. Replace codec in names with json. * #8 ♻️ Remove JsonInput.sanitize from JsonSupport and move it to the main package. * #8 🎨 Optimise imports and reformat a few files. * #8 📝 Modify docs to reflect changes in JSON deserialisation support. * #8 📝 Add backwards compatibility TL;DR regarding circe module to the changelog 👌. * ♻️ Make `EncodedJson` final. * #8 ✨ Add jsoniter-scala module. * 💚 Fix CI. * ⬆️ Bump sbt-scalajs. * ⬆️ Bump scala 3 version. * 🔧 Tweak deprecation warning configuration for jsoniter modules. Macro-generated code uses deprecated methods: ``` .../com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMaker.scala: method isEmpty in class IterableOnceExtensionMethods is deprecated since 2.13.0: Use .iterator.isEmpty instead ``` * ♻️ Move OAuth2Error creation to common to avoid duplication in JSON implementations.
- Loading branch information
1 parent
19099b9
commit 76f7aa2
Showing
54 changed files
with
1,493 additions
and
881 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,7 +27,7 @@ jobs: | |
strategy: | ||
matrix: | ||
os: [ubuntu-latest] | ||
scala: [2.12.17, 2.13.10, 3.1.3] | ||
scala: [2.12.17, 2.13.10, 3.2.2] | ||
java: [[email protected]] | ||
runs-on: ${{ matrix.os }} | ||
steps: | ||
|
@@ -59,7 +59,7 @@ jobs: | |
- run: sbt ++${{ matrix.scala }} test docs/mdoc mimaReportBinaryIssues | ||
|
||
- name: Compress target directories | ||
run: tar cf targets.tar oauth2/js/target oauth2-cache/js/target oauth2-cache-ce2/target target oauth2-cache-scalacache/target mdoc/target oauth2-cache-cats/target oauth2-cache-future/jvm/target oauth2-cache/jvm/target oauth2-cache-future/js/target oauth2/jvm/target project/target | ||
run: tar cf targets.tar oauth2-jsoniter/jvm/target oauth2/js/target oauth2-cache/js/target oauth2-cache-ce2/target oauth2-jsoniter/js/target target oauth2-cache-scalacache/target mdoc/target oauth2-circe/jvm/target oauth2-cache-cats/target oauth2-cache-future/jvm/target oauth2-circe/js/target oauth2-cache/jvm/target oauth2-cache-future/js/target oauth2/jvm/target project/target | ||
|
||
- name: Upload target directories | ||
uses: actions/upload-artifact@v2 | ||
|
@@ -120,12 +120,12 @@ jobs: | |
tar xf targets.tar | ||
rm targets.tar | ||
- name: Download target directories (3.1.3) | ||
- name: Download target directories (3.2.2) | ||
uses: actions/download-artifact@v2 | ||
with: | ||
name: target-${{ matrix.os }}-3.1.3-${{ matrix.java }} | ||
name: target-${{ matrix.os }}-3.2.2-${{ matrix.java }} | ||
|
||
- name: Inflate target directories (3.1.3) | ||
- name: Inflate target directories (3.2.2) | ||
run: | | ||
tar xf targets.tar | ||
rm targets.tar | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,7 +33,7 @@ def crossPlugin(x: sbt.librarymanagement.ModuleID) = compilerPlugin(x.cross(Cros | |
|
||
val Scala212 = "2.12.17" | ||
val Scala213 = "2.13.10" | ||
val Scala3 = "3.1.3" | ||
val Scala3 = "3.2.2" | ||
|
||
val GraalVM11 = "[email protected]" | ||
|
||
|
@@ -62,6 +62,7 @@ val Versions = new { | |
val catsEffect = "3.3.14" | ||
val catsEffect2 = "2.5.5" | ||
val circe = "0.14.3" | ||
val jsoniter = "2.21.1" | ||
val monix = "3.4.1" | ||
val scalaTest = "3.2.15" | ||
val sttp = "3.3.18" | ||
|
@@ -97,12 +98,8 @@ lazy val oauth2 = crossProject(JSPlatform, JVMPlatform) | |
.settings( | ||
name := "sttp-oauth2", | ||
libraryDependencies ++= Seq( | ||
"org.typelevel" %%% "cats-core" % Versions.catsCore, | ||
"io.circe" %%% "circe-parser" % Versions.circe, | ||
"io.circe" %%% "circe-core" % Versions.circe, | ||
"io.circe" %%% "circe-refined" % Versions.circe, | ||
"com.softwaremill.sttp.client3" %%% "core" % Versions.sttp, | ||
"com.softwaremill.sttp.client3" %%% "circe" % Versions.sttp, | ||
"org.typelevel" %%% "cats-core" % Versions.catsCore, | ||
"eu.timepit" %%% "refined" % Versions.refined, | ||
"org.scalatest" %%% "scalatest" % Versions.scalaTest % Test | ||
), | ||
|
@@ -114,6 +111,42 @@ lazy val oauth2 = crossProject(JSPlatform, JVMPlatform) | |
jsSettings | ||
) | ||
|
||
lazy val `oauth2-circe` = crossProject(JSPlatform, JVMPlatform) | ||
.withoutSuffixFor(JVMPlatform) | ||
.in(file("oauth2-circe")) | ||
.settings( | ||
name := "sttp-oauth2-circe", | ||
libraryDependencies ++= Seq( | ||
"io.circe" %%% "circe-parser" % Versions.circe, | ||
"io.circe" %%% "circe-core" % Versions.circe, | ||
"io.circe" %%% "circe-refined" % Versions.circe | ||
), | ||
mimaSettings, | ||
compilerPlugins | ||
) | ||
.jsSettings( | ||
jsSettings | ||
) | ||
.dependsOn(oauth2 % "compile->compile;test->test") | ||
|
||
lazy val `oauth2-jsoniter` = crossProject(JSPlatform, JVMPlatform) | ||
.withoutSuffixFor(JVMPlatform) | ||
.in(file("oauth2-jsoniter")) | ||
.settings( | ||
name := "sttp-oauth2-jsoniter", | ||
libraryDependencies ++= Seq( | ||
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % Versions.jsoniter, | ||
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % Versions.jsoniter % "compile-internal" | ||
), | ||
mimaSettings, | ||
compilerPlugins, | ||
scalacOptions ++= Seq("-Wconf:cat=deprecation:info") // jsoniter-scala macro-generated code uses deprecated methods | ||
) | ||
.jsSettings( | ||
jsSettings | ||
) | ||
.dependsOn(oauth2 % "compile->compile;test->test") | ||
|
||
lazy val docs = project | ||
.in(file("mdoc")) // important: it must not be docs/ | ||
.settings( | ||
|
@@ -212,5 +245,9 @@ val root = project | |
`oauth2-cache-ce2`, | ||
`oauth2-cache-future`.jvm, | ||
`oauth2-cache-future`.js, | ||
`oauth2-cache-scalacache` | ||
`oauth2-cache-scalacache`, | ||
`oauth2-circe`.jvm, | ||
`oauth2-circe`.js, | ||
`oauth2-jsoniter`.jvm, | ||
`oauth2-jsoniter`.js, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
--- | ||
sidebar_position: 7 | ||
description: Choosing JSON deserialisation module | ||
--- | ||
|
||
# Choosing JSON deserialisation module | ||
JSON deserialisation has been decoupled from the core modules. | ||
There are now a couple of options to choose from: | ||
|
||
## circe | ||
To use [circe](https://github.com/circe/circe) implementation | ||
add the following module to your dependencies: | ||
|
||
```scala | ||
"com.ocadotechnology" %% "sttp-oauth2-circe" % "@VERSION@" | ||
``` | ||
|
||
Then import appropriate set of implicit instances: | ||
|
||
```scala | ||
import com.ocadotechnology.sttp.oauth2.json.circe.instances._ | ||
``` | ||
|
||
## jsoniter-scala | ||
To use [jsoniter-scala](https://github.com/plokhotnyuk/jsoniter-scala) implementation | ||
add the following module to your dependencies: | ||
|
||
```scala | ||
"com.ocadotechnology" %% "sttp-oauth2-jsoniter" % "@VERSION@" | ||
``` | ||
|
||
Then import appropriate set of implicit instances: | ||
|
||
```scala | ||
import com.ocadotechnology.sttp.oauth2.json.jsoniter.instances._ | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
141 changes: 141 additions & 0 deletions
141
.../shared/src/main/scala/com/ocadotechnology/sttp/oauth2/json/circe/CirceJsonDecoders.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package com.ocadotechnology.sttp.oauth2.json.circe | ||
|
||
import cats.syntax.all._ | ||
import com.ocadotechnology.sttp.oauth2.ClientCredentialsToken.AccessTokenResponse | ||
import com.ocadotechnology.sttp.oauth2.UserInfo | ||
import com.ocadotechnology.sttp.oauth2.common.Error.OAuth2Error | ||
import com.ocadotechnology.sttp.oauth2.ExtendedOAuth2TokenResponse | ||
import com.ocadotechnology.sttp.oauth2.Introspection.Audience | ||
import com.ocadotechnology.sttp.oauth2.Introspection.SeqAudience | ||
import com.ocadotechnology.sttp.oauth2.Introspection.StringAudience | ||
import com.ocadotechnology.sttp.oauth2.Introspection.TokenIntrospectionResponse | ||
import com.ocadotechnology.sttp.oauth2.OAuth2TokenResponse | ||
import com.ocadotechnology.sttp.oauth2.RefreshTokenResponse | ||
import com.ocadotechnology.sttp.oauth2.Secret | ||
import com.ocadotechnology.sttp.oauth2.TokenUserDetails | ||
import com.ocadotechnology.sttp.oauth2.json.JsonDecoder | ||
import io.circe.Decoder | ||
import io.circe.refined._ | ||
|
||
import java.time.Instant | ||
import scala.concurrent.duration.DurationLong | ||
import scala.concurrent.duration.FiniteDuration | ||
|
||
trait CirceJsonDecoders { | ||
|
||
implicit def jsonDecoder[A](implicit decoder: Decoder[A]): JsonDecoder[A] = | ||
(data: String) => io.circe.parser.decode[A](data).leftMap(error => JsonDecoder.Error(error.getMessage, cause = Some(error))) | ||
|
||
implicit val userInfoDecoder: Decoder[UserInfo] = ( | ||
Decoder[Option[String]].at("sub"), | ||
Decoder[Option[String]].at("name"), | ||
Decoder[Option[String]].at("given_name"), | ||
Decoder[Option[String]].at("family_name"), | ||
Decoder[Option[String]].at("job_title"), | ||
Decoder[Option[String]].at("domain"), | ||
Decoder[Option[String]].at("preferred_username"), | ||
Decoder[Option[String]].at("email"), | ||
Decoder[Option[Boolean]].at("email_verified"), | ||
Decoder[Option[String]].at("locale"), | ||
Decoder[List[String]].at("sites").or(Decoder.const(List.empty[String])), | ||
Decoder[List[String]].at("banners").or(Decoder.const(List.empty[String])), | ||
Decoder[List[String]].at("regions").or(Decoder.const(List.empty[String])), | ||
Decoder[List[String]].at("fulfillment_contexts").or(Decoder.const(List.empty[String])) | ||
).mapN(UserInfo.apply) | ||
|
||
implicit val secondsDecoder: Decoder[FiniteDuration] = Decoder.decodeLong.map(_.seconds) | ||
|
||
implicit val instantDecoder: Decoder[Instant] = Decoder.decodeLong.map(Instant.ofEpochSecond) | ||
|
||
implicit val tokenDecoder: Decoder[AccessTokenResponse] = | ||
Decoder | ||
.forProduct4( | ||
"access_token", | ||
"domain", | ||
"expires_in", | ||
"scope" | ||
)(AccessTokenResponse.apply) | ||
.validate { | ||
_.downField("token_type").as[String] match { | ||
case Right(value) if value.equalsIgnoreCase("Bearer") => List.empty | ||
case Right(string) => List(s"Error while decoding '.token_type': value '$string' is not equal to 'Bearer'") | ||
case Left(s) => List(s"Error while decoding '.token_type': ${s.getMessage}") | ||
} | ||
} | ||
|
||
implicit val errorDecoder: Decoder[OAuth2Error] = | ||
Decoder.forProduct2[OAuth2Error, String, Option[String]]("error", "error_description")(OAuth2Error.fromErrorTypeAndDescription) | ||
|
||
implicit val tokenResponseDecoder: Decoder[OAuth2TokenResponse] = | ||
Decoder.forProduct5( | ||
"access_token", | ||
"scope", | ||
"token_type", | ||
"expires_in", | ||
"refresh_token" | ||
)(OAuth2TokenResponse.apply) | ||
|
||
implicit val tokenUserDetailsDecoder: Decoder[TokenUserDetails] = | ||
Decoder.forProduct7( | ||
"username", | ||
"name", | ||
"forename", | ||
"surname", | ||
"mail", | ||
"cn", | ||
"sn" | ||
)(TokenUserDetails.apply) | ||
|
||
implicit val extendedTokenResponseDecoder: Decoder[ExtendedOAuth2TokenResponse] = | ||
Decoder.forProduct11( | ||
"access_token", | ||
"refresh_token", | ||
"expires_in", | ||
"user_name", | ||
"domain", | ||
"user_details", | ||
"roles", | ||
"scope", | ||
"security_level", | ||
"user_id", | ||
"token_type" | ||
)(ExtendedOAuth2TokenResponse.apply) | ||
|
||
implicit val audienceDecoder: Decoder[Audience] = | ||
Decoder.decodeString.map(StringAudience.apply).or(Decoder.decodeSeq[String].map(SeqAudience.apply)) | ||
|
||
implicit val tokenIntrospectionResponseDecoder: Decoder[TokenIntrospectionResponse] = | ||
Decoder.forProduct13( | ||
"active", | ||
"client_id", | ||
"domain", | ||
"exp", | ||
"iat", | ||
"nbf", | ||
"authorities", | ||
"scope", | ||
"token_type", | ||
"sub", | ||
"iss", | ||
"jti", | ||
"aud" | ||
)(TokenIntrospectionResponse.apply) | ||
|
||
implicit val refreshTokenResponseDecoder: Decoder[RefreshTokenResponse] = | ||
Decoder.forProduct11( | ||
"access_token", | ||
"refresh_token", | ||
"expires_in", | ||
"user_name", | ||
"domain", | ||
"user_details", | ||
"roles", | ||
"scope", | ||
"security_level", | ||
"user_id", | ||
"token_type" | ||
)(RefreshTokenResponse.apply) | ||
|
||
implicit def secretDecoder[A: Decoder]: Decoder[Secret[A]] = Decoder[A].map(Secret(_)) | ||
|
||
} |
3 changes: 3 additions & 0 deletions
3
...h2-circe/shared/src/main/scala/com/ocadotechnology/sttp/oauth2/json/circe/instances.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package com.ocadotechnology.sttp.oauth2.json.circe | ||
|
||
object instances extends CirceJsonDecoders |
Oops, something went wrong.