Git Product home page Git Product logo

exconstructor's Introduction

ExConstructor

Build Status Coverage Status Hex.pm Version

ExConstructor is an Elixir library that makes it easy to instantiate structs from external data, such as that emitted by a JSON parser.

Add use ExConstructor after a defstruct statement to inject a constructor function into the module.

The generated constructor, called new by default, handles map-vs-keyword-list, string-vs-atom-keys, and camelCase-vs-under_score input data issues automatically, DRYing up your code and letting you move on to the interesting parts of your program.

Example

defmodule TestStruct do
  defstruct field_one: nil,
            field_two: nil,
            field_three: nil,
            field_four: nil
  use ExConstructor
end

TestStruct.new(%{"field_one" => "a", "fieldTwo" => "b", :field_three => "c", :FieldFour => "d"})
# => %TestStruct{field_one: "a", field_two: "b", field_three: "c", field_four: "d"}

Full ExConstructor documentation is available on Hexdocs.pm.

Contributors

Many thanks to those who've contributed to ExConstructor:

How to Contribute

My favorite contributions are PRs with code that matches project style, and that come with full test coverage and documentation. I have a hard time saying no to them.

Feature requests are also welcome, but the timeline may be much longer.

Bug reports are great -- please include as much information as possible (Erlang/Elixir/Mix version, dependencies and their versions, minimal test case, etc.) and I will be much quicker in resolving the issue.

Authorship and License

ExConstructor is copyright 2016-2021 Appcues, Inc.

ExConstructor is released under the MIT License.

exconstructor's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

exconstructor's Issues

Error in readme example

This:

/app # iex -S mix
Erlang/OTP 21 [erts-10.3.5.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Interactive Elixir (1.7.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> defmodule TestStruct do
...(1)>   defstruct field_one: nil,
...(1)>             field_two: nil,
...(1)>             field_three: nil,
...(1)>             field_four: nil
...(1)>   use ExConstructor
...(1)> end
{:module, TestStruct,
 <<70, 79, 82, 49, 0, 0, 7, 224, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 249,
   0, 0, 0, 23, 17, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 83, 116,
   114, 117, 99, 116, 8, 95, 95, 105, 110, ...>>, :ok}
iex(2)> 
nil
iex(3)> TestStruct.new(%{"field_one" => "a", "fieldTwo" => "b", :field_three => "c", :FieldFour => "d"})
%TestStruct{field_four: nil, field_one: "a", field_three: "c", field_two: "b"}

Expect: %TestStruct{field_one: "a", field_two: "b", field_three: "c", field_four: "d"}

(option to) Use Kernel.struct!/2 to be stricter about making structs

Kernel.struct!/2 is stricter (see docs), which will prevent the ignorance of @enforce_keys and prevent invalid struct keys being passed.

If you want some code to do the two checks independently, you could add something like this:

  use ExConstructor, check_enforce_keys: true, check_no_invalid_args: true

  ...

  # pass @enforce_keys as 2nd param
  def check_enforce_keys(args, enforce_keys) do
    arg_keys =
      args
      |> Map.new
      |> Map.keys
      |> MapSet.new
    required_keys = enforce_keys |> MapSet.new

    if not MapSet.subset?(required_keys, arg_keys) do
      raise(
        ArgumentError,
        "Requires keys #{required_keys |> Enum.to_list |> inspect} "
        <> "but only #{arg_keys |> Enum.to_list |> inspect} "
        <> "were given"
      )
    end
  end

  def check_no_invalid_args(args, module) do
    arg_keys =
      args
      |> Map.new
      |> Map.keys
      |> MapSet.new
    valid_keys =
      module
      |> struct([])
      |> Map.from_struct
      |> Map.keys
      |> MapSet.new

    if not MapSet.subset?(arg_keys, valid_keys) do
      raise(
        KeyError,
        "Allowed keys are #{valid_keys |> Enum.to_list |> inspect} "
        <> "but #{arg_keys |> Enum.to_list |> inspect} "
        <> "were given"
      )
    end
  end

Feature request: produce (or deconstruct)

It would be a useful extension to be able to produce maps with keys cased in one specific flavour.

One example use would be to produce Javascript-style camelCase API responses while maintaining structs and maps internally in snake_case.

use ExConstructor position and main example

Hi,

the doc say to put the use ExConstructor after the struct creation, but i've put it before and it's seams to work the same. Am i miss something? I'm running elixir 1.15.

The example of TestStruct say that field_four sould be correctlly filled with d, but it seams not working.

image

incorrect value assigned

iex(5)> TestStruct.new(%{"field_one" => "a", "fieldTwo" => "b", :field_Three => "c", :fieldFour => "d"})
%TestStruct{field_four: "d", field_one: "a", field_three: 3, field_two: "b"}

where does it get the 3 from?

undefined function Mix.Utils.underscore

I tried running this in my escript and go this error. Are there some dependencies I'm missing?

** (UndefinedFunctionError) undefined function Mix.Utils.underscore/1 (module Mix.Utils is not available)
    Mix.Utils.underscore("field_four")
...
defmodule Bot.Mixfile do
  use Mix.Project

  def project do
    [
      app: :bot,
      version: "0.0.1",
      elixir: "~> 1.2",
      escript: [main_module: Bot],
      build_embedded: Mix.env == :prod,
      start_permanent: Mix.env == :prod,
      deps: deps,
      default_task: "bot.run"
    ]
  end

  def application do
    [
      applications: [
        :logger,
        :nadia,
        :httpoison, :poison,
        :tzdata,
        :exconstructor
      ]
    ]
  end

  defp deps do
    [{:nadia, git: "https://github.com/zhyu/nadia"},
     {:httpoison, "~> 0.8.0"},
     {:poison, "~> 1.5.0"},
     {:tzdata, "== 0.1.8", override: true},
     {:timex, git: "https://github.com/bitwalker/timex"},
     {:exconstructor, "~> 0.2.0"}
    ]
  end
end

parsing acronyms

Description

Problem parsing acronyms which are entirely uppercase within a string. Acronyms seem to be quite common is apis returning json.

What I mean by acronyms:
ImageID --> image_id, IPAddress --> ip_address

Example

  defmodule Identity.Endpoint do
    defstruct [:admin_url, :region, :internal_url, :id, :public_url]
    use ExConstructor
  end
Identity.Endpoint.new(
  %{
  "adminURL" => "adminURL", 
  "id" => "id", 
  "internalURL" => "internalURL", 
  "publicURL" => "publicURL",
   "region" => "region"
  }
)

Returns

%Identity.Endpoint{
  admin_url: nil, 
  id: "id", 
  internal_url: nil, 
  public_url: nil,
  region: "region"
}

Potential Solution:

Screen for acronyms. Pull request on its way. ๐Ÿ”จ

Compilation deadlocks in my project when using exconstructor

Tracked it down to the line underneath the unquote. Any ideas?

  def unquote(constructor_name)(map_or_kwlist, opts \\ []) do
          ExConstructor.populate_struct(
            %__MODULE__{},
            map_or_kwlist,
            Keyword.merge(@exconstructor_default_options, opts)
          )

Does not work with @enforce_keys

  defmodule Point do
    @enforce_keys [:x, :y]
    defstruct [:x, :y, :z]

    use ExConstructor
  end

Error when defining module:

** (ArgumentError) the following keys must also be given when building struct ExConstructorValidatorTest.Point: [:x, :y]

Could be caused by this:

%__MODULE__{},

Perhaps create struct with Kernel.struct/2 which does not check @enforce_keys like %_{} and Kernel.struct!/2 do:

Kernel.struct(__MODULE__, [])

String.to_atom/1 would not exhaust potentially the atom table?

@gamache hi! First of all, thanks for the library. Maybe I am mistaken, however since we are:

https://github.com/appcues/exconstructor/blob/master/lib/exconstructor.ex#L158-L159 from a newly generated string from Macro.underscore/1 and Macro.camelize/1, wouldn't that be new strings associated with newly created atoms?

For example:

iex(141)> Regex.named_captures(~r/index_table:atom_tab\n*\n.*\n.*\nentries:(?<atoms>.*)/m, :erlang.system_info(:info))
%{"atoms" => " 20963"}
iex(142)> camelized = Macro.camelize("helloworld2")                                                                   
"Helloworld2"
iex(143)> Regex.named_captures(~r/index_table:atom_tab\n*\n.*\n.*\nentries:(?<atoms>.*)/m, :erlang.system_info(:info))
%{"atoms" => " 20963"}
iex(144)> String.to_atom(camelized)                                                                                   
:Helloworld2
iex(145)> Regex.named_captures(~r/index_table:atom_tab\n*\n.*\n.*\nentries:(?<atoms>.*)/m, :erlang.system_info(:info))
%{"atoms" => " 20964"}
iex(146)> 

I already checked issue #5 -- however I might have missed something?
Thank you!

Is this safe to use with user input?

Unless I'm missing something, it looks like this calls String.to_atom/1 on maps that could come from url params, which, given that atoms are never garbage collected exposes a denial of service vulnerability. Maybe I'm missing some validation somewhere, but it seems like this would need to verify that each key string is actually a key and never turn user input into atoms.

See: https://github.com/appcues/exconstructor/blob/master/lib/exconstructor.ex#L155-L156

Parse upper case words

Hi! Very nice library but i've got problem. How can i use exconstructor if data structure is map, key type is string and key format is like GlobalIPv6Address?
For example, here what i get from docker remote api

%{"Command" => "string",
  "Created" => 1459328030, "HostConfig" => %{"NetworkMode" => "default"},
  "Id" => "string",
  "Image" => "string",
  "ImageID" => "sha256:string",
  "Labels" => %{"build-date" => "2015-12-23", "license" => "GPLv2",
    "name" => "CentOS Base Image", "vendor" => "CentOS", "string" => ""},
  "Names" => ["/boring_varahamihira"],
  "NetworkSettings" => %{"Networks" => %{"bridge" => %{"Aliases" => nil,
        "EndpointID" => "string",
        "Gateway" => "172.17.0.1", "GlobalIPv6Address" => "",
        "GlobalIPv6PrefixLen" => 0, "IPAMConfig" => nil,
        "IPAddress" => "172.17.0.2", "IPPrefixLen" => 16, "IPv6Gateway" => "",
        "Links" => nil, "MacAddress" => "02:42:ac:11:00:02",
        "NetworkID" => ""}}},
  "Ports" => [%{"IP" => "0.0.0.0", "PrivatePort" => 80, "PublicPort" => 80,
     "Type" => "tcp"}], "Status" => "Up 3 weeks"}

Example inline usage for the docs

Wasn't trivial, at least to me, to set this working. Can I submit a PR to add to docs?

defmodule CreateShift, do: [defstruct([:shift_id]), use(ExConstructor)]

What's point of this library?

I'm not sure if I'm missing anything but I can't see the point of this library.

Native: struct = struct(TestStruct, map)
Exconstructor: struct = TestStruct.new(map)

parse acronyms in jsons fields

Description

Problem parsing acronyms which are entirely uppercase within a string. Acronyms seem to be quite common is apis returning json.

What I mean by acronyms:
ImageID --> image_url, IPAddress --> ip_address

Example

  defmodule Identity.Endpoint do
    defstruct [:admin_url, :region, :internal_url, :id, :public_url]
    use ExConstructor
  end
Identity.Endpoint.new(
  %{
  "adminURL" => "adminURL", 
  "id" => "id", 
  "internalURL" => "internalURL", 
  "publicURL" => "publicURL",
   "region" => "region"
  }
)

Returns

%Identity.Endpoint{
  admin_url: nil, 
  id: "id", 
  internal_url: nil, 
  public_url: nil,
  region: "region"
}

Potential Solution:

Screen for acronyms. Pull request on its way. ๐Ÿ”จ

Support for nested structs

It will be nice if you can pass a few structs and get something like:
from
%{"Command" => "string", "Created" => 1459328030, "HostConfig" => %{"NetworkMode" => "default"}}
to
%Container{command: "string", created: 1459328030, %HostConfig{network_mode: "default"}}

if it possible. Maybe from nested module:

defmodule Constructor do

  defmodule Container do
    defstruct command: 1, created: 2, host_config: 3

    use ExConstructor
  end

  defmodule HostConfig do
    defstruct network_mode: 1 #(or 4)

    use ExConstructor
  end
end

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.