Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make the SDK AOT friendly #77

Merged
merged 1 commit into from
Mar 6, 2024
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
1 change: 1 addition & 0 deletions samples/Extism.Sdk.Sample/Extism.Sdk.Sample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<PublishAot>true</PublishAot>
</PropertyGroup>

<ItemGroup>
Expand Down
5 changes: 3 additions & 2 deletions src/Extism.Sdk/Extism.Sdk.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<TargetFrameworks>netstandard2.1;net7.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<LangVersion>11</LangVersion>
<MinVerTagPrefix>v</MinVerTagPrefix>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
</PropertyGroup>

<PropertyGroup>
Expand Down
30 changes: 26 additions & 4 deletions src/Extism.Sdk/Manifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@

namespace Extism.Sdk
{
[JsonSerializable(typeof(Manifest))]
[JsonSerializable(typeof(HttpMethod))]
[JsonSerializable(typeof(Dictionary<string, string>))]
[JsonSerializable(typeof(WasmSource))]
[JsonSerializable(typeof(ByteArrayWasmSource))]
[JsonSerializable(typeof(PathWasmSource))]
[JsonSerializable(typeof(UrlWasmSource))]
internal partial class ManifestJsonContext : JsonSerializerContext
{

}

/// <summary>
/// The manifest is a description of your plugin and some of the runtime constraints to apply to it.
/// You can think of it as a blueprint to build your plugin.
Expand Down Expand Up @@ -101,7 +113,7 @@ public class MemoryOptions
[JsonPropertyName("max_pages")]
public int MaxPages { get; set; }


/// <summary>
/// Max number of bytes allowed in an HTTP response when using extism_http_request.
/// </summary>
Expand Down Expand Up @@ -285,14 +297,24 @@ public override WasmSource Read(ref Utf8JsonReader reader, Type typeToConvert, J

public override void Write(Utf8JsonWriter writer, WasmSource value, JsonSerializerOptions options)
{
// Clone it because a JsonSerializerOptions can't be shared by multiple JsonSerializerContexts
var context = new ManifestJsonContext(new JsonSerializerOptions(options));
if (value is PathWasmSource path)
JsonSerializer.Serialize(writer, path, typeof(PathWasmSource), options);
{
JsonSerializer.Serialize(writer, path, context.PathWasmSource);
}
else if (value is ByteArrayWasmSource bytes)
JsonSerializer.Serialize(writer, bytes, typeof(ByteArrayWasmSource), options);
{
JsonSerializer.Serialize(writer, bytes, context.ByteArrayWasmSource);
}
else if (value is UrlWasmSource uri)
JsonSerializer.Serialize(writer, uri, typeof(UrlWasmSource), options);
{
JsonSerializer.Serialize(writer, uri, context.UrlWasmSource);
}
else
{
throw new ArgumentOutOfRangeException(nameof(value), "Unknown Wasm Source");
}
}
}

Expand Down
70 changes: 61 additions & 9 deletions src/Extism.Sdk/Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;

using Extism.Sdk.Native;

namespace Extism.Sdk;
Expand Down Expand Up @@ -45,8 +47,10 @@ public Plugin(Manifest manifest, HostFunction[] functions, bool withWasi)
};

options.Converters.Add(new WasmSourceConverter());
options.Converters.Add(new JsonStringEnumConverter());
var json = JsonSerializer.Serialize(manifest, options);
options.Converters.Add(new JsonStringEnumConverter<HttpMethod>());

var jsonContext = new ManifestJsonContext(options);
var json = JsonSerializer.Serialize(manifest, jsonContext.Manifest);

var bytes = Encoding.UTF8.GetBytes(json);

Expand Down Expand Up @@ -94,7 +98,7 @@ public Plugin(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi)
msg = Marshal.PtrToStringAnsi(new IntPtr(errorMsgPtr));
}

throw new ExtismException(msg);
throw new ExtismException(msg ?? "Unknown error");
}

