2021-01-22 09:21:58 +13:00
|
|
|
defmodule Ash.CiString do
|
|
|
|
@moduledoc """
|
|
|
|
Represents a case insensitive string
|
|
|
|
|
|
|
|
While some data layers are aware of case insensitive string types, in order for values
|
|
|
|
of this type to be used in other parts of Ash Framework, it has to be embedded in a module
|
|
|
|
this allows us to implement the `Comparable` protocol for it.
|
|
|
|
|
|
|
|
For the type implementation, see `Ash.Type.CiString`
|
|
|
|
"""
|
|
|
|
|
improvement: revamp ci_string
previously, ci_string would downcase all input/output automatically,
throwing away the "representation" of the original input. Now, that
is an option provided as a constraint, for example:
`attribute :email, :ci_string, constraints: [casing: :lower]`
or
`attribute :serial, :ci_string, constraints: [casing: :upper]`
All comparison logic remains the same, so the only thing that is affected
is the `to_string(value)` logic, which now returns based on the configured casing.
By default its just the value, but with `lower`/`upper` it will downcase/upcase the
value accordingly.
2021-06-26 05:19:14 +12:00
|
|
|
defstruct [:string, casted?: false, case: nil]
|
2021-01-22 09:21:58 +13:00
|
|
|
|
2023-02-01 05:15:09 +13:00
|
|
|
@type t :: %__MODULE__{
|
|
|
|
string: String.t(),
|
|
|
|
casted?: boolean(),
|
|
|
|
case: nil | :lower | :upper
|
|
|
|
}
|
|
|
|
|
|
|
|
@doc "Creates a case insensitive string"
|
improvement: revamp ci_string
previously, ci_string would downcase all input/output automatically,
throwing away the "representation" of the original input. Now, that
is an option provided as a constraint, for example:
`attribute :email, :ci_string, constraints: [casing: :lower]`
or
`attribute :serial, :ci_string, constraints: [casing: :upper]`
All comparison logic remains the same, so the only thing that is affected
is the `to_string(value)` logic, which now returns based on the configured casing.
By default its just the value, but with `lower`/`upper` it will downcase/upcase the
value accordingly.
2021-06-26 05:19:14 +12:00
|
|
|
def sigil_i(value, mods) do
|
|
|
|
cond do
|
|
|
|
?l in mods ->
|
|
|
|
new(value, :lower)
|
|
|
|
|
|
|
|
?u in mods ->
|
|
|
|
new(value, :upper)
|
|
|
|
|
|
|
|
true ->
|
|
|
|
new(value)
|
|
|
|
end
|
|
|
|
end
|
2021-01-22 09:21:58 +13:00
|
|
|
|
|
|
|
defimpl Jason.Encoder do
|
2021-06-24 04:51:47 +12:00
|
|
|
def encode(ci_string, opts) do
|
2021-06-24 04:50:13 +12:00
|
|
|
ci_string
|
|
|
|
|> Ash.CiString.value()
|
2021-06-24 04:51:47 +12:00
|
|
|
|> Jason.Encode.string(opts)
|
2021-01-22 09:21:58 +13:00
|
|
|
end
|
2021-01-24 16:28:56 +13:00
|
|
|
end
|
2021-01-22 09:21:58 +13:00
|
|
|
|
2021-01-24 16:28:56 +13:00
|
|
|
defimpl String.Chars do
|
|
|
|
def to_string(ci_string) do
|
|
|
|
Ash.CiString.value(ci_string)
|
2021-01-22 09:21:58 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-01-24 16:28:56 +13:00
|
|
|
defimpl Inspect do
|
|
|
|
import Inspect.Algebra
|
|
|
|
|
|
|
|
def inspect(%Ash.CiString{string: string}, opts) do
|
|
|
|
concat(["#Ash.CiString<", to_doc(string, opts), ">"])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-09-29 10:25:38 +13:00
|
|
|
def new(value, casing \\ nil)
|
|
|
|
|
|
|
|
def new(%__MODULE__{string: value}, casing) do
|
|
|
|
new(value, casing)
|
|
|
|
end
|
|
|
|
|
|
|
|
def new(value, casing) do
|
improvement: revamp ci_string
previously, ci_string would downcase all input/output automatically,
throwing away the "representation" of the original input. Now, that
is an option provided as a constraint, for example:
`attribute :email, :ci_string, constraints: [casing: :lower]`
or
`attribute :serial, :ci_string, constraints: [casing: :upper]`
All comparison logic remains the same, so the only thing that is affected
is the `to_string(value)` logic, which now returns based on the configured casing.
By default its just the value, but with `lower`/`upper` it will downcase/upcase the
value accordingly.
2021-06-26 05:19:14 +12:00
|
|
|
case casing do
|
|
|
|
:upper ->
|
|
|
|
%Ash.CiString{casted?: true, string: value && String.upcase(value)}
|
|
|
|
|
|
|
|
:lower ->
|
|
|
|
%Ash.CiString{casted?: true, string: value && String.downcase(value)}
|
|
|
|
|
|
|
|
nil ->
|
|
|
|
%Ash.CiString{casted?: false, string: value}
|
|
|
|
end
|
2021-01-24 16:28:56 +13:00
|
|
|
end
|
|
|
|
|
improvement: revamp ci_string
previously, ci_string would downcase all input/output automatically,
throwing away the "representation" of the original input. Now, that
is an option provided as a constraint, for example:
`attribute :email, :ci_string, constraints: [casing: :lower]`
or
`attribute :serial, :ci_string, constraints: [casing: :upper]`
All comparison logic remains the same, so the only thing that is affected
is the `to_string(value)` logic, which now returns based on the configured casing.
By default its just the value, but with `lower`/`upper` it will downcase/upcase the
value accordingly.
2021-06-26 05:19:14 +12:00
|
|
|
def value(%Ash.CiString{string: value, casted?: false, case: :lower}) do
|
2021-03-05 16:48:15 +13:00
|
|
|
value && String.downcase(value)
|
2021-01-24 16:28:56 +13:00
|
|
|
end
|
|
|
|
|
improvement: revamp ci_string
previously, ci_string would downcase all input/output automatically,
throwing away the "representation" of the original input. Now, that
is an option provided as a constraint, for example:
`attribute :email, :ci_string, constraints: [casing: :lower]`
or
`attribute :serial, :ci_string, constraints: [casing: :upper]`
All comparison logic remains the same, so the only thing that is affected
is the `to_string(value)` logic, which now returns based on the configured casing.
By default its just the value, but with `lower`/`upper` it will downcase/upcase the
value accordingly.
2021-06-26 05:19:14 +12:00
|
|
|
def value(%Ash.CiString{string: value, casted?: false, case: :upper}) do
|
|
|
|
value && String.upcase(value)
|
|
|
|
end
|
|
|
|
|
|
|
|
def value(%Ash.CiString{string: value}) do
|
2021-01-24 16:28:56 +13:00
|
|
|
value
|
2021-01-22 09:21:58 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
def compare(left, right) do
|
improvement: revamp ci_string
previously, ci_string would downcase all input/output automatically,
throwing away the "representation" of the original input. Now, that
is an option provided as a constraint, for example:
`attribute :email, :ci_string, constraints: [casing: :lower]`
or
`attribute :serial, :ci_string, constraints: [casing: :upper]`
All comparison logic remains the same, so the only thing that is affected
is the `to_string(value)` logic, which now returns based on the configured casing.
By default its just the value, but with `lower`/`upper` it will downcase/upcase the
value accordingly.
2021-06-26 05:19:14 +12:00
|
|
|
do_compare(to_comparable_string(left), to_comparable_string(right))
|
2021-01-22 09:21:58 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
defp do_compare(left, right) when left == right, do: :eq
|
2022-10-04 09:34:45 +13:00
|
|
|
defp do_compare(left, right) when left < right, do: :lt
|
2021-01-22 09:21:58 +13:00
|
|
|
defp do_compare(_, _), do: :gt
|
|
|
|
|
2022-10-04 09:34:45 +13:00
|
|
|
@doc "Returns the downcased value, only downcasing if it hasn't already been done"
|
improvement: revamp ci_string
previously, ci_string would downcase all input/output automatically,
throwing away the "representation" of the original input. Now, that
is an option provided as a constraint, for example:
`attribute :email, :ci_string, constraints: [casing: :lower]`
or
`attribute :serial, :ci_string, constraints: [casing: :upper]`
All comparison logic remains the same, so the only thing that is affected
is the `to_string(value)` logic, which now returns based on the configured casing.
By default its just the value, but with `lower`/`upper` it will downcase/upcase the
value accordingly.
2021-06-26 05:19:14 +12:00
|
|
|
def to_comparable_string(value) when is_binary(value) do
|
2021-01-22 09:21:58 +13:00
|
|
|
String.downcase(value)
|
|
|
|
end
|
|
|
|
|
improvement: revamp ci_string
previously, ci_string would downcase all input/output automatically,
throwing away the "representation" of the original input. Now, that
is an option provided as a constraint, for example:
`attribute :email, :ci_string, constraints: [casing: :lower]`
or
`attribute :serial, :ci_string, constraints: [casing: :upper]`
All comparison logic remains the same, so the only thing that is affected
is the `to_string(value)` logic, which now returns based on the configured casing.
By default its just the value, but with `lower`/`upper` it will downcase/upcase the
value accordingly.
2021-06-26 05:19:14 +12:00
|
|
|
def to_comparable_string(%__MODULE__{case: :lower, casted?: true, string: value}) do
|
|
|
|
value
|
2021-01-22 09:21:58 +13:00
|
|
|
end
|
|
|
|
|
improvement: revamp ci_string
previously, ci_string would downcase all input/output automatically,
throwing away the "representation" of the original input. Now, that
is an option provided as a constraint, for example:
`attribute :email, :ci_string, constraints: [casing: :lower]`
or
`attribute :serial, :ci_string, constraints: [casing: :upper]`
All comparison logic remains the same, so the only thing that is affected
is the `to_string(value)` logic, which now returns based on the configured casing.
By default its just the value, but with `lower`/`upper` it will downcase/upcase the
value accordingly.
2021-06-26 05:19:14 +12:00
|
|
|
def to_comparable_string(%__MODULE__{string: value}) do
|
|
|
|
value && String.downcase(value)
|
2021-01-22 09:21:58 +13:00
|
|
|
end
|
improvement: revamp ci_string
previously, ci_string would downcase all input/output automatically,
throwing away the "representation" of the original input. Now, that
is an option provided as a constraint, for example:
`attribute :email, :ci_string, constraints: [casing: :lower]`
or
`attribute :serial, :ci_string, constraints: [casing: :upper]`
All comparison logic remains the same, so the only thing that is affected
is the `to_string(value)` logic, which now returns based on the configured casing.
By default its just the value, but with `lower`/`upper` it will downcase/upcase the
value accordingly.
2021-06-26 05:19:14 +12:00
|
|
|
|
|
|
|
def to_comparable_string(nil), do: nil
|
2021-01-22 09:21:58 +13:00
|
|
|
end
|
|
|
|
|
2022-10-22 03:46:46 +13:00
|
|
|
import Ash.Type.Comparable
|
2021-01-22 09:21:58 +13:00
|
|
|
|
2021-01-24 16:28:56 +13:00
|
|
|
defcomparable left :: Ash.CiString, right :: BitString do
|
2021-01-22 09:21:58 +13:00
|
|
|
Ash.CiString.compare(left, right)
|
|
|
|
end
|
|
|
|
|
|
|
|
defcomparable left :: Ash.CiString, right :: Ash.CiString do
|
|
|
|
Ash.CiString.compare(left, right)
|
|
|
|
end
|