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/Using_ActivityPub.html b/docs/codox/Using_ActivityPub.html index ea14e0d..21b3524 100644 --- a/docs/codox/Using_ActivityPub.html +++ b/docs/codox/Using_ActivityPub.html @@ -1,6 +1,6 @@ -Using ActivityPub

Using ActivityPub

+Using ActivityPub

Using ActivityPub

Introduction

I do not know what I am doing; I am learning, and playing. Nothing in this document should be treated as good advice; it simply relates to the current state of my knowledge.

What happens when you post a new item to an ActivityPub server

diff --git a/docs/codox/Validation_Faults.html b/docs/codox/Validation_Faults.html index 712e88d..4a06c64 100644 --- a/docs/codox/Validation_Faults.html +++ b/docs/codox/Validation_Faults.html @@ -1,6 +1,6 @@ -Validation Faults in ActivityPub documents

Validation Faults in ActivityPub documents

+Validation Faults in ActivityPub documents

Validation Faults in ActivityPub documents

Motivation

This document is intended to provide an extension vocabulary for ActivityStreams documents, which provides vocabulary for categorising and describing faults in ActivityPub documents.

The motivation is to be able to serialise a validation report on an ActivityPub document as an ActivityStreams document.

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/docs/codox/dog-and-duck.quack.picky.constants.html b/docs/codox/dog-and-duck.quack.picky.constants.html new file mode 100644 index 0000000..ff6feff --- /dev/null +++ b/docs/codox/dog-and-duck.quack.picky.constants.html @@ -0,0 +1,12 @@ + +dog-and-duck.quack.picky.constants documentation

dog-and-duck.quack.picky.constants

Constants supporting the picky validator.

activitystreams-context-uri

The URI of the context of an ActivityStreams object is expected to be this literal string.

actor-types

The set of types we will accept as actors.

+

There’s an explicit set of allowed actor types.

context-key

The Clojure reader barfs on :@context, although it is in principle a valid keyword. So we’ll make it once, here, to make the code more performant and easier to read.

severity

Severity of faults found, as follows:

+
    +
  1. :info not actually a fault, but an issue noted during validation;
  2. +
  3. :minor things which I consider to be faults, but which don’t actually breach the spec;
  4. +
  5. :should instances where the spec says something SHOULD be done, which isn’t;
  6. +
  7. :must instances where the spec says something MUST be done, which isn’t;
  8. +
  9. :critical instances where I believe the fault means that the object cannot be meaningfully processed.
  10. +

severity-filters

Hack for implementing a severity hierarchy

validation-fault-context-uri

The URI of the context of a validation fault report object shall be this literal string.

verb-types

The set of types we will accept as verbs.

+

There’s an explicit set of allowed verb types.

\ No newline at end of file diff --git a/docs/codox/dog-and-duck.quack.picky.control-variables.html b/docs/codox/dog-and-duck.quack.picky.control-variables.html new file mode 100644 index 0000000..9edb0c9 --- /dev/null +++ b/docs/codox/dog-and-duck.quack.picky.control-variables.html @@ -0,0 +1,12 @@ + +dog-and-duck.quack.picky.control-variables documentation

dog-and-duck.quack.picky.control-variables

Control variables for the picky validator.

*reify-refs*

dynamic

If true, references to objects in fields will be reified and validated. If false, they won’t, but an :info level fault report will be generated.

+

There are several things in the spec which, in a document, may correctly be either

+
    +
  1. a fully fleshed out object, or
  2. +
  3. a URI pointing to such an object.
  4. +
+

Obviously to fully validate a document we ought to reify all the refs and check that they are themselves valid, but

+

a. in some of the published test documents the URIs do not reference a valid document; b. there will be performance costs to reifying all the refs; c. in perverse cases, reifying refs might result in runaway recursion.

+

TODO: I think that in production this should default to true.

*reject-severity*

dynamic

The severity at which the binary validator will return false.

+

In practice documents seen in the wild do not typically appear to be fully valid, and this does not matter. This allows the sensitivity of the binary validator (dog-and-duck.quack.quack) to be tuned. It’s in this (dog-and-duck.quack.picky) namespace, not that one, because this namespace is where concerns about severity are handled.

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

dog-and-duck.quack.picky.fault-messages

Narrative values for fault reports of specific types, used by the picky validator.

messages

Actual fault messages to which fault codes resolve.

\ No newline at end of file diff --git a/docs/codox/dog-and-duck.quack.picky.html b/docs/codox/dog-and-duck.quack.picky.html index c98eff0..8603386 100644 --- a/docs/codox/dog-and-duck.quack.picky.html +++ b/docs/codox/dog-and-duck.quack.picky.html @@ -1,6 +1,6 @@ -dog-and-duck.quack.picky documentation

dog-and-duck.quack.picky

Fault-finder for ActivityPub documents.

+dog-and-duck.quack.picky documentation

dog-and-duck.quack.picky

Fault-finder for ActivityPub documents.

Generally, each -faults function will return:

  1. nil if no faults were found;
  2. @@ -15,38 +15,10 @@
  3. :narrative whose value shall be a natural language description of the fault type.

Note that the reason for the :fault property is to be able to have a well known place, linked to from the @context URL, which allows narratives for each fault type to be served in as many natural languages as possible.

-

The idea further is that it should ultimately be possible to serialise a fault report as a document which in its own right conforms to the ActivityStreams spec.

*reify-refs*

dynamic

If true, references to objects in fields will be reified and validated. If false, they won’t, but an :info level fault report will be generated.

-

There are several things in the spec which, in a document, may correctly be either

-
    -
  1. a fully fleshed out object, or
  2. -
  3. a URI pointing to such an object.
  4. -
-

Obviously to fully validate a document we ought to reify all the refs and check that they are themselves valid, but

-

a. in some of the published test documents the URIs do not reference a valid document; b. there will be performance costs to reifying all the refs; c. in perverse cases, reifying refs might result in runaway recursion.

-

TODO: I think that in production this should default to true.

*reject-severity*

dynamic

The severity at which the binary validator will return false.

-

In practice documents seen in the wild do not typically appear to be fully valid, and this does not matter. This allows the sensitivity of the binary validator (dog-and-duck.quack.quack) to be tuned. It’s in this (dog-and-duck.quack.picky) namespace, not that one, because this namespace is where concerns about severity are handled.

accept-required-properties

As base-activity-required-properties, except that the type of the object is restricted.

activity-faults

(activity-faults x)

TODO: write docs

activity-required-properties

Properties activities should have, keyed by activity type. Values are maps of the format of base-activity-required-properties, q.v.

activity-type-faults

(activity-type-faults x)(activity-type-faults x type)

Return a list of faults found in the activity x; if type is also specified, it should be a string naming a specific activity type for which checks should be performed.

-

Some specific activity types have specific requirements which are not requirements.

activitystreams-context-uri

The URI of the context of an ActivityStreams object is expected to be this literal string.

actor-faults

(actor-faults x)

Return a list of faults found in actor x, or nil if none are.

actor-type?

(actor-type? x)

Return true if the x is a recognised actor type, else false.

actor-types

The set of types we will accept as actors.

-

There’s an explicit set of allowed actor types.

base-activity-required-properties

Properties most activities should have. Values are validating functions, each.

-

See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-activity

coll-object-reference-or-fault

(coll-object-reference-or-fault value expected-type severity token)

As object-reference-or-fault, except value argument may also be a list of objects and/or object references.

context-key

The Clojure reader barfs on :@context, although it is in principle a valid keyword. So we’ll make it once, here, to make the code more performant and easier to read.

context?

(context? x)

Returns true iff x quacks like an ActivityStreams context, else false.

-

A context is either 1. the URI (actually an IRI) activitystreams-context-uri, or 2. a collection comprising that URI and a map.

filter-severity

(filter-severity reports severity)

Return a list of reports taken from these reports where the severity of the report is greater than this or equal to this severity.

has-activity-type?

(has-activity-type? x)

Return true if the object x has a type which is an activity type, else false.

has-actor-type?

(has-actor-type? x)

Return true if the object x has a type which is an actor type, else false.

has-context?

macro

(has-context? x)

True if x is an ActivityStreams object with a valid context, else false.

has-type-or-fault

(has-type-or-fault x acceptable severity token)

If object x has a :type value which is acceptable, return nil; else return a fault object with this severity and token.

-

acceptable may be passed as either nil, a string, or a set of strings. If acceptable is nil, no type specific tests will be performed.

has-type?

(has-type? x type)

Return true if object x has type type, else false.

-

The values of type fields of ActivityStreams objects may be lists; they are considered to have a type if the type token is a member of the list.

intransitive-activity-required-properties

Properties intransitive activities should have.

-

See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-intransitiveactivity

make-fault-object

(make-fault-object severity fault)

Return a fault object with these severity, fault and narrative values.

-

An ActivityPub object MUST have a globally unique ID. Whether this is meaningful depends on whether we persist fault report objects and serve them, which at present I have no plans to do.

nil-if-empty

macro

(nil-if-empty x)

if x is an empty collection, return nil; else return x.

object-faults

(object-faults x)(object-faults x expected-type)

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.

object-reference-or-faults

(object-reference-or-faults value expected-type severity token)

If this value is either

-
    -
  1. an object of expected-type;
  2. -
  3. a URI referencing an object of expected-type; or
  4. -
  5. a link object referencing an object of expected-type
  6. -
-

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.

persistent-object-faults

(persistent-object-faults x)

Return a list of faults found in persistent object x, or nil if none are.

severity

Severity of faults found, as follows:

-
    -
  1. :info not actually a fault, but an issue noted during validation;
  2. -
  3. :minor things which I consider to be faults, but which don’t actually breach the spec;
  4. -
  5. :should instances where the spec says something SHOULD be done, which isn’t;
  6. -
  7. :must instances where the spec says something MUST be done, which isn’t;
  8. -
  9. :critical instances where I believe the fault means that the object cannot be meaningfully processed.
  10. -

severity-filters

Hack for implementing a severity hierarchy

string-or-fault

(string-or-fault value severity token)(string-or-fault value severity token pattern)

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.

truthy?

(truthy? x)

Return true if x is truthy, else false.

uri-or-fault

(uri-or-fault u severity if-missing-token)(uri-or-fault u severity if-missing-token if-invalid-token)

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 and if-missing-token. Otherwise return nil.

validation-fault-context-uri

The URI of the context of a validation fault report object shall be this literal string.

verb-type?

(verb-type? x)

true if x, a string, represents a recognised ActivityStreams activity type.

verb-types

The set of types we will accept as verbs.

-

There’s an explicit set of allowed verb types.

\ No newline at end of file +

The idea further is that it should ultimately be possible to serialise a fault report as a document which in its own right conforms to the ActivityStreams spec.

accept-required-properties

As base-activity-required-properties, except that the type of the object is restricted.

activity-faults

(activity-faults x)

TODO: write docs

activity-required-properties

Properties activities should have, keyed by activity type. Values are maps of the format of base-activity-required-properties, q.v.

activity-type-faults

(activity-type-faults x)(activity-type-faults x type)

Return a list of faults found in the activity x; if type is also specified, it should be a string naming a specific activity type for which checks should be performed.

+

Some specific activity types have specific requirements which are not requirements.

actor-faults

(actor-faults x)

Return a list of faults found in actor x, or nil if none are.

base-activity-required-properties

Properties most activities should have. Values are validating functions, each.

+

See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-activity

collection-faults

(collection-faults x)(collection-faults x type)

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.

intransitive-activity-required-properties

Properties intransitive activities should have.

+

See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-intransitiveactivity

persistent-object-faults

(persistent-object-faults x)(persistent-object-faults x types severity token)

Return a list of faults found in persistent object x, or nil if none are.

uri-or-fault

(uri-or-fault u severity if-missing-token)(uri-or-fault u severity if-missing-token if-invalid-token)

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 and if-missing-token. Otherwise return nil.

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

dog-and-duck.quack.picky.required-properties

TODO: write docs

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

dog-and-duck.quack.picky.utils

Utility functions supporting the picky validator

actor-type?

(actor-type? x)

Return true if the x is a recognised actor type, else false.

any-or-faults

(any-or-faults options severity-if-none token)

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.

coll-object-reference-or-fault

(coll-object-reference-or-fault value expected-type severity token)

As object-reference-or-fault, except value argument may also be a list of objects and/or object references.

concat-non-empty

(concat-non-empty & lists)

Quick function to replace the pattern (nil-if-empty (remove nil? (concat …))) which I’m using a lot!

cond-make-fault-object

macro

(cond-make-fault-object v severity token)

If v is false or nil, return a fault object with this severity and token, else return nil.

context?

(context? x)

Returns true iff x quacks like an ActivityStreams context, else false.

+

A context is either 1. the URI (actually an IRI) activitystreams-context-uri, or 2. a collection comprising that URI and a map.

filter-severity

(filter-severity reports severity)

Return a list of reports taken from these reports where the severity of the report is greater than this or equal to this severity.

has-activity-type?

(has-activity-type? x)

Return true if the object x has a type which is an activity type, else false.

has-actor-type?

(has-actor-type? x)

Return true if the object x has a type which is an actor type, else false.

has-context?

macro

(has-context? x)

True if x is an ActivityStreams object with a valid context, else false.

has-type-or-fault

(has-type-or-fault x acceptable severity token)

If object x has a :type value which is acceptable, return nil; else return a fault object with this severity and token.

+

acceptable may be passed as either nil, a string, or a set of strings. If acceptable is nil, no type specific tests will be performed.

has-type?

(has-type? x type)

Return true if object x has type type, else false.

+

The values of type fields of ActivityStreams objects may be lists; they are considered to have a type if the type token is a member of the list.

make-fault-object

(make-fault-object severity fault)

Return a fault object with these severity, fault and narrative values.

+

An ActivityPub object MUST have a globally unique ID. Whether this is meaningful depends on whether we persist fault report objects and serve them, which at present I have no plans to do.

nil-if-empty

macro

(nil-if-empty x)

if x is an empty collection, return nil; else return x.

object-faults

(object-faults x)(object-faults x expected-type)

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.

object-or-uri?

(object-or-uri? x)

Very basic check that x is either an object or a URI.

object-reference-or-faults

(object-reference-or-faults value expected-type severity token)

If this value is either

+
    +
  1. an object of expected-type;
  2. +
  3. a URI referencing an object of expected-type; or
  4. +
  5. a link object referencing an object of expected-type
  6. +
+

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.

string-or-fault

(string-or-fault value severity token)(string-or-fault value severity token pattern)

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.

truthy?

(truthy? x)

Return true if x is truthy, else false. There must be some more idiomatic way to do this?

verb-type?

(verb-type? x)

true if x, a string, represents a recognised ActivityStreams activity type.

\ No newline at end of file diff --git a/docs/codox/dog-and-duck.quack.quack.html b/docs/codox/dog-and-duck.quack.quack.html index 108053c..ad1566c 100644 --- a/docs/codox/dog-and-duck.quack.quack.html +++ b/docs/codox/dog-and-duck.quack.quack.html @@ -1,14 +1,14 @@ -dog-and-duck.quack.quack documentation

dog-and-duck.quack.quack

Validator for ActivityPub objects: if it walks like a duck, and it quacks like a duck…

+dog-and-duck.quack.quack documentation

dog-and-duck.quack.quack

Validator for ActivityPub objects: if it walks like a duck, and it quacks like a duck…

**NOTE THAT the ActivityPub spec says

Servers SHOULD validate the content they receive to avoid content spoofing attacks

-

but in practice ActivityPub content collected in the wild bears only a hazy relationship to the spec, so this is difficult. I suspect that I may have to implement a *strict* dynamic variable, so that users can toggle some checks off.

activity?

(activity? x)(activity? x severity)

true iff x quacks like an activity, else false.

actor-or-uri?

(actor-or-uri? x)

true if x is either a URI or an actor.

-

TODO: I need to decide about whether to reify referenced objects before validation or after. After reification, every reference to an actor must be to an actor object, but before, may only be to a URI pointing to one.

actor?

(actor? x)(actor? x severity)

Returns true if x quacks like an actor, else false.

collection-page?

(collection-page? x)

true iff x quacks like a page in a paged collection, else false.

collection?

(collection? x object-type)(collection? x)

true iff x quacks like a collection of type object-type, else false.

-

With one argument, will recognise plain collections and ordered collections, but (currently) not collection pages.

link?

(link? x)(link? x severity)

true iff x quacks like a link, else false.

object?

(object? x)(object? x severity)

Returns true iff x is recognisably an ActivityStreams object.

+

but in practice ActivityPub content collected in the wild bears only a hazy relationship to the spec, so this is difficult. I suspect that I may have to implement a *strict* dynamic variable, so that users can toggle some checks off.

activity?

(activity? x)(activity? x severity)

true iff x quacks like an activity, else false.

actor-or-uri?

(actor-or-uri? x)

true if x is either a URI or an actor.

+

TODO: I need to decide about whether to reify referenced objects before validation or after. After reification, every reference to an actor must be to an actor object, but before, may only be to a URI pointing to one.

actor?

(actor? x)(actor? x severity)

Returns true if x quacks like an actor, else false.

collection-page?

(collection-page? x)

true iff x quacks like a page in a paged collection, else false.

collection?

(collection? x object-type)(collection? x)

true iff x quacks like a collection of type object-type, else false.

+

With one argument, will recognise plain collections and ordered collections, but (currently) not collection pages.

link?

(link? x)(link? x severity)

true iff x quacks like a link, else false.

object?

(object? x)(object? x severity)

Returns true iff x is recognisably an ActivityStreams object.

NOTE THAT The ActivityStreams spec says:

All properties are optional (including the id and type)

@@ -18,5 +18,5 @@

Implementers SHOULD include the ActivityPub context in their object definitions

-

but in samples found in the wild they typically don’t.

ordered-collection-page?

(ordered-collection-page? x)

true iff x quacks like a page in an ordered paged collection, else false.

ordered-collection?

(ordered-collection? x)

true iff x quacks like an ordered collection, else false.

persistent-object?

(persistent-object? x)(persistent-object? x severity)

true iff x is a persistent object.

-

Transient objects in ActivityPub are not required to have an id key, but persistent ones must have a key, and it must be an IRI (but normally a URI).

unordered-collection?

(unordered-collection? x)

true iff x quacks like an unordered collection, else false.

\ No newline at end of file +

but in samples found in the wild they typically don’t.

ordered-collection-page?

(ordered-collection-page? x)

true iff x quacks like a page in an ordered paged collection, else false.

ordered-collection?

(ordered-collection? x)

true iff x quacks like an ordered collection, else false.

persistent-object?

(persistent-object? x)(persistent-object? x severity)

true iff x is a persistent object.

+

Transient objects in ActivityPub are not required to have an id key, but persistent ones must have a key, and it must be an IRI (but normally a URI).

unordered-collection?

(unordered-collection? x)

true iff x quacks like an unordered collection, else false.

\ No newline at end of file diff --git a/docs/codox/dog-and-duck.scratch.core.html b/docs/codox/dog-and-duck.scratch.core.html index 1a895c3..82fe411 100644 --- a/docs/codox/dog-and-duck.scratch.core.html +++ b/docs/codox/dog-and-duck.scratch.core.html @@ -1,3 +1,3 @@ -dog-and-duck.scratch.core documentation

dog-and-duck.scratch.core

TODO: write docs

foo

(foo x)

I don’t do a whole lot.

\ No newline at end of file +dog-and-duck.scratch.core documentation

dog-and-duck.scratch.core

TODO: write docs

foo

(foo x)

I don’t do a whole lot.

\ No newline at end of file diff --git a/docs/codox/dog-and-duck.scratch.parser.html b/docs/codox/dog-and-duck.scratch.parser.html index f5e260d..fc1ffd1 100644 --- a/docs/codox/dog-and-duck.scratch.parser.html +++ b/docs/codox/dog-and-duck.scratch.parser.html @@ -1,3 +1,3 @@ -dog-and-duck.scratch.parser documentation

dog-and-duck.scratch.parser

TODO: write docs

clean

(clean json)

Take this json input, and return a sequence of ActivityPub objects represented by it.

\ No newline at end of file +dog-and-duck.scratch.parser documentation

dog-and-duck.scratch.parser

TODO: write docs

clean

(clean json)

Take this json input, and return a sequence of ActivityPub objects represented by it.

\ No newline at end of file diff --git a/docs/codox/dog-and-duck.scratch.scratch.html b/docs/codox/dog-and-duck.scratch.scratch.html index c0eae90..267adc9 100644 --- a/docs/codox/dog-and-duck.scratch.scratch.html +++ b/docs/codox/dog-and-duck.scratch.scratch.html @@ -1,3 +1,3 @@ -dog-and-duck.scratch.scratch documentation

dog-and-duck.scratch.scratch

Scratchpad where I try to understand how to do this stuff.

account

Fetch my account to mess with

account-handle

TODO: write docs

handle

TODO: write docs

\ No newline at end of file +dog-and-duck.scratch.scratch documentation

dog-and-duck.scratch.scratch

Scratchpad where I try to understand how to do this stuff.

account

Fetch my account to mess with

account-handle

TODO: write docs

handle

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/dog-and-duck.utils.process.html b/docs/codox/dog-and-duck.utils.process.html index 4390b03..2b0ec24 100644 --- a/docs/codox/dog-and-duck.utils.process.html +++ b/docs/codox/dog-and-duck.utils.process.html @@ -1,5 +1,5 @@ -dog-and-duck.utils.process documentation

dog-and-duck.utils.process

TODO: write docs

get-hostname

return the hostname of the current host.

+dog-and-duck.utils.process documentation

dog-and-duck.utils.process

TODO: write docs

get-hostname

return the hostname of the current host.

Java’s methods for getting the hostname are quite startlingly slow, we do not want todo this repeatedly!

get-pid

Get the process id of the current process.

OK, this is hacky as fuck, but I hope it works. The problem is that the way to get the process id has changed several times during the history of Java development, and the code for one version of Java won’t even compile in a different version.

\ No newline at end of file diff --git a/docs/codox/index.html b/docs/codox/index.html index 0aa65a0..602453f 100644 --- a/docs/codox/index.html +++ b/docs/codox/index.html @@ -1,3 +1,3 @@ -Dog-and-duck 0.1.0-SNAPSHOT

Dog-and-duck 0.1.0-SNAPSHOT

Released under the GPL-2.0-or-later

A playground for hacking ActivityPub stuff.

Installation

To install, add the following dependency to your project or build file:

[dog-and-duck "0.1.0-SNAPSHOT"]

Topics

Namespaces

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.

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.

Public variables and functions:

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.

Public variables and functions:

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.

Public variables and functions:

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.

Public variables and functions:

dog-and-duck.quack.fault-messages

TODO: write docs

Public variables and functions:

dog-and-duck.quack.quack

Validator for ActivityPub objects: if it walks like a duck, and it quacks like a duck…

dog-and-duck.scratch.core

TODO: write docs

Public variables and functions:

dog-and-duck.scratch.parser

TODO: write docs

Public variables and functions:

dog-and-duck.scratch.scratch

Scratchpad where I try to understand how to do this stuff.

Public variables and functions:

dog-and-duck.utils.process

TODO: write docs

Public variables and functions:

\ No newline at end of file +Dog-and-duck 0.1.0-SNAPSHOT

Dog-and-duck 0.1.0-SNAPSHOT

Released under the GPL-2.0-or-later

A playground for hacking ActivityPub stuff.

Installation

To install, add the following dependency to your project or build file:

[dog-and-duck "0.1.0-SNAPSHOT"]

Topics

Namespaces

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.

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.

Public variables and functions:

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.

Public variables and functions:

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.

Public variables and functions:

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.

Public variables and functions:

dog-and-duck.quack.picky.control-variables

Control variables for the picky validator.

Public variables and functions:

dog-and-duck.quack.picky.fault-messages

Narrative values for fault reports of specific types, used by the picky validator.

Public variables and functions:

dog-and-duck.quack.picky.required-properties

TODO: write docs

