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
```
```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
```

View file

@ -27,14 +27,14 @@
:cli-help-reify "If set, reify objects referenced by URIs and check them."
:cli-help-severity "The minimum severity of faults to report."
: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"
:id-not-https "Publicly facing content SHOULD use HTTPS 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-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-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-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.)"
@ -50,6 +50,7 @@
: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-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-faults-found "No faults were found."
:no-id-persistent "Persistent objects MUST have unique global identifiers."
@ -63,4 +64,5 @@
:null-id-persistent "Persistent objects MUST have non-null identifiers."
:not-an-object "ActivityStreams object must be JSON objects."
:text-analysed "Text analysed"
:the-following "The following"
:validation-report-for "Validation report for"}

View file

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

View file

@ -1,6 +1,7 @@
(ns dog-and-duck.quack.objects
(:require [clojure.data.json :as json]
[clojure.set :refer [union]]
[clojure.walk :refer [keywordize-keys]]
[dog-and-duck.quack.constants :refer [actor-types
noun-types
re-rfc5646]]
@ -59,7 +60,7 @@
(fn [target]
(try (let [uri (URI. target)]
(when *reify-refs*
(json/read-str (slurp uri))))
(keywordize-keys (json/read-str (slurp uri)))))
(catch URISyntaxException _
(warn "Reification target" target "was not a valid URI.")
nil)
@ -94,28 +95,48 @@
**NOTE THAT** if `*reify-refs*` is `false`, referenced objects will not
actually be checked."
[value expected-type severity token]
(let [faults (cond
(string? value) (maybe-reify-or-faults value severity token expected-type)
(map? value) (if (has-type? value "Link")
(cond
([value expected-type severity token]
(let [faults (cond
(string? value) (maybe-reify-or-faults value severity token expected-type)
(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))))
(= 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-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
@ -148,7 +169,10 @@
:if-invalid [:must :invalid-actor]
:if-missing [:must :no-actor]
: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
:if-invalid [:must :invalid-number]
:validator xsd-float?}
@ -157,22 +181,30 @@
;; a Question should have a `:oneOf` or `:anyOf`, but at this layer
;; that's hard to check.
: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
: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
: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
: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
: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
: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
:if-invalid [:must :invalid-closed]
:validator (fn [pv] (truthy? (or (object-or-uri? pv)
@ -183,7 +215,7 @@
:validator string?}
:context {:functional false
: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
:if-missing [:minor :paged-collection-no-current]
:if-invalid [:must :paged-collection-invalid-current]
@ -197,8 +229,11 @@
(or (has-type? x "Collection")
(has-type? x "OrderedCollection"))
(:first x)))
:validator (fn [pv] (object-or-uri? pv #{"CollectionPage"
"OrderedCollectionPage"}))}
:validator (fn [pv] (object-reference-or-faults pv
#{"CollectionPage"
"OrderedCollectionPage"}
:must
:paged-collection-invalid-current))}
:deleted {:functional true
:if-missing [:minor :tombstone-missing-deleted]
:if-invalid [:must :invalid-deleted]
@ -207,7 +242,9 @@
:describes {:functional true
:required (fn [x] (has-type? x "Profile"))
:if-invalid [:must :invalid-describes]
:validator object-or-uri?}
:validator (fn [pv] (object-reference-or-faults pv nil
:must
:invalid-describes))}
:duration {:functional false
:if-invalid [:must :invalid-duration]
:validator xsd-duration?}
@ -227,8 +264,10 @@
(or (has-type? x "Collection")
(has-type? x "OrderedCollection"))
(:last x)))
:validator (fn [pv] (object-or-uri? pv #{"CollectionPage"
"OrderedCollectionPage"}))}
:validator (fn [pv] (object-reference-or-faults pv #{"CollectionPage"
"OrderedCollectionPage"}
:must
:paged-collection-invalid-first))}
:formerType {:functional false
:if-missing [:minor :tombstone-missing-former-type]
:if-invalid [:must :invalid-former-type]
@ -236,6 +275,7 @@
;; The narrative of the spec says this should be an `Object`,
;; but in all the provided examples it's a string. Furthermore,
;; 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?}
:generator {:functional false
:if-invalid [:must :invalid-generator]
@ -253,7 +293,9 @@
:if-invalid [:must :invalid-icon]
;; an icon is also expected to have a 1:1 aspect ratio, but that's
;; 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
:if-missing [:minor :no-id-transient]
:if-invalid [:must :invalid-id]
@ -261,13 +303,19 @@
(catch URISyntaxException _ false)))}
:image {:functional false
: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
: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
: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
:functional false
:if-invalid [:must :invalid-items]
@ -298,8 +346,10 @@
(has-type? x #{"Collection"
"OrderedCollection"})
(:first x))))
:validator (fn [pv] (object-or-uri? pv #{"CollectionPage"
"OrderedCollectionPage"}))}
:validator (fn [pv] (object-reference-or-faults pv #{"CollectionPage"
"OrderedCollectionPage"}
:must
:paged-collection-invalid-last))}
:latitude {:functional true
:if-invalid [:must :invalid-latitude]
;; The XSD spec says this is an IEEE 754-2008, and the IEEE
@ -308,7 +358,9 @@
:validator xsd-float?}
:location {:functional false
: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
:if-invalid [:must :invalid-longitude]
:validator xsd-float?}
@ -320,18 +372,25 @@
:validator string?}
:next {:functional true
:if-invalid [:must :invalid-next-page]
:validator (fn [pv] (object-or-uri? pv #{"CollectionPage"
"OrderedCollectionPage"}))}
:validator (fn [pv] (object-reference-or-faults pv #{"CollectionPage"
"OrderedCollectionPage"}
:must
:invalid-next-page))}
:object {:functional false
: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
:functional false
;; a Question should have a `:oneOf` ot `:anyOf`, but at this layer
;; that's hard to check.
: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
:functional false
:if-invalid [:must :invalid-items]
@ -346,29 +405,34 @@
:validator (fn [pv] (and (coll? pv) (every? object-or-uri? pv)))}
:origin {:functional false
: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
:if-missing [:must :missing-part-of]
:if-invalid [:must :invalid-part-of]
:required (fn [x] (object-or-uri? x #{"CollectionPage"
"OrderedCollectionPage"}))
:validator (fn [pv] (object-or-uri? pv #{"Collection"
"OrderedCollection"}))}
:required object-or-uri?
:validator (fn [pv] (object-reference-or-faults pv #{"Collection"
"OrderedCollection"}
:must
:invalid-part-of))}
:prev {:functional true
:if-invalid [:must :invalid-prior-page]
:validator (fn [pv] (object-or-uri? pv #{"CollectionPage"
"OrderedCollectionPage"}))}
:validator (fn [pv] (object-reference-or-faults pv #{"CollectionPage"
"OrderedCollectionPage"}
:must
:invalid-prior-page))}
:preview {:functional false
:if-invalid [:must :invalid-preview]
;; 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
:if-invalid [:must :invalid-date-time]
:validator xsd-date-time?}
:replies {:functional true
:if-invalid [:must :invalid-replies]
:validator (fn [pv] (object-or-uri? pv #{"Collection"
"OrderedCollection"}))}
:validator (fn [pv] (object-reference-or-faults pv #{"Collection"
"OrderedCollection"}
:must
:invalid-replies))}
:radius {:functional true
:if-invalid [:must :invalid-positive-number]
:validator (fn [pv] (and (xsd-float? pv) (> pv 0)))}
@ -381,7 +445,10 @@
}
:result {:functional false
: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
:if-invalid [:must :invalid-start-index]
:validator xsd-non-negative-integer?}
@ -392,7 +459,9 @@
:if-invalid [:must :invalid-subject]
:if-missing [:minor :no-relationship-subject]
: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
:if-invalid [:must :invalid-summary]
;; TODO: HTML formatting is allowed, but other forms of formatting
@ -400,13 +469,22 @@
:validator string?}
:tag {:functional false
: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
: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
: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
:if-invalid [:must :invalid-total-items]
:validator xsd-non-negative-integer?}
@ -434,7 +512,7 @@
"Check whether this `prop` of this `obj` is required with respect to
this `clause`; if it is both required and missing, return a list of
one fault; else return `nil`."
[obj prop clause]
[obj prop clause]
(let [required (:required clause)
[severity token] (:if-missing clause)]
(when required
@ -524,21 +602,3 @@
(list
(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
[xsd:nonNegativeInteger](https://www.w3.org/TR/xmlschema11-2/#nonNegativeInteger), else `false`"
[x]
(and (integer? x)(>= x 0)))
(if (integer? x) (>= x 0) false))
(defn has-type?
"Return `true` if object `x` has a type in `acceptable`, else `false`.
@ -224,7 +224,7 @@
(coll? x)
(seq x)
(every?
#(has-type? % "Fault") x)))
#(when (map? x)(has-type? % "Fault")) x)))
(defmacro nil-if-empty