ryanwinchester

ryanwinchester

`Workflow.add_cascade` dialyzer error when adding deps

When adding :deps to Workflow.add_cascade/4 I get a LSP/Dialyzer error

The function call will not succeed.

Oban.Pro.Workflow.add_cascade(
  %Oban.Pro.Workflow{
    :changesets => [
      %Ecto.Changeset{
        :action => atom(),
        :changes => %{atom() => _},
        :constraints => [
          %{
            :constraint =>
              binary()
              | %Regex{
                  :opts => binary() | [any()],
                  :re_pattern => _,
                  :re_version => _,
                  :source => binary()
                },
            :error_message => binary(),
            :error_type => atom(),
            :field => atom(),
            :match => :exact | :prefix | :suffix,
            :type => :check | :exclusion | :foreign_key | :unique
          }
        ],
        :data => %Oban.Job{
          :__meta__ => _,
          :args => map(),
          :attempt => non_neg_integer(),
          :attempted_at =>
            nil
            | %DateTime{
                :calendar => atom(),
                :day => pos_integer(),
                :hour => non_neg_integer(),
                :microsecond => {non_neg_integer(), non_neg_integer()},
                :minute => non_neg_integer(),
                :month => pos_integer(),
                :second => non_neg_integer(),
                :std_offset => integer(),
                :time_zone => binary(),
                :utc_offset => integer(),
                :year => integer(),
                :zone_abbr => binary()
              },
          :attempted_by => nil | [binary()],
          :cancelled_at =>
            nil
            | %DateTime{
                :calendar => atom(),
                :day => pos_integer(),
                :hour => non_neg_integer(),
                :microsecond => {non_neg_integer(), non_neg_integer()},
                :minute => non_neg_integer(),
                :month => pos_integer(),
                :second => non_neg_integer(),
                :std_offset => integer(),
                :time_zone => binary(),
                :utc_offset => integer(),
                :year => integer(),
                :zone_abbr => binary()
              },
          :completed_at =>
            nil
            | %DateTime{
                :calendar => atom(),
                :day => pos_integer(),
                :hour => non_neg_integer(),
                :microsecond => {non_neg_integer(), non_neg_integer()},
                :minute => non_neg_integer(),
                :month => pos_integer(),
                :second => non_neg_integer(),
                :std_offset => integer(),
                :time_zone => binary(),
                :utc_offset => integer(),
                :year => integer(),
                :zone_abbr => binary()
              },
          :conf =>
            nil
            | %Oban.Config{
                :dispatch_cooldown => pos_integer(),
                :engine => atom(),
                :get_dynamic_repo => nil | (-> atom() | pid()) | {atom(), atom(), [any()]},
                :insert_trigger => boolean(),
                :log =>
                  :alert
                  | :critical
                  | :debug
                  | :emergency
                  | :error
                  | false
                  | :info
                  | :notice
                  | :warn
                  | :warning,
                :name => _,
                :node => binary(),
                :notifier => {atom(), Keyword.t()},
                :peer => {atom(), Keyword.t()},
                :plugins => [atom() | {atom() | Keyword.t()}],
                :prefix => false | binary(),
                :queues => Keyword.t(Keyword.t()),
                :repo => atom(),
                :shutdown_grace_period => non_neg_integer(),
                :stage_interval => timeout(),
                :testing => :disabled | :inline | :manual
              },
          :conflict? => boolean(),
          :discarded_at =>
            nil
            | %DateTime{
                :calendar => atom(),
                :day => pos_integer(),
                :hour => non_neg_integer(),
                :microsecond => {non_neg_integer(), non_neg_integer()},
                :minute => non_neg_integer(),
                :month => pos_integer(),
                :second => non_neg_integer(),
                :std_offset => integer(),
                :time_zone => binary(),
                :utc_offset => integer(),
                :year => integer(),
                :zone_abbr => binary()
              },
          :errors => [
            %{
              :at => %DateTime{
                :calendar => atom(),
                :day => pos_integer(),
                :hour => non_neg_integer(),
                :microsecond => {non_neg_integer(), non_neg_integer()},
                :minute => non_neg_integer(),
                :month => pos_integer(),
                :second => non_neg_integer(),
                :std_offset => integer(),
                :time_zone => binary(),
                :utc_offset => integer(),
                :year => integer(),
                :zone_abbr => binary()
              },
              :attempt => pos_integer(),
              :error => binary()
            }
          ],
          :id => pos_integer(),
          :inserted_at => %DateTime{
            :calendar => atom(),
            :day => pos_integer(),
            :hour => non_neg_integer(),
            :microsecond => {non_neg_integer(), non_neg_integer()},
            :minute => non_neg_integer(),
            :month => pos_integer(),
            :second => non_neg_integer(),
            :std_offset => integer(),
            :time_zone => binary(),
            :utc_offset => integer(),
            :year => integer(),
            :zone_abbr => binary()
          },
          :max_attempts => pos_integer(),
          :meta => map(),
          :priority => 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9,
          :queue => binary(),
          :replace =>
            nil
            | [
                [
                  :args
                  | :max_attempts
                  | :meta
                  | :priority
                  | :queue
                  | :scheduled_at
                  | :tags
                  | :worker
                ]
                | {:available
                   | :cancelled
                   | :completed
                   | :discarded
                   | :executing
                   | :retryable
                   | :scheduled,
                   [
                     [
                       :args
                       | :max_attempts
                       | :meta
                       | :priority
                       | :queue
                       | :scheduled_at
                       | :tags
                       | :worker
                     ]
                   ]}
              ],
          :scheduled_at => %DateTime{
            :calendar => atom(),
            :day => pos_integer(),
            :hour => non_neg_integer(),
            :microsecond => {non_neg_integer(), non_neg_integer()},
            :minute => non_neg_integer(),
            :month => pos_integer(),
            :second => non_neg_integer(),
            :std_offset => integer(),
            :time_zone => binary(),
            :utc_offset => integer(),
            :year => integer(),
            :zone_abbr => binary()
          },
          :state => binary(),
          :tags => [binary()],
          :unique =>
            nil
            | %{
                :fields => [:args | :meta | :queue | :worker],
                :keys => [atom()],
                :period =>
                  :infinity
                  | pos_integer()
                  | {pos_integer(),
                     :day
                     | :days
                     | :hour
                     | :hours
                     | :minute
                     | :minutes
                     | :second
                     | :seconds
                     | :week
                     | :weeks},
                :states => [
                  [
                    :available
                    | :cancelled
                    | :completed
                    | :discarded
                    | :executing
                    | :retryable
                    | :scheduled
                  ]
                ],
                :timestamp => :inserted_at | :scheduled_at
              },
          :unsaved_error =>
            nil
            | %{
                :kind => :error | :exit | :throw | {:EXIT, pid()},
                :reason => _,
                :stacktrace => [
                  {(... -> any), [any()] | non_neg_integer(), Keyword.t()}
                  | {atom(), atom(), [any()] | non_neg_integer(), Keyword.t()}
                ]
              },
          :worker => binary()
        },
        :empty_values => _,
        :errors => Keyword.t({binary(), Keyword.t()}),
        :filters => %{atom() => _},
        :params => nil | %{binary() => _},
        :prepare => [
          (%Ecto.Changeset{:action => atom(), :changes => map(), _ => _} ->
             %Ecto.Changeset{:action => atom(), :changes => map(), _ => _})
        ],
        :repo => atom(),
        :repo_opts => Keyword.t(),
        :required => [atom()],
        :types => %{
          atom() =>
            atom()
            | {:array | :assoc | :embed | :in | :map | :parameterized | :supertype | :try,
               _}
        },
        :valid? => boolean(),
        :validations => Keyword.t()
      },
      ...
    ],
    :check_deps => boolean(),
    :grafts => _,
    :id => binary(),
    :names => %MapSet{:map => MapSet.internal(_) | :sets.set(_)},
    :opts => map(),
    :subs => map()
  },
  :orders,
  (_ -> {[any()], [any()]}),
  [{:deps, :init}]
)

breaks the contract
(t(), name(), cascade_capture(), add_cascade_opts()) :: t()

Marked As Solved

al2o3cr

al2o3cr

I believe there’s a bug in the type for add_cascade_opts():

@type add_cascade_opts() :: [Oban.Job.option() | add_opts()]

@type add_opts() :: [
  deps: name() | [name()],
  ignore_cancelled: boolean(),
  ignore_deleted: boolean(),
  ignore_discarded: boolean()
]

The | operator normally expects options also separated with |, like Oban.Job.option():

@type option() ::
  {:args, args()}
  | {:max_attempts, pos_integer()}
  | {:meta, map()}
  | {:priority, 0..9}
  | etc

As currently written, the type means “a list where elements are either tuples from Oban.Job.option() or a keyword list shaped like add_opts()”, so something like [[deps: :init]] would match the current spec. (but presumably fail catastrophically where options are parsed)

I don’t currently have access to Oban Pro, but updating the definition of add_cascade_opts would likely remove the error you’re seeing:

@type add_cascade_opts() :: [Oban.Job.option() | add_opt()]

@type add_opts() :: [add_opt()]

@type add_opt() ::
  {:deps, name() | [name()]}
  | {:ignore_cancelled, boolean()}
  | {:ignore_deleted, boolean()}
  | {:ignore_discarded, boolean()}

Beware that changes to files in deps/ won’t get picked up by Dialyzer until you nuke _build and any cached PLTs.

Also Liked

sorentwo

sorentwo

Oban Core Team

Thanks for reporting the issue (and thanks @al2o3cr for the resolution). This is fixed for the Pro v1.6 release :slightly_smiling_face:

Where Next?

Popular in Questions Top

Tee
can someone please explain to me how Enum.reduce works with maps
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

Other popular topics Top

malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47930 226
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New

We're in Beta

About us Mission Statement