terenceponce

terenceponce

I’m trying to write a @spec for this function that was made by mix phx.gen.auth:

# lib/my_app/identity/user_token.ex

@spec build_session_token(MyApp.Identity.User.t()) :: {binary(), t()}
def build_session_token(user) do
  token = :crypto.strong_rand_bytes(@rand_size)
  {token, %UserToken{token: token, context: "session", user_id: user.id}}
end

Unfortunately, I’m getting this error from Dialyxir:

Invalid type specification for function 'Elixir.MyApp.Identity.UserToken':build_session_token/1.
 The success typing is 'Elixir.MyApp.Identity.UserToken':build_session_token(atom() | #{'id':=_, _=>_}) -> {binary(),#{'__meta__':=#{'__struct__':='Elixir.Ecto.Schema.Metadata', 'context':='nil', 'prefix':='nil', 'schema':='Elixir.MyApp.Identity.UserToken', 'source':=<<_:96>>, 'state':='built'}, '__struct__':='Elixir.MyApp.Identity.UserToken', 'context':=<<_:56>>, 'id':='nil', 'inserted_at':='nil', 'sent_to':='nil', 'token':=binary(), 'user':=#{'__cardinality__':='one', '__field__':='user', '__owner__':='Elixir.MyApp.Identity.UserToken', '__struct__':='Elixir.Ecto.Association.NotLoaded'}, 'user_id':=_}}
 But the spec is 'Elixir.MyApp.Identity.UserToken':build_session_token('Elixir.MyApp.Identity.User':t()) -> {binary(),t()}
 The return types do not overlap

I think it looks correct, so I’m confused why I’m getting this error. What should I do to fix this?

EDIT:

# lib/my_app/identity/user_token.ex

defmodule MyApp.Identity.UserToken do

  @type t :: %__MODULE__{
          __meta__: Ecto.Schema.Metadata.t(),
          id: integer() | nil,
          token: binary(),
          context: String.t(),
          sent_to: String.t(),
          user_id: integer(),
          inserted_at: NaiveDateTime.t() | nil
        }


  schema "users_tokens" do
  # ...
  end
end
# lib/my_app/identity/user.ex

defmodule MyApp.Identity.User do

  @type t :: %__MODULE__{
          __meta__: Ecto.Schema.Metadata.t(),
          id: integer() | nil,
          email: String.t() | nil,
          password: String.t() | nil,
          hashed_password: String.t() | nil,
          confirmed_at: NaiveDateTime.t() | nil,
          inserted_at: NaiveDateTime.t() | nil,
          updated_at: NaiveDateTime.t() | nil
        }

  schema "users" do
    # ...
  end
end

Showing Posts 1 to 7

sodapopcan

sodapopcan

Ecto doesn’t generate typespecs, you have to do that yourself or use a library to auto-generate them. I can’t remember the name of the lib but it’s probably pretty easily found!

EDIT:

Oh, I dunno then :sweat_smile:

terenceponce

terenceponce OP

Sorry, I forgot to include the typespecs that I have. I actually have typespecs for my Ecto schemas. I’ve edited them into my main post.

sbuttgereit

sbuttgereit

In the UserToken typespec I see

But in the function I see the return value:

%UserToken{token: token, context: "session", user_id: user.id}

If that means that sent_to is inferred to be nil in the return type, then it doesn’t match the spec that says it must be a string. I’ve only glanced through this, so its possible that I’ve missed a detail, but that’s the one thing I see that doesn’t look quite right.

terenceponce

terenceponce OP

That fixed it. Thank you!

al2o3cr

al2o3cr

Some issues with this typespec relative to the code in build_session_token/1:

  • as @sbuttgereit called out, sent_to isn’t able to be nil so writing %UserToken{...} without sent_to isn’t going to produce a UserToken.t()

  • user.id is specced as integer() | nil, but it’s assigned to user_id which is only integer().

sbuttgereit

sbuttgereit

I have to admit… when I’m typespecing Ecto.Schema structs I set all the fields, with the exception of :__meta__, as typed: <some type> | nil without any further consideration.

I end up doing this for two reasons. The first is there are enough intermediate states for the data on its way to being complete and database ready that any attempt to represent the “completed state” of the data in the struct typespec just ends up with the kinds of errors reported here… and having to sort through and trying to reconcile the additional issues you found with the examples in the original question. The second reason is that avoiding the mental overhead of figuring out workable non-nil states has a significantly higher payoff than actually finding those edges where intermediate states could be finessed for Dialyzer.

For really keeping the data integral I’m depending on the database itself anyway. Yeah, that’s runtime and not compile time, but I think that’s the best to be done since the intermediate state problem means really typing these Schema structs more strictly means typing them in a way that’s not going to match the database requirements anyway… its just not feasible (that I can see).

dimitarvp

dimitarvp

Yeah I fully agree with you here, I do the same 99% of the time. Elixir has no static typing and as you said Ecto is basically a runtime concern, not a compile-time one, so I never even put any specs on anything Ecto-related. There’s really no point. You might catch a bug twice a year but you have spent who knows how many hours adding those specs.

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
ryanwinchester
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted” Version...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews