Concatenation of Strings fails with {ArgumentError}

Hey, guys,

I am trying to write a simple module that prepares a valid URL link. The URL needs a checksum param passed. The checksum calculations is very simple: concat all the values of the passed params(key + value) add salt and hash it.

Here is the sample* code(lets ignore for the moment that I need some kind of ordered map, cause there is no guarantee that the map is iterated in the same order. ScRequest is a struct containing the secret_key and the hashish algorithm):

defp calculate_v4_checksum(sc_request, params) do
  # this fails: str_to_hash = Enum.map(params, fn {k, v} -> Atom.to_string(k) <> v end) <> sc_request.secret_key
  str_to_hash = Enum.map(params, fn {k, v} -> Atom.to_string(k) <> v end)
  IO.puts (str_to_hash)
  IO.puts (sc_request.secret_key)
  # this fails: IO.puts (str_to_hash <> sc_request.secret_key)
  # or this: :crypto.hash(sc_request.algorithm, str_to_hash <> sc_request.secret_key)
  :crypto.hash(sc_request.algorithm, str_to_hash)
  |> Base.encode16(case: :lower)
end

Why the concatenation of the salt(secret_key) fails with ArgumentError ? Currently I can only hash the concatenated params…

Of course it does. All (well, most) Enum functions return a list. You can not use <> on Lists.

If I understand you correctly, you want this:

str_to_hash = params
|> Enum.map({k, v} -> "#{k}#{v}" end)
|> Enum.concat([sc_request.secret_key])
|> Enum.join

(untested)

2 Likes

Thank you, NobbZ, that is exactly what I wanted.

Added just the fn keyword and the function now looks like this:

  defp calculate_v4_checksum(sc_request, params) do
	str_to_hash = params
				  |> Enum.map(fn {k, v} -> "#{k}#{v}" end)
				  |> Enum.concat([sc_request.secret_key])
				  |> Enum.join
	:crypto.hash(sc_request.algorithm, str_to_hash)
	|> Base.encode16(case: :lower)
  end

But back to my original question: why this fails(aren’t both values strings ?):

IO.puts (str_to_hash <> sc_request.secret_key)

PS: You can simulate your ordered map roughly like this (and also do it single pipe):

params
|> Enum.sort()
|> Enum.map(fn {k, v} -> "#{k}#{v}" end)
|> Enum.concat([sc_request.secret_key])
|> Enum.join
|> (&:crypto.hash(sc_request.algorithm, &1)).()
|> Base.encode16(case: :lower)
1 Like

As I already said in my first answer, Enum.map returns a list, due to the function you passed into it, that was a list of String.t, but a list of strings is not a string. <> does really only work for binaries (and therefore for strings as well).

1 Like