Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fail to mount an API with default values on mutually_exclusive params #1717

Open
gencer opened this issue Dec 15, 2017 · 16 comments
Open

Fail to mount an API with default values on mutually_exclusive params #1717

gencer opened this issue Dec 15, 2017 · 16 comments

Comments

@gencer
Copy link
Contributor

gencer commented Dec 15, 2017

Definition:

params do
   optional :folder, type: String, default: nil
   optional :tag, type: Array, default: nil
   exactly_one_of :folder, :tag
end

header:

Content-Type: application/json;

request body:

{
	"tag": [1,2,3]
}

or

{
	"folder": "/hello"
}

got:

{
	"error": "folder, tag are mutually exclusive"
}
@rnubel
Copy link
Contributor

rnubel commented Dec 15, 2017

Does removing default: nil for both params change the behavior?

@gencer
Copy link
Contributor Author

gencer commented Dec 15, 2017

Definitely, did! Also, error message now shows the correct message. (not for mutually)

Instead of default value, I set allow_blank and/or regexp for better data integrity.

@dblock dblock added the bug? label Dec 17, 2017
@dblock
Copy link
Member

dblock commented Dec 17, 2017

Still a bug, right?

@gencer
Copy link
Contributor Author

gencer commented Dec 17, 2017

Yes. Default value can be useful. Exactly one of means one of them will be empty or have a default value I gave.

Update: Oh, yes, also giving a default value turns them to mutually exists parameters which is a bug.

@dblock
Copy link
Member

dblock commented Dec 17, 2017

Would appreciate a pull request even if it's just failing specs.

@gencer
Copy link
Contributor Author

gencer commented Dec 18, 2017

Before I send a PR, lets make sure my steps are correct.

After I review the code, I found that, In fact :default should be dropped. In exactly_one_of we should not use default. Because, if we do that then we cannot know which parameter given by user or by default. Due to this grape simply tell us that they are mutually exclusive, in other mean it is limited.

So, I no longer thinks this is a bug. This is an expected behavior.

I think clearly stating this on documentation will be enough.

@dblock
Copy link
Member

dblock commented Dec 19, 2017

Aha. If both values have a default it already violates exactly_one_of, but maybe if one of them has a default and you don't pass anything, then it should work?

@gencer
Copy link
Contributor Author

gencer commented Dec 20, 2017

Yes. To be clear;

Definition:

params do
   optional :folder, type: String, default: nil
   optional :tag, type: Array, default: nil
   exactly_one_of :folder, :tag
end

Request:

{
	"folder": "/hello"
}

It will fail. Why? Because I only give :folder but I also give default: nil for :tag in definition too. :tag should NOT exists in params as a key. It should not have a value like nil or empty string. It should not exists in the first place.

I believe current behavior is the more efficient and correct behavior. We just need to tell that "do not assign even nil values" to any exactly_one_of definitions.

At first I thought giving a default value should be okay. But we should not. Exactly one parameter should be exists in params bag. So we can check which key is exists not by nil or emptyness.

To prevent empty and nil values, we should use proc or regexp.

@dblock
Copy link
Member

dblock commented Dec 20, 2017

Correct. A default of nil is still a default. Maybe rewrite the title of this issue to say something like the above scenario should not even load and produce an error?

@gencer gencer changed the title exactly_one_of is not functioning as expected and says missing :default values on mutually_exclusive params Dec 21, 2017
@gencer
Copy link
Contributor Author

gencer commented Dec 21, 2017

This has been clarified by adding a note. #1720.

@ghiculescu
Copy link
Contributor

ghiculescu commented May 26, 2020

I think https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#nil-values-for-structures is causing new problems similar to this issue.

params do
   optional :folder, type: Array[String], default: []
   optional :tag, type: Array[String], default: []
   mutually_exclusive :folder, :tag
end

This will error even if neither param is provided.

@dblock
Copy link
Member

dblock commented May 27, 2020

If neither param is provided then both get the default value and those are mutually exclusive, which errors by design. LGTM.

@ghiculescu
Copy link
Contributor

The problem for me is this is a regression - it wasn't an issue before https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#nil-values-for-structures, because we didn't need to provide a default. Which meant the mutually_exclusive validation would only error if both props were actually provided.

Now, requests that previously wouldn't fail will now fail. The alternative is to not use default: [] but then you get this regression instead:

  # 1.3.1 = []
  # 1.3.2 = nil

@dblock
Copy link
Member

dblock commented May 27, 2020

I understand this is not the way it behaved before, but I would say that this was a bug, fixed since. We probably didn't even have specs for this, hence a minor version bump and a regression. I am not saying we did it on purpose, but we're trying to avoid similar problems moving forward.

If you have a mutually exclusive set of values that are both set when none are provided by the caller they are no longer mutually exclusive, which violates the principle of least surprise. Saying "they are mutually exclusive to be supplied by the user" is interpreting mutually_exclusive conveniently.

I think the workaround is straightforward:

params do
   optional :folder, type: Array[String]
   optional :tag, type: Array[String]
   mutually_exclusive :folder, :tag
end
get do
  folder_or_tag = params[:folder] || params[:tag] || []
  ...
end

Thoughts?

@dblock
Copy link
Member

dblock commented May 27, 2020

Now that we have more attention on this issue, I think we should close this. IMHO it behaves as intended.

Taking this back. An API with mutually exclusive and default values should not mount. This is the feature request.

@dblock dblock changed the title :default values on mutually_exclusive params Fail to mount an API with default values on mutually_exclusive params May 27, 2020
@ghiculescu
Copy link
Contributor

ghiculescu commented May 27, 2020 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants