Skip to content

Commit

Permalink
feat: optimize JSON serializer (#111)
Browse files Browse the repository at this point in the history
- optimize the JSON serializer with generated code
- remove part of warning for trim unused code
- optimize binary load time

Resolve part of #109 

Signed-off-by: Junjie Gao <[email protected]>
  • Loading branch information
JeyJeyGao authored May 9, 2023
1 parent f2a6eac commit 307d0bb
Show file tree
Hide file tree
Showing 15 changed files with 167 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ public void DescribeKeyResponse_ValidParameters()

// Act
DescribeKeyResponse response = new DescribeKeyResponse(keyId, keySpec);
var json = response.ToJson();

// Assert
Assert.Equal(keyId, response.KeyId);
Assert.Equal(keySpec, response.KeySpec);
Assert.Equal("{\"keyId\":\"test-key-id\",\"keySpec\":\"RSA-2048\"}", json);
}

[Theory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,26 @@ public void GenerateSignatureResponse_ThrowsArgumentNullException_WhenFieldsAreE
Assert.Throws<ArgumentNullException>(() => new GenerateSignatureResponse(keyId, signature, string.Empty, certificateChain));
Assert.Throws<ArgumentNullException>(() => new GenerateSignatureResponse(keyId, signature, signingAlgorithm, new List<byte[]>()));
}

[Fact]
public void GenerateSignatureResponse_Valid()
{
// Arrange
string keyId = "test-key-id";
byte[] signature = new byte[] { 1, 2, 3, 4, 5 };
string signingAlgorithm = "RSA-PSS";
List<byte[]> certificateChain = new List<byte[]> { new byte[] { 6, 7, 8, 9, 10 } };

// Act
GenerateSignatureResponse response = new GenerateSignatureResponse(keyId, signature, signingAlgorithm, certificateChain);
var json = response.ToJson();

// Assert
Assert.Equal(keyId, response.KeyId);
Assert.Equal(signature, response.Signature);
Assert.Equal(signingAlgorithm, response.SigningAlgorithm);
Assert.Equal(certificateChain, response.CertificateChain);
Assert.Equal("{\"keyId\":\"test-key-id\",\"signature\":\"AQIDBAU=\",\"signingAlgorithm\":\"RSA-PSS\",\"certificateChain\":[\"BgcICQo=\"]}", json);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public void GetMetadataResponse_CreatesInstance_WithCorrectValues()

// Act
GetMetadataResponse response = new GetMetadataResponse(name, description, version, url, supportedContractVersions, capabilities);
var json = response.ToJson();
var expectedJson = "{\"name\":\"Test Plugin\",\"description\":\"A test plugin for Notation\",\"version\":\"1.0.0\",\"url\":\"https://github.com/example/test-plugin\",\"supportedContractVersions\":[\"1.0\"],\"capabilities\":[\"describe-key\",\"generate-signature\"]}";

// Assert
Assert.Equal(name, response.Name);
Expand All @@ -25,6 +27,7 @@ public void GetMetadataResponse_CreatesInstance_WithCorrectValues()
Assert.Equal(url, response.Url);
Assert.Equal(supportedContractVersions, response.SupportedContractVersions);
Assert.Equal(capabilities, response.Capabilities);
Assert.Equal(expectedJson, json);
}
}
}
27 changes: 0 additions & 27 deletions Notation.Plugin.AzureKeyVault.Tests/Protocol/PluginIOTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,32 +36,5 @@ public void ReadInput_ThrowsValidationExceptionOnEmptyInput()
// Act & Assert
Assert.Throws<ValidationException>(() => PluginIO.ReadInput());
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void WriteOutput_WritesCorrectOutput(bool stderr)
{
// Arrange
var obj = new { test = "value" };
const string expectedOutput = "{\"test\":\"value\"}\n";

using var stringWriter = new StringWriter();
if (stderr)
{
Console.SetError(stringWriter);
}
else
{
Console.SetOut(stringWriter);
}

// Act
PluginIO.WriteOutput(obj, stderr);

// Assert
string actualOutput = stringWriter.ToString();
Assert.Equal(expectedOutput, actualOutput);
}
}
}
4 changes: 2 additions & 2 deletions Notation.Plugin.AzureKeyVault/Command/DescribeKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class DescribeKey : IPluginCommand
public DescribeKey(string inputJson)
{
// Deserialize JSON string to DescribeKeyRequest object
var request = JsonSerializer.Deserialize<DescribeKeyRequest>(inputJson);
var request = JsonSerializer.Deserialize(inputJson, DescribeKeyRequestContext.Default.DescribeKeyRequest);
if (request == null)
{
throw new ValidationException(invalidInputError);
Expand All @@ -37,7 +37,7 @@ public DescribeKey(DescribeKeyRequest request, IKeyVaultClient keyVaultClient)
this._keyVaultClient = keyVaultClient;
}

public async Task<object> RunAsync()
public async Task<IPluginResponse> RunAsync()
{
// Get certificate from Azure Key Vault
var cert = await _keyVaultClient.GetCertificateAsync();
Expand Down
4 changes: 2 additions & 2 deletions Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class GenerateSignature : IPluginCommand
public GenerateSignature(string inputJson)
{
// Parse the input
var request = JsonSerializer.Deserialize<GenerateSignatureRequest>(inputJson);
var request = JsonSerializer.Deserialize(inputJson, GenerateSignatureRequestContext.Default.GenerateSignatureRequest);
if (request == null)
{
throw new ValidationException("Invalid input");
Expand All @@ -38,7 +38,7 @@ public GenerateSignature(GenerateSignatureRequest request, IKeyVaultClient keyVa
this._keyVaultClient = keyVaultClient;
}

public async Task<object> RunAsync()
public async Task<IPluginResponse> RunAsync()
{
// Obtain the certificate chain
X509Certificate2Collection certBundle;
Expand Down
4 changes: 2 additions & 2 deletions Notation.Plugin.AzureKeyVault/Command/GetPluginMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ public partial class GetPluginMetadata : IPluginCommand
public static readonly string Version;
public static readonly string CommitHash;

public async Task<object> RunAsync()
public async Task<IPluginResponse> RunAsync()
{
return await Task.FromResult<object>(new GetMetadataResponse(
return await Task.FromResult<IPluginResponse>(new GetMetadataResponse(
name: "azure-kv",
description: "Notation Azure Key Vault plugin",
version: Version,
Expand Down
4 changes: 3 additions & 1 deletion Notation.Plugin.AzureKeyVault/Command/IPluginCommand.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using Notation.Plugin.Protocol;

namespace Notation.Plugin.AzureKeyVault.Command
{
/// <summary>
/// Interface for plugin commands.
/// </summary>
public interface IPluginCommand
{
Task<object> RunAsync();
Task<IPluginResponse> RunAsync();
}
}
6 changes: 3 additions & 3 deletions Notation.Plugin.AzureKeyVault/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ static async Task ExecuteAsync(string[] args)
}

// execute the command
var resp = await cmd.RunAsync();
var response = await cmd.RunAsync();

// print the output
PluginIO.WriteOutput(resp);
// write output
Console.WriteLine(response.ToJson());
}

static void PrintHelp()
Expand Down
27 changes: 25 additions & 2 deletions Notation.Plugin.AzureKeyVault/Protocol/DescribeKey.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Notation.Plugin.Protocol
Expand Down Expand Up @@ -39,11 +40,17 @@ public DescribeKeyRequest(string contractVersion, string keyId)
}
}

/// <summary>
/// Context class for describe-key command.
/// </summary>
[JsonSerializable(typeof(DescribeKeyRequest))]
internal partial class DescribeKeyRequestContext : JsonSerializerContext { }

/// <summary>
/// Response class for describe-key command.
/// The class implement the <a href="https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#describe-key">describe-key</a> response.
/// </summary>
public class DescribeKeyResponse
public class DescribeKeyResponse : IPluginResponse
{
[JsonPropertyName("keyId")]
public string KeyId { get; }
Expand All @@ -66,5 +73,21 @@ public DescribeKeyResponse(string keyId, string keySpec)
KeyId = keyId;
KeySpec = keySpec;
}

/// <summary>
/// Serializes the response object to JSON string.
/// </summary>
public string ToJson()
{
return JsonSerializer.Serialize(
value: this,
jsonTypeInfo: new DescribeKeyResponseContext(PluginIO.GetRelaxedJsonSerializerOptions()).DescribeKeyResponse);
}
}
}

/// <summary>
/// Context class for describe-key command.
/// </summary>
[JsonSerializable(typeof(DescribeKeyResponse))]
internal partial class DescribeKeyResponseContext : JsonSerializerContext { }
}
39 changes: 33 additions & 6 deletions Notation.Plugin.AzureKeyVault/Protocol/Error.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Notation.Plugin.Protocol
{
/// <summary>
Expand Down Expand Up @@ -30,15 +34,38 @@ public static void PrintError(string errorCode, string errorMessage)
// "Path: $ | LineNumber: 0 | BytePositionInLine: 0." suffix for
// exception's Message, so remove it.
errorMessage = errorMessage.Split("Path: $ |")[0];
var errorResponse = new
{
errorCode = errorCode,
errorMessage = errorMessage
};
PluginIO.WriteOutput(errorResponse, stderr: true);
var errorResponse = new ErrorResponse(errorCode, errorMessage);
Console.Error.WriteLine(errorResponse.ToJson());
}
}

public class ErrorResponse : IPluginResponse
{
[JsonPropertyName("errorCode")]
public string ErrorCode { get; set; }
[JsonPropertyName("errorMessage")]
public string ErrorMessage { get; set; }

public ErrorResponse(string errorCode, string errorMessage)
{
ErrorCode = errorCode;
ErrorMessage = errorMessage;
}

/// <summary>
/// Serializes the response object to JSON string.
/// </summary>
public string ToJson()
{
return JsonSerializer.Serialize(
value: this,
jsonTypeInfo: new ErrorResponseContext(PluginIO.GetRelaxedJsonSerializerOptions()).ErrorResponse);
}
}

[JsonSerializable(typeof(ErrorResponse))]
public partial class ErrorResponseContext : JsonSerializerContext { }

/// <summary>
/// Base class for plugin exceptions.
/// </summary>
Expand Down
31 changes: 29 additions & 2 deletions Notation.Plugin.AzureKeyVault/Protocol/GenerateSignature.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Notation.Plugin.Protocol
Expand Down Expand Up @@ -67,11 +68,17 @@ public GenerateSignatureRequest(string contractVersion, string keyId, Dictionary
}
}

/// <summary>
/// The context class for serializing/deserializing.
/// </summary>
[JsonSerializable(typeof(GenerateSignatureRequest))]
internal partial class GenerateSignatureRequestContext : JsonSerializerContext { }

/// <summary>
/// Response class for generate-signature command.
/// This class implements the <a href="https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#generate-signature">generate-signature</a> response.
/// </summary>
public class GenerateSignatureResponse
public class GenerateSignatureResponse : IPluginResponse
{
[JsonPropertyName("keyId")]
public string KeyId { get; }
Expand All @@ -85,7 +92,11 @@ public class GenerateSignatureResponse
[JsonPropertyName("certificateChain")]
public List<byte[]> CertificateChain { get; }

public GenerateSignatureResponse(string keyId, byte[] signature, string signingAlgorithm, List<byte[]> certificateChain)
public GenerateSignatureResponse(
string keyId,
byte[] signature,
string signingAlgorithm,
List<byte[]> certificateChain)
{
if (string.IsNullOrEmpty(keyId))
{
Expand All @@ -112,5 +123,21 @@ public GenerateSignatureResponse(string keyId, byte[] signature, string signingA
SigningAlgorithm = signingAlgorithm;
CertificateChain = certificateChain;
}

/// <summary>
/// Serializes the response object to JSON string.
/// </summary>
public string ToJson()
{
return JsonSerializer.Serialize(
value: this,
jsonTypeInfo: new GenerateSignatureResponseContext(PluginIO.GetRelaxedJsonSerializerOptions()).GenerateSignatureResponse);
}
}

/// <summary>
/// The context class for serializing/deserializing.
/// </summary>
[JsonSerializable(typeof(GenerateSignatureResponse))]
internal partial class GenerateSignatureResponseContext : JsonSerializerContext { }
}
19 changes: 18 additions & 1 deletion Notation.Plugin.AzureKeyVault/Protocol/GetMetadataResponse.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Notation.Plugin.Protocol
Expand All @@ -6,7 +7,7 @@ namespace Notation.Plugin.Protocol
/// Response class for get-plugin-metadata command which returns the information about the plugin.
/// This class implements the <a href="https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#plugin-metadata">get-plugin-metadata</a> response.
/// </summary>
public class GetMetadataResponse
public class GetMetadataResponse : IPluginResponse
{
[JsonPropertyName("name")]
public string Name { get; set; }
Expand Down Expand Up @@ -41,5 +42,21 @@ public GetMetadataResponse(
SupportedContractVersions = supportedContractVersions;
Capabilities = capabilities;
}

/// <summary>
/// Serializes the response object to JSON string.
/// </summary>
public string ToJson()
{
return JsonSerializer.Serialize(
value: this,
jsonTypeInfo: new GetMetadataResponseContext(PluginIO.GetRelaxedJsonSerializerOptions()).GetMetadataResponse);
}
}

/// <summary>
/// The context class for serializing/deserializing.
/// </summary>
[JsonSerializable(typeof(GetMetadataResponse))]
internal partial class GetMetadataResponseContext : JsonSerializerContext { }
}
Loading

0 comments on commit 307d0bb

Please sign in to comment.