Close, but no cigar.
This commit is contained in:
		
							parent
							
								
									34847058fc
								
							
						
					
					
						commit
						98e3c7b4e7
					
				
					 5 changed files with 107 additions and 36 deletions
				
			
		| 
						 | 
				
			
			@ -9,6 +9,7 @@
 | 
			
		|||
                                                   xsd-date-time?
 | 
			
		||||
                                                   xsd-duration?]]
 | 
			
		||||
            [dog-and-duck.quack.picky.utils :refer [concat-non-empty
 | 
			
		||||
                                                    cond-make-fault-object
 | 
			
		||||
                                                    has-activity-type?
 | 
			
		||||
                                                    has-context?
 | 
			
		||||
                                                    has-type?
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +19,7 @@
 | 
			
		|||
                                                    object-or-uri?
 | 
			
		||||
                                                    truthy?
 | 
			
		||||
                                                    xsd-non-negative-integer?]]
 | 
			
		||||
            [taoensso.timbre :refer [warn]])
 | 
			
		||||
            [taoensso.timbre :refer [info warn]])
 | 
			
		||||
  (:import [java.io FileNotFoundException]
 | 
			
		||||
           [java.net URI URISyntaxException]))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -77,7 +78,7 @@
 | 
			
		|||
              :validator xsd-float?}
 | 
			
		||||
   :anyOf {:collection true
 | 
			
		||||
           :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.
 | 
			
		||||
           :if-invalid [:must :invalid-option]
 | 
			
		||||
           :validator object-or-uri?}
 | 
			
		||||
| 
						 | 
				
			
			@ -196,21 +197,21 @@
 | 
			
		|||
           :functional false
 | 
			
		||||
           :if-invalid [:must :invalid-items]
 | 
			
		||||
           :if-missing [:must :no-items-or-pages]
 | 
			
		||||
           :required (fn [x] (or (has-type? x #{"CollectionPage"
 | 
			
		||||
                                                "OrderedCollectionPage"})
 | 
			
		||||
                                 (and (has-type? x #{"Collection"
 | 
			
		||||
                                                     "OrderedCollection"})
 | 
			
		||||
           :required (fn [x] (or (has-type? x "CollectionPage")
 | 
			
		||||
                                 (and (has-type? x "Collection")
 | 
			
		||||
                                      ;; if it's a collection and has pages,
 | 
			
		||||
                                      ;; it doesn't need items.
 | 
			
		||||
                                      (not (:current x))
 | 
			
		||||
                                      (not (:first x))
 | 
			
		||||
                                      (not (:last x)))))
 | 
			
		||||
           :validator object-or-uri?}
 | 
			
		||||
           :validator (fn [pv] (and (coll? pv) (every? object-or-uri? pv)))}
 | 
			
		||||
   :last {:functional true
 | 
			
		||||
          :if-missing [:minor :paged-collection-no-last]
 | 
			
		||||
          :if-invalid [:must :paged-collection-invalid-last]
 | 
			
		||||
          :required (fn [x] (if (try (uri? (URI. x))
 | 
			
		||||
                                     (catch URISyntaxException _ false))
 | 
			
		||||
          :required (fn [x] (if (and
 | 
			
		||||
                                 (string? x)
 | 
			
		||||
                                 (try (uri? (URI. x))
 | 
			
		||||
                                      (catch URISyntaxException _ false)))
 | 
			
		||||
                              true
 | 
			
		||||
                                 ;; if an object is a collection which has pages,
 | 
			
		||||
                                 ;; it ought to have a `:last` page. But 
 | 
			
		||||
| 
						 | 
				
			
			@ -255,8 +256,21 @@
 | 
			
		|||
           ;; that's hard to check.
 | 
			
		||||
           :if-invalid [:must :invalid-option]
 | 
			
		||||
           :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
 | 
			
		||||
            :if-invalid :invalid-origin
 | 
			
		||||
            :if-invalid [:must :invalid-origin]
 | 
			
		||||
            :validator object-or-uri?}
 | 
			
		||||
   :partOf {:functional true
 | 
			
		||||
            :if-missing [:must :missing-part-of]
 | 
			
		||||
| 
						 | 
				
			
			@ -341,24 +355,48 @@
 | 
			
		|||
           :if-invalid [:must :invalid-width]
 | 
			
		||||
           :validator xsd-non-negative-integer?}})
 | 
			
		||||
 | 
			
		||||
(defn- check-property [x p]
 | 
			
		||||
  #(let [c (object-expected-properties x)
 | 
			
		||||
         r (:required c)
 | 
			
		||||
         [s m] (:if-missing c)]
 | 
			
		||||
     (when (and r (r x) (not (x p)))
 | 
			
		||||
       (make-fault-object s m))))
 | 
			
		||||
(defn check-property-required [obj prop clause]
 | 
			
		||||
  (let [required (:required clause)
 | 
			
		||||
        [severity token] (:if-missing clause)]
 | 
			
		||||
    (when required
 | 
			
		||||
      (when
 | 
			
		||||
       (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
 | 
			
		||||
  "Return a lost of faults found on properties of the object `x`, or
 | 
			
		||||
   `nil` if none are."
 | 
			
		||||
  [x]
 | 
			
		||||
  (nil-if-empty
 | 
			
		||||
  (apply 
 | 
			
		||||
   concat-non-empty
 | 
			
		||||
   (let [props (set (keys x))
 | 
			
		||||
         required (filter
 | 
			
		||||
                   #((object-expected-properties %) :required)
 | 
			
		||||
                   (keys object-expected-properties))]
 | 
			
		||||
         required (set
 | 
			
		||||
                   (filter
 | 
			
		||||
                    #((object-expected-properties %) :required)
 | 
			
		||||
                    (keys object-expected-properties)))]
 | 
			
		||||
     (map
 | 
			
		||||
      #(check-property x %)
 | 
			
		||||
      (fn [p] (check-property x p))
 | 
			
		||||
      (union props required)))))
 | 
			
		||||
 | 
			
		||||
(defn object-faults
 | 
			
		||||
| 
						 | 
				
			
			@ -373,7 +411,7 @@
 | 
			
		|||
  ;; https://www.w3.org/TR/activitystreams-vocabulary/#properties
 | 
			
		||||
  ;; if these properties are present, these types should be checked.
 | 
			
		||||
  ([x]
 | 
			
		||||
   (nil-if-empty
 | 
			
		||||
   (concat-non-empty
 | 
			
		||||
    (remove empty?
 | 
			
		||||
            (list
 | 
			
		||||
             (when-not (map? x)
 | 
			
		||||
| 
						 | 
				
			
			@ -384,13 +422,8 @@
 | 
			
		|||
             (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))
 | 
			
		||||
             (date-time-property-or-fault x :endTime :must
 | 
			
		||||
                                          :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)))))
 | 
			
		||||
               (make-fault-object :minor :no-id-transient))))
 | 
			
		||||
    (properties-faults x)))
 | 
			
		||||
  ([x expected-type]
 | 
			
		||||
   (concat-non-empty
 | 
			
		||||
    (object-faults x)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,2 +0,0 @@
 | 
			
		|||
(ns dog-and-duck.quack.picky.required-properties)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										34
									
								
								src/dog_and_duck/quack/picky/scratch.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/dog_and_duck/quack/picky/scratch.clj
									
										
									
									
									
										Normal 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"))))))
 | 
			
		||||
| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
                                                    make-fault-object
 | 
			
		||||
                                                    truthy?]]
 | 
			
		||||
            [scot.weft.i18n.core :refer [get-message]]
 | 
			
		||||
            [taoensso.timbre :refer [warn]])
 | 
			
		||||
            [taoensso.timbre :refer [warn error]])
 | 
			
		||||
  (:import [java.time LocalDateTime]
 | 
			
		||||
           [java.time.format DateTimeFormatter DateTimeParseException]
 | 
			
		||||
           [javax.xml.datatype DatatypeFactory]))
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +33,9 @@
 | 
			
		|||
    (if (LocalDateTime/from (.parse DateTimeFormatter/ISO_DATE_TIME value)) true false)
 | 
			
		||||
    (catch DateTimeParseException _
 | 
			
		||||
      (warn (get-message :bad-date-time) ":" value)
 | 
			
		||||
      false)
 | 
			
		||||
    (catch Exception e
 | 
			
		||||
      (error "Exception thrown while parsing date" value e)
 | 
			
		||||
      false)))
 | 
			
		||||
 | 
			
		||||
(defn xsd-duration?
 | 
			
		||||
| 
						 | 
				
			
			@ -41,9 +44,12 @@
 | 
			
		|||
  [value]
 | 
			
		||||
  (truthy?
 | 
			
		||||
   (and (string? value)
 | 
			
		||||
        (try (DatatypeFactory/newDuration value)
 | 
			
		||||
        (try (.newDuration (DatatypeFactory/newInstance) value)
 | 
			
		||||
             (catch IllegalArgumentException _
 | 
			
		||||
               (warn (get-message :bad-duration) ":" value)
 | 
			
		||||
               false)
 | 
			
		||||
             (catch Exception e
 | 
			
		||||
               (error "Exception thrown while parsing duration" value e)
 | 
			
		||||
               false)))))
 | 
			
		||||
 | 
			
		||||
(defn date-time-property-or-fault
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,7 +70,7 @@
 | 
			
		|||
  ([x]
 | 
			
		||||
   (try
 | 
			
		||||
     (cond (string? x) (uri? (URI. x))
 | 
			
		||||
           (map? x) (if (and (:type x) (:id x)) true false)
 | 
			
		||||
           (map? x) true
 | 
			
		||||
           :else false)
 | 
			
		||||
     (catch URISyntaxException _ false)
 | 
			
		||||
     (catch NullPointerException _ false)))
 | 
			
		||||
| 
						 | 
				
			
			@ -228,11 +228,11 @@
 | 
			
		|||
      ;; 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
 | 
			
		||||
(defn 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)))
 | 
			
		||||
  (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` 
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue