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

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

LegitStack
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it. I’m very interested in Elixir,...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
Kagamiiiii
Student &amp; New to elixir. Nice language. I want to convert a english character, e.g. “a”, which is stored in a variable, to it’s asci...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
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
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

Other popular topics Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
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
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
magnetic
Hey :wave:t3: Elixir community, I’ve been learning Elixir, and working on some side projects. My editor of choice is VSCode, and althoug...
New

We're in Beta

About us Mission Statement