nitweb: use model filters
[nit.git] / src / web / web_base.nit
index 9cc24bc..4aad814 100644 (file)
@@ -21,12 +21,13 @@ import doc_down
 import popcorn
 import popcorn::pop_config
 import popcorn::pop_repos
+import popcorn::pop_json
 
 # Nitweb config file.
 class NitwebConfig
        super AppConfig
 
-       redef var default_db_name = "nitweb"
+       redef fun default_db_name do return "nitweb"
 
        # Model to use.
        var model: Model
@@ -36,6 +37,9 @@ class NitwebConfig
 
        # Modelbuilder used to access sources.
        var modelbuilder: ModelBuilder
+
+       # ModelView used to access model.
+       var view: ModelView
 end
 
 # Specific handler for the nitweb API.
@@ -48,20 +52,10 @@ abstract class APIHandler
        # 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
-
-       # 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(config.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
+               var mentity = model.mentity_by_full_name(full_name.from_percent_encoding)
+               if mentity == null then return null
+               if config.view.accept_mentity(mentity) then return mentity
+               return null
        end
 
        # Try to load the mentity from uri with `/:id`.
@@ -75,12 +69,52 @@ abstract class APIHandler
                        res.api_error(400, "Expected mentity full name")
                        return null
                end
-               var mentity = find_mentity(view, id)
+               var mentity = find_mentity(config.view, id)
                if mentity == null then
                        res.api_error(404, "MEntity `{id}` not found")
                end
                return mentity
        end
+
+       # Paginate a json array
+       #
+       # Returns only a subset of `results` depending on the current `page` and the
+       # number of elements to return set by `limit`.
+       #
+       # Transforms the json array into an object:
+       # ~~~json
+       # {
+       #       "page": 2,
+       #       "limit": 10,
+       #       "results: [ ... ],
+       #       "max": 5,
+       #       "total": 49
+       # }
+       # ~~~
+       fun paginate(results: JsonArray, count: Int, page, limit: nullable Int): JsonObject do
+               if page == null or page <= 0 then page = 1
+               if limit == null or limit <= 0 then limit = 20
+
+               var max = count / limit
+               if max == 0 then
+                       page = 1
+                       max = 1
+               else if page > max then
+                       page = max
+               end
+
+               var lstart = (page - 1) * limit
+               var lend = limit
+               if lstart + lend > count then lend = count - lstart
+
+               var res = new JsonObject
+               res["page"] = page
+               res["limit"] = limit
+               res["results"] = new JsonArray.from(results.subarray(lstart, lend))
+               res["max"] = max
+               res["total"] = count
+               return res
+       end
 end
 
 # A Rooter dedicated to APIHandlers.
@@ -102,29 +136,29 @@ redef class HttpResponse
        fun api_error(status: Int, message: String) do
                json(new APIError(status, message), status)
        end
+
+       # Write data as JSON and set the right content type header.
+       fun raw_json(json: nullable String, status: nullable Int) do
+               header["Content-Type"] = media_types["json"].as(not null)
+               if json == null then
+                       send(null, status)
+               else
+                       send(json, status)
+               end
+       end
 end
 
 # An error returned by the API.
 #
 # Can be serialized to json.
 class APIError
-       super Jsonable
+       serialize
 
        # Reponse status
        var status: Int
 
        # Response error message
        var message: String
-
-       # 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 to_json do return json.to_json
 end
 
 # Fullname representation that can be used to build decorated links
@@ -134,9 +168,10 @@ end
 class Namespace
        super Array[nullable NSEntity]
        super NSEntity
+       serialize
 
        redef fun to_s do return self.join("")
-       redef fun to_json do return (new JsonArray.from(self)).to_json
+       redef fun serialize_to(v) do to_a.serialize_to(v)
 end
 
 # Something that goes in a Namespace
@@ -145,7 +180,7 @@ end
 # * a `NSToken` for tokens like `::`, `>` and `$`
 # * a `MSRef` for references to mentities
 interface NSEntity
