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

first version of defaults injection #1

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open

first version of defaults injection #1

wants to merge 24 commits into from

Conversation

rkuhn
Copy link
Member

@rkuhn rkuhn commented Feb 27, 2020

No description provided.

Copy link
Member Author

@rkuhn rkuhn left a comment

Choose a reason for hiding this comment

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

@wngr I have self-reviewed, should be good to go. See some default_for_schema test for an example of conjuring a whole instance from thin air. (real code should then validate that instance)

@rkuhn
Copy link
Member Author

rkuhn commented Feb 28, 2020

It would also be great if you could give it a spin and compare the performance to version 3.2.0, with and without making use of defaults in a reasonably large example schema. And not that I’m overly worried, but this is some complex stuff, so it might be warranted to let it keep crunching schemas and validations for a few hours while observing memory usage.

Copy link

@wngr wngr left a comment

Choose a reason for hiding this comment

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

First round of reviews. Still wrapping my head around this ..

src/json_schema/keywords/items.rs Show resolved Hide resolved
@@ -37,10 +40,10 @@ impl fmt::Debug for dyn Keyword + 'static {
macro_rules! keyword_key_exists {
Copy link

Choose a reason for hiding this comment

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

I'm confused by this macro. Looking at the usage, maybe_val is &serde_json::Value. How can this work, given that if and else have incompatible types?

Copy link
Member Author

Choose a reason for hiding this comment

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

The trick is that the else branch does not yield a value but returns from the enclosing function — these macros are purely lexical, typechecking happens only after the program has been modified.

@@ -8,7 +8,7 @@ fn visit_specs<F>(dir: &path::Path, cb: F)
where
F: Fn(&path::Path, Value) + Copy,
{
let contents = fs::read_dir(dir).ok().unwrap();
let contents = fs::read_dir(dir).expect(&*format!("cannot list directory {:?}", dir));
Copy link

Choose a reason for hiding this comment

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

very helpful for newcomers indeed 🎩

src/json_schema/schema.rs Outdated Show resolved Hide resolved
schema.add_defaults_recursive(top, id, scope);
}

// step 2: use explicit default if present
Copy link

Choose a reason for hiding this comment

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

why not do this before step 1? If you have a default, you don't need to go down into tree.

Copy link
Member Author

Choose a reason for hiding this comment

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

The point of the traversal is not to get a default for this node, it is to mutate the whole subtree to get defaults, too.

src/json_schema/schema.rs Show resolved Hide resolved
@@ -222,6 +325,8 @@ impl Schema {
if let Some(validator) = keyword.keyword.compile(def, context)? {
if is_exclusive_keyword {
validators = vec![validator];
} else if keyword.keyword.place_first() {
validators.splice(0..0, std::iter::once(validator));
Copy link

Choose a reason for hiding this comment

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

What's this for? This will remove an existing validator.

Copy link
Member Author

Choose a reason for hiding this comment

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

Nope, it replaces the empty range at the beginning of the vector, i.e. pushes to the front.


// necessary due to object being mutated in the loop
let keys = object.keys().cloned().collect::<Vec<_>>();
'main: for key in keys.iter() {
Copy link

Choose a reason for hiding this comment

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

does that need to be named? you don't seem to break anyway

Copy link
Member Author

Choose a reason for hiding this comment

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

I left it in because it clarifies the continue that appears many lines down.

for schema in schemas.iter() {
let mut result = schema.validate_in(&val, path);
if result.is_valid() && result.replacement.is_some() {
*val.to_mut() = result.replacement.take().unwrap();
Copy link

Choose a reason for hiding this comment

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

Shouldn't all possible replacements be merged into val instead of it being overwritten?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, indeed that is the case: notice how the updated &val will be passed to the next schema two lines up.

rkuhn and others added 3 commits March 1, 2020 19:44
Co-Authored-By: Oliver Wangler <oliver@wngr.de>
Co-Authored-By: Oliver Wangler <oliver@wngr.de>

for url in self.schemes.iter() {
let schema = scope.resolve(url);
// second pass if defaults are enabled to check that the result is stable

Choose a reason for hiding this comment

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

What is an example of a schema where you might have divergent defaults? And can we add it as a test case?

Copy link
Member Author

Choose a reason for hiding this comment

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

good point, I shall try to make a small one

Copy link

@wngr wngr left a comment

Choose a reason for hiding this comment

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

This is good to go from my point of view.

@rkuhn
Copy link
Member Author

rkuhn commented Mar 12, 2020

Performance measurements (debug build):

  • this PR with supply_defaults: 16.7ms compilation, 4.4ms validation
  • this PR without supply_defaults: 14.5ms compilation, 1.9ms validation
  • 3.2.0: 15.7ms compilation, 1.7ms validation

measurements with release build:

  • this PR with supply_defaults: 1.9ms compilation, 560µs validation
  • this PR without supply_defaults: 1.7ms compilation, 170µs validation
  • 3.2.0: 1.8ms compilation, 150µs validation

Conclusion: about 10% slow-down when not using the new feature.

@wngr
Copy link

wngr commented Mar 12, 2020

what about a feature flag? .. but that's probably up to the maintainers

sbihel and others added 7 commits June 5, 2020 21:21
…r detail (s-panferov#67)

Include the name of the unallowed property name in the errors::Properties details, enabling users to more precisely pin-point the issue in the JSON document.
* feat(json_schema): Draft 7 date and time formats

* chore(*): Address warnings

* feat(json_schema): Draft 6 JSON pointers

* feat(json_schema): Draft 7 IDN hostname

Kind of. There's no exclusion between "regular" hostnames and IDNs.

* feat(json_schema): Draft 7 conditionals

* feat(json_schema): Draft 7 content media type/encoding

* feat(json_schema): Draft 7 remaining formats

* chore(*): cleanup

Co-authored-by: Simon Bihel <simon.bihel@vonage.com>
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.

7 participants