Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public static List<IDirective> GetPossibleFusionLookupDirectivesById(

public static void RemoveUnreferencedDefinitions(
this MutableSchemaDefinition schema,
HashSet<string> preserveInputTypeNames)
ImmutableSortedSet<MutableSchemaDefinition> sourceSchemas)
{
var touchedDefinitions = new HashSet<ITypeSystemMember>();
var backlog = new Stack<ITypeSystemMember>();
Expand All @@ -161,6 +161,16 @@ public static void RemoveUnreferencedDefinitions(
backlog.Push(schema.SubscriptionType);
}

var preservedTypeNames = GetPreservedTypeNames(sourceSchemas);

foreach (var typeName in preservedTypeNames)
{
if (schema.Types.TryGetType(typeName, out var inputType))
{
backlog.Push(inputType);
}
}

while (backlog.TryPop(out var type))
{
if (!touchedDefinitions.Add(type))
Expand Down Expand Up @@ -199,7 +209,7 @@ public static void RemoveUnreferencedDefinitions(
var definitionsToRemove = new HashSet<ITypeSystemMember>();
foreach (var type in schema.Types)
{
if (touchedDefinitions.Contains(type) || preserveInputTypeNames.Contains(type.NamedType().Name))
if (touchedDefinitions.Contains(type))
{
continue;
}
Expand Down Expand Up @@ -246,6 +256,38 @@ private static List<IDirective> GetFusionLookupDirectives(
.ToList();
}

/// <summary>
/// Returns a list of type names for types that must be preserved in the merged schema
/// even if they are not directly referenced.
/// </summary>
private static HashSet<string> GetPreservedTypeNames(ImmutableSortedSet<MutableSchemaDefinition> sourceSchemas)
{
var preservedTypeNames = new HashSet<string>();

foreach (var schema in sourceSchemas)
{
foreach (var type in schema.Types.OfType<IObjectTypeDefinition>())
{
foreach (var field in type.Fields)
{
foreach (var argument in field.Arguments)
{
var argumentInnerType = argument.Type.InnerType();

if ((argument.HasRequireDirective || field.IsLookup)
&& argumentInnerType is INameProvider { Name: var name }
&& !SpecScalarNames.IsSpecScalar(name))
{
preservedTypeNames.Add(name);
}
}
}
}
}

return preservedTypeNames;
}

private static void InspectComplexType(
ISchemaDefinition schema,
IComplexTypeDefinition complexType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public CompositionResult<MutableSchemaDefinition> Merge()
// Remove unreferenced definitions.
if (_options.RemoveUnreferencedDefinitions)
{
mergedSchema.RemoveUnreferencedDefinitions(GetPreservedInputTypeNames());
mergedSchema.RemoveUnreferencedDefinitions(_schemas);
}

// Add Fusion definitions.
Expand Down Expand Up @@ -273,37 +273,6 @@ private void AddNodeField(MutableSchemaDefinition mergedSchema)
}
}

/// <summary>
/// Returns a list of input type names for types that must be preserved in the merged schema
/// even if they are not directly referenced.
/// </summary>
private HashSet<string> GetPreservedInputTypeNames()
{
var preservedInputTypeNames = new HashSet<string>();

foreach (var schema in _schemas)
{
foreach (var type in schema.Types.OfType<IObjectTypeDefinition>())
{
foreach (var field in type.Fields)
{
foreach (var argument in field.Arguments)
{
var argumentInnerType = argument.Type.InnerType();

if (argumentInnerType is IInputObjectTypeDefinition inputObjectType
&& (argument.HasRequireDirective || field.IsLookup))
{
preservedInputTypeNames.Add(inputObjectType.Name);
}
}
}
}
}

return preservedInputTypeNames;
}

/// <summary>
/// Takes two arguments with the same name but possibly differing in type, description, or
/// default value, and returns a single, unified argument definition.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,55 @@ public void Merge_FourNamedSchemas_AddsFusionDefinitions()
SchemaFormatter.FormatAsString(result.Value).MatchSnapshot(extension: ".graphql");
}

[Fact]
public void Merge_WithRequireCustomScalar_RetainsScalarType()
{
// arrange
var sourceSchemaTextA =
new SourceSchemaText(
"A",
"""
type Query {
product: Product
}

type Product {
weight: Int!
}
""");
var sourceSchemaTextB =
new SourceSchemaText(
"B",
"""
type Product {
deliveryEstimate(
zip: String!
weight: Weight! @require(field: "weight")
): Int!
}

scalar Weight
""");
var compositionLog = new CompositionLog();
var sourceSchemaParser1 = new SourceSchemaParser(sourceSchemaTextA, compositionLog);
var sourceSchemaParser2 = new SourceSchemaParser(sourceSchemaTextB, compositionLog);
var schema1 = sourceSchemaParser1.Parse().Value;
var schema2 = sourceSchemaParser2.Parse().Value;
var schemas =
ImmutableSortedSet.Create(
new SchemaByNameComparer<MutableSchemaDefinition>(), schema1, schema2);
new SourceSchemaEnricher(schema1, schemas).Enrich();
new SourceSchemaEnricher(schema2, schemas).Enrich();
var merger = new SourceSchemaMerger(schemas);

// act
var result = merger.Merge();

// assert
Assert.True(result.IsSuccess);
Assert.True(result.Value.Types.ContainsName("Weight"));
}

[Fact]
public void Merge_WithRequireInputObject_RetainsInputObjectType()
{
Expand Down Expand Up @@ -162,4 +211,58 @@ input ProductDimensionInput @inaccessible {
Assert.True(result.IsSuccess);
Assert.True(result.Value.Types.ContainsName("ProductDimensionInput"));
}

[Fact]
public void Merge_WithRequireInputObject_ThatUsesCustomScalar_RetainsScalarDependency()
{
// arrange
var sourceSchemaTextA =
new SourceSchemaText(
"A",
"""
type Query {
product: Product
}

type Product {
weight: Int!
}
""");
var sourceSchemaTextB =
new SourceSchemaText(
"B",
"""
type Product {
deliveryEstimate(
zip: String!
dimension: ProductDimensionInput! @require(field: "{ weight }")
): Int!
}

input ProductDimensionInput @inaccessible {
coordinate: Position!
}

scalar Position
""");
var compositionLog = new CompositionLog();
var sourceSchemaParser1 = new SourceSchemaParser(sourceSchemaTextA, compositionLog);
var sourceSchemaParser2 = new SourceSchemaParser(sourceSchemaTextB, compositionLog);
var schema1 = sourceSchemaParser1.Parse().Value;
var schema2 = sourceSchemaParser2.Parse().Value;
var schemas =
ImmutableSortedSet.Create(
new SchemaByNameComparer<MutableSchemaDefinition>(), schema1, schema2);
new SourceSchemaEnricher(schema1, schemas).Enrich();
new SourceSchemaEnricher(schema2, schemas).Enrich();
var merger = new SourceSchemaMerger(schemas);

// act
var result = merger.Merge();

// assert
Assert.True(result.IsSuccess);
Assert.True(result.Value.Types.ContainsName("ProductDimensionInput"));
Assert.True(result.Value.Types.ContainsName("Position"));
}
}
Loading