package io.trygvis.cj import scala.collection.JavaConversions._ import scala.xml.{NodeSeq, Elem} import java.net._ import org.json4s.native.JsonMethods import java.io.{PrintWriter, StringWriter, Writer} import scala.xml.Group import scala.Some class Views(baseUrl: String) { val notSet = Not set def tryLink(s: String) = try { val url = new URL(s) {s} } catch { case _: MalformedURLException => {s} } def render(uri: URI) = { val s = uri.toURL.toExternalForm baseUrl + "render?url=" + URLEncoder.encode(s, "utf-8") } def delete(uri: URI) = { val s = uri.toURL.toExternalForm baseUrl + "render?url=" + URLEncoder.encode(s, "utf-8") + "&action=delete" } def examples: Array[Elem] = Array("minimal", "collection", "item", "queries", "template", "error") map { name => val cj = baseUrl + "examples/from-spec/" + name + ".collection+json" {name} } def getStackTrace(aThrowable: Throwable) = { val result: Writer = new StringWriter() val printWriter: PrintWriter = new PrintWriter(result) aThrowable.printStackTrace(printWriter) result.toString } def getName(link: Link, prefix: String, i: Int) = { prefix + link.name.orElse(link.prompt).getOrElse("#" + i) } def index = { def innerContent =

❤ Collection+JSON ❤

 
 

About

This is an interactive explorer for the Collection+JSON hypermedia. Give it an URL and it will render is as good as it can.

The purpose is twofold: it is a useful tool to inspect collections while developing or debugging an application. At the same time it's useful to show the power of hypermedia by showing how much a generic user agent can do by using only the generic Collection+JSON specification and not knowing anything about your application.

See also

There's a growing C+J community that's discussing on the Google Group.

Reading the (quite light) formal specification is useful. It also has a tutorial and some examples.

Using

Feel to use this service! However, note that it's running on a free Heroku instance so it might fail, be slow or otherwise useless.

If you want to run it against your own local servers you can either run it yourself, or use apps like localtunnel.com to make your application publicly available.

The Source Code

The source code is available here.

Examples

The Employee application is a set of resources with employees and departments. The application was made specifically for this explorer: explore now!

The specification contains a few example collections too which you can explore:

; def content =
{innerContent}
layout(content, None) } def data(request: CjRequest, response: Option[Either[Throwable, CjResponse]], targetParams: Map[String, String], collection: Option[Either[Throwable, Collection]]) = { def href(uri: URI) = { // val splits = uri.getPath.split('/') // for split in splits // a(href=url generator.render(split[1]), title='Explore #{split[1]}') #{split[0]} {uri.toURL.toExternalForm} } def link(link: Link) = {
Explore Raw
href
{href(link.href)}
rel
{tryLink(link.rel)}
name
{link.name.getOrElse(notSet)}
prompt
{link.prompt.getOrElse(notSet)}
render
{link.render.getOrElse(notSet)}
{if(link.render == "image")
Image
{link.name.getOrElse("")}
}
} def meta(implicit cj: Collection) =
version
{cj.version}
href
{cj.href.map(href).getOrElse(notSet)}
{if(cj.href.isDefined) { val url = cj.href.get

Explore Raw Delete

}} {cj.links match { case Nil => NodeSeq.Empty case _ =>

Collection Links

{cj.links.zipWithIndex.map { case (l, i) => val title = l.prompt.orElse(l.name).map(": " + _).getOrElse("") Group(Seq(, link(l))) }}
}}
// TODO: If the collection has prev/next links, add buttons to automatically navigate those. // TODO: Add ability to show the raw part of the collection. def items(cj: Collection) = { def itemLinks(cj: Collection) = { val first = cj.findLinkByRel("first") val prev = cj.findLinkByRel("prev") val next = cj.findLinkByRel("next") val last = cj.findLinkByRel("last") if(first.isDefined || prev.isDefined || next.isDefined || last.isDefined) {
{if(first.isDefined) First} {if(prev.isDefined) Previous} {if(next.isDefined) Next} {if(last.isDefined) Last}
} } {itemLinks(cj)} { cj.items.zipWithIndex.map { case (item, i) => val links = item.links

Item #{i + 1}

{if(item.href.isDefined) { val url = item.href.get }}
href
{item.href.map(href).getOrElse(notSet)}
{if(links.nonEmpty) {

Item Links

{links.zipWithIndex.map { case (l, i2) => Group(Seq(

Item Link #{i2 + 1}

, link(l))) }}
}}

Data

{item.data map { d => }}
{d.name}{d.value getOrElse ""}
{if(item.href.isDefined) { val uri = item.href.get }}
} } {itemLinks(cj)}
} def queries(implicit cj: Collection) = { {cj.queries.zipWithIndex map { case (query, i) => val prompt = query.prompt val name = query.name val title = prompt.orElse(name).getOrElse("Unnamed query #" + (i + 1))

{title}

{query.data map { d => val value = targetParams.get(d.name).orElse(d.value) getOrElse "" }}
}} } def template(implicit cj: Collection) =
{cj.href map {uri =>

The data will be submitted to: {href(uri)}

} getOrElse NodeSeq.Empty} {cj.template.get.data map { d => val value = targetParams.get(d.name).orElse(d.value).getOrElse("") }}
{cj.href match { case Some(uri) => case None =>

This collection has a template, but doesn't have a href which is required.

}}
def error(implicit cj: Collection) = { val e = cj.error.get val message = e.message map { m => val lines = m.split('\n') lines.map(s => scala.xml.Text(s): NodeSeq).reduce(_ ++
++ _) }
title
{Option(e.title).filter(!_.isEmpty).getOrElse(notSet)}
code
{e.code.getOrElse(notSet)}
message
{message.getOrElse("")}
} def httpRequest(req: CjRequest) = {
{req.url match { case None =>
Could not parse generated URL
{req.urlString}
case _ => }} { req.headers match { case None => case Some(headers) =>

Request

{headers map { case (header, values) => { values map { value => }}}}
{req.method} {req.url map { url => {url.toExternalForm} } getOrElse req.urlString}
{header}: {tryLink(value)}
}}
} def httpResponse(r: Either[Throwable, CjResponse]) = { r match { case Left(ex) => if (ex.isInstanceOf[ConnectException]) { "Unable to connect: " + ex.getMessage } else if (ex.isInstanceOf[UnknownHostException]) { "Unknown host: " + ex.getMessage } else {

Unknown error while connecting to remote server

{getStackTrace(ex)}
} case Right(res) =>
{res.headers map { case (header, values) => { values map { value => }}}}
{res.code} {res.status}
{header}: {tryLink(value)}
}} def parsedContent(implicit cj: Collection) =
{meta}
{if(cj.items.nonEmpty) {
{items(cj)}
}} {if(cj.queries.nonEmpty) {
{queries}
}} {if(cj.template.isDefined) {
{template}
}} {if(cj.error.isDefined) {
{error}
}}
def innerContent = {collection match { case None => case Some(Left(ex)) =>
{getStackTrace(ex)}
case Some(Right(cj)) => {try { parsedContent(cj) } catch { case ex: Exception =>
Unable to process model: {ex.getMessage}
}}
{JsonMethods.pretty(JsonMethods.render(cj.underlying))}
}}
{httpRequest(request)}
{response match { case None => case Some(some) =>
{httpResponse(some)}
}}
def sidebar = def content =
{sidebar}
{innerContent}
layout(content, None) } def layout(content: Elem, headSnippet: Option[String]) = Collection+JSON Explorer
{content}
}