All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ocadotechnology.sttp.oauth2.json.jsoniter.JsoniterJsonDecoders.scala Maven / Gradle / Ivy

The newest version!
package org.polyvariant.sttp.oauth2.json.jsoniter

import cats.syntax.all._
import com.github.plokhotnyuk.jsoniter_scala.core._
import com.github.plokhotnyuk.jsoniter_scala.macros._
import org.polyvariant.sttp.oauth2.json.JsonDecoder
import org.polyvariant.sttp.oauth2.ClientCredentialsToken.AccessTokenResponse
import org.polyvariant.sttp.oauth2.ExtendedOAuth2TokenResponse
import org.polyvariant.sttp.oauth2.Introspection.Audience
import org.polyvariant.sttp.oauth2.Introspection.TokenIntrospectionResponse
import org.polyvariant.sttp.oauth2.OAuth2TokenResponse
import org.polyvariant.sttp.oauth2.RefreshTokenResponse
import org.polyvariant.sttp.oauth2.Secret
import org.polyvariant.sttp.oauth2.TokenUserDetails
import org.polyvariant.sttp.oauth2.UserInfo
import org.polyvariant.sttp.oauth2.common.Error.OAuth2Error
import org.polyvariant.sttp.oauth2.Introspection.SeqAudience
import org.polyvariant.sttp.oauth2.Introspection.StringAudience
import org.polyvariant.sttp.oauth2.common.Scope
import org.polyvariant.sttp.oauth2.json.jsoniter.JsoniterJsonDecoders.oAuth2ErrorHelperDecoder
import org.polyvariant.sttp.oauth2.json.jsoniter.JsoniterJsonDecoders.tokenTypeDecoder
import org.polyvariant.sttp.oauth2.json.jsoniter.JsoniterJsonDecoders.IntermediateOAuth2Error

import java.time.Instant
import scala.concurrent.duration.DurationLong
import scala.concurrent.duration.FiniteDuration
import scala.util.Failure
import scala.util.Success
import scala.util.Try
import scala.util.control.NonFatal

trait JsoniterJsonDecoders {

  implicit def jsonDecoder[A](
    implicit jsonCodec: JsonValueCodec[A]
  ): JsonDecoder[A] =
    (data: String) =>
      Try(readFromString[A](data)) match {
        case Success(value)                    =>
          Right(value)
        case Failure(error: JsonDecoder.Error) =>
          Left(error)
        case Failure(NonFatal(throwable))      =>
          Left(JsonDecoder.Error.fromThrowable(throwable))
        case Failure(fatal)                    =>
          throw fatal
      }

  private[jsoniter] implicit val secondsDecoder: JsonValueCodec[FiniteDuration] = customDecoderFromUnsafe[FiniteDuration] { reader =>
    reader.readLong().seconds
  }

  private[jsoniter] implicit val instantCodec: JsonValueCodec[Instant] = customDecoderFromUnsafe[Instant] { reader =>
    Instant.ofEpochSecond(reader.readLong())
  }

  private[jsoniter] implicit val secretDecoder: JsonValueCodec[Secret[String]] = customDecoderFromUnsafe[Secret[String]] { reader =>
    Secret(reader.readString(default = null))
  }

  private[jsoniter] implicit val optionScopeDecoder: JsonValueCodec[Option[Scope]] = customDecoderWithDefault[Option[Scope]] { reader =>
    Try {
      reader.readString(default = null)
    }.flatMap {
      case ""    => Try(None)
      case value => Scope.of(value).toRight(JsonDecoder.Error(s"$value is not a valid $Scope")).toTry.map(Some(_))
    }
  }(None)

  private[jsoniter] implicit val scopeDecoder: JsonValueCodec[Scope] = customDecoderWithDefault[Scope] { reader =>
    Try {
      reader.readString(default = null)
    }.flatMap { value =>
      Scope.of(value).toRight(JsonDecoder.Error(s"$value is not a valid $Scope")).toTry
    }
  }(Scope.of("default").get)

  private val stringSequenceCodec: JsonValueCodec[List[String]] = JsonCodecMaker.make

  private[jsoniter] implicit val audienceDecoder: JsonValueCodec[Audience] = customDecoderTry[Audience] { jsonReader =>
    Try {
      jsonReader.setMark()
      StringAudience(jsonReader.readString(default = null))
    } orElse Try {
      jsonReader.rollbackToMark()
      SeqAudience(stringSequenceCodec.decodeValue(jsonReader, default = null))
    }
  }