Public variables and functions:

    dog-and-duck.quack.quack

    Validator for ActivityPub objects: if it walks like a duck, and it quacks like a duck…

    dog-and-duck.scratch.core

    TODO: write docs

    Public variables and functions:

    dog-and-duck.scratch.parser

    TODO: write docs

    Public variables and functions:

    dog-and-duck.scratch.scratch

    Scratchpad where I try to understand how to do this stuff.

    Public variables and functions:

    dog-and-duck.utils.process

    TODO: write docs

    Public variables and functions:

    \ No newline at end of file diff --git a/docs/codox/intro.html b/docs/codox/intro.html index 7abe3b9..f487848 100644 --- a/docs/codox/intro.html +++ b/docs/codox/intro.html @@ -1,6 +1,6 @@ -Introduction

    Introduction

    +Introduction

    Introduction

    The Old Dog and Duck

    A Clojure library designed to implement the ActivityPub protocol, obviously.

    The Dog and Duck, Derby

    @@ -19,8 +19,24 @@
  • Delivering ActivityStreams objects to peers;
  • Delivering ActivityStreams objects to clients.
  • -

    NOTE THAT what Mastodon delivers to clients is not actually in ActivityStreams format; this seems to be an ad-hoc hack that’s just never been fixed and has therefore become a de-facto standard for communication between ActivityPub hosts and their clients.

    +

    Some motivations

    +

    Empowering users

    +

    The ActivityPub spec starts by saying:

    +
    +

    ActivityPub provides two layers:

    +
      +
    1. A server to server federation protocol (so decentralized websites can share information)
    2. +
    3. A client to server protocol (so users, including real-world users, bots, and other automated processes, can communicate with ActivityPub using their accounts on servers, from a phone or desktop or web application or whatever).
    4. +
    +
    +

    I’m interested in driving much more functionality down to the the client, for example feed ordering and presentation. This would allow users, for example, to choose their own (or roll their own) feed-ordering algorithms.

    My proposal would be to deliver exactly the same ActivityStreams format to my client as to other servers. There may be a valid reason for not doing this, but if there is I will discover it in due course.

    +

    Enhanced resiliency

    +

    The ActivityStreams spec seems predicated on ‘always up’ communication between at least servers, which is perhaps why there is a two tier network of ‘servers’ and ‘clients’. It also depends on HTTPS certificates to identify servers, which implies it’s vulnerable to disruption by a hostile actor with the ability to revoke certificates.

    +

    My own history with social media dates back to Usenet over UUCP, a system designed explicitly for intermittent low bandwidth connections; such a system is immensely resilient in the face of disruption to infrastructure.

    +

    Social media is useful to concerted popular action in periods of disruption, whether in the case of civil ememrgency such as earthquakes, wild fires and floods, in the case of wars, or in the case of intrusive surveillance by authoritarian governments. But to be useful in such situations it needs to be resilient, and one of the things it needs to be resilient to is parts of the network being intermittently available, or requiring rerouting.

    +

    In this I’m influenced by and hope to try to implement ideas from Ian Clarke’s Freenet and Tahrir projects, especially webs of trust.

    +

    To be clear, it is important for The Old Dog and Duck to be able to interact with other existing ‘vanilla’ ActivityStreams implementations, but I hope to experiment with enhanced communication between Dog and Duck servers to provide more FreeNet-like resiliency.

    Proposed dog-and-duck libraries

    NOTE THAT at the present stage all the proposed libraries are in one package, namely this package, but that it is proposed that in future they will form separate libraries in separate packages.

    Bar

    diff --git a/src/dog_and_duck/quack/picky.clj b/src/dog_and_duck/quack/picky.clj index 6b5b832..187ba85 100644 --- a/src/dog_and_duck/quack/picky.clj +++ b/src/dog_and_duck/quack/picky.clj @@ -27,18 +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*]] + (: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 - cond-make-fault-object - has-context? 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 @@ -57,31 +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) - (when expected-type - (list - (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` @@ -132,82 +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, 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})))) - (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` @@ -313,41 +214,6 @@ (make-fault-object :must :not-activity-type)) (when-not (string? (:summary x)) (make-fault-object :should :no-summary))))) -(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)))) - (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 @@ -374,11 +240,12 @@ "CollectionPage" "OrderedCollectionPage"]))))) ([x type] + ;; (log/info "collection-faults called with argumens " x ", " type) (case type - ["Collection" "OrderedCollection"] (any-or-faults + ("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) + ("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 998936d..a5c6016 100644 --- a/src/dog_and_duck/quack/picky/fault_messages.clj +++ b/src/dog_and_duck/quack/picky/fault_messages.clj @@ -27,6 +27,7 @@ :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/required_properties.clj b/src/dog_and_duck/quack/picky/required_properties.clj index e69de29..c9ced25 100644 --- a/src/dog_and_duck/quack/picky/required_properties.clj +++ b/src/dog_and_duck/quack/picky/required_properties.clj @@ -0,0 +1 @@ +(ns dog-and-duck.quack.picky.required-properties) \ No newline at end of file diff --git a/src/dog_and_duck/quack/picky/utils.clj b/src/dog_and_duck/quack/picky/utils.clj index fb1a408..1e4d8d0 100644 --- a/src/dog_and_duck/quack/picky/utils.clj +++ b/src/dog_and_duck/quack/picky/utils.clj @@ -1,16 +1,16 @@ (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])) @@ -203,11 +203,117 @@ there are several different valid configurations, but few or no properties are always required." [options severity-if-none token] - (let [faults (remove empty? (reduce concat options))] - (when-not (empty? faults) (cons (make-fault-object severity-if-none token) faults)))) + (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/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