diff --git a/README.md b/README.md index d4063e1..92eb895 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ attribute :serial_number, :string, length: 16 attribute :wifi_ssid, :string, optional: true attribute :low_power_mode, :boolean, optional: true attribute :dns_servers, [:string] +attribute :user_data, :any attribute :metadata do attribute :location, :string @@ -130,6 +131,7 @@ attribute , , ``` Types: +- `any` - `boolean` - `integer` - `float` @@ -160,6 +162,37 @@ Options: - `format` - Regular expression for the valid format of a string. - error - `wrong_format` +The `any` type: +- Intended to be a passthrough attribute where the value is not validated or coerced but the value must be not `nil` at a minimum if `optional: false`. The `any` type is useful if you want to split out schemas where the top level schema is a generic envelope containing a nested data structure that could possibly be of a polymorphic nature. **Example**: + +```elixir +struct MQTT.AWS.Shadow.Update.V1 + +name "aws_shadow_update" + +attribute :state do + attribute :desired, :any +end +``` + +```elixir +struct MQTT.Light.State.V1 + +name "light_state" + +attribute [:schedule] do + attribute :start, :datetime + attribute :stop, :datetime +end +``` + +```elixir +with {:ok, shadow} <- Speck.validate(MQTT.AWS.Shadow.Update.V1, payload), + {:ok, schedule} <- Speck.validate(MQTT.Light.State.V1, shadow.state.desired) do + # do something with schedule +end +``` + ### Examples Example schemas can be found in [/protocol](https://github.com/amclain/speck/tree/main/protocol). diff --git a/lib/speck.ex b/lib/speck.ex index dea88aa..e313fb9 100644 --- a/lib/speck.ex +++ b/lib/speck.ex @@ -103,6 +103,7 @@ defmodule Speck do end end + defp do_strict_validate(:any, value, _opts) when value != nil, do: value defp do_strict_validate(:boolean, value, _opts) when is_boolean(value), do: value defp do_strict_validate(:integer, value, _opts) when is_integer(value), do: value defp do_strict_validate(:float, value, _opts) when is_float(value), do: value @@ -162,6 +163,8 @@ defmodule Speck do end) end + defp do_validate(:any, value, _opts), do: value + defp do_validate(:boolean, value, _opts) when is_boolean(value), do: value defp do_validate(:integer, value, _opts) when is_integer(value), do: value diff --git a/protocol/test/any.ex b/protocol/test/any.ex new file mode 100644 index 0000000..01bcc73 --- /dev/null +++ b/protocol/test/any.ex @@ -0,0 +1,7 @@ +struct TestSchema.Any + +name "any" + +attribute :param1, :any +attribute :param2, :any, strict: true +attribute :param3, :any, optional: true diff --git a/test/speck_test.exs b/test/speck_test.exs index d3b7b48..4aa2971 100644 --- a/test/speck_test.exs +++ b/test/speck_test.exs @@ -179,6 +179,26 @@ defmodule Speck.Test do }} end + test "can coerce a map with attribute type of any" do + params = %{ + "param1" => %{}, + "param2" => "valid" + } + + assert Speck.validate(TestSchema.Any, params) == + {:ok, %TestSchema.Any{ + param1: %{}, + param2: "valid" + }} + end + + test "returns errors if required params of type any are missing" do + params = %{} + + assert Speck.validate(TestSchema.Any, params) == + {:error, %{param1: :not_present, param2: :not_present}} + end + test "returns errors if items in a list of maps can't be coerced" do params = %{ "devices" => [