Current section

Files

Jump to
bson lib bson_encoder.ex
Raw

lib/bson_encoder.ex

defprotocol BsonEncoder do
@fallback_to_any true
@moduledoc """
`BsonEncoder` protocol defines Bson encoding according to Elixir types or Bson specific struct (see `Bson`).
List of the protocol implementations:
* `Map` - Encodes a map into a document
* `Integer` - Encodes integer in 32 or 64 bits
* `Float` - Encodes float in 64 bits
* `Atom` - Encodes special atom (`false`, `true`, `nil`,
`:nan`, `:+inf`, `:-inf`, `MIN_KEY` and `MAX_KEY`) in appropriate format
others in special type Symbol
* `Tuple` - Encodes the return of `now/1`
* `BitString` - as binary string
* `List` - Encodes a list as array
* `Bson.Regex' - see specs
* `Bson.ObjectId' - see specs
* `Bson.JS' - see specs
* `Bson.Bin' - see specs
* `Bson.Timestamp ' - see specs
"""
@doc """
Returns a binary representing a named term in Bson format
"""
def encode(term, name)
end
defimpl BsonEncoder, for: Integer do
def encode(i, name), do: pre(i) <> name <> "\x00" <> int(i)
defp int(i) when -0x80000000 <= i and i <= 0x80000000, do: Bson.int32(i)
defp int(i) when -0x8000000000000000 <= i and i <= 0x8000000000000000, do: Bson.int64(i)
defp pre(i) when -0x80000000 <= i and i <= 0x80000000, do: "\x10"
defp pre(i) when -0x8000000000000000 <= i and i <= 0x8000000000000000, do: "\x12"
end
defimpl BsonEncoder, for: Float do
def encode(f, name), do: "\x01" <> name <> "\x00" <> float(f)
defp float(f), do: <<(f)::size(64)-float-little>>
end
defimpl BsonEncoder, for: Atom do
# encode pair where value is an atom
def encode(false, name), do: "\x08" <> name <> "\x00" <> "\x00"
def encode(true, name), do: "\x08" <> name <> "\x00" <> "\x01"
def encode(nil, name), do: "\x0a" <> name <> "\x00"
def encode(:nan, name), do: "\x01" <> name <> "\x00" <> <<0, 0, 0, 0, 0, 0, 248, 127>>
def encode(:'+inf', name), do: "\x01" <> name <> "\x00" <> <<0, 0, 0, 0, 0, 0, 240, 127>>
def encode(:'-inf', name), do: "\x01" <> name <> "\x00" <> <<0, 0, 0, 0, 0, 0, 240, 255>>
def encode(MIN_KEY, name), do: <<0xff>> <> name <> "\x00"
def encode(MAX_KEY, name), do: "\x7f" <> name <> "\x00"
def encode(atom, name), do: "\x0e" <> name <> "\x00" <> Bson.string(Atom.to_string(atom))
end
defimpl BsonEncoder, for: Bson.Regex do
def encode(%Bson.Regex{pattern: p, opts: o}, name) when is_binary(p) and is_binary(o) do
"\x0b" <> name <> "\x00" <> p <> "\x00" <> o <> "\x00"
end
end
defimpl BsonEncoder, for: Bson.ObjectId do
def encode(%Bson.ObjectId{oid: oid}, name) when is_binary(oid) do
"\x07" <> name <> "\x00" <> oid
end
end
defimpl BsonEncoder, for: Bson.JS do
def encode(%Bson.JS{code: js, scope: nil}, name) when is_binary(js) do
"\x0d" <> name <> "\x00" <> Bson.string(js)
end
def encode(%Bson.JS{code: js, scope: ctx}, name) when is_binary(js) and is_map(ctx) do
"\x0f" <> name <> "\x00" <> js_ctx(Bson.string(js) <> BsonEncoder.Map.encode_e_list(ctx))
end
defp js_ctx(jsctx), do: Bson.int32(byte_size(jsctx)+4) <> jsctx
end
defimpl BsonEncoder, for: Bson.Bin do
def encode(%Bson.Bin{bin: bin, subtype: subtype}, name), do: "\x05" <> name <> "\x00" <> Bson.int32(byte_size(bin)) <> subtype <> bin
end
defimpl BsonEncoder, for: Bson.Timestamp do
def encode(%Bson.Timestamp{inc: i, ts: t}, name), do: "\x11" <> name <> "\x00" <> Bson.int32(i) <> Bson.int32(t)
end
defimpl BsonEncoder, for: Tuple do
defmodule Error do
defexception [:message]
@moduledoc """
Only 2 tuple formats can be encoded. An empty tuple and the one given by now/0
"""
end
def encode({a, s, o}, name) when is_integer(a) and is_integer(s) and is_integer(o) do
"\x09" <> name <> "\x00" <> Bson.int64(a * 1000000000 + s * 1000 + div(o, 1000))
end
def encode(t, name) do
raise BsonEncoder.Tuple.Error, message: "cannot encode tuple of size #{to_string(tuple_size(t))} ( #{name} )"
end
end
defimpl BsonEncoder, for: BitString do
def encode(s, name) when is_binary(s), do: "\x02" <> name <> "\x00" <> Bson.string(s)
end
defimpl BsonEncoder, for: List do
# def encode([], name), do: "\x03" <> name <> "\x00" <> Bson.doc(<<>>)
def encode(array, name) when is_list(array) do
"\x04" <> name <> "\x00" <> encode_array(array)
end
defp encode_array(arr) do
{_, arrbin } = arr
|> Enum.reduce({0, []}, fn(item, {n, acc}) ->
{n+1, [BsonEncoder.encode(item, n |> Integer.to_string) | acc]}
end)
arrbin |> bitlist_to_bsondoc
end
defp bitlist_to_bsondoc(arrbin), do: arrbin |> Enum.reverse |> IO.iodata_to_binary |> Bson.doc
end
defimpl BsonEncoder, for: Map do
# def encode(%{}, name), do: "\x03" <> name <> "\x00" <> Bson.doc(<<>>)
def encode(map, name) do
"\x03" <> name <> "\x00" <> encode_e_list(map)
end
def encode_e_list(map) do
:maps.fold( fn
k, v, acc when is_atom(k) -> [BsonEncoder.encode(v, k |> Atom.to_string)|acc]
k, v, acc -> [BsonEncoder.encode(v, Bson.encode(k))|acc]
end, [], map)
|> bitlist_to_bsondoc
end
defp bitlist_to_bsondoc(arrbin), do: arrbin |> Enum.reverse |> IO.iodata_to_binary |> Bson.doc
end
defimpl BsonEncoder, for: Any do
def encode(%{__struct__: _struct}=map, name) do
"\x03" <> name <> "\x00" <> BsonEncoder.Map.encode_e_list(map)
end
end