Fork me on GitHub
Ben Lieberman00:02:45

I'm a total ignoramus when it comes to Babashka, so this is almost definitely a really dumb question. But how do I use external deps? It looks to me like the role of Babashka in Biff is the various tasks? And that I should be able to add any deps of my choosing to deps.edn like normal? Which I tried but I'm getting errors when trying to use the Cognitect AWS API.

Jacob O'Bryant02:02:36

yep, babashka is used for the tasks like bb dev. for babashka deps, you can add them to tasks/deps.edn. deps.edn is for the main app. The aws issue is most likely due to this: as mentioned there, you can fix it by downgrading the Jetty adapter. if you happen to be using web sockets, you'll need to set them up differently from what's demonstrated in biff's example app.

Ben Lieberman02:02:30

Crap, should've checked issues! I was digging through the AWS API repo to no effect! Thanks @U7YNGKDHA 🙏:skin-tone-2:

Epidiah Ravachol15:02:47

I've got a bit of a knot to untangle. There's this vector [:user/email :asc] and this set {:admin :player :editor :tester} that need to make a round-trip through a string in the hx-vals attributes on some buttons, out to the client for clicking, back to the server as JSON, and into a database query as a vector and a set. The contents of these collections is different for each button. Taking a cue from how biff delivers the csrf-token, I used cheshire/generate-string on a map like this:

{:limit 5
 :offset 0
 :sort-order [:user/email :asc]
 :filter-by :user/roles
 :filter-set #{:admin :player :editor :tester}}
Which gives me something like this for the hx-vals attribute:
When it gets back to the server, some stuff happens, in Reitit, I believe, and eventually the :params key of the req map has something that looks like this for me.
{:limit "5"
 :offset "0"
 :sort-order ["user/email" "asc"]
 :filter-by "user/roles"
 :filter-set ["admin" "player" "editor" "tester"]}
So I made a little middleware function that uses malli/decode to coerce that into my original map. I must admit, I was a little proud of this solution. And this hubris of mine clearly angered the gods. For it worked, and it worked really well. It forced me to make a nice schema for my data, which kept me from straying from the path. And with the help of the malli.transform/default-value-transformer I could put default values in this schema and keep them all in one place instead of scattershot throughout the functions just to make sure they got what they needed. But then tragedy struck! If I wanted to filter on :admin alone, I'd have a hx-vals attribute with something like '{\"filter-set\":[\"admin\"]}' and that will go through to the :params key as {:filter-set "admin"}! A lone string, instead of a vector of one string. It all comes tumbling down here, because Malli won't coerce that into a set of a single keyword. There's a couple solutions I can think of here. For example, I've discovered that if a JSON array of a single item has a trailing comma after that item, it comes through to the :params key as a vector of a single string I can work with. But I'm not to keen on inserting those commas or depending on them always working. Or I think I can write some custom code for malli/decode that can be tucked into the schema and will handle this edge case. But I feel like I'm approaching this problem the wrong way. Maybe there's something I'm missing here. Is there a bit of configuration I can do that will change this behavior of turning JSON arrays of single items into lone strings? Or a cleaner way to handle this data?

Epidiah Ravachol19:02:22

I have an awkward solution for the moment.

  {:default #{:admin :player :editor :tester}
   :decode/string {:leave #(if (set? %) % (hash-set (keyword %)))}}
    [:set keyword?]]
Adding that :decode/string {:leave … } bit to the schema seems to handle the issue. But I'm not confident about it.

Jacob O'Bryant21:02:05

huh, that is some interesting behavior from malli. I wonder if that's some sort of heuristic for dealing with query params (e.g. if you have a url with .../?foo=a&foo=b then you'd want to get {:foo ["a" "b"]}, but if you have just .../?foo=a then you'd want {:foo "a"}. I would sidestep this whole issue by encoding the map to edn first and then if needed wrap that in json:

;; Option 1
[:input {:type "hidden"
         :name "query-opts"
         :value (pr-str {:limit 5 :offset 0 ...})}]

;; Option 2
[:button {:hx-vals (cheshire.core/generate-string
                    {:query-opts (pr-str {:limit 5 :offset 0 ...})})}]

;; handler is the same for either option
(defn handler [{:keys [params]}]
  (let [query-opts (clojure.edn/read-string (:query-opts params))]

Epidiah Ravachol21:02:02

Okay, this is a solution I can get behind. Whew.

👌 2