-
-
Save nha/689016c1cf8c47de3c24a1e1d07ded12 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(:require [clojure.string :as str] | |
[com.rpl.specter :refer [ALL FIRST LAST STAY AFTER-ELEM MAP-KEYS MAP-VALS recursive-path if-path collect-one traverse cond-path multi-path transform setval before-index index-nav]] | |
[schema-tools.core :as st] | |
[schema.core :as s] | |
[schema.spec.leaf]) | |
(defn primitive-schema? | |
"do we have a better way?" | |
[v] | |
;;possible other approaches | |
;; - schema.spec.leaf.LeafSpec | |
;; - check that is has no sub-schemas and that is implements a schema.core | |
;; protocol (which one?) | |
;; - ikitommi | |
;; https://github.com/metosin/schema-tools/blob/master/src/schema_tools/walk.cljc | |
;; and https://github.com/metosin/schema-viz/blob/master/src/schema_viz/core.clj | |
(boolean | |
(and ;; (satisfies? s/Schema v) | |
(or | |
(some #(= v %) [s/Any s/Int s/Num s/Bool s/Str s/Symbol s/Uuid]) | |
(some #(instance? % v) | |
[(type (s/enum)) ;; schema.core.EnumSchema | |
s/Regex | |
schema.core.Maybe | |
schema.core.One ;; s/optional | |
schema.core.Predicate]))))) | |
(def ^:private PATH-SCHEMA-WALKER | |
(recursive-path [] p | |
(cond-path | |
(fn [v] | |
(instance? schema.core.ConditionalSchema v)) | |
[(multi-path ALL (collect-one FIRST) LAST p)] | |
(fn [v] | |
(cond | |
(primitive-schema? v) false | |
(map? v) true ;; this is a real path | |
:default false)) | |
[ALL (collect-one FIRST) LAST p] | |
(fn [v] true) STAY))) | |
(defn- reduce-schema-flat* [f m] | |
(reduce | |
f | |
{} | |
(traverse PATH-SCHEMA-WALKER m))) | |
(def ^:private schema-separator "_") | |
(def ^:private schema-separator-regex (re-pattern separator)) | |
(defn key->str | |
"same as s/explicit-schema-key but does not error" | |
[k] | |
(cond (keyword? k) (name k) | |
(instance? schema.core.RequiredKey k) (name (.-k ^schema.core.RequiredKey k)) | |
(s/optional-key? k) (name (.-k ^schema.core.OptionalKey k)) | |
:default k)) | |
(defn- make-optional [k] | |
(if (instance? schema.core.OptionalKey k) | |
k | |
(s/optional-key k))) | |
(defn flatten-schema* | |
"Transform a nested map into a seq of [keyseq leaf-val] pairs | |
adapted from plumatic.plumbing" | |
[m] | |
;; TODO see if specter can do it better (like above) | |
;; TODO see ic can remove all these concat | |
(when m | |
((fn flatten-helper [keyseq m] | |
(when m | |
(cond | |
(instance? schema.core.ConditionalSchema m) (let [[p1+s1 p2+s2] (:preds-and-schemas m) | |
;; undocumented internals, | |
;; we care about schemas, not predicates | |
left (second p1+s1) | |
right (second p2+s2) | |
;; make last of the keyseq optional, | |
;; since either condition could be true | |
opt-keyseq (transform [LAST] make-optional keyseq)] | |
(into (flatten-helper opt-keyseq left) | |
(flatten-helper opt-keyseq right))) | |
(primitive-schema? m) [[keyseq m]] | |
(map? m) (mapcat (fn [[k v]] (flatten-helper (conj keyseq k) v)) m) | |
:default [[keyseq m]]))) | |
[] m))) | |
(defn flatten-schema | |
"note: this is probably rather slow, so use it once during boot/generation time" | |
[m] | |
;; NOTE: assumes m is a map TODO handle other cases | |
(let [flat-seq (->> (flatten-schema* m) | |
(map (fn [[path v]] | |
(let [str-path (apply str (interpose schema-separator (map key->str path))) | |
has-optional? (some s/optional-key? path) | |
final-path (if has-optional? (s/optional-key str-path) str-path)] | |
[final-path v]))))] | |
;; print a warning when there are duplicate keys | |
(doall (map (fn [[k v]] (when (< 1 v)(timbre/warn v "duplicate key " k))) | |
(frequencies (map first flat-seq)))) | |
(into {} flat-seq))) | |
(defn unflatten-data | |
"reverse operation of flatten-schema, intended for data only as schemas have too many edge cases" | |
[m] | |
;; NOTE assumes a map for now TODO handle others | |
(reduce (fn [acc [path v]] | |
(let [s path | |
#_(if (instance? schema.core.OptionalKey path) | |
(.-k path) | |
path)] | |
(assoc-in acc (map keyword (str/split s separator-regex)) v))) {} m)) | |
(defn map->querystring | |
"m: a flat map | |
returns a querystring fragment" | |
[m] | |
(->> m | |
(transform [MAP-VALS] codec/form-encode) | |
(map #(str/join "=" %)) | |
(str/join "&"))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment