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

Allow null option for properties #64

Merged
merged 4 commits into from
Jul 29, 2016

Conversation

andreashug
Copy link
Contributor

Proposal for solving #47

The Swagger specification in version 2 doesn't support 'null' as
property value. This adds a vendor extension 'x-nullable' which
allows properties to be 'null' if set to 'true'.

@msander
Copy link

msander commented Jan 6, 2016

+1

@analogue
Copy link
Contributor

analogue commented Jan 9, 2016

Thanks for the PR. Haven't had time to review yet so it will probably be next week.

@patch('jsonschema._validators.type_draft4')
def test_skip_when_nullable_property_schema_and_value_is_None(
m_draft4_type_validator, minimal_swagger_spec):
param_schema = {'name': 'foo', 'x-nullable': True, 'type': 'string'}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you mean for this to be prop_schema with no name key.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I'll change that.

@analogue
Copy link
Contributor

What is your interpretation of x-nullable when required==True? If I'm reading this correctly, when a property is nullable and required and the value passed is None, then that makes it OK. Hence, there would have to be changes to unmarshal_primitive(..) and marshal_primitive(..)

@andreashug
Copy link
Contributor Author

My understanding of the spec is that required==True only means the property must exist and x-nullable==True allows null as value.

required==False required==True
x-nullable==False { }->ok {"x": null}->fail { }->fail {"x": null}->fail
x-nullable==True { }->ok {"x": null}->ok { }->fail {"x": null}->ok

I'm not sure if this is possible because unmarshal_object(..) re-introduces missing properties with None. What do you think?

@andreashug
Copy link
Contributor Author

While searching for a good solution, I discovered that JSON Schema allows a list of types - e.g. { "schema": { "type": ["string", "null"] }
Actually I think this would be a much better way to solve my problem and it works well with jsonschema. Unfortunately it's not supported bravado-core yet. I'll try to come up with a proposal so this pull request can be postponed.

@wasbazi
Copy link

wasbazi commented Jun 8, 2016

This would be a really nice feature to have! There is a long thread on the OpenAPI-Specification (formerly The Swagger Specification) repository discussing how they want to solve this and leaning towards the x-nullable case OAI/OpenAPI-Specification#229

Unfortunately it seems from the discussion that using arrays for type is not intended with the 2.0 spec (which seems to be indicated as feature frozen) OAI/OpenAPI-Specification#229 (comment)

Are there things I can do to help get this PR finished?

@cyncyncyn
Copy link

As @wasbazi says, this would be a nice feature to have!
I also offer my help in order to get this PR merged.
Thanks!

@sjaensch
Copy link
Contributor

The CR has an outstanding issue, see #64 (comment)
The proposed solution of going with a list of types as allowed by JSON Schema will not be possible, the Swagger spec authors already clarified that Swagger 2.0 is not supposed to allow a list of types.

@normalex
Copy link

normalex commented Jul 1, 2016

Can we merge this? people are eager to see it released.

andreashug added a commit to andreashug/bravado-core that referenced this pull request Jul 22, 2016
The schema passed to type_validator is a prop_schema in these test cases.
Yelp#64 (diff)
The Swagger specification in version 2 doesn't support 'null' as
property value. This adds a vendor extension 'x-nullable' which
allows properties to be 'null' if set to 'true'.
See OAI/OpenAPI-Specification#229
@coveralls
Copy link

coveralls commented Jul 22, 2016

Coverage Status

Coverage increased (+0.01%) to 96.989% when pulling a6c1382 on andreashug:features/allownull into 25bb775 on Yelp:master.

The schema passed to type_validator is a prop_schema in these test cases.
Yelp#64 (diff)
@coveralls
Copy link

coveralls commented Jul 22, 2016

Coverage Status

Coverage increased (+0.01%) to 96.989% when pulling 2a438d6 on andreashug:features/allownull into 25bb775 on Yelp:master.

The tests show that unmarshaling is not affected by the null option.
@coveralls
Copy link

Coverage Status

Coverage increased (+0.01%) to 96.989% when pulling c581aab on andreashug:features/allownull into 25bb775 on Yelp:master.

@andreashug
Copy link
Contributor Author

Finally found some time to continue with this.

  1. Rebased on recent master
  2. Fixed test according to Allow null option for properties #64 (comment)
  3. Integration tests for unmarshaling

The good news is that unmarshaling doesn't seem to be affected (see matrix in https://github.com/andreashug/bravado-core/blob/c581aaba8924afe340f124375015afc9f5743762/tests/unmarshal/unmarshal_nullable_test.py)

The not so good news however is, I'm not sure how marshaling should behave in combination with x-nullable. Currently, marshal_object() drops keys whose value is None, which leads to a ValidationError (required property) ->

if v is None:
continue
. One might think, it should keep the key if x-nullable is True. Or we could say that x-nullable is only for properties and not accounted in parameter specs.

Any opinions on that?

@sjaensch
Copy link
Contributor

Great stuff @andreashug! I'm still travelling, I'll take a closer look at the patch on Tuesday.
Just to make sure I understand the issue you're seeing: If an endpoint expects a parameter of type object that has a x-nullable field then we get a ValidationError?

m_draft4_type_validator, minimal_swagger_spec):
prop_schema = {'x-nullable': False, 'type': 'string'}
args = (None, prop_schema['type'], None, prop_schema)
list(type_validator(minimal_swagger_spec, *args))
Copy link
Contributor

