Skip to content
Open
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
@@ -1,3 +1,4 @@
using System.Text.Json;
using HotChocolate.Buffers;
using HotChocolate.Fusion.Language;
using HotChocolate.Language;
Expand Down Expand Up @@ -67,18 +68,24 @@ internal static class ResultDataMapper
return null;
}

// Note: to capture data from the introspection
// system we would need to also cover raw field results.
if (result is LeafFieldResult field)
{
if (field.HasNullValue)
{
return NullValueNode.Default;
}

context.Writer ??= new PooledArrayWriter();
var parser = new JsonValueParser(buffer: context.Writer);
return parser.Parse(field.Value);
return MapLeaf(field.Value, ref context.Writer);
}

if (result is ListFieldResult listField)
{
if (listField.HasNullValue || listField.Value is null)
{
return NullValueNode.Default;
}

return MapListResult(listField.Value, ref context.Writer);
}

throw new InvalidSelectionMapPathException(node);
Expand Down Expand Up @@ -279,6 +286,107 @@ internal static class ResultDataMapper
return currentResult;
}

private static IValueNode MapListResult(ListResult list, ref PooledArrayWriter? writer)
{
switch (list)
{
case LeafListResult leafList:
{
var items = new List<IValueNode>(leafList.Items.Count);
foreach (var json in leafList.Items)
{
if (json.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
{
items.Add(NullValueNode.Default);
}
else
{
items.Add(MapLeaf(json, ref writer));
}
}

return new ListValueNode(items);
}
case ObjectListResult objectList:
{
var items = new List<IValueNode>(objectList.Items.Count);
foreach (var obj in objectList.Items)
{
items.Add(obj is null ? NullValueNode.Default : MapObjectResult(obj, ref writer));
}

return new ListValueNode(items);
}
case NestedListResult nestedList:
{
var items = new List<IValueNode>(nestedList.Items.Count);
foreach (var inner in nestedList.Items)
{
items.Add(inner is null ? NullValueNode.Default : MapListResult(inner, ref writer));
}

return new ListValueNode(items);
}
case RawListFieldResult raw:
{
// fallback to JSON serialization for raw encoded list; depth impact is minimal (flat list)
using var temp = new PooledArrayWriter();
using (var jsonWriter = new Utf8JsonWriter(temp))
{
raw.WriteTo(jsonWriter);
}

writer ??= new PooledArrayWriter();
var parser = new JsonValueParser(buffer: writer);
return parser.Parse(temp.WrittenSpan);
}
default:
throw new NotSupportedException($"Unsupported list result type {list.GetType().Name}.");
}
}

private static IValueNode MapObjectResult(ObjectResult obj, ref PooledArrayWriter? writer)
{
var fields = obj.Fields;
var list = new List<ObjectFieldNode>(fields.Length);
for (var i = 0; i < fields.Length; i++)
{
var field = fields[i];
if (field.Selection.IsInternal)
{
continue;
}

var valueNode = MapFieldResult(field, ref writer);
list.Add(new ObjectFieldNode(field.Selection.ResponseName, valueNode));
}

return new ObjectValueNode(list);
}

private static IValueNode MapFieldResult(FieldResult field, ref PooledArrayWriter? writer)
{
if (field.HasNullValue)
{
return NullValueNode.Default;
}

return field switch
{
LeafFieldResult leaf => MapLeaf(leaf.Value, ref writer),
ObjectFieldResult { Value: { } obj } => MapObjectResult(obj, ref writer),
ListFieldResult { Value: { } list } => MapListResult(list, ref writer),
_ => NullValueNode.Default
};
}

private static IValueNode MapLeaf(JsonElement element, ref PooledArrayWriter? writer)
{
writer ??= new PooledArrayWriter();
var parser = new JsonValueParser(buffer: writer);
return parser.Parse(element);
}

private readonly ref struct Context
{
private readonly ref PooledArrayWriter? _writer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,17 @@ public async Task Require_Object_In_A_List()
"c",
b => b.AddQueryType<BookShipping.Query>());

using var server4 = CreateSourceSchema(
"d",
b => b.AddQueryType<BookGenre.Query>()
.AddType<BookGenre.Query>());

using var gateway = await CreateCompositeSchemaAsync(
[
("a", server1),
("b", server2),
("c", server3)
("c", server3),
("d", server4)
]);

// act
Expand All @@ -54,6 +60,59 @@ public async Task Require_Object_In_A_List()
await MatchSnapshotAsync(gateway, request, result);
}

[Fact]
public async Task Require_Enumerable_In_List()
{
// arrange
using var server1 = CreateSourceSchema(
"a",
b => b.AddQueryType<BookCatalog.Query>());

using var server2 = CreateSourceSchema(
"b",
b => b.AddQueryType<BookInventory.Query>());

using var server3 = CreateSourceSchema(
"c",
b => b.AddQueryType<BookShipping.Query>());
using var server4 = CreateSourceSchema(
"d",
b => b.AddQueryType<BookGenre.Query>()
.AddType<BookGenre.Query>());

using var gateway = await CreateCompositeSchemaAsync(
[
("a", server1),
("b", server2),
("c", server3),
("d", server4)
]);

// act
using var client = GraphQLHttpClient.Create(gateway.CreateClient());

var request = new OperationRequest(
"""
{
books {
nodes {
title
genres {
name
}
}
}
}
""");

using var result = await client.PostAsync(
request,
new Uri("http://localhost:5000/graphql"));

// assert
await MatchSnapshotAsync(gateway, request, result);
}

[Fact(Skip = "Not yet supported")]
public async Task Require_On_MutationPayload()
{
Expand Down Expand Up @@ -132,9 +191,11 @@ public static class BookCatalog
{
private static readonly Dictionary<int, Book> s_books = new()
{
{ 1, new Book { Id = 1, Title = "The Great Gatsby", Author = new Author { Id = 1 } } },
{ 2, new Book { Id = 2, Title = "1984", Author = new Author { Id = 2 } } },
{ 3, new Book { Id = 3, Title = "The Catcher in the Rye", Author = new Author { Id = 3 } } }
{
1, new Book { Id = 1, Title = "The Great Gatsby", Author = new Author { Id = 1 }, GenreIds = [1, 3] }
},
{ 2, new Book { Id = 2, Title = "1984", Author = new Author { Id = 2 }, GenreIds = [2, 3] } },
{ 3, new Book { Id = 3, Title = "The Catcher in the Rye", Author = new Author { Id = 3 }, GenreIds = [1] } }
};

public class Query
Expand All @@ -155,6 +216,8 @@ public class Book
public required string Title { get; set; }

public required Author Author { get; set; }

public required IEnumerable<int> GenreIds { get; set; }
}

public class Author
Expand Down Expand Up @@ -194,6 +257,40 @@ public class BookDimension
}
}

public static class BookGenre
{
private static readonly Dictionary<int, Genre> s_books = new()
{
{ 1, new Genre { Id = 1, Name = "Fiction" } },
{ 2, new Genre { Id = 2, Name = "Science Fiction" } },
{ 3, new Genre { Id = 3, Name = "Classic" } }
};

public class Query
{
[Lookup]
public Book? GetBook(int id)
=> new() { Id = id };
}

public class Genre
{
public required int Id { get; set; }
public required string Name { get; set; }
}

public class Book
{
public int Id { get; set; }

public IEnumerable<Genre> Genres(
[Require("genreIds")] IEnumerable<int> genreIds)
{
return genreIds.Select(id => s_books[id]);
}
}
}

public static class BookShipping
{
public class Query
Expand All @@ -216,7 +313,7 @@ public int EstimatedDelivery(
height: dimension.height
}
""")]
BookDimensionInput dimension)
BookDimensionInput dimension)
{
return dimension.Width + dimension.Height;
}
Expand Down
Loading