lambdaisland

plexus 2020-04-01T12:44:52.053700Z

I added functions to lambdaisland/uri for dealing with query strings, would love feedback on this. I'm holding off on an official release so we're still able to change things. https://github.com/lambdaisland/uri/blob/master/src/lambdaisland/uri.cljc#L124-L234

plexus 2020-04-01T12:45:45.054800Z

lambdaisland/uri never had much of an opinion on query strings because the RFC doesn't have much of an opinion on them. It's just a string, what you do with it is up to you. The typical ?foo=bar&aaa=bbb key value format is just a common convention

plexus 2020-04-01T12:46:29.055600Z

but it's become so common that it makes sense to have first class support for it. There are some lower level functions now for parsing this stuff, but the main entry points for most use cases should be query-map and assoc-query

plexus 2020-04-01T12:48:49.055800Z

(query-map "<http://example.com?foo=bar&amp;aaa=bbb>")
;; =&gt; {:foo "bar", :aaa "bbb"}

(query-map "<http://example.com?foo=bar&amp;aaa=bbb>" {:keywordize? false})
;; =&gt; {"foo" "bar", "aaa" "bbb"}

(assoc-query "<http://example.com?foo=bar&amp;aaa=bbb>"
             :foo "baq"
             :hello "world")
;;=&gt; #lambdaisland/uri "<http://example.com?foo=baq&amp;aaa=bbb&amp;hello=world>"

plexus 2020-04-01T12:49:30.056300Z

There's also a version of assoc-query that takes a map instead of key-value pairs

plexus 2020-04-01T12:49:56.056500Z

(assoc-query* "<http://example.com?foo=bar&amp;aaa=bbb>"
              {:foo "baq"
               :hello "world"})
;;=&gt; #lambdaisland/uri "<http://example.com?foo=baq&amp;aaa=bbb&amp;hello=world>"

plexus 2020-04-01T12:51:20.057600Z

One question of potential contention is how to deal with something like ?id=1&amp;id=2, for instance ring-code will automatically turn these into a vector of values, which is the default for query-map as well (ring-code: https://github.com/ring-clojure/ring-codec/blob/b01fcee9ffe35da85eeeb555ebecb24414d7d9a6/src/ring/util/codec.clj#L9)

plexus 2020-04-01T12:51:54.057900Z

(query-map "?id=1&amp;id=2")
;; =&gt; {:id ["1" "2"]}

plexus 2020-04-01T12:52:28.058600Z

This kind of makes sense, but it is actually a pain for consumers, because now you need to check every time if what you're getting back is a collection or a scalar

plexus 2020-04-01T12:53:06.059200Z

So you can actually pick three strategies, :never, :always, :duplicates

plexus 2020-04-01T12:54:12.059700Z

so at least you get consistent results (:duplicates is the default)

(query-map "?id=1&amp;id=2" {:multikeys :never})
;; =&gt; {:id "2"}

(query-map "?foo=bar&amp;id=2" {:multikeys :always})
;; =&gt; {:foo ["bar"], :id ["2"]}

plexus 2020-04-01T12:54:51.060200Z

Similarly assoc-query is able to round-trip this

plexus 2020-04-01T12:56:34.060700Z

it will check if something is a coll?, and if so split it out into multiple entries

(assoc-query* "" (query-map "?id=1&amp;id=2"))
;;=&gt; #lambdaisland/uri "?id=1&amp;id=2"

plexus 2020-04-01T12:56:59.061300Z

Another design decision in assoc-query is that nil values are ignored. This way you can use nil as a kind of dissoc

plexus 2020-04-01T12:57:37.061500Z

(assoc-query "?id=1&amp;name=jack" :name nil)
;;=&gt; #lambdaisland/uri "?id=1"

plexus 2020-04-01T12:58:51.062700Z

I think that's all fairly elegant as API, what is completely missing is support for a convention that is quite common in some places e.g. the rails world, of using [] as indexes, so e.g. ?user[name]=jack would become {:user {:name "jack"}}

plexus 2020-04-01T12:59:26.063300Z

in this style it is explicit if something should be a collection of values by using ?id[]=1&amp;id[]=2

Ben Sless 2020-04-01T13:42:10.065900Z

I got 2c to throw in regarding performance since I had to play with optimizing uri generation for some use case: You're using several intermediate collections and a ton of StringBuilders (every call to string) It's way better to have one string builder and use a reducing function into it with a transducer. It's even faster if you recycle the string builder between calls. What I did was instance a thread-local SB and clear it after getting the string out of it. It also reduces GC pressure.

dominicm 2020-04-01T14:07:34.066300Z

I like the design. You ticked the things I think about in context.

plexus 2020-04-01T14:34:21.068100Z

@ben.sless I agree there's room for optimization, but that can happen without breaking the API. I'd like to focus on the API design right now, since for most people this is not their bottleneck, but PRs with performance improvements are of course very welcome!

👍 1