{"id":28120141,"url":"https://github.com/staticweb-io/spec-forms","last_synced_at":"2025-06-13T20:07:44.992Z","repository":{"id":62433043,"uuid":"332894797","full_name":"staticweb-io/spec-forms","owner":"staticweb-io","description":"A Clojure(Script) library for creating human-readable error messages from specs, with some helper functions for using those messages with Reforms.","archived":false,"fork":false,"pushed_at":"2021-01-25T22:38:08.000Z","size":28,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-12-14T23:57:36.557Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/staticweb-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-01-25T21:55:49.000Z","updated_at":"2021-02-25T22:16:14.000Z","dependencies_parsed_at":"2022-11-01T21:01:26.302Z","dependency_job_id":null,"html_url":"https://github.com/staticweb-io/spec-forms","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/staticweb-io/spec-forms","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/staticweb-io%2Fspec-forms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/staticweb-io%2Fspec-forms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/staticweb-io%2Fspec-forms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/staticweb-io%2Fspec-forms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/staticweb-io","download_url":"https://codeload.github.com/staticweb-io/spec-forms/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/staticweb-io%2Fspec-forms/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259712410,"owners_count":22900038,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-14T07:40:18.684Z","updated_at":"2025-06-13T20:07:44.976Z","avatar_url":"https://github.com/staticweb-io.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# spec-forms\n\n[![Clojars Project](https://img.shields.io/clojars/v/io.staticweb/spec-forms.svg)](https://clojars.org/io.staticweb/spec-forms)\n\nA Clojure(Script) library for creating human-readable error messages from [specs](https://clojure.org/guides/spec), with some helper functions for using those messages with [Reforms](https://github.com/bilus/reforms).\n\nThe current version depends on [spec-tools](https://github.com/metosin/spec-tools), but when spec-alpha2 is released and works with ClojureScript, this dependency will be dropped and spec-forms will create Specs directly.\n\n## Install\n\nAdd to deps.edn:\n```\nio.staticweb/spec-forms {:mvn/version \"0.1.0\"}\nreforms {:mvn/version \"0.4.3\"}\n```\n\nOr add to project.clj:\n```\n[io.staticweb/spec-forms \"0.1.0\"]\n[reforms \"0.4.3\"]\n```\n\n## Usage\n### Spec Creation\n\nFirst, you need some specs. You can define error messages inline with the `spec-forms.alpha/validator` macro, or you can use normal specs in conjunction with the [phrase](https://github.com/alexanderkiel/phrase) library. We'll start with the `validator` macro. It takes two arguments: a predicate function to be called on the value, and an error message for when the predicate returns false, e.g.,\n```clojure\n(require '[spec-forms.alpha :use validator])\n\n(validator\n  #(\u003e= 16 (count %))\n  \"Must be 16 characters or less.\")\n```\nThe `validator` simply adds the message to the data returned by `clojure.spec.alpha/explain-data`, under the `:reason` key. If you happen to have existing code using `spec-tools` that returns a :reason, then you can use those specs with no changes.\n\nAn example `.cljc` file with a full spec definition:\n```clojure\n(ns sf-test\n  (:require [clojure.spec.alpha :as s]\n            [clojure.string :as str]\n            [spec-forms.alpha :as sf])\n  #?(:cljs\n     (:require-macros [clojure.spec.alpha])))\n\n(defn max-length [n]\n  (sf/validator\n    #(\u003e= n (count %))\n    (str \"Must be \" n \" characters or less.\")))\n\n(defn min-length [n]\n  (sf/validator\n    #(\u003c= n (count %))\n    (str \"Must be at least \" n \" characters long.\")))\n\n(def non-blank\n  (sf/validator\n    #(and % (not (str/blank? %)))\n    \"Must not be blank.\"))\n    \n(s/def ::login (s/and (min-length 2) (max-length 4) non-blank))\n(s/def ::password (s/and (min-length 2) (max-length 4) non-blank))\n\n(s/def ::login-form (s/keys :req [::login ::password]))\n```\n\n### Validation with clojure.spec\n\nTo validate on your own (rather than using reforms), just call `spec.alpha/explain` or `spec.alpha/explain-data` on any of the specs that you created earlier. `explain` prints the failure reason, while `explain-data` returns a vector of problems which each have a :reason key with the failure message.\n\n```clojure\n(in-ns 'sf-test)\n\n(s/explain ::login \"\")\n;; \"\" - failed: Must be at least 2 characters long. spec: :sf-test/login\n;= nil\n\n(s/explain-data ::login \"\")\n;=#:clojure.spec.alpha{:problems\n                       ({:path [],\n                         :pred\n                         (clojure.core/fn\n                          [%]\n                          (clojure.core/\u003c= n (clojure.core/count %))),\n                         :val \"\",\n                         :via [:sf-test/login],\n                         :in [],\n                         :reason\n                         \"Must be at least 2 characters long.\"}),\n                       :spec :sf-test/login,\n                       :value \"\"}\n```\n\n### Form Validation\n\nWe use the [Reforms validation helpers](https://github.com/bilus/reforms#validation), but we use `spec-forms.alpha.validate!` instead of `reforms.core/validate!` The spec-forms `validate!` function takes four arguments: the data and ui-state atoms, the spec name, and an optional configuration map:\n```clojure\n(spec-forms.alpha/validate! data ui-state :sf-test/login-form\n   {:default-error-message \"Oops!\"})\n```\nThe configuration map supports three options:\n* `:default-error-message` A message to return if no other message is found. Default: \"Failed validation\"\n* `:message-fn` A 2-argument function to be called with the problem as returned by `clojure.spec.alpha/explain-data` and the configuration map. It should return a String. It's this function's responsibility to parse the `:reason` of the problem and return the `:default-error-messsage`.\n* `:required-key-message` This is used when a key is not present in the form but is required, as when `clojure.spec.alpha/keys` is used. The validator for the key will not be called due to the design of Clojure Spec. It can be a function that will receive the missing key (if you'd like different messages for different keys), or a String used for all keys. Default: `\"Required\"`.\n\nReforms implements form data as a map, so we will need a map definition in our spec, such as `(s/def ::login-form (s/keys :req [::login ::password]))` in the Usage example earlier. `::login-form` is the spec name which we give as the third argument of `validate!`. `data` is an atom containing the form data map. `ui-state` is an atom (initially nil) that Reforms uses to keep track of validation errors. See the [Reforms docs](https://github.com/bilus/reforms#validation) for more information.\n\nAn example implementation using Rum (you'll need to add [rum-reforms](https://clojars.org/rum-reforms) as a dependency to run this):\n```clojure\n(ns sf-test.client\n  (:require [clojure.spec.alpha :as s]\n            [reforms.rum :include-macros true :as f]\n            [reforms.validation :include-macros true :as v]\n            [rum.core :as rum :refer (defcs)]\n            [spec-forms.alpha :as sf]\n            [sf-test]))\n\n(defn login!\n  [data ui-state]\n  (when (sf/validate! data ui-state :sf-test/login-form)\n    (js/alert \"Logged in!\")))\n\n(defcs login-form\n  \u003c (rum/local nil ::ui-state)\n  [state data]\n  (let [comp (:rum/react-component state)\n        ui-state (::ui-state state)]\n    (v/form\n      ui-state\n      (v/text \"Login\" data [:sf-test/login])\n      (v/password \"Password\" data [:sf-test/password])\n      (f/form-buttons\n        (f/button-primary \"Log In\"\n          #(do (login! data ui-state)\n               (rum/request-render comp)))))))\n\n(defn mount-form []\n  (rum/mount\n   (login-form (atom nil))\n   (js/document.getElementById \"app\")))\n\n(defn init []\n  (js/console.log \"init\")\n  (mount-form))\n```\n\n### Using with phrase\n\nIt's very simple to use [phrase](https://github.com/alexanderkiel/phrase) with spec-forms. We just need to create a custom message-fn and pass it as an option to `validate!`:\n\n```clojure\n(require 'phrase.alpha 'spec-forms.alpha)\n\n(defn spec-problem-\u003emessage [{:keys [reason val via]} opts]\n  (or reason\n    (phrase.alpha/phrase-first {} (last via) val)\n    (:default-failure-message opts \"Failed validation\")))\n```\n\nWherever you call `validate!`:\n```clojure\n(spec-forms.alpha/validate! data ui-state :your-spec-ns/your-spec\n  {:message-fn spec-problem-\u003emessage})\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstaticweb-io%2Fspec-forms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstaticweb-io%2Fspec-forms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstaticweb-io%2Fspec-forms/lists"}