com.gu.contentapi.client.model.Queries.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of content-api-client_2.12 Show documentation
Show all versions of content-api-client_2.12 Show documentation
Scala client for the Guardian's Content API
The newest version!
package com.gu.contentapi.client.model
import com.gu.contentapi.client.Decoder.PageableResponseDecoder
import com.gu.contentapi.client.model.Direction.Next
import com.gu.contentapi.client.model.v1.{AtomUsageResponse, AtomsResponse, Content, EditionsResponse, ItemResponse, SearchResponse, SectionsResponse, Tag, TagsResponse, VideoStatsResponse}
import com.gu.contentapi.client.utils.QueryStringParams
import com.gu.contentapi.client.{Parameter, Parameters}
import com.gu.contentatom.thrift.AtomType
import com.twitter.scrooge.ThriftStruct
sealed trait ContentApiQuery[+Response <: ThriftStruct] {
def parameters: Map[String, String]
def pathSegment: String
override def toString = s"""${getClass.getSimpleName}(${getUrl("")})"""
private def url(location: String, parameters: Map[String, String]): String = {
require(!location.contains('?'), "must not specify parameters in URL")
location + QueryStringParams(parameters)
}
def getUrl(targetUrl: String, customParameters: Map[String, String] = Map.empty): String =
url(s"$targetUrl/$pathSegment", parameters ++ customParameters)
}
abstract class PaginatedApiQuery[Response <: ThriftStruct, Element](
implicit prd: PageableResponseDecoder[Response, Element]
) extends ContentApiQuery[Response] {
/**
* Produce a version of this query that explicitly sets previously ''unset'' pagination/ordering parameters,
* matching how the Content API server decided to process the previous request.
*
* For instance, if the Content API decided to process https://content.guardianapis.com/search?q=brexit
* with pageSize:10 & orderBy:relevance, that will have been detailed in the CAPI response - and therefore we
* can copy those parameters into our following query so we don't change how we're ordering the results
* as we paginate through them!
*/
def setPaginationConsistentWith(response: Response): PaginatedApiQuery[Response, Element]
def followingQueryGiven(response: Response, direction: Direction): Option[PaginatedApiQuery[Response, Element]] =
if (response.impliesNoFurtherResults) None else setPaginationConsistentWith(response).followingQueryGivenFull(response, direction)
/** Construct a query for the subsequent results after this response. This method will only be called if the
* response was supplied a full page of results, meaning that there's the possibility of more results to fetch.
*/
protected def followingQueryGivenFull(response: Response, direction: Direction): Option[PaginatedApiQuery[Response, Element]]
}
trait SearchQueryBase[Self <: SearchQueryBase[Self]]
extends ContentApiQuery[SearchResponse]
with ShowParameters[Self]
with ShowReferencesParameters[Self]
with OrderByParameter[Self]
with UseDateParameter[Self]
with PaginationParameters[Self]
with FilterParameters[Self]
with FilterExtendedParameters[Self]
with FilterSearchParameters[Self] {
this: Self =>
}
case class ItemQuery(id: String, parameterHolder: Map[String, Parameter] = Map.empty, channelId: Option[String]=None)
extends ContentApiQuery[ItemResponse]
with EditionParameters[ItemQuery]
with ShowParameters[ItemQuery]
with ShowReferencesParameters[ItemQuery]
with ShowExtendedParameters[ItemQuery]
with PaginationParameters[ItemQuery]
with OrderByParameter[ItemQuery]
with UseDateParameter[ItemQuery]
with FilterParameters[ItemQuery]
with FilterExtendedParameters[ItemQuery]
with FilterSearchParameters[ItemQuery] {
def withParameters(parameterMap: Map[String, Parameter]) = copy(id, parameterMap, channelId)
def withChannelId(newChannel:String) = copy(id, parameterHolder, Some(newChannel))
def withoutChannelId() = copy(id, parameterHolder, None)
def itemId(contentId: String): ItemQuery =
copy(id = contentId)
override def pathSegment: String = channelId match {
case None => id
case Some(chl) => s"channel/$chl/item/$id"
}
}
case class SearchQuery(parameterHolder: Map[String, Parameter] = Map.empty, channelId: Option[String] = None)
extends PaginatedApiQuery[SearchResponse, Content] with SearchQueryBase[SearchQuery] {
def setPaginationConsistentWith(response: SearchResponse): PaginatedApiQuery[SearchResponse, Content] =
pageSize.setIfUndefined(response.pageSize).orderBy.setIfUndefined(response.orderBy)
def withParameters(parameterMap: Map[String, Parameter]): SearchQuery = copy(parameterMap, channelId)
/**
* Make this search on a CAPI channel rather than against web-only content
* For more information about channels, and the reason why your app should only be in one channel,
* contact the Content API team
* @param channelId the channel to search against, or "all" to search across all channels.
*/
def withChannel(channelId:String):SearchQuery = copy(parameterHolder, Some(channelId))
def withoutChannel(): SearchQuery = copy(parameterHolder, None)
override def pathSegment: String = channelId match {
case None=>"search"
case Some(chnl)=>s"channel/$chnl/search"
}
protected override def followingQueryGivenFull(response: SearchResponse, direction: Direction) = for {
lastResultInResponse <- response.results.lastOption
} yield FollowingSearchQuery(this, lastResultInResponse.id, direction)
}
case class TagsQuery(parameterHolder: Map[String, Parameter] = Map.empty)
extends PaginatedApiQuery[TagsResponse, Tag]
with ShowReferencesParameters[TagsQuery]
with PaginationParameters[TagsQuery]
with FilterParameters[TagsQuery]
with FilterTagParameters[TagsQuery]
with FilterSearchParameters[TagsQuery] {
def setPaginationConsistentWith(response: TagsResponse): PaginatedApiQuery[TagsResponse, Tag] =
pageSize.setIfUndefined(response.pageSize)
def withParameters(parameterMap: Map[String, Parameter]) = copy(parameterMap)
override def pathSegment: String = "tags"
protected override def followingQueryGivenFull(response: TagsResponse, direction: Direction): Option[TagsQuery] = {
val followingPage = response.currentPage + direction.delta
if (followingPage >= 1 && followingPage <= response.pages) Some(page(followingPage)) else None
}
}
case class SectionsQuery(parameterHolder: Map[String, Parameter] = Map.empty)
extends ContentApiQuery[SectionsResponse]
with FilterSearchParameters[SectionsQuery]
with FilterSectionParameters[SectionsQuery]{
def withParameters(parameterMap: Map[String, Parameter]) = copy(parameterMap)
override def pathSegment: String = "sections"
}
case class EditionsQuery(parameterHolder: Map[String, Parameter] = Map.empty)
extends ContentApiQuery[EditionsResponse]
with FilterSearchParameters[EditionsQuery] {
def withParameters(parameterMap: Map[String, Parameter]) = copy(parameterMap)
override def pathSegment: String = "editions"
}
case class VideoStatsQuery(
edition: Option[String] = None,
section: Option[String] = None,
parameterHolder: Map[String, Parameter] = Map.empty)
extends ContentApiQuery[VideoStatsResponse]
with FilterSearchParameters[VideoStatsQuery] {
def withParameters(parameterMap: Map[String, Parameter]) = copy(edition, section, parameterMap)
override def pathSegment: String = Seq(Some("stats/videos"), edition, section).flatten.mkString("/")
}
case class AtomsQuery(parameterHolder: Map[String, Parameter] = Map.empty)
extends ContentApiQuery[AtomsResponse]
with AtomsParameters[AtomsQuery]
with PaginationParameters[AtomsQuery]
with UseDateParameter[AtomsQuery]
with OrderByParameter[AtomsQuery]
with FilterSearchParameters[AtomsQuery] {
def withParameters(parameterMap: Map[String, Parameter]) = copy(parameterMap)
override def pathSegment: String = "atoms"
}
case class AtomUsageQuery(atomType: AtomType, atomId: String, parameterHolder: Map[String, Parameter] = Map.empty)
extends ContentApiQuery[AtomUsageResponse]
with PaginationParameters[AtomUsageQuery] {
def withParameters(parameterMap: Map[String, Parameter]) = copy(parameterHolder = parameterMap)
override def pathSegment: String = s"atom/${atomType.toString.toLowerCase}/$atomId/usage"
}
@deprecated("Recipe atoms no longer exist and should not be relied upon. No data will be returned and this class will be removed in a future iteration of the library")
case class RecipesQuery(parameterHolder: Map[String, Parameter] = Map.empty)
extends ContentApiQuery[AtomsResponse]
with PaginationParameters[RecipesQuery]
with RecipeParameters[RecipesQuery] {
def withParameters(parameterMap: Map[String, Parameter]) = copy(parameterMap)
override def pathSegment: String = "atoms/recipes"
}
case class ReviewsQuery(parameterHolder: Map[String, Parameter] = Map.empty)
extends ContentApiQuery[AtomsResponse]
with PaginationParameters[ReviewsQuery]
with ReviewSpecificParameters[ReviewsQuery] {
def withParameters(parameterMap: Map[String, Parameter]) = copy(parameterMap)
override def pathSegment: String = "atoms/reviews"
}
case class GameReviewsQuery(parameterHolder: Map[String, Parameter] = Map.empty)
extends ContentApiQuery[AtomsResponse]
with ReviewSpecificParameters[GameReviewsQuery]
with PaginationParameters[GameReviewsQuery]
with GameParameters[GameReviewsQuery] {
def withParameters(parameterMap: Map[String, Parameter]) = copy(parameterMap)
override def pathSegment: String = "atoms/reviews/game"
}
case class RestaurantReviewsQuery(parameterHolder: Map[String, Parameter] = Map.empty)
extends ContentApiQuery[AtomsResponse]
with ReviewSpecificParameters[RestaurantReviewsQuery]
with PaginationParameters[RestaurantReviewsQuery]
with RestaurantParameters[RestaurantReviewsQuery] {
def withParameters(parameterMap: Map[String, Parameter]) = copy(parameterMap)
override def pathSegment: String = "atoms/reviews/restaurant"
}
case class FilmReviewsQuery(parameterHolder: Map[String, Parameter] = Map.empty)
extends ContentApiQuery[AtomsResponse]
with ReviewSpecificParameters[FilmReviewsQuery]
with PaginationParameters[FilmReviewsQuery]
with FilmParameters[FilmReviewsQuery] {
def withParameters(parameterMap: Map[String, Parameter]) = copy(parameterMap)
override def pathSegment: String = "atoms/reviews/film"
}
sealed trait Direction {
val pathSegment: String
val delta: Int
def guidingElementIn[T](elements: Iterable[T]): Option[T]
}
object Direction {
object Next extends Direction {
override val pathSegment: String = "next"
override val delta: Int = 1
override def guidingElementIn[T](elements: Iterable[T]): Option[T] = elements.lastOption
}
object Previous extends Direction {
override val pathSegment: String = "prev"
override val delta: Int = -1
override def guidingElementIn[T](elements: Iterable[T]): Option[T] = elements.headOption
}
def forPathSegment(pathSegment: String): Direction = pathSegment match {
case Next.pathSegment => Next
case Previous.pathSegment => Previous
}
}
case class FollowingSearchQuery(
originalQuery: PaginatedApiQuery[SearchResponse, Content], contentId: String, direction: Direction = Next
) extends PaginatedApiQuery[SearchResponse, Content] {
def parameters: Map[String, String] = originalQuery.parameters.filterKeys(not(isPaginationParameter)).toMap
override def pathSegment: String = s"content/$contentId/${direction.pathSegment}"
override def setPaginationConsistentWith(response: SearchResponse): PaginatedApiQuery[SearchResponse, Content] =
originalQuery.setPaginationConsistentWith(response)
protected override def followingQueryGivenFull(response: SearchResponse, updatedDirection: Direction): Option[PaginatedApiQuery[SearchResponse, Content]] = for {
content <- updatedDirection.guidingElementIn(response.results)
} yield copy(contentId = content.id, direction = updatedDirection)
}
trait EditionParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def edition = StringParameter("edition")
}
trait ShowParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def showFields = StringParameter("show-fields")
def showTags = StringParameter("show-tags")
def showElements = StringParameter("show-elements")
def showRights = StringParameter("show-rights")
def showBlocks = StringParameter("show-blocks")
def showAtoms = StringParameter("show-atoms")
def showSection = BoolParameter("show-section")
def showStats = BoolParameter("show-stats")
def showAliasPaths = BoolParameter("show-alias-paths")
def showSchemaOrg = BoolParameter("show-schemaorg")
def showChannels = StringParameter("show-channels")
}
trait ShowReferencesParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def showReferences = StringParameter("show-references")
}
trait ShowExtendedParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def showStoryPackage = BoolParameter("show-story-package")
def showRelated = BoolParameter("show-related")
def showMostViewed = BoolParameter("show-most-viewed")
def showEditorsPicks = BoolParameter("show-editors-picks")
def showPackages = BoolParameter("show-packages")
}
trait PaginationParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def page = IntParameter("page")
def pageSize = IntParameter("page-size")
}
trait OrderByParameter[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def orderBy = StringParameter("order-by")
}
trait UseDateParameter[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def useDate = StringParameter("use-date")
}
trait FilterParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def section = StringParameter("section")
def reference = StringParameter("reference")
def referenceType = StringParameter("reference-type")
def productionOffice = StringParameter("production-office")
}
trait FilterExtendedParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def tag = StringParameter("tag")
def ids = StringParameter("ids")
def paths = StringParameter("paths")
def rights = StringParameter("rights")
def leadContent = StringParameter("lead-content")
def fromDate = DateParameter("from-date")
def toDate = DateParameter("to-date")
def contentType = StringParameter("type")
def lang = StringParameter("lang")
def starRating = IntParameter("star-rating")
def membershipAccess = StringParameter("membership-access")
def containsElement = StringParameter("contains-element")
def commentable = BoolParameter("commentable")
def filename = StringParameter("filename")
}
trait FilterTagParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def tagType = StringParameter("type")
def sponsorshipType = StringParameter("sponsorship-type")
}
trait FilterSectionParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def sponsorshipType = StringParameter("sponsorship-type")
}
trait FilterSearchParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def q = StringParameter("q")
def queryFields = StringParameter("query-fields")
}
trait AtomsParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def types = StringParameter("types")
def searchFields = StringParameter("searchFields")
def fromDate = DateParameter("from-date")
def toDate = DateParameter("to-date")
}
trait RecipeParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def title = StringParameter("title")
def credits = StringParameter("credits")
def categories = StringParameter("category")
def cuisines = StringParameter("cuisine")
def dietary = StringParameter("dietary")
def celebration = StringParameter("celebration")
def ingredients = StringParameter("ingredients")
def maxTime = IntParameter("max-time")
}
trait ReviewSpecificParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def reviewer = StringParameter("reviewer")
def maxRating = IntParameter("max-rating")
def minRating = IntParameter("min-rating")
}
trait FilmParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def name = StringParameter("name")
def genres = StringParameter("genres")
def actors = StringParameter("actors")
def directors = StringParameter("directors")
}
trait GameParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def gameName = StringParameter("name")
}
trait RestaurantParameters[Owner <: Parameters[Owner]] extends Parameters[Owner] { this: Owner =>
def restaurantName = StringParameter("name")
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy