|
1 /* Title: Pure/General/json_api.scala |
|
2 Author: Makarius |
|
3 |
|
4 Support for JSON:API: https://jsonapi.org/format |
|
5 */ |
|
6 |
|
7 package isabelle |
|
8 |
|
9 |
|
10 object JSON_API |
|
11 { |
|
12 val mime_type = "application/vnd.api+json" |
|
13 |
|
14 def api_error(msg: String): Nothing = error("JSON API error: " + msg) |
|
15 def api_errors(msgs: List[String]): Nothing = error(("JSON API errors:" :: msgs).mkString("\n ")) |
|
16 |
|
17 sealed case class Root(json: JSON.T) |
|
18 { |
|
19 def get_links: Option[Links] = JSON.value(json, "links").map(Links) |
|
20 def get_included: List[Resource] = JSON.list(json, "included", Resource.some).getOrElse(Nil) |
|
21 def get_data: Option[Data] = JSON.value(json, "data").map(Data(_, get_included)) |
|
22 def get_errors: Option[List[JSON.T]] = JSON.list(json, "errors", Some(_)) |
|
23 |
|
24 def ok: Boolean = get_errors.isEmpty |
|
25 def check: Root = |
|
26 get_errors match { |
|
27 case None => this |
|
28 case Some(List(err)) => api_error(JSON.Format(err)) |
|
29 case Some(errs) => api_errors(errs.map(JSON.Format.apply)) |
|
30 } |
|
31 def check_data: Data = check.get_data getOrElse api_error("missing data") |
|
32 |
|
33 override def toString: String = "Root(" + (if (ok) "ok" else "errors") + ")" |
|
34 } |
|
35 |
|
36 sealed case class Links(json: JSON.T) |
|
37 { |
|
38 def get_next: Option[Root] = |
|
39 for (next <- JSON.value(json, "next") if next != null) yield Root(next) |
|
40 |
|
41 override def toString: String = |
|
42 if (get_next.isEmpty) "Links()" else "Links(next)" |
|
43 } |
|
44 |
|
45 sealed case class Data(data: JSON.T, included: List[Resource]) |
|
46 { |
|
47 def is_multi: Boolean = JSON.Value.List.unapply(data, Some(_)).nonEmpty |
|
48 |
|
49 def resource: Resource = |
|
50 if (is_multi) api_error("singleton resource expected") |
|
51 else Resource.some(data).get |
|
52 |
|
53 def resources: List[Resource] = |
|
54 JSON.Value.List.unapply(data, Resource.some) |
|
55 .getOrElse(api_error("multiple resources expected")) |
|
56 |
|
57 override def toString: String = |
|
58 if (is_multi) "Data(resources)" else "Data(resource)" |
|
59 } |
|
60 |
|
61 object Resource |
|
62 { |
|
63 def some(json: JSON.T): Some[Resource] = Some(Resource(json)) |
|
64 } |
|
65 |
|
66 sealed case class Resource(json: JSON.T) |
|
67 { |
|
68 val id: JSON.T = JSON.value(json, "id") getOrElse api_error("missing id") |
|
69 val type_ : JSON.T = JSON.value(json, "type") getOrElse api_error("missing type") |
|
70 val fields: JSON.Object.T = |
|
71 JSON.value(json, "attributes", JSON.Object.unapply).getOrElse(JSON.Object.empty) ++ |
|
72 JSON.value(json, "relationships", JSON.Object.unapply).getOrElse(JSON.Object.empty) |
|
73 |
|
74 override def toString: String = JSON.Format(json) |
|
75 } |
|
76 } |