camcaine

camcaine

Merging URI query strings

URI.merge/2 overwrites the query field. I’m assuming this is by design/spec?

Right now I’m appending query params like so:

Map.update(uri, :query, nil, fn 
  q when is_binary(q) -> q <> "&" <> URI.encode_query(params)
  _ -> URI.encode_query(params)
end)

Am I missing a different way of merging query strings?
Thanks!!

Most Liked Responses

peerreynders

peerreynders

Correct: 5.2.2. Transform References

               if defined(R.query) then
                  T.query = R.query;
               else
                  T.query = Base.query;
               endif;

i.e. the official algorithm uses the Base URI query parameters only if the Reference URI doesn’t have any query parameters - otherwise the Reference URI query parameters are used while the ones from the Base are ignored.

Am I missing a different way of merging query strings?

I’d be inclined to manage this situation a bit differently:

iex(1)> uri = "http://example.com/path/to/page?name=ferret&color=purple" |> URI.parse()
%URI{
  authority: "example.com",
  fragment: nil,
  host: "example.com",
  path: "/path/to/page",
  port: 80, 
  query: "name=ferret&color=purple",
  scheme: "http",
  userinfo: nil
}
iex(2)> query_decoded = URI.decode_query(uri.query)                         # use a Map instead of string
%{"color" => "purple", "name" => "ferret"}
iex(3)> my_uri = uri |> Map.from_struct() |> Map.put(:query, query_decoded) # Use a Map instead of URI struct 
%{
  authority: "example.com",
  fragment: nil,
  host: "example.com",
  path: "/path/to/page",
  port: 80,
  query: %{"color" => "purple", "name" => "ferret"},
  scheme: "http",
  userinfo: nil
}
iex(4)> query_key = Access.key(:query, %{})                                 # returns ":query" value or empty map if missing
#Function<4.127282935/3 in Access.key/2>
iex(5)> query_merge = fn uri, params ->
...(5)>    merge_query_params = &(Map.merge(&1, params))                    # params is a map to accept more than one param; last name/value wins for duplicate names
...(5)>    update_in(uri, [query_key], merge_query_params)
...(5)> end
#Function<12.127694169/2 in :erl_eval.expr/5>
iex(6)> no_query = Map.delete(my_uri, :query)                               # remove existing query params
%{
  authority: "example.com",
  fragment: nil,
  host: "example.com",
  path: "/path/to/page",
  port: 80,
  scheme: "http",
  userinfo: nil
}
iex(7)> my_weasel = query_merge.(no_query, %{name: "weasel"})               # add the first query params
%{
  authority: "example.com",
  fragment: nil,
  host: "example.com",
  path: "/path/to/page",
  port: 80,
  query: %{name: "weasel"},
  scheme: "http",
  userinfo: nil
}
iex(8)> my_green = query_merge.(my_weasel, %{color: "green"})               # append more query params
%{
  authority: "example.com",
  fragment: nil,
  host: "example.com",
  path: "/path/to/page",
  port: 80,
  query: %{color: "green", name: "weasel"},
  scheme: "http",
  userinfo: nil
}
iex(9)> query_encoded = URI.encode_query(my_green.query)                    # put it all together at the latest possible moment
"color=green&name=weasel"
iex(10)> new_my_uri = Map.put(my_green, :query, query_encoded)
%{
  authority: "example.com",
  fragment: nil,
  host: "example.com",
  path: "/path/to/page",
  port: 80,
  query: "color=green&name=weasel",
  scheme: "http",
  userinfo: nil
}
iex(11)> new_uri = struct(URI, new_my_uri)                                   # dump map contents into URI struct
%URI{
  authority: "example.com",
  fragment: nil,
  host: "example.com",
  path: "/path/to/page",
  port: 80, 
  query: "color=green&name=weasel",
  scheme: "http",
  userinfo: nil
}
iex(12)> URI.to_string(new_uri)
"http://example.com/path/to/page?color=green&name=weasel" 
iex(13)> 
peerreynders

peerreynders

Fair point.

The high level takeaway should be that while URI contains “Utilities for working with URIs”, it may not perfectly suit your needs. So it would make sense to create your own module (UriParts?) and perhaps struct that supports the way you need it to operate - while at the same time internally making full use of the URI module whenever possible (and defdelegate/2 when appropriate).

Having

# uri_parts = UriParts.parse(uri_string)
# uri_parts = UriParts.from_uri(uri)

uri_parts = UriParts.query_merge(uri_parts, params)

# uri_string = UriParts.to_string(uri_parts)
# uri = UriParts.to_uri(uri_parts)

is preferable to having

Map.update(uri, :query, nil, fn 
  q when is_binary(q) -> q <> "&" <> URI.encode_query(params)
  _ -> URI.encode_query(params)
end)

scattered all over the codebase.

Where Next?

Popular in Questions Top

gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
nobody
How to bind a phoenix app to a specific ip address? could not find anything about that, nowhere, unfortunately, but for me this is quite...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
marick
I had some trouble figuring out how to make many-to-many associations work. Once I got it working, I wrote a blog post. Because I’m a nov...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Other popular topics Top

albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New

We're in Beta

About us Mission Statement