Merge master

This commit is contained in:
Aurelien Foucault 2020-10-08 15:28:07 +02:00
commit f6e05d47a1
18 changed files with 104 additions and 60 deletions

View File

@ -28,3 +28,14 @@ jobs:
run: cargo build --all --verbose
- name: Run tests
run: cargo test --all --verbose
# build on nightly
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: clippy, rustfmt
- name: Build on nightly without features
run: cargo build --no-default-features
- name: Build on nightly
run: cargo build --all --verbose

View File

@ -75,7 +75,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
get_fields.push(quote! {
let #ident: #ty = #crate_name::InputValueType::parse(
Some(#crate_name::Value::Object(obj.clone()))
).map_err(#crate_name::InputValueError::propogate)?;
).map_err(#crate_name::InputValueError::propagate)?;
});
fields.push(ident);
@ -111,7 +111,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
let #ident: #ty = {
match obj.get(#name) {
Some(value) => #crate_name::InputValueType::parse(Some(value.clone()))
.map_err(#crate_name::InputValueError::propogate)?,
.map_err(#crate_name::InputValueError::propagate)?,
None => #default,
}
};
@ -119,7 +119,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
} else {
get_fields.push(quote! {
let #ident: #ty = #crate_name::InputValueType::parse(obj.get(#name).cloned())
.map_err(#crate_name::InputValueError::propogate)?;
.map_err(#crate_name::InputValueError::propagate)?;
});
}

View File

@ -19,7 +19,7 @@ impl ScalarType for StringNumber {
Ok(value.parse().map(StringNumber)?)
} else {
// If the type does not match
Err(InputValueError::ExpectedType(value))
Err(InputValueError::expected_type(value))
}
}

View File

@ -31,11 +31,11 @@ This attribute support 4 operators to create complex rules :
- `and` : perform a `and` operation between two rules. (If one rule return an error the `and` operator will return the error. If both rules return a error it's the first one that will be returned).
- `or` : perform a `and` operation between two rules. (If both rules return an error the error returned is the first one)
- `or` : perform a `or` operation between two rules. (If both rules return an error the error returned is the first one)
- `chain` : take a set of rules and run them until one return an error or return `Ok()` if all rules pass.
- `chain` : take a set of rules and run them until one return an error or return `Ok` if all rules pass.
- `race` : take a set of rules and run them until one return `Ok()` if they all fail it return the last error.
- `race` : take a set of rules and run them until one return `Ok` if they all fail it return the last error.
```rust
#[derive(SimpleObject)]

View File

@ -20,7 +20,7 @@ impl ScalarType for StringNumber {
Ok(value.parse().map(StringNumber)?)
} else {
// 类型不匹配
Err(InputValueError::ExpectedType(value))
Err(InputValueError::expected_type(value))
}
}

View File

@ -0,0 +1,68 @@
# 字段守卫(Field Guard)
您可以在给`Object`的字段定义`守卫`。 这允许在运行字段的代码逻辑之前添加检查。
义`守卫`由你预先定义的规则组成。 规则是一种实现`Guard`特质的结构。
```rust
#[derive(Eq, PartialEq, Copy, Clone)]
enum Role {
Admin,
Guest,
}
struct RoleGuard {
role: Role,
}
#[async_trait::async_trait]
impl Guard for RoleGuard {
async fn check(&self, ctx: &Context<'_>) -> Result<()> {
if ctx.data_opt::<Role>() == Some(&self.role) {
Ok(())
} else {
Err("Forbidden".into())
}
}
}
```
一旦定义了规则,就可以在`guard`字段属性中使用它。
此属性支持4个运算符创建复杂的规则
-`and`:在两个规则之间执行`与`运算。 (如果一个规则返回错误,则`and`运算符将返回错误。如果两个规则均返回错误,则将是第一个返回的错误)。
-`or`:在两个规则之间执行`或`运算。 (如果两个规则都返回错误,则返回的错误是第一个)
-`chain`:采用一组规则并运行它们,直到返回错误或如果所有规则都通过则返回`Ok`。
-`race`:采用一组规则并运行它们,直到其中一个返回`Ok`。
```rust
#[derive(SimpleObject)]
struct Query {
#[graphql(guard(RoleGuard(role = "Role::Admin")))]
value: i32,
#[graphql(guard(and(
RoleGuard(role = "Role::Admin"),
UserGuard(username = r#""test""#)
)))]
value2: i32,
#[graphql(guard(or(
RoleGuard(role = "Role::Admin"),
UserGuard(username = r#""test""#)
)))]
value3: i32,
#[graphql(guard(chain(
RoleGuard(role = "Role::Admin"),
UserGuard(username = r#""test""#),
AgeGuard(age = r#"21"#)
)))]
value4: i32,
#[graphql(guard(race(
RoleGuard(role = "Role::Admin"),
UserGuard(username = r#""test""#),
AgeGuard(age = r#"21"#)
)))]
value5: i32,
}
```

View File

@ -122,8 +122,8 @@ impl<T: InputValueType> InputValueError<T> {
Self::new(format!(r#"Failed to parse "{}": {}"#, T::type_name(), msg))
}
/// Propogate the error message to a different type.
pub fn propogate<U: InputValueType>(self) -> InputValueError<U> {
/// Propagate the error message to a different type.
pub fn propagate<U: InputValueType>(self) -> InputValueError<U> {
InputValueError::new(format!(
r#"{} (occurred while parsing "{}")"#,
self.message,

View File

@ -1,7 +1,6 @@
//! Field guards
use crate::{Context, Result};
use serde::export::PhantomData;
/// Field guard
///
@ -50,37 +49,3 @@ impl<A: Guard + Send + Sync, B: Guard + Send + Sync> Guard for Or<A, B> {
self.0.check(ctx).await.or(second_result)
}
}
/// Field post guard
///
/// This is a post-condition for a field that is resolved if `Ok(()` is returned, otherwise an error is returned.
///
/// This trait is defined through the [`async-trait`](https://crates.io/crates/async-trait) macro.
#[async_trait::async_trait]
pub trait PostGuard<T: Send + Sync> {
/// Check whether to allow the result of the field through.
async fn check(&self, ctx: &Context<'_>, result: &T) -> Result<()>;
}
/// An extension trait for `PostGuard<T>`
pub trait PostGuardExt<T: Send + Sync>: PostGuard<T> + Sized {
/// Merge the two guards.
fn and<R: PostGuard<T>>(self, other: R) -> PostAnd<T, Self, R> {
PostAnd(self, other, PhantomData)
}
}
impl<T: PostGuard<R>, R: Send + Sync> PostGuardExt<R> for T {}
/// PostGuard for [`PostGuardExt<T>::and`](trait.PostGuardExt.html#method.and).
pub struct PostAnd<T: Send + Sync, A: PostGuard<T>, B: PostGuard<T>>(A, B, PhantomData<T>);
#[async_trait::async_trait]
impl<T: Send + Sync, A: PostGuard<T> + Send + Sync, B: PostGuard<T> + Send + Sync> PostGuard<T>
for PostAnd<T, A, B>
{
async fn check(&self, ctx: &Context<'_>, result: &T) -> Result<()> {
self.0.check(ctx, result).await?;
self.1.check(ctx, result).await
}
}

View File

@ -24,7 +24,7 @@ impl ScalarType for ObjectId {
impl ScalarType for UtcDateTime {
fn parse(value: Value) -> InputValueResult<Self> {
<DateTime<Utc>>::parse(value)
.map_err(InputValueError::propogate)
.map_err(InputValueError::propagate)
.map(UtcDateTime::from)
}

View File

@ -16,7 +16,7 @@ where
.into_iter()
.map(|(name, value)| Ok((name.into_string(), T::parse(Some(value))?)))
.collect::<Result<_, _>>()
.map_err(InputValueError::propogate),
.map_err(InputValueError::propagate),
_ => Err(InputValueError::expected_type(value)),
}
}

View File

@ -16,7 +16,7 @@ where
.into_iter()
.map(|(name, value)| Ok((name.into_string(), T::parse(Some(value))?)))
.collect::<Result<_, _>>()
.map_err(InputValueError::propogate),
.map_err(InputValueError::propagate),
_ => Err(InputValueError::expected_type(value)),
}
}

View File

@ -29,11 +29,11 @@ impl<T: InputValueType + Ord> InputValueType for BTreeSet<T> {
.into_iter()
.map(|value| InputValueType::parse(Some(value)))
.collect::<Result<_, _>>()
.map_err(InputValueError::propogate),
.map_err(InputValueError::propagate),
value => Ok({
let mut result = Self::default();
result.insert(
InputValueType::parse(Some(value)).map_err(InputValueError::propogate)?,
InputValueType::parse(Some(value)).map_err(InputValueError::propagate)?,
);
result
}),

View File

@ -31,11 +31,11 @@ impl<T: InputValueType + Hash + Eq> InputValueType for HashSet<T> {
.into_iter()
.map(|value| InputValueType::parse(Some(value)))
.collect::<Result<_, _>>()
.map_err(InputValueError::propogate),
.map_err(InputValueError::propagate),
value => Ok({
let mut result = Self::default();
result.insert(
InputValueType::parse(Some(value)).map_err(InputValueError::propogate)?,
InputValueType::parse(Some(value)).map_err(InputValueError::propagate)?,
);
result
}),

View File

@ -29,11 +29,11 @@ impl<T: InputValueType> InputValueType for LinkedList<T> {
.into_iter()
.map(|value| InputValueType::parse(Some(value)))
.collect::<Result<_, _>>()
.map_err(InputValueError::propogate),
.map_err(InputValueError::propagate),
value => Ok({
let mut result = Self::default();
result.push_front(
InputValueType::parse(Some(value)).map_err(InputValueError::propogate)?,
InputValueType::parse(Some(value)).map_err(InputValueError::propagate)?,
);
result
}),

View File

@ -28,9 +28,9 @@ impl<T: InputValueType> InputValueType for Vec<T> {
.into_iter()
.map(|value| InputValueType::parse(Some(value)))
.collect::<Result<_, _>>()
.map_err(InputValueError::propogate),
.map_err(InputValueError::propagate),
value => Ok(vec![
InputValueType::parse(Some(value)).map_err(InputValueError::propogate)?
InputValueType::parse(Some(value)).map_err(InputValueError::propagate)?
]),
}
}

View File

@ -29,11 +29,11 @@ impl<T: InputValueType> InputValueType for VecDeque<T> {
.into_iter()
.map(|value| InputValueType::parse(Some(value)))
.collect::<Result<_, _>>()
.map_err(InputValueError::propogate),
.map_err(InputValueError::propagate),
value => Ok({
let mut result = Self::default();
result.push_back(
InputValueType::parse(Some(value)).map_err(InputValueError::propogate)?,
InputValueType::parse(Some(value)).map_err(InputValueError::propagate)?,
);
result
}),

View File

@ -25,7 +25,7 @@ impl<T: InputValueType> InputValueType for Option<T> {
match value.unwrap_or_default() {
Value::Null => Ok(None),
value => Ok(Some(
T::parse(Some(value)).map_err(InputValueError::propogate)?,
T::parse(Some(value)).map_err(InputValueError::propagate)?,
)),
}
}

View File

@ -130,7 +130,7 @@ impl<T: InputValueType> InputValueType for MaybeUndefined<T> {
None => Ok(MaybeUndefined::Undefined),
Some(Value::Null) => Ok(MaybeUndefined::Null),
Some(value) => Ok(MaybeUndefined::Value(
T::parse(Some(value)).map_err(InputValueError::propogate)?,
T::parse(Some(value)).map_err(InputValueError::propagate)?,
)),
}
}