From c9b80a708b760090c240c28c2f7329962d13bdab Mon Sep 17 00:00:00 2001 From: Simon Redfern Date: Thu, 20 Aug 2015 22:05:49 +0200 Subject: [PATCH] Refactoring live-docs --- src/main/scala/code/lib/ObpAPI.scala | 30 +++- src/main/scala/code/snippet/LiveDocs.scala | 185 +++++++++++---------- src/main/webapp/live-docs.html | 13 +- 3 files changed, 126 insertions(+), 102 deletions(-) diff --git a/src/main/scala/code/lib/ObpAPI.scala b/src/main/scala/code/lib/ObpAPI.scala index 3e2e2c05..06be67a7 100644 --- a/src/main/scala/code/lib/ObpAPI.scala +++ b/src/main/scala/code/lib/ObpAPI.scala @@ -5,8 +5,7 @@ import net.liftweb.json.JObject import net.liftweb.json.JsonDSL._ import net.liftweb.common.Box import net.liftweb.common.Full -import net.liftweb.json.JsonAST.JValue -import net.liftweb.json.JsonAST.JInt +import net.liftweb.json.JsonAST.{JValue, JInt} import net.liftweb.json.JDouble import net.liftweb.common.Empty import java.net.URL @@ -627,19 +626,36 @@ object ObpJson { case class PermissionsJson(permissions : Option[List[PermissionJson]]) + //////////////////////////////////////// + // Copied from OBP-API JSONFactory1_4_0 + // TODO: Import these and others from API jar file? - // Import these from API jar file? + // Matches OBP-API representation of Resource Docs etc. Used to describe where an API call is implemented + case class ImplementedByJson ( + version : String, // Short hand for the version e.g. "1_4_0" means Implementations1_4_0 + function : String // The val / partial function that implements the call e.g. "getBranches" + ) + + + // Used to describe the OBP API calls for documentation and API discovery purposes case class ResourceDocJson(id: Int, - verb: String, - url: String, - description: String) + request_verb: String, + request_url: String, + description: String, + request_body: JValue, + response_body: JValue, + implemented_by: ImplementedByJson) case class ResourceDocsJson (resource_docs : List[ResourceDocJson]) + /////////////////////////////////////////// + + // Internal representation of the ResourceDoc (may differ from the OBP API representation) case class ResourceDoc(id: Int, verb: String, url: String, - description: String) + description: String, + request_body: JValue) case class ResourceDocs (resourceDocs : List[ResourceDoc]) diff --git a/src/main/scala/code/snippet/LiveDocs.scala b/src/main/scala/code/snippet/LiveDocs.scala index cc675347..243f4878 100644 --- a/src/main/scala/code/snippet/LiveDocs.scala +++ b/src/main/scala/code/snippet/LiveDocs.scala @@ -3,14 +3,19 @@ package code.snippet import _root_.net.liftweb._ import code.lib.ObpJson.ResourceDoc import code.lib.{ObpPost, ObpGet} +//import code.snippet.CallUrlForm._ import net.liftweb.http.S -import net.liftweb.json.{JsonParser, JsonAST} +import net.liftweb.json.{Extraction, JsonParser, JsonAST} import net.liftweb.json.JsonAST.{JField, JObject, JValue} import _root_.scala.xml.{NodeSeq, Text} import net.liftweb._ +// for compact render +import net.liftweb.json._ + + import common._ import net.liftweb.util.Helpers._ @@ -28,32 +33,26 @@ Present a list of OBP resource URLs */ class LiveDocs extends Loggable { def showResources = { - case class Resource(id: String, - verb: String, - url: String, - description: String, - representation: String) // Get the requested version from the url parameter and default if none - val apiVersion = S.param("api-version").getOrElse("1.4.0") + val apiVersionRequested = S.param("api-version").getOrElse("1.4.0") - if (apiVersion != "1.4.0") S.notice("Note: Only 1.4.0 is currently supported") + if (apiVersionRequested != "1.4.0") S.notice("Note: Only 1.4.0 is currently supported") - logger.info (s"API version requested is: $apiVersion") + logger.info (s"API version requested is: $apiVersionRequested") // Get a list of resource docs from the API server // This will throw exception if resource_docs key is not populated // Convert the json representation to ResourceDoc (pretty much a one to one mapping) + + val resources = for { r <- getResourceDocsJson.map(_.resource_docs).get - } yield ResourceDoc(id = r.id, verb = r.verb, url = r.url, description = r.description) + } yield ResourceDoc(id = r.id, verb = r.request_verb, url = r.request_url, description = r.description, request_body = r.request_body) // Render the resources into a (nested) table. - // This could probably be improved. - // So far we can't (why?) set value of input field at render time like this ".resource_id_input [value]" #> s"lslslsls${i.id}" - // so have to work around and set the input fields via the try_me_button onclick javascript. - // Notes on escaping strings + // Notes on escaping strings: // To have a $ in the resulting string use two: $$ // Can't escape " with \" or use triple quoted string in the string interpolation so use the replace hack @@ -66,9 +65,72 @@ class LiveDocs extends Loggable { } + // Constant + val apiVersion = "v1.4.0" + + + + var resourceId = "" + var requestVerb = "" + var requestUrl = "" + var requestBody = "{}" + + + + def process(): JsCmd = { + logger.info(s"requestUrl is $requestUrl") + logger.info(s"resourceId is $resourceId") + + // Create json object from input string + val jsonObject = JsonParser.parse(requestBody).asInstanceOf[JObject] + // Call the url with optional body and put the response into the appropriate result div + SetHtml("result_" + resourceId, Text(getResponse(apiVersion, requestUrl, requestVerb, jsonObject))) & + // This applies json highlighting to the json + Run ("$('pre code').each(function(i, block) { hljs.highlightBlock(block);});") + } + + + def getResponse (apiVersion : String, url : String, resourceVerb: String, json : JValue) : String = { + + implicit val formats = net.liftweb.json.DefaultFormats + + val urlWithVersion = s"/$apiVersion$url" + + // TODO: Handle POST requests + val responseBodyBox = { + resourceVerb match { + case "GET" => ObpGet(urlWithVersion) + case "POST" => ObpPost(urlWithVersion, json) + case _ => { + val failMsg = s"Live Docs says: Unsupported resourceVerb: $resourceVerb. Url requested was: $url" + logger.warn(failMsg) + Failure(failMsg) + } + } + } + + + logger.info(s"responseBodyBox is ${responseBodyBox}") + + // Handle the contents of the Box + val responseBody = + responseBodyBox match { + case Full(json) => writePretty(json) + case Empty => "Empty: API did not return anything" + case Failure(message, _, _) => "Failure: " + message + } + + logger.info(s"responseBody is $responseBody") + responseBody + } + + // In case we use Extraction.decompose + implicit val formats = net.liftweb.json.DefaultFormats + + // Note! This is the return of the function. // All the replacements you want to do *must be chained here together at the end of the function*. - // Also, you can't chain replaceWith (alias for #>) + // Also, you can't use replaceWith (the alias for #>) to chain // See the following for some examples. // http://blog.knoldus.com/2013/03/08/lift-web-basics-of-using-snippets-for-a-beginner/ @@ -84,86 +146,29 @@ class LiveDocs extends Loggable { ".resource_url" #> i.url & ".resource_description" #> i.description & ".resource_representation" #> "JSON" & - ".resource_url_td [id]" #> s"resource_url_td_${i.id}" & - ".resource_verb_td [id]" #> s"resource_verb_td_${i.id}" & + ".resource_url_td [id]" #> s"resource_url_td_${i.id}" & // Probably don't need this now + ".resource_verb_td [id]" #> s"resource_verb_td_${i.id}" & // Probably don't need this now ".url_caller [id]" #> s"url_caller_${i.id}" & - "@request_url_input [id]" #> s"request_url_input_${i.id}" & - "@request_url_input [value]" #> s"${i.url}" & - ".try_me_button [onclick]" #> s"$$(DOUBLE-QUOTE#url_caller_${i.id}DOUBLE-QUOTE).fadeToggle(); $$(DOUBLE-QUOTE#request_url_input_${i.id}DOUBLE-QUOTE).val($$(DOUBLE-QUOTE#resource_url_td_${i.id}DOUBLE-QUOTE)[0].innerHTML); $$(DOUBLE-QUOTE#resource_id_input_${i.id}DOUBLE-QUOTE).val(DOUBLE-QUOTE${i.id}DOUBLE-QUOTE); $$(DOUBLE-QUOTE#request_verb_input_${i.id}DOUBLE-QUOTE).val($$(DOUBLE-QUOTE#resource_verb_td_${i.id}DOUBLE-QUOTE)[0].innerHTML);".replaceAll("DOUBLE-QUOTE",""""""") & + ".try_me_button [onclick]" #> s"$$(DOUBLE-QUOTE#url_caller_${i.id}DOUBLE-QUOTE).fadeToggle();".replaceAll("DOUBLE-QUOTE",""""""") & ".result [id]" #> s"result_${i.id}" & - "@resource_id_input [id]" #> s"resource_id_input_${i.id}" & - "@request_verb_input [id]" #> s"request_verb_input_${i.id}" & "@request_body [id]" #> s"request_body_${i.id}" & - "@request_body [style]" #> s"display: ${displayBody(i.verb)};" - } - } -} - - -/* -Call an OBP URL and return the response to the browser in JSON form. -*/ -object CallUrlForm extends Loggable { - - - def getResponse (apiVersion : String, url : String, resourceVerb: String, json : JValue) : String = { - - implicit val formats = net.liftweb.json.DefaultFormats - - // TODO: Handle POST requests - val responseBodyBox = { - resourceVerb match { - case "GET" => ObpGet(url) - case "POST" => ObpPost(url, json) - case _ => { - val failMsg = s"Live Docs says: Unsupported resourceVerb: $resourceVerb." - logger.warn(failMsg) - Failure(failMsg) - } - } - } - - - logger.info(s"responseBodyBox is ${responseBodyBox}") - - // Handle the contents of the Box - val responseBody = - responseBodyBox match { - case Full(json) => writePretty(json) - case Empty => "Empty: API did not return anything" - case Failure(message, _, _) => "Failure: " + message - } - - logger.info(s"responseBody is $responseBody") - responseBody - } - - - def render = { - - var apiVersion = "/obp/v1.4.0/" - var resourceId = "" - var requestVerb = "" - var requestUrl = "" - var requestBody = "{}" - - def process(): JsCmd = { - // Create json object from input string - val jsonObject = JsonParser.parse(requestBody).asInstanceOf[JObject] - // Call the url with optional body and put the response into the appropriate result div - SetHtml("result_" + resourceId, Text(getResponse(apiVersion, requestUrl, requestVerb, jsonObject))) & - // This applies json highlighting to the json - Run ("$('pre code').each(function(i, block) { hljs.highlightBlock(block);});") - } - + "@request_body [style]" #> s"display: ${displayBody(i.verb)};" & + ////// // The form field (on the left) is bound to the variable (urlToCall) // (However, updating the var here does not seem to update the form field value) - "@resource_id_input" #> text(resourceId, s => resourceId = s, "type" -> "hidden") & - "@request_verb_input" #> text(requestVerb, s => requestVerb = s, "type" -> "hidden") & - "@request_url_input" #> text(requestUrl, s => requestUrl = s, "maxlength" -> "255", "size" -> "100") & - "@request_body_input" #> text(requestBody, s => requestBody = s, "type" -> "text") & + // TODO use this approach. + // We provide a default value (i.url) and bind the user input to requestUrl. requestURL is available in the function process + "@request_url_input" #> text(i.url, s => requestUrl = s, "maxlength" -> "255", "size" -> "100", "id" -> s"request_url_input_${i.id}") & + // Extraction.decompose creates json representation of JObject. + "@request_body_input" #> text(pretty(render(i.request_body)), s => requestBody = s, "type" -> "text") & + // We're not using the id at the moment + "@request_verb_input" #> text(i.verb, s => requestVerb = s, "type" -> "text", "id" -> s"request_verb_input_${i.id}") & + "@resource_id_input" #> text(i.id.toString, s => resourceId = s, "type" -> "text", "id" -> s"resource_id_input_${i.id}") & // Replace the type=submit with Javascript that makes the ajax call. - "type=submit" #> ajaxSubmit("Call", process) - + // ".call_button" #> ajaxSubmit("Call", process(i.id, apiVersion, i.url, i.verb,"{}")) + //".call_button" #> ajaxSubmit("Call", process(123, "apiVersion", "i.url", "i.verb","{}")) + ".call_button" #> ajaxSubmit("Call", process) + // def process(resourceId: String, apiVersion : String, requestUrl: String, requestVerb: String, requestBody: String = "{}"): JsCmd = { + } } } \ No newline at end of file diff --git a/src/main/webapp/live-docs.html b/src/main/webapp/live-docs.html index 0a84c35b..ff34d2a5 100644 --- a/src/main/webapp/live-docs.html +++ b/src/main/webapp/live-docs.html @@ -30,21 +30,24 @@ - +
-
+
- - + +
+ + + - +