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
7 changes: 3 additions & 4 deletions Turbo.Catalog/CatalogService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Turbo.Catalog.Exceptions;
using Turbo.Database.Context;
using Turbo.Primitives.Catalog;
using Turbo.Primitives.Catalog.Enums;
Expand All @@ -29,20 +30,18 @@
return catalogType switch
{
CatalogType.Normal => _normalCatalogProvider.Current,
CatalogType.BuildersClub => throw new NotSupportedException(
$"Catalog type {catalogType} is not supported."
),
_ => throw new NotSupportedException($"Catalog type {catalogType} is not supported."),
CatalogType.BuildersClub => throw new CatalogTypeNotSupportedException(catalogType),
_ => throw new CatalogTypeNotSupportedException(catalogType),
};
}

public async Task<UpcomingLtdSnapshot?> GetUpcomingLtdAsync(CancellationToken ct)
{
await using var dbCtx = await _dbCtxFactory.CreateDbContextAsync(ct);

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 40 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

// Find the nearest upcoming active LTD drop
var now = DateTime.UtcNow;
var nextSeries = await dbCtx

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-ubuntu-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-macos-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Check warning on line 44 in Turbo.Catalog/CatalogService.cs

View workflow job for this annotation

GitHub Actions / quality-windows-latest

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)
.LtdSeries.AsNoTracking()
.Where(s => s.IsActive && s.StartsAt > now)
.OrderBy(s => s.StartsAt)
Expand Down
11 changes: 11 additions & 0 deletions Turbo.Catalog/Exceptions/CatalogTypeNotSupportedException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using Turbo.Primitives.Catalog.Enums;

namespace Turbo.Catalog.Exceptions;

/// <summary>Raised when a catalog snapshot is requested for a catalog type that has no provider.</summary>
public sealed class CatalogTypeNotSupportedException(CatalogType catalogType)
: Exception($"Catalog type '{catalogType}' is not supported.")
{
public CatalogType CatalogType { get; } = catalogType;
}
16 changes: 15 additions & 1 deletion Turbo.Main/Extensions/HostApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,21 @@ public static HostApplicationBuilder AddOrleans(this HostApplicationBuilder buil
.AddMemoryGrainStorage(OrleansStorageNames.PLAYER_STORE)
.AddMemoryGrainStorage(OrleansStorageNames.ROOM_STORE)
.AddMemoryStreams(OrleansStreamProviders.DEFAULT_STREAM_PROVIDER)
.AddMemoryStreams(OrleansStreamProviders.ROOM_STREAM_PROVIDER);
.AddMemoryStreams(
OrleansStreamProviders.ROOM_STREAM_PROVIDER,
streams =>
streams.ConfigurePullingAgent(ob =>
ob.Configure(options =>
{
// Memory streams are pull-based; the default 100ms poll
// adds up to 100ms of jitter to every room packet, which
// is visible in the avatar walk cadence.
options.GetQueueMsgsTimerPeriod = TimeSpan.FromMilliseconds(
10
);
})
)
);
}
)
);
Expand Down
24 changes: 24 additions & 0 deletions Turbo.Players/Exceptions/WalletDebitFailedException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using Turbo.Primitives.Players.Wallet;

namespace Turbo.Players.Exceptions;

/// <summary>
/// Raised when a wallet debit did not move the balance by the requested amount, which means the
/// currency could not cover it. Carries the request detail so the caller can report which currency
/// fell short without re-deriving it.
/// </summary>
public sealed class WalletDebitFailedException(
CurrencyKind currencyKind,
int requestedAmount,
int appliedAmount
) : Exception($"Wallet debit of {requestedAmount} failed; {appliedAmount} was applied instead.")
{
public CurrencyKind CurrencyKind { get; } = currencyKind;

/// <summary>The amount the debit asked for.</summary>
public int RequestedAmount { get; } = requestedAmount;

/// <summary>The amount the balance actually moved by.</summary>
public int AppliedAmount { get; } = appliedAmount;
}
7 changes: 6 additions & 1 deletion Turbo.Players/Grains/PlayerWalletGrain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.EntityFrameworkCore;
using Orleans;
using Turbo.Database.Context;
using Turbo.Players.Exceptions;
using Turbo.Primitives.Orleans;
using Turbo.Primitives.Players.Enums.Wallet;
using Turbo.Primitives.Players.Grains;
Expand Down Expand Up @@ -54,7 +55,11 @@ CancellationToken ct
var update = await ProcessDebitRequestAsync(dbCtx, request, ct);

if (update.ChangedBy != request.Amount)
throw new Exception("Failed to process debit request");
throw new WalletDebitFailedException(
request.CurrencyKind,
request.Amount,
update.ChangedBy
);

updates.Add(update);
}
Expand Down
62 changes: 62 additions & 0 deletions Turbo.Plugins/Exceptions/PluginAssemblyException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;

namespace Turbo.Plugins.Exceptions;

public enum PluginAssemblyErrorType
{
/// <summary>No assembly matching the manifest could be located in the plugin directory.</summary>
NotFound,

/// <summary>The assembly loaded, but contains no ITurboPlugin entry point.</summary>
EntryPointNotFound,

/// <summary>The entry point's key does not match the key declared in the manifest.</summary>
KeyMismatch,
}

/// <summary>Raised when a plugin's assembly cannot be located, or does not expose a usable entry point.</summary>
public sealed class PluginAssemblyException : PluginException
{
public PluginAssemblyErrorType ErrorType { get; }

/// <summary>Plugin directory or assembly name involved in the failure, when known.</summary>
public string? AssemblyLocation { get; }

/// <summary>Key reported by the loaded entry point, set only for <see cref="PluginAssemblyErrorType.KeyMismatch"/>.</summary>
public string? EntryPointKey { get; }

public PluginAssemblyException(
PluginAssemblyErrorType errorType,
string? pluginKey = null,
string? assemblyLocation = null,
string? entryPointKey = null,
Exception? innerException = null
)
: base(
BuildMessage(errorType, pluginKey, assemblyLocation, entryPointKey),
pluginKey,
innerException
)
{
ErrorType = errorType;
AssemblyLocation = assemblyLocation;
EntryPointKey = entryPointKey;
}

private static string BuildMessage(
PluginAssemblyErrorType errorType,
string? pluginKey,
string? assemblyLocation,
string? entryPointKey
) =>
errorType switch
{
PluginAssemblyErrorType.NotFound =>
$"No assembly found for plugin '{pluginKey}' in '{assemblyLocation}'.",
PluginAssemblyErrorType.EntryPointNotFound =>
$"No ITurboPlugin entry point found in assembly '{assemblyLocation}'.",
PluginAssemblyErrorType.KeyMismatch =>
$"Plugin key mismatch: manifest declares '{pluginKey}' but the entry point reports '{entryPointKey}'.",
_ => $"Plugin assembly for '{pluginKey}' is not valid.",
};
}
58 changes: 58 additions & 0 deletions Turbo.Plugins/Exceptions/PluginDependencyException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace Turbo.Plugins.Exceptions;

public enum PluginDependencyErrorType
{
/// <summary>A declared dependency is not present in the discovered plugin set.</summary>
Missing,

/// <summary>The dependency graph contains a cycle and cannot be ordered.</summary>
Cycle,

/// <summary>The plugin cannot be reloaded or unloaded because live plugins depend on it.</summary>
DependentsActive,

/// <summary>The plugin cannot be reloaded because one of its dependencies is not live.</summary>
DependencyInactive,
}

/// <summary>Raised when the plugin dependency graph prevents a load, reload or unload.</summary>
public sealed class PluginDependencyException : PluginException
{
public PluginDependencyErrorType ErrorType { get; }

/// <summary>The dependencies or dependents that caused the failure.</summary>
public ImmutableArray<string> RelatedKeys { get; }

public PluginDependencyException(
PluginDependencyErrorType errorType,
string? pluginKey = null,
IEnumerable<string>? relatedKeys = null
)
: base(BuildMessage(errorType, pluginKey, relatedKeys?.ToImmutableArray() ?? []), pluginKey)
{
ErrorType = errorType;
RelatedKeys = relatedKeys?.ToImmutableArray() ?? [];
}

private static string BuildMessage(
PluginDependencyErrorType errorType,
string? pluginKey,
ImmutableArray<string> relatedKeys
) =>
errorType switch
{
PluginDependencyErrorType.Missing =>
$"Plugin '{pluginKey}' is missing dependency '{string.Join(", ", relatedKeys)}'.",
PluginDependencyErrorType.Cycle => "Plugin dependencies contain a cycle.",
PluginDependencyErrorType.DependentsActive =>
$"Plugin '{pluginKey}' cannot be reloaded or unloaded while dependents are active: {string.Join(", ", relatedKeys)}.",
PluginDependencyErrorType.DependencyInactive =>
$"Plugin '{pluginKey}' cannot be reloaded because dependency '{string.Join(", ", relatedKeys)}' is not active.",
_ => $"Plugin '{pluginKey}' has an unsatisfied dependency graph.",
};
}
17 changes: 17 additions & 0 deletions Turbo.Plugins/Exceptions/PluginException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace Turbo.Plugins.Exceptions;

/// <summary>
/// Base type for plugin discovery, loading and lifecycle failures. Callers that want to treat any
/// plugin problem uniformly (for example, skipping a bad plugin folder) can catch this type.
/// </summary>
public abstract class PluginException(
string message,
string? pluginKey = null,
Exception? innerException = null
) : Exception(message, innerException)
{
/// <summary>The plugin key this failure relates to, when it is known.</summary>
public string? PluginKey { get; } = pluginKey;
}
10 changes: 10 additions & 0 deletions Turbo.Plugins/Exceptions/PluginExportNotBoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace Turbo.Plugins.Exceptions;

/// <summary>Raised when a reloadable export is read before any implementation has been bound to it.</summary>
public sealed class PluginExportNotBoundException(Type exportType)
: PluginException($"Export '{exportType.Name}' has not been bound yet.")
{
public Type ExportType { get; } = exportType;
}
55 changes: 55 additions & 0 deletions Turbo.Plugins/Exceptions/PluginManifestException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;

namespace Turbo.Plugins.Exceptions;

public enum PluginManifestErrorType
{
/// <summary>No manifest.json exists at the expected path.</summary>
NotFound,

/// <summary>The manifest exists but could not be read or deserialized.</summary>
Unreadable,

/// <summary>The manifest parsed, but a required field was absent or blank.</summary>
MissingField,
}

/// <summary>Raised when a plugin's manifest.json is absent, unreadable or incomplete.</summary>
public sealed class PluginManifestException : PluginException
{
public PluginManifestErrorType ErrorType { get; }

/// <summary>Path to the manifest, or to the plugin directory when the manifest is missing.</summary>
public string ManifestPath { get; }

/// <summary>The offending manifest field, set only for <see cref="PluginManifestErrorType.MissingField"/>.</summary>
public string? FieldName { get; }

public PluginManifestException(
PluginManifestErrorType errorType,
string manifestPath,
string? fieldName = null,
Exception? innerException = null
)
: base(BuildMessage(errorType, manifestPath, fieldName), null, innerException)
{
ErrorType = errorType;
ManifestPath = manifestPath;
FieldName = fieldName;
}

private static string BuildMessage(
PluginManifestErrorType errorType,
string manifestPath,
string? fieldName
) =>
errorType switch
{
PluginManifestErrorType.NotFound => $"Plugin manifest not found at '{manifestPath}'.",
PluginManifestErrorType.Unreadable =>
$"Plugin manifest at '{manifestPath}' could not be read.",
PluginManifestErrorType.MissingField =>
$"Plugin manifest at '{manifestPath}' is missing required field '{fieldName}'.",
_ => $"Plugin manifest at '{manifestPath}' is not valid.",
};
}
4 changes: 2 additions & 2 deletions Turbo.Plugins/Exports/ReloadableExport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading;
using System.Threading.Tasks;
using Turbo.Contracts.Plugins.Exports;
using Turbo.Plugins.Exceptions;

namespace Turbo.Plugins.Exports;

Expand All @@ -12,8 +13,7 @@ public sealed class ReloadableExport<T> : IExport<T>
private volatile T? _current;
private ImmutableArray<Action<T>> _subs = [];

public T Current =>
_current ?? throw new InvalidOperationException($"Export {typeof(T).Name} not bound yet.");
public T Current => _current ?? throw new PluginExportNotBoundException(typeof(T));

public async Task SwapAsync(T value)
{
Expand Down
Loading
Loading