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

hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
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
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
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
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
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
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

Other popular topics Top

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
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
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
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
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 44167 214
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

We're in Beta

About us Mission Statement