return handle;
Expand All @@ -108,7 +112,9 @@ public Plugin(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi)
/// <returns></returns>
public bool UpdateConfig(Dictionary<string, string> value, JsonSerializerOptions serializerOptions)
{
var json = JsonSerializer.Serialize(value, serializerOptions);
var jsonContext = new ManifestJsonContext(serializerOptions);

var json = JsonSerializer.Serialize(value, jsonContext.DictionaryStringString);
var bytes = Encoding.UTF8.GetBytes(json);
return UpdateConfig(bytes);
}
Expand Down Expand Up @@ -191,6 +197,11 @@ public string Call(string functionName, string input, CancellationToken? cancell
/// <param name="serializerOptions">JSON serialization options used for serialization/derserialization</param>
/// <param name="cancellationToken">CancellationToken used for cancelling the Extism call.</param>
/// <returns></returns>

#if NET7_0_OR_GREATER
[RequiresUnreferencedCode("This function call can break in AOT compiled apps because it uses reflection for serialization. Use an overload that accepts an JsonTypeInfo instead.")]
[RequiresDynamicCode("This function call can break in AOT compiled apps because it uses reflection for serialization. Use an overload that accepts an JsonTypeInfo instead.")]
#endif
public TOutput? Call<TInput, TOutput>(string functionName, TInput input, JsonSerializerOptions? serializerOptions = null, CancellationToken? cancellationToken = null)
{
var inputJson = JsonSerializer.Serialize(input, serializerOptions ?? _serializerOptions);
Expand All @@ -199,20 +210,57 @@ public string Call(string functionName, string input, CancellationToken? cancell
}

/// <summary>
/// Calls a function on the plugin and deserializes the output as UTF8 encoded JSON.
/// Calls a function on the plugin with a payload. The payload is serialized into JSON and encoded in UTF8.
/// </summary>
/// <typeparam name="TInput">Type of the input payload.</typeparam>
/// <typeparam name="TOutput">Type of the output payload returned by the function.</typeparam>
/// <param name="functionName">Name of the function in the plugin to invoke.</param>
/// <param name="input">An object that will be serialized into JSON and passed into the function as a UTF8 encoded string.</param>
/// <param name="serializerOptions">JSON serialization options used for serialization/derserialization</param>
/// <param name="inputJsonInfo">Metadata about input type.</param>
/// <param name="outputJsonInfo">Metadata about output type.</param>
/// <param name="cancellationToken">CancellationToken used for cancelling the Extism call.</param>
/// <returns></returns>
public TOutput? Call<TInput, TOutput>(string functionName, TInput input, JsonTypeInfo<TInput> inputJsonInfo, JsonTypeInfo<TOutput?> outputJsonInfo, CancellationToken? cancellationToken = null)
{
var inputJson = JsonSerializer.Serialize(input, inputJsonInfo);
var outputJson = Call(functionName, inputJson, cancellationToken);
return JsonSerializer.Deserialize(outputJson, outputJsonInfo);
}

/// <summary>
/// Calls a function on the plugin and deserializes the output as UTF8 encoded JSON.
/// </summary>
/// <typeparam name="TOutput">Type of the output payload returned by the function.</typeparam>
/// <param name="functionName">Name of the function in the plugin to invoke.</param>
/// <param name="input">Function input.</param>
/// <param name="serializerOptions">JSON serialization options used for serialization/derserialization.</param>
/// <param name="cancellationToken">CancellationToken used for cancelling the Extism call.</param>
/// <returns></returns>
#if NET7_0_OR_GREATER
[RequiresUnreferencedCode("This function call can break in AOT compiled apps because it uses reflection for serialization. Use an overload that accepts an JsonTypeInfo instead.")]
[RequiresDynamicCode("This function call can break in AOT compiled apps because it uses reflection for serialization. Use an overload that accepts an JsonTypeInfo instead.")]
#endif
public TOutput? Call<TOutput>(string functionName, string input, JsonSerializerOptions? serializerOptions = null, CancellationToken? cancellationToken = null)
{
var outputJson = Call(functionName, input, cancellationToken);
return JsonSerializer.Deserialize<TOutput>(outputJson, serializerOptions ?? _serializerOptions);
}

/// <summary>
/// Calls a function on the plugin with a payload. The payload is serialized into JSON and encoded in UTF8.
/// </summary>
/// <typeparam name="TOutput">Type of the output payload returned by the function.</typeparam>
/// <param name="functionName">Name of the function in the plugin to invoke.</param>
/// <param name="input">Function input.</param>
/// <param name="outputJsonInfo">Metadata about output type.</param>
/// <param name="cancellationToken">CancellationToken used for cancelling the Extism call.</param>
/// <returns></returns>
public TOutput? Call<TOutput>(string functionName, string input, JsonTypeInfo<TOutput?> outputJsonInfo, CancellationToken? cancellationToken = null)
{
var outputJson = Call(functionName, input, cancellationToken);
return JsonSerializer.Deserialize(outputJson, outputJsonInfo);
}

/// <summary>
/// Get the length of a plugin's output data.
/// </summary>
Expand Down Expand Up @@ -315,7 +363,7 @@ unsafe protected virtual void Dispose(bool disposing)
public static string ExtismVersion()
{
var version = LibExtism.extism_version();
return Marshal.PtrToStringAnsi(version);
return Marshal.PtrToStringAnsi(version) ?? "unknown";
}

/// <summary>
Expand All @@ -325,7 +373,9 @@ public static string ExtismVersion()
/// <param name="level">Minimum log level</param>
public static void ConfigureFileLogging(string path, LogLevel level)
{
var logLevel = Enum.GetName(typeof(LogLevel), level).ToLowerInvariant();
var logLevel = Enum.GetName(typeof(LogLevel), level)?.ToLowerInvariant()
?? throw new ArgumentOutOfRangeException(nameof(level));

LibExtism.extism_log_file(path, logLevel);
}

Expand All @@ -335,7 +385,9 @@ public static void ConfigureFileLogging(string path, LogLevel level)
/// <param name="level"></param>
public static void ConfigureCustomLogging(LogLevel level)
{
var logLevel = Enum.GetName(typeof(LogLevel), level).ToLowerInvariant();
var logLevel = Enum.GetName(typeof(LogLevel), level)?.ToLowerInvariant()
?? throw new ArgumentOutOfRangeException(nameof(level));

LibExtism.extism_log_custom(logLevel);
}

Expand Down
Loading