All Projects → alexanderkiel → Phrase

alexanderkiel / Phrase

Licence: epl-1.0
Clojure(Script) library for phrasing spec problems.

Programming Languages

clojure
4091 projects
clojurescript
191 projects

Projects that are alternatives of or similar to Phrase

form-validator-cljs
ClojureScript library to validate forms
Stars: ✭ 52 (-81.09%)
Mutual labels:  spec, form-validation
table-spec
Specs from SQL database schema for data generation and validation
Stars: ✭ 32 (-88.36%)
Mutual labels:  spec
CustomFormViews
A clean collection of views used for forms.
Stars: ✭ 12 (-95.64%)
Mutual labels:  form-validation
pwdcalc
Take no risk and help your users to choose good passwords!
Stars: ✭ 11 (-96%)
Mutual labels:  form-validation
devonfw4flutter-mts-app
Large-Scale Flutter Reference Application. An Extension of DevonFw's My Thai Star Project
Stars: ✭ 54 (-80.36%)
Mutual labels:  form-validation
automock
A library for testing classes with auto mocking capabilities using jest-mock-extended
Stars: ✭ 26 (-90.55%)
Mutual labels:  spec
formalizer
React hooks based form validation made for humans.
Stars: ✭ 12 (-95.64%)
Mutual labels:  form-validation
Formvuelar
Vue form components with server-side validation in mind
Stars: ✭ 263 (-4.36%)
Mutual labels:  form-validation
grav-plugin-form
Grav Form Plugin
Stars: ✭ 48 (-82.55%)
Mutual labels:  form-validation
eggplant
A behaviour driven development (BDD) library for Clojure. Simplicity is key.
Stars: ✭ 16 (-94.18%)
Mutual labels:  spec
microsub
For tracking issues on the Microsub specification
Stars: ✭ 23 (-91.64%)
Mutual labels:  spec
core
🔥 Antares Core Implemenation. Most important project layer, this is the heart for your app. ACL, notifiter, console, geoip, areas, utils and many more...
Stars: ✭ 24 (-91.27%)
Mutual labels:  form-validation
form-validator
A simple, easy to use, no frills, form validator for Android
Stars: ✭ 28 (-89.82%)
Mutual labels:  form-validation
ngff
Next-generation file format (NGFF) specifications for storing bioimaging data in the cloud.
Stars: ✭ 52 (-81.09%)
Mutual labels:  spec
svelte-form
JSON Schema form for Svelte v3
Stars: ✭ 47 (-82.91%)
Mutual labels:  form-validation
formio
Formio, form definition and binding library for Java platform
Stars: ✭ 24 (-91.27%)
Mutual labels:  form-validation
dropzone-ui-react
The most complete React Library Component for drag’n’drop files. Image and video previews. File validation. Multilanguage. Server side support.
Stars: ✭ 122 (-55.64%)
Mutual labels:  form-validation
Validify
Simple-as-possible React form validation
Stars: ✭ 271 (-1.45%)
Mutual labels:  form-validation
React Reactive Form
Angular like reactive forms in React.
Stars: ✭ 259 (-5.82%)
Mutual labels:  form-validation
ntast
Notion Abstract Syntax Tree specification.
Stars: ✭ 101 (-63.27%)
Mutual labels:  spec

Phrase

Build Status CircleCI Dependencies Status Downloads cljdoc

Clojure(Script) library for phrasing spec problems. Phrasing refers to converting to human readable messages.

This library can be used in various scenarios but its primary focus is on form validation. I talked about Form Validation with Clojure Spec in Feb 2017 and Phrase is the library based on this talk.

The main idea of this library is to dispatch on spec problems and let you generate human readable messages for individual and whole classes of problems. Phrase doesn't try to generically generate messages for all problems like Expound does. The target audience for generated messages are end-users of an application not developers.

Install

To install, just add the following to your project dependencies:

[phrase "0.3-alpha4"]

Usage

Assuming you like to validate passwords which have to be strings with at least 8 chars, a spec would be:

