Merge: nitweb: clean existing api
authorJean Privat <jean@pryen.org>
Mon, 30 May 2016 17:14:07 +0000 (13:14 -0400)
committerJean Privat <jean@pryen.org>
Mon, 30 May 2016 17:14:07 +0000 (13:14 -0400)
This PR cleans the Quick&Dirty(c) JSON API of nitweb to use the model_json one.

Modified services:
* /uml -> /api/um/:entity
* /code -> /api/code/:entity
* /random -> /api/random
* /search -> /api/search

Also do some renaming to match `popcorn` philosophy.

Pull-Request: #2137
Reviewed-by: Jean Privat <jean@pryen.org>

src/nitweb.nit
src/web/model_api.nit [new file with mode: 0644]
src/web/web.nit
src/web/web_actions.nit
src/web/web_base.nit
src/web/web_views.nit

index c055ee9..1df4153 100644 (file)
@@ -48,12 +48,10 @@ private class NitwebPhase
                var port = toolcontext.opt_port.value
 
                var app = new App
-               app.use("/random", new RandomAction(model))
-               app.use("/doc/:namespace", new DocAction(model, modelbuilder))
-               app.use("/code/:namespace", new CodeAction(model, modelbuilder))
-               app.use("/search/:namespace", new SearchAction(model))
-               app.use("/uml/:namespace", new UMLDiagramAction(model, mainmodule))
-               app.use("/", new TreeAction(model))
+
+               app.use("/api", new APIRouter(model, modelbuilder, mainmodule))
+               app.use("/doc/:namespace", new DocAction(model, mainmodule, modelbuilder))
+               app.use("/", new TreeAction(model, mainmodule))
 
                app.listen(host, port.to_i)
        end
