cloojure / tupelo

Tupelo: Clojure With A Spoonful of Honey
Eclipse Public License 1.0
510 stars 15 forks source link

Can` t use :*? #23

Closed blindcoding9 closed 1 year ago

blindcoding9 commented 1 year ago

From the find-step exemple:

(let [job-data  {:_id                  "56044a42a27847d11d61bfc0"
                 :schedule-template-id "55099ebdcca58a0c717df912"
                 :jobs                 [{:job-template-id "55099ebdcca58a0c717df91f"
                                         :_id             "56044a42a27847d11d61bfd5"
                                         :step-templates  [{:job-step-template-id "55099ebdcca58a0c717df921"
                                                            :_id                  "56044a42a27847d11d61bfd9"}
                                                           {:job-step-template-id "55099ebdcca58a0c717df920"
                                                            :_id                  "56044a42a27847d11d61bfd7"}]}]}

      find-step (fn [step-id]
                  (with-forest (new-forest)
                               (let [root-hid   (add-tree (edn->tree job-data))
                                     tmpl-paths (find-paths root-hid [:** {::tf/value step-id}])]
                                 (when (not-empty? tmpl-paths)
                                   (let [tmpl-hid (t/xthird (reverse (only tmpl-paths)))
                                         tmpl-edn (tree->edn (hid->tree tmpl-hid))]
                                     tmpl-edn)))))]

  ; Given an _id of a step-template we need to return the step-template map.
  (find-step "55099ebdcca58a0c717df921"))

 => {:job-step-template-id "55099ebdcca58a0c717df921", :_id "56044a42a27847d11d61bfd9"}

But if you add this keyword :* an exception is throw.

clojure.lang.ExceptionInfo: validate-attrs: failed attrs=
    tupelo.forest/index: 0
    tupelo.forest/value: :*
(let [job-data  {:_id                  "56044a42a27847d11d61bfc0"
                 :schedule-template-id "55099ebdcca58a0c717df912"
                 :jobs                 [{:job-template-id "55099ebdcca58a0c717df91f"
                                         :_id             "56044a42a27847d11d61bfd5"
                                         :new-field [:*]
                                         :step-templates  [{:job-step-template-id "55099ebdcca58a0c717df921"
                                                            :_id                  "56044a42a27847d11d61bfd9"}
                                                           {:job-step-template-id "55099ebdcca58a0c717df920"
                                                            :_id                  "56044a42a27847d11d61bfd7"}]}]}

      find-step (fn [step-id]
                  (with-forest (new-forest)
                               (let [root-hid   (add-tree (edn->tree job-data))
                                     tmpl-paths (find-paths root-hid [:** {::tf/value step-id}])]
                                 (when (not-empty? tmpl-paths)
                                   (let [tmpl-hid (t/xthird (reverse (only tmpl-paths)))
                                         tmpl-edn (tree->edn (hid->tree tmpl-hid))]
                                     tmpl-edn)))))]

  ; Given an _id of a step-template we need to return the step-template map.
  (find-step "55099ebdcca58a0c717df921"))

Im trying to use tupelo forest on Honeysql data structure:

Ex.: {:select [:*] :from [:foo] :where [:in :foo.a {:select [:a], :from [:bar]}]}

cloojure commented 1 year ago

Hi - I added an example showing the problem and solution beginning on line 1470 of tst.tupelo.forest-examples. The problem is caused since Tupelo Forest uses the keywords ; :* and :** as special wildcard symbols. If your data includes any of these, just temporarily ; encode them into a harmless version, then reverse the process.

;---------------------------------------------------------------------------------------------------
; Example of how to quote/unquote any reserved words in user data.  Tupelo Forest uses the keywords
; `:*` and `:**` as special wildcard symbols.  If your data includes any of these, just  temporarily
; encode them into a harmless version, then reverse the process

(def symbol->code {:*  :quote/kw-star
                   :** :quote/kw-star-star})
(def code->symbol (set/map-invert symbol->code))

(def reserved-symbols-set (set (keys symbol->code)))
(def reserved-codes-set (set (keys code->symbol)))

(defn reserved-symbol?
  "Returns true if arg is a reserved symbol"
  [arg] (t/contains-key? reserved-symbols-set arg))

(defn reserved-code?
  "Returns true if arg is a reserved symbol code"
  [arg] (t/contains-key? reserved-codes-set arg))

(defn reserved-symbol-encode
  "Walk EDN data, encoding any reserved symbols"
  [arg]
  (walk/postwalk (fn [item]
                   (cond-it-> item
                     (reserved-symbol? it) (grab it symbol->code)))
    arg))

(defn reserved-symbol-decode
  "Walk EDN data, decoding any reserved symbol codes"
  [arg]
  (walk/postwalk (fn [item]
                   (cond-it-> item
                     (reserved-code? it) (grab it code->symbol)))
    arg))

(verify
  ; Ensure all symbols and codes don't clash
  (is (apply = true (mapv reserved-symbol? reserved-symbols-set)))
  (is (apply = true (mapv reserved-code? reserved-codes-set)))
  (is (apply = false (mapv reserved-code? reserved-symbols-set)))
  (is (apply = false (mapv reserved-symbol? reserved-codes-set)))

  ; Verify encode/decode functions
  (let [data-orig    {:id            1
                      :problem-field [:* :**]}
        data-encoded {:id 1, :problem-field [:quote/kw-star :quote/kw-star-star]}]
    (is= data-encoded  (reserved-symbol-encode data-orig))
    (is= data-orig (reserved-symbol-decode data-encoded))))