using System.Text.Json;
using ZB.MOM.WW.ScadaBridge.Commons.Types.InboundApi;
namespace ZB.MOM.WW.ScadaBridge.InboundAPI;
///
/// WP-2: Validates and deserializes a JSON request body against a method's
/// parameter definitions. Extended type system: Boolean, Integer, Float,
/// String, Object, List.
///
///
/// InboundAPI-M2.6: validation is now RECURSIVE and type-aware for the
/// extended Object / List types. Declared object fields are
/// validated against their declared (nested) types, list elements against the
/// declared element type, and scalars at any depth against the extended type —
/// with path-qualified errors (e.g. order.items[2].quantity). The
/// definition is read as JSON Schema (the canonical persisted format produced
/// by the Central UI / migration); the legacy flat-array form is still
/// accepted for transition safety. See
///
/// for the shared recursive engine that
/// also uses.
///
///
public static class ParameterValidator
{
///
/// Validates the request body against the method's parameter definitions.
/// Returns deserialized parameters or an error message.
///
/// The parsed JSON request body; null or undefined if no body was supplied.
/// JSON Schema describing the method's parameters (an object schema), or null/empty when no parameters are defined. The legacy flat-array form is also accepted.
/// A with coerced parameter values on success, or an error message on failure.
public static ParameterValidationResult Validate(
JsonElement? body,
string? parameterDefinitions)
{
InboundApiSchema? schema;
try
{
schema = InboundApiSchema.Parse(parameterDefinitions);
}
catch (JsonException)
{
return ParameterValidationResult.Invalid("Invalid parameter definitions in method configuration");
}
// No parameters defined (or an object schema with no declared fields) —
// the body is unconstrained and yields an empty parameter set.
if (schema is null || schema.Type != "object" || schema.Fields.Count == 0)
{
return ParameterValidationResult.Valid(new Dictionary());
}
if (body == null
|| body.Value.ValueKind == JsonValueKind.Null
|| body.Value.ValueKind == JsonValueKind.Undefined)
{
var required = schema.Fields.Where(f => f.Required).ToList();
if (required.Count > 0)
{
return ParameterValidationResult.Invalid(
$"Missing required parameters: {string.Join(", ", required.Select(r => r.Name))}");
}
return ParameterValidationResult.Valid(new Dictionary());
}
if (body.Value.ValueKind != JsonValueKind.Object)
{
return ParameterValidationResult.Invalid("Request body must be a JSON object");
}
// Recursively type-check the whole body against the declared object
// schema (nested Object fields, List element types, scalars at any
// depth, undeclared-field rejection) with path-qualified errors.
var errors = new List();
schema.Validate(body.Value, string.Empty, errors);
if (errors.Count > 0)
{
return ParameterValidationResult.Invalid(string.Join("; ", errors));
}
// Materialize the coerced top-level parameter values for the script.
var result = new Dictionary();
foreach (var field in schema.Fields)
{
if (body.Value.TryGetProperty(field.Name, out var prop))
{
result[field.Name] = Materialize(prop, field.Schema);
}
}
return ParameterValidationResult.Valid(result);
}
///
/// Converts a validated JSON element to the CLR value handed to the script.
/// Validation has already passed, so this only shapes the value: scalars to
/// their primitive type, objects to ,
/// arrays to .
///
private static object? Materialize(JsonElement element, InboundApiSchema schema)
{
if (element.ValueKind == JsonValueKind.Null)
{
return null;
}
return schema.Type switch
{
"boolean" => element.GetBoolean(),
"integer" => element.GetInt64(),
"number" => element.GetDouble(),
"string" => element.GetString(),
"object" => JsonSerializer.Deserialize>(element.GetRawText()),
"array" => JsonSerializer.Deserialize>(element.GetRawText()),
_ => JsonSerializer.Deserialize