Refactored actions out into separate files.
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -6,7 +6,7 @@
|
||||
<name>Zappy</name>
|
||||
<artifactId>zappy</artifactId>
|
||||
<groupId>dstu.zappy</groupId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
<version>0.3.0-SNAPSHOT</version>
|
||||
<inceptionYear>2012</inceptionYear>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -1,418 +0,0 @@
|
||||
package dstu.zappy
|
||||
|
||||
import com.lambdaworks.jacks.JacksMapper
|
||||
import com.sun.jersey.api.client.Client
|
||||
import com.sun.jersey.core.util.MultivaluedMapImpl
|
||||
import javax.ws.rs.core.{MediaType, MultivaluedMap}
|
||||
|
||||
/**
|
||||
* Singleton container for API resources.
|
||||
*/
|
||||
object Zappos {
|
||||
val BASE_URL = "http://api.zappos.com/"
|
||||
lazy val client = Client.create
|
||||
lazy val resource = client.resource(BASE_URL)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parent class of all API actions.
|
||||
*
|
||||
* @constructor builds a new action
|
||||
* @param path the additional path component for the specific action (like "Search" or "Product"). This corresponds to an action in the main Zappos API.
|
||||
* @param key the API key to send with the action
|
||||
*/
|
||||
abstract class Base[T](path: String, key: String) {
|
||||
type Response = T
|
||||
|
||||
/**
|
||||
* Provides type conversion from the Scala Map trait to the Jersey MultivaluedMap interface.
|
||||
*/
|
||||
private implicit def mapAsMultivaluedMap(m: Map[String, String]): MultivaluedMap[String, String] =
|
||||
m.foldLeft(new MultivaluedMapImpl)((result, entry) => {
|
||||
result.add(entry._1, entry._2)
|
||||
result
|
||||
})
|
||||
|
||||
/**
|
||||
* The parameter map for the action. This specifies the
|
||||
parameters of the GET request that the action will
|
||||
send. Subclasses should override this method and fill in
|
||||
their own fields.
|
||||
*
|
||||
* @return the parameters for the action's GET request
|
||||
*/
|
||||
def parameters: Map[String, String]
|
||||
|
||||
/**
|
||||
* Hydrates a Response object from raw JSON. Subclasses may
|
||||
override this method and fill in their own implementation.
|
||||
* @param json the raw JSON
|
||||
* @return a newly hydrated Response object
|
||||
*/
|
||||
def decodeResponse(s: String)(implicit m: Manifest[Response]): Response = JacksMapper.readValue[Response](s)
|
||||
|
||||
/**
|
||||
* Makes the call to the Zappos API.
|
||||
* @return the raw JSON of the API response
|
||||
*/
|
||||
def getJson(): String =
|
||||
Zappos.resource.path(path).queryParams(parameters + Pair("key", key)).accept(MediaType.APPLICATION_JSON_TYPE).get(classOf[java.lang.String])
|
||||
|
||||
/**
|
||||
* Makes the call to the Zappos API and decodes it.
|
||||
* @return the response, decoded
|
||||
*/
|
||||
def get()(implicit m: Manifest[Response]): Response = decodeResponse(getJson)
|
||||
}
|
||||
|
||||
case class SearchResponse(statusCode: Int,
|
||||
currentResultCount: Option[Int],
|
||||
totalResultCount: Option[Int],
|
||||
limit: Option[Int],
|
||||
currentPage: Option[Int],
|
||||
pageCount: Option[Int],
|
||||
term: String,
|
||||
originalTerm: String,
|
||||
results: List[SearchResult])
|
||||
|
||||
case class SearchResult(styleId: Int,
|
||||
productId: Int,
|
||||
colorId: String,
|
||||
brandName: String,
|
||||
productUrl: String,
|
||||
thumbnailImageUrl: String,
|
||||
price: String,
|
||||
productName: String,
|
||||
originalPrice: String,
|
||||
percentOff: String,
|
||||
description: String,
|
||||
videoUrl: String,
|
||||
videoFileName: String,
|
||||
videoUploadedDate: String,
|
||||
productRating: String,
|
||||
brandId: String,
|
||||
categoryFacet: String,
|
||||
heelHeight: String,
|
||||
subCategoryFacet: String,
|
||||
gender: String)
|
||||
|
||||
/**
|
||||
* A search action with the Zappos API. An API key and search
|
||||
* term must be specified. Other parameters may be specified by
|
||||
* setter methods.
|
||||
*
|
||||
* Instances of this class are immutable; setter methods create
|
||||
* new instances.
|
||||
*/
|
||||
case class Search(key: String,
|
||||
term: String,
|
||||
includes: Set[String] = Set.empty,
|
||||
excludes: Set[String] = Set.empty,
|
||||
facets: Set[String] = Set.empty,
|
||||
facetFilters: Map[String, Set[String]] = Map.empty,
|
||||
limit: Option[Int] = None,
|
||||
page: Option[Int] = None) extends Base[SearchResponse]("Search", key) {
|
||||
private def include(item: String) = copy(includes = includes + item, excludes = excludes - item)
|
||||
private def exclude(item: String) = copy(includes = includes - item, excludes = excludes + item)
|
||||
|
||||
def withSearchTerm(s: String) = copy(term = s)
|
||||
|
||||
/**
|
||||
* Sets the maximum number of items to return.
|
||||
* @param i the desired maximum number of items
|
||||
* @return a new Search object with the item limit set to the desired value
|
||||
*/
|
||||
def withLimit(i: Int) = copy(limit = Some(i))
|
||||
/**
|
||||
* Unsets the maximum number of items to return. (The default behavior of the Zappos API will be used instead.)
|
||||
* @return a new Search object with the item limit unset
|
||||
*/
|
||||
def withoutLimit = copy(limit = None)
|
||||
|
||||
/**
|
||||
* Sets the page of results to return.
|
||||
* @param i the desired page
|
||||
* @return a new Search object with the page set to the desired value
|
||||
*/
|
||||
def withPage(i: Int) = copy(page = Some(i))
|
||||
/**
|
||||
* Unsets the page to return. (The default behavior of the Zappos API will be used instead.)
|
||||
* @return a new Search object with the page unset
|
||||
*/
|
||||
def withoutPage = copy(page = None)
|
||||
|
||||
/**
|
||||
* Adds a facet filter, filtering for items that have one of
|
||||
the given values in the given facet. This replaces any
|
||||
existing filter on the given facet. Multiple filters may be
|
||||
specified; they are logically AND'ed by the Zappos API.
|
||||
|
||||
* @param facet the facet to filter on
|
||||
* @param values the values to filter for
|
||||
* @return a new Search object with the new facet filter
|
||||
*/
|
||||
def withFacetFilter(facet: String, values: Set[String]) = copy(facetFilters = facetFilters + Pair(facet, values))
|
||||
/**
|
||||
* Removes all filters on the given facet.
|
||||
* @param facet the facet for which to remove filters
|
||||
* @return a new Search object without the given facet filter
|
||||
*/
|
||||
def withoutFacetFilter(facet: String) = copy(facetFilters = facetFilters - facet)
|
||||
/**
|
||||
* Removes all facet filters.
|
||||
* @return a new Search object without any facet filters
|
||||
*/
|
||||
def withoutFacetFilters = copy(facetFilters = Map.empty)
|
||||
|
||||
def withStyleId = include("styleId")
|
||||
def withoutStyleId = exclude("styleId")
|
||||
def withProductId = include("productId")
|
||||
def withoutProductId = exclude("productId")
|
||||
def withColorId = include("colorId")
|
||||
def withoutColorId = exclude("colorId")
|
||||
def withBrandName = include("withBrandName")
|
||||
def withoutBrandName = exclude("withBrandName")
|
||||
def withProductName = include("productName")
|
||||
def withoutProductName = exclude("productName")
|
||||
def withProductUrl = include("productUrl")
|
||||
def withoutProductUrl = exclude("productUrl")
|
||||
def withThumbnailImageUrl = include("thumbnailImageUrl")
|
||||
def withoutThumbnailImageUrl = exclude("thumbnailImageUrl")
|
||||
def withPrice = include("price")
|
||||
def withoutPrice = exclude("price")
|
||||
def withOriginalPrice = include("originalPrice")
|
||||
def withoutOriginalPrice = exclude("originalPrice")
|
||||
def withPercentOff = include("percentOff")
|
||||
def withoutPercentOff = exclude("percentOff")
|
||||
def withDescription = include("description")
|
||||
def withoutDescription = exclude("description")
|
||||
def withVideoUrl = include("videoUrl")
|
||||
def withoutVideoUrl = exclude("videoUrl")
|
||||
def withVideoFileName = include("videoFileName")
|
||||
def withoutVideoFileName = exclude("videoFileName")
|
||||
def withVideoUploadedDate = include("videoUploadedDate")
|
||||
def withoutVideoUploadedDate = exclude("videoUploadedDate")
|
||||
def withProductRating = include("productRating")
|
||||
def withoutProductRating = exclude("productRating")
|
||||
def withBrandId = include("brandId")
|
||||
def withoutBrandId = exclude("brandId")
|
||||
def withCategoryFacet = include("categoryFacet")
|
||||
def withoutCategoryFacet = exclude("categoryFacet")
|
||||
def withHeelHeight = include("heelHeight")
|
||||
def withoutHeelHeight = exclude("heelHeight")
|
||||
def withSubCategoryFacet = include("subCategoryFacet")
|
||||
def withoutSubCategoryFacet = exclude("subCategoryFacet")
|
||||
def withGender = include("txAttrFacet_Gender")
|
||||
def withoutGender = exclude("txAttrFacet_Gender")
|
||||
def withTotalResultCount = include("totalResultCount")
|
||||
def withoutTotalResultCount = exclude("totalResultCount")
|
||||
def withFacets = include("facets")
|
||||
def withoutFacets = exclude("facets")
|
||||
|
||||
override def parameters =
|
||||
Map("term" -> term) ++
|
||||
includes.map(i => Pair("includes", JacksMapper.writeValueAsString(i))).toMap ++
|
||||
excludes.map(e => Pair("excludes", JacksMapper.writeValueAsString(e))).toMap ++
|
||||
facets.map(f => Pair("facets", JacksMapper.writeValueAsString(f))).toMap ++
|
||||
(if (facetFilters.isEmpty) Map.empty
|
||||
else Map("filters" -> JacksMapper.writeValueAsString(facetFilters))) ++
|
||||
limit.map(i => Pair("limit", i.toString)).toMap ++
|
||||
page.map(i => Pair("page", i.toString)).toMap
|
||||
}
|
||||
|
||||
case class ImageResponse(statusCode: Int,
|
||||
productId: String,
|
||||
images: Map[String, List[ImageResult]])
|
||||
|
||||
case class ImageResult(styleId: String,
|
||||
productId: String,
|
||||
`type`: String,
|
||||
recipeName: String,
|
||||
format: String,
|
||||
filename: String,
|
||||
colorId: String,
|
||||
width: Option[Int],
|
||||
height: Option[Int],
|
||||
uploadDate: String,
|
||||
isHighResolution: Boolean,
|
||||
tiles: String)
|
||||
|
||||
/**
|
||||
* An image query. An API key and product ID must be
|
||||
specified. Other parameters may be specified by setter
|
||||
methods.
|
||||
*
|
||||
* Instances of this class are immutable; setter methods create
|
||||
* new instances.
|
||||
*/
|
||||
case class Image(key: String, productId: String, includes: Set[String] = Set.empty, excludes: Set[String] = Set.empty) extends Base[ImageResponse]("Image", key) {
|
||||
def withStyleId = copy(includes = includes + "styleId", excludes = excludes - "styleId")
|
||||
def withoutStyleId = copy(includes = includes - "styleId", excludes = excludes + "styleId")
|
||||
def withProductId = copy(includes = includes + "productId", excludes = excludes - "productId")
|
||||
def withoutProductId = copy(includes = includes - "productId", excludes = excludes + "productId")
|
||||
def withType = copy(includes = includes + "type", excludes = excludes - "type")
|
||||
def withoutType = copy(includes = includes - "type", excludes = excludes + "type")
|
||||
def withRecipeName = copy(includes = includes + "recipeName", excludes = excludes - "recipeName")
|
||||
def withoutRecipeName = copy(includes = includes - "recipeName", excludes = excludes + "recipeName")
|
||||
def withFormat = copy(includes = includes + "format", excludes = excludes - "format")
|
||||
def withoutFormat = copy(includes = includes - "format", excludes = excludes + "format")
|
||||
def withFilename = copy(includes = includes + "filename", excludes = excludes - "filename")
|
||||
def withoutFilename = copy(includes = includes - "filename", excludes = excludes + "filename")
|
||||
def withColorId = copy(includes = includes + "colorId", excludes = excludes - "colorId")
|
||||
def withoutColorId = copy(includes = includes - "colorId", excludes = excludes + "colorId")
|
||||
def withWidth = copy(includes = includes + "width", excludes = excludes - "width")
|
||||
def withoutWidth = copy(includes = includes - "width", excludes = excludes + "width")
|
||||
def withHeight = copy(includes = includes + "height", excludes = excludes - "height")
|
||||
def withoutHeight = copy(includes = includes - "height", excludes = excludes + "height")
|
||||
def withUploadDate = copy(includes = includes + "uploadDate", excludes = excludes - "uploadDate")
|
||||
def withoutUploadDate = copy(includes = includes - "uploadDate", excludes = excludes + "uploadDate")
|
||||
def withHighResolution = copy(includes = includes + "isHighResolution", excludes = excludes - "isHighResolution")
|
||||
def withoutHighResolution = copy(includes = includes - "isHighResolution", excludes = excludes + "isHighResolution")
|
||||
def withTiles = copy(includes = includes + "tiles", excludes = excludes - "tiles")
|
||||
def withoutTiles = copy(includes = includes - "tiles", excludes = excludes + "tiles")
|
||||
|
||||
override def parameters =
|
||||
Map("productId" -> productId) ++
|
||||
includes.map(i => Pair("includes", JacksMapper.writeValueAsString(i))).toMap ++
|
||||
excludes.map(e => Pair("excludes", JacksMapper.writeValueAsString(e))).toMap
|
||||
}
|
||||
|
||||
case class ProductResponse(statusCode: Int,
|
||||
product: List[ProductResult])
|
||||
|
||||
case class ProductResult(productId: String,
|
||||
brandName: String,
|
||||
productName: String,
|
||||
defaultProductUrl: String,
|
||||
defaultPrettyProductUrl: String,
|
||||
defaultImageUrl: String,
|
||||
description: String,
|
||||
gender: String,
|
||||
weight: String,
|
||||
videos: List[VideoInfo],
|
||||
sizeFit: String,
|
||||
widthFit: String,
|
||||
archFit: String,
|
||||
productRating: Double,
|
||||
overallRating: Double,
|
||||
comfortRating: Double,
|
||||
lookRating: Double,
|
||||
styles: List[StyleInfo],
|
||||
defaultProductType: String,
|
||||
defaultCategory: String,
|
||||
defaultSubCategory: String,
|
||||
attributeFacetFields: Map[String, String])
|
||||
|
||||
case class VideoInfo(id: String, filename: String, productId: String, uploadedDate: String, videoEncodingId: String, videoEncodingExtension: String)
|
||||
case class StyleInfo(styleId: String, color: String, originalPrice: String, price: String, productUrl: String, imageUrl: String, stocks: List[Map[String, String]])
|
||||
|
||||
/**
|
||||
* A product query action with the Zappos API. An API key and
|
||||
* at least one product ID must be specified. Other parameters may be
|
||||
* specified by setter methods.
|
||||
*
|
||||
* Instances of this class are immutable; setter methods create
|
||||
* new instances.
|
||||
*/
|
||||
case class Product(key: String,
|
||||
productIds: Set[String] = Set.empty,
|
||||
stockIds: Set[String] = Set.empty,
|
||||
upcs: Set[String] = Set.empty,
|
||||
includes: Set[String] = Set.empty,
|
||||
excludes: Set[String] = Set.empty) extends Base[ProductResponse]("Product", key) {
|
||||
private def include(item: String) = copy(includes = includes + item, excludes = excludes - item)
|
||||
private def exclude(item: String) = copy(includes = includes - item, excludes = excludes + item)
|
||||
|
||||
def withProductIdss(s: Seq[String]) = copy(productIds = s.toSet)
|
||||
def withStockIds(s: Seq[String]) = copy(stockIds = s.toSet)
|
||||
def withUpcs(s: Seq[String]) = copy(upcs = s.toSet)
|
||||
|
||||
def withProductId = copy(includes = includes + "productId", excludes = excludes - "productId")
|
||||
def withoutProductId = copy(includes = includes - "productId", excludes = excludes + "productId")
|
||||
def withBrandName = copy(includes = includes + "brandName", excludes = excludes - "brandName")
|
||||
def withoutBrandName = copy(includes = includes - "brandName", excludes = excludes + "brandName")
|
||||
def withProductName = copy(includes = includes + "productName", excludes = excludes - "productName")
|
||||
def withoutProductName = copy(includes = includes - "productName", excludes = excludes + "productName")
|
||||
def withDefaultProductUrl = copy(includes = includes + "defaultProductUrl", excludes = excludes - "defaultProductUrl")
|
||||
def withoutDefaultProductUrl = copy(includes = includes - "defaultProductUrl", excludes = excludes + "defaultProductUrl")
|
||||
def withDefaultPrettyProductUrl = copy(includes = includes + "defaultPrettyProductUrl", excludes = excludes - "defaultPrettyProductUrl")
|
||||
def withoutDefaultPrettyProductUrl = copy(includes = includes - "defaultPrettyProductUrl", excludes = excludes + "defaultPrettyProductUrl")
|
||||
def withDefaultImageUrl = copy(includes = includes + "defaultImageUrl", excludes = excludes - "defaultImageUrl")
|
||||
def withoutDefaultImageUrl = copy(includes = includes - "defaultImageUrl", excludes = excludes + "defaultImageUrl")
|
||||
def withDescription = copy(includes = includes + "description", excludes = excludes - "description")
|
||||
def withoutDescription = copy(includes = includes - "description", excludes = excludes + "description")
|
||||
def withGender = copy(includes = includes + "gender", excludes = excludes - "gender")
|
||||
def withoutGender = copy(includes = includes - "gender", excludes = excludes + "gender")
|
||||
def withWeight = copy(includes = includes + "weight", excludes = excludes - "weight")
|
||||
def withoutWeight = copy(includes = includes - "weight", excludes = excludes + "weight")
|
||||
def withVideos = copy(includes = includes + "videos", excludes = excludes - "videos")
|
||||
def withoutVideos = copy(includes = includes - "videos", excludes = excludes + "videos")
|
||||
def withVideoFileName = copy(includes = includes + "videoFileName", excludes = excludes - "videoFileName")
|
||||
def withoutVideoFileName = copy(includes = includes - "videoFileName", excludes = excludes + "videoFileName")
|
||||
def withVideoUrl = copy(includes = includes + "videoUrl", excludes = excludes - "videoUrl")
|
||||
def withoutVideoUrl = copy(includes = includes - "videoUrl", excludes = excludes + "videoUrl")
|
||||
def withVideoUploadedDate = copy(includes = includes + "videoUploadedDate", excludes = excludes - "videoUploadedDate")
|
||||
def withoutVideoUploadedDate = copy(includes = includes - "videoUploadedDate", excludes = excludes + "videoUploadedDate")
|
||||
def withSizeFit = copy(includes = includes + "sizeFit", excludes = excludes - "sizeFit")
|
||||
def withoutSizeFit = copy(includes = includes - "sizeFit", excludes = excludes + "sizeFit")
|
||||
def withWidthFit = copy(includes = includes + "widthFit", excludes = excludes - "widthFit")
|
||||
def withoutWidthFit = copy(includes = includes - "widthFit", excludes = excludes + "widthFit")
|
||||
def withArchFit = copy(includes = includes + "archFit", excludes = excludes - "archFit")
|
||||
def withoutArchFit = copy(includes = includes - "archFit", excludes = excludes + "archFit")
|
||||
def withProductRating = copy(includes = includes + "productRating", excludes = excludes - "productRating")
|
||||
def withoutProductRating = copy(includes = includes - "productRating", excludes = excludes + "productRating")
|
||||
def withOverallRating = copy(includes = includes + "overallRating", excludes = excludes - "overallRating")
|
||||
def withoutOverallRating = copy(includes = includes - "overallRating", excludes = excludes + "overallRating")
|
||||
def withComfortRating = copy(includes = includes + "comfortRating", excludes = excludes - "comfortRating")
|
||||
def withoutComfortRating = copy(includes = includes - "comfortRating", excludes = excludes + "comfortRating")
|
||||
def withLookRating = copy(includes = includes + "lookRating", excludes = excludes - "lookRating")
|
||||
def withoutLookRating = copy(includes = includes - "lookRating", excludes = excludes + "lookRating")
|
||||
def withStyles = copy(includes = includes + "styles", excludes = excludes - "styles")
|
||||
def withoutStyles = copy(includes = includes - "styles", excludes = excludes + "styles")
|
||||
def withDefaultCategory = copy(includes = includes + "defaultCategory", excludes = excludes - "defaultCategory")
|
||||
def withoutDefaultCategory = copy(includes = includes - "defaultCategory", excludes = excludes + "defaultCategory")
|
||||
def withDefaultSubCategory = copy(includes = includes + "defaultSubCategory", excludes = excludes - "defaultSubCategory")
|
||||
def withoutDefaultSubCategory = copy(includes = includes - "defaultSubCategory", excludes = excludes + "defaultSubCategory")
|
||||
def withAttributeFacetFields = copy(includes = includes + "attributeFacetFields", excludes = excludes - "attributeFacetFields")
|
||||
def withoutAttributeFacetFields = copy(includes = includes - "attributeFacetFields", excludes = excludes + "attributeFacetFields")
|
||||
|
||||
override def parameters = {
|
||||
val nonEmpty = List(Pair("id", productIds), Pair("stockId", stockIds), Pair("upc", upcs)).filter(!_._2.isEmpty)
|
||||
assert(nonEmpty.map(_._2.size).sum <= 10, "Must specify at most 10 products")
|
||||
// assert(nonEmpty.size == 1, "Must specify only one of product ID, stock ID, or UPC")
|
||||
// val searchField = nonEmpty.head._1
|
||||
// val searchValues = nonEmpty.head._2
|
||||
|
||||
nonEmpty.map(p => Pair(p._1, JacksMapper.writeValueAsString(p._2))).toMap ++
|
||||
includes.map(i => Pair("includes", JacksMapper.writeValueAsString(i))).toMap ++
|
||||
excludes.map(e => Pair("excludes", JacksMapper.writeValueAsString(e))).toMap
|
||||
}
|
||||
}
|
||||
|
||||
case class ReviewResponse(statusCode: Int,
|
||||
page: Option[Int],
|
||||
offset: Option[Int],
|
||||
reviews: List[ReviewResult])
|
||||
|
||||
case class ReviewResult(id: String,
|
||||
date: String,
|
||||
name: String,
|
||||
location: String,
|
||||
otherShoes: String,
|
||||
summary: String,
|
||||
shoeSize: String,
|
||||
shoeWidth: String,
|
||||
shoeArch: String,
|
||||
overallRating: Option[Int],
|
||||
comfortRating: Option[Int],
|
||||
lookRating: Option[Int])
|
||||
|
||||
case class Review(key: String,
|
||||
productId: String,
|
||||
page: Option[Int] = None,
|
||||
startId: Option[Int] = None) extends Base[ReviewResponse]("Review", key) {
|
||||
override def parameters =
|
||||
Map("productId" -> productId) ++
|
||||
page.map(i => Pair("page", i.toString)).toMap ++
|
||||
startId.map(i => Pair("startId", i.toString)).toMap
|
||||
}
|
||||
66
src/main/scala/dstu/zappy/Base.scala
Normal file
66
src/main/scala/dstu/zappy/Base.scala
Normal file
@@ -0,0 +1,66 @@
|
||||
package dstu.zappy
|
||||
|
||||
import com.lambdaworks.jacks.JacksMapper
|
||||
import com.sun.jersey.api.client.Client
|
||||
import com.sun.jersey.core.util.MultivaluedMapImpl
|
||||
import javax.ws.rs.core.{MediaType, MultivaluedMap}
|
||||
|
||||
/**
|
||||
* Singleton container for API resources.
|
||||
*/
|
||||
object Zappos {
|
||||
val BASE_URL = "http://api.zappos.com/"
|
||||
lazy val client = Client.create
|
||||
lazy val resource = client.resource(BASE_URL)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parent class of all API actions.
|
||||
*
|
||||
* @constructor builds a new action
|
||||
* @param path the additional path component for the specific action (like "Search" or "Product"). This corresponds to an action in the main Zappos API.
|
||||
* @param key the API key to send with the action
|
||||
*/
|
||||
abstract class BaseAction[T](path: String, key: String) {
|
||||
type Response = T
|
||||
|
||||
/**
|
||||
* Provides type conversion from the Scala Map trait to the Jersey MultivaluedMap interface.
|
||||
*/
|
||||
private implicit def mapAsMultivaluedMap(m: Map[String, String]): MultivaluedMap[String, String] =
|
||||
m.foldLeft(new MultivaluedMapImpl)((result, entry) => {
|
||||
result.add(entry._1, entry._2)
|
||||
result
|
||||
})
|
||||
|
||||
/**
|
||||
* The parameter map for the action. This specifies the
|
||||
parameters of the GET request that the action will
|
||||
send. Subclasses should override this method and fill in
|
||||
their own fields.
|
||||
*
|
||||
* @return the parameters for the action's GET request
|
||||
*/
|
||||
def parameters: Map[String, String]
|
||||
|
||||
/**
|
||||
* Hydrates a Response object from raw JSON. Subclasses may
|
||||
override this method and fill in their own implementation.
|
||||
* @param json the raw JSON
|
||||
* @return a newly hydrated Response object
|
||||
*/
|
||||
def decodeResponse(s: String)(implicit m: Manifest[Response]): Response = JacksMapper.readValue[Response](s)
|
||||
|
||||
/**
|
||||
* Makes the call to the Zappos API.
|
||||
* @return the raw JSON of the API response
|
||||
*/
|
||||
def getJson(): String =
|
||||
Zappos.resource.path(path).queryParams(parameters + Pair("key", key)).accept(MediaType.APPLICATION_JSON_TYPE).get(classOf[java.lang.String])
|
||||
|
||||
/**
|
||||
* Makes the call to the Zappos API and decodes it.
|
||||
* @return the response, decoded
|
||||
*/
|
||||
def get()(implicit m: Manifest[Response]): Response = decodeResponse(getJson)
|
||||
}
|
||||
60
src/main/scala/dstu/zappy/Image.scala
Normal file
60
src/main/scala/dstu/zappy/Image.scala
Normal file
@@ -0,0 +1,60 @@
|
||||
package dstu.zappy
|
||||
|
||||
import com.lambdaworks.jacks.JacksMapper
|
||||
|
||||
case class ImageResponse(statusCode: Int,
|
||||
productId: String,
|
||||
images: Map[String, List[ImageResult]])
|
||||
|
||||
case class ImageResult(styleId: String,
|
||||
productId: String,
|
||||
`type`: String,
|
||||
recipeName: String,
|
||||
format: String,
|
||||
filename: String,
|
||||
colorId: String,
|
||||
width: Option[Int],
|
||||
height: Option[Int],
|
||||
uploadDate: String,
|
||||
isHighResolution: Option[Boolean],
|
||||
tiles: String)
|
||||
|
||||
/**
|
||||
* An image query. An API key and product ID must be
|
||||
specified. Other parameters may be specified by setter
|
||||
methods.
|
||||
*
|
||||
* Instances of this class are immutable; setter methods create
|
||||
* new instances.
|
||||
*/
|
||||
case class Image(key: String, productId: String, includes: Set[String] = Set.empty, excludes: Set[String] = Set.empty) extends BaseAction[ImageResponse]("Image", key) {
|
||||
def withStyleId = copy(includes = includes + "styleId", excludes = excludes - "styleId")
|
||||
def withoutStyleId = copy(includes = includes - "styleId", excludes = excludes + "styleId")
|
||||
def withProductId = copy(includes = includes + "productId", excludes = excludes - "productId")
|
||||
def withoutProductId = copy(includes = includes - "productId", excludes = excludes + "productId")
|
||||
def withType = copy(includes = includes + "type", excludes = excludes - "type")
|
||||
def withoutType = copy(includes = includes - "type", excludes = excludes + "type")
|
||||
def withRecipeName = copy(includes = includes + "recipeName", excludes = excludes - "recipeName")
|
||||
def withoutRecipeName = copy(includes = includes - "recipeName", excludes = excludes + "recipeName")
|
||||
def withFormat = copy(includes = includes + "format", excludes = excludes - "format")
|
||||
def withoutFormat = copy(includes = includes - "format", excludes = excludes + "format")
|
||||
def withFilename = copy(includes = includes + "filename", excludes = excludes - "filename")
|
||||
def withoutFilename = copy(includes = includes - "filename", excludes = excludes + "filename")
|
||||
def withColorId = copy(includes = includes + "colorId", excludes = excludes - "colorId")
|
||||
def withoutColorId = copy(includes = includes - "colorId", excludes = excludes + "colorId")
|
||||
def withWidth = copy(includes = includes + "width", excludes = excludes - "width")
|
||||
def withoutWidth = copy(includes = includes - "width", excludes = excludes + "width")
|
||||
def withHeight = copy(includes = includes + "height", excludes = excludes - "height")
|
||||
def withoutHeight = copy(includes = includes - "height", excludes = excludes + "height")
|
||||
def withUploadDate = copy(includes = includes + "uploadDate", excludes = excludes - "uploadDate")
|
||||
def withoutUploadDate = copy(includes = includes - "uploadDate", excludes = excludes + "uploadDate")
|
||||
def withHighResolution = copy(includes = includes + "isHighResolution", excludes = excludes - "isHighResolution")
|
||||
def withoutHighResolution = copy(includes = includes - "isHighResolution", excludes = excludes + "isHighResolution")
|
||||
def withTiles = copy(includes = includes + "tiles", excludes = excludes - "tiles")
|
||||
def withoutTiles = copy(includes = includes - "tiles", excludes = excludes + "tiles")
|
||||
|
||||
override def parameters =
|
||||
Map("productId" -> productId) ++
|
||||
includes.map(i => Pair("includes", JacksMapper.writeValueAsString(i))).toMap ++
|
||||
excludes.map(e => Pair("excludes", JacksMapper.writeValueAsString(e))).toMap
|
||||
}
|
||||
115
src/main/scala/dstu/zappy/Product.scala
Normal file
115
src/main/scala/dstu/zappy/Product.scala
Normal file
@@ -0,0 +1,115 @@
|
||||
package dstu.zappy
|
||||
|
||||
import com.lambdaworks.jacks.JacksMapper
|
||||
|
||||
case class ProductResponse(statusCode: Int,
|
||||
product: List[ProductResult])
|
||||
|
||||
case class ProductResult(productId: String,
|
||||
brandName: String,
|
||||
productName: String,
|
||||
defaultProductUrl: String,
|
||||
defaultPrettyProductUrl: String,
|
||||
defaultImageUrl: String,
|
||||
description: String,
|
||||
gender: String,
|
||||
weight: String,
|
||||
videos: List[VideoInfo],
|
||||
sizeFit: String,
|
||||
widthFit: String,
|
||||
archFit: String,
|
||||
productRating: Double,
|
||||
overallRating: Double,
|
||||
comfortRating: Double,
|
||||
lookRating: Double,
|
||||
styles: List[StyleInfo],
|
||||
defaultProductType: String,
|
||||
defaultCategory: String,
|
||||
defaultSubCategory: String,
|
||||
attributeFacetFields: Map[String, String])
|
||||
|
||||
case class VideoInfo(id: String, filename: String, productId: String, uploadedDate: String, videoEncodingId: String, videoEncodingExtension: String)
|
||||
case class StyleInfo(styleId: String, color: String, originalPrice: String, price: String, productUrl: String, imageUrl: String, stocks: List[Map[String, String]])
|
||||
|
||||
/**
|
||||
* A product query action with the Zappos API. An API key and
|
||||
* at least one product ID must be specified. Other parameters may be
|
||||
* specified by setter methods.
|
||||
*
|
||||
* Instances of this class are immutable; setter methods create
|
||||
* new instances.
|
||||
*/
|
||||
case class Product(key: String,
|
||||
productIds: Set[String] = Set.empty,
|
||||
stockIds: Set[String] = Set.empty,
|
||||
upcs: Set[String] = Set.empty,
|
||||
includes: Set[String] = Set.empty,
|
||||
excludes: Set[String] = Set.empty) extends BaseAction[ProductResponse]("Product", key) {
|
||||
private def include(item: String) = copy(includes = includes + item, excludes = excludes - item)
|
||||
private def exclude(item: String) = copy(includes = includes - item, excludes = excludes + item)
|
||||
|
||||
def withProductIdss(s: Seq[String]) = copy(productIds = s.toSet)
|
||||
def withStockIds(s: Seq[String]) = copy(stockIds = s.toSet)
|
||||
def withUpcs(s: Seq[String]) = copy(upcs = s.toSet)
|
||||
|
||||
def withProductId = copy(includes = includes + "productId", excludes = excludes - "productId")
|
||||
def withoutProductId = copy(includes = includes - "productId", excludes = excludes + "productId")
|
||||
def withBrandName = copy(includes = includes + "brandName", excludes = excludes - "brandName")
|
||||
def withoutBrandName = copy(includes = includes - "brandName", excludes = excludes + "brandName")
|
||||
def withProductName = copy(includes = includes + "productName", excludes = excludes - "productName")
|
||||
def withoutProductName = copy(includes = includes - "productName", excludes = excludes + "productName")
|
||||
def withDefaultProductUrl = copy(includes = includes + "defaultProductUrl", excludes = excludes - "defaultProductUrl")
|
||||
def withoutDefaultProductUrl = copy(includes = includes - "defaultProductUrl", excludes = excludes + "defaultProductUrl")
|
||||
def withDefaultPrettyProductUrl = copy(includes = includes + "defaultPrettyProductUrl", excludes = excludes - "defaultPrettyProductUrl")
|
||||
def withoutDefaultPrettyProductUrl = copy(includes = includes - "defaultPrettyProductUrl", excludes = excludes + "defaultPrettyProductUrl")
|
||||
def withDefaultImageUrl = copy(includes = includes + "defaultImageUrl", excludes = excludes - "defaultImageUrl")
|
||||
def withoutDefaultImageUrl = copy(includes = includes - "defaultImageUrl", excludes = excludes + "defaultImageUrl")
|
||||
def withDescription = copy(includes = includes + "description", excludes = excludes - "description")
|
||||
def withoutDescription = copy(includes = includes - "description", excludes = excludes + "description")
|
||||
def withGender = copy(includes = includes + "gender", excludes = excludes - "gender")
|
||||
def withoutGender = copy(includes = includes - "gender", excludes = excludes + "gender")
|
||||
def withWeight = copy(includes = includes + "weight", excludes = excludes - "weight")
|
||||
def withoutWeight = copy(includes = includes - "weight", excludes = excludes + "weight")
|
||||
def withVideos = copy(includes = includes + "videos", excludes = excludes - "videos")
|
||||
def withoutVideos = copy(includes = includes - "videos", excludes = excludes + "videos")
|
||||
def withVideoFileName = copy(includes = includes + "videoFileName", excludes = excludes - "videoFileName")
|
||||
def withoutVideoFileName = copy(includes = includes - "videoFileName", excludes = excludes + "videoFileName")
|
||||
def withVideoUrl = copy(includes = includes + "videoUrl", excludes = excludes - "videoUrl")
|
||||
def withoutVideoUrl = copy(includes = includes - "videoUrl", excludes = excludes + "videoUrl")
|
||||
def withVideoUploadedDate = copy(includes = includes + "videoUploadedDate", excludes = excludes - "videoUploadedDate")
|
||||
def withoutVideoUploadedDate = copy(includes = includes - "videoUploadedDate", excludes = excludes + "videoUploadedDate")
|
||||
def withSizeFit = copy(includes = includes + "sizeFit", excludes = excludes - "sizeFit")
|
||||
def withoutSizeFit = copy(includes = includes - "sizeFit", excludes = excludes + "sizeFit")
|
||||
def withWidthFit = copy(includes = includes + "widthFit", excludes = excludes - "widthFit")
|
||||
def withoutWidthFit = copy(includes = includes - "widthFit", excludes = excludes + "widthFit")
|
||||
def withArchFit = copy(includes = includes + "archFit", excludes = excludes - "archFit")
|
||||
def withoutArchFit = copy(includes = includes - "archFit", excludes = excludes + "archFit")
|
||||
def withProductRating = copy(includes = includes + "productRating", excludes = excludes - "productRating")
|
||||
def withoutProductRating = copy(includes = includes - "productRating", excludes = excludes + "productRating")
|
||||
def withOverallRating = copy(includes = includes + "overallRating", excludes = excludes - "overallRating")
|
||||
def withoutOverallRating = copy(includes = includes - "overallRating", excludes = excludes + "overallRating")
|
||||
def withComfortRating = copy(includes = includes + "comfortRating", excludes = excludes - "comfortRating")
|
||||
def withoutComfortRating = copy(includes = includes - "comfortRating", excludes = excludes + "comfortRating")
|
||||
def withLookRating = copy(includes = includes + "lookRating", excludes = excludes - "lookRating")
|
||||
def withoutLookRating = copy(includes = includes - "lookRating", excludes = excludes + "lookRating")
|
||||
def withStyles = copy(includes = includes + "styles", excludes = excludes - "styles")
|
||||
def withoutStyles = copy(includes = includes - "styles", excludes = excludes + "styles")
|
||||
def withDefaultCategory = copy(includes = includes + "defaultCategory", excludes = excludes - "defaultCategory")
|
||||
def withoutDefaultCategory = copy(includes = includes - "defaultCategory", excludes = excludes + "defaultCategory")
|
||||
def withDefaultSubCategory = copy(includes = includes + "defaultSubCategory", excludes = excludes - "defaultSubCategory")
|
||||
def withoutDefaultSubCategory = copy(includes = includes - "defaultSubCategory", excludes = excludes + "defaultSubCategory")
|
||||
def withAttributeFacetFields = copy(includes = includes + "attributeFacetFields", excludes = excludes - "attributeFacetFields")
|
||||
def withoutAttributeFacetFields = copy(includes = includes - "attributeFacetFields", excludes = excludes + "attributeFacetFields")
|
||||
|
||||
override def parameters = {
|
||||
val nonEmpty = List(Pair("id", productIds), Pair("stockId", stockIds), Pair("upc", upcs)).filter(!_._2.isEmpty)
|
||||
assert(nonEmpty.map(_._2.size).sum <= 10, "Must specify at most 10 products")
|
||||
// assert(nonEmpty.size == 1, "Must specify only one of product ID, stock ID, or UPC")
|
||||
// val searchField = nonEmpty.head._1
|
||||
// val searchValues = nonEmpty.head._2
|
||||
|
||||
nonEmpty.map(p => Pair(p._1, JacksMapper.writeValueAsString(p._2))).toMap ++
|
||||
includes.map(i => Pair("includes", JacksMapper.writeValueAsString(i))).toMap ++
|
||||
excludes.map(e => Pair("excludes", JacksMapper.writeValueAsString(e))).toMap
|
||||
}
|
||||
}
|
||||
29
src/main/scala/dstu/zappy/Review.scala
Normal file
29
src/main/scala/dstu/zappy/Review.scala
Normal file
@@ -0,0 +1,29 @@
|
||||
package dstu.zappy
|
||||
|
||||
case class ReviewResponse(statusCode: Int,
|
||||
page: Option[Int],
|
||||
offset: Option[Int],
|
||||
reviews: List[ReviewResult])
|
||||
|
||||
case class ReviewResult(id: String,
|
||||
date: String,
|
||||
name: String,
|
||||
location: String,
|
||||
otherShoes: String,
|
||||
summary: String,
|
||||
shoeSize: String,
|
||||
shoeWidth: String,
|
||||
shoeArch: String,
|
||||
overallRating: Option[Int],
|
||||
comfortRating: Option[Int],
|
||||
lookRating: Option[Int])
|
||||
|
||||
case class Review(key: String,
|
||||
productId: String,
|
||||
page: Option[Int] = None,
|
||||
startId: Option[Int] = None) extends BaseAction[ReviewResponse]("Review", key) {
|
||||
override def parameters =
|
||||
Map("productId" -> productId) ++
|
||||
page.map(i => Pair("page", i.toString)).toMap ++
|
||||
startId.map(i => Pair("startId", i.toString)).toMap
|
||||
}
|
||||
158
src/main/scala/dstu/zappy/Search.scala
Normal file
158
src/main/scala/dstu/zappy/Search.scala
Normal file
@@ -0,0 +1,158 @@
|
||||
package dstu.zappy
|
||||
|
||||
import com.lambdaworks.jacks.JacksMapper
|
||||
|
||||
case class SearchResponse(statusCode: Int,
|
||||
currentResultCount: Option[Int],
|
||||
totalResultCount: Option[Int],
|
||||
limit: Option[Int],
|
||||
currentPage: Option[Int],
|
||||
pageCount: Option[Int],
|
||||
term: String,
|
||||
originalTerm: String,
|
||||
results: List[SearchResult])
|
||||
|
||||
case class SearchResult(styleId: Int,
|
||||
productId: Int,
|
||||
colorId: String,
|
||||
brandName: String,
|
||||
productUrl: String,
|
||||
thumbnailImageUrl: String,
|
||||
price: String,
|
||||
productName: String,
|
||||
originalPrice: String,
|
||||
percentOff: String,
|
||||
description: String,
|
||||
videoUrl: String,
|
||||
videoFileName: String,
|
||||
videoUploadedDate: String,
|
||||
productRating: String,
|
||||
brandId: String,
|
||||
categoryFacet: String,
|
||||
heelHeight: String,
|
||||
subCategoryFacet: String,
|
||||
gender: String)
|
||||
|
||||
/**
|
||||
* A search action with the Zappos API. An API key and search
|
||||
* term must be specified. Other parameters may be specified by
|
||||
* setter methods.
|
||||
*
|
||||
* Instances of this class are immutable; setter methods create
|
||||
* new instances.
|
||||
*/
|
||||
case class Search(key: String,
|
||||
term: String,
|
||||
includes: Set[String] = Set.empty,
|
||||
excludes: Set[String] = Set.empty,
|
||||
facets: Set[String] = Set.empty,
|
||||
facetFilters: Map[String, Set[String]] = Map.empty,
|
||||
limit: Option[Int] = None,
|
||||
page: Option[Int] = None) extends BaseAction[SearchResponse]("Search", key) {
|
||||
private def include(item: String) = copy(includes = includes + item, excludes = excludes - item)
|
||||
private def exclude(item: String) = copy(includes = includes - item, excludes = excludes + item)
|
||||
|
||||
def withSearchTerm(s: String) = copy(term = s)
|
||||
|
||||
/**
|
||||
* Sets the maximum number of items to return.
|
||||
* @param i the desired maximum number of items
|
||||
* @return a new Search object with the item limit set to the desired value
|
||||
*/
|
||||
def withLimit(i: Int) = copy(limit = Some(i))
|
||||
/**
|
||||
* Unsets the maximum number of items to return. (The default behavior of the Zappos API will be used instead.)
|
||||
* @return a new Search object with the item limit unset
|
||||
*/
|
||||
def withoutLimit = copy(limit = None)
|
||||
|
||||
/**
|
||||
* Sets the page of results to return.
|
||||
* @param i the desired page
|
||||
* @return a new Search object with the page set to the desired value
|
||||
*/
|
||||
def withPage(i: Int) = copy(page = Some(i))
|
||||
/**
|
||||
* Unsets the page to return. (The default behavior of the Zappos API will be used instead.)
|
||||
* @return a new Search object with the page unset
|
||||
*/
|
||||
def withoutPage = copy(page = None)
|
||||
|
||||
/**
|
||||
* Adds a facet filter, filtering for items that have one of
|
||||
the given values in the given facet. This replaces any
|
||||
existing filter on the given facet. Multiple filters may be
|
||||
specified; they are logically AND'ed by the Zappos API.
|
||||
|
||||
* @param facet the facet to filter on
|
||||
* @param values the values to filter for
|
||||
* @return a new Search object with the new facet filter
|
||||
*/
|
||||
def withFacetFilter(facet: String, values: Set[String]) = copy(facetFilters = facetFilters + Pair(facet, values))
|
||||
/**
|
||||
* Removes all filters on the given facet.
|
||||
* @param facet the facet for which to remove filters
|
||||
* @return a new Search object without the given facet filter
|
||||
*/
|
||||
def withoutFacetFilter(facet: String) = copy(facetFilters = facetFilters - facet)
|
||||
/**
|
||||
* Removes all facet filters.
|
||||
* @return a new Search object without any facet filters
|
||||
*/
|
||||
def withoutFacetFilters = copy(facetFilters = Map.empty)
|
||||
|
||||
def withStyleId = include("styleId")
|
||||
def withoutStyleId = exclude("styleId")
|
||||
def withProductId = include("productId")
|
||||
def withoutProductId = exclude("productId")
|
||||
def withColorId = include("colorId")
|
||||
def withoutColorId = exclude("colorId")
|
||||
def withBrandName = include("withBrandName")
|
||||
def withoutBrandName = exclude("withBrandName")
|
||||
def withProductName = include("productName")
|
||||
def withoutProductName = exclude("productName")
|
||||
def withProductUrl = include("productUrl")
|
||||
def withoutProductUrl = exclude("productUrl")
|
||||
def withThumbnailImageUrl = include("thumbnailImageUrl")
|
||||
def withoutThumbnailImageUrl = exclude("thumbnailImageUrl")
|
||||
def withPrice = include("price")
|
||||
def withoutPrice = exclude("price")
|
||||
def withOriginalPrice = include("originalPrice")
|
||||
def withoutOriginalPrice = exclude("originalPrice")
|
||||
def withPercentOff = include("percentOff")
|
||||
def withoutPercentOff = exclude("percentOff")
|
||||
def withDescription = include("description")
|
||||
def withoutDescription = exclude("description")
|
||||
def withVideoUrl = include("videoUrl")
|
||||
def withoutVideoUrl = exclude("videoUrl")
|
||||
def withVideoFileName = include("videoFileName")
|
||||
def withoutVideoFileName = exclude("videoFileName")
|
||||
def withVideoUploadedDate = include("videoUploadedDate")
|
||||
def withoutVideoUploadedDate = exclude("videoUploadedDate")
|
||||
def withProductRating = include("productRating")
|
||||
def withoutProductRating = exclude("productRating")
|
||||
def withBrandId = include("brandId")
|
||||
def withoutBrandId = exclude("brandId")
|
||||
def withCategoryFacet = include("categoryFacet")
|
||||
def withoutCategoryFacet = exclude("categoryFacet")
|
||||
def withHeelHeight = include("heelHeight")
|
||||
def withoutHeelHeight = exclude("heelHeight")
|
||||
def withSubCategoryFacet = include("subCategoryFacet")
|
||||
def withoutSubCategoryFacet = exclude("subCategoryFacet")
|
||||
def withGender = include("txAttrFacet_Gender")
|
||||
def withoutGender = exclude("txAttrFacet_Gender")
|
||||
def withTotalResultCount = include("totalResultCount")
|
||||
def withoutTotalResultCount = exclude("totalResultCount")
|
||||
def withFacets = include("facets")
|
||||
def withoutFacets = exclude("facets")
|
||||
|
||||
override def parameters =
|
||||
Map("term" -> term) ++
|
||||
includes.map(i => Pair("includes", JacksMapper.writeValueAsString(i))).toMap ++
|
||||
excludes.map(e => Pair("excludes", JacksMapper.writeValueAsString(e))).toMap ++
|
||||
facets.map(f => Pair("facets", JacksMapper.writeValueAsString(f))).toMap ++
|
||||
(if (facetFilters.isEmpty) Map.empty
|
||||
else Map("filters" -> JacksMapper.writeValueAsString(facetFilters))) ++
|
||||
limit.map(i => Pair("limit", i.toString)).toMap ++
|
||||
page.map(i => Pair("page", i.toString)).toMap
|
||||
}
|
||||
Reference in New Issue
Block a user