How to stop 500 errors when UUID doesn't match

I have an 500 error when my user changes UUID which is relation to Category UUID for inserting post.

** (exit) an exception was raised:
    ** (Ecto.ChangeError) value `"a863228-727c-4cf3-93f5-9b2f79df1288"` for `TrangellCmsService.Cms.Db.Post.cms_post_category_id` in `insert` does not match type :binary_id

How do I prevent an 500 error in client request? I want to show 400 error instead of 500 error , how do ?

my changeset :

def changeset(struct, params \\ %{}) do
        struct
        |> cast(params, @all_fields)
        |> validate_required(@all_fields)
        |> unique_constraint(:seo_alias_link, name: :index_of_post_alias_unique_link, message: "alias link already exists.")
        |> validate_inclusion(:seo_language, ["en", "fa"])
        |> validate_inclusion(:group_acl, ["admin", "actived", "unactived", "blocked"])
        |> validate_inclusion(:post_type, ["article", "shop", "free"])
        |> validate_length(:title, min: 3, max: 100)
        |> validate_length(:description, min: 100)
        |> validate_length(:seo_words, min: 3, max: 100)
        |> validate_length(:seo_description, min: 50, max: 264)
        |> foreign_key_constraint(:cms_post_category_id)
    end

1 Like

The UUID posted is invalid. I counted only 7 digits in the first group, but there need to be 8.

2 Likes

I know that it is invalid, I said that when UUID is invalid , phoenix shows 400 error , not shows 500 error, because my client may send invalid UUID and I don’t want Phoeinix to show 500 error.

Have you checked if your changeset is valid before inserting it? If it is in fact valid I’d file a bug for ecto and in the meantime add a regex validation.

If though you haven’t checked for validity, you really should before inserting…

Then if your changeset is invalid you can do any response code you want

my code for inserting :

	def insert_post(params) do
		changeset = Post.changeset(%Post{}, params)
		case Repo.insert(changeset) do
			{:ok, post} 				-> 	{:ok, post}
			{:error, changeset} ->  {:error, changeset}
		end
	end 

You say I check UUID to validate before inserting? because my Ecto can’t validate it ? my Changesert can’t understand this error.

But you do not do a changeset.valid?, you really should…

1 Like

@shahryarjb try logging the changset before you insert it and you should see it has a field valid?: false with some more info in the errors field. You can match on these to handle your specific case.

2 Likes

What is the reason for checking changeset.valid?? Repo.insert checks if it is valid before inserting.

1 Like

Because Changeset isn’t able to validate UUID before inserting, then it shows me an 500 error.

I think , I will need to create a function to check UUID and I put this in my Changeset.

Changeset Custom error

It might help to have the controller code… in particular create action.

Do You use fallback controller?

I use umbrella project, my controller in Phoenix,

   def create_cms_post(conn, %{"title" => _title, "status" => _status, "post_type" => _post_type, "download_ext_link" => _download_ext_link, "price" => _price, "pic_x1_link" => _pic_x1_link, "pic_x2_link" => _pic_x2_link, "pic_x3_link" => _pic_x3_link, "group_acl" => _group_acl, "description" => _description, "changelog" => _changelog, "changelog_category" => _changelog_category, "plugin" => _plugin, "plugin_category" => _plugin_category, "discourse" => _discourse, "discourse_link" => _discourse_link, "screen_shot" => _screen_shot, "screen_shot_category" => _screen_shot_category, "learn" => _learn, "learn_category" => _learn_category, "seo_tag" => _seo_tag, "seo_alias_link" => _seo_alias_link, "seo_words" => _seo_words, "seo_description" => _seo_description, "seo_language" => _seo_language, "seo_language_link" => _seo_language_link, "cms_post_category_id" => _cms_post_category_id} = allreq) do

      create_cms_posts = case PostQuery.insert_post(allreq) do
         {:ok, _post} 		   -> 
            conn
            |> put_status(200)
            |> json(%{message: "The post has been created."})            
         {:error, changeset}  ->
            IO.inspect changeset
            conn
            |> put_status(403)
            |> json(%{error_code: "403"})
      end
      create_cms_posts 
   end

and my insert function in elixir project :

	def insert_post(params) do
		changeset = Post.changeset(%Post{}, params)
		case Repo.insert(changeset) do
			{:ok, post} 				-> 	{:ok, post}
			{:error, changeset} ->  {:error, changeset}
		end
	end

do you see any problem in this ?

and changeset

    def changeset(struct, params \\ %{}) do
        struct
        |> cast(params, @all_fields)
        |> validate_required(@all_fields)
        |> unique_constraint(:seo_alias_link, name: :index_of_post_alias_unique_link, message: "alias link already exists.")
        |> validate_inclusion(:seo_language, ["en", "fa"])
        |> validate_inclusion(:group_acl, ["admin", "actived", "unactived", "blocked"])
        |> validate_inclusion(:post_type, ["article", "shop", "free"])
        |> validate_length(:title, min: 3, max: 100)
        |> validate_length(:description, min: 100)
        |> validate_length(:seo_words, min: 3, max: 100)
        |> validate_length(:seo_description, min: 50, max: 264)
        |> foreign_key_constraint(:cms_post_category_id)
    end

Is the field actually part of @all_fields? The custome type should validate the format during cast or am I missunderstanding the custom type stuff? And how does the schema looks like?

1 Like

is in the controller params, so :cms_post_category_id is in @all_fields probably, otherwise I don’t see how it gets into the changeset.

Maybe do |> foreign_key_constraint(:cms_post_category_id) only if the changeset is valid (that is, cms_post_category_id’s uuid is valid). However, it shouldn’t matter. The error (Ecto.ChangeError) seems to come from the changeset anyway (and not from the database). Maybe add a helper function to check for the validity of the uuid before foreign_key_constraint.

1 Like

@NobbZ , my schema is : https://gist.github.com/shahryarjb/5a1a6e1ee53e9d65e846ca7d846a3c2d

My user who is admin in my web service have to comply the rules , especially he should send valid category_id which is UUID.

I user Postgres.

If I’m not mistaken, a :binary_id cannot be validated during the changeset process because it can be a different actual type based on the database that you are using. For example, Postgres will use Ecto.UUID as it’s :binary_id type. Whereas mongo will use a BSON.ObjectId.

Edit

This unfortunately leads to a :binary_id being any valid binary. Of course, this isn’t true in practice, but it is the best that can be done.

1 Like

Humm… then , how can I validate UUID , do I have a way for this?

It can be probably checked on per adapter basis, so postgrex would check for uuid. So @shahryarjb you should try adding a check for it before the actual dumping which I suppose happens either in cast or foreign_key_constraint. You can use the same function https://hexdocs.pm/ecto/Ecto.UUID.html#dump/1 but instead of raising, mark the changeset as invalid.

I have heard not to use raise in my project, because it is a time bomb.

It most certainly could. I just meant that you cannot do it from Ecto.Changeset.cast/4 alone because it does not know which adapter you are going to be using. A schema is not attached to an adapter.

1 Like

I have heard not to use raise in my project, because it is a time bomb.

I actually use raise quite often (“fail fast” and all that). I wouldn’t call it a time bomb …

1 Like