0
votes

I have the following function working in an Elixir app:

  def emojify(%Conv{status: 200, resp_headers: resp_headers} = conv) do
    body = if resp_headers["Content-Type"] == "application/json", do: conv.resp_body, else: "????" <> "\n\n" <> conv.resp_body <> "\n\n" <> "????"
  
    %{ conv | resp_body: body }
  end

  def emojify(%Conv{} = conv), do: conv

I'd like to refactor it to eliminate the need for the if inside it. I imagine that the way to do it is through pattern matching the content-type inside the resp_headers, but that would be pattern matching of a map inside a map (a struct inside a map to be more precise, but I don't think this detail is relevant in this case), and I'm not sure how to do that.

I was trying something like def emojify(%Conv{status: 200, resp_headers: %{"Content-Type" => "application/json"}} = conv), but I understand that this would only work if resp_headers has only "Content-Type" => "application/json" inside it, and that isn't the case. So how could I accomplish this?

2
Please remember, that HTTP headers usually are case insensitive and also can occur multiple times. Therefore the libarary you are using might actually not use a map at all for the headers but wrap them in a struct that implements Access. - NobbZ
For now, I'm just building things from scratch to understand the "Elixir way", so I'm not using a library and it's not an app for production. But good to know, I'll remember! - rwehresmann

2 Answers

3
votes

I understand that this would only work if resp_headers has only "Content-Type" => "application/json" inside it

This assumption is wrong. The map matches if the fields in the match are presented in the input and all pinned variables are matched. You have no pinned variables, so you are OK.

Match to the empty map is frequently used instead of is_map(map) guard as foo(%{} = map) do ...

%{} = %{foo: 42, bar: :baz} # success

# and also
%{foo: foo} = %{foo: 42, bar: :baz} # success
foo
#⇒ 42

That said, the answer would be

def emojify(%Conv{
    status: 200,
    resp_headers: %{"Content-Type" => "application/json"}} = conv),
  do: %{ conv | resp_body: conv.resp_body }

def emojify(%Conv{status: 200} = conv),
  do: %{ conv | resp_body: "🎉" <> "\n\n" <> conv.resp_body <> "\n\n" <> "🎉"}

def emojify(%Conv{} = conv), do: conv

Sidenote: example with pinned variables.

%{foo: 42} = %{foo: 42, bar: :baz} # success

foo = 42
%{foo: ^foo} = %{foo: 42, bar: :baz} # success

%{foo: ^foo} = %{foo: :incorrect, bar: :baz} # raises
1
votes

You can pattern-match like so

def emojify( %{status: 200, resp_headers: %{"Content-Type" => "application/json" } } = conv
) do
    %{ conv | resp_body: conv.resp_body }    
   end
   def emojify(%{status: 200} = conv) do
    %{ conv | resp_body: "🎉" <> "\n\n" <> conv.resp_body <> "\n\n" <> "🎉" }
   end
   def emojify(conv), do: conv

You can always pattern-match values of specific keys in a map or a struct

For more check this out joyofelixir.