diff --git a/src/web/model_api.nit b/src/web/model_api.nit
new file mode 100644 (file)
index 0000000..85a2360
--- /dev/null
@@ -0,0 +1,251 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module model_api
+
+import web_base
+import highlight
+import uml
+
+# Specific handler for nitweb API.
+abstract class APIHandler
+       super ModelHandler
+
+       # The JSON API does not filter anything by default.
+       #
+       # So we can cache the model view.
+       var view: ModelView is lazy do
+               var view = new ModelView(model)
+               view.min_visibility = private_visibility
+               view.include_fictive = true
+               view.include_empty_doc = true
+               view.include_attribute = true
+               view.include_test_suite = true
+               return view
+       end
+
+       # Try to load the mentity from uri with `/:id`.
+       #
+       # Send 400 if `:id` is null.
+       # Send 404 if no entity is found.
+       # Return null in both cases.
+       fun mentity_from_uri(req: HttpRequest, res: HttpResponse): nullable MEntity do
+               var id = req.param("id")
+               if id == null then
+                       res.error 400
+                       return null
+               end
+               var mentity = find_mentity(view, id)
+               if mentity == null then
+                       res.error 404
+               end
+               return mentity
+       end
+end
+
+# Group all api handlers in one router.
+class APIRouter
+       super Router
+
+       # Model to pass to handlers.
+       var model: Model
+
+       # ModelBuilder to pass to handlers.
+       var modelbuilder: ModelBuilder
+
+       # Mainmodule to pass to handlers.
+       var mainmodule: MModule
+
+       init do
+               use("/list", new APIList(model, mainmodule))
+               use("/search", new APISearch(model, mainmodule))
+               use("/random", new APIRandom(model, mainmodule))
+               use("/entity/:id", new APIEntity(model, mainmodule))
+               use("/code/:id", new APIEntityCode(model, mainmodule, modelbuilder))
+               use("/uml/:id", new APIEntityUML(model, mainmodule))
+       end
+end
+
+# Search mentities from a query string.
+#
+# Example: `GET /search?q=Arr`
+class APISearch
+       super APIHandler
+
+       redef fun get(req, res) do
+               var q = req.string_arg("q")
+               if q == null then
+                       res.error 400
+                       return
+               end
+               var arr = new JsonArray
+               for mentity in view.mentities do
+                       if mentity.name.has_prefix(q) then arr.add mentity
+               end
+               res.json arr
+       end
+end
+
+# List all mentities.
+#
+# MEntities can be filtered on their kind using the `k` parameter.
+# Allowed kinds are `package`, `group`, `module`, `class`, `classdef`, `property`, `propdef`.
+#
+# List size can be limited with the `n` parameter.
+#
+# Example: `GET /list?k=module?n=10`
+class APIList
+       super APIHandler
+
+       # List mentities depending on the `k` kind parameter.
+       fun list_mentities(req: HttpRequest): Array[MEntity] do
+               var k = req.string_arg("k")
+               var mentities = new Array[MEntity]
+               if k == "package" then
+                       for mentity in view.mpackages do mentities.add mentity
+               else if k == "group" then
+                       for mentity in view.mgroups do mentities.add mentity
+               else if k == "module" then
+                       for mentity in view.mmodules do mentities.add mentity
+               else if k == "class" then
+                       for mentity in view.mclasses do mentities.add mentity
+               else if k == "classdef" then
+                       for mentity in view.mclassdefs do mentities.add mentity
+               else if k == "property" then
+                       for mentity in view.mproperties do mentities.add mentity
+               else if k == "propdef" then
+                       for mentity in view.mpropdefs do mentities.add mentity
+               else
+                       for mentity in view.mentities do mentities.add mentity
+               end
+               return mentities
+       end
+
+       # Limit mentities depending on the `n` parameter.
+       fun limit_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
+               var n = req.int_arg("n")
+               if n != null then
+                       return mentities.sub(0, n)
+               end
+               return mentities
+       end
+
+       redef fun get(req, res) do
+               var mentities = list_mentities(req)
+               mentities = limit_mentities(req, mentities)
+               var arr = new JsonArray
+               for mentity in mentities do arr.add mentity
+               res.json arr
+       end
+end
+
+# Return a random list of MEntities.
+#
+# Example: `GET /random?n=10&k=module`
+class APIRandom
+       super APIList
+
+       # Randomize mentities order.
+       fun randomize_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
+               var res = mentities.to_a
+               res.shuffle
+               return res
+       end
+
+       redef fun get(req, res) do
+               var mentities = list_mentities(req)
+               mentities = limit_mentities(req, mentities)
+               mentities = randomize_mentities(req, mentities)
+               var arr = new JsonArray
+               for mentity in mentities do arr.add mentity
+               res.json arr
+       end
+end
+
+# Return the JSON representation of a MEntity.
+#
+# Example: `GET /entity/core::Array`
+class APIEntity
+       super APIHandler
+
+       redef fun get(req, res) do
+               var mentity = mentity_from_uri(req, res)
+               if mentity == null then return
+               res.json mentity.api_json(self)
+       end
+end
+
+
+# Return a UML representation of MEntity.
+#
+# Example: `GET /entity/core::Array/uml`
+class APIEntityUML
+       super APIHandler
+
+       redef fun get(req, res) do
+               var mentity = mentity_from_uri(req, res)
+               var dot
+               if mentity isa MClassDef then mentity = mentity.mclass
+               if mentity isa MClass then
+                       var uml = new UMLModel(view, mainmodule)
+                       dot = uml.generate_class_uml.write_to_string
+               else if mentity isa MModule then
+                       var uml = new UMLModel(view, mentity)
+                       dot = uml.generate_package_uml.write_to_string
+               else
+                       res.error 404
+                       return
+               end
+               res.send render_svg(dot)
+       end
+
+       # Render a `dot` string as a svg image.
+       fun render_svg(dot: String): String do
+               var proc = new ProcessDuplex("dot", "-Tsvg")
+               var svg = proc.write_and_read(dot)
+               proc.close
+               proc.wait
+               return svg
+       end
+end
+
+# Return the source code of MEntity.
+#
+# Example: `GET /entity/core::Array/code`
+class APIEntityCode
+       super APIHandler
+
+       # Modelbuilder used to access sources.
+       var modelbuilder: ModelBuilder
+
+       redef fun get(req, res) do
+               var mentity = mentity_from_uri(req, res)
+               if mentity == null then return
+               var source = render_source(mentity)
+               if source == null then
+                       res.error 404
+                       return
+               end
+               res.send source
+       end
+
+       # Highlight `mentity` source code.
+       private fun render_source(mentity: MEntity): nullable HTMLTag do
+               var node = modelbuilder.mentity2node(mentity)
+               if node == null then return null
+               var hl = new HighlightVisitor
+               hl.enter_visit node
+               return hl.html
+       end
+end
index 6f581f4..c6a12f0 100644 (file)
@@ -16,3 +16,4 @@
 module web
 
 import web_actions
+import model_api
index e9198ae..cb193fd 100644 (file)
@@ -20,7 +20,7 @@ import uml
 
 # Display the tree of all loaded mentities.
 class TreeAction
-       super ModelAction
+       super ModelHandler
 
        redef fun get(req, res) do
                var model = init_model_view(req)
@@ -29,51 +29,9 @@ class TreeAction
        end
 end
 
-# Display the list of mentities matching `namespace`.
-class SearchAction
-       super ModelAction
-
-       # TODO handle more than full namespaces.
-       redef fun get(req, res) do
-               var namespace = req.param("namespace")
-               var model = init_model_view(req)
-               var mentity = find_mentity(model, namespace)
-               if mentity == null then
-                       res.error(404)
-                       return
-               end
-               if req.is_json_asked then
-                       res.json(mentity.to_json)
-                       return
-               end
-               var view = new HtmlResultPage(namespace or else "null", [mentity])
-               res.send_view(view)
-       end
-end
-
-# Display a MEntity source code.
-class CodeAction
-       super ModelAction
-
-       # Modelbuilder used to access sources.
-       var modelbuilder: ModelBuilder
-
-       redef fun get(req, res) do
-               var namespace = req.param("namespace")
-               var model = init_model_view(req)
-               var mentity = find_mentity(model, namespace)
-               if mentity == null then
-                       res.error(404)
-                       return
-               end
-               var view = new HtmlSourcePage(modelbuilder, mentity)
-               res.send_view(view)
-       end
-end
-
 # Display the doc of a MEntity.
 class DocAction
-       super ModelAction
+       super ModelHandler
 
        # Modelbuilder used to access sources.
        var modelbuilder: ModelBuilder
@@ -86,76 +44,7 @@ class DocAction
                        res.error(404)
                        return
                end
-               if req.is_json_asked then
-                       res.json(mentity.to_json)
-                       return
-               end
-
                var view = new HtmlDocPage(modelbuilder, mentity)
                res.send_view(view)
        end
 end
-
-# Return an UML diagram for `namespace`.
-class UMLDiagramAction
-       super ModelAction
-
-       # Mainmodule used for hierarchy flattening.
-       var mainmodule: MModule
-
-       redef fun get(req, res) do
-               var namespace = req.param("namespace")
-               var model = init_model_view(req)
-               var mentity = find_mentity(model, namespace)
-               if mentity == null then
-                       res.error(404)
-                       return
-               end
-
-               var dot
-               if mentity isa MClassDef then mentity = mentity.mclass
-               if mentity isa MClass then
-                       var uml = new UMLModel(model, mainmodule)
-                       dot = uml.generate_class_uml.write_to_string
-               else if mentity isa MModule then
-                       var uml = new UMLModel(model, mentity)
-                       dot = uml.generate_package_uml.write_to_string
-               else
-                       res.error(404)
-                       return
-               end
-               var view = new HtmlDotPage(dot, mentity.as(not null).html_name)
-               res.send_view(view)
-       end
-end
-
-# Return a random list of MEntities.
-class RandomAction
-       super ModelAction
-
-       redef fun get(req, res) do
-               var n = req.int_arg("n") or else 10
-               var k = req.string_arg("k") or else "modules"
-               var model = init_model_view(req)
-               var mentities: Array[MEntity]
-               if k == "modules" then
-                       mentities = model.mmodules.to_a
-               else if k == "classdefs" then
-                       mentities = model.mclassdefs.to_a
-               else
-                       mentities = model.mpropdefs.to_a
-               end
-               mentities.shuffle
-               mentities = mentities.sub(0, n)
-               if req.is_json_asked then
-                       var json = new JsonArray
-                       for mentity in mentities do
-                               json.add mentity.to_json
-                       end
-                       res.json(json)
-                       return
-               end
-               var view = new HtmlResultPage("random", mentities)
-               res.send_view(view)
-       end
-end
index 258ec60..ec9bf75 100644 (file)
@@ -17,15 +17,19 @@ module web_base
 
 import model::model_views
 import model::model_json
