Skip to content

Commit

Permalink
OpaClient: Refactor for json serializer settings (#80)
Browse files Browse the repository at this point in the history
* OpaClient: Refactor ctors down to just one ctor with optional params.

Signed-off-by: Philip Conrad <[email protected]>

* OpaClient: Remove overloads + Refactor to plumb in global/local JsonSerializerSettings.

This commit removes redundant method overloads, greatly reducing
code clutter, for minimal, if any, performance impact.

This commit also plumbs in JsonSerializerSettings as an optional named
parameter in the `check`, `evaluate`, and `evaluateDefault` methods.

Signed-off-by: Philip Conrad <[email protected]>

* OpaClient: Fix null input handling.

Signed-off-by: Philip Conrad <[email protected]>

* test: Add basic JsonSerializer test for evaluate<T>.

Signed-off-by: Philip Conrad <[email protected]>

* Fix the build.

Signed-off-by: Philip Conrad <[email protected]>

---------

Signed-off-by: Philip Conrad <[email protected]>
  • Loading branch information
philipaconrad authored Sep 16, 2024
1 parent 0acf0e8 commit 5b66fe0
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 266 deletions.
294 changes: 44 additions & 250 deletions Styra/Opa/OpaClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,111 +37,19 @@ public class OpaClient

private readonly ILogger _logger;

/// <summary>
/// Constructs a default OpaClient, connecting to a specified server address.
/// </summary>
public OpaClient()
{
opa = new OpaApiClient(serverIndex: 0, serverUrl: sdkServerUrl);
_logger = new NullLogger<OpaClient>();
}

/// <summary>
/// Constructs an OpaClient using the provided server URL.
/// </summary>
/// <param name="serverUrl">The URL for connecting to the OPA server instance.</param>
public OpaClient(string serverUrl)
{
opa = new OpaApiClient(serverIndex: 0, serverUrl: serverUrl);
_logger = new NullLogger<OpaClient>();
}

/// <summary>
/// Constructs a default OpaClient, connecting to a specified server address.
/// </summary>
/// <param name="logger">The ILogger instance to use for this OpaClient.</param>
public OpaClient(ILogger<OpaClient> logger)
{
opa = new OpaApiClient(serverIndex: 0, serverUrl: sdkServerUrl);
_logger = logger;
}

/// <summary>
/// Constructs a default OpaClient, connecting to a specified server address.
/// </summary>
/// <param name="serverUrl">The URL for connecting to the OPA server instance.</param>
/// <param name="logger">The ILogger instance to use for this OpaClient.</param>
public OpaClient(string serverUrl, ILogger<OpaClient> logger)
{
opa = new OpaApiClient(serverIndex: 0, serverUrl: serverUrl);
_logger = logger;
}

/// <summary>
/// Simple allow/deny-style check against a rule, using the provided input mapping.
/// </summary>
/// <param name="path">The rule to evaluate. (Example: "app/rbac")</param>
/// <returns>Result, as a boolean</returns>
public async Task<bool> check(string path)
{
return await evaluate<bool>(path);
}
private readonly JsonSerializerSettings? _jsonSerializerSettings;

/// <summary>
/// Simple allow/deny-style check against a rule, using the provided input boolean value.
/// Constructs an OpaClient, connecting to a specified server address if provided.
/// </summary>
/// <param name="input">The input boolean value OPA will use for evaluating the rule.</param>
/// <param name="path">The rule to evaluate. (Example: "app/rbac")</param>
/// <returns>Result, as a boolean</returns>
public async Task<bool> check(string path, bool input)
{
return await evaluate<bool>(path, input);
}

/// <summary>
/// Simple allow/deny-style check against a rule, using the provided floating-point number.
/// </summary>
/// <param name="input">The input floating-point number OPA will use for evaluating the rule.</param>
/// <param name="path">The rule to evaluate. (Example: "app/rbac")</param>
/// <returns>Result, as a boolean</returns>
public async Task<bool> check(string path, double input)
{
return await evaluate<bool>(path, input);
}

/// <summary>
/// Simple allow/deny-style check against a rule, using the provided input string.
/// </summary>
/// <param name="input">The input string OPA will use for evaluating the rule.</param>
/// <param name="path">The rule to evaluate. (Example: "app/rbac")</param>
/// <returns>Result, as a boolean</returns>
public async Task<bool> check(string path, string input)
/// <param name="serverUrl">The URL for connecting to the OPA server instance. (default: "http://localhost:8181")</param>
/// <param name="logger">The ILogger instance to use for this OpaClient. (default: NullLogger)</param>
/// <param name="jsonSerializerSettings">The Newtonsoft.Json.JsonSerializerSettings to use as the default for serializing inputs for OPA. (default: none)</param>
public OpaClient(string? serverUrl = null, ILogger<OpaClient>? logger = null, JsonSerializerSettings? jsonSerializerSettings = null)
{
return await evaluate<bool>(path, input);
}

/// <summary>
/// Simple allow/deny-style check against a rule, using the provided input list.
/// </summary>
/// <param name="input">The input List value OPA will use for evaluating the rule.</param>
/// <param name="path">The rule to evaluate. (Example: "app/rbac")</param>
/// <returns>Result, as a boolean</returns>
/// <remarks>The closest idiomatic type mapping to a JSON Array type for .NET is a List, so we use that here.</remarks>
public async Task<bool> check(string path, List<object> input)
{
return await evaluate<bool>(path, input);
}

/// <summary>
/// Simple allow/deny-style check against a rule, using the provided input mapping.
/// </summary>
/// <param name="input">The input Dictionary value OPA will use for evaluating the rule.</param>
/// <param name="path">The rule to evaluate. (Example: "app/rbac")</param>
/// <returns>Result, as a boolean</returns>
/// <remarks>The closest idiomatic type mapping to a JSON Object type for .NET is a Dictionary, so we use that here.</remarks>
public async Task<bool> check(string path, Dictionary<string, object> input)
{
return await evaluate<bool>(path, input);
opa = new OpaApiClient(serverIndex: 0, serverUrl: serverUrl ?? sdkServerUrl);
_logger = logger ?? new NullLogger<OpaClient>();
_jsonSerializerSettings = jsonSerializerSettings;
}

/// <summary>
Expand All @@ -151,97 +59,60 @@ public async Task<bool> check(string path, Dictionary<string, object> input)
/// </summary>
/// <param name="input">The input C# object OPA will use for evaluating the rule.</param>
/// <param name="path">The rule to evaluate. (Example: "app/rbac")</param>
/// <param name="jsonSerializerSettings">The Newtonsoft.Json.JsonSerializerSettings object to use for round-tripping the input through JSON serdes. (default: global serializer settings, if any)</param>
/// <returns>Result, as a boolean</returns>
public async Task<bool> check(string path, object input)
public async Task<bool> check(string path, object? input, JsonSerializerSettings? jsonSerializerSettings = null)
{
if (input is null)
{
return await evaluate<bool>(path, input);
}
// Round-trip through JSON conversion, such that it becomes an Input.
var jsonInput = JsonConvert.SerializeObject(input);
var roundTrippedInput = JsonConvert.DeserializeObject<Input>(jsonInput) ?? throw new OpaException(string.Format("could not convert object type to a valid OPA input"));

var jsonInput = JsonConvert.SerializeObject(input, jsonSerializerSettings ?? _jsonSerializerSettings);
var roundTrippedInput = JsonConvert.DeserializeObject<Input>(jsonInput, jsonSerializerSettings ?? _jsonSerializerSettings) ?? throw new OpaException(string.Format("could not convert object type to a valid OPA input"));
return await evaluate<bool>(path, roundTrippedInput);
}

/// <summary>
/// Evaluate a policy, then coerce the result to type T.
/// </summary>
/// <param name="path">The rule to evaluate. (Example: "app/rbac")</param>
/// <returns>Result, as an instance of T</returns>
public async Task<T> evaluate<T>(string path)
{
return await queryMachinery<T>(path, Input.CreateNull());
}

/// <summary>
/// Evaluate a policy, using the provided input boolean value, then coerce the result to type T.
/// </summary>
/// <param name="input">The input boolean value OPA will use for evaluating the rule.</param>
/// <param name="path">The rule to evaluate. (Example: "app/rbac")</param>
/// <returns>Result, as an instance of T</returns>
public async Task<T> evaluate<T>(string path, bool input)
{
return await queryMachinery<T>(path, Input.CreateBoolean(input));
}

/// <summary>
/// Evaluate a policy, using the provided input floating-point number, then coerce the result to type T.
/// </summary>
/// <param name="input">The input floating-point number OPA will use for evaluating the rule.</param>
/// <param name="path">The rule to evaluate. (Example: "app/rbac")</param>
/// <returns>Result, as an instance of T</returns>
public async Task<T> evaluate<T>(string path, double input)
{
return await queryMachinery<T>(path, Input.CreateNumber(input));
}

/// <summary>
/// Evaluate a policy, using the provided input string, then coerce the result to type T.
/// </summary>
/// <param name="input">The input string OPA will use for evaluating the rule.</param>
/// <param name="path">The rule to evaluate. (Example: "app/rbac")</param>
/// <returns>Result, as an instance of T</returns>
public async Task<T> evaluate<T>(string path, string input)
{
return await queryMachinery<T>(path, Input.CreateStr(input));
}

/// <summary>
/// Evaluate a policy, using the provided input boolean value, then coerce the result to type T.
/// </summary>
/// <param name="input">The input List value OPA will use for evaluating the rule.</param>
/// <param name="path">The rule to evaluate. (Example: "app/rbac")</param>
/// <returns>Result, as an instance of T</returns>
/// <remarks>The closest idiomatic type mapping to a JSON Array type for .NET is a List, so we use that here.</remarks>
public async Task<T> evaluate<T>(string path, List<object> input)
{
return await queryMachinery<T>(path, Input.CreateArrayOfAny(input));
}

/// <summary>
/// Evaluate a policy, using the provided input map, then coerce the result to type T.
/// Evaluate a policy, using the provided object, then coerce the result to
/// type T. This will round-trip an object through Newtonsoft.JsonConvert,
/// in order to generate the input object for the eventual OPA API call.
/// </summary>
/// <param name="input">The input Dictionary OPA will use for evaluating the rule.</param>
/// <param name="input">The input C# object OPA will use for evaluating the rule.</param>
/// <param name="path">The rule to evaluate. (Example: "app/rbac")</param>
/// <param name="jsonSerializerSettings">The Newtonsoft.Json.JsonSerializerSettings object to use for round-tripping the input through JSON serdes. (default: global serializer settings, if any)</param>
/// <returns>Result, as an instance of T</returns>
public async Task<T> evaluate<T>(string path, Dictionary<string, object> input)
public async Task<T> evaluate<T>(string path, object? input, JsonSerializerSettings? jsonSerializerSettings = null)
{
return await queryMachinery<T>(path, Input.CreateMapOfAny(input));
if (input is null)
{
return await queryMachinery<T>(path, Input.CreateNull());
}
// Round-trip through JSON conversion, such that it becomes an Input.
var jsonInput = JsonConvert.SerializeObject(input, jsonSerializerSettings ?? _jsonSerializerSettings);
var roundTrippedInput = JsonConvert.DeserializeObject<Input>(jsonInput, jsonSerializerSettings ?? _jsonSerializerSettings) ?? throw new OpaException(string.Format("could not convert object type to a valid OPA input"));
return await queryMachinery<T>(path, roundTrippedInput);
}

/// <summary>
/// Evaluate a policy, using the provided object, then coerce the result to
/// type T. This will round-trip an object through Newtonsoft.JsonConvert,
/// in order to generate the input object for the eventual OPA API call.
/// Evaluate the server's default policy, using the provided object, then
/// coerce the result to type T. This will round-trip an object through
/// Newtonsoft.JsonConvert, in order to generate the input object for the
/// eventual OPA API call.
/// </summary>
/// <param name="input">The input C# object OPA will use for evaluating the rule.</param>
/// <param name="path">The rule to evaluate. (Example: "app/rbac")</param>
/// <param name="jsonSerializerSettings">The Newtonsoft.Json.JsonSerializerSettings object to use for round-tripping the input through JSON serdes. (default: global serializer settings, if any)</param>
/// <returns>Result, as an instance of T</returns>
public async Task<T> evaluate<T>(string path, object input)
public async Task<T> evaluateDefault<T>(object? input, JsonSerializerSettings? jsonSerializerSettings = null)
{
if (input is null)
{
return await queryMachineryDefault<T>(Input.CreateNull());
}
// Round-trip through JSON conversion, such that it becomes an Input.
var jsonInput = JsonConvert.SerializeObject(input);
var roundTrippedInput = JsonConvert.DeserializeObject<Input>(jsonInput) ?? throw new OpaException(string.Format("could not convert object type to a valid OPA input"));

return await queryMachinery<T>(path, roundTrippedInput);
var jsonInput = JsonConvert.SerializeObject(input, jsonSerializerSettings ?? _jsonSerializerSettings);
var roundTrippedInput = JsonConvert.DeserializeObject<Input>(jsonInput, jsonSerializerSettings ?? _jsonSerializerSettings) ?? throw new OpaException(string.Format("could not convert object type to a valid OPA input"));
return await queryMachineryDefault<T>(roundTrippedInput);
}

/// <exclude />
Expand Down Expand Up @@ -269,83 +140,6 @@ private async Task<T> queryMachinery<T>(string path, Input input)
return convertResult<T>(result);
}

