lib/bson.ex

defmodule Bson do
  @moduledoc """
  `Bson` provides encoding and decoding function for Bson format
  see http://bsonspec.org/

  Usage:
  ```

    iex> term = %{
    ...> a:  -4.230845,
    ...> b:  "hello",
    ...> c:  %{x: -1, y: 2.2001},
    ...> d:  [23, 45, 200],
    ...> eeeeeeeee:  %Bson.Bin{ subtype: Bson.Bin.subtyx(:binary),
    ...>               bin:  <<200, 12, 240, 129, 100, 90, 56, 198, 34, 0, 0>>},
    ...> f:  %Bson.Bin{ subtype: Bson.Bin.subtyx(:function),
    ...>               bin:  <<200, 12, 240, 129, 100, 90, 56, 198, 34, 0, 0>>},
    ...> g:  %Bson.Bin{ subtype: Bson.Bin.subtyx(:uuid),
    ...>               bin:  <<49, 0, 0, 0, 4, 66, 83, 79, 78, 0, 38, 0, 0, 0,
    ...>                       2, 48, 0, 8, 0, 0, 0, 97, 119, 101, 115, 111, 109,
    ...>                       101, 0, 1, 49, 0, 51, 51, 51, 51, 51, 51, 20, 64,
    ...>                       16, 50, 0, 194, 7, 0, 0, 0, 0>>},
    ...> h:  %Bson.Bin{ subtype: Bson.Bin.subtyx(:md5),
    ...>               bin:  <<200, 12, 240, 129, 100, 90, 56, 198, 34, 0, 0>>},
    ...> i:  %Bson.Bin{ subtype: Bson.Bin.subtyx(:user),
    ...>               bin:  <<49, 0, 0, 0, 4, 66, 83, 79, 78, 0, 38, 0, 0, 0, 2,
    ...>                       48, 0, 8, 0, 0, 0, 97, 119, 101, 115, 111, 109, 101,
    ...>                       0, 1, 49, 0, 51, 51, 51, 51, 51, 51, 20, 64, 16, 50,
    ...>                       0, 194, 7, 0, 0, 0, 0>>},
    ...> j:  %Bson.ObjectId{oid: <<82, 224, 229, 161, 0, 0, 2, 0, 3, 0, 0, 4>>},
    ...> k1: false,
    ...> k2: true,
    ...> l:  Bson.UTC.from_now({1390, 470561, 277000}),
    ...> m:  nil,
    ...> q1: -2000444000,
    ...> q2: -8000111000222001,
    ...> r:  %Bson.Timestamp{ts: 2},
    ...> s1: :min_key,
    ...> s2: :max_key,
    ...> t: Bson.ObjectId.from_string("52e0e5a10000020003000004")
    ...> }
    ...> bson = Bson.encode(term)
    <<100, 1, 0, 0, 1, 97, 0, 206, 199, 181, 161, 98, 236, 16, 192, 2, 98, 0, 6, 0, 0, 0, 104, 101, 108, 108, 111,
    0, 3, 99, 0, 23, 0, 0, 0, 16, 120, 0, 255, 255, 255, 255, 1, 121, 0, 210, 111, 95, 7, 206, 153, 1, 64, 0, 4, 100,
    0, 26, 0, 0, 0, 16, 48, 0, 23, 0, 0, 0, 16, 49, 0, 45, 0, 0, 0, 16, 50, 0, 200, 0, 0, 0, 0, 5, 101, 101, 101, 101,
    101, 101, 101, 101, 101, 0, 11, 0, 0, 0, 0, 200, 12, 240, 129, 100, 90, 56, 198, 34, 0, 0, 5, 102, 0, 11, 0, 0, 0,
    1, 200, 12, 240, 129, 100, 90, 56, 198, 34, 0, 0, 5, 103, 0, 49, 0, 0, 0, 4, 49, 0, 0, 0, 4, 66, 83, 79, 78, 0, 38,
    0, 0, 0, 2, 48, 0, 8, 0, 0, 0, 97, 119, 101, 115, 111, 109, 101, 0, 1, 49, 0, 51, 51, 51, 51, 51, 51, 20, 64, 16, 50,
    0, 194, 7, 0, 0, 0, 0, 5, 104, 0, 11, 0, 0, 0, 5, 200, 12, 240, 129, 100, 90, 56, 198, 34, 0, 0, 5, 105, 0, 49, 0, 0,
    0, 128, 49, 0, 0, 0, 4, 66, 83, 79, 78, 0, 38, 0, 0, 0, 2, 48, 0, 8, 0, 0, 0, 97, 119, 101, 115, 111, 109, 101, 0, 1,
    49, 0, 51, 51, 51, 51, 51, 51, 20, 64, 16, 50, 0, 194, 7, 0, 0, 0, 0, 7, 106, 0, 82, 224, 229, 161, 0, 0, 2, 0, 3, 0,
    0, 4, 8, 107, 49, 0, 0, 8, 107, 50, 0, 1, 9, 108, 0, 253, 253, 128, 190, 67, 1, 0, 0, 10, 109, 0, 16, 113, 49, 0, 160,
    165, 195, 136, 18, 113, 50, 0, 207, 6, 171, 1, 241, 147, 227, 255, 17, 114, 0, 2, 0, 0, 0, 0, 0, 0, 0, 255, 115, 49, 0,
    127, 115, 50, 0, 7, 116, 0, 82, 224, 229, 161, 0, 0, 2, 0, 3, 0, 0, 4, 0>>
    ...> decodedTerm = Bson.decode(bson, [:return_atom])
    ...> # assert that one by one all decoded element are identical to the original
    ...> Enum.all? term, fn({k, v}) -> assert Map.get(decodedTerm, k) == v end
    true

  ```

  see `encode/1` and `decode/1`

  """
  defmodule ObjectId do
    defstruct oid: nil

    @moduledoc """
    Represents the [MongoDB ObjectId](http://docs.mongodb.org/manual/reference/object-id/)

    * `:oid` - contains a binary size 12

    iex> inspect %Bson.ObjectId{}
    "ObjectId()"
    iex> inspect %Bson.ObjectId{oid: "\x0F\x1B\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10"}
    "ObjectId(0f1b01020304050607080910)"
    iex> to_string %Bson.ObjectId{oid: "\x0F\x1B\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10"}
    "0f1b01020304050607080910"

    """
    defimpl Inspect, for: Bson.ObjectId do
      def inspect(%Bson.ObjectId{oid: nil}, _), do: "ObjectId()"

      def inspect(%Bson.ObjectId{oid: oid}, _) when is_binary(oid),
        do: "ObjectId(#{Bson.hex(oid)})"

      def inspect(%Bson.ObjectId{oid: oid}, _), do: "InvalidObjectId(#{inspect(oid)})"
    end

    defimpl String.Chars, for: Bson.ObjectId do
      def to_string(%Bson.ObjectId{oid: oid}) do
        CBson.nif_objectid2bin(oid)
      end
    end

    @doc """
    Converts a string into a %Bson.ObjectId

    * `:object_id` - A string representing the BSON Object Id

    Usage:

    iex> inspect Bson.ObjectId.from_string("52e0e5a10000020003000004")
    "ObjectId(52e0e5a10000020003000004)"
    """
    def from_string(object_id) do
      %Bson.ObjectId{
        #oid: Base.decode16!(object_id, case: :lower)
        oid: CBson.nif_bin2objectid(object_id)
      }
    end
  end

  @doc false
  def hex(oid) do
    CBson.nif_objectid2bin(oid)
  end

  defmodule Regex do
    defstruct pattern: "", opts: ""

    @moduledoc """
    Represents a Regex

    * `:pattern` - a bynary that is the regex pattern
    * `:opts` - a bianry that contains the regex options string identified by characters, which must be stored in alphabetical order. Valid options are 'i' for case insensitive matching, 'm' for multiline matching, 'x' for verbose mode, 'l' to make \w, \W, etc. locale dependent, 's' for dotall mode ('.' matches everything), and 'u' to make \w, \W, etc. match unicode
    """
  end

  defmodule JS do
    defstruct code: "", scope: nil

    @moduledoc """
    Represents a Javascript function and optionally its scope

    * `:code` - a bynary that is the function code
    * `:scope` - a Map representing a bson document, the scope of the function
    """
  end

  defmodule Timestamp do
    defstruct ts: nil

    @moduledoc """
    Represents the special internal type Timestamp used by MongoDB
    """
  end

  defmodule UTC do
    defstruct ms: nil

    @moduledoc """
    Represent UTC datetime

    * `:ms` - miliseconds

    iex> inspect %Bson.UTC{ms: 1410473634449}
    "2014-9-11T22:13:54"

    """
    defimpl Inspect, for: Bson.UTC do
      def inspect(bson_utc, _) do
        {{y, mo, d}, {h, mi, s}} = :calendar.now_to_universal_time(Bson.UTC.to_now(bson_utc))
        "#{y}-#{mo}-#{d}T#{h}:#{mi}:#{s}"
      end
    end

    @doc """
    Returns a struct `Bson.UTC` using a tuple given by `:erlang.now/0`

    ```
    iex> Bson.UTC.from_now({1410, 473634, 449058})
    %Bson.UTC{ms: 1410473634449}

    ```
    """
    def from_now({a, s, o}), do: %UTC{ms: a * 1_000_000_000 + s * 1000 + div(o, 1000)}

    @doc """
    Returns a triplet tuple similar to the return value of `:erlang.now/0` using a struct `Bson.UTC`

    ```
    iex> Bson.UTC.to_now(%Bson.UTC{ms: 1410473634449})
    {1410, 473634, 449000}

    ```
    """
    def to_now(%UTC{ms: ms}),
      do: {div(ms, 1_000_000_000), rem(div(ms, 1000), 1_000_000), rem(ms * 1000, 1_000_000)}
  end

  defmodule Bin do
    defstruct bin: "", subtype: 0x00

    @moduledoc """
    Represents Binary data
    """
    @doc """
    Returns the subtype of the bynary data (`Binary` is the default). Other subtypes according to specs are:

    * `:binary` - Binary / Generic
    * `:function` - Function
    * `:binary.Old` - Binary (Old)
    * `:uuid_old` - UUID (Old)
    * `:uuid` - UUID
    * `:md5` - MD5
    * `:usrer` - User defined
    """
    def subtyx(:binary), do: 0x00
    def subtyx(:function), do: 0x01
    def subtyx(:binary_old), do: 0x02
    def subtyx(:uuid_old), do: 0x03
    def subtyx(:uuid), do: 0x04
    def subtyx(:md5), do: 0x05
    def subtyx(:user), do: 0x80

    @doc """
    Returns the atom coresponding to the subtype of the bynary data
    """
    def xsubty(0x00), do: :binary
    def xsubty(0x01), do: :function
    def xsubty(0x02), do: :binary
    def xsubty(0x03), do: :uuid
    def xsubty(0x04), do: :uuid
    def xsubty(0x05), do: :md5
    def xsubty(0x80), do: :user

    @doc """
    Retruns a struct `Bson.Bin`
    """
    def new(bin, subtype), do: %Bin{bin: bin, subtype: subtype}
  end

  @doc """
  Returns a binary representing a Bson document.

  It accepts a Map and returns a binary

  ```
  iex> Bson.encode(%{})
  <<5, 0, 0, 0, 0>>
  iex> Bson.encode(%{a: 1})
  <<12, 0, 0, 0, 16, 97, 0, 1, 0, 0, 0, 0>>
  iex> Bson.encode(%{a: 1, b: 2})
  <<19, 0, 0, 0, 16, 97, 0, 1, 0, 0, 0, 16, 98, 0, 2, 0, 0, 0, 0>>

  ```
  """
  defdelegate encode(term), to: CBson
  defdelegate encode(term, opts), to: CBson

  @doc """
  Returns decoded terms from a Bson binary document into a map with keys in the form of atoms (for other options use `Bson.Decoder.document/2`)


  ```
  iex> %{} |> Bson.encode |> Bson.decode
  %{}

  iex> %{a: "a"} |> Bson.encode |> Bson.decode([:return_atom])
  %{a: "a"}

  iex> %{a: 1, b: [2, "c"]} |> Bson.encode |> Bson.decode([:return_atom])
  %{a: 1, b: [2, "c"]}

  ```
  """

  defdelegate decode(term), to: CBson
  defdelegate decode(term, opts), to: CBson
end