defmodule Gemini.Response do @moduledoc """ A response to a Gemini protocol request. """ @enforce_keys [:status, :meta] defstruct [:status, :meta, :body] @type t :: %__MODULE__{ status: integer(), meta: String.t(), body: nil | binary() } @spec parse(data :: binary()) :: {:ok, t()} | {:error, term()} @doc """ Parse a Gemini response from the given data. """ def parse(<>) do status = String.to_integer(status) case parse_meta(rest) do {:error, reason} -> {:error, reason} {:ok, meta, body} -> { :ok, %__MODULE__{ status: status, meta: meta, body: body } } end end def parse(_) do {:error, "Expected Gemini response to begin with two digit status code and space"} end defp parse_meta(data, acc \\ [], length \\ 0) defp parse_meta(<<"\r\n", rest::binary>>, acc, _length) do body = if rest == "" do nil else rest end { :ok, acc |> Enum.reverse() |> :erlang.list_to_binary(), body } end defp parse_meta(_data, _acc, 1024) do {:error, "Expected meta string in Gemini response to be no longer than 1024 bytes"} end defp parse_meta(<>, acc, length) do parse_meta(rest, [c | acc], length + 1) end end