# Copyright(c) 2015-2023 ACCESS CO., LTD. All rights reserved.
use Croma
defmodule Antikythera.Http do
defmodule Method do
use Croma.SubtypeOfAtom,
values: [:get, :post, :put, :patch, :delete, :options, :connect, :trace, :head]
@all [:get, :post, :put, :patch, :delete, :options, :connect, :trace, :head]
def all(), do: @all
@spec from_string(String.t()) :: t
@spec to_string(t) :: String.t()
for method <- @all do
method_str = method |> Atom.to_string() |> String.upcase()
def from_string(unquote(method_str)), do: unquote(method)
def to_string(unquote(method)), do: unquote(method_str)
end
end
defmodule QueryParams do
use Croma.SubtypeOfMap, key_module: Croma.String, value_module: Croma.String, default: %{}
end
defmodule Headers do
@moduledoc """
HTTP headers as a map.
If multiple headers in a single request/response have the same header name,
their values are concatenated with commas.
In case of `cookie` header values are concatenated using semicolons instead of commas.
"""
use Croma.SubtypeOfMap, key_module: Croma.String, value_module: Croma.String, default: %{}
end
defmodule SetCookie do
alias Croma.TypeGen
use Croma.Struct,
recursive_new?: true,
fields: [
value: Croma.String,
path: TypeGen.nilable(Antikythera.EncodedPath),
domain: TypeGen.nilable(Antikythera.Domain),
secure: TypeGen.nilable(Croma.Boolean),
http_only: TypeGen.nilable(Croma.Boolean),
max_age: TypeGen.nilable(Croma.Integer)
]
@type options_t :: %{
optional(:path) => Antikythera.EncodedPath.t(),
optional(:domain) => Antikythera.Domain.t(),
optional(:secure) => boolean,
optional(:http_only) => boolean,
optional(:max_age) => non_neg_integer
}
defun parse!(s :: v[String.t()]) :: {String.t(), t} do
[pair | attrs] = String.split(s, ~R/\s*;\s*/)
[name, value] = String.split(pair, "=", parts: 2)
cookie =
Enum.reduce(attrs, %__MODULE__{value: value}, fn attr, acc ->
case attr_to_opt(attr) do
nil -> acc
{opt_name, opt_value} -> Map.put(acc, opt_name, opt_value)
end
end)
{name, cookie}
end
defp attr_to_opt(attr) do
[name | rest] = String.split(attr, ~R/\s*=\s*/, parts: 2)
case String.downcase(name) do
"path" -> {:path, hd(rest)}
"domain" -> {:domain, hd(rest)}
"secure" -> {:secure, true}
"httponly" -> {:http_only, true}
"max-age" -> {:max_age, String.to_integer(hd(rest))}
# version, expires or comment attribute
_ -> nil
end
end
end
defmodule SetCookiesMap do
use Croma.SubtypeOfMap, key_module: Croma.String, value_module: SetCookie, default: %{}
end
defmodule ReqCookiesMap do
use Croma.SubtypeOfMap, key_module: Croma.String, value_module: Croma.String, default: %{}
end
defmodule RawBody do
@type t :: binary
defun valid?(v :: term) :: boolean, do: is_binary(v)
def default(), do: ""
end
defmodule Body do
@type t :: binary | [any] | %{String.t() => any}
defun valid?(v :: term) :: boolean do
is_binary(v) or is_map(v) or is_list(v)
end
def default(), do: ""
end
defmodule Status do
statuses = [
continue: 100,
switching_protocols: 101,
processing: 102,
ok: 200,
created: 201,
accepted: 202,
non_authoritative_information: 203,
no_content: 204,
reset_content: 205,
partial_content: 206,
multi_status: 207,
already_reported: 208,
multiple_choices: 300,
moved_permanently: 301,
found: 302,
see_other: 303,
not_modified: 304,
use_proxy: 305,
reserved: 306,
temporary_redirect: 307,
permanent_redirect: 308,
bad_request: 400,
unauthorized: 401,
payment_required: 402,
forbidden: 403,
not_found: 404,
method_not_allowed: 405,
not_acceptable: 406,
proxy_authentication_required: 407,
request_timeout: 408,
conflict: 409,
gone: 410,
length_required: 411,
precondition_failed: 412,
request_entity_too_large: 413,
request_uri_too_long: 414,
unsupported_media_type: 415,
requested_range_not_satisfiable: 416,
expectation_failed: 417,
unprocessable_entity: 422,
locked: 423,
failed_dependency: 424,
upgrade_required: 426,
precondition_required: 428,
too_many_requests: 429,
request_header_fields_too_large: 431,
internal_server_error: 500,
not_implemented: 501,
bad_gateway: 502,
service_unavailable: 503,
gateway_timeout: 504,
http_version_not_supported: 505,
variant_also_negotiates: 506,
insufficient_storage: 507,
loop_detected: 508,
not_extended: 510,
network_authentication_required: 511
]
defmodule Atom do
use Croma.SubtypeOfAtom, values: Keyword.keys(statuses)
end
defmodule Int do
use Croma.SubtypeOfInt, min: 100, max: 999
end
@type t :: Atom.t() | Int.t()
defun valid?(v :: term) :: boolean do
Int.valid?(v) or Atom.valid?(v)
end
@spec code(Int.t() | atom) :: Int.t()
def code(int) when int in 100..999 do
int
end
for {atom, code} <- statuses do
def code(unquote(atom)), do: unquote(code)
end
end
end