feat(resp): add resp2 protocol parser
This commit is contained in:
1 parent
456e822464
commit
243c5b1b6c
1 file changed
+92
@@ -0,0 +1,92 @@
|
||||
defmodule Excache.RESP.Parser do
|
||||
@moduledoc false
|
||||
|
||||
@type result :: {:ok, [binary], binary} | :incomplete | {:error, String.t()}
|
||||
|
||||
@spec parse_command(binary) :: result
|
||||
def parse_command(<<>>), do: :incomplete
|
||||
def parse_command(<<"*", _::binary>> = buffer), do: parse_multibulk(buffer)
|
||||
def parse_command(buffer), do: parse_inline(buffer)
|
||||
|
||||
defp parse_inline(buffer) do
|
||||
case read_line(buffer) do
|
||||
:incomplete -> :incomplete
|
||||
{:ok, line, rest} -> {:ok, tokenize(line), rest}
|
||||
end
|
||||
end
|
||||
|
||||
defp tokenize(line) do
|
||||
line
|
||||
|> String.split([" ", "\t"], trim: true)
|
||||
end
|
||||
|
||||
defp parse_multibulk(buffer) do
|
||||
case read_line(buffer) do
|
||||
:incomplete ->
|
||||
:incomplete
|
||||
|
||||
{:ok, <<"*", digits::binary>>, rest} ->
|
||||
case Integer.parse(digits) do
|
||||
{count, ""} when count <= 0 -> {:ok, [], rest}
|
||||
{count, ""} -> parse_bulks(count, rest, [])
|
||||
_ -> {:error, "invalid multibulk length"}
|
||||
end
|
||||
|
||||
{:ok, _other, _rest} ->
|
||||
{:error, "expected multibulk header"}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_bulks(0, rest, acc), do: {:ok, Enum.reverse(acc), rest}
|
||||
|
||||
defp parse_bulks(remaining, buffer, acc) do
|
||||
case read_line(buffer) do
|
||||
:incomplete ->
|
||||
:incomplete
|
||||
|
||||
{:ok, <<"$", digits::binary>>, rest} ->
|
||||
case Integer.parse(digits) do
|
||||
{len, ""} when len >= 0 -> take_bulk(len, rest, remaining, acc)
|
||||
{-1, ""} -> parse_bulks(remaining - 1, rest, [nil | acc])
|
||||
_ -> {:error, "invalid bulk length"}
|
||||
end
|
||||
|
||||
{:ok, _other, _rest} ->
|
||||
{:error, "expected bulk header"}
|
||||
end
|
||||
end
|
||||
|
||||
defp take_bulk(len, rest, remaining, acc) do
|
||||
needed = len + 2
|
||||
|
||||
cond do
|
||||
byte_size(rest) < needed ->
|
||||
:incomplete
|
||||
|
||||
binary_part(rest, len, 2) == "\r\n" ->
|
||||
data = binary_part(rest, 0, len)
|
||||
tail = binary_part(rest, needed, byte_size(rest) - needed)
|
||||
parse_bulks(remaining - 1, tail, [data | acc])
|
||||
|
||||
true ->
|
||||
{:error, "bulk terminator missing"}
|
||||
end
|
||||
end
|
||||
|
||||
defp read_line(buffer) do
|
||||
case :binary.split(buffer, "\n") do
|
||||
[line, rest] -> {:ok, strip_cr(line), rest}
|
||||
[_only] -> :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
defp strip_cr(line) do
|
||||
size = byte_size(line)
|
||||
|
||||
if size > 0 and binary_part(line, size - 1, 1) == "\r" do
|
||||
binary_part(line, 0, size - 1)
|
||||
else
|
||||
line
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user