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 post-validation FromInputValue errors #987

Merged
merged 32 commits into from
Dec 14, 2021

Conversation

tyranron
Copy link
Member

@tyranron tyranron commented Oct 15, 2021

Required for #975

Synopsis

At the moment, FromInputValue fallibility is considered during input operation validation only. Also, the real error is erased, due to Option<T> usage, thus argument absence is unobviously mixed with convertion errors over the code base.

Being applicable to most cases, still there are some situations where the error may occur only during resolution after passing the validation (from #975 it's deserializing json::Value into Json<T: Deserialize>). At the moment, we're ending up with runtime panics in such situation, which is not good.

Solution

Make FromInputValue returning Result with the actual error preservation. Require this error be IntoFieldError convertible, so it may naturally bubble-up during operation execution.

Checklist

  • tests renewed
  • documentation updated
  • CHANGELOG entry described

@tyranron tyranron added enhancement Improvement of existing features or bugfix semver::breaking Breaking change in terms of SemVer labels Oct 15, 2021
@tyranron tyranron self-assigned this Oct 15, 2021
@tyranron tyranron marked this pull request as draft October 15, 2021 10:18
@tyranron tyranron assigned ilslv and unassigned tyranron Nov 22, 2021
@tyranron
Copy link
Member Author

cc @ilslv

Before continuing this, make sure master is built OK on latest nightly, and update it (if required) in a separate PR.

@ilslv
Copy link
Member

ilslv commented Dec 1, 2021

@tyranron PR is ready for review

Copy link
Member Author

@tyranron tyranron left a comment

Choose a reason for hiding this comment

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

@ilslv I've refactored base machinery in juniper:

  • more errors propagation;
  • simplifying code with using String in implementations rather than FieldError<S>.

Codegen needs some updates, though, described below.

NotEnough(usize),

/// Too many elements. Contains __all__ [`InputValue`] elements.
TooMuch(Vec<T>),
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 do think keeping Vec gives no actual use here, while complicates relative unsafe code (and so reasoning) a lot.

<Vec<i32>>::from_input_value(&V::list(vec![V::scalar(1), V::scalar(2), V::scalar(3)])),
Ok(vec![1, 2, 3]),
);
// TODO: all examples
Copy link
Member Author

Choose a reason for hiding this comment

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

@ilslv we should provide here a complete test suite. But let's do it in a separate PR after merging this.

@@ -743,5 +740,5 @@ fn try_parse_fn<S, T>(v: &InputValue<S>) -> bool
where
T: FromInputValue<S>,
{
<T as FromInputValue<S>>::from_input_value(v).is_some()
T::from_input_value(v).is_ok()
Copy link
Member Author

Choose a reason for hiding this comment

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

We don't actually propagate any errors here, only silence them as we did before. Let's do propagation.

.map(ID),
_ => None,
}
fn from_input_value(v: &InputValue) -> Result<ID, FieldError> {
Copy link
Member Author

Choose a reason for hiding this comment

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

We should be more accurate here and use FieldError<S> to omit redundant scalar conversions.

}
}
None => return Box::pin(::juniper::futures::future::ready(
Err(::juniper::FieldError::from("This GraphQLType has no name"))
Copy link
Member Author

Choose a reason for hiding this comment

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

@ilslv to DRYing the generated code a bit, let's move such things to helper functions inside juniper crate, and reuse them here.

ilslv added 10 commits December 13, 2021 08:41
…nput-value-resolve-errors

# Conflicts:
#	juniper/CHANGELOG.md
# Conflicts:
#	integration_tests/juniper_tests/src/issue_372.rs
#	juniper/CHANGELOG.md
#	juniper/src/validation/rules/fields_on_correct_type.rs
#	juniper_actix/src/lib.rs
@ilslv
Copy link
Member

ilslv commented Dec 14, 2021

@tyranron this PR is ready for review

},
Ok,
)
})?
Copy link
Member Author

Choose a reason for hiding this comment

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

@ilslv I don't follow the logic here.

It was:

  1. args.get::<#ty>(#name) returns Option<T>.
  2. If it's None, then ::juniper::FromInputValue::<#scalar>::from_implicit_null returns Option<T>.
  3. If it's None we panic.

Now it is:

  1. args.get::<#ty>(#name) returns Result<Option<T>, FieldError>.
  2. If it's Ok, we check the inner opt.
  3. If it Some we map it to Some(Ok(v)) and so have Result<Option<Result<T>, FieldError>, FieldError>.

Why?

It seems the more natural way is:

  1. Unpack the Result early: args.get::<#ty>(#name)?
  2. .transpose() the inner error in Option and return it with ?.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh... understood. .map_or_else doesn't map an inner Option value, but rather maps the whole Option into something.

.ok_or_else will do better here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Meh... .ok_or_else() cannot map absense to Result::Ok.

@tyranron tyranron marked this pull request as ready for review December 14, 2021 16:55
Copy link
Member Author

@tyranron tyranron left a comment

Choose a reason for hiding this comment

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

@ilslv LGTM

let e = ::juniper::IntoFieldError::into_field_error(e);
::juniper::FieldError::new(
format!(#err_text, e.message()),
e.extensions().clone(),
Copy link
Member Author

Choose a reason for hiding this comment

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

This mess is better be replaced with .map_message method on FieldError.

},
Ok,
)
})?
Copy link
Member Author

Choose a reason for hiding this comment

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

Meh... .ok_or_else() cannot map absense to Result::Ok.

@tyranron tyranron self-assigned this Dec 14, 2021
@tyranron tyranron merged commit 46be97a into master Dec 14, 2021
@tyranron tyranron deleted the allow-from-input-value-resolve-errors branch December 14, 2021 17:30
@ilslv ilslv mentioned this pull request Jan 10, 2022
15 tasks
@tyranron tyranron added this to the 0.16.0 milestone Apr 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Improvement of existing features or bugfix semver::breaking Breaking change in terms of SemVer
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants