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