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

Apply spec compliant error formatting #10

Merged
merged 1 commit into from
Nov 5, 2024
Merged

Conversation

twist900
Copy link
Member

@twist900 twist900 commented Nov 5, 2024

Adds error formatting phase that groups related validation errors under a single group_error code (e.g. BAD_USER_INPUT), following GraphQL spec.

Example Output:

{
  "errors": [
    {
      "message": "Invalid input",
      "extensions": {
        "code": "BAD_USER_INPUT",
        "details": {
          "fields": [
            {
              "message": "min_not_met",
              "path": ["description"],
              "details": { "min": 5 },
              "locations": [{ "line": 6, "column": 7 }],
              "custom_error_code": "min_not_met"
            },
            {
              "message": "max_exceeded",
              "path": ["title"],
              "details": { "max": 10 },
              "locations": [{ "line": 7, "column": 7 }],
              "custom_error_code": "max_exceeded"
            }
          ]
        }
      }
    }
  ]
}

@twist900 twist900 self-assigned this Nov 5, 2024
@twist900 twist900 force-pushed the propagate-errors-to-fe branch from a359d90 to fd8b815 Compare November 5, 2024 09:17
"fields": [
{
"message": "min_not_met",
"path": ["description"],
Copy link
Member Author

@twist900 twist900 Nov 5, 2024

Choose a reason for hiding this comment

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

the path currently includes only the field name, we'd need to figure out how to have the full path including nesting and indexing into lists here. I've seen something here:

https://hexdocs.pm/absinthe/Absinthe.Resolution.html#path/1

but its seems to be available once the schema is resolved, which happens later in the pipeline. That said, happy to look into it separately again.

Choose a reason for hiding this comment

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

  1. FYI don't use path_string - it doesn't work with list (positinos in list are integers and 😭)
  2. we could try making it middleware rather then pipeline; there you have access to resolution

Copy link
Member Author

Choose a reason for hiding this comment

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

FYI don't use path_string - it doesn't work with list (positinos in list are integers and 😭)

the link is indeed to path 👍

we could try making it middleware rather then pipeline; there you have access to resolution

unfortunately these errors cause early exit in the Absinthe pipeline, i.e. they never reach the Execution phase where the middlewares are called

@andreyuhai
Copy link
Contributor

following GraphQL spec.

Do you mind sharing the link to the spec @twist900? I can't find it mentioned here: https://spec.graphql.org/October2021/

@twist900
Copy link
Member Author

twist900 commented Nov 5, 2024

Do you mind sharing the link to the spec @twist900? I can't find it mentioned here: https://spec.graphql.org/October2021/

@andreyuhai I was mostly thinking about including extensions in the errors response, please see here:

GraphQL services may provide an additional entry to errors with key extensions. This entry, if set, must have a map as its value. This entry is reserved for implementers to add additional information to errors however they see fit, and there are no additional restrictions on its contents.

{
  "errors": [
    {
      "message": "Name for character with ID 1002 could not be fetched.",
      "locations": [{ "line": 6, "column": 7 }],
      "path": ["hero", "heroFriends", 1, "name"],
      "extensions": {
        "code": "CAN_NOT_FETCH_BY_ID",
        "timestamp": "Fri Feb 9 14:33:09 UTC 2018"
      }
    }
  ]
}


## Error Formatting

The package includes GraphQL specification compliant error formatting. When enabled, validation errors from constraints or transform failures are formatted consistently.

Choose a reason for hiding this comment

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

This package is internal, isn't it? Can we link to RFC?

Copy link
Member Author

Choose a reason for hiding this comment

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

package is public :)

"fields": [
{
"message": "min_not_met",
"path": ["description"],

Choose a reason for hiding this comment

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

  1. FYI don't use path_string - it doesn't work with list (positinos in list are integers and 😭)
  2. we could try making it middleware rather then pipeline; there you have access to resolution

Comment on lines +30 to +50
|> Enum.flat_map(&format_error_group/1)
end

defp get_error_code(%{group_code: group_code}), do: group_code
defp get_error_code(_), do: nil

defp format_error_group({nil, errors}), do: errors

defp format_error_group({group_code, errors}) do
[
%{
message: "Invalid input",
locations: [],
extensions: %{
code: group_code,
details: %{
fields: Enum.map(errors, &format_field/1)
}
}
}
]
end

Choose a reason for hiding this comment

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

That should be idiomatic; you still would like to have one entry per group

Suggested change
|> Enum.flat_map(&format_error_group/1)
end
defp get_error_code(%{group_code: group_code}), do: group_code
defp get_error_code(_), do: nil
defp format_error_group({nil, errors}), do: errors
defp format_error_group({group_code, errors}) do
[
%{
message: "Invalid input",
locations: [],
extensions: %{
code: group_code,
details: %{
fields: Enum.map(errors, &format_field/1)
}
}
}
]
end
|> Enum.map(&format_error_group/1)
end
defp get_error_code(%{group_code: group_code}), do: group_code
defp get_error_code(_), do: nil
defp format_error_group({nil, errors}), do: errors
defp format_error_group({group_code, errors}) do
%{
message: "Invalid input",
locations: [],
extensions: %{
code: group_code,
details: %{
fields: Enum.map(errors, &format_field/1)
}
}
}
end

Copy link
Member Author

Choose a reason for hiding this comment

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

not sure what is intended by this suggestion, should line 36 also format the errors as a single map? otherwise in one case we return a list in another a map

@mpmiszczyk
Copy link

Have you thought about adding the changelog? 🙏

@twist900 twist900 force-pushed the propagate-errors-to-fe branch from fd8b815 to 26d11a3 Compare November 5, 2024 16:33
@twist900 twist900 merged commit 2027dec into main Nov 5, 2024
4 checks passed
@twist900 twist900 deleted the propagate-errors-to-fe branch November 5, 2024 16:35
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.

3 participants