{"id":19293992,"url":"https://github.com/igrishaev/formality","last_synced_at":"2026-02-27T22:38:35.708Z","repository":{"id":147470039,"uuid":"109709126","full_name":"igrishaev/formality","owner":"igrishaev","description":null,"archived":false,"fork":false,"pushed_at":"2017-11-07T07:54:08.000Z","size":6,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-15T01:35:32.727Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/igrishaev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-11-06T14:56:00.000Z","updated_at":"2017-11-06T14:56:00.000Z","dependencies_parsed_at":null,"dependency_job_id":"a928d551-5025-42a0-9c92-e49dbd38c2d5","html_url":"https://github.com/igrishaev/formality","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/igrishaev/formality","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fformality","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fformality/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fformality/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fformality/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/igrishaev","download_url":"https://codeload.github.com/igrishaev/formality/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fformality/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29917845,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-27T19:37:42.220Z","status":"ssl_error","status_checked_at":"2026-02-27T19:37:41.463Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":"2024-11-09T22:36:47.462Z","updated_at":"2026-02-27T22:38:35.666Z","avatar_url":"https://github.com/igrishaev.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"```clojure\n(ns forms)\n\n;;;;;;\n\n(defmulti validate-field :type)\n\n(defmethod validate-field :min\n  [value min-value]\n  (\u003e= value min-value))\n\n(defmethod validate-field :max\n  [value max-value]\n  (\u003c= value max-value))\n\n(defmethod validate-field :enum\n  [value values]\n  (contains? (set values) value))\n\n(defprotocol IValidator\n  (_validator-artifact [this])\n  (_validate-field [this value]))\n\n(defrecord MinValidator [v]\n\n  IValidator\n\n  (_validator-artifact [this]\n    :error/min-validator)\n\n  (_validate-field [this value]\n    (\u003e= value v)))\n\n(defrecord RangeValidator [v1 v2]\n\n  IValidator\n\n  (_validator-artifact [this]\n    :error/range-validator)\n\n  (_validate-field [this value]\n    (and (\u003e= value v1)\n         (\u003c= value v2))))\n\n(defrecord RegexValidator [pattern]\n\n  IValidator\n\n  (_validator-artifact [this]\n    :error/regex-validator)\n\n  (_validate-field [this value]\n    (re-find pattern value)))\n\n(defn min-validator [v]\n  (-\u003eMinValidator v))\n\n(defn ragne-validator [v1 v2]\n  (-\u003eRangeValidator v1 v2))\n\n(defn regex-validator [pattern]\n  (-\u003eRegexValidator pattern))\n\n#_(defrecord RegexValidator [pattern]\n\n  IValidator\n\n  (validate [this value]\n    (re-find pattern value)))\n\n#_(defrecord EnumValidator [values]\n\n  IValidator\n\n  (validate [this value]\n    (contains? (set values) value)))\n\n;;;;;;;\n\n(defprotocol IField\n  (_field-type [this])\n  (_clean-field [this value]))\n\n(defrecord IntegerField []\n\n  IField\n\n  (_field-type [this] :integer)\n\n  (_clean-field\n    [this value]\n    (cond\n      (integer? value) value\n      (string? value) (Integer/parseInt value))))\n\n(defrecord ListField [field]\n\n  IField\n\n  (_field-type [this] :list)\n\n  (_clean-field\n    [this values]\n\n    #_(doseq [value values]\n      (-\u003e sample\n          (set-field-value value)\n          clean-field\n          validate-field\n          )\n      )\n    ))\n\n(defrecord FormField [form]\n\n  IField\n\n  (_field-type [this] :form)\n\n  (_clean-field\n    [this data]\n    )\n\n  )\n\n(def field-defaults\n  {:required true\n   :nullable false})\n\n(defn create-field [map-fn name \u0026 {:as args}]\n  (let [field (map-fn (merge field-defaults args))]\n    (assoc field\n           :type (_field-type field)\n           :name name)))\n\n(def integer-field\n  (partial create-field map-\u003eIntegerField))\n\n(def list-field (partial create-field map-\u003eListField))\n\n(defmulti set-field-value :type)\n\n(defmethod set-field-value :default\n  [field value]\n  (assoc field :value value))\n\n(defmethod set-field-value :list\n  [{_field :field :as field} values]\n  (cond\n    (nil? values)\n    (assoc field :value nil)\n\n    (vector? values)\n    (assoc field\n           :value values\n           :fields (mapv set-field-value (repeat _field) values))\n\n    :default\n    (set-field-error field :error/list)))\n\n(defn field-set?\n  [field]\n  (contains? field :value))\n\n(defmulti get-field-value :type)\n\n(defmethod get-field-value :default\n  [field]\n  (when (field-set? field)\n    (:value field)))\n\n(defmethod get-field-value :list\n  [field]\n\n\n  (when-let [fields (:fields field)]\n    (mapv get-field-value fields)))\n\n#_(defmethod get-field-value :form\n  [field]\n  (mapv get-field-value (:fields field)))\n\n(defn set-field-error\n  [field error]\n  (update field :errors concat [error]))\n\n(defmacro with-safe [\u0026 body]\n  `(try\n     ~@body\n     (catch Throwable e#)))\n\n(defmulti clean-field :type)\n\n(defmethod clean-field :default\n  [{:keys [required nullable] :as field}]\n  (let [value (get-field-value field)]\n    (if (and (field-set? field)\n             (not (nil? value)))\n      (if-let [clean-value (with-safe (_clean-field field value))]\n        (set-field-value field clean-value)\n        (set-field-error field :error/clean))\n      field)))\n\n(defmulti field-failed :type)\n\n(defmethod field-failed :default\n  [field]\n  (-\u003e field :errors not-empty))\n\n(defmethod field-failed :list\n  [field]\n  (some identity (map field-failed (:fields field))))\n\n(defmethod clean-field :list\n  [{fields :fields :as field}]\n  (let [field (assoc field :fields (mapv clean-field fields))]\n    (if (field-failed field)\n      (set-field-error field :error/clean)\n      field)))\n\n#_(defmethod clean-field :form\n  [{form :form :as field}]\n  (assoc field :form (clean-form form)))\n\n#_(defn field-failed\n  [field]\n  (-\u003e field :errors not-empty))\n\n(defn apply-field-validator\n  [field validator]\n  (if (_validate-field validator (get-field-value field))\n    field\n    (set-field-error field (_validator-artifact validator))))\n\n(defn validate-field\n  [{:keys [required nullable] :as field}]\n\n  (cond\n\n    (field-failed field)\n    field\n\n    (and required (not (field-set? field)))\n    (set-field-error field :error/required)\n\n    (and (not nullable)\n         (field-set? field)\n         (nil? (get-field-value field)))\n    (set-field-error field :error/nullable)\n\n    (nil? (get-field-value field))\n    field\n\n    :default\n    (loop [field field\n           validators (:validators field)]\n      (if (empty? validators)\n        field\n        (recur (apply-field-validator field (first validators))\n               (rest validators))))))\n\n;;;;;;\n\n(defrecord Form\n    [fields validators])\n\n(defn field-exists?\n  [form field-name]\n  (get-in form [:fields field-name]))\n\n(defn set-form-value\n  [form field value]\n  (if (field-exists? form field)\n    (update-in form [:fields field] set-field-value value)\n    form))\n\n(defn set-form-data\n  [form data]\n  (let [pairs (vec data)]\n    (loop [form form\n           pairs pairs]\n      (if (empty? pairs)\n        form\n        (let [[field value] (first pairs)\n              form (set-form-value form field value)]\n          (recur form (rest pairs)))))))\n\n(defn clean-form-field\n  [form field-name]\n  (if-let [field (get-in form [:fields field-name])]\n    (update-in form [:fields field-name] (clean-field field))\n    form))\n\n(defn iter-form\n  [form]\n  (-\u003e\u003e form :fields vals (sort-by :index)))\n\n(defn clean-form\n  [form]\n  (let [pairs (-\u003e form :fields vec)]\n    (loop [form form\n           pairs pairs]\n      (if (empty? pairs)\n        form\n        (let [[field field-obj] (first pairs)\n              form (update-in\n                    form\n                    [:fields field]\n                    clean-field)]\n          (recur form (rest pairs)))))))\n\n(defn validate-form\n  [form]\n  (let [pairs (-\u003e form :fields vec)]\n    (loop [form form\n           pairs pairs]\n      (if (empty? pairs)\n        form\n        (let [[field field-obj] (first pairs)\n              form (update-in\n                    form\n                    [:fields field]\n                    validate-field)]\n          (recur form (rest pairs)))))))\n\n(defn field? [obj]\n  (satisfies? IField obj))\n\n(defn validator? [obj]\n  (satisfies? IValidator obj))\n\n(defn create-form [\u0026 args]\n  (let [fields (filterv field? args)\n        validators (filterv validator? args)]\n    (-\u003eForm\n     (into {} (for [[i field] (map vector (range) fields)]\n                [(:name field) (assoc field :index i)]))\n     validators)))\n\n#_(defprotocol IWidget\n  (render [this obj]))\n\n#_(defrecord InputWidget []\n  (render [this field]\n    [:input {:name (:name field)\n             :value (:value field)}]))\n\n#_(defrecord FormWidget []\n  (render [this form]\n    (for [field (iter-form form)]\n      (render field))))\n\n(def f\n  (create-form\n   (integer-field :age)\n   (integer-field :foo)))\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figrishaev%2Fformality","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Figrishaev%2Fformality","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figrishaev%2Fformality/lists"}