暫無描述

clarity.clj 5.8KB

    (ns clarity (:use clojure.core.typed)) ; (HMap :mandatory {:a ... :b ...}) -> { :a input :b input } ; needs nesting, dynamic rules? ; - required/optional? sometimes comes from above, an environment? ; - optional fields: adding dynamically would be better? ; * but how to realize without creating an abstract ui model? ; - additional attributes (req/opt, hidden, defaults) are a problem ; in general as they are quite tightly coupled to the implementations, ; is there a name for this? is this a leaking abstraction? ; what do i want? from a description of the data i want to create an ; input method that only allows inserting valid values. ; sometimes we have existing data present or required so we must ; render that as part of the input tree. we also sometimes want to add ; information dynamically, for example adding key-value-pairs to maps ; and new elements to collections. to be able to do that we need to ; dynamically add new typed input methods on request. we also want to ; delete information sometimes, but only when it is not required. (defmulti make-input (fn [type & _] (if (seq? type) (first type) type))) (defmethod make-input 'Value [type] (let [x (second type) attrs {:value (str x) :readonly ""}] [:input (into attrs (cond (instance? Boolean x) {:type "checkbox"} (number? x) {:type "number"} (or (instance? java.net.URL x) (instance? java.net.URI x)) {:type "url"} (keyword? x) {:pattern "^:(\\w+|\\w+(\\.\\w+)*\\/\\w+)$" :type "text"} :else {:type "text"}))])) (defmethod make-input 'Option [[_ type]] (make-input type)) (defmethod make-input 'U [[_ & alts]] [:select (map (fn [[_ x]] [:option (str x)]) alts)]) (defmethod make-input 'Keyword [_] [:input {:type "text" :placeholder ":my.ns/identifier" :pattern "^:(\\w+|\\w+(\\.\\w+)*\\/\\w+)$"}]) (defmethod make-input 'String [_] [:input {:type "text"}]) (defmethod make-input 'Boolean [_] [:input {:type "checkbox"}]) (defmethod make-input 'Number [_] [:input {:type "number"}]) (defmethod make-input 'HVec [[_ & types]] ; display existing values as editable *and* allow adding new elements ; those elements can be of multiple types -> dynamism required? ) (defmethod make-input 'HMap [[ _ & {:keys [mandatory optional]}]] (concat '("{") (map (fn [[ key val]] [:div.field [:label (str key)] (make-input val)]) mandatory) (map (fn [[key val]] [:div.field.optional [:label (str key)] (make-input val)]) optional) '("}"))) (def datomic-attr-type '(HMap :mandatory {:db/id (Value "#db/id[db.part/db]") :db/ident Keyword :db/valueType (U (Value :db.type/keyword) (Value :db.type/string) (Value :db.type/boolean) (Value :db.type/long) (Value :db.type/bigint) (Value :db.type/float) (Value :db.type/double) (Value :db.type/bigdec) (Value :db.type/ref) (Value :db.type/instant) (Value :db.type/uuid) (Value :db.type/uri) (Value :db.type/bytes)) :db/cardinality (U (Value :db.cardinality/one) (Value :db.cardinality/many)) :db.install/_attribute (Value :db.part/db)} :optional {:db/doc String :db/unique (Option (U (Value :db.unique/value) (Value :db.unique/identity))) :db/index Boolean ; what about defaults? :db/fulltext Boolean :db/isComponent (Value :db.type/ref) ; FIXME: needs a custom type (dynamic even, because only valid refs should be allowed) :db/noHistory Boolean})) (defmulti friendly-check (fn [form type] (if (seq? type) (first type) type))) (defmethod friendly-check 'Value [val [_ expected]] (if (= val expected) true {:error (str "Expected value " expected ", but found " val ".")})) (defmacro def-friendly-check [sym pred] `(defmethod friendly-check ~sym [val# _#] (if (~pred val#) true {:error (str "Expected value of type " (name ~sym) ", but got value " val# ".")}))) (def-friendly-check 'String string?) (def-friendly-check 'Keyword keyword?) (def-friendly-check 'Boolean #(instance? Boolean %)) (defmethod friendly-check 'U [val [_ & types]] (if (some true? (map (partial friendly-check val) types)) true {:error (str "Expected one of " types ", but found " val ".")})) (defn friendly-check-keys [m mandatory _] (map (fn [[key type]] (if-let [val (get m key)] (let [check (friendly-check val type)] (if (true? check) true {key (:error check)})) {key (str "No value found, but expected one of type " type ".")})) mandatory)) (defmethod friendly-check 'HMap [val [_ & {:keys [mandatory optional]}]] (if (map? val) (let [key-checks (friendly-check-keys val mandatory optional)] (if (every? true? key-checks) true {:error (filter (comp not true?) key-checks)})) {:error (str "Expected value of type Map, but got value " val ".")}))