(require '[clojure.spec.alpha :as s])

(s/def ::password
  #(<= 8 (count %)))

executing

(s/explain-data ::password "1234")

will return one problem:

{:path [],
 :pred (clojure.core/fn [%] (clojure.core/<= 8 (clojure.core/count %))),
 :val "",
 :via [:user/password],
 :in []}

Phrase helps you to convert such problem maps into messages for your end-users which you define. Phrase doesn't generate messages in a generic way.

The main discriminator in the problem map is the predicate. Phrase provides a way to dispatch on that predicate in a quite advanced way. It allows to substitute concrete values with symbols which bind to that values. In our case we would like to dispatch on all predicates which require a minimum string length regardless of the concrete boundary. In Phrase you can define a phraser:

(require '[phrase.alpha :refer [defphraser]])

(defphraser #(<= min-length (count %))
  [_ _ min-length]
  (str "Please use at least " min-length " chars."))

the following code:

(require '[phrase.alpha :refer [phrase-first]])

(phrase-first {} ::password "1234")

returns the desired message:

"Please use at least 8 chars."

The defphraser macro

In its minimal form, the defphraser macro takes a predicate and an argument vector of two arguments, a context and the problem:

(defphraser int?
  [context problem]
  "Please enter an integer.")

The context is the same as given to phrase-first it can be used to generate I18N messages. The problem is the spec problem which can be used to retrieve the invalid value for example.

In addition to the minimal form, the argument vector can contain one or more trailing arguments which can be used in the predicate to capture concrete values. In the example before, we captured min-length:

(defphraser #(<= min-length (count %))
  [_ _ min-length]
  (str "Please use at least " min-length " chars."))

In case the predicated used in a spec is #(<= 8 (count %)), min-length resolves to 8.

Combined with the invalid value from the problem, we can build quite advanced messages:

(s/def ::password
  #(<= 8 (count %) 256))
  
(defphraser #(<= min-length (count %) max-length)
  [_ {:keys [val]} min-length max-length]
  (let [[a1 a2 a3] (if (< (count val) min-length)
                     ["less" "minimum" min-length]
                     ["more" "maximum" max-length])]
    (str "You entered " (count val) " chars which is " a1 " than the " a2 " length of " a3 " chars.")))
           
(phrase-first {} ::password "1234")
;;=> "You entered 4 chars which is less than the minimum length of 8 chars."

(phrase-first {} ::password (apply str (repeat 257 "x"))) 
;;=> "You entered 257 chars which is more than the maximum length of 256 chars."          

Besides dispatching on the predicate, we can additionally dispatch on :via of the problem. In :via spec encodes a path of spec names (keywords) in which the predicate is located. Consider the following:

(s/def ::year
  pos-int?)

(defphraser pos-int?
  [_ _]
  "Please enter a positive integer.")

(defphraser pos-int?
  {:via [::year]}
  [_ _]
  "The year has to be a positive integer.")

(phrase-first {} ::year "1942")
;;=> "The year has to be a positive integer."

Without the additional phraser with the :via specifier, the message "Please enter a positive integer." would be returned. By defining a phraser with a :via specifier of [::year], the more specific message "The year has to be a positive integer." is returned.

Default Phraser

It's certainly useful to have a default phraser which is used whenever no matching phraser is found. You can define a default phraser using the keyword :default instead of a predicate.

(defphraser :default
  [_ _]
  "Invalid value!")

You can remove the default phraser by calling (remove-default!).

More Complex Example

If you like to validate more than one thing, for example correct length and various regexes, I suggest that you build a spec using s/and as opposed to building a big, complex predicate which would be difficult to match.

In this example, I require a password to have the right length and contain at least one number, one lowercase letter and one uppercase letter. For each requirement, I have a separate predicate.

(s/def ::password
  (s/and #(<= 8 (count %) 256)
         #(re-find #"\d" %)
         #(re-find #"[a-z]" %)
         #(re-find #"[A-Z]" %)))

(defphraser #(<= lo (count %) up)
  [_ {:keys [val]} lo up]
  (str "Length has to be between " lo " and " up " but was " (count val) "."))

;; Because Phrase replaces every concrete value like the regex, we can't match
;; on it. Instead, we define only one phraser for `re-find` and use a case to 
;; build the message.
(defphraser #(re-find re %)
  [_ _ re]
  (str "Has to contain at least one "
       (case (str/replace (str re) #"/" "")
         "\\d" "number"
         "[a-z]" "lowercase letter"
         "[A-Z]" "uppercase letter")
       "."))

(phrase-first {} ::password "a")
;;=> "Length has to be between 8 and 256 but was 1."

(phrase-first {} ::password "aaaaaaaa")
;;=> "Has to contain at least one number."

(phrase-first {} ::password "AAAAAAA1")
;;=> "Has to contain at least one lowercase letter."

(phrase-first {} ::password "aaaaaaa1")
;;=> "Has to contain at least one uppercase letter."

(s/valid? ::password "aaaaaaA1")
;;=> true

Further Examples

You can find further examples here.

Phrasing Problems

The main function to phrase problems is phrase. It takes the problem directly. There is a helper function called phrase-first which does the whole thing. It calls s/explain-data on the value using the supplied spec and phrases the first problem, if there is any. However, you have to use phrase directly if you like to phrase more than one problem. The library doesn't contain a phrase-all function because it doesn't know how to concatenate messages.

Kinds of Messages

Phrase doesn't assume anything about messages. Messages can be strings or other things like hiccup-style data structures which can be converted into HTML later. Everything is supported. Just return it from the defphraser macro. Phrase does nothing with it.

API Docs

You can view the API Docs at cljdoc for v0.3-alpha4.

Related Work

  • Expound - aims to generate more readable messages as s/explain. The audience are developers not end-users.

Complete Example in ClojureScript using Planck

First install Planck if you haven't already. Planck can use libraries which are already downloaded into your local Maven repository. A quick way to download the Phrase Jar is to use boot:

boot -d phrase:0.3-alpha4

After that, start Planck with Phrase as dependency:

planck -D phrase:0.3-alpha4

After that, you can paste the following into the Planck REPL:

(require '[clojure.spec.alpha :as s])
(require '[phrase.alpha :refer [defphraser phrase-first]])

(s/def ::password
  #(<= 8 (count %)))
  
(defphraser #(<= min-length (count %))
  [_ _ min-length]
  (str "Please use at least " min-length " chars."))
  
(phrase-first {} ::password "1234")

The output should be:

nil
nil
:cljs.user/password
#object[cljs.core.MultiFn]
"Please use at least 8 chars."

License

Copyright © 2017 Alexander Kiel

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].