feat(core): add PluginManifest json parsing
This commit is contained in:
parent
caf57cca3e
commit
c082ecf36a
3 changed files with 135 additions and 3 deletions
|
|
@ -1,3 +0,0 @@
|
||||||
// src/AcDream.Core/Placeholder.cs
|
|
||||||
namespace AcDream.Core;
|
|
||||||
internal static class Placeholder { }
|
|
||||||
73
src/AcDream.Core/Plugins/PluginManifest.cs
Normal file
73
src/AcDream.Core/Plugins/PluginManifest.cs
Normal 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) { }
|
||||||
|
}
|
||||||
62
tests/AcDream.Core.Tests/Plugins/PluginManifestTests.cs
Normal file
62
tests/AcDream.Core.Tests/Plugins/PluginManifestTests.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue