src/Pure/General/json_api.scala
changeset 74946 0dd14d8b16da
child 74962 2c9c4ad4c816
equal deleted inserted replaced
74945:4dc90b43ba94 74946:0dd14d8b16da
       
     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 }