Giter Club home page Giter Club logo

strukt's Issues

Incorrect Typespec Generation for Fields with Custom or Parameterized Ecto Types in Strukt

Problem Description:

When defining a field with a custom Ecto type or a parameterized type in Strukt, the generated typespec for the field defaults to any() instead of the expected specific type (e.g., Ecto.UUID.t()). This issue may lead to less precise type checks and unexpected behaviors in development environments that rely on typespecs for static analysis or documentation purposes.

Example Code:

defmodule Foo do
  use Strukt
  
  defstruct do
    field :bar, Ecto.UUID
  end
end

Expected: The typespec for :bar should be Ecto.UUID.t().
Actual: The typespec for :bar is any().

Identified Issues:

Incorrect Handling of value_type:

The value_type in the typespec.ex function is not being correctly passed as a quoted expression. This results in the type not matching the expected case for processing.

Relevant code:

type_name = type_to_type_name(meta.value_type)

defp type_to_type_name({:__aliases__, _, parts} = ast) do

Failure in defines_type? Check:

The defines_type? function check always fails. This function, intended to verify if the current module defines a given type, is not feasible in current Elixir compiler. The docs of defines_type? say

Checks if the current module defines the given type (private, opaque or not).
This function is only available for modules being compiled.

Discussion related to this issue can be found here: Getting the @type module attribute
Relevant code:

if Kernel.Typespec.defines_type?(mod, {:t, 0}) do

Unexpected error raised when validating `embeds_many` field

Description

We are using strukt for validating external data input. For example we defined a schema with embeds_many field: bar:

defmodule Foo do
  use Strukt

  defstruct do
    embeds_many :bar, Bar do
      field :baz, :string
    end
  end
end

when using new/1 to parsing external data with wrong "embedded" type, it raise an unexpected error:

iex(2)> Foo.new(%{bar: "baz"})
** (Protocol.UndefinedError) protocol Enumerable not implemented for "baz" of type BitString. This protocol is implemented for the following type(s): Date.Range, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, Jason.OrderedObject, List, Map, MapSet, Range, Stream
    (elixir 1.16.1) lib/enum.ex:1: Enumerable.impl_for!/1
    (elixir 1.16.1) lib/enum.ex:166: Enumerable.reduce/3
    (elixir 1.16.1) lib/enum.ex:4399: Enum.map_reduce/3
    (elixir 1.16.1) lib/enum.ex:3865: Enum.with_index/2
    (strukt 0.3.2) lib/params.ex:41: Strukt.Params.transform/4
    (strukt 0.3.2) lib/params.ex:72: Strukt.Params.map_value_to_field/4
    (elixir 1.16.1) lib/enum.ex:1700: Enum."-map/2-lists^map/1-1-"/2
    iex:2: (file)

According to the stacktrace, it happens during conforming params:

formed_params = Strukt.Params.transform(__MODULE__, params, struct)

Expected behaviour

returns a validation error:

{:error,
 #Ecto.Changeset<
   action: :insert,
   changes: %{},
   errors: [bar: {"is invalid", [validation: :embed, type: {:array, :map}]}],
   data: #Foo<>,
   valid?: false
 >}

dialyxir warning when define a schema without embedded fields

Hello, thanks for create an amazing library.

I met a dialyxir warning when I use this library:

# foo.ex
defmodule Foo do
  use Strukt

  defstruct do
    field(:bar, :string)
    field(:baz, :string)
  end
end

and dialyxir(1.1.0) wanning at first line:

The pattern can never match the type.

Pattern:
_ = %Ecto.Changeset{:params => _}, [_ | _]

Type:

  %Ecto.Changeset{
    :action => :delete | :insert | nil | :update,
    :changes => %{atom() => _},
    :constraints => [map()],
    :data => nil | map(),
    :empty_values => _,
    :errors => [{_, _}],
    :filters => %{atom() => _},
    :params => nil | %{binary() => _},
    :prepare => [(_ -> any())],
    :repo => atom(),
    :repo_opts => [{_, _}],
    :required => [atom()],
    :types => nil | %{atom() => atom() | {_, _} | {_, _, _}},
    :valid? => boolean(),
    :validations => [{_, _}]
  },
  []

I guess this message is due to when @cast_embed_fields is [] we defined a "dead code" with type %Ecto.Changeset{:params => _}, [_ | _]

strukt/lib/strukt.ex

Lines 455 to 462 in 236c253

|> __cast_embeds__(@cast_embed_fields)
|> __validate__()
|> validate()
end
defp __cast_embeds__(changeset, []), do: changeset
defp __cast_embeds__(%Ecto.Changeset{params: params} = changeset, [field | fields]) do

Precise typing of embeds

Currently strukt typing embeds_one :foo, Foo's type as Foo.t(), embeds_many :foos, Foo as [Foo.t()]:

strukt/lib/typespec.ex

Lines 72 to 77 in 2b6c643

|> Enum.map(fn
{name, %{type: :embeds_one, value_type: type}} ->
{name, compose_call(type, :t, [])}
{name, %{type: :embeds_many, value_type: type}} ->
{name, List.wrap(compose_call(type, :t, []))}

it couldn't handle situations like

  • :foo field could be null which type should be Foo.t() | nil,
  • :foos could be nil, but element couldn't: [Foo.t()] | nil
  • :foos could be nil, element's could be nil too: nil | [Foo.t() | nil]

any idea for handle these cases?

when passing params with nil for embed_one field, will have `FunctionClauseError`

Given a struct below

  defmodule CustomFieldsWithEmbeddedSchema do
    use Strukt

    defstruct do
      field :name, :string
      embeds_one :meta, Meta
    end
  end

  defmodule Meta do
    use Strukt

    defstruct do
      field(:name, :string)
    end
  end

in iex

iex(5)> CustomFieldsWithEmbeddedSchema.new(%{meta: nil})
** (FunctionClauseError) no function clause matching in CustomFieldsWithEmbeddedSchema.transform_params/4

    The following arguments were given to CustomFieldsWithEmbeddedSchema.transform_params/4:

        # 1
        Meta

        # 2
        nil

        # 3
        nil

        # 4
        []

    lib/strukt.ex:1: CustomFieldsWithEmbeddedSchema.transform_params/4
    lib/strukt.ex:564: CustomFieldsWithEmbeddedSchema.map_value_to_field/4
    lib/strukt.ex:532: anonymous fn/5 in CustomFieldsWithEmbeddedSchema.transform_params/3
    (elixir 1.12.2) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
    lib/strukt.ex:529: CustomFieldsWithEmbeddedSchema.transform_params/3
    lib/strukt.ex:356: CustomFieldsWithEmbeddedSchema.new/1

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.