diff --git a/docs/cloverage/codecov.json b/docs/cloverage/codecov.json index 8a65628..def2f8e 100644 --- a/docs/cloverage/codecov.json +++ b/docs/cloverage/codecov.json @@ -1,18 +1,55 @@ {"coverage": - {"dog_and_duck/quack/quack.clj": + {"dog_and_duck/quack/picky/constants.clj": + [null, 1, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, 1, null, null, null, + null, 1, null, null, null, null, 1, null, null, null, null, null, 1, + null, null, null, 1, null, 1, null, null, null, null, null, null, + null, null, null, null, null, 1, null, 1, null, 1, 1, 1, 1, 1, 1, + null, 1, null, null, null, null, 1, null, null, null, null, 1, null, + null, null, null], + "dog_and_duck/quack/picky/utils.clj": + [null, 1, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, 1, + null, null, 7, null, 1, null, null, null, 0, null, 1, null, null, + null, null, null, 0, 0, 0, 0, 0, null, 1, null, null, 0, 0, 0, null, + null, null, null, true, null, null, 0, null, null, 1, null, null, + null, 4, null, 1, null, null, null, 0, 0, 0, 0, null, 1, null, null, + null, 3, 3, true, 3, null, 1, null, null, null, true, true, 45, 45, + 45, 70, 45, null, 0, 0, null, 0, 0, null, 1, null, null, null, null, + null, null, true, 162, 66, true, 6, null, null, null, 53, null, + null, 3, null, 1, null, null, null, null, null, null, null, null, + 220, 220, 220, 220, null, 220, null, 220, null, 220, 220, true, + null, 0, 0, null, 53, null, null, 2, 2, null, 1, null, null, null, + true, null, 1, null, null, null, null, null, null, 0, 0, 0, 0, 0, 0, + 0, 0, 0, null, 0, 0, 0, 0, 0, 0, null, 1, null, null, null, null, + null, null, null, null, null, 0, 0, null, 0, null, 3, null, null, + null, 3, null, 1, null, null, null, null, null, 0, null, 0, 0, null, + null, 1, null, null, null, null, null, null, true, 53, 53, 53, 3, + 53, 53, 41, 53, 9, 53, 16, null, 0, 0, 0, 0, 0, null, null, 1, null, + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, 0, 0, 0, 0, 0, 0, null, 0, 0, 0, null, + null, 0, 0, 0, null, 0, 0, 0, 0, 0, null, 0, 0, 0, 0, 0, null, 1, + null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, null, 0, 0, 0, 0], + "dog_and_duck/quack/quack.clj": [null, 1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, 1, null, null, null, null, null, null, null, + null, null, null, null, null, null, 1, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, 34, null, 34, null, 1, null, null, null, null, + null, 4, null, 4, null, 1, null, 3, null, 3, null, 1, null, null, + null, null, null, null, null, 0, 0, 0, 0, null, null, null, null, 1, + null, 0, null, 0, null, 1, null, 0, null, 0, null, 1, null, null, + null, null, null, 0, 0, 0, null, null, 1, null, null, null, null, + null, true, true, true, true, 2, 1, null, 1, 1, true, true, 1, null, + null, null, null, null, null, null, null, 0, 0, 0, 0, null, null, 1, + null, null, 0, null, 1, null, null, 0, null, 1, null, null, 0, null, + 1, null, null, 2, null, null], + "dog_and_duck/quack/picky/fault_messages.clj": + [null, 1, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, 1, null, 1, null, null, null, null, null, null, null, null, null, null, null, - null, 35, null, 35, null, 1, null, null, null, null, null, 4, null, - 4, null, 1, null, 3, null, 3, null, 1, null, null, null, null, null, - null, null, 0, 0, 0, 0, null, null, null, null, 1, null, 0, null, 0, - null, 1, null, 0, null, 0, null, 1, null, null, null, null, null, 0, - 0, 0, null, null, 1, null, null, null, null, null, true, true, true, - true, 2, 1, null, 1, 1, true, true, 1, null, null, null, null, null, - null, null, null, 0, 0, 0, 0, null, null, 1, null, null, 0, null, 1, - null, null, 0, null, 1, null, null, 0, null, 1, null, null, 2, null, null], "dog_and_duck/utils/process.clj": [null, 1, null, null, null, null, null, null, null, null, null, null, @@ -30,8 +67,15 @@ "dog_and_duck/scratch/parser.clj": [null, 1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, 1, null, null, null, 11, 11, 11, 11, 11, true, 0, null, 1, - null, 1, 0, 0, 0, 0, 0, 0, 1, null, 1], + null, 1, null, null, null, 9, 9, 9, 9, 9, true, 0, null, null, null, + null, null, null, null, null, null, null, null, null, null], + "dog_and_duck/quack/picky/required_properties.clj":[null, 1], + "dog_and_duck/quack/picky/control_variables.clj": + [null, 1, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, 1, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, 1, null, null, null, null, null, + null, null, null], "clj_activitypub/core.clj": [null, 1, null, null, null, null, null, null, null, null, null, 1, null, null, null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, null, 1, null, @@ -53,44 +97,19 @@ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, 1, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, 1, null, - null, null, null, null, null, null, null, null, 1, null, null, null, - 1, null, 1, null, null, null, null, null, null, null, null, null, - null, null, 1, null, 1, null, 1, 1, 1, 1, 1, 1, null, 1, null, null, - 0, null, 1, null, null, null, null, null, 0, 0, 0, 0, 0, null, 1, - null, null, null, true, true, 46, 46, 46, 72, 46, null, 0, 0, null, - 0, 0, null, 1, null, null, null, null, 1, null, null, null, null, 1, - null, null, null, null, null, null, true, 261, 123, true, 6, null, - null, null, 89, null, null, 3, null, 1, null, null, null, null, - null, null, null, null, 372, 372, 372, 372, null, 372, null, 372, - null, 372, 372, true, null, 0, 0, null, true, null, null, 6, 6, - null, 1, null, null, null, null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, - 0, null, 0, 0, 0, 0, 0, 0, null, 1, null, null, null, null, null, - true, 89, 89, 89, 5, 89, 89, 66, 89, 13, 89, 23, null, 0, 0, 0, 0, - 0, null, null, 0, null, 1, null, null, null, null, 0, null, 16, 16, - null, 0, null, 0, null, 12, null, 1, null, null, true, 17, 17, 17, - 17, 17, 14, 13, 8, null, 1, null, 0, 3, null, 1, null, null, null, - null, 1, null, null, null, null, null, 1, null, null, 12, null, 1, - null, null, null, 8, 8, true, 8, null, 1, null, null, true, 3, 3, 3, - 3, 1, 3, 3, 3, 3, null, 1, null, null, null, null, 1, null, null, - null, null, 1, null, null, null, 4, null, 1, null, null, null, 0, 0, - 0, 0, null, 1, null, null, null, null, null, 0, null, 0, 0, null, 1, - null, null, null, null, 0, 0, 0, 0, null, null, null, 1, null, null, - null, null, null, null, null, null, null, null, null, null, null, - null, null, null, 0, 0, 0, 0, 0, 0, null, 0, 0, 0, null, null, 0, 0, - null, 0, 0, 0, 0, 0, null, 0, 0, 0, 0, 0, null, 1, null, null, 0, - null, 1, null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null, 0, - 0, 0, 0, null, 1, null, null, null, true, 0, true, true, null, 1, - null, null, null, 1, null, 1, null, null, 1, null, 1, 0, null, null, - null, 1, null, null, 1, 1, 1, 1, null, 1, 1, 1, 1, 1, 1, null, 1, 1, - 1, 0, null, null, null, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, null, 1, null, null, null, null, null, null, null, 0, 0, 0, null, - 0, 0, 0, 0, null, 1, null, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "dog_and_duck/quack/fault_messages.clj": - [null, 1, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, 1, null, 1, null, null, - null, null, null, null, null, null, null, null], + null, null, null, null, null, null, 1, null, null, null, null, 0, + null, 6, 6, null, 0, null, 0, null, 4, null, 1, null, null, 12, 12, + 12, 12, 9, 8, 4, null, 1, null, 0, 3, null, 0, 0, 0, 0, null, 1, + null, null, 3, 3, 3, 3, 1, 3, 3, 3, 3, null, 1, null, null, null, + null, 0, 0, 0, 0, 0, 0, null, null, null, 1, null, null, null, true, + 0, true, true, null, 1, null, null, null, 1, null, 1, null, null, 1, + null, 1, 0, null, null, null, 1, null, null, 1, 1, 1, 1, null, 1, 1, + 1, 1, 1, 1, null, 1, 1, 1, 0, null, null, null, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, null, 1, null, null, null, null, null, + null, null, 0, 0, 0, null, 0, 0, 0, 0, null, 1, null, 0, 0, 0, 0, 0, + 0, 0, null, 1, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, 0, 0, 0, 0, 0, 0, null, null, + null, null, null, 0, 0, 0, 0, null, null, 0, 0], "dog_and_duck/scratch/core.clj": [null, 1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 1, null, null, 0], @@ -103,4 +122,10 @@ null, null, null, null, null, null, null, null, null, null, null, null, null, 1, null, 1, 1, 1, null, null, null, null, 1, null, 1, 1, 1, 1, null, null, 1, null, null, null, null, null, null, null, 1, 1, - 1, 1, 1, 1, null, null]}} + 1, 1, 1, 1, null, null], + "dog_and_duck/quack/picky/collections.clj": + [null, 1, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, + null, 1, null, null, null, 0, 0, 0, 0, 0, 0, null, 1, null, null, + null, 0, 0, 0, 0, 0, 0, 0, null, 1, null, 0, 0, 0, 0, 0, null, null, + 0, 0]}} diff --git a/docs/cloverage/coverage.xml b/docs/cloverage/coverage.xml index 6417165..a91e783 100644 --- a/docs/cloverage/coverage.xml +++ b/docs/cloverage/coverage.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/cloverage/dog_and_duck/quack/picky.clj.html b/docs/cloverage/dog_and_duck/quack/picky.clj.html index 18b3ae0..9364e58 100644 --- a/docs/cloverage/dog_and_duck/quack/picky.clj.html +++ b/docs/cloverage/dog_and_duck/quack/picky.clj.html @@ -92,1558 +92,670 @@ 029                                ActivityStreams spec."
- 030      (:require [clojure.set :refer [intersection]] + 030      (:require [dog-and-duck.quack.picky.collections :refer [collection-page-faults
- 031                [dog-and-duck.quack.fault-messages :refer [messages]] + 031                                                              paged-collection-faults
- 032                [dog-and-duck.utils.process :refer [get-hostname get-pid]] + 032                                                              simple-collection-faults]]
- 033                [taoensso.timbre :as timbre + 033                [dog-and-duck.quack.picky.constants :refer [actor-types]]
- 034        ;; Optional, just refer what you like: + 034                [dog-and-duck.quack.picky.utils :refer [any-or-faults
- 035                 :refer [warn]] + 035                                                        coll-object-reference-or-fault
- 036                [clojure.data.json :as json]) + 036                                                        concat-non-empty
- 037      (:import [java.net URI URISyntaxException])) -
- - 038   + 037                                                        has-activity-type?
- 039  ;;;     Copyright (C) Simon Brooke, 2022 -
- - 040   + 038                                                        has-actor-type? has-type?
- 041  ;;;     This program is free software; you can redistribute it and/or + 039                                                        has-type-or-fault
- 042  ;;;     modify it under the terms of the GNU General Public License + 040                                                        make-fault-object
- 043  ;;;     as published by the Free Software Foundation; either version 2 + 041                                                        object-faults
- 044  ;;;     of the License, or (at your option) any later version. + 042                                                        object-reference-or-faults +
+ + 043                                                        string-or-fault]]) +
+ + 044      (:import [java.net URI URISyntaxException]))
045  
- 046  ;;;     This program is distributed in the hope that it will be useful, -
- - 047  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of -
- - 048  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the -
- - 049  ;;;     GNU General Public License for more details. + 046  ;;;     Copyright (C) Simon Brooke, 2022
- 050   + 047  
- 051  ;;;     You should have received a copy of the GNU General Public License + 048  ;;;     This program is free software; you can redistribute it and/or
- 052  ;;;     along with this program; if not, write to the Free Software + 049  ;;;     modify it under the terms of the GNU General Public License
- 053  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. + 050  ;;;     as published by the Free Software Foundation; either version 2 +
+ + 051  ;;;     of the License, or (at your option) any later version.
- 054   + 052  
- 055  ;; ERRATA + 053  ;;;     This program is distributed in the hope that it will be useful, +
+ + 054  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 055  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 056  ;;;     GNU General Public License for more details.
- 056   -
- - 057  (def ^:dynamic *reify-refs* + 057  
- 058    "If `true`, references to objects in fields will be reified and validated.  + 058  ;;;     You should have received a copy of the GNU General Public License
- 059     If `false`, they won't, but an `:info` level fault report will be generated. + 059  ;;;     along with this program; if not, write to the Free Software
- 060      + 060  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. +
+ + 061   +
+ + 062  (defn uri-or-fault
- 061     There are several things in the spec which, in a document, may correctly be + 063    "If `u` is not a valid URI, return a fault object with this `severity` and 
- 062     either + 064     `if-invalid-token`. If it's `nil`, return a fault object with this `severity`
- 063      + 065     and `if-missing-token`. Otherwise return nil."
- 064     1. a fully fleshed out object, or + 066    ([u severity if-missing-token] +
+ + 067     (uri-or-fault u severity if-missing-token if-missing-token))
- 065     2. a URI pointing to such an object. + 068    ([u severity if-missing-token if-invalid-token] +
+ + 069     (try +
+ + 070       (if (uri? (URI. u))
- 066      + 071         nil +
+ + 072         (make-fault-object severity if-invalid-token))
- 067     Obviously to fully validate a document we ought to reify all the refs and  + 073       (catch URISyntaxException _ +
+ + 074         (make-fault-object severity if-invalid-token))
- 068     check that they are themselves valid, but + 075       (catch NullPointerException _
- - 069      -
- - 070     a. in some of the published test documents the URIs do not reference a -
- - 071        valid document; -
- - 072     b. there will be performance costs to reifying all the refs; -
- - 073     c. in perverse cases, reifying refs might result in runaway recursion. -
- - 074      -
- - 075     TODO: I think that in production this should default to `true`." -
- - 076    false) + + 076         (make-fault-object severity if-missing-token)))))
077  
- - 078  (def ^:dynamic *reject-severity* -
- - 079    "The severity at which the binary validator will return `false`. -
- - 080      -
- - 081     In practice documents seen in the wild do not typically appear to be  -
- - 082     fully valid, and this does not matter. This allows the sensitivity of -
- - 083     the binary validator (`dog-and-duck.quack.quack`) to be tuned. It's in -
- - 084     this (`dog-and-duck.quack.picky`) namespace, not that one, because this -
- - 085     namespace is where concerns about severity are handled." -
- - 086    :must) -
- - 087   -
- 088  (def ^:const context-key + 078  (defn persistent-object-faults
- 089    "The Clojure reader barfs on `:@context`, although it is in principle a valid  + 079    "Return a list of faults found in persistent object `x`, or `nil` if none are."
- 090     keyword. So we'll make it once, here, to make the code more performant and + 080    ([x]
- - 091     easier to read." + + 081     (concat-non-empty
- 092    (keyword "@context")) + 082      (object-faults x)
- - 093   + + 083      (list
- - 094  (def ^:const severity -
- - 095    "Severity of faults found, as follows: -
- - 096      -
- - 097     0. `:info` not actually a fault, but an issue noted during validation; -
- - 098     1. `:minor` things which I consider to be faults, but which  -
- - 099        don't actually breach the spec; -
- - 100     2. `:should` instances where the spec says something SHOULD -
- - 101        be done, which isn't; -
- - 102     3. `:must` instances where the spec says something MUST -
- - 103        be done, which isn't; -
- - 104     4. `:critical` instances where I believe the fault means that -
- - 105        the object cannot be meaningfully processed." + + 084       (if (contains? x :id)
- 106    #{:info :minor :should :must :critical}) + 085         (try (let [id (URI. (:id x))]
- - 107   -
- - 108  (def ^:const severity-filters -
- - 109    "Hack for implementing a severity hierarchy" -
- - 110    {:all #{} -
- - 111     :info #{} -
- - 112     :minor #{:info} -
- - 113     :should #{:info :minor} + + 086                (when-not (= (.getScheme id) "https")
- 114     :must #{:info :minor :should} -
- - 115     :critical severity}) -
- - 116   -
- - 117  (defn truthy? + 087                  (make-fault-object :should :id-not-https)))
- 118    "Return `true` if `x` is truthy, else `false`." + 088              (catch URISyntaxException _ +
+ + 089                (make-fault-object :must :id-not-uri))
- 119    [x] + 090              (catch NullPointerException _
- 120    (if x true false)) + 091                (make-fault-object :must :null-id-persistent)))
- - 121   -
- - 122  (defn has-type? + + 092         (make-fault-object :must :no-id-persistent)))))
- 123    "Return `true` if object `x` has type `type`, else `false`. -
- - 124      -
- - 125     The values of `type` fields of ActivityStreams objects may be lists; they -
- - 126     are considered to have a type if the type token is a member of the list." -
- - 127    [x type] -
- - 128    (assert (map? x) (string? type)) -
- - 129    (let [tv (:type x)] + 093    ([x types severity token]
- 130      (cond -
- - 131        (coll? tv) (truthy? (not-empty (filter #(= % type) tv))) + 094     (concat-non-empty
- 132        :else (= tv type)))) + 095      (persistent-object-faults x)
- - 133   -
- - 134  (defn filter-severity -
- - 135    "Return a list of reports taken from these `reports` where the severity -
- - 136     of the report is greater than this or equal to this `severity`." -
- - 137    [reports severity] -
- - 138    (cond (nil? reports) nil -
- - 139          (and -
- - 140           (coll? reports) -
- - 141           (every? map? reports) -
- - 142           (every? :severity reports)) (remove -
- - 143                                        #((severity-filters severity) (:severity %)) -
- - 144                                        reports) -
- - 145          :else -
- - 146          (throw -
- - 147           (ex-info -
- - 148            "Argument `reports` was not a collection of fault reports" + + 096      (list
- 149            {:arguments {:reports reports -
- - 150                         :severity severity}})))) + 097       (has-type-or-fault x types severity token)))))
- 151   -
- - 152  (def ^:const activitystreams-context-uri -
- - 153    "The URI of the context of an ActivityStreams object is expected to be this -
- - 154     literal string." -
- - 155    "https://www.w3.org/ns/activitystreams") -
- - 156   -
- - 157  (def ^:const validation-fault-context-uri -
- - 158    "The URI of the context of a validation fault report object shall be this -
- - 159     literal string." -
- - 160    "https://simon-brooke.github.io/dog-and-duck/codox/Validation_Faults.html") -
- - 161   + 098  
- 162  (defn context? + 099  (defn actor-faults
- 163    "Returns `true` iff `x` quacks like an ActivityStreams context, else false. + 100    "Return a list of faults found in actor `x`, or `nil` if none are."
- 164      + 101    [x]
- - 165     A context is either -
- - 166     1. the URI (actually an IRI) `activitystreams-context-uri`, or -
- - 167     2. a collection comprising that URI and a map." -
- - 168    [x] -
- - 169    (cond + + 102    (concat-non-empty
- 170      (nil? x) false + 103     (persistent-object-faults x)
- - 171      (string? x) (and (= x activitystreams-context-uri) true) + + 104     (list
- - 172      (coll? x) (and (context? (first (remove map? x))) + + 105      (when-not (has-actor-type? x)
- 173                     (= (count x) 2) -
- - 174                     true) -
- - 175      :else false)) -
- - 176   -
- - 177  (defmacro has-context? -
- - 178    "True if `x` is an ActivityStreams object with a valid context, else `false`." -
- - 179    [x] -
- - 180    `(context? (context-key ~x))) -
- - 181   -
- - 182  (defn make-fault-object -
- - 183    "Return a fault object with these `severity`, `fault` and `narrative` values. -
- - 184      -
- - 185     An ActivityPub object MUST have a globally unique ID. Whether this is  -
- - 186     meaningful depends on whether we persist fault report objects and serve -
- - 187     them, which at present I have no plans to do." -
- - 188    ;; TODO: should not pass in the narrative; instead should use the :fault value -
- - 189    ;; to look up the narrative in a resource file. -
- - 190    [severity fault] -
- - 191    (assoc {} -
- - 192           context-key validation-fault-context-uri + 106        (make-fault-object :must :not-actor-type))
- 193           :id (str "https://" -
- - 194                    (get-hostname) -
- - 195                    "/fault/" -
- - 196                    (get-pid) -
- - 197                    ":" + 107      (uri-or-fault
- 198                    (inst-ms (java.util.Date.))) + 108       (:inbox x) :must :no-inbox :invalid-inbox-uri)
- - 199           :type "Fault" + + 109      (uri-or-fault +
+ + 110       (:outbox x) :must :no-outbox :invalid-outbox-uri)))) +
+ + 111  
- 200           :severity severity -
- - 201           :fault fault -
- - 202           :narrative (or (messages fault) + 112  (defn link-faults
- 203                          (do + 113    "A link object is required to have an `href` property. It may have all of
- - 204                            (warn "No narrative provided for fault token " fault) + + 114     `rel` | `mediaType` | `name` | `hreflang` | `height` | `width` | `preview` +
+ + 115     but I *think* they're all optional." +
+ + 116    [x] +
+ + 117    (concat-non-empty +
+ + 118     (object-reference-or-faults x "Link" :critical :expected-link) +
+ + 119     (list +
+ + 120      (uri-or-fault
- 205                            (str fault))))) + 121       (:href x) :must :no-href-uri :invalid-href-uri) +
+ + 122      (string-or-fault (:mediaType x) :minor :no-media-type #"\w+\/[-+.\w]+") +
+ + 123     ;; TODO: possibly more here. Audit against the specs +
+ + 124      ))) +
+ + 125   +
+ + 126  (def ^:const base-activity-required-properties +
+ + 127    "Properties most activities should have. Values are validating functions, each. +
+ + 128      +
+ + 129     See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-activity" +
+ + 130    {:summary (fn [v] (when-not (string? v) +
+ + 131                        (list (make-fault-object :should :no-summary)))) +
+ + 132     :actor (fn [v] (object-reference-or-faults v actor-types :must :no-actor)) +
+ + 133     :object (fn [v] (object-reference-or-faults v nil :must :no-object))}) +
+ + 134   +
+ + 135  (def ^:const intransitive-activity-required-properties +
+ + 136    "Properties intransitive activities should have. +
+ + 137      +
+ + 138     See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-intransitiveactivity" +
+ + 139    (dissoc base-activity-required-properties :object)) +
+ + 140   +
+ + 141  (def ^:const accept-required-properties +
+ + 142    "As base-activity-required-properties, except that the type of the object +
+ + 143     is restricted." +
+ + 144    (assoc base-activity-required-properties +
+ + 145           :object +
+ + 146           (fn [v] +
+ + 147             (object-reference-or-faults v #{"Invite" "Person"} +
+ + 148                                         :must +
+ + 149                                         :bad-accept-target)))) +
+ + 150   +
+ + 151  (def ^:const activity-required-properties +
+ + 152    "Properties activities should have, keyed by activity type. Values are maps  +
+ + 153     of the format of `base-activity-required-properties`, q.v." +
+ + 154    {"Accept" accept-required-properties +
+ + 155     "Add" base-activity-required-properties +
+ + 156     "Announce" base-activity-required-properties +
+ + 157     "Arrive" intransitive-activity-required-properties +
+ + 158     ;; TODO: is `:location` required for arrive? +
+ + 159     "Block" base-activity-required-properties +
+ + 160     "Create" base-activity-required-properties +
+ + 161     "Delete" base-activity-required-properties +
+ + 162     "Dislike" base-activity-required-properties +
+ + 163     "Flag" base-activity-required-properties +
+ + 164     "Follow" base-activity-required-properties +
+ + 165     ;; TODO: is `:object` required to be an actor? +
+ + 166     "Ignore" base-activity-required-properties +
+ + 167     "Invite" (assoc base-activity-required-properties :target +
+ + 168                     (fn [v] +
+ + 169                       (coll-object-reference-or-fault v #{"Event" "Group"} +
+ + 170                                                       :must +
+ + 171                                                       :bad-accept-target))) +
+ + 172     ;; TODO: are here other things one could meaningfully be invited to? +
+ + 173     "Join" base-activity-required-properties +
+ + 174     "Leave" base-activity-required-properties +
+ + 175     "Like" base-activity-required-properties +
+ + 176     "Listen" base-activity-required-properties +
+ + 177     "Move" base-activity-required-properties +
+ + 178     "Offer" base-activity-required-properties +
+ + 179     "Question" intransitive-activity-required-properties +
+ + 180     "Reject" base-activity-required-properties +
+ + 181     "Read" base-activity-required-properties +
+ + 182     "Remove" base-activity-required-properties +
+ + 183     "TentativeReject" base-activity-required-properties +
+ + 184     "TentativeAccept" accept-required-properties +
+ + 185     "Travel" base-activity-required-properties +
+ + 186     "Undo" base-activity-required-properties +
+ + 187     "Update" base-activity-required-properties +
+ + 188     "View" base-activity-required-properties}) +
+ + 189   +
+ + 190  (defn activity-type-faults +
+ + 191    "Return a list of faults found in the activity `x`; if `type` is also  +
+ + 192     specified, it should be a string naming a specific activity type for +
+ + 193     which checks should be performed. +
+ + 194      +
+ + 195     Some specific activity types have specific requirements which are not +
+ + 196     requirements." +
+ + 197    ([x] +
+ + 198     (if (coll? (:type x)) +
+ + 199       (map #(activity-type-faults x %) (:type x)) +
+ + 200       (activity-type-faults x (:type x)))) +
+ + 201    ([x type] +
+ + 202     (let [checks (activity-required-properties type)] +
+ + 203       (map +
+ + 204        #(apply (checks %) (x %)) +
+ + 205        (keys checks)))))
206  
- - 207  (defmacro nil-if-empty -
- - 208    "if `x` is an empty collection, return `nil`; else return `x`." -
- - 209    [x] -
- - 210    `(if (and (coll? ~x) (empty? ~x)) nil -
- 211         ~x)) -
- - 212   -
- - 213  (defn has-type-or-fault + 207  (defn activity-faults
- 214    "If object `x` has a `:type` value which is `acceptable`, return `nil`; -
- - 215     else return a fault object with this `severity` and `token`. -
- - 216      -
- - 217     `acceptable` may be passed as either nil, a string, or a set of strings. -
- - 218     If `acceptable` is `nil`, no type specific tests will be performed." -
- - 219    [x acceptable severity token] -
- - 220    (when acceptable -
- - 221      (let [tv (:type x)] -
- - 222        (when-not + 208    [x]
- 223         (cond + 209    (concat-non-empty (persistent-object-faults x)
- - 224           (and (string? tv) (string? acceptable)) (= tv acceptable) -
- - 225           (and (string? tv) (set? acceptable)) (acceptable tv) -
- - 226           (and (coll? tv) (string? acceptable)) ((set tv) acceptable) -
- - 227           (and (coll? tv) (set? acceptable)) (not-empty -
- - 228                                               (intersection (set tv) acceptable)) -
- - 229           :else -
- - 230           (throw (ex-info "Type value or `acceptable` argument not as expected." -
- - 231                           {:arguments {:x x -
- - 232                                        :acceptable acceptable -
- - 233                                        :severity severity -
- - 234                                        :token token}}))) -
- - 235          (make-fault-object severity token))))) -
- - 236   -
- - 237  (defn object-faults -
- - 238    "Return a list of faults found in object `x`, or `nil` if none are. -
- - 239      -
- - 240     If `expected-type` is also passed, verify that `x` has `expected-type`. -
- - 241     `expected-type` may be passed as a string or as a set of strings." -
- - 242    ([x] -
- - 243     (nil-if-empty -
- - 244      (remove empty? -
- - 245              (list -
- - 246               (when-not (map? x) -
- - 247                 (make-fault-object :critical :not-an-object)) -
- - 248               (when-not -
- - 249                (has-context? x) -
- - 250                 (make-fault-object :should :no-context)) -
- - 251               (when-not (:type x) -
- - 252                 (make-fault-object :minor :no-type)) -
- - 253               (when-not (and (map? x) (contains? x :id)) -
- - 254                 (make-fault-object :minor :no-id-transient)))))) -
- - 255    ([x expected-type] -
- - 256     (nil-if-empty -
- - 257      (remove empty? -
- - 258              (concat -
- - 259               (object-faults x) -
- - 260               (list -
- - 261                ;; TODO: should resolve the correct `-faults`function for the -
- - 262                ;; `expected-type` and call that; but that's for later. -
- - 263                (has-type-or-fault x expected-type :critical :unexpected-type))))))) -
- - 264   -
- - 265  (defn uri-or-fault -
- - 266    "If `u` is not a valid URI, return a fault object with this `severity` and  -
- - 267     `if-invalid-token`. If it's `nil`, return a fault object with this `severity` -
- - 268     and `if-missing-token`. Otherwise return nil." -
- - 269    ([u severity if-missing-token] -
- - 270     (uri-or-fault u severity if-missing-token if-missing-token)) -
- - 271    ([u severity if-missing-token if-invalid-token] -
- - 272     (try -
- - 273       (if (uri? (URI. u)) -
- - 274         nil -
- - 275         (make-fault-object severity if-invalid-token)) -
- - 276       (catch URISyntaxException _ -
- - 277         (make-fault-object severity if-invalid-token)) -
- - 278       (catch NullPointerException _ -
- - 279         (make-fault-object severity if-missing-token))))) -
- - 280   -
- - 281  (defn persistent-object-faults -
- - 282    "Return a list of faults found in persistent object `x`, or `nil` if none are." -
- - 283    [x] -
- - 284    (nil-if-empty -
- - 285     (remove empty? -
- - 286             (concat -
- - 287              (object-faults x) -
- - 288              (list -
- - 289               (if (contains? x :id) -
- - 290                 (try (let [id (URI. (:id x))] -
- - 291                        (when-not (= (.getScheme id) "https") -
- - 292                          (make-fault-object :should :id-not-https))) -
- - 293                      (catch URISyntaxException _ -
- - 294                        (make-fault-object :must :id-not-uri)) -
- - 295                      (catch NullPointerException _ -
- - 296                        (make-fault-object :must :null-id-persistent))) -
- - 297                 (make-fault-object :must :no-id-persistent))))))) -
- - 298   -
- - 299  (def ^:const actor-types -
- - 300    "The set of types we will accept as actors. -
- - 301      -
- - 302     There's an [explicit set of allowed actor types] -
- - 303     (https://www.w3.org/TR/activitystreams-vocabulary/#actor-types)." -
- - 304    #{"Application" -
- - 305      "Group" -
- - 306      "Organization" -
- - 307      "Person" -
- - 308      "Service"}) -
- - 309   -
- - 310  (defn actor-type? -
- - 311    "Return `true` if the `x` is a recognised actor type, else `false`." -
- - 312    [^String x] -
- - 313    (if (actor-types x) true false)) -
- - 314   -
- - 315  (defn has-actor-type? -
- - 316    "Return `true` if the object `x` has a type which is an actor type, else  -
- - 317     `false`." -
- - 318    [x] -
- - 319    (let [tv (:type x)] -
- - 320      (cond -
- - 321        (coll? tv) (truthy? (not-empty (filter actor-type? tv))) -
- - 322        :else (actor-type? tv)))) -
- - 323   -
- - 324  (defn actor-faults -
- - 325    "Return a list of faults found in actor `x`, or `nil` if none are." -
- - 326    [x] -
- - 327    (nil-if-empty -
- - 328     (remove empty? -
- - 329             (concat (persistent-object-faults x) -
- - 330                     (list -
- - 331                      (when-not (has-actor-type? x) -
- - 332                        (make-fault-object :must :not-actor-type)) -
- - 333                      (uri-or-fault -
- - 334                       (:inbox x) :must :no-inbox :invalid-inbox-uri) -
- - 335                      (uri-or-fault -
- - 336                       (:outbox x) :must :no-outbox :invalid-outbox-uri)))))) -
- - 337   -
- - 338  (def ^:const verb-types -
- - 339    "The set of types we will accept as verbs. -
- - 340      -
- - 341     There's an [explicit set of allowed verb types] -
- - 342     (https://www.w3.org/TR/activitystreams-vocabulary/#activity-types)." -
- - 343    #{"Accept" "Add" "Announce" "Arrive" "Block" "Create" "Delete" "Dislike" -
- - 344      "Flag" "Follow" "Ignore" "Invite" "Join" "Leave" "Like" "Listen" "Move" -
- - 345      "Offer" "Question" "Reject" "Read" "Remove" "TentativeAccept" -
- - 346      "TentativeReject" "Travel" "Undo" "Update" "View"}) -
- - 347   -
- - 348  (defn verb-type? -
- - 349    "`true` if `x`, a string, represents a recognised ActivityStreams activity -
- - 350     type." -
- - 351    [^String x] -
- - 352    (if (verb-types x) true false)) -
- - 353   -
- - 354  (defn has-activity-type? -
- - 355    "Return `true` if the object `x` has a type which is an activity type, else  -
- - 356     `false`." -
- - 357    [x] -
- - 358    (let [tv (:type x)] + + 210                      (activity-type-faults x)
- 359      (cond -
- - 360        (coll? tv) (truthy? (not-empty (filter verb-type? tv))) + 211                      (list
- 361        :else (actor-type? tv)))) + 212                       (when-not
- - 362   -
- - 363  (defn string-or-fault -
- - 364    "If this `value` is not a string, return a fault object with this `severity`  -
- - 365     and `token`, else `nil`. If `pattern` is also passed, it is expected to be -
- - 366     a Regex, and the fault object will be returned unless `value` matches the  -
- - 367     `pattern`." -
- - 368    ([value severity token] -
- - 369     (when-not (string? value) (make-fault-object severity token))) -
- - 370    ([value severity token pattern] -
- - 371     (when not (and (string? value) (re-matches pattern value)) + + 213                        (has-activity-type? x)
- 372           (make-fault-object severity token)))) -
- - 373   -
- - 374  (defn link-faults -
- - 375    "A link object is required to have an `href` property. It may have all of -
- - 376     `rel` | `mediaType` | `name` | `hreflang` | `height` | `width` | `preview` -
- - 377     but I *think* they're all optional." -
- - 378    [x] -
- - 379    (list -
- - 380     (uri-or-fault -
- - 381      (:href x) :must :no-href-uri :invalid-href-uri) -
- - 382     (string-or-fault (:mediaType x) :minor :no-media-type #"\w+\/[-+.\w]+") -
- - 383     ;; TODO: possibly more here. Audit against the specs -
- - 384     )) -
- - 385   -
- - 386  (defn object-reference-or-faults -
- - 387    "If this `value` is either  -
- - 388      -
- - 389     1. an object of `expected-type`; -
- - 390     2. a URI referencing an object of  `expected-type`; or -
- - 391     3. a link object referencing an object of  `expected-type` -
- - 392      -
- - 393     and no faults are returned from validating the linked object, then return -
- - 394     `nil`; else return a sequence comprising a fault object with this `severity` -
- - 395     and `token`, prepended to the faults returned. -
- - 396      -
- - 397     As with `has-type-or-fault` (q.v.), `expected-type` may be passed as a -
- - 398     string or as a set of strings. -
- - 399      -
- - 400     **NOTE THAT** if `*reify-refs*` is `false`, referenced objects will not -
- - 401     actually be checked." -
- - 402    [value expected-type severity token] -
- - 403    (let [faults (cond -
- - 404                   (string? value) (try (let [uri (URI. value) -
- - 405                                              object (when *reify-refs* -
- - 406                                                       (json/read-str (slurp uri)))] -
- - 407                                          (when object -
- - 408                                            (object-faults object expected-type))) -
- - 409                                        (catch URISyntaxException _ -
- - 410                                          (make-fault-object severity token))) -
- - 411                   (map? value) (if (has-type? value "Link") -
- - 412                                  (cond -
- - 413                                    ;; if we were looking for a link and we've  -
- - 414                                    ;; found a link, that's OK. -
- - 415                                    (= expected-type "Link") nil -
- - 416                                    (and (set? expected-type) (expected-type "Link")) nil -
- - 417                                    :else -
- - 418                                    (object-reference-or-faults -
- - 419                                     (:href value) expected-type severity token)) -
- - 420                                  (object-faults value expected-type)) -
- - 421                   :else (throw -
- - 422                          (ex-info -
- - 423                           "Argument `value` was not an object or a link to an object" -
- - 424                           {:arguments {:value value} -
- - 425                            :expected-type expected-type -
- - 426                            :severity severity -
- - 427                            :token token})))] -
- - 428      (when faults (cons (make-fault-object severity token) faults)))) -
- - 429   -
- - 430  (defn link-faults -
- - 431    "Return a list of faults found in the link `x`, or `nil` if none are found." -
- - 432    [x] -
- - 433    (object-reference-or-faults x "Link" :critical :expected-link)) -
- - 434   -
- - 435  (defn coll-object-reference-or-fault -
- - 436    "As object-reference-or-fault, except `value` argument may also be a list of -
- - 437     objects and/or object references." -
- - 438    [value expected-type severity token] -
- - 439    (cond -
- - 440      (map? value) (object-reference-or-faults value expected-type severity token) -
- - 441      (coll? value) (nil-if-empty -
- - 442                     (remove nil? -
- - 443                             (reduce concat -
- - 444                                     (map -
- - 445                                      #(object-reference-or-faults -
- - 446                                        % expected-type severity token) -
- - 447                                      value)))) -
- - 448      :else (throw -
- - 449             (ex-info -
- - 450              "Argument `value` was not an object, a link to an object, nor a list of these." -
- - 451              {:arguments {:value value} -
- - 452               :expected-type expected-type -
- - 453               :severity severity -
- - 454               :token token})))) -
- - 455   -
- - 456  (def ^:const base-activity-required-properties -
- - 457    "Properties most activities should have. Values are validating functions, each. -
- - 458      -
- - 459     See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-activity" -
- - 460    {:summary (fn [v] (when-not (string? v) -
- - 461                        (list (make-fault-object :should :no-summary)))) -
- - 462     :actor (fn [v] (object-reference-or-faults v actor-types :must :no-actor)) -
- - 463     :object (fn [v] (object-reference-or-faults v nil :must :no-object))}) -
- - 464   -
- - 465  (def ^:const intransitive-activity-required-properties -
- - 466    "Properties intransitive activities should have. -
- - 467      -
- - 468     See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-intransitiveactivity" -
- - 469    (dissoc base-activity-required-properties :object)) -
- - 470   -
- - 471  (def ^:const accept-required-properties -
- - 472    "As base-activity-required-properties, except that the type of the object -
- - 473     is restricted." -
- - 474    (assoc base-activity-required-properties -
- - 475           :object -
- - 476           (fn [v] -
- - 477             (object-reference-or-faults v #{"Invite" "Person"} -
- - 478                                         :must -
- - 479                                         :bad-accept-target)))) -
- - 480   -
- - 481  (def ^:const activity-required-properties -
- - 482    "Properties activities should have, keyed by activity type. Values are maps  -
- - 483     of the format of `base-activity-required-properties`, q.v." -
- - 484    {"Accept" accept-required-properties -
- - 485     "Add" base-activity-required-properties -
- - 486     "Announce" base-activity-required-properties -
- - 487     "Arrive" intransitive-activity-required-properties -
- - 488     ;; TODO: is `:location` required for arrive? -
- - 489     "Block" base-activity-required-properties -
- - 490     "Create" base-activity-required-properties -
- - 491     "Delete" base-activity-required-properties -
- - 492     "Dislike" base-activity-required-properties -
- - 493     "Flag" base-activity-required-properties -
- - 494     "Follow" base-activity-required-properties -
- - 495     ;; TODO: is `:object` required to be an actor? -
- - 496     "Ignore" base-activity-required-properties -
- - 497     "Invite" (assoc base-activity-required-properties :target -
- - 498                     (fn [v] -
- - 499                       (coll-object-reference-or-fault v #{"Event" "Group"} -
- - 500                                                       :must -
- - 501                                                       :bad-accept-target))) -
- - 502     ;; TODO: are here other things one could meaningfully be invited to? -
- - 503     "Join" base-activity-required-properties -
- - 504     "Leave" base-activity-required-properties -
- - 505     "Like" base-activity-required-properties -
- - 506     "Listen" base-activity-required-properties -
- - 507     "Move" base-activity-required-properties -
- - 508     "Offer" base-activity-required-properties -
- - 509     "Question" intransitive-activity-required-properties -
- - 510     "Reject" base-activity-required-properties -
- - 511     "Read" base-activity-required-properties -
- - 512     "Remove" base-activity-required-properties -
- - 513     "TentativeReject" base-activity-required-properties -
- - 514     "TentativeAccept" accept-required-properties -
- - 515     "Travel" base-activity-required-properties -
- - 516     "Undo" base-activity-required-properties -
- - 517     "Update" base-activity-required-properties -
- - 518     "View" base-activity-required-properties}) -
- - 519   -
- - 520  (defn activity-type-faults -
- - 521    "Return a list of faults found in the activity `x`; if `type` is also  -
- - 522     specified, it should be a string naming a specific activity type for -
- - 523     which checks should be performed. -
- - 524      -
- - 525     Some specific activity types have specific requirements which are not -
- - 526     requirements." -
- - 527    ([x] -
- - 528     (if (coll? (:type x)) -
- - 529       (map #(activity-type-faults x %) (:type x)) -
- - 530       (activity-type-faults x (:type x)))) -
- - 531    ([x type] -
- - 532     (let [checks (activity-required-properties type)] -
- - 533       (map -
- - 534        #(apply (checks %) (x %)) -
- - 535        (keys checks))))) -
- - 536   -
- - 537  (defn activity-faults -
- - 538    [x] -
- - 539    (nil-if-empty -
- - 540     (remove empty? -
- - 541             (concat (persistent-object-faults x) -
- - 542                     (activity-type-faults x) -
- - 543                     (list -
- - 544                      (when-not -
- - 545                       (has-activity-type? x) + 214                         (make-fault-object :must :not-activity-type))
- 546                        (make-fault-object :must :not-activity-type)) + 215                       (when-not (string? (:summary x)) (make-fault-object :should :no-summary)))))
- - 547                      (when-not (string? (:summary x)) (make-fault-object :should :no-summary))))))) + + 216   +
+ + 217  (defn collection-faults +
+ + 218    "Return a list of faults found in the collection `x`; if `type` is also  +
+ + 219     specified, it should be a string naming a specific collection type for +
+ + 220     which checks should be performed.  +
+ + 221      +
+ + 222     Every collection *should*(?) have a `totalItems` field (an integer). +
+ + 223      +
+ + 224     Beyond that, collections are either 'just collections' (in which case +
+ + 225     they *should* have an `items` field (a sequence)), or else they're paged +
+ + 226     collections, in which case they *must*(?) have a `first` field which is  +
+ + 227     a collection page or a URI pointing to a collection page, and *should*  +
+ + 228     have a `last` field which is similar. +
+ + 229      +
+ + 230     The pages of collections *should* be collection pages; the pages of  +
+ + 231     ordered collections *should* be ordered collection pages." +
+ + 232    ([x] +
+ + 233     (collection-faults +
+ + 234      x +
+ + 235      (first +
+ + 236       (remove nil? +
+ + 237               (map #(when (has-type? x %) %) +
+ + 238                    ["Collection" +
+ + 239                     "OrderedCollection" +
+ + 240                     "CollectionPage" +
+ + 241                     "OrderedCollectionPage"]))))) +
+ + 242    ([x type] +
+ + 243     ;; (log/info "collection-faults called with argumens " x ", " type) +
+ + 244     (case type +
+ + 245       ("Collection" "OrderedCollection") (any-or-faults +
+ + 246                                           (list (simple-collection-faults x type) +
+ + 247                                                 (paged-collection-faults x type)) +
+ + 248                                           :must +
+ + 249                                           :no-items) +
+ + 250       ("CollectionPage" "OrderedCollectionPage") (collection-page-faults x type) +
+ + 251       (list (make-fault-object :critical :expected-collection)))))
diff --git a/docs/cloverage/dog_and_duck/quack/picky/collections.clj.html b/docs/cloverage/dog_and_duck/quack/picky/collections.clj.html new file mode 100644 index 0000000..f6149ce --- /dev/null +++ b/docs/cloverage/dog_and_duck/quack/picky/collections.clj.html @@ -0,0 +1,179 @@ + + + + dog_and_duck/quack/picky/collections.clj + + + + 001  (ns dog-and-duck.quack.picky.collections +
+ + 002    (:require [dog-and-duck.quack.picky.utils :refer [concat-non-empty +
+ + 003                                                      cond-make-fault-object +
+ + 004                                                      object-faults +
+ + 005                                                      object-reference-or-faults]])) +
+ + 006   +
+ + 007   +
+ + 008  ;;;     Copyright (C) Simon Brooke, 2022 +
+ + 009   +
+ + 010  ;;;     This program is free software; you can redistribute it and/or +
+ + 011  ;;;     modify it under the terms of the GNU General Public License +
+ + 012  ;;;     as published by the Free Software Foundation; either version 2 +
+ + 013  ;;;     of the License, or (at your option) any later version. +
+ + 014   +
+ + 015  ;;;     This program is distributed in the hope that it will be useful, +
+ + 016  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 017  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 018  ;;;     GNU General Public License for more details. +
+ + 019   +
+ + 020  ;;;     You should have received a copy of the GNU General Public License +
+ + 021  ;;;     along with this program; if not, write to the Free Software +
+ + 022  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. +
+ + 023   +
+ + 024  (defn paged-collection-faults +
+ + 025    "Return a list of faults found in `x` considered as a paged collection +
+ + 026     object of this sub-`type`, or `nil` if none are found." +
+ + 027    [x type] +
+ + 028    (concat-non-empty +
+ + 029     (object-faults x type) +
+ + 030     (list (object-reference-or-faults x type :critical :expected-collection) +
+ + 031           (cond-make-fault-object (integer? (:totalItems x)) :should :no-total-items) +
+ + 032           (object-reference-or-faults (:first x) nil :must :no-first-page) +
+ + 033           (object-reference-or-faults (:last x) nil :should :no-last-page)))) +
+ + 034   +
+ + 035  (defn simple-collection-faults +
+ + 036    "Return a list of faults found in `x` considered as a non-paged collection +
+ + 037     object of this sub-`type`, or `nil` if none are found." +
+ + 038    [x type] +
+ + 039    (concat-non-empty +
+ + 040     (object-faults x type) +
+ + 041     (cons +
+ + 042      (list (object-reference-or-faults x type :critical :expected-collection) +
+ + 043            (cond-make-fault-object (integer? (:totalItems x)) :should :no-total-items) +
+ + 044            (cond-make-fault-object (coll? (:items x)) :must :no-items-collection)) +
+ + 045      (map #(object-reference-or-faults % nil :must :not-object-reference) (:items x))))) +
+ + 046   +
+ + 047  (defn collection-page-faults +
+ + 048    [x type] +
+ + 049    (concat-non-empty +
+ + 050     (simple-collection-faults x type) +
+ + 051     (list +
+ + 052      (object-reference-or-faults (:partOf x) +
+ + 053                                  (apply str (drop-last 4 type)) +
+ + 054                                  :should +
+ + 055                                  :n-part-of) +
+ + 056      (object-reference-or-faults (:next x) type :minor :no-next-page) +
+ + 057      (object-reference-or-faults (:prev x) type :minor :no-prev-page)))) +
+ + diff --git a/docs/cloverage/dog_and_duck/quack/picky/constants.clj.html b/docs/cloverage/dog_and_duck/quack/picky/constants.clj.html new file mode 100644 index 0000000..e174b06 --- /dev/null +++ b/docs/cloverage/dog_and_duck/quack/picky/constants.clj.html @@ -0,0 +1,245 @@ + + + + dog_and_duck/quack/picky/constants.clj + + + + 001  (ns dog-and-duck.quack.picky.constants +
+ + 002    "Constants supporting the picky validator.") +
+ + 003   +
+ + 004  ;;;     Copyright (C) Simon Brooke, 2022 +
+ + 005   +
+ + 006  ;;;     This program is free software; you can redistribute it and/or +
+ + 007  ;;;     modify it under the terms of the GNU General Public License +
+ + 008  ;;;     as published by the Free Software Foundation; either version 2 +
+ + 009  ;;;     of the License, or (at your option) any later version. +
+ + 010   +
+ + 011  ;;;     This program is distributed in the hope that it will be useful, +
+ + 012  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 013  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 014  ;;;     GNU General Public License for more details. +
+ + 015   +
+ + 016  ;;;     You should have received a copy of the GNU General Public License +
+ + 017  ;;;     along with this program; if not, write to the Free Software +
+ + 018  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. +
+ + 019   +
+ + 020  (def ^:const activitystreams-context-uri +
+ + 021    "The URI of the context of an ActivityStreams object is expected to be this +
+ + 022     literal string." +
+ + 023    "https://www.w3.org/ns/activitystreams") +
+ + 024   +
+ + 025  (def ^:const actor-types +
+ + 026    "The set of types we will accept as actors. +
+ + 027      +
+ + 028     There's an [explicit set of allowed actor types] +
+ + 029     (https://www.w3.org/TR/activitystreams-vocabulary/#actor-types)." +
+ + 030    #{"Application" +
+ + 031      "Group" +
+ + 032      "Organization" +
+ + 033      "Person" +
+ + 034      "Service"}) +
+ + 035   +
+ + 036  (def ^:const context-key +
+ + 037    "The Clojure reader barfs on `:@context`, although it is in principle a valid  +
+ + 038     keyword. So we'll make it once, here, to make the code more performant and +
+ + 039     easier to read." +
+ + 040    (keyword "@context")) +
+ + 041   +
+ + 042  (def ^:const severity +
+ + 043    "Severity of faults found, as follows: +
+ + 044      +
+ + 045     0. `:info` not actually a fault, but an issue noted during validation; +
+ + 046     1. `:minor` things which I consider to be faults, but which  +
+ + 047        don't actually breach the spec; +
+ + 048     2. `:should` instances where the spec says something SHOULD +
+ + 049        be done, which isn't; +
+ + 050     3. `:must` instances where the spec says something MUST +
+ + 051        be done, which isn't; +
+ + 052     4. `:critical` instances where I believe the fault means that +
+ + 053        the object cannot be meaningfully processed." +
+ + 054    #{:info :minor :should :must :critical}) +
+ + 055   +
+ + 056  (def ^:const severity-filters +
+ + 057    "Hack for implementing a severity hierarchy" +
+ + 058    {:all #{} +
+ + 059     :info #{} +
+ + 060     :minor #{:info} +
+ + 061     :should #{:info :minor} +
+ + 062     :must #{:info :minor :should} +
+ + 063     :critical severity}) +
+ + 064   +
+ + 065  (def ^:const validation-fault-context-uri +
+ + 066    "The URI of the context of a validation fault report object shall be this +
+ + 067     literal string." +
+ + 068    "https://simon-brooke.github.io/dog-and-duck/codox/Validation_Faults.html") +
+ + 069   +
+ + 070  (def ^:const verb-types +
+ + 071    "The set of types we will accept as verbs. +
+ + 072      +
+ + 073     There's an [explicit set of allowed verb types] +
+ + 074     (https://www.w3.org/TR/activitystreams-vocabulary/#activity-types)." +
+ + 075    #{"Accept" "Add" "Announce" "Arrive" "Block" "Create" "Delete" "Dislike" +
+ + 076      "Flag" "Follow" "Ignore" "Invite" "Join" "Leave" "Like" "Listen" "Move" +
+ + 077      "Offer" "Question" "Reject" "Read" "Remove" "TentativeAccept" +
+ + 078      "TentativeReject" "Travel" "Undo" "Update" "View"}) +
+ + 079   +
+ + diff --git a/docs/cloverage/dog_and_duck/quack/picky/control_variables.clj.html b/docs/cloverage/dog_and_duck/quack/picky/control_variables.clj.html new file mode 100644 index 0000000..fcf7378 --- /dev/null +++ b/docs/cloverage/dog_and_duck/quack/picky/control_variables.clj.html @@ -0,0 +1,155 @@ + + + + dog_and_duck/quack/picky/control_variables.clj + + + + 001  (ns dog-and-duck.quack.picky.control-variables +
+ + 002    "Control variables for the picky validator.") +
+ + 003   +
+ + 004  ;;;     Copyright (C) Simon Brooke, 2022 +
+ + 005   +
+ + 006  ;;;     This program is free software; you can redistribute it and/or +
+ + 007  ;;;     modify it under the terms of the GNU General Public License +
+ + 008  ;;;     as published by the Free Software Foundation; either version 2 +
+ + 009  ;;;     of the License, or (at your option) any later version. +
+ + 010   +
+ + 011  ;;;     This program is distributed in the hope that it will be useful, +
+ + 012  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 013  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 014  ;;;     GNU General Public License for more details. +
+ + 015   +
+ + 016  ;;;     You should have received a copy of the GNU General Public License +
+ + 017  ;;;     along with this program; if not, write to the Free Software +
+ + 018  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. +
+ + 019   +
+ + 020  (def ^:dynamic *reify-refs* +
+ + 021    "If `true`, references to objects in fields will be reified and validated.  +
+ + 022     If `false`, they won't, but an `:info` level fault report will be generated. +
+ + 023      +
+ + 024     There are several things in the spec which, in a document, may correctly be +
+ + 025     either +
+ + 026      +
+ + 027     1. a fully fleshed out object, or +
+ + 028     2. a URI pointing to such an object. +
+ + 029      +
+ + 030     Obviously to fully validate a document we ought to reify all the refs and  +
+ + 031     check that they are themselves valid, but +
+ + 032      +
+ + 033     a. in some of the published test documents the URIs do not reference a +
+ + 034        valid document; +
+ + 035     b. there will be performance costs to reifying all the refs; +
+ + 036     c. in perverse cases, reifying refs might result in runaway recursion. +
+ + 037      +
+ + 038     TODO: I think that in production this should default to `true`." +
+ + 039    false) +
+ + 040   +
+ + 041  (def ^:dynamic *reject-severity* +
+ + 042    "The severity at which the binary validator will return `false`. +
+ + 043      +
+ + 044     In practice documents seen in the wild do not typically appear to be  +
+ + 045     fully valid, and this does not matter. This allows the sensitivity of +
+ + 046     the binary validator (`dog-and-duck.quack.quack`) to be tuned. It's in +
+ + 047     this (`dog-and-duck.quack.picky`) namespace, not that one, because this +
+ + 048     namespace is where concerns about severity are handled." +
+ + 049    :must) +
+ + diff --git a/docs/cloverage/dog_and_duck/quack/picky/fault_messages.clj.html b/docs/cloverage/dog_and_duck/quack/picky/fault_messages.clj.html new file mode 100644 index 0000000..603ee97 --- /dev/null +++ b/docs/cloverage/dog_and_duck/quack/picky/fault_messages.clj.html @@ -0,0 +1,113 @@ + + + + dog_and_duck/quack/picky/fault_messages.clj + + + + 001  (ns dog-and-duck.quack.picky.fault-messages +
+ + 002    "Narrative values for fault reports of specific types, used by the picky +
+ + 003     validator.") +
+ + 004   +
+ + 005  ;;;     Copyright (C) Simon Brooke, 2022 +
+ + 006   +
+ + 007  ;;;     This program is free software; you can redistribute it and/or +
+ + 008  ;;;     modify it under the terms of the GNU General Public License +
+ + 009  ;;;     as published by the Free Software Foundation; either version 2 +
+ + 010  ;;;     of the License, or (at your option) any later version. +
+ + 011   +
+ + 012  ;;;     This program is distributed in the hope that it will be useful, +
+ + 013  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 014  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 015  ;;;     GNU General Public License for more details. +
+ + 016   +
+ + 017  ;;;     You should have received a copy of the GNU General Public License +
+ + 018  ;;;     along with this program; if not, write to the Free Software +
+ + 019  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. +
+ + 020   +
+ + 021  (def messages +
+ + 022    "Actual fault messages to which fault codes resolve." +
+ + 023    {:expected-collection "A collection was expected, but was not found." +
+ + 024     :id-not-https "Publicly facing content SHOULD use HTTPS URIs" +
+ + 025     :id-not-uri "identifiers must be publicly dereferencable URIs" +
+ + 026     :no-context "Section 3 of the ActivityPub specification states Implementers SHOULD include the ActivityPub context in their object definitions`." +
+ + 027     :no-id-persistent "Persistent objects MUST have unique global identifiers." +
+ + 028     :no-id-transient "The ActivityPub specification allows objects without `id` fields only if they are intentionally transient; even so it is preferred that the object should have an explicit null id." +
+ + 029     :no-inbox "Actor objects MUST have an `inbox` property, whose value MUST be a reference to an ordered collection." +
+ + 030     :no-items-collection "A collection expected to be simple had no items." +
+ + 031     :no-outbox "Actor objects MUST have an `outbox` property, whose value MUST be a reference to an ordered collection." +
+ + 032     :no-type "The ActivityPub specification states that the `type` field is optional, but it is hard to process objects with no known type." +
+ + 033     :not-actor-type "The `type` value of the object was not a recognised actor type." +
+ + 034     :null-id-persistent "Persistent objects MUST have non-null identifiers." +
+ + 035     :not-an-object "ActivityStreams object must be JSON objects."}) +
+ + diff --git a/docs/cloverage/dog_and_duck/quack/picky/required_properties.clj.html b/docs/cloverage/dog_and_duck/quack/picky/required_properties.clj.html new file mode 100644 index 0000000..803fb79 --- /dev/null +++ b/docs/cloverage/dog_and_duck/quack/picky/required_properties.clj.html @@ -0,0 +1,11 @@ + + + + dog_and_duck/quack/picky/required_properties.clj + + + + 001  (ns dog-and-duck.quack.picky.required-properties) +
+ + diff --git a/docs/cloverage/dog_and_duck/quack/picky/utils.clj.html b/docs/cloverage/dog_and_duck/quack/picky/utils.clj.html new file mode 100644 index 0000000..51c6d14 --- /dev/null +++ b/docs/cloverage/dog_and_duck/quack/picky/utils.clj.html @@ -0,0 +1,965 @@ + + + + dog_and_duck/quack/picky/utils.clj + + + + 001  (ns dog-and-duck.quack.picky.utils +
+ + 002    "Utility functions supporting the picky validator" +
+ + 003    (:require [clojure.data.json :as json] +
+ + 004              [clojure.set :refer [intersection]] +
+ + 005              [dog-and-duck.quack.picky.constants :refer [activitystreams-context-uri +
+ + 006                                                          actor-types +
+ + 007                                                          context-key severity-filters +
+ + 008                                                          validation-fault-context-uri +
+ + 009                                                          verb-types]] +
+ + 010              [dog-and-duck.quack.picky.control-variables :refer [*reify-refs*]] +
+ + 011              [dog-and-duck.quack.picky.fault-messages :refer [messages]] +
+ + 012              [dog-and-duck.utils.process :refer [get-hostname get-pid]] +
+ + 013              [taoensso.timbre :as log :refer [warn]]) +
+ + 014   +
+ + 015    (:import [java.net URI URISyntaxException])) +
+ + 016   +
+ + 017  ;;;     Copyright (C) Simon Brooke, 2022 +
+ + 018   +
+ + 019  ;;;     This program is free software; you can redistribute it and/or +
+ + 020  ;;;     modify it under the terms of the GNU General Public License +
+ + 021  ;;;     as published by the Free Software Foundation; either version 2 +
+ + 022  ;;;     of the License, or (at your option) any later version. +
+ + 023   +
+ + 024  ;;;     This program is distributed in the hope that it will be useful, +
+ + 025  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of +
+ + 026  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 027  ;;;     GNU General Public License for more details. +
+ + 028   +
+ + 029  ;;;     You should have received a copy of the GNU General Public License +
+ + 030  ;;;     along with this program; if not, write to the Free Software +
+ + 031  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. +
+ + 032   +
+ + 033   +
+ + 034  (defn actor-type? +
+ + 035    "Return `true` if the `x` is a recognised actor type, else `false`." +
+ + 036    [^String x] +
+ + 037    (if (actor-types x) true false)) +
+ + 038   +
+ + 039  (defn truthy? +
+ + 040    "Return `true` if `x` is truthy, else `false`. There must be some more  +
+ + 041     idiomatic way to do this?" +
+ + 042    [x] +
+ + 043    (if x true false)) +
+ + 044   +
+ + 045  (defn has-type? +
+ + 046    "Return `true` if object `x` has type `type`, else `false`. +
+ + 047      +
+ + 048     The values of `type` fields of ActivityStreams objects may be lists; they +
+ + 049     are considered to have a type if the type token is a member of the list." +
+ + 050    [x type] +
+ + 051    (assert (map? x) (string? type)) +
+ + 052    (let [tv (:type x)] +
+ + 053      (cond +
+ + 054        (coll? tv) (truthy? (not-empty (filter #(= % type) tv))) +
+ + 055        :else (= tv type)))) +
+ + 056   +
+ + 057  (defn object-or-uri? +
+ + 058    "Very basic check that `x` is either an object or a URI." +
+ + 059    [x] +
+ + 060    (try +
+ + 061      (cond (string? x) (uri? (URI. x)) +
+ + 062            (map? x) (if (and (:type x) (:id x)) true false) +
+ + 063            :else false) +
+ + 064      (catch URISyntaxException _ false) +
+ + 065      (catch NullPointerException _ false))) +
+ + 066   +
+ + 067  (defmacro link-or-uri? +
+ + 068    "Very basic check that `x` is either a link object or a URI." +
+ + 069    [x] +
+ + 070    `(if (object-or-uri? ~x) (has-type? ~x "Link") false)) +
+ + 071   +
+ + 072   +
+ + 073  (defn verb-type? +
+ + 074    "`true` if `x`, a string, represents a recognised ActivityStreams activity +
+ + 075     type." +
+ + 076    [^String x] +
+ + 077    (if (verb-types x) true false)) +
+ + 078   +
+ + 079  (defn has-activity-type? +
+ + 080    "Return `true` if the object `x` has a type which is an activity type, else  +
+ + 081     `false`." +
+ + 082    [x] +
+ + 083    (let [tv (:type x)] +
+ + 084      (cond +
+ + 085        (coll? tv) (truthy? (not-empty (filter verb-type? tv))) +
+ + 086        :else (verb-type? tv)))) +
+ + 087   +
+ + 088  (defn has-actor-type? +
+ + 089    "Return `true` if the object `x` has a type which is an actor type, else  +
+ + 090     `false`." +
+ + 091    [x] +
+ + 092    (let [tv (:type x)] +
+ + 093      (cond +
+ + 094        (coll? tv) (truthy? (not-empty (filter actor-type? tv))) +
+ + 095        :else (actor-type? tv)))) +
+ + 096   +
+ + 097  (defn filter-severity +
+ + 098    "Return a list of reports taken from these `reports` where the severity +
+ + 099     of the report is greater than this or equal to this `severity`." +
+ + 100    [reports severity] +
+ + 101    (cond (nil? reports) nil +
+ + 102          (and +
+ + 103           (coll? reports) +
+ + 104           (every? map? reports) +
+ + 105           (every? :severity reports)) (remove +
+ + 106                                        #((severity-filters severity) (:severity %)) +
+ + 107                                        reports) +
+ + 108          :else +
+ + 109          (throw +
+ + 110           (ex-info +
+ + 111            "Argument `reports` was not a collection of fault reports" +
+ + 112            {:arguments {:reports reports +
+ + 113                         :severity severity}})))) +
+ + 114   +
+ + 115  (defn context? +
+ + 116    "Returns `true` iff `x` quacks like an ActivityStreams context, else false. +
+ + 117      +
+ + 118     A context is either +
+ + 119     1. the URI (actually an IRI) `activitystreams-context-uri`, or +
+ + 120     2. a collection comprising that URI and a map." +
+ + 121    [x] +
+ + 122    (cond +
+ + 123      (nil? x) false +
+ + 124      (string? x) (and (= x activitystreams-context-uri) true) +
+ + 125      (coll? x) (and (context? (first (remove map? x))) +
+ + 126                     (= (count x) 2) +
+ + 127                     true) +
+ + 128      :else false)) +
+ + 129   +
+ + 130  (defmacro has-context? +
+ + 131    "True if `x` is an ActivityStreams object with a valid context, else `false`." +
+ + 132    [x] +
+ + 133    `(context? (context-key ~x))) +
+ + 134   +
+ + 135  (defn make-fault-object +
+ + 136    "Return a fault object with these `severity`, `fault` and `narrative` values. +
+ + 137      +
+ + 138     An ActivityPub object MUST have a globally unique ID. Whether this is  +
+ + 139     meaningful depends on whether we persist fault report objects and serve +
+ + 140     them, which at present I have no plans to do." +
+ + 141    ;; TODO: should not pass in the narrative; instead should use the :fault value +
+ + 142    ;; to look up the narrative in a resource file. +
+ + 143    [severity fault] +
+ + 144    (assoc {} +
+ + 145           context-key validation-fault-context-uri +
+ + 146           :id (str "https://" +
+ + 147                    (get-hostname) +
+ + 148                    "/fault/" +
+ + 149                    (get-pid) +
+ + 150                    ":" +
+ + 151                    (inst-ms (java.util.Date.))) +
+ + 152           :type "Fault" +
+ + 153           :severity severity +
+ + 154           :fault fault +
+ + 155           :narrative (or (messages fault) +
+ + 156                          (do +
+ + 157                            (warn "No narrative provided for fault token " fault) +
+ + 158                            (str fault))))) +
+ + 159   +
+ + 160  (defmacro nil-if-empty +
+ + 161    "if `x` is an empty collection, return `nil`; else return `x`." +
+ + 162    [x] +
+ + 163    `(if (and (coll? ~x) (empty? ~x)) nil +
+ + 164         ~x)) +
+ + 165   +
+ + 166  (defn concat-non-empty +
+ + 167    "Quick function to replace the pattern (nil-if-empty (remove nil? (concat ...))) +
+ + 168     which I'm using a lot!" +
+ + 169    [& lists] +
+ + 170    (nil-if-empty (remove nil? (apply concat lists)))) +
+ + 171   +
+ + 172  (defn has-type-or-fault +
+ + 173    "If object `x` has a `:type` value which is `acceptable`, return `nil`; +
+ + 174     else return a fault object with this `severity` and `token`. +
+ + 175      +
+ + 176     `acceptable` may be passed as either nil, a string, or a set of strings. +
+ + 177     If `acceptable` is `nil`, no type specific tests will be performed." +
+ + 178    [x acceptable severity token] +
+ + 179    (when acceptable +
+ + 180      (let [tv (:type x)] +
+ + 181        (when-not +
+ + 182         (cond +
+ + 183           (and (string? tv) (string? acceptable)) (= tv acceptable) +
+ + 184           (and (string? tv) (set? acceptable)) (acceptable tv) +
+ + 185           (and (coll? tv) (string? acceptable)) ((set tv) acceptable) +
+ + 186           (and (coll? tv) (set? acceptable)) (not-empty +
+ + 187                                               (intersection (set tv) acceptable)) +
+ + 188           :else +
+ + 189           (throw (ex-info "Type value or `acceptable` argument not as expected." +
+ + 190                           {:arguments {:x x +
+ + 191                                        :acceptable acceptable +
+ + 192                                        :severity severity +
+ + 193                                        :token token}}))) +
+ + 194          (make-fault-object severity token))))) +
+ + 195   +
+ + 196  (defn any-or-faults +
+ + 197    "Return `nil` if validating one of these options returns `nil`; otherwise  +
+ + 198     return a list comprising a fault report object with this `severity-if-none` +
+ + 199     and this token followed by all the fault reports from validating each +
+ + 200     option. +
+ + 201      +
+ + 202     There are several places - but especially in validating collections - where +
+ + 203     there are several different valid configurations, but few or no properties +
+ + 204     are always required." +
+ + 205    [options severity-if-none token] +
+ + 206    (let [faults (filter empty? options)] +
+ + 207      (when (empty? faults)  +
+ + 208        ;; i.e. there was at least one option that returned no faults... +
+ + 209        (cons (make-fault-object severity-if-none token) faults)))) +
+ + 210   +
+ + 211  (defmacro cond-make-fault-object +
+ + 212    "If `v` is `false` or `nil`, return a fault object with this `severity` and `token`, +
+ + 213     else return nil." +
+ + 214    [v severity token] +
+ + 215    `(when-not ~v (make-fault-object ~severity ~token))) +
+ + 216   +
+ + 217  (defn string-or-fault +
+ + 218    "If this `value` is not a string, return a fault object with this `severity`  +
+ + 219     and `token`, else `nil`. If `pattern` is also passed, it is expected to be +
+ + 220     a Regex, and the fault object will be returned unless `value` matches the  +
+ + 221     `pattern`." +
+ + 222    ([value severity token] +
+ + 223     (when-not (string? value) (make-fault-object severity token))) +
+ + 224    ([value severity token pattern] +
+ + 225     (when not (and (string? value) (re-matches pattern value)) +
+ + 226           (make-fault-object severity token)))) +
+ + 227   +
+ + 228   +
+ + 229  (defn object-faults +
+ + 230    "Return a list of faults found in object `x`, or `nil` if none are. +
+ + 231      +
+ + 232     If `expected-type` is also passed, verify that `x` has `expected-type`. +
+ + 233     `expected-type` may be passed as a string or as a set of strings. Detailed +
+ + 234     verification of the particular features of types is not done here." +
+ + 235    ([x] +
+ + 236     (nil-if-empty +
+ + 237      (remove empty? +
+ + 238              (list +
+ + 239               (when-not (map? x) +
+ + 240                 (make-fault-object :critical :not-an-object)) +
+ + 241               (when-not +
+ + 242                (has-context? x) +
+ + 243                 (make-fault-object :should :no-context)) +
+ + 244               (when-not (:type x) +
+ + 245                 (make-fault-object :minor :no-type)) +
+ + 246               (when-not (and (map? x) (contains? x :id)) +
+ + 247                 (make-fault-object :minor :no-id-transient)))))) +
+ + 248    ([x expected-type] +
+ + 249     (concat-non-empty +
+ + 250      (object-faults x) +
+ + 251      (when expected-type +
+ + 252        (list +
+ + 253         (has-type-or-fault x expected-type :critical :unexpected-type)))))) +
+ + 254   +
+ + 255   +
+ + 256  (defn object-reference-or-faults +
+ + 257    "If this `value` is either  +
+ + 258      +
+ + 259     1. an object of `expected-type`; +
+ + 260     2. a URI referencing an object of  `expected-type`; or +
+ + 261     3. a link object referencing an object of  `expected-type` +
+ + 262      +
+ + 263     and no faults are returned from validating the linked object, then return +
+ + 264     `nil`; else return a sequence comprising a fault object with this `severity` +
+ + 265     and `token`, prepended to the faults returned. +
+ + 266      +
+ + 267     As with `has-type-or-fault` (q.v.), `expected-type` may be passed as a +
+ + 268     string, as a set of strings, or `nil` (indicating the type of the  +
+ + 269     referenced object should not be checked). +
+ + 270      +
+ + 271     **NOTE THAT** if `*reify-refs*` is `false`, referenced objects will not +
+ + 272     actually be checked." +
+ + 273    [value expected-type severity token] +
+ + 274    (let [faults (cond +
+ + 275                   (string? value) (try (let [uri (URI. value) +
+ + 276                                              object (when *reify-refs* +
+ + 277                                                       (json/read-str (slurp uri)))] +
+ + 278                                          (when object +
+ + 279                                            (object-faults object expected-type))) +
+ + 280                                        (catch URISyntaxException _ +
+ + 281                                          (make-fault-object severity token))) +
+ + 282                   (map? value) (if (has-type? value "Link") +
+ + 283                                  (cond +
+ + 284                                    ;; if we were looking for a link and we've  +
+ + 285                                    ;; found a link, that's OK. +
+ + 286                                    (= expected-type "Link") nil +
+ + 287                                    (and (set? expected-type) (expected-type "Link")) nil +
+ + 288                                    (nil? expected-type) nil +
+ + 289                                    :else +
+ + 290                                    (object-reference-or-faults +
+ + 291                                     (:href value) expected-type severity token)) +
+ + 292                                  (object-faults value expected-type)) +
+ + 293                   :else (throw +
+ + 294                          (ex-info +
+ + 295                           "Argument `value` was not an object or a link to an object" +
+ + 296                           {:arguments {:value value} +
+ + 297                            :expected-type expected-type +
+ + 298                            :severity severity +
+ + 299                            :token token})))] +
+ + 300      (when faults (cons (make-fault-object severity token) faults)))) +
+ + 301   +
+ + 302  (defn coll-object-reference-or-fault +
+ + 303    "As object-reference-or-fault, except `value` argument may also be a list of +
+ + 304     objects and/or object references." +
+ + 305    [value expected-type severity token] +
+ + 306    (cond +
+ + 307      (map? value) (object-reference-or-faults value expected-type severity token) +
+ + 308      (coll? value) (concat-non-empty +
+ + 309                     (map +
+ + 310                      #(object-reference-or-faults +
+ + 311                        % expected-type severity token) +
+ + 312                      value)) +
+ + 313      :else (throw +
+ + 314             (ex-info +
+ + 315              "Argument `value` was not an object, a link to an object, nor a list of these." +
+ + 316              {:arguments {:value value} +
+ + 317               :expected-type expected-type +
+ + 318               :severity severity +
+ + 319               :token token})))) +
+ + diff --git a/docs/cloverage/dog_and_duck/quack/quack.clj.html b/docs/cloverage/dog_and_duck/quack/quack.clj.html index 3c5e951..00d1cff 100644 --- a/docs/cloverage/dog_and_duck/quack/quack.clj.html +++ b/docs/cloverage/dog_and_duck/quack/quack.clj.html @@ -50,466 +50,472 @@ 015  

- 016    (:require [dog-and-duck.quack.picky :refer [*reject-severity* activity-faults + 016    (:require [dog-and-duck.quack.picky :refer [activity-faults actor-faults 
- 017                                                actor-faults filter-severity link-faults + 017                                                link-faults 
- 018                                                object-faults persistent-object-faults]]) -
- - 019   + 018                                                persistent-object-faults]] 
- 020    (:import [java.net URI URISyntaxException])) + 019              [dog-and-duck.quack.picky.control-variables :refer [*reject-severity*]] +
+ + 020              [dog-and-duck.quack.picky.utils :refer [filter-severity object-faults]])
021  
- 022  ;;;     Copyright (C) Simon Brooke, 2022 + 022    (:import [java.net URI URISyntaxException]))
023  
- 024  ;;;     This program is free software; you can redistribute it and/or -
- - 025  ;;;     modify it under the terms of the GNU General Public License -
- - 026  ;;;     as published by the Free Software Foundation; either version 2 -
- - 027  ;;;     of the License, or (at your option) any later version. + 024  ;;;     Copyright (C) Simon Brooke, 2022
- 028   + 025  
- 029  ;;;     This program is distributed in the hope that it will be useful, + 026  ;;;     This program is free software; you can redistribute it and/or
- 030  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of + 027  ;;;     modify it under the terms of the GNU General Public License
- 031  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + 028  ;;;     as published by the Free Software Foundation; either version 2
- 032  ;;;     GNU General Public License for more details. + 029  ;;;     of the License, or (at your option) any later version.
- 033   + 030  
- 034  ;;;     You should have received a copy of the GNU General Public License + 031  ;;;     This program is distributed in the hope that it will be useful,
- 035  ;;;     along with this program; if not, write to the Free Software + 032  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
- 036  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. + 033  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +
+ + 034  ;;;     GNU General Public License for more details.
- 037   + 035   +
+ + 036  ;;;     You should have received a copy of the GNU General Public License +
+ + 037  ;;;     along with this program; if not, write to the Free Software +
+ + 038  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. +
+ + 039  
- 038  (defn object? + 040  (defn object?
- 039    "Returns `true` iff `x` is recognisably an ActivityStreams object. + 041    "Returns `true` iff `x` is recognisably an ActivityStreams object.
- 040      + 042     
- 041     **NOTE THAT** The ActivityStreams spec  + 043     **NOTE THAT** The ActivityStreams spec 
- 042     [says](https://www.w3.org/TR/activitystreams-core/#object): -
- - 043      -
- - 044     > All properties are optional (including the id and type) + 044     [says](https://www.w3.org/TR/activitystreams-core/#object):
045     
- 046     But we are *just not having that*, because otherwise we're flying blind. + 046     > All properties are optional (including the id and type)
- 047     We *shall* reject objects lacking at least `:type`. Missing `:id` keys are + 047     
- 048     tolerable because they represent transient objects, which we expect to  + 048     But we are *just not having that*, because otherwise we're flying blind.
- 049     handle. + 049     We *shall* reject objects lacking at least `:type`. Missing `:id` keys are
- 050      + 050     tolerable because they represent transient objects, which we expect to 
- 051     **NOTE THAT** The ActivityPub spec [says](https://www.w3.org/TR/activitypub/#obj) + 051     handle.
052     
- 053     > Implementers SHOULD include the ActivityPub context in their object  + 053     **NOTE THAT** The ActivityPub spec [says](https://www.w3.org/TR/activitypub/#obj)
- 054     > definitions + 054     
- 055      + 055     > Implementers SHOULD include the ActivityPub context in their object 
- 056     but in samples found in the wild they typically don't." + 056     > definitions
- 057    ([x] + 057      +
+ + 058     but in samples found in the wild they typically don't." +
+ + 059    ([x]
- 058     (object? x *reject-severity*)) + 060     (object? x *reject-severity*))
- 059    ([x severity] + 061    ([x severity]
- 060     (empty? (filter-severity (object-faults x) severity)))) + 062     (empty? (filter-severity (object-faults x) severity))))
- 061   + 063  
- 062  (defn persistent-object? + 064  (defn persistent-object?
- 063    "`true` iff `x` is a persistent object. + 065    "`true` iff `x` is a persistent object.
- 064   + 066  
- 065     Transient objects in ActivityPub are not required to have an `id` key, but persistent + 067     Transient objects in ActivityPub are not required to have an `id` key, but persistent
- 066     ones must have a key, and it must be an IRI (but normally a URI)." + 068     ones must have a key, and it must be an IRI (but normally a URI)."
- 067    ([x] + 069    ([x]
- 068     (persistent-object? x *reject-severity*)) + 070     (persistent-object? x *reject-severity*))
- 069    ([x severity] + 071    ([x severity]
- 070     (empty? (filter-severity (persistent-object-faults x) severity)))) + 072     (empty? (filter-severity (persistent-object-faults x) severity))))
- 071   + 073  
- 072  (defn actor? + 074  (defn actor?
- 073    "Returns `true` if `x` quacks like an actor, else false." + 075    "Returns `true` if `x` quacks like an actor, else false."
- 074    ([x] (actor? x *reject-severity*)) + 076    ([x] (actor? x *reject-severity*))
- 075    ([x severity] + 077    ([x severity]
- 076     (empty? (filter-severity (actor-faults x) severity)))) + 078     (empty? (filter-severity (actor-faults x) severity))))
- 077   + 079  
- 078  (defn actor-or-uri? + 080  (defn actor-or-uri?
- 079    "`true` if `x` is either a URI or an actor. + 081    "`true` if `x` is either a URI or an actor.
- 080      + 082     
- 081     **TODO**: I need to decide about whether to reify referenced objects + 083     **TODO**: I need to decide about whether to reify referenced objects
- 082     before validation or after. After reification, every reference to an actor + 084     before validation or after. After reification, every reference to an actor
- 083     *must be* to an actor object, but before, may only be to a URI pointing to  + 085     *must be* to an actor object, but before, may only be to a URI pointing to 
- 084     one." + 086     one."
- 085    [x] + 087    [x]
- 086    (try + 088    (try
- 087      (and + 089      (and
- 088       (cond (string? x) (uri? (URI. x)) + 090       (cond (string? x) (uri? (URI. x))
- 089             :else (actor? x)) + 091             :else (actor? x))
- 090       true) + 092       true)
- 091      (catch URISyntaxException _ false) + 093      (catch URISyntaxException _ false)
- 092      (catch NullPointerException _ false))) + 094      (catch NullPointerException _ false)))
- 093   + 095  
- 094  (defn activity? + 096  (defn activity?
- 095    "`true` iff `x` quacks like an activity, else false." + 097    "`true` iff `x` quacks like an activity, else false."
- 096    ([x] (activity? x *reject-severity*)) + 098    ([x] (activity? x *reject-severity*))
- 097    ([x severity] + 099    ([x severity]
- 098     (empty? (filter-severity (activity-faults x) severity)))) + 100     (empty? (filter-severity (activity-faults x) severity))))
- 099   + 101  
- 100  (defn link? + 102  (defn link?
- 101    "`true` iff `x` quacks like a link, else false." + 103    "`true` iff `x` quacks like a link, else false."
- 102    ([x] (link? x *reject-severity*)) + 104    ([x] (link? x *reject-severity*))
- 103    ([x severity] + 105    ([x severity]
- 104     (empty? (filter-severity (link-faults x) severity)))) + 106     (empty? (filter-severity (link-faults x) severity))))
- 105   + 107  
- 106  (defn link-or-uri? + 108  (defn link-or-uri?
- 107    "`true` iff `x` is either a URI or a link, else false. + 109    "`true` iff `x` is either a URI or a link, else false.
- 108      + 110     
- 109     There are several points in the specification where e.g. the `:image` + 111     There are several points in the specification where e.g. the `:image`
- 110     property (if present) may be either a link or a URI." + 112     property (if present) may be either a link or a URI."
- 111    [x] + 113    [x]
- 112    (and + 114    (and
- 113     (cond (string? x) (uri? (URI. x)) + 115     (cond (string? x) (uri? (URI. x))
- 114           :else (link? x)) + 116           :else (link? x))
- 115     true)) + 117     true))
- 116   + 118  
- 117  (defn collection? + 119  (defn collection?
- 118    "`true` iff `x` quacks like a collection of type `object-type`, else `false`. + 120    "`true` iff `x` quacks like a collection of type `object-type`, else `false`.
- 119      + 121     
- 120     With one argument, will recognise plain collections and ordered collections, + 122     With one argument, will recognise plain collections and ordered collections,
- 121     but (currently) not collection pages." + 123     but (currently) not collection pages."
- 122    ([x ^String object-type] + 124    ([x ^String object-type]
- 123     (let [items (or (:items x) (:orderedItems x))] + 125     (let [items (or (:items x) (:orderedItems x))]
- 124       (and + 126       (and
- 125        (cond + 127        (cond
- 126          (:items x) (nil? (:orderedItems x)) + 128          (:items x) (nil? (:orderedItems x))
- 127          (:orderedItems x) (nil? (:items x)) ;; can't have both properties + 129          (:orderedItems x) (nil? (:items x)) ;; can't have both properties
- 128          (integer? (:totalItems x)) true ;; can have neither, provided it has totalItems. + 130          (integer? (:totalItems x)) true ;; can have neither, provided it has totalItems.
- 129          :else false) + 131          :else false)
- 130        (object? x) + 132        (object? x)
- 131        (= (:type x) object-type) + 133        (= (:type x) object-type)
- 132        (if items + 134        (if items
- 133          (and (coll? items) + 135          (and (coll? items)
- 134               (every? object? items) ;; if there are items, they must form a + 136               (every? object? items) ;; if there are items, they must form a
- 135                                      ;; collection of objects. + 137                                      ;; collection of objects.
- 136               true) + 138               true)
- 137          true) ;; but it's OK if there aren't. + 139          true) ;; but it's OK if there aren't.
- 138        true) + 140        true)
- 139       ;; test for totalItems not done here, because collection pages don't + 141       ;; test for totalItems not done here, because collection pages don't
- 140       ;; have it. + 142       ;; have it.
- 141       )) + 143       ))
- 142    ([x] + 144    ([x]
- 143     (and + 145     (and
- 144      (or (collection? x "Collection") + 146      (or (collection? x "Collection")
- 145          (collection? x "OrderedCollection")) + 147          (collection? x "OrderedCollection"))
- 146      (integer? (:totalItems x)) + 148      (integer? (:totalItems x))
- 147      true))) + 149      true)))
- 148   + 150  
- 149  (defn unordered-collection? + 151  (defn unordered-collection?
- 150    "`true` iff `x` quacks like an unordered collection, else `false`." + 152    "`true` iff `x` quacks like an unordered collection, else `false`."
- 151    [x] + 153    [x]
- 152    (and (collection? x "Collection") (integer? (:totalItems x)) true)) + 154    (and (collection? x "Collection") (integer? (:totalItems x)) true))
- 153   + 155  
- 154  (defn ordered-collection? + 156  (defn ordered-collection?
- 155    "`true` iff `x` quacks like an ordered collection, else `false`." + 157    "`true` iff `x` quacks like an ordered collection, else `false`."
- 156    [x] + 158    [x]
- 157    (and (collection? x "OrderedCollection") (integer? (:totalItems x)) true)) + 159    (and (collection? x "OrderedCollection") (integer? (:totalItems x)) true))
- 158   + 160  
- 159  (defn collection-page? + 161  (defn collection-page?
- 160    "`true` iff `x` quacks like a page in a paged collection, else `false`." + 162    "`true` iff `x` quacks like a page in a paged collection, else `false`."
- 161    [x] + 163    [x]
- 162    (collection? x "CollectionPage")) + 164    (collection? x "CollectionPage"))
- 163   + 165  
- 164  (defn ordered-collection-page? + 166  (defn ordered-collection-page?
- 165    "`true` iff `x` quacks like a page in an ordered paged collection, else `false`." + 167    "`true` iff `x` quacks like a page in an ordered paged collection, else `false`."
- 166    [x] + 168    [x]
- 167    (collection? x "OrderedCollectionPage")) + 169    (collection? x "OrderedCollectionPage"))
- 168   + 170  
- 169   + 171  
diff --git a/docs/cloverage/dog_and_duck/scratch/parser.clj.html b/docs/cloverage/dog_and_duck/scratch/parser.clj.html index 1f2b0a5..5349da3 100644 --- a/docs/cloverage/dog_and_duck/scratch/parser.clj.html +++ b/docs/cloverage/dog_and_duck/scratch/parser.clj.html @@ -8,10 +8,10 @@ 001  (ns dog-and-duck.scratch.parser

- 002    (:require [clojure.java.io :refer [file]] + 002    (:require ;; [clojure.java.io :refer [file]]
- 003              [clojure.string :refer [ends-with?]] + 003              ;; [clojure.string :refer [ends-with?]]
004              [clojure.walk :refer [keywordize-keys]] @@ -109,41 +109,41 @@ 035  
- - 036  (clean (slurp "resources/activitystreams-test-documents/core-ex1-jsonld.json")) + + 036  ;; (clean (slurp "resources/activitystreams-test-documents/core-ex1-jsonld.json"))
037  
- - 038  (map + + 038  ;; (map
- - 039   #(when  + + 039  ;;  #(when 
- - 040     (ends-with? (str %) ".json")  + + 040  ;;    (ends-with? (str %) ".json") 
- - 041      (let [objects (clean (slurp %))] + + 041  ;;     (let [objects (clean (slurp %))]
- - 042        (list (str %)  + + 042  ;;       (list (str %) 
- - 043              (count objects)  + + 043  ;;             (count objects) 
- - 044              (map :type objects)))) + + 044  ;;             (map :type objects))))
- - 045   (file-seq (file "resources/activitystreams-test-documents"))) + + 045  ;;  (file-seq (file "resources/activitystreams-test-documents")))
046  
- - 047  (-> "resources/activitystreams-test-documents/simple0020.json" slurp clean first :actor) + + 047  ;; (-> "resources/activitystreams-test-documents/simple0020.json" slurp clean first :actor)
diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html index 71a7ff8..b0c5087 100644 --- a/docs/cloverage/index.html +++ b/docs/cloverage/index.html @@ -94,32 +94,97 @@ 35518 - dog-and-duck.quack.fault-messages
dog-and-duck.quack.picky
169
237
+41.63 % +
65
3
43
+61.26 % +25115111 + + + dog-and-duck.quack.picky.collections
4
129
+3.01 % +
4
20
+16.67 % +57824 + + + dog-and-duck.quack.picky.constants
25
+ float:left;"> 73
+100.00 % +
18
+100.00 % +791218 + + + dog-and-duck.quack.picky.control-variables
5
100.00 %
3
100.00 % -3153 +4963 - dog-and-duck.quack.picky
825
719
-53.43 % + dog-and-duck.quack.picky.fault-messages
29
+100.00 %
135
13
104
-58.73 % -54741252 + style="width:100.0%; + float:left;"> 3 +100.00 % +3553 + + + dog-and-duck.quack.picky.required-properties
1
+100.00 % +
1
+100.00 % +101 + + + dog-and-duck.quack.picky.utils
479
480
+49.95 % +
57
9
81
+44.90 % +31930147 dog-and-duck.quack.quack
18
63.27 % -1692149 +1712149 dog-and-duck.scratch.core
dog-and-duck.scratch.parser
43
34
-55.84 % + style="width:70.0%; + float:left;"> 21
9
+70.00 %
11
7
1
7
-63.16 % -47819 + style="width:11.11111111111111%; + float:left;"> 1 +88.89 % +4789 dog-and-duck.scratch.scratch
Totals: -54.39 % +50.34 % -60.38 % +57.34 % diff --git a/docs/codox/clj-activitypub.core.html b/docs/codox/clj-activitypub.core.html index bfbb205..61ed842 100644 --- a/docs/codox/clj-activitypub.core.html +++ b/docs/codox/clj-activitypub.core.html @@ -1,3 +1,3 @@ -clj-activitypub.core documentation

clj-activitypub.core

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

activity

multimethod

Produces a map representing an ActivityPub activity which can be serialized directly to JSON in the form expected by the ActivityStreams 2.0 spec. See https://www.w3.org/TR/activitystreams-vocabulary/ for reference.

actor

(actor {:keys [user-id username public-key]})

Accepts a config, and returns a map in the form expected by the ActivityPub spec. See https://www.w3.org/TR/activitypub/#actor-objects for reference.

auth-headers

(auth-headers config {:keys [body headers]})

Given a config and request map of {:body … :headers …}, returns the original set of headers with Signature and Digest attributes appended.

config

(config {:keys [domain username username-route public-key private-key], :or {username-route "/users/", public-key nil, private-key nil}})

Creates hash of computed data relevant for most ActivityPub utilities.

fetch-user

(fetch-user user-id)

Fetches the customer account details located at user-id from a remote server. Will return cached results if they exist in memory.

gen-signature-header

(gen-signature-header config headers)

Generates a HTTP Signature string based on the provided map of headers.

obj

multimethod

Produces a map representing an ActivityPub object which can be serialized directly to JSON in the form expected by the ActivityStreams 2.0 spec. See https://www.w3.org/TR/activitystreams-vocabulary/ for reference.

parse-account

(parse-account handle)

Given an ActivityPub handle (e.g. @jahfer@mastodon.social), produces a map containing {:domain … :username …}.

signature-headers

TODO: write docs

with-config

(with-config config)

Returns curried forms of the #activity and #obj multimethods in the form {:activity … :obj …}, with the initial parameter set to config.

\ No newline at end of file +clj-activitypub.core documentation

clj-activitypub.core

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

activity

multimethod

Produces a map representing an ActivityPub activity which can be serialized directly to JSON in the form expected by the ActivityStreams 2.0 spec. See https://www.w3.org/TR/activitystreams-vocabulary/ for reference.

actor

(actor {:keys [user-id username public-key]})

Accepts a config, and returns a map in the form expected by the ActivityPub spec. See https://www.w3.org/TR/activitypub/#actor-objects for reference.

auth-headers

(auth-headers config {:keys [body headers]})

Given a config and request map of {:body … :headers …}, returns the original set of headers with Signature and Digest attributes appended.

config

(config {:keys [domain username username-route public-key private-key], :or {username-route "/users/", public-key nil, private-key nil}})

Creates hash of computed data relevant for most ActivityPub utilities.

fetch-user

(fetch-user user-id)

Fetches the customer account details located at user-id from a remote server. Will return cached results if they exist in memory.

gen-signature-header

(gen-signature-header config headers)

Generates a HTTP Signature string based on the provided map of headers.

obj

multimethod

Produces a map representing an ActivityPub object which can be serialized directly to JSON in the form expected by the ActivityStreams 2.0 spec. See https://www.w3.org/TR/activitystreams-vocabulary/ for reference.

parse-account

(parse-account handle)

Given an ActivityPub handle (e.g. @jahfer@mastodon.social), produces a map containing {:domain … :username …}.

signature-headers

TODO: write docs

with-config

(with-config config)

Returns curried forms of the #activity and #obj multimethods in the form {:activity … :obj …}, with the initial parameter set to config.

\ No newline at end of file diff --git a/docs/codox/clj-activitypub.internal.crypto.html b/docs/codox/clj-activitypub.internal.crypto.html index 8213e22..d209b31 100644 --- a/docs/codox/clj-activitypub.internal.crypto.html +++ b/docs/codox/clj-activitypub.internal.crypto.html @@ -1,3 +1,3 @@ -clj-activitypub.internal.crypto documentation

clj-activitypub.internal.crypto

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

base64-encode

(base64-encode bytes)

TODO: write docs

private-key

(private-key private-pem-str)

TODO: write docs

sha256-base64

(sha256-base64 data)

TODO: write docs

sign

(sign data private-key)

TODO: write docs

\ No newline at end of file +clj-activitypub.internal.crypto documentation

clj-activitypub.internal.crypto

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

base64-encode

(base64-encode bytes)

TODO: write docs

private-key

(private-key private-pem-str)

TODO: write docs

sha256-base64

(sha256-base64 data)

TODO: write docs

sign

(sign data private-key)

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/clj-activitypub.internal.http-util.html b/docs/codox/clj-activitypub.internal.http-util.html index aabc4a3..4cecc62 100644 --- a/docs/codox/clj-activitypub.internal.http-util.html +++ b/docs/codox/clj-activitypub.internal.http-util.html @@ -1,3 +1,3 @@ -clj-activitypub.internal.http-util documentation

clj-activitypub.internal.http-util

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

date

(date)

TODO: write docs

digest

(digest body)

Accepts body from HTTP request and generates string for use in HTTP Digest request header.

encode-url-params

(encode-url-params params)

TODO: write docs

\ No newline at end of file +clj-activitypub.internal.http-util documentation

clj-activitypub.internal.http-util

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

date

(date)

TODO: write docs

digest

(digest body)

Accepts body from HTTP request and generates string for use in HTTP Digest request header.

encode-url-params

(encode-url-params params)

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/clj-activitypub.internal.thread-cache.html b/docs/codox/clj-activitypub.internal.thread-cache.html index 77a71ca..eea8a03 100644 --- a/docs/codox/clj-activitypub.internal.thread-cache.html +++ b/docs/codox/clj-activitypub.internal.thread-cache.html @@ -1,3 +1,3 @@ -clj-activitypub.internal.thread-cache documentation

clj-activitypub.internal.thread-cache

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

make

(make)(make cache-if-nil)

Creates a thread-local cache.

\ No newline at end of file +clj-activitypub.internal.thread-cache documentation

clj-activitypub.internal.thread-cache

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

make

(make)(make cache-if-nil)

Creates a thread-local cache.

\ No newline at end of file diff --git a/docs/codox/clj-activitypub.webfinger.html b/docs/codox/clj-activitypub.webfinger.html index 2cd570e..b2302ac 100644 --- a/docs/codox/clj-activitypub.webfinger.html +++ b/docs/codox/clj-activitypub.webfinger.html @@ -1,3 +1,3 @@ -clj-activitypub.webfinger documentation

clj-activitypub.webfinger

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

fetch-user-id

(fetch-user-id domain username)

Follows the webfinger request to a remote domain, retrieving the ID of the requested account. Typically returns a string in the form of a URL.

remote-uri-path

TODO: write docs

resource-url

(resource-url domain username & [params])

Builds a URL pointing to the user’s account on the remote server.

\ No newline at end of file +clj-activitypub.webfinger documentation

clj-activitypub.webfinger

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

fetch-user-id

(fetch-user-id domain username)

Follows the webfinger request to a remote domain, retrieving the ID of the requested account. Typically returns a string in the form of a URL.

remote-uri-path

TODO: write docs

resource-url

(resource-url domain username & [params])

Builds a URL pointing to the user’s account on the remote server.

\ No newline at end of file diff --git a/docs/codox/dog-and-duck.quack.picky.collections.html b/docs/codox/dog-and-duck.quack.picky.collections.html new file mode 100644 index 0000000..8beb00f --- /dev/null +++ b/docs/codox/dog-and-duck.quack.picky.collections.html @@ -0,0 +1,3 @@ + +dog-and-duck.quack.picky.collections documentation

dog-and-duck.quack.picky.collections

TODO: write docs

collection-page-faults

(collection-page-faults x type)

TODO: write docs

paged-collection-faults

(paged-collection-faults x type)

Return a list of faults found in x considered as a paged collection object of this sub-type, or nil if none are found.

simple-collection-faults

(simple-collection-faults x type)

Return a list of faults found in x considered as a non-paged collection object of this sub-type, or nil if none are found.

\ No newline at end of file diff --git a/src/dog_and_duck/quack/picky.clj b/src/dog_and_duck/quack/picky.clj index 113eb57..9e25edc 100644 --- a/src/dog_and_duck/quack/picky.clj +++ b/src/dog_and_duck/quack/picky.clj @@ -27,16 +27,20 @@ possible to serialise a fault report as a document which in its own right conforms to the ActivityStreams spec." - (:require [dog-and-duck.quack.picky.constants :refer [actor-types]] - [dog-and-duck.quack.picky.control-variables :refer [*reify-refs*]] - [dog-and-duck.quack.picky.utils :refer [concat-non-empty - has-context? + (:require [dog-and-duck.quack.picky.collections :refer [collection-page-faults + paged-collection-faults + simple-collection-faults]] + [dog-and-duck.quack.picky.constants :refer [actor-types]] + [dog-and-duck.quack.picky.utils :refer [any-or-faults + coll-object-reference-or-fault + concat-non-empty has-activity-type? has-actor-type? has-type? has-type-or-fault make-fault-object - nil-if-empty]] - [clojure.data.json :as json]) + object-faults + object-reference-or-faults + string-or-fault]]) (:import [java.net URI URISyntaxException])) ;;; Copyright (C) Simon Brooke, 2022 @@ -55,32 +59,6 @@ ;;; along with this program; if not, write to the Free Software ;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -(defn object-faults - "Return a list of faults found in object `x`, or `nil` if none are. - - If `expected-type` is also passed, verify that `x` has `expected-type`. - `expected-type` may be passed as a string or as a set of strings." - ([x] - (nil-if-empty - (remove empty? - (list - (when-not (map? x) - (make-fault-object :critical :not-an-object)) - (when-not - (has-context? x) - (make-fault-object :should :no-context)) - (when-not (:type x) - (make-fault-object :minor :no-type)) - (when-not (and (map? x) (contains? x :id)) - (make-fault-object :minor :no-id-transient)))))) - ([x expected-type] - (concat-non-empty - (object-faults x) - (list - ;; TODO: should resolve the correct `-faults`function for the - ;; `expected-type` and call that; but that's for later. - (has-type-or-fault x expected-type :critical :unexpected-type))))) - (defn uri-or-fault "If `u` is not a valid URI, return a fault object with this `severity` and `if-invalid-token`. If it's `nil`, return a fault object with this `severity` @@ -131,80 +109,6 @@ (uri-or-fault (:outbox x) :must :no-outbox :invalid-outbox-uri)))) -(defn string-or-fault - "If this `value` is not a string, return a fault object with this `severity` - and `token`, else `nil`. If `pattern` is also passed, it is expected to be - a Regex, and the fault object will be returned unless `value` matches the - `pattern`." - ([value severity token] - (when-not (string? value) (make-fault-object severity token))) - ([value severity token pattern] - (when not (and (string? value) (re-matches pattern value)) - (make-fault-object severity token)))) - -(defn object-reference-or-faults - "If this `value` is either - - 1. an object of `expected-type`; - 2. a URI referencing an object of `expected-type`; or - 3. a link object referencing an object of `expected-type` - - and no faults are returned from validating the linked object, then return - `nil`; else return a sequence comprising a fault object with this `severity` - and `token`, prepended to the faults returned. - - As with `has-type-or-fault` (q.v.), `expected-type` may be passed as a - string or as a set of strings. - - **NOTE THAT** if `*reify-refs*` is `false`, referenced objects will not - actually be checked." - [value expected-type severity token] - (let [faults (cond - (string? value) (try (let [uri (URI. value) - object (when *reify-refs* - (json/read-str (slurp uri)))] - (when object - (object-faults object expected-type))) - (catch URISyntaxException _ - (make-fault-object severity token))) - (map? value) (if (has-type? value "Link") - (cond - ;; if we were looking for a link and we've - ;; found a link, that's OK. - (= expected-type "Link") nil - (and (set? expected-type) (expected-type "Link")) nil - :else - (object-reference-or-faults - (:href value) expected-type severity token)) - (object-faults value expected-type)) - :else (throw - (ex-info - "Argument `value` was not an object or a link to an object" - {:arguments {:value value} - :expected-type expected-type - :severity severity - :token token})))] - (when faults (cons (make-fault-object severity token) faults)))) - -(defn coll-object-reference-or-fault - "As object-reference-or-fault, except `value` argument may also be a list of - objects and/or object references." - [value expected-type severity token] - (cond - (map? value) (object-reference-or-faults value expected-type severity token) - (coll? value) (concat-non-empty - (map - #(object-reference-or-faults - % expected-type severity token) - value)) - :else (throw - (ex-info - "Argument `value` was not an object, a link to an object, nor a list of these." - {:arguments {:value value} - :expected-type expected-type - :severity severity - :token token})))) - (defn link-faults "A link object is required to have an `href` property. It may have all of `rel` | `mediaType` | `name` | `hreflang` | `height` | `width` | `preview` @@ -311,3 +215,38 @@ (make-fault-object :must :not-activity-type)) (when-not (string? (:summary x)) (make-fault-object :should :no-summary))))) +(defn collection-faults + "Return a list of faults found in the collection `x`; if `type` is also + specified, it should be a string naming a specific collection type for + which checks should be performed. + + Every collection *should*(?) have a `totalItems` field (an integer). + + Beyond that, collections are either 'just collections' (in which case + they *should* have an `items` field (a sequence)), or else they're paged + collections, in which case they *must*(?) have a `first` field which is + a collection page or a URI pointing to a collection page, and *should* + have a `last` field which is similar. + + The pages of collections *should* be collection pages; the pages of + ordered collections *should* be ordered collection pages." + ([x] + (collection-faults + x + (first + (remove nil? + (map #(when (has-type? x %) %) + ["Collection" + "OrderedCollection" + "CollectionPage" + "OrderedCollectionPage"]))))) + ([x type] + ;; (log/info "collection-faults called with argumens " x ", " type) + (case type + ("Collection" "OrderedCollection") (any-or-faults + (list (simple-collection-faults x type) + (paged-collection-faults x type)) + :must + :no-items) + ("CollectionPage" "OrderedCollectionPage") (collection-page-faults x type) + (list (make-fault-object :critical :expected-collection))))) diff --git a/src/dog_and_duck/quack/picky/collections.clj b/src/dog_and_duck/quack/picky/collections.clj new file mode 100644 index 0000000..d87df6f --- /dev/null +++ b/src/dog_and_duck/quack/picky/collections.clj @@ -0,0 +1,57 @@ +(ns dog-and-duck.quack.picky.collections + (:require [dog-and-duck.quack.picky.utils :refer [concat-non-empty + cond-make-fault-object + object-faults + object-reference-or-faults]])) + + +;;; Copyright (C) Simon Brooke, 2022 + +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. + +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. + +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +(defn paged-collection-faults + "Return a list of faults found in `x` considered as a paged collection + object of this sub-`type`, or `nil` if none are found." + [x type] + (concat-non-empty + (object-faults x type) + (list (object-reference-or-faults x type :critical :expected-collection) + (cond-make-fault-object (integer? (:totalItems x)) :should :no-total-items) + (object-reference-or-faults (:first x) nil :must :no-first-page) + (object-reference-or-faults (:last x) nil :should :no-last-page)))) + +(defn simple-collection-faults + "Return a list of faults found in `x` considered as a non-paged collection + object of this sub-`type`, or `nil` if none are found." + [x type] + (concat-non-empty + (object-faults x type) + (cons + (list (object-reference-or-faults x type :critical :expected-collection) + (cond-make-fault-object (integer? (:totalItems x)) :should :no-total-items) + (cond-make-fault-object (coll? (:items x)) :must :no-items-collection)) + (map #(object-reference-or-faults % nil :must :not-object-reference) (:items x))))) + +(defn collection-page-faults + [x type] + (concat-non-empty + (simple-collection-faults x type) + (list + (object-reference-or-faults (:partOf x) + (apply str (drop-last 4 type)) + :should + :n-part-of) + (object-reference-or-faults (:next x) type :minor :no-next-page) + (object-reference-or-faults (:prev x) type :minor :no-prev-page)))) diff --git a/src/dog_and_duck/quack/picky/fault_messages.clj b/src/dog_and_duck/quack/picky/fault_messages.clj index 0516158..a5c6016 100644 --- a/src/dog_and_duck/quack/picky/fault_messages.clj +++ b/src/dog_and_duck/quack/picky/fault_messages.clj @@ -20,12 +20,14 @@ (def messages "Actual fault messages to which fault codes resolve." - {:id-not-https "Publicly facing content SHOULD use HTTPS URIs" + {:expected-collection "A collection was expected, but was not found." + :id-not-https "Publicly facing content SHOULD use HTTPS URIs" :id-not-uri "identifiers must be publicly dereferencable URIs" :no-context "Section 3 of the ActivityPub specification states Implementers SHOULD include the ActivityPub context in their object definitions`." :no-id-persistent "Persistent objects MUST have unique global identifiers." :no-id-transient "The ActivityPub specification allows objects without `id` fields only if they are intentionally transient; even so it is preferred that the object should have an explicit null id." :no-inbox "Actor objects MUST have an `inbox` property, whose value MUST be a reference to an ordered collection." + :no-items-collection "A collection expected to be simple had no items." :no-outbox "Actor objects MUST have an `outbox` property, whose value MUST be a reference to an ordered collection." :no-type "The ActivityPub specification states that the `type` field is optional, but it is hard to process objects with no known type." :not-actor-type "The `type` value of the object was not a recognised actor type." diff --git a/src/dog_and_duck/quack/picky/utils.clj b/src/dog_and_duck/quack/picky/utils.clj index 9bf9c57..1e4d8d0 100644 --- a/src/dog_and_duck/quack/picky/utils.clj +++ b/src/dog_and_duck/quack/picky/utils.clj @@ -1,16 +1,18 @@ (ns dog-and-duck.quack.picky.utils "Utility functions supporting the picky validator" - (:require [clojure.set :refer [intersection]] + (:require [clojure.data.json :as json] + [clojure.set :refer [intersection]] [dog-and-duck.quack.picky.constants :refer [activitystreams-context-uri actor-types context-key severity-filters validation-fault-context-uri verb-types]] + [dog-and-duck.quack.picky.control-variables :refer [*reify-refs*]] [dog-and-duck.quack.picky.fault-messages :refer [messages]] [dog-and-duck.utils.process :refer [get-hostname get-pid]] - [taoensso.timbre :as timbre - ;; Optional, just refer what you like: - :refer [warn]])) + [taoensso.timbre :as log :refer [warn]]) + + (:import [java.net URI URISyntaxException])) ;;; Copyright (C) Simon Brooke, 2022 @@ -35,7 +37,8 @@ (if (actor-types x) true false)) (defn truthy? - "Return `true` if `x` is truthy, else `false`." + "Return `true` if `x` is truthy, else `false`. There must be some more + idiomatic way to do this?" [x] (if x true false)) @@ -51,6 +54,22 @@ (coll? tv) (truthy? (not-empty (filter #(= % type) tv))) :else (= tv type)))) +(defn object-or-uri? + "Very basic check that `x` is either an object or a URI." + [x] + (try + (cond (string? x) (uri? (URI. x)) + (map? x) (if (and (:type x) (:id x)) true false) + :else false) + (catch URISyntaxException _ false) + (catch NullPointerException _ false))) + +(defmacro link-or-uri? + "Very basic check that `x` is either a link object or a URI." + [x] + `(if (object-or-uri? ~x) (has-type? ~x "Link") false)) + + (defn verb-type? "`true` if `x`, a string, represents a recognised ActivityStreams activity type." @@ -173,3 +192,128 @@ :severity severity :token token}}))) (make-fault-object severity token))))) + +(defn any-or-faults + "Return `nil` if validating one of these options returns `nil`; otherwise + return a list comprising a fault report object with this `severity-if-none` + and this token followed by all the fault reports from validating each + option. + + There are several places - but especially in validating collections - where + there are several different valid configurations, but few or no properties + are always required." + [options severity-if-none token] + (let [faults (filter empty? options)] + (when (empty? faults) + ;; i.e. there was at least one option that returned no faults... + (cons (make-fault-object severity-if-none token) faults)))) + +(defmacro cond-make-fault-object + "If `v` is `false` or `nil`, return a fault object with this `severity` and `token`, + else return nil." + [v severity token] + `(when-not ~v (make-fault-object ~severity ~token))) + +(defn string-or-fault + "If this `value` is not a string, return a fault object with this `severity` + and `token`, else `nil`. If `pattern` is also passed, it is expected to be + a Regex, and the fault object will be returned unless `value` matches the + `pattern`." + ([value severity token] + (when-not (string? value) (make-fault-object severity token))) + ([value severity token pattern] + (when not (and (string? value) (re-matches pattern value)) + (make-fault-object severity token)))) + + +(defn object-faults + "Return a list of faults found in object `x`, or `nil` if none are. + + If `expected-type` is also passed, verify that `x` has `expected-type`. + `expected-type` may be passed as a string or as a set of strings. Detailed + verification of the particular features of types is not done here." + ([x] + (nil-if-empty + (remove empty? + (list + (when-not (map? x) + (make-fault-object :critical :not-an-object)) + (when-not + (has-context? x) + (make-fault-object :should :no-context)) + (when-not (:type x) + (make-fault-object :minor :no-type)) + (when-not (and (map? x) (contains? x :id)) + (make-fault-object :minor :no-id-transient)))))) + ([x expected-type] + (concat-non-empty + (object-faults x) + (when expected-type + (list + (has-type-or-fault x expected-type :critical :unexpected-type)))))) + + +(defn object-reference-or-faults + "If this `value` is either + + 1. an object of `expected-type`; + 2. a URI referencing an object of `expected-type`; or + 3. a link object referencing an object of `expected-type` + + and no faults are returned from validating the linked object, then return + `nil`; else return a sequence comprising a fault object with this `severity` + and `token`, prepended to the faults returned. + + As with `has-type-or-fault` (q.v.), `expected-type` may be passed as a + string, as a set of strings, or `nil` (indicating the type of the + referenced object should not be checked). + + **NOTE THAT** if `*reify-refs*` is `false`, referenced objects will not + actually be checked." + [value expected-type severity token] + (let [faults (cond + (string? value) (try (let [uri (URI. value) + object (when *reify-refs* + (json/read-str (slurp uri)))] + (when object + (object-faults object expected-type))) + (catch URISyntaxException _ + (make-fault-object severity token))) + (map? value) (if (has-type? value "Link") + (cond + ;; if we were looking for a link and we've + ;; found a link, that's OK. + (= expected-type "Link") nil + (and (set? expected-type) (expected-type "Link")) nil + (nil? expected-type) nil + :else + (object-reference-or-faults + (:href value) expected-type severity token)) + (object-faults value expected-type)) + :else (throw + (ex-info + "Argument `value` was not an object or a link to an object" + {:arguments {:value value} + :expected-type expected-type + :severity severity + :token token})))] + (when faults (cons (make-fault-object severity token) faults)))) + +(defn coll-object-reference-or-fault + "As object-reference-or-fault, except `value` argument may also be a list of + objects and/or object references." + [value expected-type severity token] + (cond + (map? value) (object-reference-or-faults value expected-type severity token) + (coll? value) (concat-non-empty + (map + #(object-reference-or-faults + % expected-type severity token) + value)) + :else (throw + (ex-info + "Argument `value` was not an object, a link to an object, nor a list of these." + {:arguments {:value value} + :expected-type expected-type + :severity severity + :token token})))) diff --git a/src/dog_and_duck/quack/quack.clj b/src/dog_and_duck/quack/quack.clj index 259787e..4036d9c 100644 --- a/src/dog_and_duck/quack/quack.clj +++ b/src/dog_and_duck/quack/quack.clj @@ -14,10 +14,10 @@ toggle some checks off." (:require [dog-and-duck.quack.picky :refer [activity-faults actor-faults - link-faults object-faults + link-faults persistent-object-faults]] [dog-and-duck.quack.picky.control-variables :refer [*reject-severity*]] - [dog-and-duck.quack.picky.utils :refer [filter-severity]]) + [dog-and-duck.quack.picky.utils :refer [filter-severity object-faults]]) (:import [java.net URI URISyntaxException])) diff --git a/src/dog_and_duck/scratch/parser.clj b/src/dog_and_duck/scratch/parser.clj index 81f2832..d7b73d4 100644 --- a/src/dog_and_duck/scratch/parser.clj +++ b/src/dog_and_duck/scratch/parser.clj @@ -1,6 +1,6 @@ (ns dog-and-duck.scratch.parser - (:require [clojure.java.io :refer [file]] - [clojure.string :refer [ends-with?]] + (:require ;; [clojure.java.io :refer [file]] + ;; [clojure.string :refer [ends-with?]] [clojure.walk :refer [keywordize-keys]] [clojure.data.json :as json] [dog-and-duck.quack.quack :as q])) @@ -33,15 +33,15 @@ (cond (map? feed) (list (keywordize-keys feed)) (coll? feed) (map keywordize-keys feed)))))) -(clean (slurp "resources/activitystreams-test-documents/core-ex1-jsonld.json")) +;; (clean (slurp "resources/activitystreams-test-documents/core-ex1-jsonld.json")) -(map - #(when - (ends-with? (str %) ".json") - (let [objects (clean (slurp %))] - (list (str %) - (count objects) - (map :type objects)))) - (file-seq (file "resources/activitystreams-test-documents"))) +;; (map +;; #(when +;; (ends-with? (str %) ".json") +;; (let [objects (clean (slurp %))] +;; (list (str %) +;; (count objects) +;; (map :type objects)))) +;; (file-seq (file "resources/activitystreams-test-documents"))) -(-> "resources/activitystreams-test-documents/simple0020.json" slurp clean first :actor) \ No newline at end of file +;; (-> "resources/activitystreams-test-documents/simple0020.json" slurp clean first :actor) \ No newline at end of file diff --git a/test/dog_and_duck/quack/picky_test.clj b/test/dog_and_duck/quack/picky_test.clj index 5ad289c..79b0424 100644 --- a/test/dog_and_duck/quack/picky_test.clj +++ b/test/dog_and_duck/quack/picky_test.clj @@ -1,9 +1,11 @@ (ns dog-and-duck.quack.picky-test (:require [clojure.test :refer [deftest is testing]] - [dog-and-duck.quack.picky.constants :refer [activitystreams-context-uri]] - [dog-and-duck.quack.picky.utils :refer [filter-severity]] - [dog-and-duck.quack.picky :refer [object-faults - persistent-object-faults]])) + [dog-and-duck.quack.picky.constants :refer + [activitystreams-context-uri]] + [dog-and-duck.quack.picky.utils :refer + [filter-severity object-faults]] + [dog-and-duck.quack.picky :refer + [persistent-object-faults]])) ;;; Copyright (C) Simon Brooke, 2022