-
-
Notifications
You must be signed in to change notification settings - Fork 6.9k
Separate Validation of the Client Submission from Resource Dependent checks #5385
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
Comments
Two questions initially:
As I see it the two-phase behaviour would be worse not better from the user perspective, as we'd hide uniqueness failures if we're in the presence of also having another unrelated field with a validation failure. I'm not 100% a "no" on this, but I don't have any interest in tackling it myself. Addressing it as an alternative Serializer base class or something similar would probably be the best course. Would be very happy to be able to provide different styles of behaviour, without us having to take that work on in the core package. |
From my response (to the same question) in #4509:
To your second question:
Yes. Not just uniqueness but any resource-dependent checks like concurrency. Until the sender provides a valid request, comparing it to the database is unpredictable. It creates issues like #3381 which is currently addressed through workarounds like #4217. Do you really feel that the ideal behavior for a uniqueness validator is to handle a bunch of unrelated error cases? On the flip side, running both concurrently doesn't guarantee that the resulting list of errors is exhaustive. For example, a value that doesn't validate can never fail a uniqueness tests. For backwards compatibility, a two-stage validation system can still accumulate all of the errors before raising anything -- except that we'd skip resource-dependent checks involving any field that already failed request-level validation (the ideal fix for #3381).
I appreciate your interest in keeping the core slim, but this is part of the REST spec. Unlike the last time I reported a spec issue, the change proposed here is fully compatible with DRF's architecture. In fact, all it requires is that DRF use a pattern of validation that is already the best practice in the Django universe. It also eliminates the need for the existing workaround in #4217. FWIW the right thing to do is to run the resource-dependent checks in a transaction with the save method. That would address the race condition noted in #3876 and other conceivable race conditions -- |
I can assemble a PR that includes the acceptable changes, but want to make sure the effort is not going to go to waste. |
If you provide a pull request then we can make a judgement call on that - that's not something that we can do up front.
In which case the sensible thing to do would be to do the work as a third party package instead. If there's clear uptake, or if this issue is repeatedly re-raised then we could consider adopting it. I've not hesitation in agreeing that 409 for uniqueness errors might be marginally nicer, but "it's in the REST spec" is over-playing it - there's nothing in the HTTP specification that precludes using 400 for those types of validation error, and it's also still unclear to me what status code you'd want to expect in the case where you have some field with value validation errors, and other fields with uniqueness validation errors. If you've a proposal which is clearly a more simple or robust approach than #4217, or if you can demonstrate that it'd resolve some other behavioral issue, then go for it. |
Quoting from the spec:
Uniqueness and concurrency are resource-dependent issues. The existence of an error code specifically documented as covering this case would be a strong "implicit" argument for me. However, the 400 error also says (again, emphasis added):
The easy test for a client error is whether adequate client-side validation would have discovered it. It's self-evident that uniqueness tests (except within a multi-object submission) don't meet this criteria. It follows that uniqueness constraints are not client issues so 400 is not the spec response for these issues. This is also the consensus of the community.
Re-read (1) in my original post. Comparing invalid client data (400) to the resource (409) is logically dubious and creates unpredictable errors. Until a client resolves all client-side errors (400), the ideal flow wouldn't even run the other (409) checks. Even in a backwards-compatible mode (i.e. running both checks simultaneously), the client must resolve their errors (400) first so this response code always takes precedence. It's even logically possible that the resource-dependent issue will fix itself (e.g. a duplicate is deleted) while the client fixes client-side (400) issues. The opposite is never true.
I certainly can, but I wanted to make sure the change and proposed approach wasn't DOA. #4231 was the last significant issue I ran into. It took a lot of back-and-forth to reach consensus that there was a problem and then the discussion of implementation options made it clear that a compact PR was going to be rejected because it would violate architecture-level assumptions. |
My point of view here is that 400 is OK for uniqueness constraints. You may want to return 409 for uniqueness issues in some specific use cases such as acquiring a lock or something similar but that should better be performed by specific code rather than the generic DRF one that may have some issues here and there (such as concurrency issues, one of them being in our list of issues). |
For most users that'd be a degradation in expected behavior, rather than an improvement. (You're going to have a knock-on effect all the way through to end-users editing form fields in an application, correcting an error in one field, resubmitting the form, and unexpected having an error in a different field, which previously appeared to be okay.) By all means go ahead and look into altering the current behavior, but I'd strongly suggest that resolution in an optional third party package would be the best first-pass. |
Anything's "OK" by client and server consent. I thought I was adopting a REST-compliant framework, not a by-consent API framework. I certainly would like it to at least allow a REST-compliant endpoint.
OK. So how should this be done in the current architecture?
Am I missing something obvious? |
I've repeatedly covered this case:
|
What you’re missing is that you’ve been told repeatedly that you’re welcome to implement this behaviour yourself but that it’s not something we’d accept in the core framework. I can see that that is not the answer you’re looking for but I need to ask you to accept it nonetheless. I’m afraid our time to maintain the framework is limited. We can’t continue this discussion here now. Thank you for your understanding. |
@carltongibson. I'm more than happy to contribute to a 3rd party plugin when that model makes sense (e.g. significant contributions to My most recent question was how a 3rd party plugin would implement this behavior subject to the current architecture. I realize I'm not an expert at DRF's architecture, but haven't seen a natural place/way to do so. If someone can give me a satisfactory answer, I'll do it. More importantly, I'm not demanding that you fix it for me. I'm offering to submit a PR. I'm trying to get a rough architecture/approach pre-approved. I'm no paragon of OSS, but that's normally a welcomed approach that's considerate of everyone's time. We use the package and would be motivated to help fix issues if it didn't feel I was pulling teeth each time I had to communicate with the team. If your concern is ongoing maintenance, you could even make the improvements conditional on contribution to development or a support package. Instead... well you wrote the post. |
@claytondaley as for the how you can implement this, drop the unique constraint validators, override the view function and catch integrity errors. |
@xordoquy Thanks. That's certainly more pythonic than introspecting error codes (the obvious option left). +1 for eliminating the race condition on uniqueness. +1 for supporting 409 codes on other resource consistency issues (like foreign keys). It sounds like I may be able to wrap this up in Mixins (+decorators for overloaded functions). To make the code reusable, can I iterate through each field's validators on a serializer instance? or do they get moved somewhere problematic that I need to remove them before instance creation? Are there any other edge cases you've run into that are worth anticipating? |
Totally fair enough - for which the answer is no. I don't agree that the proposed behavior would be an improvement.
We still have to take a judgement on what the default behavior should be. Having a default that masks uniqueness errors in the presence of other unrelated errors on other fields would be a degradation for most users.
There's no such thing as "REST-compliant" in the context of HTTP status codes. There is the HTTP spec, tho. 400 and 409 are behaviorally identical, the only difference is semantic, and 400 is broad enough to be a reasonable choice for both types of validation error. We provide further semantic information if users require it, in the form of per-field validation codes and messages, which have the benefit of being able to differentiate in the case where there are both uniqueness and value errors. I think it would be great to have an option of the behavior that you require in the way you've specified it, but I'm not convinced we'd want it to be the default, and I think that "as a third party" is both the most practical way to move this forward, and the best option for our users. |
I was trying to figure out how to return a 409 error on uniqueness issues. When researching the possibility, I found several closed issues (#1848, #4509) and the logically related issue #3381. I'd like to make the case that handling validation and uniqueness (technically any database-dependent validation) should occur in two stages.
409
errors much easier. Because 409 is "conflict with the existing resource", the pattern actually mirrors the spec.Reason (1) is actually the root cause of #3381. The implemented solution (#4217) was to make uniqueness checks robust to validation-derived issues.
Reason (3) is actually the most relevant to me. Right now, the ONLY way to distinguish a uniqueness issue in the
handle exception
stage is to introspect error messages. To make things worse django hardcodes these messages in ways that cannot be used by the detection code. To muddy the waters further, the error I get for uniqueness on a singleid
column is (according to the inline documentation) part of a "unique together" check. I'm not sure if I'm misunderstanding the inline documentation, but it would be annoying if a future "fix" for this issue invalidated my 409 detection code.If two-pass validation is acceptable, I suggest returning a subclass of
ValidationError
(e.g.ResourceValidationError
) so existing error handlers are unaffected. However, folks wanting to support409
can check for the subclass and alter behavior accordingly. Eventually, this could be implemented in the core error handler with a settings flag.The text was updated successfully, but these errors were encountered: