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 @@ -35,6 +35,7 @@ public static async Task<bool> TryComposeAsync(
archive,
environment,
compositionSettings,
legacyArchive: null,
cancellationToken);

var output = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public static async Task<CompositionResult<MutableSchemaDefinition>> ComposeAsyn
FusionArchive archive,
string environment,
CompositionSettings? compositionSettings,
Stream? legacyArchive,
CancellationToken cancellationToken)
{
var existingSourceSchemaNames = new SortedSet<string>(
Expand Down Expand Up @@ -148,6 +149,16 @@ await archive.SetGatewayConfigurationAsync(

await SaveCompositionSettingsAsync(archive, mergedCompositionSettings, cancellationToken);

if (legacyArchive is not null)
{
if (legacyArchive.CanSeek)
{
legacyArchive.Position = 0;
}

await archive.SetLegacyArchiveFileAsync(legacyArchive, cancellationToken);
}

await archive.CommitAsync(cancellationToken);

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<ItemGroup>
<InternalsVisibleTo Include="nitro" />
<InternalsVisibleTo Include="ChilliCream.Nitro.CommandLine.Tests" />
<InternalsVisibleTo Include="HotChocolate.Fusion.Aspire" />
<InternalsVisibleTo Include="HotChocolate.Fusion.Composition.Tests" />
<InternalsVisibleTo Include="HotChocolate.Fusion.Execution.Tests" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ public void Handle(SchemaEvent @event, CompositionContext context)
.SelectMany(x => x.o.Fields.AsEnumerable(), (x, f) => (x.s, x.o, f))
.SelectMany(
x => x.f.Arguments.AsEnumerable().Where(a => a.HasIsDirective),
(x, a) => new FieldArgumentInfo(a, x.f, x.o, x.s));
(x, a) => new FieldArgumentInfo(a, x.f, x.o, x.s))
.Where(info =>
schema.Types.ContainsName(info.Argument.Type.AsTypeDefinition().Name)
&& schema.Types.ContainsName(info.Field.Type.AsTypeDefinition().Name));

var validator = new FieldSelectionMapValidator(schema);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public void Handle(SchemaEvent @event, CompositionContext context)

var sourceKeyDirectives = context.SchemaDefinitions
.SelectMany(s => s.Types.OfType<MutableComplexTypeDefinition>(), (s, o) => (s, o))
.Where(x => schema.Types.ContainsName(x.o.Name))
.SelectMany(
x =>
x.o.Directives.AsEnumerable().Where(d => d.Name == DirectiveNames.Key),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ public void Handle(SchemaEvent @event, CompositionContext context)
.SelectMany(x => x.o.Fields.AsEnumerable(), (x, f) => (x.s, x.o, f))
.SelectMany(
x => x.f.Arguments.AsEnumerable().Where(a => a.HasRequireDirective),
(x, a) => new FieldArgumentInfo(a, x.f, x.o, x.s));
(x, a) => new FieldArgumentInfo(a, x.f, x.o, x.s))
.Where(info =>
schema.Types.ContainsName(info.Argument.Type.AsTypeDefinition().Name)
&& schema.Types.ContainsName(info.Type.Name));

var validator = new FieldSelectionMapValidator(
schema,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@ private static async Task ExtractFileAsync(
private int GetAllowedSize(FileKind kind)
=> kind switch
{
FileKind.Schema
=> _readOptions.MaxAllowedSchemaSize,
FileKind.LegacyArchive => _readOptions.MaxAllowedLegacyArchiveSize,
FileKind.Schema => _readOptions.MaxAllowedSchemaSize,
FileKind.Manifest or FileKind.Settings or FileKind.Metadata or FileKind.Signature
=> _readOptions.MaxAllowedSettingsSize,
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null)
Expand Down
3 changes: 2 additions & 1 deletion src/HotChocolate/Fusion/src/Fusion.Packaging/FileKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ internal enum FileKind
Settings,
Manifest,
Metadata,
Signature
Signature,
LegacyArchive
}
4 changes: 4 additions & 0 deletions src/HotChocolate/Fusion/src/Fusion.Packaging/FileNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal static class FileNames
public const string CompositionSettings = "composition-settings.json";
public const string SignatureManifest = ".signature/manifest.json";
public const string Signature = ".signature/signature.p7s";
public const string LegacyArchive = "legacy-v1-archive.fgp";

public static string GetGatewaySchemaPath(Version version)
=> string.Format(GatewaySchemaFormat, version);
Expand Down Expand Up @@ -46,6 +47,9 @@ public static FileKind GetFileKind(string fileName)
case "signature.json":
return FileKind.Signature;

case "legacy-v1-archive.fgp":
return FileKind.LegacyArchive;

default:
return FileKind.Settings;
}
Expand Down
43 changes: 42 additions & 1 deletion src/HotChocolate/Fusion/src/Fusion.Packaging/FusionArchive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ public static FusionArchive Open(
ArgumentNullException.ThrowIfNull(stream);
var readOptions = new FusionArchiveReadOptions(
options.MaxAllowedSchemaSize ?? FusionArchiveReadOptions.Default.MaxAllowedSchemaSize,
options.MaxAllowedSettingsSize ?? FusionArchiveReadOptions.Default.MaxAllowedSettingsSize);
options.MaxAllowedSettingsSize ?? FusionArchiveReadOptions.Default.MaxAllowedSettingsSize,
options.MaxAllowedLegacyArchiveSize ?? FusionArchiveReadOptions.Default.MaxAllowedLegacyArchiveSize);
return new FusionArchive(stream, mode, leaveOpen, readOptions);
}

Expand Down Expand Up @@ -533,6 +534,46 @@ Task<Stream> OpenReadSchemaAsync(CancellationToken ct)
=> _session.OpenReadAsync(FileNames.GetSourceSchemaPath(schemaName), FileKind.Schema, ct);
}

/// <summary>
/// Sets the legacy archive file in the archive by copying the content from the provided stream.
/// </summary>
/// <param name="content">The stream containing the legacy archive content.</param>
/// <param name="cancellationToken">Token to cancel the operation.</param>
/// <exception cref="ArgumentNullException">Thrown when content is null.</exception>
/// <exception cref="ObjectDisposedException">Thrown when the archive has been disposed.</exception>
/// <exception cref="InvalidOperationException">Thrown when the archive is read-only.</exception>
public async Task SetLegacyArchiveFileAsync(
Stream content,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(content);
ObjectDisposedException.ThrowIf(_disposed, this);
EnsureMutable();

await using var stream = _session.OpenWrite(FileNames.LegacyArchive);
await content.CopyToAsync(stream, cancellationToken);
}

/// <summary>
/// Attempts to get the legacy archive file from the archive as a stream.
/// Returns null if no legacy archive file is present.
/// </summary>
/// <param name="cancellationToken">Token to cancel the operation.</param>
/// <returns>A stream to read the legacy archive content, or null if not present.</returns>
/// <exception cref="ObjectDisposedException">Thrown when the archive has been disposed.</exception>
public async Task<Stream?> TryGetLegacyArchiveFileAsync(
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(_disposed, this);

if (!await _session.ExistsAsync(FileNames.LegacyArchive, FileKind.LegacyArchive, cancellationToken))
{
return null;
}

return await _session.OpenReadAsync(FileNames.LegacyArchive, FileKind.LegacyArchive, cancellationToken);
}

/// <summary>
/// Digitally signs the archive using the provided certificate with private key.
/// Creates a manifest of all files and their SHA-256 hashes, then signs the manifest.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ public struct FusionArchiveOptions
/// Gets or sets the maximum allowed size of the settings in the archive.
/// </summary>
public int? MaxAllowedSettingsSize { get; set; }

/// <summary>
/// Gets or sets the maximum allowed size of the legacy archive in the archive.
/// </summary>
public int? MaxAllowedLegacyArchiveSize { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ namespace HotChocolate.Fusion.Packaging;
/// </summary>
internal readonly record struct FusionArchiveReadOptions(
int MaxAllowedSchemaSize,
int MaxAllowedSettingsSize)
int MaxAllowedSettingsSize,
int MaxAllowedLegacyArchiveSize)
{
/// <summary>
/// Gets the default read options.
/// </summary>
public static FusionArchiveReadOptions Default { get; } = new(50_000_000, 512_000);
public static FusionArchiveReadOptions Default { get; } = new(
50_000_000,
512_000,
100_000_000);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ public static class WellKnownVersions
public static readonly Version LatestGatewayFormatVersion = new(2, 0, 0);

public static readonly Version LatestSourceSchemaVersion = new(2, 0, 0);

public static readonly Version LegacyGatewayFormatVersion = new(1, 0, 0);
}
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,57 @@ public async Task GetSourceSchemaNames_WithoutMetadata_ReturnsEmpty()
Assert.Empty(names);
}

[Fact]
public async Task SetLegacyArchiveFile_WithValidContent_RoundTripsCorrectly()
{
// arrange
await using var stream = CreateStream();
var content = Encoding.UTF8.GetBytes("legacy archive payload");
await using var contentStream = new MemoryStream(content);

// act
using (var archive = FusionArchive.Create(stream, leaveOpen: true))
{
await archive.SetLegacyArchiveFileAsync(contentStream);
await archive.CommitAsync();
}

// assert
stream.Position = 0;
using var readArchive = FusionArchive.Open(stream, leaveOpen: true);
await using var retrieved = await readArchive.TryGetLegacyArchiveFileAsync();
Assert.NotNull(retrieved);
await using var buffer = new MemoryStream();
await retrieved.CopyToAsync(buffer);
Assert.Equal(content, buffer.ToArray());
}

[Fact]
public async Task SetLegacyArchiveFile_WithNullContent_ThrowsArgumentNullException()
{
// arrange
await using var stream = CreateStream();
using var archive = FusionArchive.Create(stream);

// act & assert
await Assert.ThrowsAsync<ArgumentNullException>(
() => archive.SetLegacyArchiveFileAsync(null!));
}

[Fact]
public async Task TryGetLegacyArchiveFile_WhenNotSet_ReturnsNull()
{
// arrange
await using var stream = CreateStream();
using var archive = FusionArchive.Create(stream);

// act
var result = await archive.TryGetLegacyArchiveFileAsync();

// assert
Assert.Null(result);
}

private Stream CreateStream()
{
var stream = new MemoryStream();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,62 @@ public Task<JsonDocument> GetFusionGraphSettingsAsync(
return ReadJsonPartAsync(part, cancellationToken);
}

internal async Task<ReadOnlyMemory<byte>?> TryGetFusionGraphSettingsRawAsync(
CancellationToken cancellationToken = default)
{
if ((_package.FileOpenAccess & FileAccess.Read) != FileAccess.Read)
{
throw new FusionGraphPackageException(FusionGraphPackage_CannotRead);
}

if (!_package.RelationshipExists(FusionSettingsId))
{
return null;
}

var relationship = _package.GetRelationship(FusionSettingsId);
var part = _package.GetPart(relationship.TargetUri);
return await ReadPartRawBytesAsync(part, cancellationToken);
}

internal async Task<ReadOnlyMemory<byte>?> TryGetSubgraphConfigurationRawAsync(
string name,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(name);

if ((_package.FileOpenAccess & FileAccess.Read) != FileAccess.Read)
{
throw new FusionGraphPackageException(FusionGraphPackage_CannotRead);
}

if (!_package.RelationshipExists(name))
{
return null;
}

var relationship = _package.GetRelationship(name);
var rootPart = _package.GetPart(relationship.TargetUri);
return await ReadPartRawBytesAsync(rootPart, cancellationToken);
}

private static async Task<ReadOnlyMemory<byte>> ReadPartRawBytesAsync(
PackagePart part,
CancellationToken ct)
{
await using var stream = part.GetStream(FileMode.Open, FileAccess.Read);
var buffer = new ArrayBufferWriter<byte>();
int read;

do
{
read = await stream.ReadAsync(buffer.GetMemory(256), ct);
buffer.Advance(read);
} while (read > 0);

return buffer.WrittenMemory;
}

public Task SetFusionGraphSettingsAsync(
JsonDocument document,
CancellationToken cancellationToken = default)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
<IsPackable>true</IsPackable>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="nitro" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\HotChocolate\Language\src\Language\HotChocolate.Language.csproj" />
</ItemGroup>
Expand Down
Loading
Loading