Skip to content

Latest commit

 

History

History
118 lines (93 loc) · 4.24 KB

argument-level-overrides.md

File metadata and controls

118 lines (93 loc) · 4.24 KB

Argument level overrides

Not all rules, configured globally, suites for corner cases

This library allows to override all globals for field-specific rules

I will use notifications. It is not well designed schema, but can explain thats and whys, when you have same inputs in multiple mutations, etc.

public record Notification(Guid UserId, string Message);

You can send Notifications with SendNotification and SendSystemNotification

descriptor.Field(x => x.SendNotification(default!))
  .Argument("input", argument => argument.UseFluentValidation());

descriptor.Field(x => x.SendSystemNotification(default!))
  .Argument("input", argument => argument.UseFluentValidation());

Well, there is a problem, mutations are using the same input, but each mutation has its own validation rules (it is an artificial restriction, imagine you can't change them to more domain specific Notification and SystemNotification, it is a breaking change)

To resolve this problem and to not to validate each Notification with the same IValidator set you can specify which validators should validate input

descriptor.Field(x => x.SendNotification(default!))
  .Argument("input", argument => argument.UseFluentValidation(options =>
  {
    // HERE
    options.UseValidator<NotificationValidator>();
  }));

descriptor.Field(x => x.SendSystemNotification(default!))
  .Argument("input", argument => argument.UseFluentValidation(options =>
  {
    // HERE
    options.UseValidator<SystemNotificationValidator>();
  }));

But what if regular notifications should validate only Message with notification rule set using all available validators (and system too, why not?), but system only with one?

You can pass not only a concrete validator class, but an interface to resolve all available validators and override ValidationStrategy<Notification>

descriptor.Field(x => x.SendNotification(default!))
  .Argument("input", argument => argument.UseFluentValidation(options =>
  {
    // UseValidators resolves all validators for notification
    options.UseValidators<Notification, IValidator<Notification>>(strategy =>
    {
      strategy.IncludeProperties(x => x.Message);
      strategy.IncludeRuleSets("notification");
    });
  }));

descriptor.Field(x => x.SendSystemNotification(default!))
  .Argument("input", argument => argument.UseFluentValidation(options =>
  {
    options.UseValidator<SystemNotificationValidator>();
  }));

Also for system notifications you want to have extended validation details in graphql errors

To achieve this, you can override error mappers

descriptor.Field(x => x.SendNotification(default!))
  .Argument("input", argument => argument.UseFluentValidation(options =>
  {
    options.UseValidators<Notification, IValidator<Notification>>(strategy =>
    {
      strategy.IncludeProperties(x => x.Message);
      strategy.IncludeRuleSets("notification");
    });
  }));

descriptor.Field(x => x.SendSystemNotification(default!))
  .Argument("input", argument => argument.UseFluentValidation(options =>
  {
    options.UseValidator<SystemNotificationValidator>();
    // HERE
    options.UseDefaultErrorMapperWithExtendedDetails();
  }));

After all only system notifications can skip validation if some condition happens

descriptor.Field(x => x.SendNotification(default!))
  .Argument("input", argument => argument.UseFluentValidation(options =>
  {
    options.UseValidators<Notification, IValidator<Notification>>(strategy =>
    {
      strategy.IncludeProperties(x => x.Message);
      strategy.IncludeRuleSets("notification");
    });
  }));

descriptor.Field(x => x.SendSystemNotification(default!))
  .Argument("input", argument => argument.UseFluentValidation(options =>
  {
    // HERE
    options.SkipValidation(context => context.MiddlewareContext.Services
      .GetRequiredService<ISomeConditionService>()
      .SomeConditionHappens());

    options.UseValidator<SystemNotificationValidator>();
    options.UseDefaultErrorMapperWithExtendedDetails();
  }));

This document has nothing with real world example, but it explains main features of argument level overrides

There are some restrictions by design, most overrides is not supported with attribute-based approach, you can override only validator types or create your own attributes