Close, but no cigar.
This commit is contained in:
		
							parent
							
								
									34847058fc
								
							
						
					
					
						commit
						98e3c7b4e7
					
				|  | @ -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 | ||||
|          required (set | ||||
|                    (filter | ||||
|                     #((object-expected-properties %) :required) | ||||
|                    (keys object-expected-properties))] | ||||
|                     (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…
	
		Reference in a new issue