Skip to content

Commit

Permalink
Merge branch 'main' into gai/opt-in-features
Browse files Browse the repository at this point in the history
  • Loading branch information
glen-84 committed Nov 18, 2024
2 parents 3d79b77 + f086132 commit 4a8b57c
Show file tree
Hide file tree
Showing 196 changed files with 26,642 additions and 406 deletions.
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "9.0.100-rc.2.24474.11",
"version": "9.0.100",
"rollForward": "latestMinor"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ protected virtual void OnWriteResponseHeaders(
return _multiPartFormat;
}

if (mediaType.Kind is EventStream)
if (mediaType.Kind is EventStream or All)
{
return _eventStreamFormat;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ protected virtual TestServer CreateStarWarsServer(
.AddStarWarsTypes()
.AddTypeExtension<QueryExtension>()
.AddTypeExtension<SubscriptionsExtensions>()
.AddType<Foo>()
.AddStarWarsRepositories()
.AddInMemorySubscriptions()
.UseInstrumentation()
Expand Down Expand Up @@ -165,4 +166,10 @@ protected virtual TestServer CreateServer(
.UseRouting()
.UseEndpoints(endpoints => configureConventions?.Invoke(endpoints)));
}

[DirectiveType(DirectiveLocation.Subscription)]
public class Foo
{
public required int Bar { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,102 @@ public async Task EventStream_Sends_KeepAlive()
Snapshot
.Create()
.Add(response)
.MatchInline("""
.MatchInline(
"""
Headers:
Cache-Control: no-cache
Content-Type: text/event-stream; charset=utf-8
-------------------------->
Status Code: OK
-------------------------->
event: next
data: {"data":{"delay":"next"}}
:
event: next
data: {"data":{"delay":"next"}}
:
event: complete
""");
}

[Fact]
public async Task EventStream_When_Accept_Is_All()
{
// arrange
var server = CreateStarWarsServer();
var client = server.CreateClient();
client.Timeout = TimeSpan.FromSeconds(30);

// act
using var request = new HttpRequestMessage(HttpMethod.Post, _url);
request.Content = JsonContent.Create(
new ClientQueryRequest
{
Query = "subscription {delay(count: 2, delay:15000)}",
});
request.Headers.Add("Accept", "*/*");

using var response = await client.SendAsync(request, ResponseHeadersRead);

// assert
Snapshot
.Create()
.Add(response)
.MatchInline(
"""
Headers:
Cache-Control: no-cache
Content-Type: text/event-stream; charset=utf-8
-------------------------->
Status Code: OK
-------------------------->
event: next
data: {"data":{"delay":"next"}}
:
event: next
data: {"data":{"delay":"next"}}
:
event: complete
""");
}

[Fact]
public async Task EventStream_When_Accept_Is_All_And_Subscription_Directive()
{
// arrange
var server = CreateStarWarsServer();
var client = server.CreateClient();
client.Timeout = TimeSpan.FromSeconds(30);

// act
using var request = new HttpRequestMessage(HttpMethod.Post, _url);
request.Content = JsonContent.Create(
new ClientQueryRequest
{
Query = "subscription foo @foo(bar: 1) {delay(count: 2, delay:15000)}",
});
request.Headers.Add("Accept", "*/*");

using var response = await client.SendAsync(request, ResponseHeadersRead);

// assert
Snapshot
.Create()
.Add(response)
.MatchInline(
"""
Headers:
Cache-Control: no-cache
Content-Type: text/event-stream; charset=utf-8
Expand Down
1 change: 1 addition & 0 deletions src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public static class Execution
public const string OneSlicingArgumentRequired = "HC0082";

public const string NonNullViolation = "HC0018";
public const string SemanticNonNullViolation = "HC0088";
public const string MustBeInputType = "HC0017";
public const string InvalidType = "HC0016";
public const string QueryNotFound = "HC0015";
Expand Down
10 changes: 10 additions & 0 deletions src/HotChocolate/Core/src/Abstractions/WellKnownDirectives.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ public static class WellKnownDirectives
/// </summary>
public const string Name = "name";

/// <summary>
/// The name of the @semanticNonNull directive.
/// </summary>
public const string SemanticNonNull = "semanticNonNull";

/// <summary>
/// The name of the @semanticNonNull argument levels.
/// </summary>
public const string Levels = "levels";

/// <summary>
/// The name of the @requiresOptIn directive.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/HotChocolate/Core/src/Abstractions/WellKnownMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,9 @@ public static class WellKnownMiddleware
/// The key identifies the authorization middleware.
/// </summary>
public const string Authorization = "HotChocolate.Authorization";

/// <summary>
/// This key identifies the semantic-non-null middleware.
/// </summary>
public const string SemanticNonNull = "HotChocolate.Types.SemanticNonNull";
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ protected override ValueTask<Connection> SliceAsync(
=> source switch
{
IQueryable<TEntity> q => SliceAsyncInternal(context, Executable.From(q), arguments),
IEnumerable<TEntity> e => SliceAsyncInternal(context, Executable.From(e.AsQueryable()), arguments),
IEnumerable<TEntity> e => e.GetType().IsValueType
? throw new GraphQLException("Cannot handle the specified data source.")
: SliceAsyncInternal(context, Executable.From(e.AsQueryable()), arguments),
IQueryableExecutable<TEntity> ex => SliceAsyncInternal(context, ex, arguments),
_ => throw new GraphQLException("Cannot handle the specified data source."),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ protected override ValueTask<CollectionSegment> SliceAsync(
return source switch
{
IQueryable<TEntity> q => ResolveAsync(context, q, arguments, ct),
IEnumerable<TEntity> e => ResolveAsync(context, e.AsQueryable(), arguments, ct),
IEnumerable<TEntity> e => e.GetType().IsValueType
? throw new GraphQLException("Cannot handle the specified data source.")
: ResolveAsync(context, e.AsQueryable(), arguments, ct),
IExecutable<TEntity> ex => SliceAsync(context, ex.Source, arguments),
_ => throw new GraphQLException("Cannot handle the specified data source."),
};
Expand Down
38 changes: 22 additions & 16 deletions src/HotChocolate/Core/src/Types.Scalars/LocalDateType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace HotChocolate.Types;
/// character sequences YYYY-MM-DD. The scalar follows the specification defined in
/// <a href="https://tools.ietf.org/html/rfc3339">RFC3339</a>
/// </summary>
public class LocalDateType : ScalarType<DateTime, StringValueNode>
public class LocalDateType : ScalarType<DateOnly, StringValueNode>
{
private const string _localFormat = "yyyy-MM-dd";

Expand Down Expand Up @@ -42,13 +42,14 @@ public override IValueNode ParseResult(object? resultValue)
{
null => NullValueNode.Default,
string s => new StringValueNode(s),
DateTimeOffset o => ParseValue(o.DateTime),
DateTime dt => ParseValue(dt),
DateOnly d => ParseValue(d),
DateTimeOffset o => ParseValue(DateOnly.FromDateTime(o.DateTime)),
DateTime dt => ParseValue(DateOnly.FromDateTime(dt)),
_ => throw ThrowHelper.LocalDateType_ParseValue_IsInvalid(this),
};
}

protected override DateTime ParseLiteral(StringValueNode valueSyntax)
protected override DateOnly ParseLiteral(StringValueNode valueSyntax)
{
if (TryDeserializeFromString(valueSyntax.Value, out var value))
{
Expand All @@ -58,7 +59,7 @@ protected override DateTime ParseLiteral(StringValueNode valueSyntax)
throw ThrowHelper.LocalDateType_ParseLiteral_IsInvalid(this);
}

protected override StringValueNode ParseValue(DateTime runtimeValue)
protected override StringValueNode ParseValue(DateOnly runtimeValue)
{
return new(Serialize(runtimeValue));
}
Expand All @@ -70,10 +71,13 @@ public override bool TrySerialize(object? runtimeValue, out object? resultValue)
case null:
resultValue = null;
return true;
case DateTime dt:
resultValue = Serialize(dt);
case DateOnly d:
resultValue = Serialize(d);
return true;
case DateTimeOffset o:
resultValue = Serialize(o);
return true;
case DateTimeOffset dt:
case DateTime dt:
resultValue = Serialize(dt);
return true;
default:
Expand All @@ -92,11 +96,14 @@ public override bool TryDeserialize(object? resultValue, out object? runtimeValu
case string s when TryDeserializeFromString(s, out var d):
runtimeValue = d;
return true;
case DateTimeOffset d:
runtimeValue = d.DateTime;
case DateOnly d:
runtimeValue = d;
return true;
case DateTimeOffset o:
runtimeValue = DateOnly.FromDateTime(o.DateTime);
return true;
case DateTime dt:
runtimeValue = dt;
runtimeValue = DateOnly.FromDateTime(dt);
return true;
default:
runtimeValue = null;
Expand All @@ -111,16 +118,15 @@ private static string Serialize(IFormattable value)

private static bool TryDeserializeFromString(
string? serialized,
[NotNullWhen(true)] out DateTime? value)
[NotNullWhen(true)] out DateOnly? value)
{
if (serialized is not null
&& DateTime.TryParse(
&& DateOnly.TryParse(
serialized,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeLocal,
out var dt))
out var date))
{
value = dt;
value = date;
return true;
}

Expand Down
30 changes: 18 additions & 12 deletions src/HotChocolate/Core/src/Types.Scalars/LocalTimeType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace HotChocolate.Types;
/// The `LocalTime` scalar type is a local time string (i.e., with no associated timezone)
/// in 24-hr HH:mm:ss.
/// </summary>
public class LocalTimeType : ScalarType<DateTime, StringValueNode>
public class LocalTimeType : ScalarType<TimeOnly, StringValueNode>
{
private const string _localFormat = "HH:mm:ss";

Expand Down Expand Up @@ -41,13 +41,14 @@ public override IValueNode ParseResult(object? resultValue)
{
null => NullValueNode.Default,
string s => new StringValueNode(s),
TimeOnly t => ParseValue(t),
DateTimeOffset d => ParseValue(d),
DateTime dt => ParseValue(dt),
_ => throw ThrowHelper.LocalTimeType_ParseValue_IsInvalid(this),
};
}

protected override DateTime ParseLiteral(StringValueNode valueSyntax)
protected override TimeOnly ParseLiteral(StringValueNode valueSyntax)
{
if (TryDeserializeFromString(valueSyntax.Value, out var value))
{
Expand All @@ -57,7 +58,7 @@ protected override DateTime ParseLiteral(StringValueNode valueSyntax)
throw ThrowHelper.LocalTimeType_ParseLiteral_IsInvalid(this);
}

protected override StringValueNode ParseValue(DateTime runtimeValue)
protected override StringValueNode ParseValue(TimeOnly runtimeValue)
{
return new(Serialize(runtimeValue));
}
Expand All @@ -69,6 +70,9 @@ public override bool TrySerialize(object? runtimeValue, out object? resultValue)
case null:
resultValue = null;
return true;
case TimeOnly t:
resultValue = Serialize(t);
return true;
case DateTimeOffset dt:
resultValue = Serialize(dt);
return true;
Expand All @@ -88,14 +92,17 @@ public override bool TryDeserialize(object? resultValue, out object? runtimeValu
case null:
runtimeValue = null;
return true;
case string s when TryDeserializeFromString(s, out var d):
runtimeValue = d;
case string s when TryDeserializeFromString(s, out var t):
runtimeValue = t;
return true;
case TimeOnly t:
runtimeValue = t;
return true;
case DateTimeOffset d:
runtimeValue = d.DateTime;
runtimeValue = TimeOnly.FromDateTime(d.DateTime);
return true;
case DateTime d:
runtimeValue = d;
runtimeValue = TimeOnly.FromDateTime(d);
return true;
default:
runtimeValue = null;
Expand All @@ -110,16 +117,15 @@ private static string Serialize(IFormattable value)

private static bool TryDeserializeFromString(
string? serialized,
[NotNullWhen(true)] out DateTime? value)
[NotNullWhen(true)] out TimeOnly? value)
{
if (serialized is not null
&& DateTime.TryParse(
&& TimeOnly.TryParse(
serialized,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeLocal,
out var dt))
out var time))
{
value = dt;
value = time;
return true;
}

Expand Down
7 changes: 7 additions & 0 deletions src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,13 @@ public interface IReadOnlySchemaOptions
/// </summary>
bool EnableStream { get; }

/// <summary>
/// Enables the @semanticNonNull directive and rewrites Non-Null types to nullable types
/// with this directive attached to indicate semantic non-nullability.
/// This feature is experimental and might be changed or removed in the future.
/// </summary>
bool EnableSemanticNonNull { get; }

/// <summary>
/// Specifies the maximum allowed nodes that can be fetched at once through the nodes field.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/HotChocolate/Core/src/Types/SchemaBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public partial class SchemaBuilder : ISchemaBuilder
typeof(IntrospectionTypeInterceptor),
typeof(InterfaceCompletionTypeInterceptor),
typeof(MiddlewareValidationTypeInterceptor),
typeof(SemanticNonNullTypeInterceptor),
typeof(OptInFeaturesTypeInterceptor)
];

Expand Down
Loading

0 comments on commit 4a8b57c

Please sign in to comment.