defmodule Uptight.Base do
@moduledoc """
Type wrappers for BaseN representations of binary data.
"""
import Algae
import Witchcraft
import Witchcraft.Foldable
alias Uptight.Binary
alias Uptight.Result
alias Uptight.Result.{Ok, Err}
alias Uptight.Base.{Sixteen, ThirtyTwo, SixtyFour, Urlsafe}
@dialyzer {:no_return, {:new, 0}}
@dialyzer {:no_return, {:new, 1}}
defsum do
defdata Sixteen do
encoded :: String.t()
raw :: binary()
end
defdata ThirtyTwo do
encoded :: String.t()
raw :: binary()
end
defdata SixtyFour do
encoded :: String.t()
raw :: binary()
end
defdata Urlsafe do
encoded :: String.t()
raw :: binary()
end
end
@doc """
Defensive constructor.
## Examples
iex> Uptight.Base.new("0L/Ri9GJIG9sb2xvINGPINCy0L7QtNC40YLQtdC70Ywg0J3Qm9CeIQ==")
%Uptight.Result.Ok{
ok: %Uptight.Base.SixtyFour{
encoded: "0L/Ri9GJIG9sb2xvINGPINCy0L7QtNC40YLQtdC70Ywg0J3Qm9CeIQ==",
raw: "пыщ ololo я водитель НЛО!"
}
}
iex> Uptight.Base.new("0L/Ri9GJIG9sb2xvINGPINCy0L7QtNC40YLQtdC70Ywg0J3Qm9CeIQ==") |> Uptight.Result.is_ok?()
true
"""
@spec new(binary()) :: Result.t()
def new(x), do: Result.new(fn -> new!(x) end)
@spec new!(binary()) :: __MODULE__.t()
def new!(<<x::binary>>) do
# There must be some `ap`/`apply` trick here
%Ok{ok: res} =
[&mk16/1, &mk32/1, &mk_url/1, &mk64/1]
|> left_fold(Err.new([]), fn acc, f ->
case acc do
y = %Ok{} -> y
_ -> f.(x)
end
end)
res
end
@doc """
Perhaps, constructs a representation of a hex string.
## Example
iex> Uptight.Base.mk16("1337C0DE")
%Uptight.Result.Ok{ok: %Uptight.Base.Sixteen{encoded: "1337C0DE", raw: <<19, 55, 192, 222>>}}
"""
@spec mk16(binary()) :: Result.t()
def mk16(x) do
Result.new(fn -> Base.decode16!(x) end)
|> Result.cont(fn res -> %Sixteen{encoded: x, raw: res} end)
end
@spec mk32(binary()) :: Result.t()
def mk32(x) do
Result.new(fn -> Base.decode32!(x) end)
|> Result.cont(fn res -> %ThirtyTwo{encoded: x, raw: res} end)
end
@spec mk64(binary()) :: Result.t()
def mk64(x) do
Result.new(fn -> Base.decode64!(x) end)
|> Result.cont(fn res -> %SixtyFour{encoded: x, raw: res} end)
end
@spec mk_url(binary()) :: Result.t()
def mk_url(x) do
Result.new(fn -> Base.url_decode64!(x) end)
|> Result.cont(fn res -> %Urlsafe{encoded: x, raw: res} end)
end
@spec mk_url!(binary()) :: __MODULE__.Urlsafe.t()
def mk_url!(x) do
mk_url(x) |> Result.from_ok()
end
@spec raw_to_urlsafe(binary) :: Result.t()
def raw_to_urlsafe(<<x::binary>>) do
x |> Base.url_encode64() |> mk_url()
end
@spec binary_to_urlsafe(Binary.t()) :: Result.t()
def binary_to_urlsafe(%Binary{} = x) do
x |> Binary.un() |> raw_to_urlsafe()
end
@spec raw_to_urlsafe!(binary) :: __MODULE__.Urlsafe.t()
def raw_to_urlsafe!(<<x::binary>>) do
raw_to_urlsafe(x) |> Result.from_ok()
end
@spec binary_to_urlsafe!(Binary.t()) :: Urlsafe.t()
def binary_to_urlsafe!(%Binary{} = x) do
x |> Binary.un() |> raw_to_urlsafe!()
end
@spec safe(Uptight.Binary.t()) :: Result.t()
defdelegate safe(binary), to: __MODULE__, as: :binary_to_urlsafe
@spec safe!(Uptight.Binary.t()) :: __MODULE__.Urlsafe.t()
defdelegate safe!(binary), to: __MODULE__, as: :binary_to_urlsafe!
end
require Protocol
defimpl Jason.Encoder, for: Uptight.Base.Sixteen do
@spec encode(Uptight.Base.Sixteen.t(), Jason.Encode.opts()) :: [
binary | maybe_improper_list(any, binary | []) | byte,
...
]
def encode(value, opts) do
Jason.Encode.string(Map.get(value, :encoded), opts)
end
end
defimpl Jason.Encoder, for: Uptight.Base.ThirtyTwo do
@spec encode(Uptight.Base.ThirtyTwo.t(), Jason.Encode.opts()) :: [
binary | maybe_improper_list(any, binary | []) | byte,
...
]
def encode(value, opts) do
Jason.Encode.string(Map.get(value, :encoded), opts)
end
end
defimpl Jason.Encoder, for: Uptight.Base.SixtyFour do
@spec encode(Uptight.Base.SixtyFour.t(), Jason.Encode.opts()) :: [
binary | maybe_improper_list(any, binary | []) | byte,
...
]
def encode(value, opts) do
Jason.Encode.string(Map.get(value, :encoded), opts)
end
end
defimpl Jason.Encoder, for: Uptight.Base.Urlsafe do
@spec encode(Uptight.Base.Urlsafe.t(), Jason.Encode.opts()) :: [
binary | maybe_improper_list(any, binary | []) | byte,
...
]
def encode(value, opts) do
Jason.Encode.string(Map.get(value, :encoded), opts)
end
end