-
-
Notifications
You must be signed in to change notification settings - Fork 48
Make parameter aliases work for all schemas #248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
7969487
c45fbc1
edab5db
09dfdeb
eacac72
04a7686
883dbb0
afd66f4
4c5b260
6d8d215
2ecf1fb
332d3b1
0579763
ddf76bc
726064c
34a97b3
b823695
ef5917e
7c0e4a2
b24d513
ffe9032
4e1bbb5
99b6a57
ba4bd43
1beecd3
f843ae5
4842d26
95bdbd9
367c7da
a5ad9f4
cbcb972
8ca01bc
ec32339
9c4fda4
71d6438
afb6984
b9d6df0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,54 +1,127 @@ | ||
| (ns martian.parameter-aliases | ||
| (:require [schema.core :as s] | ||
| [camel-snake-kebab.core :refer [->kebab-case]] | ||
| [clojure.set :refer [rename-keys]] | ||
| [martian.schema-tools :refer [key-seqs prewalk-with-path]])) | ||
| (:require [clojure.set :refer [rename-keys]] | ||
| [martian.schema-tools :as schema-tools] | ||
| [schema.core :as s])) | ||
|
|
||
| ;; todo lean on schema-tools.core for some of this | ||
| (defn- aliases-at | ||
| "Internal helper. Given a `schema`, a path-local `cache` (atom), an `interner` | ||
| (atom), and a `path` (vector or seq), returns the alias map for that path. | ||
| (defn ->idiomatic [k] | ||
| (when (and k (s/specific-key? k) (not (and (keyword? k) (namespace k)))) | ||
| (->kebab-case (s/explicit-schema-key k)))) | ||
| - Delegates the actual computation to `compute-aliases-at`, which understands | ||
| schema wrappers (e.g. `:schema`, `:schemas`, etc.) and vector transparency. | ||
| - Caches results per-path in the provided `cache` to avoid recomputation. | ||
| - Interns identical alias maps across the paths via `interner`, so equal maps | ||
| share a single canonical instance (reduces memory churn in large APIs). | ||
| - Returns `nil` when there are no aliases at the given path." | ||
| [schema cache interner path] | ||
| (let [path' (if (vector? path) path (vec path))] | ||
| (or (get @cache path') | ||
| (let [m (schema-tools/compute-aliases-at schema path') | ||
| m' (when m | ||
| (or (get @interner m) | ||
| (-> interner | ||
| (swap! #(if (contains? % m) % (assoc % m m))) | ||
| (get m))))] | ||
| (swap! cache assoc path' m') | ||
| m')))) | ||
|
|
||
| #?(:bb nil | ||
|
|
||
| :clj | ||
| (deftype LazyRegistry [schema cache interner] | ||
| clojure.lang.ILookup | ||
| (valAt [_ k] | ||
| (aliases-at schema cache interner k)) | ||
| (valAt [_ k not-found] | ||
| (or (aliases-at schema cache interner k) not-found)) | ||
| Object | ||
| (toString [_] (str "#LazyRegistry (cached " (count @cache) ")"))) | ||
|
|
||
| :cljs | ||
| (deftype LazyRegistry [schema cache interner] | ||
| cljs.core/ILookup | ||
| (-lookup [_ k] | ||
| (aliases-at schema cache interner k)) | ||
| (-lookup [_ k not-found] | ||
| (or (aliases-at schema cache interner k) not-found)) | ||
| cljs.core/IPrintWithWriter | ||
| (-pr-writer [_ writer _opts] | ||
| (-write writer (str "#LazyRegistry (cached " (count @cache) ")"))))) | ||
|
|
||
| (defn- idiomatic-path [path] | ||
| (vec (keep ->idiomatic path))) | ||
| (vec (keep schema-tools/->idiomatic path))) | ||
|
|
||
| (defn parameter-aliases | ||
| "Produces a data structure for use with `unalias-data`" | ||
| (defn aliases-hash-map | ||
| "Eagerly computes the registry as a data structure for the given `schema`. | ||
| Produces a plain hash map with idiomatic keys (aliases) mappings per path | ||
| in a (possibly, deeply nested) `schema` for all its unqualified keys. | ||
| The result is then used with `alias-schema` and `unalias-data` functions." | ||
| [schema] | ||
| (reduce (fn [acc path] | ||
| (if-let [idiomatic-key (some-> path last ->idiomatic)] | ||
| (if-not (= (last path) idiomatic-key) | ||
| (update acc (idiomatic-path (drop-last path)) merge {idiomatic-key (last path)}) | ||
| acc) | ||
| acc)) | ||
| (let [leaf (peek path) | ||
| idiomatic-key (some-> leaf (schema-tools/->idiomatic))] | ||
| (if (and idiomatic-key (not= leaf idiomatic-key)) | ||
| (update acc (idiomatic-path (pop path)) assoc idiomatic-key leaf) | ||
| acc))) | ||
| {} | ||
| (key-seqs schema))) | ||
| (schema-tools/key-seqs schema))) | ||
|
|
||
|
Comment on lines
19
to
+70
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The implementation does the same thing, but faster (uses faster |
||
| (defn registry | ||
| "Builds a lookupable registry of parameter alias maps for the given `schema`. | ||
| - On JVM/CLJS: | ||
| Returns an instance of a lazy registry. | ||
| Aliases are computed on demand (via `compute-aliases-at`), so materializing | ||
| massive alias maps upfront is avoided. Per-path results are memoized within | ||
| the registry. Identical alias maps are shared to cut memory usage. | ||
| A returned value implements `ILookup` and is indexed by \"idiomatic paths\". | ||
| Looking up a path gives an alias map for that level, mapping idiomatic keys | ||
| (kebab-case, unqualified) to their original schema keys. | ||
| - On Babashka: | ||
| Returns a plain hash map registry that is computed eagerly via `key-seqs`." | ||
| [schema] | ||
| (when schema | ||
| #?(:bb (aliases-hash-map schema) | ||
| :default (new LazyRegistry schema (atom {}) (atom {}))))) | ||
|
Comment on lines
+89
to
+90
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Babashka does not yet support interfaces implementations on custom records/types ( |
||
|
|
||
| ;; TODO: An alias for backward compatibility. Remove later on. | ||
| (def parameter-aliases registry) | ||
|
|
||
| (defn unalias-data | ||
| "Takes parameter aliases and (deeply nested) data, returning data with deeply-nested keys renamed as described by parameter-aliases" | ||
| [parameter-aliases x] | ||
| (if parameter-aliases | ||
| (prewalk-with-path (fn [path x] | ||
| (if (map? x) | ||
| (rename-keys x (get parameter-aliases (idiomatic-path path))) | ||
| x)) | ||
| [] | ||
| x) | ||
| x)) | ||
| "Given a (possibly, deeply nested) `data` structure, returns it with all its | ||
| keys renamed from \"idiomatic\" (aliases) using the given parameter aliases | ||
| `registry`." | ||
| [registry data] | ||
| (if registry | ||
| (schema-tools/prewalk-with-path | ||
| (fn [path x] | ||
| (if (map? x) | ||
| (rename-keys x (get registry (idiomatic-path path))) | ||
| x)) | ||
| data) | ||
| data)) | ||
|
|
||
| (defn alias-schema | ||
| "Walks a schema, transforming all keys into their aliases (idiomatic keys)" | ||
| [parameter-aliases schema] | ||
| (if parameter-aliases | ||
| (prewalk-with-path (fn [path x] | ||
| (if (map? x) | ||
| (let [kmap (reduce-kv (fn [kmap k v] | ||
| (assoc kmap v k (s/optional-key v) (s/optional-key k))) | ||
| {} | ||
| (get parameter-aliases (idiomatic-path path)))] | ||
| (rename-keys x kmap)) | ||
| x)) | ||
| [] | ||
| schema) | ||
| "Given a (possibly, deeply nested) `schema`, renames all keys (in it and its | ||
| subschemas) into corresponding \"idiomatic\" keys (aliases) using the given | ||
| parameter aliases `registry`." | ||
| [registry schema] | ||
| (if registry | ||
| (schema-tools/prewalk-with-path | ||
| (fn [path subschema] | ||
| (if (map? subschema) | ||
| (let [kmap (reduce-kv (fn [kmap idiomatic original] | ||
| (assoc kmap | ||
| original idiomatic | ||
| (s/optional-key original) (s/optional-key idiomatic))) | ||
| {} | ||
| (get registry (idiomatic-path path)))] | ||
| (rename-keys subschema kmap)) | ||
| subschema)) | ||
| schema) | ||
| schema)) | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is not possible since
schema-toolsdon't provide any utilities for traversing schemas withpath, while we heavily rely on this kind of traversal for both schemas and data. Other schema-related utilities were moved out of this namespace into themartian.schema-tools, leveragingschema-toolsto some degree.