*: update redefs of `to_json`
[nit.git] / src / web / web_base.nit
index 5d7dbde..60f7f2b 100644 (file)
@@ -17,111 +17,354 @@ module web_base
 
 import model::model_views
 import model::model_json
-import nitcorn
+import doc_down
+import popcorn
+import popcorn::pop_config
+import popcorn::pop_repos
 
-# Nitcorn server runned by `nitweb`.
-#
-# Usage:
+# Nitweb config file.
+class NitwebConfig
+       super AppConfig
+
+       redef var default_db_name = "nitweb"
+
+       # Model to use.
+       var model: Model
+
+       # MModule used to flatten model.
+       var mainmodule: MModule
+
+       # Modelbuilder used to access sources.
+       var modelbuilder: ModelBuilder
+
+       # 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
+end
+
+# Specific handler for the nitweb API.
+abstract class APIHandler
+       super Handler
+
+       # App config.
+       var config: NitwebConfig
+
+       # 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
+               return model.mentity_by_full_name(full_name.from_percent_encoding)
+       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.api_error(400, "Expected mentity full name")
+                       return null
+               end
+               var mentity = find_mentity(config.view, id)
+               if mentity == null then
+                       res.api_error(404, "MEntity `{id}` not found")
+               end
+               return mentity
+       end
+end
+
+# A Rooter dedicated to APIHandlers.
+class APIRouter
+       super Router
+
+       # App config
+       var config: NitwebConfig
+end
+
+redef class HttpResponse
+
+       # Return an HTTP error response with `status`
+       #
+       # Like the rest of the API, errors are formated as JSON:
+       # ~~~json
+       # { "status": 404, "message": "Not found" }
+       # ~~~
+       fun api_error(status: Int, message: String) do
+               json(new APIError(status, message), status)
+       end
+end
+
+# An error returned by the API.
 #
-# ~~~nitish
-# var srv = new NitServer("localhost", 3000)
-# srv.routes.add new Route("/", new MyAction)
-# src.listen
-# ~~~
-class NitServer
+# Can be serialized to json.
+class APIError
+       super Jsonable
 
-       # Host to bind.
-       var host: String
+       # Reponse status
+       var status: Int
 
-       # Port to use.
-       var port: Int
+       # Response error message
+       var message: String
 
-       # Routes knwon by the server.
-       var routes = new Array[Route]
+       # Json Object for this error
+       var json: JsonObject is lazy do
+               var obj = new JsonObject
+               obj["status"] = status
+               obj["message"] = message
+               return obj
+       end
+
+       redef fun serialize_to(v) do json.serialize_to(v)
+end
 
-       # Start listen on `host:port`.
-       fun listen do
-               var iface = "{host}:{port}"
-               print "Launching server on http://{iface}/"
+# Fullname representation that can be used to build decorated links
+#
+# * MPackage: `mpackage_name`
+# * MGroup: `(mpackage_name::)mgroup_name`
+class Namespace
+       super Array[nullable NSEntity]
+       super NSEntity
+       serialize
+
+       redef fun to_s do return self.join("")
+       redef fun serialize_to(v) do to_a.serialize_to(v)
+end
+
+# Something that goes in a Namespace
+#
+# Can be either:
+# * a `NSToken` for tokens like `::`, `>` and `$`
+# * a `MSRef` for references to mentities
+interface NSEntity
+       super Jsonable
+end
+
+# A reference to a MEntity that can be rendered as a link.
+#
+# We do not reuse `MEntityRef` ref since NSRef can be found in a ref and create
+# an infinite loop.
+class NSRef
+       super NSEntity
+       serialize
 
-               var vh = new VirtualHost(iface)
-               for route in routes do vh.routes.add route
+       # The mentity to link to/
+       var mentity: MEntity
 
-               var fac = new HttpFactory.and_libevent
-               fac.config.virtual_hosts.add vh
-               fac.run
+       redef fun serialize_to(v) do
+               var obj = new JsonObject
+               obj["web_url"] = mentity.web_url
+               obj["api_url"] = mentity.api_url
+               obj["name"] = mentity.name
+               obj.serialize_to(v)
        end
 end
 
-# Specific nitcorn Action for nitweb.
-class NitAction
-       super Action
+# A namespace token representation
+#
+# Used for namespace tokens like `::`, `>` and `$`
+redef class String
+       super NSEntity
+end
+
+redef class MEntity
 
-       # Link to the NitServer that runs this action.
-       var srv: NitServer
+       # URL to `self` within the web interface.
+       fun web_url: String do return "/doc/" / full_name
 
-       # Build a custom http response for errors.
-       fun render_error(code: Int, message: String): HttpResponse do
-               var response = new HttpResponse(code)
-               var tpl = new Template
-               tpl.add "<h1>Error {code}</h1>"
-               tpl.add "<pre><code>{message.html_escape}</code></pre>"
-               response.body = tpl.write_to_string
-               return response
+       # 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["namespace"] = namespace
+               obj["web_url"] = web_url
+               obj["api_url"] = api_url
+               return obj
        end
 
-       # Render a view as a HttpResponse 200.
-       fun render_view(view: NitView): HttpResponse do
-               var response = new HttpResponse(200)
-               response.body = view.render.write_to_string
-               return response
+       # Get the full json repesentation of `self` with MEntityRefs resolved.
+       fun api_json(handler: APIHandler): JsonObject do return full_json
+
+       # Return `self.full_name` as an object that can be serialized to json.
+       fun namespace: nullable Namespace do return null
+
+       # Return a new NSRef to `self`.
+       fun to_ns_ref: NSRef do return new NSRef(self)
+end
+
+redef class MEntityRef
+       redef fun json do
+               var obj = super
+               obj["namespace"] = mentity.namespace
+               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
+               var modifiers = new JsonArray
+               for modifier in mentity.collect_modifiers do
+                       modifiers.add modifier
+               end
+               obj["modifiers"] = modifiers
+               var mentity = self.mentity
+               if mentity isa MMethod then
+                       obj["msignature"] = mentity.intro.msignature
+               else if mentity isa MMethodDef then
+                       obj["msignature"] = mentity.msignature
+               else if mentity isa MVirtualTypeProp then
+                       obj["bound"] = to_mentity_ref(mentity.intro.bound)
+               else if mentity isa MVirtualTypeDef then
+                       obj["bound"] = to_mentity_ref(mentity.bound)
+               end
+               return obj
        end
 
-       # Return a HttpResponse containing `json`.
-       fun render_json(json: Jsonable): HttpResponse do
-               var response = new HttpResponse(200)
-               response.body = json.to_json
-               return response
+       redef fun full_json do
+               var obj = super
+               obj["location"] = mentity.location
+               return obj
        end
 end
 
-# Specific nitcorn Action that uses a Model
-class ModelAction
-       super NitAction
+redef class MDoc
 
-       # Model to use.
-       var model: Model
+       # Add doc down processing
+       redef fun json do
+               var obj = new JsonObject
+               obj["html_synopsis"] = html_synopsis.write_to_string
+               obj["html_documentation"] = html_documentation.write_to_string
+               return obj
+       end
+end
 
-       # 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
-               return model.mentity_by_full_name(full_name.from_percent_encoding)
+redef class MPackage
+       redef fun namespace do return new Namespace.from([to_ns_ref])
+end
+
+redef class MGroup
+       redef fun namespace do
+               var p = parent
+               if p == null then
+                       return new Namespace.from([to_ns_ref, ">": nullable NSEntity])
+               end
+               return new Namespace.from([p.namespace, to_ns_ref, ">": nullable NSEntity])
        end
+end
 
-       # Init the model view from the `req` uri parameters.
-       fun init_model_view(req: HttpRequest): ModelView do
-               var view = new ModelView(model)
-               var show_private = req.bool_arg("private") or else false
-               if not show_private then view.min_visibility = protected_visibility
+redef class MModule
+       redef fun namespace do
+               var mgroup = self.mgroup
+               if mgroup == null then
+                       return new Namespace.from([to_ns_ref])
+               end
+               return new Namespace.from([mgroup.mpackage.to_ns_ref, "::", to_ns_ref: nullable NSEntity])
+       end
+
+       private fun ns_for(visibility: MVisibility): nullable Namespace do
+               if visibility <= private_visibility then return namespace
+               var mgroup = self.mgroup
+               if mgroup == null then return namespace
+               return mgroup.mpackage.namespace
+       end
+end
 
-               view.include_fictive = req.bool_arg("fictive") or else false
-               view.include_empty_doc = req.bool_arg("empty-doc") or else true
-               view.include_test_suite = req.bool_arg("test-suite") or else false
-               view.include_attribute = req.bool_arg("attributes") or else true
+redef class MClass
+       redef fun namespace do
+               return new Namespace.from([intro_mmodule.ns_for(visibility), "::", to_ns_ref: nullable NSEntity])
+       end
+end
 
-               return view
+redef class MClassDef
+       redef fun namespace do
+               if is_intro then
+                       return new Namespace.from([mmodule.ns_for(mclass.visibility), "$", to_ns_ref: nullable NSEntity])
+               else if mclass.intro_mmodule.mpackage != mmodule.mpackage then
+                       return new Namespace.from([mmodule.namespace, "$", mclass.namespace: nullable NSEntity])
+               else if mclass.visibility > private_visibility then
+                       return new Namespace.from([mmodule.namespace, "$", mclass.to_ns_ref: nullable NSEntity])
+               end
+               return new Namespace.from([mmodule.full_name, "$::", mclass.intro_mmodule.to_ns_ref: nullable NSEntity])
        end
 end
 
-# A NitView is rendered by an action.
-interface NitView
-       # Renders this view and returns something that can be written to a HTTP response.
-       fun render: Writable is abstract
+redef class MProperty
+       redef fun namespace do
+               if intro_mclassdef.is_intro then
+                       return new Namespace.from([intro_mclassdef.mmodule.ns_for(visibility), "::", intro_mclassdef.mclass.to_ns_ref, "::", to_ns_ref: nullable NSEntity])
+               else
+                       return new Namespace.from([intro_mclassdef.mmodule.namespace, "::", intro_mclassdef.mclass.to_ns_ref, "::", to_ns_ref: nullable NSEntity])
+               end
+       end
 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 MPropDef
+       redef fun namespace do
+               var res = new Namespace
+               res.add mclassdef.namespace
+               res.add "$"
+
+               if mclassdef.mclass == mproperty.intro_mclassdef.mclass then
+                       res.add to_ns_ref
+               else
+                       if mclassdef.mmodule.mpackage != mproperty.intro_mclassdef.mmodule.mpackage then
+                               res.add mproperty.intro_mclassdef.mmodule.ns_for(mproperty.visibility)
+                               res.add "::"
+                       else if mproperty.visibility <= private_visibility then
+                               if mclassdef.mmodule.namespace_for(mclassdef.mclass.visibility) != mproperty.intro_mclassdef.mmodule.mpackage then
+                                       res.add "::"
+                                       res.add mproperty.intro_mclassdef.mmodule.to_ns_ref
+                                       res.add "::"
+                               end
+                       end
+                       if mclassdef.mclass != mproperty.intro_mclassdef.mclass then
+                               res.add mproperty.intro_mclassdef.to_ns_ref
+                               res.add "::"
+                       end
+                       res.add to_ns_ref
+               end
+               return res
+       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
+
+redef class POSetElement[E]
+       super Jsonable
+
+       # Return JSON representation of `self`.
+       fun json: JsonObject do
+               assert self isa POSetElement[MEntity]
+               var obj = new JsonObject
+               obj["greaters"] = to_mentity_refs(greaters)
+               obj["direct_greaters"] = to_mentity_refs(direct_greaters)
+               obj["direct_smallers"] = to_mentity_refs(direct_smallers)
+               obj["smallers"] = to_mentity_refs(smallers)
+               return obj
+       end
+
+       redef fun serialize_to(v) do json.serialize_to(v)
 end