lib/gpp/sections/tcf.ex

defmodule Gpp.Sections.Tcf do
  @moduledoc """
  EU IAB Transparency and Consent Framework v1.1 & v2.0 String Decoding.

  Only decodes the "version" and the "vendor consents" parts of the "core" segment.
  """
  alias Gpp.BitUtil
  alias Gpp.Sections.Tcf.DecodeError
  alias Gpp.Sections.{Tcfv1, Tcfv2}
  @behaviour Gpp.Section

  @type vendor_id :: pos_integer()
  @type t :: %__MODULE__{version: pos_integer(), vendor_consents: [vendor_id()]}
  defstruct [
    :section_id,
    :version,
    :vendor_consents,
    :value
  ]

  @impl Gpp.Section
  def parse(input) do
    with [core_segment | _] <- String.split(input, ".", parts: 2),
         {:ok, bits} <- BitUtil.url_base64_to_bits(core_segment),
         {:ok, type, _rest} <- segment_type(bits),
         {:ok, version, rest} <- version(bits) do
      case version do
        2 -> Tcfv2.Segment.decode(input, type, rest)
        1 -> Tcfv1.Segment.decode(input, type, rest)
        other -> {:error, %DecodeError{message: "unknown TCF EU version: #{other}"}}
      end
    end
  end

  @doc """
  Checks if the provided vendor_id is in the list of vendor_consents

  ## Examples

      iex> Tcf.has_consent?(%Tcf{vendor_consents: [8, 6, 2]}, 8)
      true

      iex> Tcf.has_consent?(%Tcf{vendor_consents: [8, 6, 2]}, 123)
      false
  """
  @spec has_consent?(t(), vendor_id()) :: boolean()
  def has_consent?(%{vendor_consents: consents}, vendor_id) do
    vendor_id in consents
  end

  def segment_type(bits) do
    with {:ok, type_int, rest} <- BitUtil.parse_3bit_int(bits),
         {:ok, type} <- __MODULE__.SegmentId.to_name(type_int) do
      {:ok, type, rest}
    end
  end

  def version(input), do: BitUtil.parse_6bit_int(input)
end