feat(core): add PluginManifest json parsing

This commit is contained in:
Erik 2026-04-10 09:28:08 +02:00
parent caf57cca3e
commit c082ecf36a
3 changed files with 135 additions and 3 deletions

View file

@ -1,3 +0,0 @@
// src/AcDream.Core/Placeholder.cs
namespace AcDream.Core;
internal static class Placeholder { }

View file

@ -0,0 +1,73 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace AcDream.Core.Plugins;
public sealed record PluginManifest(
string Id,
string DisplayName,
string Version,
string EntryDll,
int ApiVersion,
IReadOnlyList<string> Dependencies)
{
public static PluginManifest Parse(string json)
{
PluginManifestDto? dto;
try
{
dto = JsonSerializer.Deserialize<PluginManifestDto>(json, JsonOptions);
}
catch (JsonException ex)
{
throw new PluginManifestException($"invalid json: {ex.Message}", ex);
}
if (dto is null)
throw new PluginManifestException("manifest is empty");
Require(dto.Id, nameof(dto.Id));
Require(dto.DisplayName, nameof(dto.DisplayName));
Require(dto.Version, nameof(dto.Version));
Require(dto.EntryDll, nameof(dto.EntryDll));
if (dto.ApiVersion <= 0)
throw new PluginManifestException("apiVersion must be >= 1");
return new PluginManifest(
dto.Id!,
dto.DisplayName!,
dto.Version!,
dto.EntryDll!,
dto.ApiVersion,
dto.Dependencies ?? Array.Empty<string>());
}
private static void Require(string? value, string fieldName)
{
if (string.IsNullOrWhiteSpace(value))
throw new PluginManifestException(
$"missing required field: {char.ToLowerInvariant(fieldName[0])}{fieldName[1..]}");
}
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
};
private sealed class PluginManifestDto
{
public string? Id { get; set; }
public string? DisplayName { get; set; }
public string? Version { get; set; }
public string? EntryDll { get; set; }
public int ApiVersion { get; set; }
public IReadOnlyList<string>? Dependencies { get; set; }
}
}
public sealed class PluginManifestException : Exception
{
public PluginManifestException(string message) : base(message) { }
public PluginManifestException(string message, Exception inner) : base(message, inner) { }
}

View file

@ -0,0 +1,62 @@
using AcDream.Core.Plugins;
namespace AcDream.Core.Tests.Plugins;
public class PluginManifestTests
{
[Fact]
public void Parse_ValidManifest_ReturnsManifest()
{
const string json = """
{
"id": "acdream.smoke",
"displayName": "Smoke Test",
"version": "0.1.0",
"entryDll": "AcDream.Plugins.Smoke.dll",
"apiVersion": 1
}
""";
var manifest = PluginManifest.Parse(json);
Assert.Equal("acdream.smoke", manifest.Id);
Assert.Equal("Smoke Test", manifest.DisplayName);
Assert.Equal("0.1.0", manifest.Version);
Assert.Equal("AcDream.Plugins.Smoke.dll", manifest.EntryDll);
Assert.Equal(1, manifest.ApiVersion);
}
[Fact]
public void Parse_MissingRequiredField_Throws()
{
const string json = """
{ "id": "x", "version": "0.1.0", "entryDll": "x.dll", "apiVersion": 1 }
""";
var ex = Assert.Throws<PluginManifestException>(() => PluginManifest.Parse(json));
Assert.Contains("displayName", ex.Message);
}
[Fact]
public void Parse_MalformedJson_Throws()
{
Assert.Throws<PluginManifestException>(() => PluginManifest.Parse("{ not json"));
}
[Fact]
public void Parse_EmptyDependencies_DefaultsToEmptyList()
{
const string json = """
{
"id": "x",
"displayName": "X",
"version": "0.1.0",
"entryDll": "x.dll",
"apiVersion": 1
}
""";
var manifest = PluginManifest.Parse(json);
Assert.Empty(manifest.Dependencies);
}
}