diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 6e5b056..0000000 --- a/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -/target -/classes -/checkouts -pom.xml -pom.xml.asc -*.jar -*.class -/.lein-* -/.nrepl-port -/*-init.clj -/doc/dist -/out -/repl -/node_modules -/settings.xml -/.cpcache \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index e6444d8..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,41 +0,0 @@ -# Changelog # - -## Version 1.3.0 ## - -Date: 2018-06-02 - -- Fix message formatting. - - -## Version 1.2.0 ## - -Date: 2017-01-11 - -- Allow `number-str` and `integer-str` receive already coerced values. -- Minor code cleaning. -- Update dependencies. - -## Version 1.1.0 ## - -Date: 2017-08-16 - -- Add count validators. -- Update cuerdas to 2.0.3 - - -## Version 1.0.0 ## - -Date: 2016-06-24 - -- Add support for neested data structures. -- Add fast skip already validated and failed paths (performance improvement). -- BREAKING CHANGE: the errors are now simple strings. No additional list - wrapping is done anymore. Because the design of the library is just fail - fast and only one error is allowed. - - -## Version 0.1.0 ## - -Date: 2016-04-19 - -Initial version. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index fda3591..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,13 +0,0 @@ -# Contributed Code # - -In order to keep *struct* completely free and unencumbered by copyright, all new -contributors to the *struct* code base are asked to dedicate their contributions to -the public domain. If you want to send a patch or enhancement for possible inclusion -in the *struct* source tree, please accompany the patch with the following -statement: - - The author or authors of this code dedicate any and all copyright interest - in this code to the public domain. We make this dedication for the benefit of - the public at large and to the detriment of our heirs and successors. We - intend this dedication to be an overt act of relinquishment in perpetuity of - all present and future rights to this code under copyright law. diff --git a/README.md b/README.md deleted file mode 100644 index a7672d4..0000000 --- a/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# struct # - -A structural validation library for Clojure(Script). - -[![Clojars Project](http://clojars.org/funcool/struct/latest-version.svg)](http://clojars.org/funcool/struct) - -Documentation: http://funcool.github.io/struct/latest/ diff --git a/UNLICENSE b/UNLICENSE deleted file mode 100644 index 00d2e13..0000000 --- a/UNLICENSE +++ /dev/null @@ -1,24 +0,0 @@ -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to \ No newline at end of file diff --git a/deps.edn b/deps.edn deleted file mode 100644 index df1ac4f..0000000 --- a/deps.edn +++ /dev/null @@ -1,20 +0,0 @@ -{:deps {funcool/cuerdas {:mvn/version "2.2.0"}} - :paths ["src"] - :aliases - {:dev - {:extra-deps {org.clojure/clojurescript {:mvn/version "1.10.516"} - ;; org.clojure/clojure {:mvn/version "1.10.0"} - com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"} - com.bhauman/rebel-readline {:mvn/version "0.1.4"} - com.bhauman/figwheel-main {:mvn/version "0.2.0"} - eftest/eftest {:mvn/version "0.5.7"}} - :extra-paths ["test"]} - - :ancient {:main-opts ["-m" "deps-ancient.deps-ancient"] - :extra-deps {deps-ancient {:mvn/version "RELEASE"}}} - - :jar {:extra-deps {seancorfield/depstar {:mvn/version "RELEASE"}} - :main-opts ["-m" "hf.depstar.jar"]} - - :repl {:main-opts ["-m" "rebel-readline.main"]} - }} diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index be1f227..0000000 --- a/doc/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -all: doc - -api: - cd .. - lein doc - -doc: - mkdir -p dist/latest/ - asciidoctor -a docinfo -a stylesheet! -o dist/latest/index.html content.adoc - -github: doc api - ghp-import -m "Generate documentation" -b gh-pages dist/ - git push origin gh-pages diff --git a/doc/content-docinfo.html b/doc/content-docinfo.html deleted file mode 100644 index a16e8f9..0000000 --- a/doc/content-docinfo.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/doc/content.adoc b/doc/content.adoc deleted file mode 100644 index 2f51d44..0000000 --- a/doc/content.adoc +++ /dev/null @@ -1,387 +0,0 @@ -= struct - validation library for Clojure(Script) -:toc: left -:!numbered: -:idseparator: - -:idprefix: -:sectlinks: -:source-highlighter: pygments -:pygments-style: friendly - - -== Introduction - -A structural validation library for Clojure and ClojureScript. - -Highlights: - -- *No macros*: validators are defined using plain data. -- *Dependent validators*: the ability to access to already validated data. -- *Coercion*: the ability to coerce incoming values to other types. -- *No exceptions*: no exceptions used in the validation process. - -Based on similar ideas of -link:https://github.com/leonardoborges/bouncer[bouncer]. - - -=== Project Maturity - -Since _struct_ is a young project there may be some API breakage. - - -=== Install - -Just include that in your dependency vector on *_project.clj_*: - -[source,clojure] ----- -[funcool/struct "1.3.0"] ----- - - -== User guide - -=== Quick Start - -Let's require the main _struct_ namespace: - -[source, clojure] ----- -(require '[struct.core :as st]) ----- - -Define a small schema for the example purpose: - -[source, clojure] ----- -(def +scheme+ - {:name [st/required st/string] - :year [st/required st/number]}) ----- - -You can observe that it consists in a simple map when you declare keys and -corresponding validators for that key. A vector as value allows us to put -more than one validator for the same key. If you have only one validator for the -key, you can omit the vector and put it as single value. - -The same schema can be defined using vectors, if the order of validation -matters: - -[source, clojure] ----- -(def +scheme+ - [[:name st/required st/string] - [:year st/required st/number]]) ----- - -By default, all validators are optional so if the value is missing, no error -will reported. If you want make the value mandatory, you should use a specific -`required` validator. - -And finally, start validating your data: - -[source, clojure] ----- -(-> {:name "Blood of Elves" :year 1994} - (st/validate +scheme+)) -;; => [nil {:name "Blood of Elves" :year 1994}] - -(-> {:name "Blood of Elves" :year "1994"} - (st/validate +scheme+)) -;; => [{:year "must be a number"} {:name "Blood of Elves", :year "1994"}] - -(-> {:year "1994"} - (st/validate +scheme+)) -;; => [{:name "this field is mandatory", :year "must be a number"} {}] ----- - -If only want to know if some data is valid or not, you can use the `valid?` predicate -for that purpose: - -[source, clojure] ----- -(st/valid? {:year "1994"} +scheme+) -;; => false ----- - -The additional entries in the map are not stripped by default, but this behavior -can be changed passing an additional flag as the third argument: - -[source, clojure] ----- -(-> {:name "Blood of Elves" :year 1994 :foo "bar"} - (st/validate +scheme+)) -;; => [nil {:name "Blood of Elves" :year 1994 :foo "bar"}] - -(-> {:name "Blood of Elves" :year 1994 :foo "bar"} - (st/validate +scheme+ {:strip true})) -;; => [nil {:name "Blood of Elves" :year 1994}] - ----- - -With similar syntax you can validate neested data structures, specifying in the -key part the proper path to the neested data structure: - -[source, clojure] ----- -(def +scheme+ - {[:a :b] st/integer - [:c :d] st/string}) - -(-> {:a {:b "foo"} {:c {:d "bar"}}} - (st/validate +scheme+)) -;; => [{:a {:b "must be a number"}} {:c {:d "bar"}}] ----- - - -=== Parametrized validators - -In addition to simple validators, one may use additional contraints -(e.g. `in-range`). This is how they can be passed to the validator: - -[source, clojure] ----- -(def schema {:num [[st/in-range 10 20]]}) - -(st/validate {:num 21} schema) -;; => [{:num "not in range"} {}] - -(st/validate {:num 19} schema) -;; => [nil {:num 19}] ----- - -Note the double vector; the outer denotes a list of validatiors and the inner -denotes a validator with patameters. - - -=== Custom messages - -The builtin validators comes with default messages in human readable format, but -sometimes you may want to change them (e.g. for i18n purposes). This is how you -can do it: - -[source, clojure] ----- -(def schema - {:num [[st/in-range 10 20 :message "errors.not-in-range"]]}) - -(st/validate {:num 21} schema) -;; => [{:num "errors.not-in-range"} {}] ----- - -A message can contains format wildcards `%s`, these wildcards will be replaced by `args` of validator, e.g.: - -[source, clojure] ----- -(def schema - {:age [[st/in-range 18 26 :message "The age must be between %s and %s"]]}) - -(st/validate {:age 30} schema) -;; => [{:age "The age must be between 18 and 26"} {}] - ----- - - -=== Data coercions - -In addition to simple validations, this library includes the ability -to coerce values, and a collection of validators that matches over strings. Let's -see some code: - -.Example attaching custom coercions -[source, clojure] ----- -(def schema - {:year [[st/integer :coerce str]]}) - -(st/validate {:year 1994} schema)) -;; => [nil {:year "1994"}] ----- - -Looking at the data returned from the validation -process, one can see that the value is properly coerced with the specified coercion function. - -This library comes with a collection of validators that already -have attached coercion functions. These serve to validate parameters -that arrive as strings but need to be converted to the appropriate type: - -[source, clojure] ----- -(def schema {:year [st/required st/integer-str] - :id [st/required st/uuid-str]}) - -(st/validate {:year "1994" - :id "543e7472-6624-4cb5-b65e-f3c341843d0f"} - schema) -;; => [nil {:year 1994, :id #uuid "543e7472-6624-4cb5-b65e-f3c341843d0f"}] ----- - -To facilitate this operation, the `validate!` function receives the -data and schema, then returns the resulting data. If data not matches the schema -an exception will be raised using `ex-info` clojure facility: - -[source, clojure] ----- -(st/validate! {:year "1994" :id "543e7472-6624-4cb5-b65e-f3c341843d0f"} schema) -;; => {:year 1994, :id #uuid "543e7472-6624-4cb5-b65e-f3c341843d0f"} ----- - -=== Builtin Validators - -This is the table with available builtin validators: - -.Builtin Validators -[options="header", cols="2,1,4"] -|=========================================================================== -| Identifier | Coercion | Description -| `struct.core/keyword` | no | Validator for clojure's keyword -| `struct.core/uuid` | no | Validator for UUID's -| `struct.core/uuid-str` | yes | Validator for uuid strings with coercion to UUID -| `struct.core/email` | no | Validator for email string. -| `struct.core/required` | no | Marks field as required. -| `struct.core/number` | no | Validator for Number. -| `struct.core/number-str` | yes | Validator for number string. -| `struct.core/integer` | no | Validator for integer. -| `struct.core/integer-str` | yes | Validator for integer string. -| `struct.core/boolean` | no | Validator for boolean. -| `struct.core/boolean-str` | yes | Validator for boolean string. -| `struct.core/string` | no | Validator for string. -| `struct.core/string-str` | yes | Validator for string like. -| `struct.core/in-range` | no | Validator for a number range. -| `struct.core/member` | no | Validator for check if a value is member of coll. -| `struct.core/positive` | no | Validator for positive number. -| `struct.core/negative` | no | Validator for negative number. -| `struct.core/function` | no | Validator for IFn interface. -| `struct.core/vector` | no | Validator for clojure vector. -| `struct.core/map` | no | Validator for clojure map. -| `struct.core/set` | no | Validator for clojure set. -| `struct.core/coll` | no | Validator for clojure coll. -| `struct.core/every` | no | Validator to check if pred match for every item in coll. -| `struct.core/identical-to` | no | Validator to check that value is identical to other field. -| `struct.core/min-count` | no | Validator to check that value is has at least a minimum number of characters. -| `struct.core/max-count` | no | Validator to check that value is not larger than a maximum number of characters. -|=========================================================================== - -Additional notes: - -* `number-str` coerces to `java.lang.Double` or `float` (cljs) -* `boolean-str` coerces to `true` (`"t"`, `"true"`, `"1"`) or `false` (`"f"`, `"false"`, `"0"`). -* `string-str` coerces anything to string using `str` function. - - -=== Define your own validator - -As mentioned previously, the validators in _struct_ library are defined using plain -hash-maps. For example, this is how the builtin `integer` validator is defined: - -[source, clojure] ----- -(def integer - {:message "must be a integer" - :optional true - :validate integer?})) ----- - -If the validator needs access to previously validated data, the `:state` key -should be present with the value `true`. Let see the `identical-to` validator as example: - -[source,clojure] ----- -(def identical-to - {:message "does not match" - :optional true - :state true - :validate (fn [state v ref] - (let [prev (get state ref)] - (= prev v)))}) ----- - -Validators that access the state receive an additional argument with the state for validator -function. - -=== Translating validation messages - -`struct.core/validate` accepts a third options argument where a function can be passed in with the `:translate` key like the following: - -[source,clojure] ----- -(st/validate {:year "1994" - :id "543e7472-6624-4cb5-b65e-f3c341843d0f"} - schema - {:translate (fn [message] (clojure.string/uppercase message))) ----- - -The translation function accepts the `:message` of the schma upon validation failure. -This allows easy integration with an i18n library such as tempura if you privide a keyword for the schema's `:message` that in turn maps to the localised message in the dictionary. - -== Developers Guide - -=== Contributing - -Unlike Clojure and other Clojure contrib libs, there aren't many restrictions for -contributions. Just open an issue or pull request. - - -=== Get the Code - -_struct_ is open source and can be found on -link:https://github.com/funcool/struct[github]. - -You can clone the public repository with this command: - -[source,text] ----- -git clone https://github.com/funcool/struct ----- - - -=== Run tests - -To run the tests execute the following: - -For the JVM platform: - -[source, text] ----- -lein test ----- - -And for JS platform: - -[source, text] ----- -./scripts/build -node out/tests.js ----- - -You will need to have nodejs installed on your system. - -=== License - -_struct_ is under public domain: - ----- -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to ----- diff --git a/latest/api/css/default.css b/latest/api/css/default.css new file mode 100644 index 0000000..257ab2c --- /dev/null +++ b/latest/api/css/default.css @@ -0,0 +1,442 @@ +@import url(http://fonts.googleapis.com/css?family=Droid+Sans+Mono:200,300,400); +@import url(http://fonts.googleapis.com/css?family=Lato:light,regular); +@import url(http://fonts.googleapis.com/css?family=Ubuntu:300,400,500); + +body { + font-family: "Lato", Helvetica, Arial, sans-serif; + font-weight: 300; + color:#585858; + font-size: 100%; + margin: 0px; +} + +pre, code { + font-family: "Droid Sans Mono","DejaVu Sans Mono","Monospace",monospace; + font-weight: 300; +} + +section.container { + display: flex; + font-size: 100%; +} + +h2 { + font-weight: normal; + font-size: 3em; + padding: 10px 0 2px 0; + margin: 0; +} + +header { + color: #333; + padding: 10px; +} + +header small { + font-style: italic; +} + +header h1 { + margin: 0; + padding: 0; + /* font-size: 12pt; */ + font-weight: lighter; + /* text-shadow: -1px -1px 0px #333; */ +} + +header h1 a { + color: #333; + /* font-size: 32px; */ + font-weight: 400; + text-decoration: none; +} + +#content { + overflow: auto; + background: #fff; + color: #333; + padding: 0 18px; + font-size: 1.3em; +} + +#namespaces { + border-right: solid 1px #cccccc; + min-width: 200px; + padding-right: 15px; +} + +#vars { + border-right: solid 1px #cccccc; + width: 200px; +} + +.sidebar { + overflow: auto; +} + +.sidebar a { + color: #333; + display: block; + text-decoration: none; +} + +.sidebar h3 { + margin: 0; + padding: 10px 10px 0 10px; + font-size: 19px; + font-weight: normal; +} + +.sidebar ul { + padding: 0.5em 0em; + margin: 0; +} + +.sidebar li { + display: block; + vertical-align: middle; +} + +.sidebar li a, .sidebar li .no-link { + border-left: 3px solid transparent; + padding: 0 15px; + white-space: nowrap; +} + +.sidebar li .no-link { + display: block; + color: #777; + font-style: italic; +} + +.sidebar li .inner { + display: inline-block; + padding-top: 7px; + height: 24px; +} + +.sidebar li a, .sidebar li .tree { + height: 31px; + /* height: 25px; */ +} + +.depth-1 .inner { padding-left: 2px; } +.depth-2 .inner { padding-left: 6px; } +.depth-3 .inner { padding-left: 20px; } +.depth-4 .inner { padding-left: 34px; } +.depth-5 .inner { padding-left: 48px; } +.depth-6 .inner { padding-left: 62px; } + +.sidebar li .tree { + display: block; + float: left; + position: relative; + top: -10px; + margin: 0 4px 0 0; + padding: 0; +} + +.sidebar li.depth-1 .tree { + display: none; +} + +.sidebar li .tree .top, .sidebar li .tree .bottom { + display: block; + margin: 0; + padding: 0; + width: 7px; +} + +.sidebar li .tree .top { + border-left: 1px solid #aaa; + border-bottom: 1px solid #aaa; + height: 19px; +} + +.sidebar li .tree .bottom { + height: 22px; +} + +.sidebar li.branch .tree .bottom { + border-left: 1px solid #aaa; +} + +#namespaces li.current a { + border-left: 3px solid #a33; + border-left: 3px solid #7a2518; + color: #a33; + color: #7a2518; + +} + +#vars li.current a { + border-left: 3px solid #33a; + color: #33a; +} + +.namespace-docs h2 { + color: #7a2518; +} + +.namespace-docs h3 a { + color: #ba3925; + font-family: "Droid Sans Mono","DejaVu Sans Mono","Monospace",monospace; + font-weight: 400; + text-decoration: none; +} + +.namespace-docs .usage code { + display: block; + color: #777; + margin: 2px 0; + font-size: 0.6em; +} + +/* .usage code:first-child { */ +/* padding-top: 10px; */ +/* } */ + + + +.namespace-index h3 a { + text-decoration: none; + color: #ba3925; + font-family: "Droid Sans Mono","DejaVu Sans Mono","Monospace",monospace; + font-weight: 300; +} + +.public h3 { + margin: 0; +} + +.public { + margin: 0; + border-top: 1px solid #efefef; + padding-top: 14px; + padding-bottom: 6px; +} + +.public:last-child { + margin-bottom: 20%; +} + +.members .public:last-child { + margin-bottom: 0; +} + +.members { + margin: 15px 0; +} + +.members h4 { + color: #555; + font-weight: normal; + font-variant: small-caps; + margin: 0 0 5px 0; +} + +.members .inner { + padding-top: 5px; + padding-left: 12px; + margin-top: 2px; + margin-left: 7px; + border-left: 1px solid #bbb; +} + +#content .members .inner h3 { + /* font-size: 12pt; */ +} + +.members .public { + border-top: none; + margin-top: 0; + padding-top: 6px; + padding-bottom: 0; +} + +.members .public:first-child { + padding-top: 0; +} + +h4.type, +h4.dynamic, +h4.added, +h4.deprecated { + margin: 3px 10px 10spx 0; + font-weight: bold; + font-variant: small-caps; +} + +.public h4.type, +.public h4.dynamic, +.public h4.added, +.public h4.deprecated { + font-weight: bold; + /* margin: 3px 0 0 10px; */ + font-size: 0.7em; +} + +.members h4.type, +.members h4.added, +.members h4.deprecated { + margin-top: 1px; +} + +h4.type { + color: #717171; +} + +h4.dynamic { + color: #9933aa; +} + +h4.added { + color: #508820; +} + +h4.deprecated { + color: #880000; +} + +.namespace { + margin-bottom: 40px; +} + +.namespace:last-child { + margin-bottom: 10%; +} + +.index { + padding: 0; + margin: 15px 0; +} + +.index * { + display: inline; +} + +.index p { + padding-right: 3px; +} + +.index li { + padding-right: 5px; +} + +.index li a { + color: #333; + font-family: "Droid Sans Mono","DejaVu Sans Mono","Monospace",monospace; + font-size: 0.8em; + text-decoration: none; + font-weight: 300; +} + +.index ul { + padding-left: 0; +} + +p { + margin: 15px 0; +} + +.public p:first-child, .public pre.plaintext { + margin-top: 12px; +} + +.doc { + margin: 0 0 26px 0; + clear: both; +} + +.public .doc { + margin: 0; +} + +.namespace-index .doc { + margin-bottom: 20px; +} + +.namespace-index .namespace .doc { + margin-bottom: 10px; +} + +.markdown { + /* line-height: 18px; */ + /* font-size: 16px; */ +} + +.doc, .public, .namespace .index { + max-width: 780px; + overflow-x: visible; +} + +.markdown code, .src-link a { + border-radius: 2px; + font-size: 0.8em; + color: #444; +} + +.markdown pre { + background: #f4f4f4; + border: 1px solid #e0e0e0; + /* border-radius: 2px; */ + padding: 5px 5px; + border-top: 1px solid #e0e0e0; + border-bottom: 1px solid #e0e0e0; +} + +.markdown pre code { + background: transparent; + border: none; +} + +.doc ul, .doc ol { + padding-left: 30px; +} + +.doc table { + border-collapse: collapse; + margin: 0 10px; +} + +.doc table td, .doc table th { + border: 1px solid #dddddd; + padding: 4px 6px; +} + +.doc table th { + background: #f2f2f2; +} + +.doc dl { + margin: 0 10px 20px 10px; +} + +.doc dl dt { + font-weight: bold; + margin: 0; + padding: 3px 0; + border-bottom: 1px solid #ddd; +} + +.doc dl dd { + padding: 5px 0; + margin: 0 0 5px 10px; +} + +.doc abbr { + border-bottom: 1px dotted #333; + font-variant: none + cursor: help; +} + +.src-link { + margin-bottom: 15px; +} + +.src-link a { + /* font-size: 70%; */ + padding: 1px 4px; + text-decoration: none; + color: #5555bb; +} \ No newline at end of file diff --git a/latest/api/index.html b/latest/api/index.html new file mode 100644 index 0000000..96fae4f --- /dev/null +++ b/latest/api/index.html @@ -0,0 +1,2 @@ + +Struct 1.3.0 API documentation

Struct Api Documentation

Version: 1.3.0

Struct

A structural validation library for Clojure(Script)

\ No newline at end of file diff --git a/latest/api/promesa.core.html b/latest/api/promesa.core.html new file mode 100644 index 0000000..7777a55 --- /dev/null +++ b/latest/api/promesa.core.html @@ -0,0 +1,2 @@ + +promesa.core documentation

Promesa Api Documentation

Version: 1.1.1

promesa.core

alet

macro

(alet bindings & body)

all

(all promises)

Given an array of promises, return a promise that is fulfilled when all the items in the array are fulfilled.

any

(any promises)

Given an array of promises, return a promise that is fulfilled when first one item in the array is fulfilled.

await

(await & args)

bind

(bind p callback)

A chain helper for promises.

branch

(branch p success failure)

cancel!

(cancel! p)

Cancel the promise.

cancelled?

(cancelled? v)

Return true if v is a cancelled promise.

catch

(catch p f)(catch p type f)

Catch all promise chain helper.

chain

(chain p & funcs)

Like then but accepts multiple parameters.

delay

(delay t)(delay t v)

Given a timeout in miliseconds and optional value, returns a promise that will fulfilled with provided value (or nil) after the time is reached.

done?

(done? p)

Returns true if promise p is already done.

err

A short alias for error function.

error

(error f p)(error f type p)

Same as catch but with parameters inverted.

extract

(extract p)

Returns the current promise value.

finally

(finally p callback)

Attach handler to promise that will be executed independently if promise is resolved or rejected.

ICancellable

protocol

A cancellation abstraction.

members

-cancel

(-cancel _)

-cancelled?

(-cancelled? _)

IPromise

protocol

A basic future abstraction.

members

-bind

(-bind _ callback)

Chain a promise.

-catch

(-catch _ callback)

Catch a error in a promise.

-map

(-map _ callback)

Chain a promise.

IPromiseFactory

protocol

A promise constructor abstraction.

members

-promise

(-promise _)

Create a promise instance.

IScheduler

protocol

members

-schedule

(-schedule _ ms func)

IState

protocol

Additional state/introspection abstraction.

members

-extract

(-extract _)

Extract the current value.

-pending?

(-pending? _)

Retutns true if a promise is pending.

-rejected?

(-rejected? _)

Returns true if a promise is rejected.

-resolved?

(-resolved? _)

Returns true if a promise is resolved.

map

(map f p)

Apply a function to the promise value and return a new promise with the result.

mapcat

(mapcat f p)

Same as map but removes one level of promise neesting. Useful when the map function returns a promise instead of value.

In JS environment this function is analogous to map because the promise abstraction overloads the map operator.

pending?

(pending? p)

Returns true if promise p is stil pending.

promise

(promise v)

The promise constructor.

promise->str

(promise->str p)

promise?

(promise? v)

Return true if v is a promise instance.

promisify

(promisify callable)

Given a nodejs like function that accepts a callback as the last argument and return an other function that returns a promise.

rejected

(rejected v)

Return a rejected promise with provided reason.

rejected?

(rejected? p)

Returns true if promise p is already rejected.

resolved

(resolved v)

Return a resolved promise with provided value.

resolved?

(resolved? p)

Returns true if promise p is already fulfilled.

schedule

(schedule ms func)

Schedule a callable to be executed after the ms delay is reached.

In JVM it uses a scheduled executor service and in JS it uses the setTimeout function.

then

(then p f)

Same as map but with parameters inverted for convenience and for familiarity with javascript’s promises .then operator.

\ No newline at end of file diff --git a/latest/api/promesa.monad.html b/latest/api/promesa.monad.html new file mode 100644 index 0000000..d8fec47 --- /dev/null +++ b/latest/api/promesa.monad.html @@ -0,0 +1,2 @@ + +promesa.monad documentation

Promesa Api Documentation

Version: 1.1.1

promesa.monad

An optional cats integration.

\ No newline at end of file diff --git a/latest/api/promesa.protocols.html b/latest/api/promesa.protocols.html new file mode 100644 index 0000000..c84b432 --- /dev/null +++ b/latest/api/promesa.protocols.html @@ -0,0 +1,2 @@ + +promesa.protocols documentation

Promesa Api Documentation

Version: 0.8.1

promesa.protocols

ICancellablePromise

protocol

A cancellation abstraction for promises.

members

-cancel

(-cancel _)

Cancel promise.

-cancelled?

(-cancelled? _)

Check if promise is cancelled.

IPromise

protocol

A basic future abstraction.

members

-bind

(-bind _ callback)

Chain a promise.

-catch

(-catch _ callback)

Catch a error in a promise.

-map

(-map _ callback)

Chain a promise.

IPromiseFactory

protocol

A promise constructor abstraction.

members

-promise

(-promise _)

Create a promise instance.

IState

protocol

Additional state/introspection abstraction.

members

-extract

(-extract _)

Extract the current value.

-pending?

(-pending? _)

Retutns true if a promise is pending.

-rejected?

(-rejected? _)

Returns true if a promise is rejected.

-resolved?

(-resolved? _)

Returns true if a promise is resolved.

\ No newline at end of file diff --git a/latest/api/struct.core.html b/latest/api/struct.core.html new file mode 100644 index 0000000..47c75f2 --- /dev/null +++ b/latest/api/struct.core.html @@ -0,0 +1,4 @@ + +struct.core documentation

Struct Api Documentation

Version: 1.3.0

struct.core

valid-single?

(valid-single? data schema)

Analogous function to valid? that just validates single value.

valid?

(valid? data schema)

Return true if the data matches the schema, otherwise return false.

validate

(validate data schema)(validate data schema {:keys [strip], :or {strip false}, :as opts})

Validate data with specified schema.

+

This function by default strips all data that are not defined in schema, but this behavior can be changed by passing {:strip false} as third argument.

validate!

(validate! data schema)(validate! data schema {:keys [message], :or {message "Schema validation error"}, :as opts})

Analogous function to the validate that instead of return the errors, just raise a ex-info exception with errors in case them are or just return the validated data.

+

This function accepts the same parameters as validate with an additional :message that serves for customize the exception message.

validate-single

(validate-single data schema)(validate-single data schema opts)

A helper that used just for validate one value.

\ No newline at end of file diff --git a/latest/index.html b/latest/index.html new file mode 100644 index 0000000..f6bcaec --- /dev/null +++ b/latest/index.html @@ -0,0 +1,668 @@ + + + + + + + +struct - validation library for Clojure(Script) + + + + + + +
+
+

Introduction

+
+
+

A structural validation library for Clojure and ClojureScript.

+
+
+

Highlights:

+
+
+
    +
  • +

    No macros: validators are defined using plain data.

    +
  • +
  • +

    Dependent validators: the ability to access to already validated data.

    +
  • +
  • +

    Coercion: the ability to coerce incoming values to other types.

    +
  • +
  • +

    No exceptions: no exceptions used in the validation process.

    +
  • +
+
+
+

Based on similar ideas of +bouncer.

+
+
+

Project Maturity

+
+

Since struct is a young project there may be some API breakage.

+
+
+
+

Install

+
+

Just include that in your dependency vector on project.clj:

+
+
+
+
[funcool/struct "1.3.0"]
+
+
+
+
+
+
+

User guide

+
+
+

Quick Start

+
+

Let’s require the main struct namespace:

+
+
+
+
(require '[struct.core :as st])
+
+
+
+

Define a small schema for the example purpose:

+
+
+
+
(def +scheme+
+  {:name [st/required st/string]
+   :year [st/required st/number]})
+
+
+
+

You can observe that it consists in a simple map when you declare keys and +corresponding validators for that key. A vector as value allows us to put +more than one validator for the same key. If you have only one validator for the +key, you can omit the vector and put it as single value.

+
+
+

The same schema can be defined using vectors, if the order of validation +matters:

+
+
+
+
(def +scheme+
+  [[:name st/required st/string]
+   [:year st/required st/number]])
+
+
+
+

By default, all validators are optional so if the value is missing, no error +will reported. If you want make the value mandatory, you should use a specific +required validator.

+
+
+

And finally, start validating your data:

+
+
+
+
(-> {:name "Blood of Elves" :year 1994}
+    (st/validate +scheme+))
+;; => [nil {:name "Blood of Elves" :year 1994}]
+
+(-> {:name "Blood of Elves" :year "1994"}
+    (st/validate +scheme+))
+;; => [{:year "must be a number"} {:name "Blood of Elves", :year "1994"}]
+
+(-> {:year "1994"}
+    (st/validate +scheme+))
+;; => [{:name "this field is mandatory", :year "must be a number"} {}]
+
+
+
+

If only want to know if some data is valid or not, you can use the valid? predicate +for that purpose:

+
+
+
+
(st/valid? {:year "1994"} +scheme+)
+;; => false
+
+
+
+

The additional entries in the map are not stripped by default, but this behavior +can be changed passing an additional flag as the third argument:

+
+
+
+
(-> {:name "Blood of Elves" :year 1994 :foo "bar"}
+    (st/validate +scheme+))
+;; => [nil {:name "Blood of Elves" :year 1994 :foo "bar"}]
+
+(-> {:name "Blood of Elves" :year 1994 :foo "bar"}
+    (st/validate +scheme+ {:strip true}))
+;; => [nil {:name "Blood of Elves" :year 1994}]
+
+
+
+

With similar syntax you can validate neested data structures, specifying in the +key part the proper path to the neested data structure:

+
+
+
+
(def +scheme+
+  {[:a :b] st/integer
+   [:c :d] st/string})
+
+(-> {:a {:b "foo"} {:c {:d "bar"}}}
+    (st/validate +scheme+))
+;; => [{:a {:b "must be a number"}} {:c {:d "bar"}}]
+
+
+
+
+

Parametrized validators

+
+

In addition to simple validators, one may use additional contraints +(e.g. in-range). This is how they can be passed to the validator:

+
+
+
+
(def schema {:num [[st/in-range 10 20]]})
+
+(st/validate {:num 21} schema)
+;; => [{:num "not in range"} {}]
+
+(st/validate {:num 19} schema)
+;; => [nil {:num 19}]
+
+
+
+

Note the double vector; the outer denotes a list of validatiors and the inner +denotes a validator with patameters.

+
+
+
+

Custom messages

+
+

The builtin validators comes with default messages in human readable format, but +sometimes you may want to change them (e.g. for i18n purposes). This is how you +can do it:

+
+
+
+
(def schema
+  {:num [[st/in-range 10 20 :message "errors.not-in-range"]]})
+
+(st/validate {:num 21} schema)
+;; => [{:num "errors.not-in-range"} {}]
+
+
+
+

A message can contains format wildcards %s, these wildcards will be replaced by args of validator, e.g.:

+
+
+
+
(def schema
+  {:age [[st/in-range 18 26 :message "The age must be between %s and %s"]]})
+
+(st/validate {:age 30} schema)
+;; => [{:age "The age must be between 18 and 26"} {}]
+
+
+
+
+

Data coercions

+
+

In addition to simple validations, this library includes the ability +to coerce values, and a collection of validators that matches over strings. Let’s +see some code:

+
+
+
Example attaching custom coercions
+
+
(def schema
+  {:year [[st/integer :coerce str]]})
+
+(st/validate {:year 1994} schema))
+;; => [nil {:year "1994"}]
+
+
+
+

Looking at the data returned from the validation +process, one can see that the value is properly coerced with the specified coercion function.

+
+
+

This library comes with a collection of validators that already +have attached coercion functions. These serve to validate parameters +that arrive as strings but need to be converted to the appropriate type:

+
+
+
+
(def schema {:year [st/required st/integer-str]
+             :id [st/required st/uuid-str]})
+
+(st/validate {:year "1994"
+              :id "543e7472-6624-4cb5-b65e-f3c341843d0f"}
+             schema)
+;; => [nil {:year 1994, :id #uuid "543e7472-6624-4cb5-b65e-f3c341843d0f"}]
+
+
+
+

To facilitate this operation, the validate! function receives the +data and schema, then returns the resulting data. If data not matches the schema +an exception will be raised using ex-info clojure facility:

+
+
+
+
(st/validate! {:year "1994" :id "543e7472-6624-4cb5-b65e-f3c341843d0f"} schema)
+;; => {:year 1994, :id #uuid "543e7472-6624-4cb5-b65e-f3c341843d0f"}
+
+
+
+
+

Builtin Validators

+
+

This is the table with available builtin validators:

+
+ + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Table 1. Builtin Validators
IdentifierCoercionDescription

struct.core/keyword

no

Validator for clojure’s keyword

struct.core/uuid

no

Validator for UUID’s

struct.core/uuid-str

yes

Validator for uuid strings with coercion to UUID

struct.core/email

no

Validator for email string.

struct.core/required

no

Marks field as required.

struct.core/number

no

Validator for Number.

struct.core/number-str

yes

Validator for number string.

struct.core/integer

no

Validator for integer.

struct.core/integer-str

yes

Validator for integer string.

struct.core/boolean

no

Validator for boolean.

struct.core/boolean-str

yes

Validator for boolean string.

struct.core/string

no

Validator for string.

struct.core/string-str

yes

Validator for string like.

struct.core/in-range

no

Validator for a number range.

struct.core/member

no

Validator for check if a value is member of coll.

struct.core/positive

no

Validator for positive number.

struct.core/negative

no

Validator for negative number.

struct.core/function

no

Validator for IFn interface.

struct.core/vector

no

Validator for clojure vector.

struct.core/map

no

Validator for clojure map.

struct.core/set

no

Validator for clojure set.

struct.core/coll

no

Validator for clojure coll.

struct.core/every

no

Validator to check if pred match for every item in coll.

struct.core/identical-to

no

Validator to check that value is identical to other field.

struct.core/min-count

no

Validator to check that value is has at least a minimum number of characters.

struct.core/max-count

no

Validator to check that value is not larger than a maximum number of characters.

+
+

Additional notes:

+
+
+
    +
  • +

    number-str coerces to java.lang.Double or float (cljs)

    +
  • +
  • +

    boolean-str coerces to true ("t", "true", "1") or false ("f", "false", "0").

    +
  • +
  • +

    string-str coerces anything to string using str function.

    +
  • +
+
+
+
+

Define your own validator

+
+

As mentioned previously, the validators in struct library are defined using plain +hash-maps. For example, this is how the builtin integer validator is defined:

+
+
+
+
(def integer
+  {:message "must be a integer"
+   :optional true
+   :validate integer?}))
+
+
+
+

If the validator needs access to previously validated data, the :state key +should be present with the value true. Let see the identical-to validator as example:

+
+
+
+
(def identical-to
+  {:message "does not match"
+   :optional true
+   :state true
+   :validate (fn [state v ref]
+               (let [prev (get state ref)]
+                 (= prev v)))})
+
+
+
+

Validators that access the state receive an additional argument with the state for validator +function.

+
+
+
+
+
+

Developers Guide

+
+
+

Contributing

+
+

Unlike Clojure and other Clojure contrib libs, there aren’t many restrictions for +contributions. Just open an issue or pull request.

+
+
+
+

Get the Code

+
+

struct is open source and can be found on +github.

+
+
+

You can clone the public repository with this command:

+
+
+
+
git clone https://github.com/funcool/struct
+
+
+
+
+

Run tests

+
+

To run the tests execute the following:

+
+
+

For the JVM platform:

+
+
+
+
lein test
+
+
+
+

And for JS platform:

+
+
+
+
./scripts/build
+node out/tests.js
+
+
+
+

You will need to have nodejs installed on your system.

+
+
+
+

License

+
+

struct is under public domain:

+
+
+
+
This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/struct/core.cljc b/src/struct/core.cljc deleted file mode 100644 index 28b2df4..0000000 --- a/src/struct/core.cljc +++ /dev/null @@ -1,329 +0,0 @@ -(ns struct.core - (:refer-clojure :exclude [keyword uuid vector boolean long map set]) - (:require [cuerdas.core :as str])) - -;; --- Impl details - -(def ^:private map' #?(:cljs cljs.core/map - :clj clojure.core/map)) - -(defn- apply-validation - [step data value] - (if-let [validate (:validate step nil)] - (let [args (:args step [])] - (if (:state step) - (apply validate data value args) - (apply validate value args))) - true)) - -(defn- dissoc-in - [m [k & ks :as keys]] - (if ks - (if-let [nextmap (get m k)] - (let [newmap (dissoc-in nextmap ks)] - (if (seq newmap) - (assoc m k newmap) - (dissoc m k))) - m) - (dissoc m k))) - -(defn- prepare-message - [opts step] - (if (::nomsg opts) - ::nomsg - (let [msg (:message step "errors.invalid") - tr (:translate opts identity)] - (apply str/format (tr msg) (vec (:args step)))))) - -(def ^:const ^:private opts-params - #{:coerce :message :optional}) - -(def ^:private notopts? - (complement opts-params)) - -(defn- build-step - [key item] - (letfn [(coerce-key [key] (if (vector? key) key [key]))] - (if (vector? item) - (let [validator (first item) - result (split-with notopts? (rest item)) - args (first result) - opts (apply hash-map (second result))] - (merge (assoc validator :args args :path (coerce-key key)) - (select-keys opts [:coerce :message :optional]))) - (assoc item :args [] :path (coerce-key key))))) - -(defn- normalize-step-map-entry - [acc key value] - (if (vector? value) - (reduce #(conj! %1 (build-step key %2)) acc value) - (conj! acc (build-step key value)))) - -(defn- normalize-step-entry - [acc [key & values]] - (reduce #(conj! %1 (build-step key %2)) acc values)) - -(defn- build-steps - [schema] - (cond - (vector? schema) - (persistent! - (reduce normalize-step-entry (transient []) schema)) - - (map? schema) - (persistent! - (reduce-kv normalize-step-map-entry (transient []) schema)) - - :else - (throw (ex-info "Invalid schema." {})))) - -(defn- strip-values - [data steps] - (reduce (fn [acc path] - (let [value (get-in data path ::notexists)] - (if (not= value ::notexists) - (assoc-in acc path value) - acc))) - {} - (into #{} (map' :path steps)))) - -(defn- validate-internal - [data steps opts] - (loop [skip #{} - errors nil - data data - steps steps] - (if-let [step (first steps)] - (let [path (:path step) - value (get-in data path)] - (cond - (contains? skip path) - (recur skip errors data (rest steps)) - - (and (nil? value) (:optional step)) - (recur skip errors data (rest steps)) - - (apply-validation step data value) - (let [value ((:coerce step identity) value)] - (recur skip errors (assoc-in data path value) (rest steps))) - - :else - (let [message (prepare-message opts step)] - (recur (conj skip path) - (assoc-in errors path message) - (dissoc-in data path) - (rest steps))))) - [errors data]))) - -;; --- Public Api - -(defn validate - "Validate data with specified schema. - - This function by default strips all data that are not defined in - schema, but this behavior can be changed by passing `{:strip false}` - as third argument." - ([data schema] - (validate data schema nil)) - ([data schema {:keys [strip] - :or {strip false} - :as opts}] - (let [steps (build-steps schema) - data (if strip (strip-values data steps) data)] - (validate-internal data steps opts)))) - -(defn validate-single - "A helper that used just for validate one value." - ([data schema] (validate-single data schema nil)) - ([data schema opts] - (let [data {:field data} - steps (build-steps {:field schema})] - (mapv :field (validate-internal data steps opts))))) - -(defn validate! - "Analogous function to the `validate` that instead of return - the errors, just raise a ex-info exception with errors in case - them are or just return the validated data. - - This function accepts the same parameters as `validate` with - an additional `:message` that serves for customize the exception - message." - ([data schema] - (validate! data schema nil)) - ([data schema {:keys [message] :or {message "Schema validation error"} :as opts}] - (let [[errors data] (validate data schema opts)] - (if (seq errors) - (throw (ex-info message errors)) - data)))) - -(defn valid? - "Return true if the data matches the schema, otherwise - return false." - [data schema] - (nil? (first (validate data schema {::nomsg true})))) - -(defn valid-single? - "Analogous function to `valid?` that just validates single value." - [data schema] - (nil? (first (validate-single data schema {::nomsg true})))) - -;; --- Validators - -(def keyword - {:message "must be a keyword" - :optional true - :validate keyword? - :coerce identity}) - -(def uuid - {:message "must be an uuid" - :optional true - :validate #?(:clj #(instance? java.util.UUID %) - :cljs #(instance? cljs.core.UUID %))}) - -(def ^:const ^:private +uuid-re+ - #"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$") - -(def uuid-str - {:message "must be an uuid" - :optional true - :validate #(and (string? %) - (re-seq +uuid-re+ %)) - :coerce #?(:clj #(java.util.UUID/fromString %) - :cljs #(uuid %))}) - -(def email - (let [rx #"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"] - {:message "must be a valid email" - :optional true - :validate #(and (string? %) - (re-seq rx %))})) - -(def required - {:message "this field is mandatory" - :optional false - :validate #(if (string? %) - (not (empty? %)) - (not (nil? %)))}) - -(def number - {:message "must be a number" - :optional true - :validate number?}) - -(def number-str - {:message "must be a number" - :optional true - :validate #(or (number? %) (and (string? %) (str/numeric? %))) - :coerce #(if (number? %) % (str/parse-number %))}) - -(def integer - {:message "must be a integer" - :optional true - :validate #?(:cljs #(js/Number.isInteger %) - :clj #(integer? %))}) - -(def integer-str - {:message "must be a long" - :optional true - :validate #(or (number? %) (and (string? %) (str/numeric? %))) - :coerce #(if (number? %) (int %) (str/parse-int %))}) - -(def boolean - {:message "must be a boolean" - :optional true - :validate #(or (= false %) (= true %))}) - -(def boolean-str - {:message "must be a boolean" - :optional true - :validate #(and (string? %) - (re-seq #"^(?:t|true|false|f|0|1)$" %)) - :coerce #(contains? #{"t" "true" "1"} %)}) - -(def string - {:message "must be a string" - :optional true - :validate string?}) - -(def string-like - {:message "must be a string" - :optional true - :coerce str}) - -(def in-range - {:message "not in range %s and %s" - :optional true - :validate #(and (number? %1) - (number? %2) - (number? %3) - (<= %2 %1 %3))}) - -(def positive - {:message "must be positive" - :optional true - :validate pos?}) - -(def negative - {:message "must be negative" - :optional true - :validate neg?}) - -(def map - {:message "must be a map" - :optional true - :validate map?}) - -(def set - {:message "must be a set" - :optional true - :validate set?}) - -(def coll - {:message "must be a collection" - :optional true - :validate coll?}) - -(def vector - {:message "must be a vector instance" - :optional true - :validate vector?}) - -(def every - {:message "must match the predicate" - :optional true - :validate #(every? %2 %1)}) - -(def member - {:message "not in coll" - :optional true - :validate #(some #{%1} %2)}) - -(def function - {:message "must be a function" - :optional true - :validate ifn?}) - -(def identical-to - {:message "does not match" - :optional true - :state true - :validate (fn [state v ref] - (let [prev (get state ref)] - (= prev v)))}) - -(def min-count - (letfn [(validate [v minimum] - {:pre [(number? minimum)]} - (>= (count v) minimum))] - {:message "less than the minimum %s" - :optional true - :validate validate})) - -(def max-count - (letfn [(validate [v maximum] - {:pre [(number? maximum)]} - (<= (count v) maximum))] - {:message "longer than the maximum %s" - :optional true - :validate validate})) diff --git a/test/struct/tests.cljc b/test/struct/tests.cljc deleted file mode 100644 index f7af2bc..0000000 --- a/test/struct/tests.cljc +++ /dev/null @@ -1,178 +0,0 @@ -(ns struct.tests - (:require #?(:cljs [cljs.test :as t] - :clj [clojure.test :as t]) - [struct.core :as st])) - -;; --- Tests - -(t/deftest test-optional-validators - (let [scheme {:max st/number - :scope st/string} - input {:scope "foobar"} - result (st/validate input scheme)] - (t/is (= nil (first result))) - (t/is (= input (second result))))) - -(t/deftest test-simple-validators - (let [scheme {:max st/number - :scope st/string} - input {:scope "foobar" :max "d"} - errors {:max "must be a number"} - result (st/validate input scheme)] - (t/is (= errors (first result))) - (t/is (= {:scope "foobar"} (second result))))) - -(t/deftest test-neested-validators - (let [scheme {[:a :b] st/number - [:c :d :e] st/string} - input {:a {:b "foo"} :c {:d {:e "bar"}}} - errors {:a {:b "must be a number"}} - result (st/validate input scheme)] - (t/is (= errors (first result))) - (t/is (= {:c {:d {:e "bar"}}} (second result))))) - -(t/deftest test-single-validators - (let [result1 (st/validate-single 2 st/number) - result2 (st/validate-single nil st/number) - result3 (st/validate-single nil [st/required st/number])] - (t/is (= [nil 2] result1)) - (t/is (= [nil nil] result2)) - (t/is (= ["this field is mandatory" nil] result3)))) - -(t/deftest test-parametric-validators - (let [result1 (st/validate - {:name "foo"} - {:name [[st/min-count 4]]}) - result2 (st/validate - {:name "bar"} - {:name [[st/max-count 2]]})] - (t/is (= {:name "less than the minimum 4"} (first result1))) - (t/is (= {:name "longer than the maximum 2"} (first result2))))) - -(t/deftest test-simple-validators-with-vector-schema - (let [scheme [[:max st/number] - [:scope st/string]] - input {:scope "foobar" :max "d"} - errors {:max "must be a number"} - result (st/validate input scheme)] - (t/is (= errors (first result))) - (t/is (= {:scope "foobar"} (second result))))) - -(t/deftest test-simple-validators-with-translate - (let [scheme [[:max st/number] - [:scope st/string]] - input {:scope "foobar" :max "d"} - errors {:max "a"} - result (st/validate input scheme {:translate (constantly "a")})] - (t/is (= errors (first result))) - (t/is (= {:scope "foobar"} (second result))))) - -(t/deftest test-dependent-validators-1 - (let [scheme [[:password1 st/string] - [:password2 [st/identical-to :password1]]] - input {:password1 "foobar" - :password2 "foobar."} - errors {:password2 "does not match"} - result (st/validate input scheme)] - (t/is (= errors (first result))) - (t/is (= {:password1 "foobar"} (second result))))) - -(t/deftest test-dependent-validators-2 - (let [scheme [[:password1 st/string] - [:password2 [st/identical-to :password1]]] - input {:password1 "foobar" - :password2 "foobar"} - result (st/validate input scheme)] - (t/is (= nil (first result))) - (t/is (= {:password1 "foobar" - :password2 "foobar"} (second result))))) - -(t/deftest test-multiple-validators - (let [scheme {:max [st/required st/number] - :scope st/string} - input {:scope "foobar"} - errors {:max "this field is mandatory"} - result (st/validate input scheme)] - (t/is (= errors (first result))) - (t/is (= {:scope "foobar"} (second result))))) - -(t/deftest test-validation-with-coersion - (let [scheme {:max st/integer-str - :scope st/string} - input {:max "2" :scope "foobar"} - result (st/validate input scheme)] - (t/is (= nil (first result))) - (t/is (= {:max 2 :scope "foobar"} (second result))))) - -(t/deftest test-validation-with-custom-coersion - (let [scheme {:max [[st/number-str :coerce (constantly :foo)]] - :scope st/string} - input {:max "2" :scope "foobar"} - result (st/validate input scheme)] - (t/is (= nil (first result))) - (t/is (= {:max :foo :scope "foobar"} (second result))))) - -(t/deftest test-validation-with-custom-message - (let [scheme {:max [[st/number-str :message "custom msg"]] - :scope st/string} - input {:max "g" :scope "foobar"} - errors {:max "custom msg"} - result (st/validate input scheme)] - (t/is (= errors (first result))) - (t/is (= {:scope "foobar"} (second result))))) - -(t/deftest test-coersion-with-valid-values - (let [scheme {:a st/number-str - :b st/integer-str} - input {:a 2.3 :b 3.3} - [errors data] (st/validate input scheme)] - (t/is (= {:a 2.3 :b 3} data)))) - -(t/deftest test-validation-nested-data-in-a-vector - (let [scheme {:a [st/vector [st/every number?]]} - input1 {:a [1 2 3 4]} - input2 {:a [1 2 3 4 "a"]} - [errors1 data1] (st/validate input1 scheme) - [errors2 data2] (st/validate input2 scheme)] - (t/is (= data1 input1)) - (t/is (= errors1 nil)) - (t/is (= data2 {})) - (t/is (= errors2 {:a "must match the predicate"})))) - -(t/deftest test-in-range-validator - (t/is (= {:age "not in range 18 and 26"} - (-> {:age 17} - (st/validate {:age [[st/in-range 18 26]]}) - first)))) - -(t/deftest test-honor-nested-data - (let [scheme {[:a :b] [st/required - st/string - [st/min-count 2 :message "foobar"] - [st/max-count 5]]} - input1 {:a {:b "abcd"}} - input2 {:a {:b "abcdefgh"}} - input3 {:a {:b "a"}} - [errors1 data1] (st/validate input1 scheme) - [errors2 data2] (st/validate input2 scheme) - [errors3 data3] (st/validate input3 scheme)] - (t/is (= data1 input1)) - (t/is (= errors1 nil)) - (t/is (= data2 {})) - (t/is (= errors2 {:a {:b "longer than the maximum 5"}})) - (t/is (= data3 {})) - (t/is (= errors3 {:a {:b "foobar"}})))) - -;; --- Entry point - -#?(:cljs - (do - (enable-console-print!) - (set! *main-cli-fn* #(t/run-tests)))) - -#?(:cljs - (defmethod t/report [:cljs.test/default :end-run-tests] - [m] - (if (t/successful? m) - (set! (.-exitCode js/process) 0) - (set! (.-exitCode js/process) 1)))) diff --git a/tools.clj b/tools.clj deleted file mode 100644 index d689311..0000000 --- a/tools.clj +++ /dev/null @@ -1,112 +0,0 @@ -(require '[clojure.java.shell :as shell] - '[clojure.main]) -(require '[rebel-readline.core] - '[rebel-readline.clojure.main] - '[rebel-readline.clojure.line-reader] - '[rebel-readline.clojure.service.local] - '[rebel-readline.cljs.service.local] - '[rebel-readline.cljs.repl] - '[eftest.runner :as ef]) -(require '[cljs.build.api :as api] - '[cljs.repl :as repl] - '[cljs.repl.node :as node]) - -(defmulti task first) - -(defmethod task :default - [args] - (let [all-tasks (-> task methods (dissoc :default) keys sort) - interposed (->> all-tasks (interpose ", ") (apply str))] - (println "Unknown or missing task. Choose one of:" interposed) - (System/exit 1))) - -(defmethod task "repl" - [[_ type]] - (case type - (nil "clj") - (rebel-readline.core/with-line-reader - (rebel-readline.clojure.line-reader/create - (rebel-readline.clojure.service.local/create)) - (clojure.main/repl - :prompt (fn []) ;; prompt is handled by line-reader - :read (rebel-readline.clojure.main/create-repl-read))) - - "node" - (rebel-readline.core/with-line-reader - (rebel-readline.clojure.line-reader/create (rebel-readline.cljs.service.local/create)) - (cljs.repl/repl - (node/repl-env) - :prompt (fn []) ;; prompt is handled by line-reader - :read (rebel-readline.cljs.repl/create-repl-read) - :output-dir "out" - :cache-analysis false)) - (println "Unknown repl: " type) - (System/exit 1))) - -(def options - {:main 'struct.tests - :output-to "out/tests.js" - :output-dir "out/tests" - :target :nodejs - :pretty-print true - :optimizations :advanced - :language-in :ecmascript5 - :language-out :ecmascript5 - :verbose true}) - -(defmethod task "test" - [[_ exclude]] - (let [tests (ef/find-tests "test") - tests (if (string? exclude) - (ef/find-tests (symbol exclude)) - tests)] - (ef/run-tests tests - {:fail-fast? true - :capture-output? false - :multithread? false}) - (System/exit 1))) - - -(defmethod task "test-cljs" - [[_ type]] - (letfn [(build [optimizations] - (api/build (api/inputs "src" "test") - (cond-> (assoc options :optimizations optimizations) - (= optimizations :none) (assoc :source-map true)))) - - (run-tests [] - (let [{:keys [out err]} (shell/sh "node" "out/tests.js")] - (println out err))) - - (test-once [] - (build :none) - (run-tests) - (shutdown-agents)) - - (test-watch [] - (println "Start watch loop...") - (try - (api/watch (api/inputs "src", "test") - (assoc options - :parallel-build false - :watch-fn run-tests - :cache-analysis false - :optimizations :none - :source-map true)) - (catch Exception e - (println "ERROR:" e) - (Thread/sleep 2000) - (test-watch))))] - - (case type - (nil "once") (test-once) - "watch" (test-watch) - "build-none" (build :none) - "build-simple" (build :simple) - "build-advanced" (build :advanced) - (do (println "Unknown argument to test task:" type) - (System/exit 1))))) - -;;; Build script entrypoint. This should be the last expression. - -(task *command-line-args*)