At least one numerical check still breaking.

The command line:
java -jar target/uberjar/quack-0.1.0-SNAPSHOT-standalone.jar -i ../dog-and-duck/resources/activitystreams-test-documents//vocabulary-ex190-jsonld.json -o docs/samples/vocabulary-ex190-jsonld.html -f html

triggers the error.
This commit is contained in:
Simon Brooke 2023-01-11 00:49:16 +00:00
parent 440ea003e2
commit 4c4b517212
5 changed files with 150 additions and 86 deletions

View file

@ -12,7 +12,7 @@ Download from http://example.com/FIXME.
## Usage ## Usage
``` ```bash
java -jar target/dog-and-duck-0.1.0-standalone.jar -i resources/activitystreams-test-documents/vocabulary-ex10-jsonld.json -f html -o report.html -s info java -jar target/dog-and-duck-0.1.0-standalone.jar -i resources/activitystreams-test-documents/vocabulary-ex10-jsonld.json -f html -o report.html -s info
``` ```

View file

@ -27,14 +27,14 @@
:cli-help-reify "If set, reify objects referenced by URIs and check them." :cli-help-reify "If set, reify objects referenced by URIs and check them."
:cli-help-severity "The minimum severity of faults to report." :cli-help-severity "The minimum severity of faults to report."
:expected-collection "A collection was expected, but was not found." :expected-collection "A collection was expected, but was not found."
:faults-found "The following faults were found" :faults-found "faults were found"
:generated-on "Generated on" :generated-on "Generated on"
:id-not-https "Publicly facing content SHOULD use HTTPS URIs" :id-not-https "Publicly facing content SHOULD use HTTPS URIs"
:id-not-uri "identifiers must be publicly dereferencable URIs" :id-not-uri "identifiers must be publicly dereferencable URIs"
:invalid-actor "The value of the `actor` property of an activity MUST be an instance of an Actor type" :invalid-actor "The value of the `actor` property of an activity MUST be an instance of an Actor type"
:invalid-attachment "The value of the `attachment` property MUST be an instance of Object or of Link." :invalid-attachment "The value of the `attachment` property MUST be an instance of Object or of Link."
:invalid-attribution "The value of the `attributedTo` property MUST be an instance of Object or of Link, or a sequence or collection of such." :invalid-attribution "The value of the `attributedTo` property MUST be an instance of Object or of Link, or a sequence or collection of such."
:invalid-audience "The value of the `audience` property MUST be an instance of Object or of Link, or a sequence or collection of such." :invalid-audience "The value of the `audience`, `cc` and `bcc` properties MUST be instances of Object or of Link, or sequences or collections of such."
:invalid-closed "The value of the `closed` property MUST be one of: 1. an Object; 2. an xsd:dateTime; 3. a boolean." :invalid-closed "The value of the `closed` property MUST be one of: 1. an Object; 2. an xsd:dateTime; 3. a boolean."
:invalid-content "The value of the `content` property MUST be a string, optionally with embedded markup." :invalid-content "The value of the `content` property MUST be a string, optionally with embedded markup."
:invalid-context "The value of the `context` property (NOTE: different from `@context` MUST be an instance of Object or of Link, or a sequence or collection of such.)" :invalid-context "The value of the `context` property (NOTE: different from `@context` MUST be an instance of Object or of Link, or a sequence or collection of such.)"
@ -50,6 +50,7 @@
:invalid-origin "The value of the `origin` property MUST be an Object." :invalid-origin "The value of the `origin` property MUST be an Object."
:invalid-part-of "The value of the `partOf` property of a CollectionPage MUST be an instance of a Collection or an OrderedCollection." :invalid-part-of "The value of the `partOf` property of a CollectionPage MUST be an instance of a Collection or an OrderedCollection."
:invalid-prior-page "The value of the `prev` property of a CollectionPage MUST be an instance of a Collection or an OrderedCollection." :invalid-prior-page "The value of the `prev` property of a CollectionPage MUST be an instance of a Collection or an OrderedCollection."
:missing-part-of "The `partOf` field of a CollectionPage was missing. It identifies the Collection to which a CollectionPage object's items belong."
:no-context "Section 3 of the ActivityPub specification states Implementers SHOULD include the ActivityPub context in their object definitions`." :no-context "Section 3 of the ActivityPub specification states Implementers SHOULD include the ActivityPub context in their object definitions`."
:no-faults-found "No faults were found." :no-faults-found "No faults were found."
:no-id-persistent "Persistent objects MUST have unique global identifiers." :no-id-persistent "Persistent objects MUST have unique global identifiers."
@ -63,4 +64,5 @@
:null-id-persistent "Persistent objects MUST have non-null identifiers." :null-id-persistent "Persistent objects MUST have non-null identifiers."
:not-an-object "ActivityStreams object must be JSON objects." :not-an-object "ActivityStreams object must be JSON objects."
:text-analysed "Text analysed" :text-analysed "Text analysed"
:the-following "The following"
:validation-report-for "Validation report for"} :validation-report-for "Validation report for"}

View file

@ -149,7 +149,9 @@
(java.time.LocalDateTime/now) (java.time.LocalDateTime/now)
(get-message :by) (get-message :by)
version]))] version]))]
[:h2 (get-message :faults-found)] [:h2 (join " " (list (get-message :the-following)
(count faults)
(get-message :faults-found)))]
(if-not (if-not
(empty? faults) (empty? faults)
(apply (apply

View file

@ -1,6 +1,7 @@
(ns dog-and-duck.quack.objects (ns dog-and-duck.quack.objects
(:require [clojure.data.json :as json] (:require [clojure.data.json :as json]
[clojure.set :refer [union]] [clojure.set :refer [union]]
[clojure.walk :refer [keywordize-keys]]
[dog-and-duck.quack.constants :refer [actor-types [dog-and-duck.quack.constants :refer [actor-types
noun-types noun-types
re-rfc5646]] re-rfc5646]]
@ -59,7 +60,7 @@
(fn [target] (fn [target]
(try (let [uri (URI. target)] (try (let [uri (URI. target)]
(when *reify-refs* (when *reify-refs*
(json/read-str (slurp uri)))) (keywordize-keys (json/read-str (slurp uri)))))
(catch URISyntaxException _ (catch URISyntaxException _
(warn "Reification target" target "was not a valid URI.") (warn "Reification target" target "was not a valid URI.")
nil) nil)
@ -94,7 +95,7 @@
**NOTE THAT** if `*reify-refs*` is `false`, referenced objects will not **NOTE THAT** if `*reify-refs*` is `false`, referenced objects will not
actually be checked." actually be checked."
[value expected-type severity token] ([value expected-type severity token]
(let [faults (cond (let [faults (cond
(string? value) (maybe-reify-or-faults value severity token expected-type) (string? value) (maybe-reify-or-faults value severity token expected-type)
(map? value) (if (has-type? value "Link") (map? value) (if (has-type? value "Link")
@ -115,7 +116,27 @@
:expected-type expected-type :expected-type expected-type
:severity severity :severity severity
:token token})))] :token token})))]
(when faults (cons (make-fault-object severity token) faults)))) (when faults (cons (make-fault-object severity token) faults)))))
(defn coll-object-reference-or-faults
"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
(string? value) (maybe-reify-or-faults value expected-type severity token)
(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}))))
(def object-expected-properties (def object-expected-properties
@ -148,7 +169,10 @@
:if-invalid [:must :invalid-actor] :if-invalid [:must :invalid-actor]
:if-missing [:must :no-actor] :if-missing [:must :no-actor]
:required has-activity-type? :required has-activity-type?
:validator object-or-uri?} :validator (fn [pv] (coll-object-reference-or-faults pv
actor-types
:must
:invalid-actor))}
:altitude {:functional false :altitude {:functional false
:if-invalid [:must :invalid-number] :if-invalid [:must :invalid-number]
:validator xsd-float?} :validator xsd-float?}
@ -157,22 +181,30 @@
;; a Question should have a `:oneOf` or `:anyOf`, but at this layer ;; a Question should have a `:oneOf` or `:anyOf`, but at this layer
;; that's hard to check. ;; that's hard to check.
:if-invalid [:must :invalid-option] :if-invalid [:must :invalid-option]
:validator object-or-uri?} :validator (fn [pv] (coll-object-reference-or-faults pv nil
:must
:invalid-actor))}
:attachment {:functional false :attachment {:functional false
:if-invalid [:must :invalid-attachment] :if-invalid [:must :invalid-attachment]
:validator object-or-uri?} :validator (fn [pv] (coll-object-reference-or-faults pv nil
:must
:invalid-attachment))}
:attributedTo {:functional false :attributedTo {:functional false
:if-invalid [:must :invalid-attribution] :if-invalid [:must :invalid-attribution]
:validator object-or-uri?} :validator (fn [pv] (coll-object-reference-or-faults pv nil
:must
:invalid-attribution))}
:audience {:functional false :audience {:functional false
:if-invalid [:must :invalid-audience] :if-invalid [:must :invalid-audience]
:validator object-or-uri?} :validator (fn [pv] (coll-object-reference-or-faults pv nil
:must
:invalid-audience))}
:bcc {:functional false :bcc {:functional false
:if-invalid [:must :invalid-audience] ;; do we need a separate message for bcc, cc, etc? :if-invalid [:must :invalid-audience] ;; do we need a separate message for bcc, cc, etc?
:validator object-or-uri?} :validator (fn [pv] (coll-object-reference-or-faults pv nil :must :invalid-audience))}
:cc {:functional false :cc {:functional false
:if-invalid [:must :invalid-audience] ;; do we need a separate message for bcc, cc, etc? :if-invalid [:must :invalid-audience] ;; do we need a separate message for bcc, cc, etc?
:validator object-or-uri?} :validator (fn [pv] (coll-object-reference-or-faults pv nil :must :invalid-audience))}
:closed {:functional false :closed {:functional false
:if-invalid [:must :invalid-closed] :if-invalid [:must :invalid-closed]
:validator (fn [pv] (truthy? (or (object-or-uri? pv) :validator (fn [pv] (truthy? (or (object-or-uri? pv)
@ -183,7 +215,7 @@
:validator string?} :validator string?}
:context {:functional false :context {:functional false
:if-invalid [:must :invalid-context] :if-invalid [:must :invalid-context]
:validator object-or-uri?} :validator (fn [pv] (coll-object-reference-or-faults pv nil :must :invalid-context))}
:current {:functional true :current {:functional true
:if-missing [:minor :paged-collection-no-current] :if-missing [:minor :paged-collection-no-current]
:if-invalid [:must :paged-collection-invalid-current] :if-invalid [:must :paged-collection-invalid-current]
@ -197,8 +229,11 @@
(or (has-type? x "Collection") (or (has-type? x "Collection")
(has-type? x "OrderedCollection")) (has-type? x "OrderedCollection"))
(:first x))) (:first x)))
:validator (fn [pv] (object-or-uri? pv #{"CollectionPage" :validator (fn [pv] (object-reference-or-faults pv
"OrderedCollectionPage"}))} #{"CollectionPage"
"OrderedCollectionPage"}
:must
:paged-collection-invalid-current))}
:deleted {:functional true :deleted {:functional true
:if-missing [:minor :tombstone-missing-deleted] :if-missing [:minor :tombstone-missing-deleted]
:if-invalid [:must :invalid-deleted] :if-invalid [:must :invalid-deleted]
@ -207,7 +242,9 @@
:describes {:functional true :describes {:functional true
:required (fn [x] (has-type? x "Profile")) :required (fn [x] (has-type? x "Profile"))
:if-invalid [:must :invalid-describes] :if-invalid [:must :invalid-describes]
:validator object-or-uri?} :validator (fn [pv] (object-reference-or-faults pv nil
:must
:invalid-describes))}
:duration {:functional false :duration {:functional false
:if-invalid [:must :invalid-duration] :if-invalid [:must :invalid-duration]
:validator xsd-duration?} :validator xsd-duration?}
@ -227,8 +264,10 @@
(or (has-type? x "Collection") (or (has-type? x "Collection")
(has-type? x "OrderedCollection")) (has-type? x "OrderedCollection"))
(:last x))) (:last x)))
:validator (fn [pv] (object-or-uri? pv #{"CollectionPage" :validator (fn [pv] (object-reference-or-faults pv #{"CollectionPage"
"OrderedCollectionPage"}))} "OrderedCollectionPage"}
:must
:paged-collection-invalid-first))}
:formerType {:functional false :formerType {:functional false
:if-missing [:minor :tombstone-missing-former-type] :if-missing [:minor :tombstone-missing-former-type]
:if-invalid [:must :invalid-former-type] :if-invalid [:must :invalid-former-type]
@ -236,6 +275,7 @@
;; The narrative of the spec says this should be an `Object`, ;; The narrative of the spec says this should be an `Object`,
;; but in all the provided examples it's a string. Furthermore, ;; but in all the provided examples it's a string. Furthermore,
;; it seems it must name a known object type within the context. ;; it seems it must name a known object type within the context.
;; So TODO I'm assuming an error in the spec here.
:validator string?} :validator string?}
:generator {:functional false :generator {:functional false
:if-invalid [:must :invalid-generator] :if-invalid [:must :invalid-generator]
@ -253,7 +293,9 @@
:if-invalid [:must :invalid-icon] :if-invalid [:must :invalid-icon]
;; an icon is also expected to have a 1:1 aspect ratio, but that's ;; an icon is also expected to have a 1:1 aspect ratio, but that's
;; too much detail at this level of verification ;; too much detail at this level of verification
:validator (fn [pv] (object-or-uri? pv "Image"))} :validator (fn [pv] (coll-object-reference-or-faults pv "Image"
:must
:invalid-icon))}
:id {:functional true :id {:functional true
:if-missing [:minor :no-id-transient] :if-missing [:minor :no-id-transient]
:if-invalid [:must :invalid-id] :if-invalid [:must :invalid-id]
@ -261,13 +303,19 @@
(catch URISyntaxException _ false)))} (catch URISyntaxException _ false)))}
:image {:functional false :image {:functional false
:if-invalid [:must :invalid-image] :if-invalid [:must :invalid-image]
:validator (fn [pv] (object-or-uri? pv "Image"))} :validator (fn [pv] (coll-object-reference-or-faults pv "Image"
:must
:invalid-image))}
:inReplyTo {:functional false :inReplyTo {:functional false
:if-invalid [:must :invalid-in-reply-to] :if-invalid [:must :invalid-in-reply-to]
:validator (fn [pv] (object-or-uri? pv noun-types))} :validator (fn [pv] (coll-object-reference-or-faults pv noun-types
:must
:invalid-in-reply-to))}
:instrument {:functional false :instrument {:functional false
:if-invalid [:must :invalid-instrument] :if-invalid [:must :invalid-instrument]
:validator object-or-uri?} :validator (fn [pv] (coll-object-reference-or-faults pv nil
:must
:invalid-instrument))}
:items {:collection true :items {:collection true
:functional false :functional false
:if-invalid [:must :invalid-items] :if-invalid [:must :invalid-items]
@ -298,8 +346,10 @@
(has-type? x #{"Collection" (has-type? x #{"Collection"
"OrderedCollection"}) "OrderedCollection"})
(:first x)))) (:first x))))
:validator (fn [pv] (object-or-uri? pv #{"CollectionPage" :validator (fn [pv] (object-reference-or-faults pv #{"CollectionPage"
"OrderedCollectionPage"}))} "OrderedCollectionPage"}
:must
:paged-collection-invalid-last))}
:latitude {:functional true :latitude {:functional true
:if-invalid [:must :invalid-latitude] :if-invalid [:must :invalid-latitude]
;; The XSD spec says this is an IEEE 754-2008, and the IEEE ;; The XSD spec says this is an IEEE 754-2008, and the IEEE
@ -308,7 +358,9 @@
:validator xsd-float?} :validator xsd-float?}
:location {:functional false :location {:functional false
:if-invalid [:must :invalid-location] :if-invalid [:must :invalid-location]
:validator (fn [pv] (object-or-uri? pv #{"Place"}))} :validator (fn [pv] (coll-object-reference-or-faults pv #{"Place"}
:must
:invalid-location))}
:longitude {:functional true :longitude {:functional true
:if-invalid [:must :invalid-longitude] :if-invalid [:must :invalid-longitude]
:validator xsd-float?} :validator xsd-float?}
@ -320,18 +372,25 @@
:validator string?} :validator string?}
:next {:functional true :next {:functional true
:if-invalid [:must :invalid-next-page] :if-invalid [:must :invalid-next-page]
:validator (fn [pv] (object-or-uri? pv #{"CollectionPage" :validator (fn [pv] (object-reference-or-faults pv #{"CollectionPage"
"OrderedCollectionPage"}))} "OrderedCollectionPage"}
:must
:invalid-next-page))}
:object {:functional false :object {:functional false
:if-invalid [:must :invalid-direct-object] :if-invalid [:must :invalid-direct-object]
:validator object-or-uri?} :validator (fn [pv]
(coll-object-reference-or-faults pv nil
:must
:invalid-direct-object))}
:oneOf {:collection true :oneOf {:collection true
:functional false :functional false
;; a Question should have a `:oneOf` ot `:anyOf`, but at this layer ;; a Question should have a `:oneOf` ot `:anyOf`, but at this layer
;; that's hard to check. ;; that's hard to check.
:if-invalid [:must :invalid-option] :if-invalid [:must :invalid-option]
:validator object-or-uri?} :validator (fn [pv]
(coll-object-reference-or-faults pv nil
:must
:invalid-option))}
:orderedItems {:collection true :orderedItems {:collection true
:functional false :functional false
:if-invalid [:must :invalid-items] :if-invalid [:must :invalid-items]
@ -346,29 +405,34 @@
:validator (fn [pv] (and (coll? pv) (every? object-or-uri? pv)))} :validator (fn [pv] (and (coll? pv) (every? object-or-uri? pv)))}
:origin {:functional false :origin {:functional false
:if-invalid [:must :invalid-origin] :if-invalid [:must :invalid-origin]
:validator object-or-uri?} :validator (fn [pv] (coll-object-reference-or-faults pv nil :must :invalid-origin))}
:partOf {:functional true :partOf {:functional true
:if-missing [:must :missing-part-of] :if-missing [:must :missing-part-of]
:if-invalid [:must :invalid-part-of] :if-invalid [:must :invalid-part-of]
:required (fn [x] (object-or-uri? x #{"CollectionPage" :required object-or-uri?
"OrderedCollectionPage"})) :validator (fn [pv] (object-reference-or-faults pv #{"Collection"
:validator (fn [pv] (object-or-uri? pv #{"Collection" "OrderedCollection"}
"OrderedCollection"}))} :must
:invalid-part-of))}
:prev {:functional true :prev {:functional true
:if-invalid [:must :invalid-prior-page] :if-invalid [:must :invalid-prior-page]
:validator (fn [pv] (object-or-uri? pv #{"CollectionPage" :validator (fn [pv] (object-reference-or-faults pv #{"CollectionPage"
"OrderedCollectionPage"}))} "OrderedCollectionPage"}
:must
:invalid-prior-page))}
:preview {:functional false :preview {:functional false
:if-invalid [:must :invalid-preview] :if-invalid [:must :invalid-preview]
;; probably likely to be an Image or Video, but that isn't stated. ;; probably likely to be an Image or Video, but that isn't stated.
:validator object-or-uri?} :validator (fn [pv] (coll-object-reference-or-faults pv nil :must :invalid-preview))}
:published {:functional true :published {:functional true
:if-invalid [:must :invalid-date-time] :if-invalid [:must :invalid-date-time]
:validator xsd-date-time?} :validator xsd-date-time?}
:replies {:functional true :replies {:functional true
:if-invalid [:must :invalid-replies] :if-invalid [:must :invalid-replies]
:validator (fn [pv] (object-or-uri? pv #{"Collection" :validator (fn [pv] (object-reference-or-faults pv #{"Collection"
"OrderedCollection"}))} "OrderedCollection"}
:must
:invalid-replies))}
:radius {:functional true :radius {:functional true
:if-invalid [:must :invalid-positive-number] :if-invalid [:must :invalid-positive-number]
:validator (fn [pv] (and (xsd-float? pv) (> pv 0)))} :validator (fn [pv] (and (xsd-float? pv) (> pv 0)))}
@ -381,7 +445,10 @@
} }
:result {:functional false :result {:functional false
:if-invalid [:must :invalid-result] :if-invalid [:must :invalid-result]
:validator object-or-uri?} :validator (fn [pv]
(coll-object-reference-or-faults pv nil
:must
:invalid-result))}
:startIndex {:functional true :startIndex {:functional true
:if-invalid [:must :invalid-start-index] :if-invalid [:must :invalid-start-index]
:validator xsd-non-negative-integer?} :validator xsd-non-negative-integer?}
@ -392,7 +459,9 @@
:if-invalid [:must :invalid-subject] :if-invalid [:must :invalid-subject]
:if-missing [:minor :no-relationship-subject] :if-missing [:minor :no-relationship-subject]
:required (fn [x] (has-type? x "Relationship")) :required (fn [x] (has-type? x "Relationship"))
:validator object-or-uri?} :validator (fn [pv] (object-reference-or-faults pv nil
:must
:invalid-subject))}
:summary {:functional false :summary {:functional false
:if-invalid [:must :invalid-summary] :if-invalid [:must :invalid-summary]
;; TODO: HTML formatting is allowed, but other forms of formatting ;; TODO: HTML formatting is allowed, but other forms of formatting
@ -400,13 +469,22 @@
:validator string?} :validator string?}
:tag {:functional false :tag {:functional false
:if-invalid [:must :invalid-tag] :if-invalid [:must :invalid-tag]
:validator object-or-uri?} :validator (fn [pv]
(coll-object-reference-or-faults pv nil
:must
:invalid-tag))}
:target {:functional false :target {:functional false
:if-invalid [:must :invalid-target] :if-invalid [:must :invalid-target]
:validator object-or-uri?} :validator (fn [pv]
(coll-object-reference-or-faults pv nil
:must
:invalid-target))}
:to {:functional false :to {:functional false
:if-invalid [:must :invalid-to] :if-invalid [:must :invalid-to]
:validator (fn [pv] (object-or-uri? pv actor-types))} :validator (fn [pv]
(coll-object-reference-or-faults pv actor-types
:must
:invalid-to))}
:totalItems {:functional true :totalItems {:functional true
:if-invalid [:must :invalid-total-items] :if-invalid [:must :invalid-total-items]
:validator xsd-non-negative-integer?} :validator xsd-non-negative-integer?}
@ -524,21 +602,3 @@
(list (list
(has-type-or-fault x expected-type :critical :unexpected-type)))))) (has-type-or-fault x expected-type :critical :unexpected-type))))))
;; (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}))))

View file

@ -44,7 +44,7 @@
"Return `true` if `value` matches the pattern for an "Return `true` if `value` matches the pattern for an
[xsd:nonNegativeInteger](https://www.w3.org/TR/xmlschema11-2/#nonNegativeInteger), else `false`" [xsd:nonNegativeInteger](https://www.w3.org/TR/xmlschema11-2/#nonNegativeInteger), else `false`"
[x] [x]
(and (integer? x)(>= x 0))) (if (integer? x) (>= x 0) false))
(defn has-type? (defn has-type?
"Return `true` if object `x` has a type in `acceptable`, else `false`. "Return `true` if object `x` has a type in `acceptable`, else `false`.
@ -224,7 +224,7 @@
(coll? x) (coll? x)
(seq x) (seq x)
(every? (every?
#(has-type? % "Fault") x))) #(when (map? x)(has-type? % "Fault")) x)))
(defmacro nil-if-empty (defmacro nil-if-empty