+import doc_down
 import popcorn
 
 # Specific nitcorn Action that uses a Model
-class ModelAction
+class ModelHandler
        super Handler
 
        # Model to use.
        var model: Model
 
+       # MModule used to flatten model.
+       var mainmodule: MModule
+
        # Find the MEntity ` with `full_name`.
        fun find_mentity(model: ModelView, full_name: nullable String): nullable MEntity do
                if full_name == null then return null
@@ -58,9 +62,147 @@ redef class HttpResponse
        fun send_view(view: NitView, status: nullable Int) do send(view.render, status)
 end
 
-redef class HttpRequest
-       # Does the client asked for a json formatted response?
-       #
-       # Checks the URL get parameter `?json=true`.
-       fun is_json_asked: Bool do return bool_arg("json") or else false
+redef class MEntity
+
+       # URL to `self` within the web interface.
+       fun web_url: String is abstract
+
+       # URL to `self` within the JSON api.
+       fun api_url: String do return "/api/entity/" / full_name
+
+       redef fun json do
+               var obj = super
+               obj["web_url"] = web_url
+               obj["api_url"] = api_url
+               return obj
+       end
+
+       # Get the full json repesentation of `self` with MEntityRefs resolved.
+       fun api_json(handler: ModelHandler): JsonObject do return json
+end
+
+redef class MEntityRef
+       redef fun json do
+               var obj = super
+               obj["web_url"] = mentity.web_url
+               obj["api_url"] = mentity.api_url
+               obj["name"] = mentity.name
+               obj["mdoc"] = mentity.mdoc_or_fallback
+               obj["visibility"] = mentity.visibility
+               obj["location"] = mentity.location
+               var modifiers = new JsonArray
+               for modifier in mentity.collect_modifiers do
+                       modifiers.add modifier
+               end
+               obj["modifiers"] = modifiers
+               return obj
+       end
+end
+
+redef class MDoc
+
+       # Add doc down processing
+       redef fun json do
+               var obj = super
+               obj["synopsis"] = synopsis
+               obj["documentation"] = documentation
+               obj["comment"] = comment
+               obj["html_synopsis"] = html_synopsis.write_to_string
+               obj["html_documentation"] = html_documentation.write_to_string
+               obj["html_comment"] = html_comment.write_to_string
+               return obj
+       end
+end
+
+redef class MPackage
+       redef var web_url = "/package/{full_name}" is lazy
+end
+
+redef class MGroup
+       redef var web_url = "/group/{full_name}" is lazy
+end
+
+redef class MModule
+       redef var web_url = "/module/{full_name}" is lazy
+
+       redef fun api_json(handler) do
+               var obj = super
+               obj["intro_mclassdefs"] = to_mentity_refs(collect_intro_mclassdefs(private_view))
+               obj["redef_mclassdefs"] = to_mentity_refs(collect_redef_mclassdefs(private_view))
+               obj["imports"] = to_mentity_refs(in_importation.direct_greaters)
+               return obj
+       end
+end
+
+redef class MClass
+       redef var web_url = "/class/{full_name}" is lazy
+
+       redef fun api_json(handler) do
+               var obj = super
+               obj["all_mproperties"] = to_mentity_refs(collect_accessible_mproperties(private_view))
+               obj["intro_mproperties"] = to_mentity_refs(collect_intro_mproperties(private_view))
+               obj["redef_mproperties"] = to_mentity_refs(collect_redef_mproperties(private_view))
+               var poset = hierarchy_poset(handler.mainmodule, private_view)
+               obj["parents"] = to_mentity_refs(poset[self].direct_greaters)
+               return obj
+       end
+end
+
+redef class MClassDef
+       redef var web_url = "/classdef/{full_name}" is lazy
+
+       redef fun json do
+               var obj = super
+               obj["intro"] = to_mentity_ref(mclass.intro)
+               obj["mpackage"] = to_mentity_ref(mmodule.mpackage)
+               return obj
+       end
+
+       redef fun api_json(handler) do
+               var obj = super
+               obj["intro_mpropdefs"] = to_mentity_refs(collect_intro_mpropdefs(private_view))
+               obj["redef_mpropdefs"] = to_mentity_refs(collect_redef_mpropdefs(private_view))
+               return obj
+       end
+end
+
+redef class MProperty
+       redef var web_url = "/property/{full_name}" is lazy
+
+       redef fun json do
+               var obj = super
+               obj["intro_mclass"] = to_mentity_ref(intro_mclassdef.mclass)
+               obj["mpackage"] = to_mentity_ref(intro_mclassdef.mmodule.mpackage)
+               return obj
+       end
+end
+
+redef class MPropDef
+       redef var web_url = "/propdef/{full_name}" is lazy
+
+       redef fun json do
+               var obj = super
+               obj["intro"] = to_mentity_ref(mproperty.intro)
+               obj["intro_mclassdef"] = to_mentity_ref(mproperty.intro.mclassdef)
+               obj["mmodule"] = to_mentity_ref(mclassdef.mmodule)
+               obj["mgroup"] = to_mentity_ref(mclassdef.mmodule.mgroup)
+               obj["mpackage"] = to_mentity_ref(mclassdef.mmodule.mpackage)
+               return obj
+       end
+end
+
+redef class MClassType
+       redef var web_url = mclass.web_url is lazy
+end
+
+redef class MNullableType
+       redef var web_url = mtype.web_url is lazy
+end
+
+redef class MParameterType
+       redef var web_url = mclass.web_url is lazy
+end
+
+redef class MVirtualType
+       redef var web_url = mproperty.web_url is lazy
 end
