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

Mutation Conventions #4519

Merged
merged 32 commits into from
Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0981a57
Started work on mutation conventions
michaelstaib Dec 2, 2021
54e7c5e
Started work on payloads
michaelstaib Dec 2, 2021
4a267c7
Added basic functionality
michaelstaib Dec 2, 2021
d85fc41
Refinements
michaelstaib Dec 2, 2021
6e350c1
wip
michaelstaib Dec 3, 2021
1ec321e
wip
michaelstaib Dec 3, 2021
2401a5c
Added parameter expression builder
michaelstaib Dec 3, 2021
d4150ed
Addded code first
michaelstaib Dec 3, 2021
39243c0
Rworked how we rewrite arguments
michaelstaib Dec 6, 2021
15d2f8e
Moved FieldInfo into separate file
michaelstaib Dec 6, 2021
bde0978
Fixed compile issue
michaelstaib Dec 6, 2021
9c598ad
Integrated Errors
michaelstaib Dec 7, 2021
a9c9e79
Handle NonNull
michaelstaib Dec 7, 2021
278984e
Fixed tests
michaelstaib Dec 7, 2021
1650337
Fixed middleware cleanup algorithm
michaelstaib Dec 7, 2021
94c7e2b
Added more tests
michaelstaib Dec 7, 2021
5fe9e87
cleanup
michaelstaib Dec 7, 2021
0c58288
Cleanup
michaelstaib Dec 7, 2021
24993fe
Cleanup
michaelstaib Dec 7, 2021
3c7c5f5
Merge branch 'main' into mst/mutationConventions
michaelstaib Dec 7, 2021
3e31e44
exclude generic attributes when not .net 6
michaelstaib Dec 7, 2021
c87a205
disabled generic attributes
michaelstaib Dec 7, 2021
f4a383d
removed records from tests
michaelstaib Dec 7, 2021
914a68e
cleanup
michaelstaib Dec 7, 2021
801074b
Cleanup
michaelstaib Dec 7, 2021
abc125f
Refinements
michaelstaib Dec 8, 2021
50ef772
Cleanup
michaelstaib Dec 8, 2021
5cd23e0
reverted modifier change
michaelstaib Dec 8, 2021
679c333
Refined
michaelstaib Dec 8, 2021
4fe15e1
Introduced output field info
michaelstaib Dec 8, 2021
eb339bb
Fixed compile issue
michaelstaib Dec 8, 2021
2f56b3a
Fixed Data Tests
michaelstaib Dec 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
cleanup
  • Loading branch information
michaelstaib committed Dec 7, 2021
commit 5fe9e87c308ce937c571a55a30c3ebf04f4cb3a0
171 changes: 167 additions & 4 deletions src/HotChocolate/Core/src/Types.Mutations/Errors/ErrorAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ namespace HotChocolate.Types;

/// <summary>
/// The <see cref="ErrorAttribute"/> registers a middleware that will catch all exceptions of
/// type <see cref="ErrorAttribute.ErrorType"/>. By annotating the attribute the response type
/// of the annotated resolver, will be automatically extended by a field of type
/// type <see cref="ErrorAttribute.ErrorType"/> on mutations.
///
/// By annotating the attribute the response type
/// of the annotated mutation resolver, will be automatically extended by a field of type
/// <c>errors:[Error!]</c>. This field will return errors that are caught by the middleware.
/// All the other fields on this type will be rewritten to nullable types. In case of a error
/// these fields will be set to null.
Expand Down Expand Up @@ -184,9 +186,170 @@ public override void OnConfigure(
=> descriptor.Error(ErrorType);
}

