Pattern Matching Maps in Elixir
Exercise: Rewriting
URLs are commonly rewritten to make them prettier or to improve a page’s SEO. Just for practice, add a function to the pipeline that rewrites requests for /bears?id=1 to /bears/1, /bears?id=2 to /bears/2, and so on. Here’s an example request:
request = """
GET /bears?id=1 HTTP/1.1
Host: example.com
User-Agent: ExampleBrowser/1.0
Accept: */*
"""Solution:
def rewrite_path(%{path: "/bears?id=" <> id} = conv) do
%{ conv | path: "/bears/#{id}" }
endExercise: Emojify Worthy Responses
Just for fun, suppose you want your web server to decorate all responses that have a 200 status with emojis before and after the actual content. Write an emojify function that emojifies those responses and plug that function into the pipeline.
Solution:
def emojify(%{status: 200} = conv) do
emojies = String.duplicate("🎉", 5)
body = emojies <> "\n" <> conv.resp_body <> "\n" <> emojies
%{ conv | resp_body: body }
end
def emojify(conv), do: convYou’ll also need to change how the “Content-Length” of the response is computed. It needs to be the number of bytes (not characters) in the response. Use byte_size/1 for this purpose:
"""
Content-Length: #{byte_size(conv.resp_body)}
"""Exercise: Handle DELETE Differently
Change the DELETE route you created in a previous exercise to pattern match on the conv map, rather than taking three arguments.
Solution:
def route(%{method: "DELETE", path: "/bears/" <> _id} = conv) do
%{ conv | status: 403, resp_body: "Bears must never be deleted!" }
endThought Experiment
Can you think of other useful functions to add to the pipeline, either to modify the request before it’s routed or modify the response before it’s sent back? You may need to consider additional fields in the conversation map that are affected by the functions.
Alternative Approach Using Regular Expressions
In the first exercise above, you rewrote requests for /bears?id=1. Now suppose you want to do the same for lions, tigers, and so on, but rather than writing separate rewrite_path function clauses for each wildthing, you want to handle them all in one generic function.
Solution using regular expressions:
def rewrite_path(%{path: path} = conv) do
regex = ~r{\/(?<thing>\w+)\?id=(?<id>\d+)}
captures = Regex.named_captures(regex, path)
rewrite_path_captures(conv, captures)
end
def rewrite_path_captures(conv, %{"thing" => thing, "id" => id}) do
%{ conv | path: "/#{thing}/#{id}" }
end
def rewrite_path_captures(conv, nil), do: convThis function uses regular expressions to capture thing and id values and rewrite the path accordingly. If no captures are found, the path remains unchanged.
Open-Ended Exercise: Use the Logger
In the video, we tracked a 404 by printing a warning to the console. To get a tad fancier, you can use the built-in Logger module, which supports various levels of logging and a wide range of configuration options.
To use Logger in your code, first require it:
require LoggerThen, you can call logging functions, such as:
Logger.info "It's lunchtime somewhere."
Logger.warn "Do we have a problem, Houston?"
Logger.error "Danger Will Robinson!"Experiment with different logging levels, but don’t forget to return to the main course!
Code So Far
The code for this section is in the rewriting directory found within the video-code directory of the code bundle
Why always me?