index 58016e2..d8f8c9f 100644 (file)
@@ -35,34 +35,6 @@ class HtmlHomePage
        end
 end
 
-# Display a search results list.
-class HtmlResultPage
-       super NitView
-
-       # Initial query.
-       var query: String
-
-       # Result set
-       var results: Array[MEntity]
-
-       redef fun render do
-               var tpl = new Template
-               tpl.add new Header(1, "Results for {query}")
-               if results.is_empty then
-                       tpl.add "<p>No result for {query}.<p>"
-                       return tpl
-               end
-               var list = new UnorderedList
-               for mentity in results do
-                       var link = mentity.html_link
-                       link.text = mentity.html_full_name
-                       list.add_li new ListItem(link)
-               end
-               tpl.add list
-               return tpl
-       end
-end
-
 # Display the source for each mentities
 class HtmlSourcePage
        super NitView
@@ -119,29 +91,3 @@ class HtmlDocPage
                return tpl
        end
 end
-
-# Display the source for each mentities
-class HtmlDotPage
-       super NitView
-
-       # Dot to process.
-       var dot: Text
-
-       # Page title.
-       var title: String
-
-       redef fun render do
-               var tpl = new Template
-               tpl.add new Header(1, title)
-               tpl.add render_dot
-               return tpl
-       end
-
-       private fun render_dot: String do
-               var proc = new ProcessDuplex("dot", "-Tsvg", "-Tcmapx")
-               var svg = proc.write_and_read(dot)
-               proc.close
-               proc.wait
-               return svg
-       end
-end