juniper icon indicating copy to clipboard operation
juniper copied to clipboard

`IntoFieldError` and `Into<FieldError>` have different behaviors

Open UkonnRa opened this issue 5 years ago • 3 comments

Describe the bug FieldError::from and error.into_field_error have different behaviors. The former one has no extensions field, but the latter one has.

To Reproduce


    #[derive(thiserror::Error, Debug, Deserialize, Serialize)]
    enum SomeError {
        #[error("SomeError Item")]
        Item,
    }
    impl IntoFieldError for SomeError {
        fn into_field_error(self) -> juniper::FieldError {
            juniper::FieldError::new(
                "Get Some Error",
                juniper::graphql_value!({
                    "type": "SomeError",
                }),
            )
        }
    }

    #[test]
    fn test_err() -> anyhow::Result<()> {
        println!("--- FieldError::from ---");
        println!("{:#?}", FieldError::<DefaultScalarValue>::from(SomeError::Item));
        println!("--- into_field_error ---");
        println!("{:#?}", SomeError::Item.into_field_error());

        Ok(())
    }

Expected behavior The result should be the same.

Actual bebavior

--- FieldError::from ---
FieldError {
    message: "SomeError Item",
    extensions: Null,
}
--- into_field_error ---
FieldError {
    message: "Get Some Error",
    extensions: Object(
        Object {
            key_value_list: [
                (
                    "type",
                    Scalar(
                        String(
                            "SomeError",
                        ),
                    ),
                ),
            ],
        },
    ),
}

So:

  1. I think FieldError::from should be a bug,
  2. I have no idea why the same action(convert from CustomError to FieldError) has two different way to achieve. Maybe one is enough...

UkonnRa avatar Jan 26 '20 14:01 UkonnRa

They aren’t meant to do the same. FieldError::from is provided by From from std. It only requires that your type and be converted into a string. There is no way to provide custom extensions. It is mostly provided for ergonomic purposes.

Users of juniper wouldn’t be able to implement From<CustomError> for FieldError since neither the trait nor FieldError is defined in the users own crate. That is the orphan rule. So we need our own trait to do the conversion.

davidpdrsn avatar Jan 26 '20 16:01 davidpdrsn

But it's a little redundant when returning Errors if I have to use IntoFieldError.

Say a service:

pub struct Service;
impl juniper::Context for Service {}

impl Service {
    async fn hello(&self) -> Result<String, SomeError> {
        Err(SomeError::Item {
            arg1: "arg1".to_string(),
            arg2: 10i32,
        })
    }
}

If using into_field_error, I should write code like:

pub struct Query;

#[juniper::graphql_object(Context=Service)]
impl Query {
    pub async fn hello(ctx: &Service) -> FieldResult<String> {
        Ok(ctx.hello().await.map_err(|err| err.into_field_error())?)
    }
}

But the following code is far better:

pub struct Query;

#[juniper::graphql_object(Context=Service)]
impl Query {
    pub async fn hello(ctx: &Service) -> FieldResult<String> {
        Ok(ctx.hello().await?)
    }
}

UkonnRa avatar Jan 26 '20 17:01 UkonnRa

I don’t exactly remember how the conversion works but do you have to explicitly call “.into_field_error()”? Doesn’t the macro insert that call for you? I might be mistaking and on phone right now so I cannot check. If not I’ll investigate later.

davidpdrsn avatar Jan 26 '20 17:01 davidpdrsn