-       super Jsonable
+       super Serializable
 end
 
 # A reference to a MEntity that can be rendered as a link.
@@ -154,16 +189,15 @@ end
 # an infinite loop.
 class NSRef
        super NSEntity
+       serialize
 
        # The mentity to link to/
        var mentity: MEntity
 
-       redef fun to_json do
-               var obj = new JsonObject
-               obj["web_url"] = mentity.web_url
-               obj["api_url"] = mentity.api_url
-               obj["name"] = mentity.name
-               return obj.to_json
+       redef fun core_serialize_to(v) do
+               v.serialize_attribute("web_url", mentity.web_url)
+               v.serialize_attribute("api_url", mentity.api_url)
+               v.serialize_attribute("name", mentity.name)
        end
 end
 
@@ -182,17 +216,13 @@ redef class MEntity
        # 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
+       redef fun core_serialize_to(v) do
+               super
+               v.serialize_attribute("namespace", namespace)
+               v.serialize_attribute("web_url", web_url)
+               v.serialize_attribute("api_url", api_url)
        end
 
-       # 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
 
@@ -201,47 +231,35 @@ redef class MEntity
 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
+       redef fun core_serialize_to(v) do
+               super
+               v.serialize_attribute("namespace", mentity.namespace)
+               v.serialize_attribute("web_url", mentity.web_url)
+               v.serialize_attribute("api_url", mentity.api_url)
+               v.serialize_attribute("name", mentity.name)
+               v.serialize_attribute("mdoc", mentity.mdoc_or_fallback)
+               v.serialize_attribute("visibility", mentity.visibility.to_s)
+               v.serialize_attribute("modifiers", mentity.collect_modifiers)
+               v.serialize_attribute("class_name", mentity.class_name)
                var mentity = self.mentity
                if mentity isa MMethod then
-                       obj["msignature"] = mentity.intro.msignature
+                       v.serialize_attribute("msignature", mentity.intro.msignature)
                else if mentity isa MMethodDef then
-                       obj["msignature"] = mentity.msignature
+                       v.serialize_attribute("msignature", mentity.msignature)
                else if mentity isa MVirtualTypeProp then
-                       obj["bound"] = to_mentity_ref(mentity.intro.bound)
+                       v.serialize_attribute("bound", to_mentity_ref(mentity.intro.bound))
                else if mentity isa MVirtualTypeDef then
-                       obj["bound"] = to_mentity_ref(mentity.bound)
+                       v.serialize_attribute("bound", to_mentity_ref(mentity.bound))
                end
-               return obj
-       end
-
-       redef fun full_json do
-               var obj = super
-               obj["location"] = mentity.location
-               return obj
+               v.serialize_attribute("location", mentity.location)
        end
 end
 
 redef class MDoc
 
        # 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
+       redef fun core_serialize_to(v) do
+               v.serialize_attribute("html_synopsis", html_synopsis.write_to_string)
        end
 end
 
@@ -293,6 +311,8 @@ redef class MClassDef
                end
                return new Namespace.from([mmodule.full_name, "$::", mclass.intro_mmodule.to_ns_ref: nullable NSEntity])
        end
+
+       redef fun web_url do return "{mclass.web_url}/lin#{full_name}"
 end
 
 redef class MProperty
@@ -332,6 +352,8 @@ redef class MPropDef
                end
                return res
        end
+
+       redef fun web_url do return "{mproperty.web_url}/lin#{full_name}"
 end
 
 redef class MClassType
@@ -351,18 +373,11 @@ redef class MVirtualType
 end
 
 redef class POSetElement[E]
-       super Jsonable
+       super Serializable
 
-       # Return JSON representation of `self`.
-       fun json: JsonObject do
+       redef fun core_serialize_to(v) 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
+               v.serialize_attribute("direct_greaters", to_mentity_refs(direct_greaters))
+               v.serialize_attribute("direct_smallers", to_mentity_refs(direct_smallers))
        end
-
-       redef fun to_json do return json.to_json
 end