public class ErrorAttribute<TError> : ErrorAttribute
/// <summary>
/// The <see cref="ErrorAttribute{T}"/> registers a middleware that will catch all exceptions of
/// type <see cref="ErrorAttribute{T}.ErrorType"/> on mutations.
///
/// By annotating the attribute the response type
/// of the annotated mutation resolver, will be automatically extended by a field of type
/// <c>errors:[Error!]</c>. This field will return errors that are caught by the middleware.
/// All the other fields on this type will be rewritten to nullable types. In case of a error
/// these fields will be set to null.
/// <para>
/// There are three different ways to map exceptions to GraphQL errors.
/// </para>
/// </summary>
/// <remarks>
/// The idea of the error middleware is to keep the resolver clean of any error handling code
/// and use exceptions to signal a error state. The HotChocolate schema is automatically
/// rewritten into a common error handling pattern.
/// <a href="https://xuorig.medium.com/a-guide-to-graphql-errors-bb9ba9f15f85">Learn More</a>
/// </remarks>
/// <example>
/// <para>
/// There are three different ways to map exceptions to GraphQL errors.
/// </para>
/// <list type="number">
/// <item>
/// <para>
/// <b>Catching exceptions directly</b>
/// </para>
/// If <see cref="ErrorAttribute.ErrorType"/> is a exception, the exception is automatically
/// mapped into a GraphQL error and the middleware will catch this exception
/// <code>
/// public class Mutation
/// {
/// [Error<SomeSpecificDomainError>]
/// [Error<SomeOtherError>]
/// public CreateUserPayload CreateUser(CreateUserInput input)
/// {
/// // ...
/// }
/// }
///
/// public record CreateUserInput(string UserName);
///
/// public record CreateUserPayload(User User);
/// </code>
/// This will generate the following schema
/// <code>
/// type Mutation {
/// createUser(input: CreateUserInput!): CreateUserPayload!
/// }
///
/// input CreateUserInput {
/// userName: String!
/// }
///
/// type CreateUserPayload {
/// user: User
/// errors: [CreateUserError!]
/// }
///
/// type User {
/// username: String
/// }
///
/// interface Error {
/// message: String!
/// }
///
/// type SomeSpecificDomainError implements Error {
/// message: String!
/// }
///
/// type SomeOtherDomainError implements Error {
/// message: String!
/// }
///
/// union CreateUserError = SomeSpecificDomainError | SomeOtherDomainError
/// </code>
/// </item>
/// <item>
/// <para>
/// <b>Map Exceptions with a factory method</b>
/// </para>
/// <para>
/// If there should be any translation between exception and error, you can defined a class
/// with factory methods. These factory methods receive a <see cref="Exception"/> and return
/// a object which will be used as the representation of the error
/// </para>
/// <para>
/// A factory method has to be `public static` and the name of the method has to be
/// `CreateErrorFrom`. There should only be one parameter of type <see cref="Exception"/> and
/// it can return a arbitrary class/struct/record that will be used as the representation
/// of the error.
/// </para>
/// <code>
/// public class MyCustomError
/// {
/// public static MyCustomError CreateErrorFrom(DomainExceptionA ex)
/// {
/// return new MyCustomError();
/// }
///
/// public static MyCustomError CreateErrorFrom(DomainExceptionB ex)
/// {
/// return new MyCustomError();
/// }
///
/// public string Message => "My custom error Message";
/// }
///
/// public class Mutation
/// {
/// [Error<MyCustomError>]
/// public CreateUserPayload CreateUser(CreateUserInput input)
/// {
/// // ...
/// }
/// }
///
/// public record CreateUserInput(string UserName);
///
/// public record CreateUserPayload(User User);
/// </code>
/// </item>
/// <item>
/// <para>
/// <b>Map exceptions with a constructors</b>
/// </para>
/// <para>
/// As a alternative to mapping exceptions with factory methods, you can also map the exception
/// in the constructor of the object that should be used to represent the error in the schema.
/// </para>
/// <code>
/// public class MyCustomError
/// {
/// public MyCustomError(MyCustomDomainException exception)
/// {
/// Message = exception.Message;
/// }
///
/// public MyCustomError(MyCustomDomainException2 exception)
/// {
/// Message = exception.Message;
/// }
///
/// public string Message { get; }
/// }
///
/// public class Mutation
/// {
/// [Error<MyCustomError>]
/// public CreateUserPayload CreateUser(CreateUserInput input)
/// {
/// // ...
/// }
/// }
/// </code>
/// </item>
/// </list>
/// </example>
public sealed class ErrorAttribute<TError> : ErrorAttribute
{
/// <inheritdoc cref="ErrorAttribute"/>
public ErrorAttribute() : base(typeof(TError))
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ public static ExtensionData MarkAsError(this ExtensionData extensionData)
return extensionData;
}

public static bool IsError(this ExtensionData extensionData) =>
extensionData.ContainsKey(IsErrorType);
public static bool IsError(this ExtensionData extensionData)
=> extensionData.ContainsKey(IsErrorType);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace HotChocolate.Types;

internal class ErrorDefinition
internal sealed class ErrorDefinition
{
public ErrorDefinition(Type runtimeType, Type schemaType, CreateError factory)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace HotChocolate.Types;

internal class ErrorInterfaceType : InterfaceType
internal sealed class ErrorInterfaceType : InterfaceType
{
protected override void Configure(IInterfaceTypeDescriptor descriptor)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ namespace HotChocolate.Types;

internal sealed class ErrorMiddleware
{
public const string MiddlewareIdentifier = "HotChocolate.Types.Errors.ErrorMiddleware";

public static readonly object ErrorObject = new();

private readonly FieldDelegate _next;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace HotChocolate.Types;

internal class ErrorObjectType<T> : ObjectType<T>
internal sealed class ErrorObjectType<T> : ObjectType<T>
{
protected override void Configure(IObjectTypeDescriptor<T> descriptor)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace HotChocolate.Types;

internal class ExceptionObjectType<T> : ObjectType<T> where T : Exception
internal sealed class ExceptionObjectType<T> : ObjectType<T> where T : Exception
{
protected override void Configure(IObjectTypeDescriptor<T> descriptor)
{
Expand All @@ -21,7 +21,7 @@ private static string GetNameFromException()
{
var name = typeof(T).Name;
const string exceptionSuffix = nameof(Exception);

if (name.EndsWith(exceptionSuffix))
{
return $"{name.Substring(0, name.Length - exceptionSuffix.Length)}Error";
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ public static class ErrorObjectFieldDescriptorExtensions
{
/// <summary>
/// The <c>.Error&lt;TError>()</c> extension method registers a middleware that will catch
/// all exceptions of type <typeparamref name="TError"/>. By annotating the attribute the
/// all exceptions of type <typeparamref name="TError"/> on mutations.
///
/// By applying the error extension to a mutation field the
/// response type of the annotated resolver, will be automatically extended by a field of
/// type <c>errors:[Error!]</c>. This field will return errors that are caught by the
/// middleware. All the other fields on this type will be rewritten to nullable types.
Expand Down Expand Up @@ -178,8 +180,10 @@ public static IObjectFieldDescriptor Error<TError>(this IObjectFieldDescriptor d
Error(descriptor, typeof(TError));

/// <summary>
/// The <c>.Error()</c> extension method registers a middleware that will catch all
/// exceptions of type <paramref name="errorType"/>. By annotating the attribute the
/// The <c>.Error&lt;TError>()</c> extension method registers a middleware that will catch
/// all exceptions of type <typeparamref name="TError"/> on mutations.
///
/// By applying the error extension to a mutation field the
/// response type of the annotated resolver, will be automatically extended by a field of
/// type <c>errors:[Error!]</c>. This field will return errors that are caught by the
/// middleware. All the other fields on this type will be rewritten to nullable types.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public static class ErrorSchemaBuilderExtensions
/// <returns>j
/// The schema builder
/// </returns>
public static ISchemaBuilder AddErrorInterfaceType<T>(this ISchemaBuilder schemaBuilder) =>
schemaBuilder.AddErrorInterfaceType(typeof(T));
public static ISchemaBuilder AddErrorInterfaceType<T>(this ISchemaBuilder schemaBuilder)
=> schemaBuilder.AddErrorInterfaceType(typeof(T));

/// <summary>
/// Defines the common interface that all errors implement.
Expand All @@ -44,6 +44,6 @@ public static ISchemaBuilder AddErrorInterfaceType<T>(this ISchemaBuilder schema
/// </returns>
public static ISchemaBuilder AddErrorInterfaceType(
this ISchemaBuilder schemaBuilder,
Type type) =>
schemaBuilder.SetContextData(ErrorContextDataKeys.ErrorType, type);
Type type)
=> schemaBuilder.SetContextData(ErrorContextDataKeys.ErrorType, type);
}

This file was deleted.

This file was deleted.

Loading