Close, but no cigar.

This commit is contained in:
Simon Brooke 2023-01-08 21:29:36 +00:00
parent 34847058fc
commit 98e3c7b4e7
No known key found for this signature in database
GPG key ID: A7A4F18D1D4DF987
5 changed files with 107 additions and 36 deletions

View file

@ -9,6 +9,7 @@
xsd-date-time? xsd-date-time?
xsd-duration?]] xsd-duration?]]
[dog-and-duck.quack.picky.utils :refer [concat-non-empty [dog-and-duck.quack.picky.utils :refer [concat-non-empty
cond-make-fault-object
has-activity-type? has-activity-type?
has-context? has-context?
has-type? has-type?
@ -18,7 +19,7 @@
object-or-uri? object-or-uri?
truthy? truthy?
xsd-non-negative-integer?]] xsd-non-negative-integer?]]
[taoensso.timbre :refer [warn]]) [taoensso.timbre :refer [info warn]])
(:import [java.io FileNotFoundException] (:import [java.io FileNotFoundException]
[java.net URI URISyntaxException])) [java.net URI URISyntaxException]))
@ -77,7 +78,7 @@
:validator xsd-float?} :validator xsd-float?}
:anyOf {:collection true :anyOf {:collection true
:functional false :functional false
;; a Question should have a `:oneOf` ot `: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 object-or-uri?}
@ -196,21 +197,21 @@
:functional false :functional false
:if-invalid [:must :invalid-items] :if-invalid [:must :invalid-items]
:if-missing [:must :no-items-or-pages] :if-missing [:must :no-items-or-pages]
:required (fn [x] (or (has-type? x #{"CollectionPage" :required (fn [x] (or (has-type? x "CollectionPage")
"OrderedCollectionPage"}) (and (has-type? x "Collection")
(and (has-type? x #{"Collection"
"OrderedCollection"})
;; if it's a collection and has pages, ;; if it's a collection and has pages,
;; it doesn't need items. ;; it doesn't need items.
(not (:current x)) (not (:current x))
(not (:first x)) (not (:first x))
(not (:last x))))) (not (:last x)))))
:validator object-or-uri?} :validator (fn [pv] (and (coll? pv) (every? object-or-uri? pv)))}
:last {:functional true :last {:functional true
:if-missing [:minor :paged-collection-no-last] :if-missing [:minor :paged-collection-no-last]
:if-invalid [:must :paged-collection-invalid-last] :if-invalid [:must :paged-collection-invalid-last]
:required (fn [x] (if (try (uri? (URI. x)) :required (fn [x] (if (and
(catch URISyntaxException _ false)) (string? x)
(try (uri? (URI. x))
(catch URISyntaxException _ false)))
true true
;; if an object is a collection which has pages, ;; if an object is a collection which has pages,
;; it ought to have a `:last` page. But ;; it ought to have a `:last` page. But
@ -255,8 +256,21 @@
;; 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 object-or-uri?}
:orderedItems {:collection true
:functional false
:if-invalid [:must :invalid-items]
:if-missing [:must :no-items-or-pages]
:required (fn [x] (or (has-type? x "OrderedCollectionPage")
(and (has-type? x "OrderedCollection")
;; if it's a collection and has pages,
;; it doesn't need items.
(not (:current x))
(not (:first x))
(not (:last x)))))
:validator (fn [pv] (and (coll? pv) (every? object-or-uri? pv)))}
:origin {:functional false :origin {:functional false
:if-invalid :invalid-origin :if-invalid [:must :invalid-origin]
:validator object-or-uri?} :validator object-or-uri?}
:partOf {:functional true :partOf {:functional true
:if-missing [:must :missing-part-of] :if-missing [:must :missing-part-of]
@ -341,24 +355,48 @@
:if-invalid [:must :invalid-width] :if-invalid [:must :invalid-width]
:validator xsd-non-negative-integer?}}) :validator xsd-non-negative-integer?}})
(defn- check-property [x p] (defn check-property-required [obj prop clause]
#(let [c (object-expected-properties x) (let [required (:required clause)
r (:required c) [severity token] (:if-missing clause)]
[s m] (:if-missing c)] (when required
(when (and r (r x) (not (x p))) (when
(make-fault-object s m)))) (and (apply required (list obj)) (not (obj prop)))
(make-fault-object severity token)))))
(defn check-property-valid
[obj prop clause]
(info "obj" obj "prop" prop "clause" clause)
(let [val (obj prop)
validator (:validator clause)
[severity token] (:if-invalid clause)]
(when (and val validator)
(cond-make-fault-object
(apply validator (list val))
severity token))))
(defn check-property [obj prop]
(assert (map? obj))
(assert (keyword? prop))
(let [clause (object-expected-properties prop)]
(nil-if-empty
(remove nil?
(list
(check-property-required obj prop clause)
(check-property-valid obj prop clause))))))
(defn properties-faults (defn properties-faults
"Return a lost of faults found on properties of the object `x`, or "Return a lost of faults found on properties of the object `x`, or
`nil` if none are." `nil` if none are."
[x] [x]
(nil-if-empty (apply
concat-non-empty
(let [props (set (keys x)) (let [props (set (keys x))
required (filter required (set
(filter
#((object-expected-properties %) :required) #((object-expected-properties %) :required)
(keys object-expected-properties))] (keys object-expected-properties)))]
(map (map
#(check-property x %) (fn [p] (check-property x p))
(union props required))))) (union props required)))))
(defn object-faults (defn object-faults
@ -373,7 +411,7 @@
;; https://www.w3.org/TR/activitystreams-vocabulary/#properties ;; https://www.w3.org/TR/activitystreams-vocabulary/#properties
;; if these properties are present, these types should be checked. ;; if these properties are present, these types should be checked.
([x] ([x]
(nil-if-empty (concat-non-empty
(remove empty? (remove empty?
(list (list
(when-not (map? x) (when-not (map? x)
@ -384,13 +422,8 @@
(when-not (:type x) (when-not (:type x)
(make-fault-object :minor :no-type)) (make-fault-object :minor :no-type))
(when-not (and (map? x) (contains? x :id)) (when-not (and (map? x) (contains? x :id))
(make-fault-object :minor :no-id-transient)) (make-fault-object :minor :no-id-transient))))
(date-time-property-or-fault x :endTime :must (properties-faults x)))
:not-valid-date-time false)
(date-time-property-or-fault x :published :must
:not-valid-date-time false)
(date-time-property-or-fault x :startTime :must
:not-valid-date-time false)))))
([x expected-type] ([x expected-type]
(concat-non-empty (concat-non-empty
(object-faults x) (object-faults x)

View file

@ -1,2 +0,0 @@
(ns dog-and-duck.quack.picky.required-properties)

View file

@ -0,0 +1,34 @@
(ns dog-and-duck.quack.picky.scratch
"Development scratchpad"
(:require [clojure.data.json :refer [read-str]]
[clojure.java.io :refer [file]]
[clojure.walk :refer [keywordize-keys]]
[dog-and-duck.quack.picky.objects :refer
[object-faults]]
[dog-and-duck.quack.picky.utils :refer [concat-non-empty]]))
(def r
(remove
nil?
(map
#(try
(let [contents (read-str (slurp %))
faults (cond (map? contents) (object-faults
(keywordize-keys contents))
;; (coll? contents) (apply
;; concat-non-empty
;; (map (fn [obj]
;; (object-faults
;; (keywordize-keys obj)))
;; contents))
)]
(when-not (nil? faults)
[(.getName %) faults]))
(catch Exception any
[(.getName %) (str "Exception "
(.getName (.getClass any))
": "
(.getMessage any))]))
(filter
#(and (.isFile %) (.endsWith (.getName %) ".json"))
(file-seq (file "resources/activitystreams-test-documents"))))))

View file

@ -4,7 +4,7 @@
make-fault-object make-fault-object
truthy?]] truthy?]]
[scot.weft.i18n.core :refer [get-message]] [scot.weft.i18n.core :refer [get-message]]
[taoensso.timbre :refer [warn]]) [taoensso.timbre :refer [warn error]])
(:import [java.time LocalDateTime] (:import [java.time LocalDateTime]
[java.time.format DateTimeFormatter DateTimeParseException] [java.time.format DateTimeFormatter DateTimeParseException]
[javax.xml.datatype DatatypeFactory])) [javax.xml.datatype DatatypeFactory]))
@ -33,6 +33,9 @@
(if (LocalDateTime/from (.parse DateTimeFormatter/ISO_DATE_TIME value)) true false) (if (LocalDateTime/from (.parse DateTimeFormatter/ISO_DATE_TIME value)) true false)
(catch DateTimeParseException _ (catch DateTimeParseException _
(warn (get-message :bad-date-time) ":" value) (warn (get-message :bad-date-time) ":" value)
false)
(catch Exception e
(error "Exception thrown while parsing date" value e)
false))) false)))
(defn xsd-duration? (defn xsd-duration?
@ -41,9 +44,12 @@
[value] [value]
(truthy? (truthy?
(and (string? value) (and (string? value)
(try (DatatypeFactory/newDuration value) (try (.newDuration (DatatypeFactory/newInstance) value)
(catch IllegalArgumentException _ (catch IllegalArgumentException _
(warn (get-message :bad-duration) ":" value) (warn (get-message :bad-duration) ":" value)
false)
(catch Exception e
(error "Exception thrown while parsing duration" value e)
false))))) false)))))
(defn date-time-property-or-fault (defn date-time-property-or-fault

View file

@ -70,7 +70,7 @@
([x] ([x]
(try (try
(cond (string? x) (uri? (URI. x)) (cond (string? x) (uri? (URI. x))
(map? x) (if (and (:type x) (:id x)) true false) (map? x) true
:else false) :else false)
(catch URISyntaxException _ false) (catch URISyntaxException _ false)
(catch NullPointerException _ false))) (catch NullPointerException _ false)))
@ -228,11 +228,11 @@
;; i.e. there was at least one option that returned no faults... ;; i.e. there was at least one option that returned no faults...
(cons (make-fault-object severity-if-none token) faults)))) (cons (make-fault-object severity-if-none token) faults))))
(defmacro cond-make-fault-object (defn cond-make-fault-object
"If `v` is `false` or `nil`, return a fault object with this `severity` and `token`, "If `v` is `false` or `nil`, return a fault object with this `severity` and `token`,
else return nil." else return nil."
[v severity token] [v severity token]
`(when-not ~v (make-fault-object ~severity ~token))) (when-not v (make-fault-object severity token)))
(defn string-or-fault (defn string-or-fault
"If this `value` is not a string, return a fault object with this `severity` "If this `value` is not a string, return a fault object with this `severity`