forked from guardian/content-api-scala-client
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Api.scala
292 lines (211 loc) · 7.52 KB
/
Api.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
package com.gu.openplatform.contentapi
import connection.{JavaNetHttp, ApacheHttpClient, Http}
import java.net.URLEncoder
import com.gu.openplatform.contentapi.parser.JsonParser
import model.TagsResponse
import org.joda.time.format.ISODateTimeFormat
import org.joda.time.ReadableInstant
// thrown when an "expected" error is thrown by the api
case class ApiError(val httpStatus: Int, val httpMessage: String)
extends Exception(httpMessage)
abstract class Api extends Http {
val targetUrl = "http://content.guardianapis.com"
var apiKey: Option[String] = None
def sections = new SectionsQuery
def tags = new TagsQuery
def search = new SearchQuery
def item = new ItemQuery
trait Parameters {
def parameters: Map[String, Any] = Map.empty
}
trait GeneralParameters extends Parameters {
override def parameters = super.parameters ++
apiKey.map("api-key" -> _)
}
trait PaginationParameters extends Parameters {
var _pageSize: Option[Int] = None
var _page: Option[Int] = None
def pageSize(newPageSize: Int): this.type = {
_pageSize = Some(newPageSize); this
}
def page(newPage: Int): this.type = {
_page = Some(newPage); this
}
override def parameters = super.parameters ++
_pageSize.map("page-size" -> _) ++
_page.map("page" -> _)
}
trait FilterParameters extends Parameters {
var _q: Option[String] = None
var _section: Option[String] = None
var _ids: Option[String] = None
var _tag: Option[String] = None
def section(s: String): this.type = {
_section = Some(s)
this
}
def q(newQ: String): this.type = {
_q = Some(newQ)
this
}
def tags(newTagTerm: String): this.type = {
_tag = Some(newTagTerm)
this
}
def ids(newIds: String): this.type= {
_ids = Some(newIds)
this
}
override def parameters = super.parameters ++
_q.map("q" -> _) ++
_section.map("section" -> _) ++
_ids.map("ids" -> _) ++
_tag.map("tag" -> )
}
trait ContentFilterParamters extends FilterParameters {
var _orderBy: Option[String] = None
var _fromDate: Option[ReadableInstant] = None
var _toDate: Option[ReadableInstant] = None
def orderBy(s: String): this.type = {
_orderBy = Some(s); this
}
// most likely, you'll want to use DateMidnight
// to pass to this
def fromDate(d: ReadableInstant): this.type = {
_fromDate = Some(d); this
}
// most likely, you'll want to use DateMidnight
// to pass to this
def toDate(d: ReadableInstant): this.type = {
_toDate = Some(d); this
}
override def parameters = super.parameters ++
_orderBy.map("order-by" -> _) ++
_fromDate.map("from-date" -> _) ++
_toDate.map("to-date" -> _)
}
trait ShowParameters extends Parameters {
var _showFields: Option[String] = None
var _showSnippets: Option[String] = None
var _showTags: Option[String] = None
var _showFactboxes: Option[String] = None
var _showMedia: Option[String] = None
def showFields(newFields: String): this.type = {
_showFields = Some(newFields)
this
}
def showSnippets(newSnippets: String): this.type = {
_showSnippets = Some(newSnippets)
this
}
def showTags(newShowTags: String): this.type = {
_showTags = Some(newShowTags)
this
}
def showFactboxes(newShowFactboxes: String): this.type = {
_showFactboxes = Some(newShowFactboxes)
this
}
def showMedia(newShowMediaTypes: String): this.type = {
_showMedia = Some(newShowMediaTypes)
this
}
override def parameters = super.parameters ++
_showFields.map("show-fields" -> _) ++
_showSnippets.map("show-snippets" -> _) ++
_showTags.map("show-tags" -> _) ++
_showFactboxes.map("show-factboxes" -> _) ++
_showMedia.map("show-media" -> _)
}
trait RefinementParameters extends Parameters {
var _showRefinements: Option[String] = None
var _refinementSize: Option[Int] = None
def showRefinements(newShowRefinements: String): this.type = {
_showRefinements = Some(newShowRefinements)
this
}
def refinementSize(newRefinementSize: Int): this.type = {
_refinementSize = Some(newRefinementSize)
this
}
override def parameters = super.parameters ++
_showRefinements.map("show-refinements" -> _) ++
_refinementSize.map("refinement-size" -> _)
}
class SectionsQuery
extends GeneralParameters
with FilterParameters
with JsonParser {
lazy val response = parseSections(fetch(targetUrl + "/sections", parameters))
}
object SectionsQuery {
implicit def asResponse(q: SectionsQuery) = q.response
implicit def asSections(q: SectionsQuery) = q.response.results
}
class TagsQuery extends GeneralParameters
with PaginationParameters
with FilterParameters
with JsonParser {
var _tagType: Option[String] = None
def tagType(newTypeTerm: String) = {
_tagType = Some(newTypeTerm)
this
}
lazy val response = parseTags(fetch(targetUrl + "/tags", parameters))
override def parameters = super.parameters ++
_tagType.map("type" -> _)
}
object TagsQuery {
implicit def asResponse(q: TagsQuery) = q.response
implicit def asTags(q: TagsQuery) = q.response.results
}
class SearchQuery extends GeneralParameters
with PaginationParameters
with ShowParameters
with RefinementParameters
with FilterParameters
with ContentFilterParamters
with JsonParser {
lazy val response = parseSearch(fetch(targetUrl + "/search", parameters))
}
object SearchQuery {
implicit def asResponse(q: SearchQuery) = q.response
implicit def asContent(q: SearchQuery) = q.response.results
}
class ItemQuery extends GeneralParameters
with ShowParameters
with ContentFilterParamters
with PaginationParameters
with JsonParser {
var _apiUrl: Option[String] = None
def apiUrl(newContentPath: String): this.type = {
require(newContentPath startsWith targetUrl, "apiUrl expects a full url; use itemId if you only have an id")
_apiUrl = Some(newContentPath)
this
}
def itemId(contentId: String): this.type = apiUrl(targetUrl + "/" + contentId)
lazy val response = parseItem(
fetch(
_apiUrl.getOrElse(throw new Exception("No api url provided to item query, ensure withApiUrl is called")),
parameters))
}
object ItemQuery {
implicit def asResponse(q: ItemQuery) = q.response
}
protected def fetch(url: String, parameters: Map[String, Any] = Map.empty): String = {
require(!url.contains('?'), "must not specify paramaters on query string")
def encodeParameter(p: Any): String = p match {
case dt: ReadableInstant => ISODateTimeFormat.yearMonthDay.print(dt)
case other => URLEncoder.encode(other.toString, "UTF-8")
}
val queryString = parameters.map {case (k, v) => k + "=" + encodeParameter(v)}.mkString("&")
val target = url + "?" + queryString
val response = GET(target, List("User-Agent" -> "scala-api-client", "Accept" -> "application/json"))
if (List(200, 302) contains response.statusCode) {
response.body
} else {
throw new ApiError(response.statusCode, response.statusMessage)
}
}
}
object Api extends Api with JavaNetHttp