/// <summary>
/// Evaluate the server's default policy, then coerce the result to type T.
/// </summary>
/// <returns>Result, as an instance of T</returns>
public async Task<T> evaluateDefault<T>()
{
return await queryMachineryDefault<T>(Input.CreateNull());
}

/// <summary>
/// Evaluate the server's default policy, using the provided input boolean value, then coerce the result to type T.
/// </summary>
/// <param name="input">The input boolean value OPA will use for evaluating the rule.</param>
/// <returns>Result, as an instance of T</returns>
public async Task<T> evaluateDefault<T>(bool input)
{
return await queryMachineryDefault<T>(Input.CreateBoolean(input));
}

/// <summary>
/// Evaluate the server's default policy, using the provided input floating-point number, then coerce the result to type T.
/// </summary>
/// <param name="input">The input floating-point number OPA will use for evaluating the rule.</param>
/// <returns>Result, as an instance of T</returns>
public async Task<T> evaluateDefault<T>(double input)
{
return await queryMachineryDefault<T>(Input.CreateNumber(input));
}

/// <summary>
/// Evaluate the server's default policy, using the provided input string, then coerce the result to type T.
/// </summary>
/// <param name="input">The input string OPA will use for evaluating the rule.</param>
/// <returns>Result, as an instance of T</returns>
public async Task<T> evaluateDefault<T>(string input)
{
return await queryMachineryDefault<T>(Input.CreateStr(input));
}

/// <summary>
/// Evaluate the server's default policy, using the provided input boolean value, then coerce the result to type T.
/// </summary>
/// <param name="input">The input List value OPA will use for evaluating the rule.</param>
/// <returns>Result, as an instance of T</returns>
/// <remarks>The closest idiomatic type mapping to a JSON Array type for .NET is a List, so we use that here.</remarks>
public async Task<T> evaluateDefault<T>(List<object> input)
{
return await queryMachineryDefault<T>(Input.CreateArrayOfAny(input));
}

/// <summary>
/// Evaluate the server's default policy, using the provided input map, then coerce the result to type T.
/// </summary>
/// <param name="input">The input Dictionary OPA will use for evaluating the rule.</param>
/// <returns>Result, as an instance of T</returns>
public async Task<T> evaluateDefault<T>(Dictionary<string, object> input)
{
return await queryMachineryDefault<T>(Input.CreateMapOfAny(input));
}

/// <summary>
/// Evaluate the server's default policy, using the provided object, then
/// coerce the result to type T. This will round-trip an object through
/// Newtonsoft.JsonConvert, in order to generate the input object for the
/// eventual OPA API call.
/// </summary>
/// <param name="input">The input C# object OPA will use for evaluating the rule.</param>
/// <returns>Result, as an instance of T</returns>
public async Task<T> evaluateDefault<T>(object input)
{
// Round-trip through JSON conversion, such that it becomes an Input.
var jsonInput = JsonConvert.SerializeObject(input);
var roundTrippedInput = JsonConvert.DeserializeObject<Input>(jsonInput) ?? throw new OpaException(string.Format("could not convert object type to a valid OPA input"));

return await queryMachineryDefault<T>(roundTrippedInput);
}

/// <exclude />
private async Task<T> queryMachineryDefault<T>(Input input)
{
Expand Down
Loading

0 comments on commit 5b66fe0

Please sign in to comment.