Created
April 28, 2014 15:21
-
-
Save alco/11375287 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Memcachedx.Packet.Parser do | |
@doc """ | |
Parses the response to find the status code and return a friendly message | |
based on it. | |
""" | |
# Those seem to be unused | |
#alias Memcachedx.Packet.Response.Header, as: Header | |
#alias Memcachedx.Packet.Response.Body, as: Body | |
# 1. Organize the code in a top-down approach: | |
# - public functions go first | |
# - helpers functions are defined after the point they are first used | |
def parse_response(message) do | |
Enum.reverse(recur_response(message, [])) | |
end | |
# 2. Define helpers functions and macros for repetitive stuff | |
defmacrop size_bytes(n), do: quote(do: [size(unquote(n)), unit(8)]) | |
defp recur_response("", result), do: result | |
defp recur_response(message, acc) do | |
<< | |
_magic :: size_bytes(1), | |
opcode :: size_bytes(1), | |
key_length :: size_bytes(2), | |
extras_length :: size_bytes(1), | |
_data_type :: size_bytes(1), | |
status :: size_bytes(2), | |
rest :: binary | |
>> = message | |
# 3. Don't put many conditionals in one function. Split into smaller | |
# functions to make it read like prose | |
intermediate_params = [ | |
opcode: symbolic_opcode(opcode), | |
key_length: key_length, | |
extras_length: extras_length, | |
] | |
{params, rest} = extract_params(rest, intermediate_params) | |
# 4. Tail recursion not only reduces memory footprint, | |
# it also helps to avoid conditionals inside function body | |
recur_response(rest, [{symbolic_status(status), params}] ++ acc) | |
end | |
defp extract_params("", params), do: {params, ""} | |
defp extract_params(bin, params) do | |
<< | |
total_body_length :: size_bytes(4), | |
opaque :: size_bytes(4), | |
cas :: size_bytes(8), | |
rest :: binary | |
>> = bin | |
extras_length = params[:extras_length] | |
body_length = total_body_length - extras_length | |
<< | |
extras :: size_bytes(extras_length), | |
body :: [binary, size(body_length)], | |
rest :: binary | |
>> = rest | |
prefinal_params = params ++ [ | |
total_body_length: total_body_length, | |
opaque: opaque, | |
cas: cas, | |
extras: extras | |
] | |
{parse_body_params(prefinal_params, body), rest} | |
end | |
defp parse_body_params(params, body) do | |
key_length = params[:key_length] | |
# 5. Put conditional assignment into one expression to make it clear | |
# that the variable will get one of two values exclusively | |
# Solves problem with getk/getkq having the wrong total_body_length as the | |
# total_body_length should be (extras_length + key_length + value_length) | |
value_length = if params[:opcode] == :getk do | |
params[:total_body_length] - params[:extras_length] | |
else | |
params[:total_body_length] - params[:extras_length] - key_length | |
end | |
# 6. Again, remove conditions by adding functions | |
{key, rest_body} = extract_key(key_length, body) | |
{value, _} = extract_value(value_length, rest_body) | |
params ++ [key: key, value: value] | |
end | |
defp extract_key(0, body), do: {"", body} | |
defp extract_key(length, body) do | |
<< | |
key :: [binary, size(length)], | |
rest :: binary | |
>> = body | |
{key, rest} | |
end | |
defp extract_value(0, body), do: {"", body} | |
defp extract_value(length, body) do | |
<< | |
value :: [binary, size(length)], | |
rest :: binary | |
>> = body | |
{value, rest} | |
end | |
defp symbolic_status(0), do: :ok | |
defp symbolic_status(_), do: :error | |
defp symbolic_opcode(opcode) do | |
case opcode do | |
0x00 -> :get | |
0x01 -> :set | |
0x02 -> :add | |
0x03 -> :replace | |
0x04 -> :delete | |
0x05 -> :incr | |
0x06 -> :decr | |
0x07 -> :quit | |
0x08 -> :flush | |
0x0A -> :noop | |
0x0B -> :version | |
0x0D -> :getkq | |
0x0E -> :append | |
0x0F -> :prepend | |
0x10 -> :stat | |
0x11 -> :setq | |
0x12 -> :addq | |
0x13 -> :replaceq | |
0x14 -> :deleteq | |
0x15 -> :incrq | |
0x16 -> :decrq | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment