Home > Net >  How to paginate external calls while deserialise a Trait with type parameters using Spray?
How to paginate external calls while deserialise a Trait with type parameters using Spray?

Time:10-08

I have objects which have similar structure like

case class Countries(countries: Array[Country])
case class Users(users: Array[User])
case class States(states: Array[States]) 

The above mentioned classes are used to populate data from external API call:

class GenericHttpDataPuller[U:JsonReader, V:JsonReader](val endPoint:String) {
  def getEntity[T:JsonReader](request: HttpRequest): Option[T] = {
      Try(httpClient.singleRequest(httpRequest).entity.parseJson.convertTo[T]) match 
     {
        case scala.util.Success(entityTry) => Some(entityTry)
        case scala.util.Failure(exception) => None
     }
  }

  def get(id:String, options: Map[String, String]): Option[U] = {
     val uri = Uri(s"/$endPoint/$Id").withQuery(Uri.Query(queryMap.getOrElse(Map.empty[String,String])))
     val httpRequest = HttpRequest(HttpMethods.GET,uri = uri,headers = additionalHeaders)
     getEntity[U](httpRequest)
  }

  def getList(options: Map[String, String]): Option[V] = {
     val uri = Uri(s"/$endPoint").withQuery(Uri.Query(queryMap.getOrElse(Map.empty[String,String])))
     val httpRequest = HttpRequest(HttpMethods.GET,uri = uri,headers = additionalHeaders)
     getEntity[V](httpRequest)
  }
}

Caller

type HttpUserDataPuller = GenericHttpDataPuller[User, Users]
val users = new HttpUserDataPuller("users"). getList(None)
val user = new HttpUserDataPuller("users").get("someId", None)

The above code works fine.

I would like make pagination calls for the getList method based on options parameter. My changes are not compatible with above mentioned code,

trait Pagination[V, U] {
  def getSize(): Int
  def getAppend(u: Option[U]): Option[U]
}

case class Users(users: Array[User]) extends Pagination[User, Users] {
   override def getSize(): Int = users.size
   override def getAppend(u: Option[Users]): Option[Users] = { // u is the return value from getEntity()
      val users = u.map(that => that.users    this.users).getOrElse(Array.empty[User])
      Some(Users(users)) }
}

getList method would look like below, using the getSize and getAppend methods I can figure wether to make a next call(skip when getSize != current_page_size), based on the return type of getEntity[PaginationAppender] when I do that my code breaks as I'm unable to deserialise a trait with type parameters using Spary.

  def getList(options: Map[String, String]): Option[PaginationAppender[U, V]] = {
     val limit = Try(queryMap.getOrElse(Map.empty[String, String]).getOrElse("limit", "-1").toInt).getOrElse(-1)
     val uri = Uri(s"/$endPoint").withQuery(Uri.Query(queryMap.getOrElse(Map.empty[String,String])))
     val httpRequest = HttpRequest(HttpMethods.GET,uri = uri,headers = additionalHeaders)
     val returnValue = getEntity[PaginationAppender[U, V]]

     def paginateData(currentPage: Option[PaginationAppender[U, V]]):Option[PaginationAppender[U, V]] = {
         //page size is same as current request return value then go for next page
         if(limit == currentPage.map(_.getSize()).getOrElse(0)) {
             val nextPage = getEntity[PaginationAppender[U, V]](updatedRequsestForNextPage)
             paginateData(currentPage.flatMap(now => now.append(nextPage)))
         } else {
              currentPage
         }
        paginateData(returnValue)
     }
    
  }

Is there any better work-around for this problem without making much changes to the existing structure.

Cheers!!!

CodePudding user response:

used a trait

trait PaginationAppender {
  type T
  def get(): Array[T]
}

implemented by user as

case class Users(users:Array[User]) extends PaginationAppender {
  override type T = User
  override def get(): Array[User] = this.users
}

paginate method

def paginateData[T:ClassTag, V <: PaginationAppender](httpUserDataPuller: GenericHttpDataPuller[T, V],
                                                        userId:String,
                                                        externalQueryMapOpt:Option[Map[String, String]] = None,
                                                        additionalHeaders:Option[Array[HttpHeader]] = None,
                                                        retry: Boolean = true,
                                                        limit: Int = 10,
                                                        offset: Int = 0
                                                       ): ArraySeq[V#T] = {
    val updatedExternalQueryMap = externalQueryMapOpt.getOrElse(Map.empty[String, String])   ("limit" -> limit.toString) // add -OR- update limit to existing options
    @tailrec
    def getData(makeNextCall:Boolean, currentOffset: Int, existingData:  ArraySeq[V#T] = ArraySeq.empty[V#T]): ArraySeq[V#T] = {
      makeNextCall match {
        case true =>
          val currentPage = httpUserDataPuller.list(Some(updatedExternalQueryMap   ("offset" -> currentOffset.toString)), // add -OR- update offset to existing options
            userId,
            additionalHeaders,
            retry).getOrElse(throw new IllegalArgumentException(s"Failed to list from generic pagination")).get()
          getData(currentPage.size == limit, currentOffset   limit, existingData    currentPage)
        case false => existingData
      }
    }
    getData(true, offset) //first time call
  }

caller

paginateData[User, Users](new HttpUserDataPuller("users"), "user")
  • Related