  private val tokenDecoderWithoutTypeValidation: JsonValueCodec[AccessTokenResponse] =
    JsonCodecMaker.make(CodecMakerConfig.withFieldNameMapper(JsonCodecMaker.enforce_snake_case))

  implicit val tokenDecoderWithTypeValidation: JsonValueCodec[AccessTokenResponse] = customDecoderFromUnsafe[AccessTokenResponse] { in =>
    in.setMark()
    val tokenType = tokenTypeDecoder.decodeValue(in, tokenTypeDecoder.nullValue)
    if (tokenType.tokenType === "Bearer") {
      in.rollbackToMark()
      tokenDecoderWithoutTypeValidation.decodeValue(in, tokenDecoderWithoutTypeValidation.nullValue)
    } else {
      throw JsonDecoder.Error(s"Error while decoding '.token_type': value '$tokenType' is not equal to 'Bearer'")
    }
  }

  implicit val errorDecoder: JsonValueCodec[OAuth2Error] = customDecoderFromUnsafe[OAuth2Error] { in =>
    val IntermediateOAuth2Error(error, description) = oAuth2ErrorHelperDecoder.decodeValue(in, null)

    OAuth2Error.fromErrorTypeAndDescription(error, description)
  }

  implicit val userInfoDecoder: JsonValueCodec[UserInfo] =
    JsonCodecMaker.make(CodecMakerConfig.withFieldNameMapper(JsonCodecMaker.enforce_snake_case))

  implicit val tokenResponseDecoder: JsonValueCodec[OAuth2TokenResponse] =
    JsonCodecMaker.make(CodecMakerConfig.withFieldNameMapper(JsonCodecMaker.enforce_snake_case))

  implicit val tokenUserDetailsDecoder: JsonValueCodec[TokenUserDetails] =
    JsonCodecMaker.make(CodecMakerConfig.withFieldNameMapper(JsonCodecMaker.enforce_snake_case))

  implicit val extendedTokenResponseDecoder: JsonValueCodec[ExtendedOAuth2TokenResponse] =
    JsonCodecMaker.make(CodecMakerConfig.withFieldNameMapper(JsonCodecMaker.enforce_snake_case))

  implicit val tokenIntrospectionResponseDecoder: JsonValueCodec[TokenIntrospectionResponse] =
    JsonCodecMaker.make(CodecMakerConfig.withFieldNameMapper(JsonCodecMaker.enforce_snake_case))

  implicit val refreshTokenResponseDecoder: JsonValueCodec[RefreshTokenResponse] =
    JsonCodecMaker.make(CodecMakerConfig.withFieldNameMapper(JsonCodecMaker.enforce_snake_case))

  private def customDecoderFromUnsafe[A](
    read: JsonReader => A
  )(
    implicit toNull: Null <:< A
  ): JsonValueCodec[A] =
    customDecoderTry[A](reader => Try(read(reader)))

  private def customDecoderTry[A](
    read: JsonReader => Try[A]
  )(
    implicit toNull: Null <:< A
  ): JsonValueCodec[A] =
    customDecoderWithDefault[A](read)(toNull(null))

  private def customDecoderWithDefault[A](read: JsonReader => Try[A])(default: A) = new JsonValueCodec[A] {

    override def decodeValue(reader: JsonReader, default: A): A =
      read(reader).get

    override def encodeValue(x: A, out: JsonWriter): Unit = throw JsonDecoder.Error("Tried to encode a value using a decoder 🤷")

    override def nullValue: A = default
  }

}

object JsoniterJsonDecoders {

  private final case class TokenType(tokenType: String)

  private implicit val tokenTypeDecoder: JsonValueCodec[TokenType] =
    JsonCodecMaker.make(CodecMakerConfig.withFieldNameMapper(JsonCodecMaker.enforce_snake_case))

  private final case class IntermediateOAuth2Error(error: String, errorDescription: Option[String])

  private implicit val oAuth2ErrorHelperDecoder: JsonValueCodec[IntermediateOAuth2Error] =
    JsonCodecMaker.make(CodecMakerConfig.withFieldNameMapper(JsonCodecMaker.enforce_snake_case))

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy