/** * Generated by API Builder - https://www.apibuilder.io * Service version: 0.0.1-dev * User agent: apibuilder app.apibuilder.io/shopr/clickouts-api/0.0.1-dev/http4s_0_15 */ package com.shopr.api.clickouts.api.v0.models { /** * Represents an Offer click-out. It contains all the information needed to * properly identify the offer, trace it ot the user navigating from search page, * and relevant information about the offer at the click-out moment (e.g.: price, * lowest price among other offers, etc.) * * @param productId Product identifier * @param seller Seller/Retailer unique identifier * @param sellerProductId Product as identifier given by the seller/retailer * @param price Offer price at the moment of the click-out * @param deepUrl Full product URL at the retailer side * @param lowestPrice Lowest price among all offers * @param highestPrice Seller/Retailer unique identifier * @param offerCount Number of offers * @param offerPosition Offer position among other offers (e.g.: first, second, etc.) * @param categoryId Category where the product was put into * @param tracking Tracking information * @param creationTime When this click-out happened */ final case class Clickout( productId: String, seller: String, sellerProductId: String, price: BigDecimal, deepUrl: String, lowestPrice: BigDecimal, highestPrice: BigDecimal, offerCount: Int = 1, offerPosition: Int, categoryId: String, tracking: com.shopr.api.clickouts.api.v0.models.Tracking, creationTime: _root_.java.time.Instant ) /** * @param code Machine readable code for this specific error message * @param localizedStringCode A localized description of the error code */ final case class ErrorCode( code: String, localizedStringCode: String ) /** * @param message User-friendly message. * @param errorCodes List of errors that may happened all together. * @param fingerprint The request fingerprint associated with this error. */ final case class ErrorResponse( message: _root_.scala.Option[String] = None, errorCodes: Seq[com.shopr.api.clickouts.api.v0.models.ErrorCode], fingerprint: _root_.scala.Option[String] = None ) /** * Response for ping, health check and version */ final case class Status( response: String ) /** * Series of attributes for tracking purposes * * @param origin Client originating the order request essentially any of the values of the * client_origin enum. * @param referrer Optional page referrer. */ final case class Tracking( ipAddress: String, origin: com.shopr.api.clickouts.api.v0.models.ClientOrigin, userAgent: _root_.scala.Option[String] = None, referrer: _root_.scala.Option[String] = None ) /** * Describes the client where the request for a clickout has originated. */ sealed trait ClientOrigin extends _root_.scala.Product with _root_.scala.Serializable object ClientOrigin { /** * The Shopr website */ case object ShoprWebsite extends ClientOrigin { override def toString = "shopr_website" } /** * The Shopr website, but from a mobile. */ case object ShoprMobileWeb extends ClientOrigin { override def toString = "shopr_mobile_web" } /** * IPhone native application. */ case object ShoprIphone extends ClientOrigin { override def toString = "shopr_iphone" } /** * Native application running on an IPad. */ case object ShoprIpad extends ClientOrigin { override def toString = "shopr_ipad" } /** * Android native application. */ case object ShoprAndroid extends ClientOrigin { override def toString = "shopr_android" } /** * UNDEFINED captures values that are sent either in error or * that were added by the server after this library was * generated. We want to make it easy and obvious to users of * this library to handle this case gracefully. * * We use all CAPS for the variable name to avoid collisions * with the camel cased values above. */ final case class UNDEFINED(override val toString: String) extends ClientOrigin /** * all returns a list of all the valid, known values. We use * lower case to avoid collisions with the camel cased values * above. */ val all: scala.List[ClientOrigin] = scala.List(ShoprWebsite, ShoprMobileWeb, ShoprIphone, ShoprIpad, ShoprAndroid) private val byName: Map[String, ClientOrigin] = all.map(x => x.toString.toLowerCase -> x).toMap def apply(value: String): ClientOrigin = fromString(value).getOrElse(UNDEFINED(value)) def fromString(value: String): _root_.scala.Option[ClientOrigin] = byName.get(value.toLowerCase) } } package com.shopr.api.clickouts.api.v0.models { package object json { import io.circe.Decoder._ import io.circe.Encoder._ import scala.language.implicitConversions // See below - Make Scala 2.11 Either monadic import scala.util.Try import io.circe.{Json, JsonObject, Encoder, Decoder, DecodingFailure} import io.circe.syntax._ import com.shopr.api.clickouts.api.v0.models.json._ // Make Scala 2.11 Either monadic private[v0] implicit def eitherOps[A,B](e: Either[A,B]) = cats.implicits.catsSyntaxEither(e) private[v0] implicit val decodeUUID: Decoder[_root_.java.util.UUID] = Decoder.decodeString.emapTry(str => Try(_root_.java.util.UUID.fromString(str))) private[v0] implicit val encodeUUID: Encoder[_root_.java.util.UUID] = Encoder.encodeString.contramap[_root_.java.util.UUID](uuid => uuid.toString) private[v0] implicit val decodeInstant: Decoder[_root_.java.time.Instant] = Decoder.decodeString.emapTry(str => Try(_root_.java.time.OffsetDateTime.parse(str).toInstant)) private[v0] implicit val encodeInstant: Encoder[_root_.java.time.Instant] = Encoder.encodeString.contramap[_root_.java.time.Instant](_.toString) private[v0] implicit val decodeLocalDate: Decoder[_root_.java.time.LocalDate] = Decoder.decodeString.emapTry(str => Try(_root_.java.time.LocalDate.parse(str))) private[v0] implicit val encodeLocalDate: Encoder[_root_.java.time.LocalDate] = Encoder.encodeString.contramap[_root_.java.time.LocalDate](_.toString) implicit val jsonDecoderClickoutsApiClientOrigin: Decoder[com.shopr.api.clickouts.api.v0.models.ClientOrigin] = Decoder.decodeString.map(com.shopr.api.clickouts.api.v0.models.ClientOrigin(_)) implicit val jsonEncoderClickoutsApiClientOrigin: Encoder[com.shopr.api.clickouts.api.v0.models.ClientOrigin] = Encoder.encodeString.contramap[com.shopr.api.clickouts.api.v0.models.ClientOrigin](_.toString) implicit def decodeClickoutsApiClickout: Decoder[Clickout] = Decoder.instance { c => for { productId <- c.downField("productId").as[String] seller <- c.downField("seller").as[String] sellerProductId <- c.downField("sellerProductId").as[String] price <- c.downField("price").as[BigDecimal] deepUrl <- c.downField("deepUrl").as[String] lowestPrice <- c.downField("lowestPrice").as[BigDecimal] highestPrice <- c.downField("highestPrice").as[BigDecimal] offerCount <- c.downField("offerCount").as[Int] offerPosition <- c.downField("offerPosition").as[Int] categoryId <- c.downField("categoryId").as[String] tracking <- c.downField("tracking").as[com.shopr.api.clickouts.api.v0.models.Tracking] creationTime <- c.downField("creationTime").as[_root_.java.time.Instant] } yield { Clickout( productId = productId, seller = seller, sellerProductId = sellerProductId, price = price, deepUrl = deepUrl, lowestPrice = lowestPrice, highestPrice = highestPrice, offerCount = offerCount, offerPosition = offerPosition, categoryId = categoryId, tracking = tracking, creationTime = creationTime ) } } implicit def encodeClickoutsApiClickout: Encoder[Clickout] = Encoder.instance { t => Json.fromFields(Seq( Some("productId" -> t.productId.asJson), Some("seller" -> t.seller.asJson), Some("sellerProductId" -> t.sellerProductId.asJson), Some("price" -> t.price.asJson), Some("deepUrl" -> t.deepUrl.asJson), Some("lowestPrice" -> t.lowestPrice.asJson), Some("highestPrice" -> t.highestPrice.asJson), Some("offerCount" -> t.offerCount.asJson), Some("offerPosition" -> t.offerPosition.asJson), Some("categoryId" -> t.categoryId.asJson), Some("tracking" -> t.tracking.asJson), Some("creationTime" -> t.creationTime.asJson) ).flatten) } implicit def decodeClickoutsApiErrorCode: Decoder[ErrorCode] = Decoder.instance { c => for { code <- c.downField("code").as[String] localizedStringCode <- c.downField("localized_string_code").as[String] } yield { ErrorCode( code = code, localizedStringCode = localizedStringCode ) } } implicit def encodeClickoutsApiErrorCode: Encoder[ErrorCode] = Encoder.instance { t => Json.fromFields(Seq( Some("code" -> t.code.asJson), Some("localized_string_code" -> t.localizedStringCode.asJson) ).flatten) } implicit def decodeClickoutsApiErrorResponse: Decoder[ErrorResponse] = Decoder.instance { c => for { message <- c.downField("message").as[Option[String]] errorCodes <- c.downField("error_codes").as[Seq[com.shopr.api.clickouts.api.v0.models.ErrorCode]] fingerprint <- c.downField("fingerprint").as[Option[String]] } yield { ErrorResponse( message = message, errorCodes = errorCodes, fingerprint = fingerprint ) } } implicit def encodeClickoutsApiErrorResponse: Encoder[ErrorResponse] = Encoder.instance { t => Json.fromFields(Seq( t.message.map(t => "message" -> t.asJson), Some("error_codes" -> t.errorCodes.asJson), t.fingerprint.map(t => "fingerprint" -> t.asJson) ).flatten) } implicit def decodeClickoutsApiStatus: Decoder[Status] = Decoder.instance { c => for { response <- c.downField("response").as[String] } yield { Status( response = response ) } } implicit def encodeClickoutsApiStatus: Encoder[Status] = Encoder.instance { t => Json.fromFields(Seq( Some("response" -> t.response.asJson) ).flatten) } implicit def decodeClickoutsApiTracking: Decoder[Tracking] = Decoder.instance { c => for { ipAddress <- c.downField("ipAddress").as[String] origin <- c.downField("origin").as[com.shopr.api.clickouts.api.v0.models.ClientOrigin] userAgent <- c.downField("userAgent").as[Option[String]] referrer <- c.downField("referrer").as[Option[String]] } yield { Tracking( ipAddress = ipAddress, origin = origin, userAgent = userAgent, referrer = referrer ) } } implicit def encodeClickoutsApiTracking: Encoder[Tracking] = Encoder.instance { t => Json.fromFields(Seq( Some("ipAddress" -> t.ipAddress.asJson), Some("origin" -> t.origin.asJson), t.userAgent.map(t => "userAgent" -> t.asJson), t.referrer.map(t => "referrer" -> t.asJson) ).flatten) } } } package com.shopr.api.clickouts.api.v0 { import org.http4s.client.blaze._ import io.circe.syntax._ object Constants { val BaseUrl = "http://api.shopr.nl/stats" val Namespace = "com.shopr.api.clickouts.api.v0" val UserAgent = "apibuilder app.apibuilder.io/shopr/clickouts-api/0.0.1-dev/http4s_0_15" val Version = "0.0.1-dev" val VersionMajor = 0 } class Client( val baseUrl: org.http4s.Uri = org.http4s.Uri.unsafeFromString("http://api.shopr.nl/stats"), auth: scala.Option[com.shopr.api.clickouts.api.v0.Authorization] = None, defaultHeaders: Seq[(String, String)] = Nil, asyncHttpClient: org.http4s.client.Client = Client.defaultAsyncHttpClient ) extends interfaces.Client { import org.http4s.Response import com.shopr.api.clickouts.api.v0.models.json._ def closeAsyncHttpClient(): Unit = { asyncHttpClient.shutdownNow() } def clickouts: Clickouts = Clickouts def statuses: Statuses = Statuses object Clickouts extends Clickouts { override def post( fingerprint: String, clickout: com.shopr.api.clickouts.api.v0.models.Clickout, requestHeaders: Seq[(String, String)] = Nil ): scalaz.concurrent.Task[String] = { val urlPath = Seq(fingerprint, "clickouts") val (payload, formPayload) = (Some(clickout), None) _executeRequest[com.shopr.api.clickouts.api.v0.models.Clickout, String]("POST", path = urlPath, body = payload, formBody = formPayload, requestHeaders = requestHeaders) { case r if r.status.code == 202 => _root_.com.shopr.api.clickouts.api.v0.Client.parseJson[String]("String", r) case r if r.status.code == 422 => scalaz.concurrent.Task.fail(new com.shopr.api.clickouts.api.v0.errors.ErrorResponsesResponse(r)) case r => scalaz.concurrent.Task.fail(new com.shopr.api.clickouts.api.v0.errors.FailedRequest(r.status.code, s"Unsupported response code[${r.status.code}]. Expected: 202, 422")) } } } object Statuses extends Statuses { override def getPing( requestHeaders: Seq[(String, String)] = Nil ): scalaz.concurrent.Task[com.shopr.api.clickouts.api.v0.models.Status] = { val urlPath = Seq("_internal_", "ping") _executeRequest[Unit, com.shopr.api.clickouts.api.v0.models.Status]("GET", path = urlPath, requestHeaders = requestHeaders) { case r if r.status.code == 200 => _root_.com.shopr.api.clickouts.api.v0.Client.parseJson[com.shopr.api.clickouts.api.v0.models.Status]("com.shopr.api.clickouts.api.v0.models.Status", r) case r => scalaz.concurrent.Task.fail(new com.shopr.api.clickouts.api.v0.errors.FailedRequest(r.status.code, s"Unsupported response code[${r.status.code}]. Expected: 200")) } } override def getStatus( requestHeaders: Seq[(String, String)] = Nil ): scalaz.concurrent.Task[com.shopr.api.clickouts.api.v0.models.Status] = { val urlPath = Seq("_internal_", "status") _executeRequest[Unit, com.shopr.api.clickouts.api.v0.models.Status]("GET", path = urlPath, requestHeaders = requestHeaders) { case r if r.status.code == 200 => _root_.com.shopr.api.clickouts.api.v0.Client.parseJson[com.shopr.api.clickouts.api.v0.models.Status]("com.shopr.api.clickouts.api.v0.models.Status", r) case r => scalaz.concurrent.Task.fail(new com.shopr.api.clickouts.api.v0.errors.FailedRequest(r.status.code, s"Unsupported response code[${r.status.code}]. Expected: 200")) } } override def getVersion( requestHeaders: Seq[(String, String)] = Nil ): scalaz.concurrent.Task[String] = { val urlPath = Seq("_internal_", "version") _executeRequest[Unit, String]("GET", path = urlPath, requestHeaders = requestHeaders) { case r if r.status.code == 200 => _root_.com.shopr.api.clickouts.api.v0.Client.parseJson[String]("String", r) case r => scalaz.concurrent.Task.fail(new com.shopr.api.clickouts.api.v0.errors.FailedRequest(r.status.code, s"Unsupported response code[${r.status.code}]. Expected: 200")) } } } private lazy val defaultApiHeaders = Seq( ("User-Agent", Constants.UserAgent), ("X-Apidoc-Version", Constants.Version), ("X-Apidoc-Version-Major", Constants.VersionMajor.toString) ) def apiHeaders: Seq[(String, String)] = defaultApiHeaders def modifyRequest(request: scalaz.concurrent.Task[org.http4s.Request]): scalaz.concurrent.Task[org.http4s.Request] = request implicit def circeJsonEncoder[A](implicit encoder: io.circe.Encoder[A]) = org.http4s.circe.jsonEncoderOf[A] def _executeRequest[T, U]( method: String, path: Seq[String], queryParameters: Seq[(String, String)] = Nil, requestHeaders: Seq[(String, String)] = Nil, body: Option[T] = None, formBody : Option[org.http4s.UrlForm] = None )(handler: org.http4s.Response => scalaz.concurrent.Task[U] )(implicit encoder: io.circe.Encoder[T]): scalaz.concurrent.Task[U] = { import org.http4s.QueryParamEncoder._ val m = org.http4s.Method.fromString(method) match { case scalaz.\/-(m) => m case scalaz.-\/(e) => sys.error(e.toString) } val headers = org.http4s.Headers(( apiHeaders ++ defaultHeaders ++ requestHeaders ).groupBy(_._1).map { case (k, l) => org.http4s.Header(k, l.last._2) }.toList) val queryMap = queryParameters.groupBy(_._1).map { case (k, v) => k -> v.map(_._2) } val uri = path.foldLeft(baseUrl){ case (uri, segment) => uri / segment }.setQueryParams(queryMap) val request = org.http4s.Request(method = m, uri = uri, headers = headers) val reqAndMaybeAuth = auth.fold(request) { case Authorization.Basic(username, passwordOpt) => { val userpass = s"$username:${passwordOpt.getOrElse("")}" val token = java.util.Base64.getEncoder.encodeToString(userpass.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1)) request.putHeaders(org.http4s.Header("Authorization", s"Basic $token")) } case a => sys.error("Invalid authorization scheme[" + a.getClass + "]") } val reqAndMaybeAuthAndBody = if (formBody.nonEmpty) formBody.fold(scalaz.concurrent.Task.now(reqAndMaybeAuth))(reqAndMaybeAuth.withBody) else body.fold(scalaz.concurrent.Task.now(reqAndMaybeAuth))(reqAndMaybeAuth.withBody) asyncHttpClient.fetch(modifyRequest(reqAndMaybeAuthAndBody))(handler) } } object Client { implicit def circeJsonDecoder[A](implicit decoder: io.circe.Decoder[A]) = org.http4s.circe.jsonOf[A] private lazy val defaultAsyncHttpClient = PooledHttp1Client() def parseJson[T]( className: String, r: org.http4s.Response )(implicit decoder: io.circe.Decoder[T]): scalaz.concurrent.Task[T] = r.attemptAs[T].run.flatMap { case scalaz.\/-(value) => scalaz.concurrent.Task.now(value) case scalaz.-\/(error) => scalaz.concurrent.Task.fail(new com.shopr.api.clickouts.api.v0.errors.FailedRequest(r.status.code, s"Invalid json for class[" + className + "]", None, error)) } } sealed trait Authorization extends _root_.scala.Product with _root_.scala.Serializable object Authorization { final case class Basic(username: String, password: Option[String] = None) extends Authorization } package interfaces { trait Client { def baseUrl: org.http4s.Uri def clickouts: com.shopr.api.clickouts.api.v0.Clickouts def statuses: com.shopr.api.clickouts.api.v0.Statuses } } trait Clickouts { /** * Create a new click-out. Requests are accepted for eventual processing. * * @param fingerprint User fingerprint used for correlation purposes. */ def post( fingerprint: String, clickout: com.shopr.api.clickouts.api.v0.models.Clickout, requestHeaders: Seq[(String, String)] = Nil ): scalaz.concurrent.Task[String] } trait Statuses { /** * Ping in milliseconds. */ def getPing( requestHeaders: Seq[(String, String)] = Nil ): scalaz.concurrent.Task[com.shopr.api.clickouts.api.v0.models.Status] /** * Current health check status. */ def getStatus( requestHeaders: Seq[(String, String)] = Nil ): scalaz.concurrent.Task[com.shopr.api.clickouts.api.v0.models.Status] /** * API version. */ def getVersion( requestHeaders: Seq[(String, String)] = Nil ): scalaz.concurrent.Task[String] } package errors { import com.shopr.api.clickouts.api.v0.models.json._ final case class ErrorResponsesResponse( response: org.http4s.Response, message: Option[String] = None ) extends Exception(message.getOrElse(s"${response.status.code}: ${response.body}")) { lazy val errorResponses = _root_.com.shopr.api.clickouts.api.v0.Client.parseJson[Seq[com.shopr.api.clickouts.api.v0.models.ErrorResponse]]("Seq[com.shopr.api.clickouts.api.v0.models.ErrorResponse]", response) } final case class FailedRequest(responseCode: Int, message: String, requestUri: Option[_root_.java.net.URI] = None, parent: Exception = null) extends _root_.java.lang.Exception(s"HTTP $responseCode: $message", parent) } }