lib/open_rtb_ecto/v2/bid_request/video.ex

defmodule OpenRtbEcto.V2.BidRequest.Video do
  @moduledoc """
  This object represents an in-stream video impression. Many of the fields are non-essential for
  minimally viable transactions, but are included to offer fine control when needed. Video in
  OpenRTB generally assumes compliance with the VAST standard. As such, the notion of companion ads
  is supported by optionally including an array of Banner objects (refer to the Banner object in
  Section 3.2.6) that define these companion ads.

  The presence of a Video as a subordinate of the Imp object indicates that this impression is
  offered as a video type impression. At the publisher’s discretion, that same impression may also
  be offered as banner, audio, and/or native by also including as Imp subordinates objects of those
  types. However, any given bid for the impression must conform to one of the offered types.
  """

  use Ecto.Schema
  import Ecto.Changeset

  alias OpenRtbEcto.Types.TinyInt
  alias OpenRtbEcto.V2.BidRequest.Banner

  @type t :: %__MODULE__{}

  embedded_schema do
    field(:mimes, {:array, :string})
    field(:minduration, :integer)
    field(:maxduration, :integer)
    field(:rqddurs, {:array, :integer})
    field(:protocols, {:array, :integer})
    field(:protocol, :integer)
    field(:w, :integer)
    field(:h, :integer)
    field(:startdelay, :integer)
    field(:maxseq, :integer)
    field(:poddur, :integer)
    field(:podid, :integer)
    field(:podseq, :integer)
    field(:mincpmpersec, :float)
    field(:slotinpod, :integer)
    field(:placement, :integer)
    field(:plcmt, :integer)
    field(:linearity, :integer)
    field(:skip, TinyInt)
    field(:skipmin, :integer, default: 0)
    field(:skipafter, :integer, default: 0)
    field(:sequence, :integer)
    field(:battr, {:array, :integer})
    field(:maxextended, :integer)
    field(:minbitrate, :integer)
    field(:maxbitrate, :integer)
    field(:boxingallowed, TinyInt, default: 1)
    field(:playbackmethod, {:array, :integer})
    field(:playbackend, :integer)
    field(:delivery, {:array, :integer})
    field(:pos, :integer)
    embeds_many(:companionad, Banner)
    field(:api, {:array, :integer})
    field(:companiontype, {:array, :integer})
    field(:ext, :map, default: %{})
  end

  def changeset(video, attrs \\ %{}) do
    video
    |> cast(attrs, [
      :mimes,
      :minduration,
      :maxduration,
      :rqddurs,
      :protocols,
      :protocol,
      :w,
      :h,
      :startdelay,
      :maxseq,
      :poddur,
      :podid,
      :podseq,
      :mincpmpersec,
      :slotinpod,
      :placement,
      :plcmt,
      :linearity,
      :skip,
      :skipmin,
      :skipafter,
      :sequence,
      :battr,
      :maxextended,
      :minbitrate,
      :maxbitrate,
      :boxingallowed,
      :playbackmethod,
      :playbackend,
      :delivery,
      :pos,
      :api,
      :companiontype,
      :ext
    ])
    |> cast_embed(:companionad)
    |> validate_required([:mimes])
    |> validate_inclusion(:linearity, 1..2)
    |> validate_inclusion(:protocol, 1..10)
    |> validate_subset(:protocols, 1..10)
    |> validate_inclusion(:placement, 1..5)
    |> validate_inclusion(:plcmt, 1..4)
    |> validate_number(:startdelay, greater_than_or_equal_to: -2)
    |> validate_number(:pos, greater_than: -1, less_than: 8)
    |> validate_number(:maxextended, greater_than_or_equal_to: -1)
    |> validate_subset(:battr, 1..17)
    |> validate_subset(:playbackmethod, 1..6)
    |> validate_inclusion(:playbackend, 1..3)
    |> validate_subset(:delivery, 1..3)
    |> validate_list_of_pos_ints(:api)
    |> validate_subset(:companiontype, 1..3)
  end

  defp validate_list_of_pos_ints(changeset, field) do
    case get_change(changeset, field) do
      nil ->
        changeset

      values ->
        if Enum.all?(values, fn v -> is_integer(v) and v > 0 end) do
          changeset
        else
          add_error(changeset, field, "has an invalid entry")
        end
    end
  end
end