zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

encoder.ex (6560B)


      1 defprotocol Jason.Encoder do
      2   @moduledoc """
      3   Protocol controlling how a value is encoded to JSON.
      4 
      5   ## Deriving
      6 
      7   The protocol allows leveraging the Elixir's `@derive` feature
      8   to simplify protocol implementation in trivial cases. Accepted
      9   options are:
     10 
     11     * `:only` - encodes only values of specified keys.
     12     * `:except` - encodes all struct fields except specified keys.
     13 
     14   By default all keys except the `:__struct__` key are encoded.
     15 
     16   ## Example
     17 
     18   Let's assume a presence of the following struct:
     19 
     20       defmodule Test do
     21         defstruct [:foo, :bar, :baz]
     22       end
     23 
     24   If we were to call `@derive Jason.Encoder` just before `defstruct`,
     25   an implementation similar to the following implementation would be generated:
     26 
     27       defimpl Jason.Encoder, for: Test do
     28         def encode(value, opts) do
     29           Jason.Encode.map(Map.take(value, [:foo, :bar, :baz]), opts)
     30         end
     31       end
     32 
     33   If we called `@derive {Jason.Encoder, only: [:foo]}`, an implementation
     34   similar to the following implementation would be generated:
     35 
     36       defimpl Jason.Encoder, for: Test do
     37         def encode(value, opts) do
     38           Jason.Encode.map(Map.take(value, [:foo]), opts)
     39         end
     40       end
     41 
     42   If we called `@derive {Jason.Encoder, except: [:foo]}`, an implementation
     43   similar to the following implementation would be generated:
     44 
     45       defimpl Jason.Encoder, for: Test do
     46         def encode(value, opts) do
     47           Jason.Encode.map(Map.take(value, [:bar, :baz]), opts)
     48         end
     49       end
     50 
     51   The actually generated implementations are more efficient computing some data
     52   during compilation similar to the macros from the `Jason.Helpers` module.
     53 
     54   ## Explicit implementation
     55 
     56   If you wish to implement the protocol fully yourself, it is advised to
     57   use functions from the `Jason.Encode` module to do the actual iodata
     58   generation - they are highly optimized and verified to always produce
     59   valid JSON.
     60   """
     61 
     62   @type t :: term
     63   @type opts :: Jason.Encode.opts()
     64 
     65   @fallback_to_any true
     66 
     67   @doc """
     68   Encodes `value` to JSON.
     69 
     70   The argument `opts` is opaque - it can be passed to various functions in
     71   `Jason.Encode` (or to the protocol function itself) for encoding values to JSON.
     72   """
     73   @spec encode(t, opts) :: iodata
     74   def encode(value, opts)
     75 end
     76 
     77 defimpl Jason.Encoder, for: Any do
     78   defmacro __deriving__(module, struct, opts) do
     79     fields = fields_to_encode(struct, opts)
     80     kv = Enum.map(fields, &{&1, generated_var(&1, __MODULE__)})
     81     escape = quote(do: escape)
     82     encode_map = quote(do: encode_map)
     83     encode_args = [escape, encode_map]
     84     kv_iodata = Jason.Codegen.build_kv_iodata(kv, encode_args)
     85 
     86     quote do
     87       defimpl Jason.Encoder, for: unquote(module) do
     88         require Jason.Helpers
     89 
     90         def encode(%{unquote_splicing(kv)}, {unquote(escape), unquote(encode_map)}) do
     91           unquote(kv_iodata)
     92         end
     93       end
     94     end
     95   end
     96 
     97   # The same as Macro.var/2 except it sets generated: true
     98   defp generated_var(name, context) do
     99     {name, [generated: true], context}
    100   end
    101 
    102   def encode(%_{} = struct, _opts) do
    103     raise Protocol.UndefinedError,
    104       protocol: @protocol,
    105       value: struct,
    106       description: """
    107       Jason.Encoder protocol must always be explicitly implemented.
    108 
    109       If you own the struct, you can derive the implementation specifying \
    110       which fields should be encoded to JSON:
    111 
    112           @derive {Jason.Encoder, only: [....]}
    113           defstruct ...
    114 
    115       It is also possible to encode all fields, although this should be \
    116       used carefully to avoid accidentally leaking private information \
    117       when new fields are added:
    118 
    119           @derive Jason.Encoder
    120           defstruct ...
    121 
    122       Finally, if you don't own the struct you want to encode to JSON, \
    123       you may use Protocol.derive/3 placed outside of any module:
    124 
    125           Protocol.derive(Jason.Encoder, NameOfTheStruct, only: [...])
    126           Protocol.derive(Jason.Encoder, NameOfTheStruct)
    127       """
    128   end
    129 
    130   def encode(value, _opts) do
    131     raise Protocol.UndefinedError,
    132       protocol: @protocol,
    133       value: value,
    134       description: "Jason.Encoder protocol must always be explicitly implemented"
    135   end
    136 
    137   defp fields_to_encode(struct, opts) do
    138     fields = Map.keys(struct)
    139 
    140     cond do
    141       only = Keyword.get(opts, :only) ->
    142         case only -- fields do
    143           [] ->
    144             only
    145 
    146           error_keys ->
    147             raise ArgumentError,
    148               "`:only` specified keys (#{inspect(error_keys)}) that are not defined in defstruct: " <>
    149                 "#{inspect(fields -- [:__struct__])}"
    150 
    151         end
    152 
    153       except = Keyword.get(opts, :except) ->
    154         case except -- fields do
    155           [] ->
    156             fields -- [:__struct__ | except]
    157 
    158           error_keys ->
    159             raise ArgumentError,
    160               "`:except` specified keys (#{inspect(error_keys)}) that are not defined in defstruct: " <>
    161                 "#{inspect(fields -- [:__struct__])}"
    162 
    163         end
    164 
    165       true ->
    166         fields -- [:__struct__]
    167     end
    168   end
    169 end
    170 
    171 # The following implementations are formality - they are already covered
    172 # by the main encoding mechanism in Jason.Encode, but exist mostly for
    173 # documentation purposes and if anybody had the idea to call the protocol directly.
    174 
    175 defimpl Jason.Encoder, for: Atom do
    176   def encode(atom, opts) do
    177     Jason.Encode.atom(atom, opts)
    178   end
    179 end
    180 
    181 defimpl Jason.Encoder, for: Integer do
    182   def encode(integer, _opts) do
    183     Jason.Encode.integer(integer)
    184   end
    185 end
    186 
    187 defimpl Jason.Encoder, for: Float do
    188   def encode(float, _opts) do
    189     Jason.Encode.float(float)
    190   end
    191 end
    192 
    193 defimpl Jason.Encoder, for: List do
    194   def encode(list, opts) do
    195     Jason.Encode.list(list, opts)
    196   end
    197 end
    198 
    199 defimpl Jason.Encoder, for: Map do
    200   def encode(map, opts) do
    201     Jason.Encode.map(map, opts)
    202   end
    203 end
    204 
    205 defimpl Jason.Encoder, for: BitString do
    206   def encode(binary, opts) when is_binary(binary) do
    207     Jason.Encode.string(binary, opts)
    208   end
    209 
    210   def encode(bitstring, _opts) do
    211     raise Protocol.UndefinedError,
    212       protocol: @protocol,
    213       value: bitstring,
    214       description: "cannot encode a bitstring to JSON"
    215   end
    216 end
    217 
    218 defimpl Jason.Encoder, for: [Date, Time, NaiveDateTime, DateTime] do
    219   def encode(value, _opts) do
    220     [?", @for.to_iso8601(value), ?"]
    221   end
    222 end
    223 
    224 defimpl Jason.Encoder, for: Decimal do
    225   def encode(value, _opts) do
    226     # silence the xref warning
    227     decimal = Decimal
    228     [?", decimal.to_string(value), ?"]
    229   end
    230 end
    231 
    232 defimpl Jason.Encoder, for: Jason.Fragment do
    233   def encode(%{encode: encode}, opts) do
    234     encode.(opts)
    235   end
    236 end