001  (ns dog-and-duck.quack.picky "Fault-finder for ActivityPub documents. 
                
                002                                
                
                003                                Generally, each `-faults` function will return:
                
                004                                1. `nil` if no faults were found;
                
                005                                2. a sequence of fault objects if faults were found.
                
                006                                
                
                007                                Each fault object shall have the properties:
                
                008                                1. `:@context` whose value shall be the URL of a 
                
                009                                   document specifying this vocabulary;
                
                010                                2. `:type` whose value shall be `Fault`;
                
                011                                3. `:severity` whose value shall be one of 
                
                012                                   `minor`, `should`, `must` or `critical`;
                
                013                                4. `:fault` whose value shall be a unique token
                
                014                                   representing the particular fault type;
                
                015                                5. `:narrative` whose value shall be a natural
                
                016                                   language description of the fault type.
                
                017                                
                
                018                                Note that the reason for the `:fault` property is
                
                019                                to be able to have a well known place, linked to
                
                020                                from the @context URL, which allows narratives 
                
                021                                for each fault type to be served in as many
                
                022                                natural languages as possible.
                
                023                                
                
                024                                The idea further is that it should ultimately be
                
                025                                possible to serialise a fault report as a 
                
                026                                document which in its own right conforms to the
                
                027                                ActivityStreams spec."
                
                028   (:require [dog-and-duck.utils.process :refer [pid]]))
                
                029  
                
                030  (def ^:const severity
                
                031    "Severity of faults found, as follows:
                
                032     
                
                033     1. `:minor` things which I consider to be faults, but which 
                
                034        don't actually breach the spec;
                
                035     2. `:should` instances where the spec says something SHOULD
                
                036        be done, which isn't;
                
                037     3. `:must` instances where the spec says something MUST
                
                038        be done, which isn't;
                
                039     4. `:critical` instances where I believe the fault means that
                
                040        the object cannot be meaningfully processed."
                
                041    #{:minor :should :must :critical})
                
                042  
                
                043  (def ^:const severity-filters
                
                044    "Hack for implementing a severity hierarchy"
                
                045    {:all #{}
                
                046     :minor #{:minor}
                
                047     :should #{:minor :should}
                
                048     :must #{:minor :should :must}
                
                049     :critical severity})
                
                050  
                
                051  (defn filter-severity
                
                052    "Return a list of reports taken from these `reports` where the severity
                
                053     of the report is greater than this `severity`."
                
                054    [reports severity]
                
                055    (assert 
                
                056     (and 
                
                057      (coll? reports) 
                
                058      (every? map? reports) 
                
                059      (every? :severity reports)))
                
                060    (remove 
                
                061     #((severity-filters severity) (:severity %))
                
                062     reports))
                
                063  
                
                064  (def ^:const activitystreams-context-uri
                
                065    "The URI of the context of an ActivityStreams object is expected to be this
                
                066     literal string."
                
                067    "https://www.w3.org/ns/activitystreams")
                
                068  
                
                069  (def ^:const validation-fault-context-uri
                
                070    "The URI of the context of a validation fault report object shall be this
                
                071     literal string."
                
                072    "https://simon-brooke.github.io/dog-and-duck/codox/Validation_Faults.html")
                
                073  
                
                074  (defn context?
                
                075    "Returns `true` iff `x` quacks like an ActivityStreams context, else false.
                
                076     
                
                077     A context is either
                
                078     1. the URI (actually an IRI) `activitystreams-context-uri`, or
                
                079     2. a collection comprising that URI and a map."
                
                080    [x]
                
                081    (cond
                
                082      (nil? x) false
                
                083      (string? x) (and (= x activitystreams-context-uri) true)
                
                084      (coll? x) (and (context? (first (remove map? x)))
                
                085                     (= (count x) 2)
                
                086                     true)
                
                087      :else false))
                
                088  
                
                089  (defmacro has-context?
                
                090    "True if `x` is an ActivityStreams object with a valid context, else `false`."
                
                091    [x]
                
                092    `(context? ((keyword "@context") ~x)))
                
                093  
                
                094  
                
                095  
                
                096  (defn make-fault-object
                
                097    "Return a fault object with these `severity`, `fault` and `narrative` values.
                
                098     
                
                099     An ActivityPub object MUST have a globally unique ID. Whether this is 
                
                100     meaningful depends on whether we persist fault report objects and serve
                
                101     them, which at present I have no plans to do."
                
                102    [severity fault narrative]
                
                103    (assoc {}
                
                104           (keyword "@context") validation-fault-context-uri
                
                105           :id (str "https://"
                
                106                    (.. java.net.InetAddress getLocalHost getHostName)
                
                107                    "/fault/"
                
                108                    pid
                
                109                    ":"
                
                110                    (inst-ms (java.util.Date.)))
                
                111           :type "Fault"
                
                112           :severity severity
                
                113           :fault fault
                
                114           :narrative narrative))
                
                115  
                
                116  (defn object-faults
                
                117    [x]
                
                118    (remove 
                
                119     empty?
                
                120     (list
                
                121      (when-not
                
                122       (has-context? x)
                
                123        (make-fault-object 
                
                124         :should 
                
                125         :no-context 
                
                126         "Section 3 of the ActivityPub specification states 
                
                127          `Implementers SHOULD include the ActivityPub context in 
                
                128          their object definitions`.")
                
                129      (when-not (:type x) 
                
                130        (make-fault-object 
                
                131         :minor 
                
                132         :no-type
                
                133         "The ActivityPub specification states that the `type` field is
                
                134          optional, but it is hard to process objects with no known type."))
                
                135        (when-not (contains? x :id)
                
                136          (make-fault-object
                
                137           :minor
                
                138           :no-id-transient
                
                139           "The ActivityPub specification allows objects without `id` fields
                
                140            only if they are intentionally transient; even so it is preferred
                
                141            that the object should have an explicit null id."
                
                142           ))
                
                143      ))))