# Copyright 2021 Nicolas Jouanin # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. defmodule Polyjuice.Util.Identifiers.V1.UserIdentifier do @type t :: %__MODULE__{ sigil: String.t(), localpart: String.t(), domain: String.t() } alias Polyjuice.Util.Identifiers.V1.UserIdentifier @enforce_keys [:sigil, :localpart, :domain] defstruct [ :sigil, :localpart, :domain ] @sigil "@" @localpart_regex ~r|^[a-z0-9\-\.\=\_\/]+$| @max_length 255 @behaviour Polyjuice.Util.Identifiers @randomize_length 15 def generate(domain) do {:ok, user} = Polyjuice.Util.Randomizer.randomize(@randomize_length, :downcase_numeric) |> Polyjuice.Util.Identifiers.V1.UserIdentifier.new(domain) user end def new!(identifier) do case parse(identifier) do {:ok, user} -> user {:error, error} -> raise ArgumentError, error end end @impl Polyjuice.Util.Identifiers def new([localpart, domain]), do: new(localpart, domain) @impl Polyjuice.Util.Identifiers def new({localpart, domain}), do: new(localpart, domain) @impl Polyjuice.Util.Identifiers def new(%{localpart: localpart, domain: domain}), do: new(localpart, domain) @impl Polyjuice.Util.Identifiers def new(identifier), do: parse(identifier) @spec new(String.t(), String.t()) :: {:ok, UserIdentifier.t()} | {:error, String.t()} def new(localpart, domain) do user = %UserIdentifier{sigil: @sigil, localpart: localpart, domain: domain} if valid?(user) do {:ok, user} else {:error, :invalid_user_id} end end @impl Polyjuice.Util.Identifiers def valid?(%UserIdentifier{} = this) do Regex.match?(@localpart_regex, this.localpart) && String.length(fqid(this)) < @max_length && String.length(this.domain) > 0 && String.length(this.localpart) > 0 end @impl Polyjuice.Util.Identifiers def valid?(identifier_string) do case parse(identifier_string) do {:ok, _} -> true _ -> false end end def fqid(%UserIdentifier{} = this) do "#{@sigil}#{this.localpart}:#{this.domain}" end @impl Polyjuice.Util.Identifiers @spec parse(String.t()) :: {:ok, UserIdentifier.t()} | {:error, String.t()} def parse(id) do with @sigil <> id <- id do [localpart, domain] = String.split(id, ":", parts: 2, trim: true) new(localpart, domain) end end end defimpl String.Chars, for: Polyjuice.Util.Identifiers.V1.UserIdentifier do def to_string(this), do: Polyjuice.Util.Identifiers.V1.UserIdentifier.fqid(this) end