Skip to content

Commit e14d07d

Browse files
committed
Merge pull request #4 from yetanalytics/better_errors
Better errors
2 parents 2b26a9d + 4767ee8 commit e14d07d

9 files changed

Lines changed: 344 additions & 159 deletions

File tree

README.org

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,18 @@ You can use the =errors->data= fn to coerce errors into nested data that looks l
100100
(let [bad-statement (-> statement
101101
(dissoc "actor")
102102
(assoc "id" 123)]
103-
(xs/errors->data (xs/statement-checker bad-statement))))
104-
;; => {"actor" "Missing", "id" "Not a string: 123"}
103+
(xs/errors->data (xs/statement-checker bad-statement :en)))) ;; ltag is optional, defaults to :en
104+
;; => {"actor" "Missing required key", "id" "Not a string: 123"}
105+
#+END_SRC
106+
107+
If you want a map of errors to their paths in the nested data, use =errors->paths=:
108+
109+
#+BEGIN_SRC clojure
110+
(let [bad-statement (-> statement
111+
(dissoc "actor")
112+
(assoc "id" 123)]
113+
(xs/errors->paths (xs/statement-checker bad-statement :en))))
114+
;; => {"Missing required key" ["actor"], "Not a string: 123" ["id"]}
105115
#+END_SRC
106116

107117
**** Use SubSchemata

project.clj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
[org.clojure/clojurescript "0.0-3291"]
88
[prismatic/schema "0.4.2"]
99
[cheshire "5.4.0"]
10-
[org.clojure/core.match "0.3.0-alpha4"]]
10+
[org.clojure/core.match "0.3.0-alpha4"]
11+
[com.taoensso/tower "3.1.0-beta3"]]
1112
:exclusions [[org.clojure/clojure]
1213
[org.clojure/clojurescript]]
1314
:plugins [[lein-cljsbuild "1.0.6"]
@@ -49,7 +50,7 @@
4950
:optimizations :advanced}}]
5051
:test-commands {"test" ["phantomjs" "bin/speclj" "target/js/xapi_schema_test.js"]}}
5152
:source-paths ["target/classes/clj"]
52-
:resource-paths ["target/classes/cljs"]
53+
:resource-paths ["resources" "target/classes/cljs"]
5354
:test-paths ["target/classes/clj" "target/spec/clj" "spec/clj"]
5455
:prep-tasks [["cljx" "once"] "javac" "compile"]
5556
:aliases {"build-once" ["do" "clean," "cljx" "once"]

resources/i18n/dict.clj

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{:en {:not "not"
2+
:missing-required-key "Missing required key"
3+
:disallowed-key "Key not allowed"
4+
:threw "threw"
5+
:sequential "sequential"
6+
:map "map"
7+
:integer "integer"
8+
:string "string"
9+
:number "number"
10+
:boolean "boolean"
11+
:in "in"
12+
:present "present"
13+
:predicates
14+
{:revision-not-allowed "valid if Statement object is an Activity"
15+
:platform-not-allowed "valid if Statement object is an Activity"
16+
:no-multi-ifi "there is at most one IFI"
17+
:no-ifi "present: IFI"
18+
:no-anon-group-member "present: member key on anonymous group"
19+
:distinct-ic-ids "distinct: Interaction Component IDs"
20+
:valid-component-keys "valid Interaction Component List key(s)"
21+
:score-lt-max "less than max"
22+
:score-gt-min "greater than min"
23+
:valid-ltag "a valid RFC 5646 Language Tag"
24+
:valid-iri "a valid IRI address"
25+
:valid-mailto-iri "a valid MailTo IRI address"
26+
:valid-irl "a valid IRL"
27+
:valid-openid "a valid OpenID URL"
28+
:valid-uuid "a valid Uuid"
29+
:valid-timestamp "a valid ISO 8601 timestamp"
30+
:valid-duration "a valid ISO 8601 duration"
31+
:valid-xapi-version "a valid xAPI 1.x.x version"
32+
:valid-sha-2-sum "a valid SHA-2 sum"
33+
:valid-sha-1-sum "a valid SHA-1 sum"
34+
:exactly-2-members "Exactly 2 Members"
35+
:at-least-one-agent "at least one Agent"
36+
:at-least-one-activity "at least one Activity"
37+
:at-least-one-attachment "at least one Attachment"
38+
:one-oauth-consumer "one OAuth Consumer"
39+
:at-least-one-statement "at least one statement"}}}

spec/cljx/xapi_schema/schemata/util_spec.cljx

Lines changed: 140 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
error->string
1515
errors->data
1616
named-error?
17-
validation-error?]]
17+
validation-error?
18+
leaves-and-paths
19+
errors->paths]]
1820
[xapi-schema.schemata.json :as json]
1921
[schema.core :as s
2022
:include-macros true]
@@ -29,6 +31,31 @@
2931
[clojure.walk :refer [postwalk]]
3032
[xapi-schema.support.data :as d]))
3133

34+
(describe
35+
"leaves-and-paths"
36+
(it "returns a map of leaves to paths"
37+
(should= {"h" ["a" "e" "f" "g"]
38+
"m" ["a" "e" "f" "l"]
39+
"d" ["a" "b" "c"]
40+
"j" ["i"]}
41+
(leaves-and-paths {"a"{"e" {"f" {"g" "h"
42+
"l" "m"}}
43+
"b" {"c" "d"}}
44+
"i" "j"})))
45+
(it "consumes all nested data"
46+
(should= {"h" [0 "a" "e" "f" "g"]
47+
"m" [0 "a" "e" "f" "l"]
48+
"d" [0 "a" "b" "c"]
49+
"j" [0 "i"]
50+
"n" [0 "a" "b" "k" 0]
51+
"o" [0 "a" "b" "k" 1]
52+
"q" [0 "a" "b" "k" 2 "p"]}
53+
(leaves-and-paths [{"a"{"e" {"f" {"g" "h"
54+
"l" "m"}}
55+
"b" {"c" "d"
56+
"k" ["n" "o" {"p" "q"}]}}
57+
"i" "j"}]))))
58+
3259
(describe "check-type"
3360
(with pred (check-type "Activity"))
3461
(it "returns a predicate that checks for the given objectType"
@@ -75,144 +102,166 @@
75102
"given a missing key error"
76103
(with err (s/check {(s/required-key "foo") s/Str} {}))
77104
(it "converts it to an English string"
78-
(should= "Missing"
105+
(should= "Missing required key"
79106
(error->string (get
80107
@err
81108
"foo")))))
82109
(context
83110
"given a disallowed key error"
84111
(with err (s/check {} {"foo" "bar"}))
85112
(it "converts it to an English string"
86-
(should= "Not Allowed"
113+
(should= "Key not allowed"
87114
(error->string (get
88115
@err
89116
"foo")))))
90117
(context
91118
"given a NOT validation error"
92119
(context
93120
"from a predicate"
94-
(with err (su/validation-error-explain ;; expect it to be coerced!
95-
(s/check (s/pred (fn [bar]
96-
(= "bar" bar)) "a bar") "foo")))
121+
(with err
122+
(s/check (s/pred (fn [bar]
123+
(= "bar" bar)) "a bar") "foo"))
97124
(it "converts it to an English string"
98125
(should= "Not a bar: foo"
99126
(error->string @err))))
100127
(context
101128
"from a predicate that throws"
102-
(with err (su/validation-error-explain
103-
(s/check (s/pred seq "a sequable thing") true)))
129+
(with err
130+
(s/check (s/pred seq "a sequable thing") true))
104131
(it "mentions it"
105132
(should= "Not a sequable thing: true (threw)"
106133
(error->string @err))))
107134
(context
108135
"from a seq schema"
109-
(with err (su/validation-error-explain
110-
(s/check [] {})))
136+
(with err
137+
(s/check [] {}))
111138
(it "converts"
112139
(should= "Not sequential: {}"
113140
(error->string @err))))
114141
(context
115142
"from a map schema"
116-
(with err (su/validation-error-explain
117-
(s/check {} [])))
143+
(with err
144+
(s/check {} []))
118145
(it "converts"
119146
(should= "Not map: []"
120147
(error->string @err))))
121148
(context
122149
"from an integer"
123-
(with err (su/validation-error-explain
124-
(s/check s/Int "foo")))
150+
(with err
151+
(s/check s/Int "foo"))
125152
(it "converts"
126-
(should= "Not an integer: foo"
153+
(should= "Not integer: foo"
127154
(error->string @err))))
128155
(context
129156
"from s/Str"
130-
(with err (su/validation-error-explain
131-
(s/check s/Str 1)))
157+
(with err
158+
(s/check s/Str 1))
132159
(it "converts"
133-
(should= "Not a string: 1"
160+
(should= "Not string: 1"
134161
(error->string @err))))
135162
(context
136163
"from s/Num"
137-
(with err (su/validation-error-explain
138-
(s/check s/Num "foo")))
164+
(with err
165+
(s/check s/Num "foo"))
139166
(it "converts"
140-
(should= "Not a number: foo"
167+
(should= "Not number: foo"
141168
(error->string @err))))
142169
(context
143170
"from s/Bool"
144-
(with err (su/validation-error-explain
145-
(s/check s/Bool "foo")))
171+
(with err
172+
(s/check s/Bool "foo"))
146173
(it "converts"
147-
(should= "Not a boolean: foo"
174+
(should= "Not boolean: foo"
148175
(error->string @err))))))
149176

150-
(describe
151-
"named-error?"
152-
(it "returns true if the error is a named error"
153-
(should (named-error? (s/check (s/named s/Str "foo") 1)))))
177+
(context
178+
"error processing"
154179

155-
(describe
156-
"validation-error?"
157-
(it "returns true if the error is a validation error"
158-
(should (validation-error? (s/check s/Str 1)))))
180+
(with schema (s/named
181+
{(s/required-key "foo") s/Str
182+
(s/required-key "bar") s/Num
183+
(s/required-key "baz") s/Int
184+
(s/required-key "quxx") s/Bool
185+
(s/required-key "map") {}
186+
(s/required-key "string-seq") [s/Str]
187+
(s/required-key "not-there") s/Any
188+
(s/required-key "equals") (s/eq "foo")
189+
(s/required-key "enum") (s/enum "foo" "bar" "baz")
190+
(s/required-key "one") [(s/one s/Str "at least one string")]}
191+
"bob"))
159192

160-
(describe
161-
"errors->data"
162-
(context
163-
"with nested named and validation errors"
164-
(with schema (s/named
165-
{(s/required-key "foo") s/Str
166-
(s/required-key "bar") s/Num
167-
(s/required-key "baz") s/Int
168-
(s/required-key "quxx") s/Bool
169-
(s/required-key "map") {}
170-
(s/required-key "string-seq") [s/Str]
171-
(s/required-key "not-there") s/Any
172-
(s/required-key "equals") (s/eq "foo")
173-
(s/required-key "enum") (s/enum "foo" "bar" "baz")
174-
(s/required-key "one") [(s/one s/Str "at least one string")]}
175-
"bob"))
176-
(with err (s/check @schema {"foo" 1
177-
"bar" true
178-
"baz" 1.1
179-
"quxx" "foo"
180-
"map" []
181-
"string-seq" {}
182-
"unknown-key" "hey"
183-
"equals" "bar"
184-
"enum" "quxx"
185-
"one" []
186-
}))
187-
(it "converts all error objects to data"
188-
(should-not-throw
189-
(postwalk
190-
(fn [node]
191-
(if (or (named-error? node)
192-
(validation-error? node))
193-
(throw (#+clj Exception.
194-
#+cljs js/Error. "error obj found!"))
195-
node))
196-
(errors->data @err))))
197-
(it "converts all predicate and scalar errors to strings"
198-
(should= {"unknown-key" "Not Allowed"
199-
"not-there" "Missing"
200-
"string-seq" "Not sequential: {}"
201-
"map" "Not map: []"
202-
"quxx" "Not a boolean: foo"
203-
"baz" "Not an integer: 1.1"
204-
"bar" "Not a number: true"
205-
"foo" "Not a string: 1"
206-
"equals" "Not foo: bar"
207-
"enum" "Not in #{\"foo\" \"bar\" \"baz\"}: quxx"
208-
"one" ["Not present: at least one string"]}
209-
(errors->data @err))))
210-
(context "given some xapi validation cases"
211-
(it "parses an agent objectType error"
212-
(should-not-throw
213-
(errors->data
214-
(s/check json/Statement
215-
(assoc d/long-statement
216-
"actor"
217-
{"mbox" "mailto:milt@yetanalytics.com"
218-
"objectType" "NotAnAgent"}))))))))
193+
(with err (s/check @schema {"foo" 1
194+
"bar" true
195+
"baz" 1.1
196+
"quxx" "foo"
197+
"map" []
198+
"string-seq" {}
199+
"unknown-key" "hey"
200+
"equals" "bar"
201+
"enum" "quxx"
202+
"one" []
203+
}))
204+
205+
(describe
206+
"named-error?"
207+
(it "returns true if the error is a named error"
208+
(should (named-error? (s/check (s/named s/Str "foo") 1)))))
209+
210+
(describe
211+
"validation-error?"
212+
(it "returns true if the error is a validation error"
213+
(should (validation-error? (s/check s/Str 1)))))
214+
215+
(describe
216+
"errors->data"
217+
(context
218+
"with nested named and validation errors"
219+
(it "converts all error objects to data"
220+
(should-not-throw
221+
(postwalk
222+
(fn [node]
223+
(if (or (named-error? node)
224+
(validation-error? node))
225+
(throw (#+clj Exception.
226+
#+cljs js/Error. "error obj found!"))
227+
node))
228+
(errors->data @err))))
229+
(it "converts all predicate and scalar errors to strings"
230+
(should= {"unknown-key" "Key not allowed"
231+
"not-there" "Missing required key"
232+
"string-seq" "Not sequential: {}"
233+
"map" "Not map: []"
234+
"quxx" "Not boolean: foo"
235+
"baz" "Not integer: 1.1"
236+
"bar" "Not number: true"
237+
"foo" "Not string: 1"
238+
"equals" "Not foo: bar"
239+
"enum" "Not in #{\"foo\" \"bar\" \"baz\"}: quxx"
240+
"one" ["Not present: at least one string"]}
241+
(errors->data @err))))
242+
(context "given some xapi validation cases"
243+
(it "parses an agent objectType error"
244+
(should-not-throw
245+
(errors->data
246+
(s/check json/Statement
247+
(assoc d/long-statement
248+
"actor"
249+
{"mbox" "mailto:milt@yetanalytics.com"
250+
"objectType" "NotAnAgent"})))))))
251+
(describe
252+
"errors->paths"
253+
(it "returns a map"
254+
(should (map? (errors->paths @err))))
255+
(it "maps errors to their paths"
256+
(should= (errors->paths @err)
257+
{"Key not allowed" ["unknown-key"]
258+
"Missing required key" ["not-there"]
259+
"Not sequential: {}" ["string-seq"]
260+
"Not map: []" ["map"]
261+
"Not boolean: foo" ["quxx"]
262+
"Not integer: 1.1" ["baz"]
263+
"Not number: true" ["bar"]
264+
"Not string: 1" ["foo"]
265+
"Not foo: bar" ["equals"]
266+
"Not in #{\"foo\" \"bar\" \"baz\"}: quxx" ["enum"]
267+
"Not present: at least one string" ["one" 0]})))))

src/cljx/xapi_schema/core.cljx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
(def errors->data
1919
u/errors->data)
2020

21+
(def errors->paths
22+
u/errors->paths)
23+
2124
(defn validate-statement [s]
2225
(if-let [error (statement-checker s)]
2326
#+clj (throw (Exception. (str error)))

0 commit comments

Comments
 (0)