@sjaensch sjaensch Jul 23, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use keyword arguments here as well, just like you did in the test above. It's more readable. I know it makes the test a bit longer since you have to repeat the arguments below, but I think that's fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because assert_called_once_with() expects the arguments without keywords. The existing tests in this file do it this way so I aligned with them ;-)

Reduce test complexity by splitting up in multiple tests, suggested
in Yelp#64 (comment)
@coveralls
Copy link

coveralls commented Jul 24, 2016

Coverage Status

Coverage increased (+0.01%) to 96.989% when pulling 0a65478 on andreashug:features/allownull into 25bb775 on Yelp:master.

@andreashug
Copy link
Contributor Author

Regarding marshal_object(): The ValidationError is raised when x-allownull is True, the value is required the value is set to None. See https://dpaste.de/D0ax

@sjaensch
Copy link
Contributor

This is great! Could you please create another paste? I didn't manage to open it in time. 😁

@andreashug
Copy link
Contributor Author

Sure: http://pastebin.com/U3xeA48R (Seems like there is a bug in expiration settings on dpaste.de)

@sjaensch
Copy link
Contributor

Thanks again @andreashug! While I think we could even ship this without fixing the edge case about a required x-nullable parameter, I think the fix is not that difficult:

diff --git a/bravado_core/marshal.py b/bravado_core/marshal.py
index 536e627..8a3771a 100644
--- a/bravado_core/marshal.py
+++ b/bravado_core/marshal.py
@@ -125,13 +125,14 @@ def marshal_object(swagger_spec, object_spec, object_value):
     result = {}
     for k, v in iteritems(object_value):

-        # Values cannot be None - skip them entirely!
-        if v is None:
-            continue
-
         prop_spec = get_spec_for_prop(
             swagger_spec, deref(object_spec), object_value, k)

+        # Values cannot be None unless x-nullable is set
+        is_nullable = prop_spec and prop_spec.get('x-nullable', False)
+        if v is None and not is_nullable:
+            continue
+

With it the test you wrote (which is in your dpaste) passes. We might want to handle the other cases (nullable primitives etc.) as well, but honestly I think it's not that important, since the use case for x-nullable are model properties in responses. You're welcome to incorporate this small change as well as the test in the branch though.

I'm going to find some time to test this with bravado and make sure it does what we want. 😊

@sjaensch
Copy link
Contributor

An manual test using bravado to query an endpoint that returns a model with an x-nullable field worked perfectly! I'm going to merge this in and work on a second pull request that will contain my above change, a version bump and a big thank you to @andreashug in the changelog. 😊

@sjaensch sjaensch merged commit 6496b4d into Yelp:master Jul 29, 2016
@andreashug
Copy link
Contributor Author

Yay, thanks \o/

@andreashug andreashug deleted the features/allownull branch July 30, 2016 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants