wrren
I wrote this library several years ago, but I’ve recently had a use case for it that’s caused me to make several updates that I think would also make it quite useful for others. Initially, I wanted a library that let me convert between types in Erlang and Elixir in a logical and consistent way. Over time, I’ve extended it to try and solve the general problem of coercing complex data from various sources into well-defined structures. On a basic level, want lets you convert between scalar types.
# String to integer
{:ok, 1} = Want.integer("1")
# String to integer, raise on failure
1 = Want.integer!("1")
# String to float
{:ok, 1.0} = Want.float("1")
# String to float, raise on failure
1.0 = Want.float!("1")
# Integer to string
{:ok, "1"} = Want.string(1)
# Integer to string, raise on failure
"1" = Want.string!(1)
# String to boolean
{:ok, true} = Want.boolean("true")
{:ok, false} = Want.boolean("FALSE")
# String to boolean, raise on failure
true = Want.boolean!("true")
However where want really shines is in defining structures and casting incoming data into them. This is similar to what Ecto.Schema can accomplish, but in a more generic way and with more options that let you deal with dirty data.
First you can define a schema for conversion using a map, and then convert incoming data into a map or keyword list like so:
@schema %{
hello: [type: :string, from: "world"],
foo: [type: :integer, default: 0]
}
{:ok, %{hello: "goodbye", foo: 0}} = Want.map(%{"world" => "goodbye"}, @schema)
In this simple example you can see how a schema lets you define source fields, types, defaults, etc. We can make this more complex like so:
@schema %{
hello: [type: :string, from: "world", transform: %String.upcase/1],
foo: [type: :integer, default: 0, from: {"a", "b", "c"}]
}
{:ok, %{hello: "GOODBYE", foo: 10}} = Want.map(%{"world" => "goodbye", "a" => %{"b" => %{"c" => 10}}}, @schema)
Here you can see field transformations in action, which are applied after a field has been cast, as well as sourcing data from nested fields.
Finally we have shape/1, which is a macro that lets us define a schema and struct at the same time, exposing functions for casting from data or lists of data.
defmodule MyShape do
use Want.Shape
shape transform: &__MODULE__.transform/1 do
field :is_valid, :boolean, default: false
field :count, :integer, default: 0
field :from, :string, from: "FromField"
field :multi_from, :integer, from: ["a", {"b", "c", "d"}], default: 0
end
def transform(%MyShape{count: c} = s),
do: %{s | count: c * 2}
end
{:ok, %MyModule{is_valid: true, count: 20, from: "Foo", multi_from: 10}} = MyShape.cast(%{
"is_valid" => "true",
"count" => "10",
"from" => "Foo",
"b" => %{
"c" => %{
"d" => 10
}
}
})
As with map schemas, you can define types and options. shape also lets you specify a transformation function that’s applied to the entire result after casting. use Want.Shape exposes the cast/1, cast!/1, cast_all/1 and cast_all!/1 functions as part of your module.
Want on hex.pm
Want on github:
https://github.com/wrren/want.erl
Trending in Announcing
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixir-ls
- #ai
- #elixirconf-us
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 8- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
Eiji
What I generally advise is to use common naming that you can find in
Elixir,ectoorphoenix. In this case I recommend to use:sourceinstead offromas it’s already well known inecto, see::sourceoption in: Options | Ecto.Schema.field/3.I like the idea of flexibility between passing schema map and using
DSL.mayel
Thanks for sharing, this looks handy!
Is there a way to define custom types to to extend the type parsing rules? In the meantime, here’s a quick PR, as I can see this being a quick win for parsing ENV variables for config: Comparing wrren:master...mayel:patch-1 · wrren/want.erl · GitHub
Eiji
You forgot to create a
PRFor
CLII would also suggest:y(short foryes)t(short fortrue)n(short forno)f(short forfalse)I use them more often than
0and1.mayel
Thanks, PRed now: parse more strings into booleans by mayel · Pull Request #1 · wrren/want.erl · GitHub
wrren
Thanks! I’ve merged your patch. Regarding custom types, that’s a cool idea. It should be reasonably trivial using a
behaviourand some checks. I’ll add that feature today.wrren
Custom type support has been added. You just need to implement the
Want.Typebehaviour and it will work. See the updated README for more details.code-shoily
Lol looking at the title, I thought you “wanted” a library for this and this was a wish list
It looks good.
mayel
Thanks for merging and adding custom types so quickly
Here’s a quick module I put together for parsing env vars into config, it ended up being more of a wrapper around
Wantthan a custom type, though I guess it could be both if we had a function likeWant.cast(input, CustomType, opts). Posting it here for feedback and if it looks good to you I’ll submit a couple more PRs (mentioned in the code by TODOs) to simplify things.