diff --git a/src/FairyBread.Tests/FairyBread.Tests.csproj b/src/FairyBread.Tests/FairyBread.Tests.csproj
index fabd8c7..458972e 100644
--- a/src/FairyBread.Tests/FairyBread.Tests.csproj
+++ b/src/FairyBread.Tests/FairyBread.Tests.csproj
@@ -23,4 +23,182 @@
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ MutationConventionsTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+ $([System.String]::Copy('%(FileName)').Split('.')[0])
+ GeneralTests.cs
+
+
+
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Doesnt_Call_Field_Resolver_If_Invalid.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Doesnt_Call_Field_Resolver_If_Invalid.verified.txt
new file mode 100644
index 0000000..9ed713d
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Doesnt_Call_Field_Resolver_If_Invalid.verified.txt
@@ -0,0 +1,32 @@
+{
+ Errors: [
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: someResolver,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foo,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: SomeInteger,
+ severity: Error,
+ validatorName: FooInputDtoValidator
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Ignores_Null_Argument_Value.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Ignores_Null_Argument_Value.verified.txt
new file mode 100644
index 0000000..764366a
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Ignores_Null_Argument_Value.verified.txt
@@ -0,0 +1,5 @@
+{
+ Data: {
+ read: SomeInteger: 1, SomeString: hello;
+ }
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Multi_TopLevelFields_And_MultiRuns_Works.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Multi_TopLevelFields_And_MultiRuns_Works.verified.txt
new file mode 100644
index 0000000..a7a8c13
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Multi_TopLevelFields_And_MultiRuns_Works.verified.txt
@@ -0,0 +1,98 @@
+{
+ result1: {
+ Errors: [
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: read,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foo,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: SomeInteger,
+ severity: Error,
+ validatorName: FooInputDtoValidator
+ }
+ }
+ ]
+ },
+ result2: {
+ Errors: [
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: read,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foo,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: SomeInteger,
+ severity: Error,
+ validatorName: FooInputDtoValidator
+ }
+ }
+ ]
+ },
+ result3: {
+ Errors: [
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: read,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foo,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: SomeInteger,
+ severity: Error,
+ validatorName: FooInputDtoValidator
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Mutation_Works_caseData=1.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Mutation_Works_caseData=1.verified.txt
new file mode 100644
index 0000000..f24d515
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Mutation_Works_caseData=1.verified.txt
@@ -0,0 +1,5 @@
+{
+ Data: {
+ write: SomeInteger: 1, SomeString: hello; EmailAddress: ben@lol.com
+ }
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Mutation_Works_caseData=2.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Mutation_Works_caseData=2.verified.txt
new file mode 100644
index 0000000..8bdd86f
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Mutation_Works_caseData=2.verified.txt
@@ -0,0 +1,32 @@
+{
+ Errors: [
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: write,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foo,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: SomeInteger,
+ severity: Error,
+ validatorName: FooInputDtoValidator
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Mutation_Works_caseData=3.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Mutation_Works_caseData=3.verified.txt
new file mode 100644
index 0000000..27e452c
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Mutation_Works_caseData=3.verified.txt
@@ -0,0 +1,30 @@
+{
+ Errors: [
+ {
+ Message: The specified condition was not met for 'Email Address'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: write,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: bar,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: AsyncPredicateValidator,
+ errorMessage: The specified condition was not met for 'Email Address'.,
+ formattedMessagePlaceholderValues: {
+ PropertyName: Email Address,
+ PropertyValue: -1
+ },
+ propertyName: EmailAddress,
+ severity: Error,
+ validatorName: BarInputDtoAsyncValidator
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Mutation_Works_caseData=4.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Mutation_Works_caseData=4.verified.txt
new file mode 100644
index 0000000..b863843
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Mutation_Works_caseData=4.verified.txt
@@ -0,0 +1,86 @@
+{
+ Errors: [
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: write,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foo,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: SomeInteger,
+ severity: Error,
+ validatorName: FooInputDtoValidator
+ }
+ },
+ {
+ Message: 'Some String' must be equal to 'hello'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: write,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foo,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some String' must be equal to 'hello'.,
+ formattedMessagePlaceholderValues: {
+ ComparisonProperty: ,
+ ComparisonValue: hello,
+ PropertyName: Some String,
+ PropertyValue: -1
+ },
+ propertyName: SomeString,
+ severity: Error,
+ validatorName: FooInputDtoValidator
+ }
+ },
+ {
+ Message: The specified condition was not met for 'Email Address'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: write,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: bar,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: AsyncPredicateValidator,
+ errorMessage: The specified condition was not met for 'Email Address'.,
+ formattedMessagePlaceholderValues: {
+ PropertyName: Email Address,
+ PropertyValue: -1
+ },
+ propertyName: EmailAddress,
+ severity: Error,
+ validatorName: BarInputDtoAsyncValidator
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=1.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=1.verified.txt
new file mode 100644
index 0000000..524ebb1
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=1.verified.txt
@@ -0,0 +1,5 @@
+{
+ Data: {
+ readWithArrayArg: SomeInteger: 1, SomeString: hello
+ }
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=2.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=2.verified.txt
new file mode 100644
index 0000000..524ebb1
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=2.verified.txt
@@ -0,0 +1,5 @@
+{
+ Data: {
+ readWithArrayArg: SomeInteger: 1, SomeString: hello
+ }
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=3.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=3.verified.txt
new file mode 100644
index 0000000..8835b7c
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=3.verified.txt
@@ -0,0 +1,5 @@
+{
+ Data: {
+ readWithArrayArg: SomeInteger: 1, SomeString: hello, SomeInteger: 1, SomeString: hello
+ }
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=4.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=4.verified.txt
new file mode 100644
index 0000000..84a6415
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=4.verified.txt
@@ -0,0 +1,33 @@
+{
+ Errors: [
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: readWithArrayArg,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foos,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ CollectionIndex: 0,
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: x[0].SomeInteger,
+ severity: Error,
+ validatorName: ArrayOfFooInputDtoValidator
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=5.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=5.verified.txt
new file mode 100644
index 0000000..84a6415
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=5.verified.txt
@@ -0,0 +1,33 @@
+{
+ Errors: [
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: readWithArrayArg,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foos,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ CollectionIndex: 0,
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: x[0].SomeInteger,
+ severity: Error,
+ validatorName: ArrayOfFooInputDtoValidator
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=6.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=6.verified.txt
new file mode 100644
index 0000000..2c45fc2
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_Array_Works_caseData=6.verified.txt
@@ -0,0 +1,62 @@
+{
+ Errors: [
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: readWithArrayArg,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foos,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ CollectionIndex: 0,
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: x[0].SomeInteger,
+ severity: Error,
+ validatorName: ArrayOfFooInputDtoValidator
+ }
+ },
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: readWithArrayArg,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foos,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ CollectionIndex: 1,
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: x[1].SomeInteger,
+ severity: Error,
+ validatorName: ArrayOfFooInputDtoValidator
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=1.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=1.verified.txt
new file mode 100644
index 0000000..e6a0e98
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=1.verified.txt
@@ -0,0 +1,5 @@
+{
+ Data: {
+ readWithListArg: SomeInteger: 1, SomeString: hello
+ }
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=2.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=2.verified.txt
new file mode 100644
index 0000000..e6a0e98
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=2.verified.txt
@@ -0,0 +1,5 @@
+{
+ Data: {
+ readWithListArg: SomeInteger: 1, SomeString: hello
+ }
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=3.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=3.verified.txt
new file mode 100644
index 0000000..8442d9f
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=3.verified.txt
@@ -0,0 +1,5 @@
+{
+ Data: {
+ readWithListArg: SomeInteger: 1, SomeString: hello, SomeInteger: 1, SomeString: hello
+ }
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=4.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=4.verified.txt
new file mode 100644
index 0000000..62952f3
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=4.verified.txt
@@ -0,0 +1,33 @@
+{
+ Errors: [
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: readWithListArg,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foos,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ CollectionIndex: 0,
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: x[0].SomeInteger,
+ severity: Error,
+ validatorName: ListOfFooInputDtoValidator
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=5.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=5.verified.txt
new file mode 100644
index 0000000..62952f3
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=5.verified.txt
@@ -0,0 +1,33 @@
+{
+ Errors: [
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: readWithListArg,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foos,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ CollectionIndex: 0,
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: x[0].SomeInteger,
+ severity: Error,
+ validatorName: ListOfFooInputDtoValidator
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=6.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=6.verified.txt
new file mode 100644
index 0000000..26113d1
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_List_Works_caseData=6.verified.txt
@@ -0,0 +1,62 @@
+{
+ Errors: [
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: readWithListArg,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foos,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ CollectionIndex: 0,
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: x[0].SomeInteger,
+ severity: Error,
+ validatorName: ListOfFooInputDtoValidator
+ }
+ },
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: readWithListArg,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foos,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ CollectionIndex: 1,
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: x[1].SomeInteger,
+ severity: Error,
+ validatorName: ListOfFooInputDtoValidator
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_Works_caseData=1.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_Works_caseData=1.verified.txt
new file mode 100644
index 0000000..0392acb
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_Works_caseData=1.verified.txt
@@ -0,0 +1,5 @@
+{
+ Data: {
+ read: SomeInteger: 1, SomeString: hello; EmailAddress: ben@lol.com
+ }
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_Works_caseData=2.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_Works_caseData=2.verified.txt
new file mode 100644
index 0000000..fe07897
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_Works_caseData=2.verified.txt
@@ -0,0 +1,32 @@
+{
+ Errors: [
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: read,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foo,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: SomeInteger,
+ severity: Error,
+ validatorName: FooInputDtoValidator
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_Works_caseData=3.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_Works_caseData=3.verified.txt
new file mode 100644
index 0000000..0069536
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_Works_caseData=3.verified.txt
@@ -0,0 +1,30 @@
+{
+ Errors: [
+ {
+ Message: The specified condition was not met for 'Email Address'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: read,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: bar,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: AsyncPredicateValidator,
+ errorMessage: The specified condition was not met for 'Email Address'.,
+ formattedMessagePlaceholderValues: {
+ PropertyName: Email Address,
+ PropertyValue: -1
+ },
+ propertyName: EmailAddress,
+ severity: Error,
+ validatorName: BarInputDtoAsyncValidator
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Query_Works_caseData=4.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Query_Works_caseData=4.verified.txt
new file mode 100644
index 0000000..e62fefd
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Query_Works_caseData=4.verified.txt
@@ -0,0 +1,86 @@
+{
+ Errors: [
+ {
+ Message: 'Some Integer' must be equal to '1'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: read,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foo,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some Integer' must be equal to '1'.,
+ formattedMessagePlaceholderValues: {
+ ComparisonProperty: ,
+ ComparisonValue: 1,
+ PropertyName: Some Integer,
+ PropertyValue: -1
+ },
+ propertyName: SomeInteger,
+ severity: Error,
+ validatorName: FooInputDtoValidator
+ }
+ },
+ {
+ Message: 'Some String' must be equal to 'hello'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: read,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: foo,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: EqualValidator,
+ errorMessage: 'Some String' must be equal to 'hello'.,
+ formattedMessagePlaceholderValues: {
+ ComparisonProperty: ,
+ ComparisonValue: hello,
+ PropertyName: Some String,
+ PropertyValue: -1
+ },
+ propertyName: SomeString,
+ severity: Error,
+ validatorName: FooInputDtoValidator
+ }
+ },
+ {
+ Message: The specified condition was not met for 'Email Address'.,
+ Code: FairyBread_ValidationError,
+ Path: {
+ Name: read,
+ Parent: {
+ IsRoot: true
+ },
+ Length: 1,
+ IsRoot: false
+ },
+ Extensions: {
+ argumentName: bar,
+ attemptedValue: -1,
+ code: FairyBread_ValidationError,
+ errorCode: AsyncPredicateValidator,
+ errorMessage: The specified condition was not met for 'Email Address'.,
+ formattedMessagePlaceholderValues: {
+ PropertyName: Email Address,
+ PropertyValue: -1
+ },
+ propertyName: EmailAddress,
+ severity: Error,
+ validatorName: BarInputDtoAsyncValidator
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.SchemaWorks.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.SchemaWorks.verified.txt
new file mode 100644
index 0000000..ed63812
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.SchemaWorks.verified.txt
@@ -0,0 +1,26 @@
+schema {
+ query: Query
+ mutation: Mutation
+}
+
+type Mutation {
+ write(foo: FooInputDtoInput! bar: BarInputDtoInput!): String!
+}
+
+type Query {
+ read(foo: FooInputDtoInput! bar: BarInputDtoInput): String!
+ readWithArrayArg(foos: [FooInputDtoInput!]!): String!
+ readWithListArg(foos: [FooInputDtoInput!]!): String!
+ someResolver(foo: FooInputDtoInput! bar: BarInputDtoInput): String!
+ intResolver(count: Int!): String!
+ nullableIntResolver(count: Int): String!
+}
+
+input BarInputDtoInput {
+ emailAddress: String!
+}
+
+input FooInputDtoInput {
+ someInteger: Int!
+ someString: String!
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.Should_Respect_ShouldValidateArgument_Option.verified.txt b/src/FairyBread.Tests/MutationConventionsTests.Should_Respect_ShouldValidateArgument_Option.verified.txt
new file mode 100644
index 0000000..59ea752
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.Should_Respect_ShouldValidateArgument_Option.verified.txt
@@ -0,0 +1,5 @@
+{
+ Data: {
+ write: SomeInteger: -1, SomeString: hello; EmailAddress: ben@lol.com
+ }
+}
\ No newline at end of file
diff --git a/src/FairyBread.Tests/MutationConventionsTests.cs b/src/FairyBread.Tests/MutationConventionsTests.cs
new file mode 100644
index 0000000..d1f76c9
--- /dev/null
+++ b/src/FairyBread.Tests/MutationConventionsTests.cs
@@ -0,0 +1,209 @@
+namespace FairyBread.Tests;
+
+[UsesVerify]
+public class MutationsConventionTests
+{
+ static MutationsConventionTests()
+ {
+ VerifierSettings.NameForParameter(_ => _.CaseId);
+ }
+
+ private static async Task GetRequestExecutorAsync(
+ Action? configureOptions = null,
+ Action? configureServices = null,
+ bool registerValidators = true)
+ {
+ var services = new ServiceCollection();
+ configureServices?.Invoke(services);
+
+ if (registerValidators)
+ {
+ services.AddValidator();
+ services.AddValidator();
+ services.AddValidator>();
+ services.AddValidator();
+ services.AddValidator();
+ services.AddValidator();
+ }
+
+ var builder = services
+ .AddGraphQL()
+ .AddQueryType()
+ .AddMutationType()
+ .AddMutationConventions()
+ .AddFairyBread(options =>
+ {
+ configureOptions?.Invoke(options);
+ });
+
+ return await builder
+ .BuildRequestExecutorAsync();
+ }
+
+ [Fact]
+ public async Task SchemaWorks()
+ {
+ // Arrange
+ var executor = await GetRequestExecutorAsync();
+
+ // Act
+ var result = executor.Schema.ToString();
+
+ // Assert
+ await Verifier.Verify(result);
+ }
+
+ [Theory]
+ [MemberData(nameof(Cases))]
+ public async Task Mutation_Works(CaseData caseData)
+ {
+ // Arrange
+ var executor = await GetRequestExecutorAsync();
+
+ var query = $$"""
+ mutation {
+ write(foo: {{caseData.FooInput}}, bar: {{caseData.BarInput}}) {
+ string
+ errors {
+ __typename
+ }
+ }
+ }
+ """;
+
+ // Act
+ var result = await executor.ExecuteAsync(query);
+
+ // Assert
+ var verifySettings = new VerifySettings();
+ verifySettings.UseParameters(caseData);
+ await Verifier.Verify(result, verifySettings);
+ }
+
+ public static IEnumerable