using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.ScriptAnalysis;
///
/// Runtime globals for the Test Run sandbox. Mirrors the real site-runtime
/// ScriptGlobals surface (ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts) member-for-member
/// so the same user code that runs at a site also compiles and runs here.
///
/// Instance-context members — Instance.GetAttribute/SetAttribute/CallScript ,
/// Attributes , Children , Parent — need a live deployed
/// instance. With no instance bound they throw ;
/// with one bound (see ) they route to it.
///
/// ExternalSystem , Database , and Scripts.CallShared run
/// against central's real services and fire for real; Notify is a
/// signature-faithful no-op fake. None of them depend on a bound instance.
///
public class SandboxScriptHost
{
///
/// Script parameters passed to the sandbox.
///
public ScriptParameters Parameters { get; init; } = new();
///
/// Cancellation token for the sandbox execution.
///
public CancellationToken CancellationToken { get; init; }
///
/// Alarm context for the sandbox.
///
public AlarmContext? Alarm { get; init; }
///
/// Script scope defining the execution context.
///
public ScriptScope Scope { get; init; } = ScriptScope.Root;
///
/// Instance context providing access to deployed instance data.
///
public SandboxInstanceContext Instance { get; init; } = new();
///
/// Helper for external system calls.
///
public SandboxExternalHelper ExternalSystem => Instance.ExternalSystem;
///
/// Helper for database operations.
///
public SandboxDatabaseHelper Database => Instance.Database;
///
/// Helper for sending notifications.
///
public SandboxNotifyHelper Notify => Instance.Notify;
///
/// Helper for calling scripts.
///
public SandboxScriptCallHelper Scripts => Instance.Scripts;
///
/// Accessor for attributes scoped to the current instance.
///
public SandboxAttributeAccessor Attributes => new(Instance, Scope.SelfPath);
///
/// Accessor for child compositions.
///
public SandboxChildrenAccessor Children => new(Instance, Scope.SelfPath);
///
/// Accessor for the parent composition, or null if at root.
///
public SandboxCompositionAccessor? Parent =>
Scope.ParentPath == null ? null : new SandboxCompositionAccessor(Instance, Scope.ParentPath);
}
///
/// Backs the sandbox Instance when a Test Run is bound to a real
/// deployed instance. Null when unbound. The implementation routes to the
/// instance cross-site over the cluster transport.
///
public interface ISandboxInstanceGateway
{
///
/// Gets the value of an attribute with the specified canonical name.
///
/// The canonical name of the attribute.
/// Cancellation token.
/// The attribute value, or null if not found.
Task GetAttributeAsync(string canonicalName, CancellationToken ct);
///
/// Sets the value of an attribute with the specified canonical name.
///
/// The canonical name of the attribute.
/// The value to set.
/// Cancellation token.
/// A task that represents the asynchronous operation.
Task SetAttributeAsync(string canonicalName, string value, CancellationToken ct);
///
/// Calls a script with the specified canonical name.
///
/// The canonical name of the script.
/// Script parameters, or null if none.
/// Cancellation token.
/// The script result, or null if none.
Task CallScriptAsync(
string canonicalScriptName, IReadOnlyDictionary? parameters, CancellationToken ct);
}
///
/// Sandbox mirror of ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts.ScriptRuntimeContext —
/// the Instance global. Attribute and sibling-script access needs a real
/// deployed instance: with no gateway wired it throws; with one (a bound
/// instance) it routes cross-site. ExternalSystem /Database /
/// Scripts run against central's real services regardless of binding;
/// Notify is a signature-faithful no-op fake.
///
public class SandboxInstanceContext
{
private readonly ISandboxInstanceGateway? _gateway;
///
/// Helper for external system calls.
///
public SandboxExternalHelper ExternalSystem { get; }
///
/// Helper for database operations.
///
public SandboxDatabaseHelper Database { get; }
///
/// Helper for sending notifications.
///
public SandboxNotifyHelper Notify { get; }
///
/// Helper for calling scripts.
///
public SandboxScriptCallHelper Scripts { get; }
///
/// Initializes a new instance of the SandboxInstanceContext.
///
/// Gateway for accessing deployed instance data, or null if unbound.
/// External system helper, or null to create a default.
/// Database helper, or null to create a default.
/// Notification helper, or null to create a default.
/// Script call helper, or null to create a default.
public SandboxInstanceContext(
ISandboxInstanceGateway? gateway = null,
SandboxExternalHelper? external = null,
SandboxDatabaseHelper? database = null,
SandboxNotifyHelper? notify = null,
SandboxScriptCallHelper? scripts = null)
{
_gateway = gateway;
ExternalSystem = external ?? new SandboxExternalHelper(null, "");
Database = database ?? new SandboxDatabaseHelper(null, "");
Notify = notify ?? new SandboxNotifyHelper();
Scripts = scripts ?? new SandboxScriptCallHelper(null);
}
///
/// Gets the value of an attribute.
///
/// The name of the attribute.
/// The attribute value, or null if not found.
public Task GetAttribute(string attributeName)
{
if (_gateway == null)
throw new ScriptSandboxException(
$"GetAttribute(\"{attributeName}\") needs a deployed instance — " +
"bind one in Test Run to read live attribute values.");
return _gateway.GetAttributeAsync(attributeName, CancellationToken.None);
}
///
/// Sets the value of an attribute.
///
/// The name of the attribute.
/// The value to set.
public void SetAttribute(string attributeName, string value)
{
if (_gateway == null)
throw new ScriptSandboxException(
$"SetAttribute(\"{attributeName}\") needs a deployed instance — " +
"bind one in Test Run to write attribute values.");
_gateway.SetAttributeAsync(attributeName, value, CancellationToken.None).GetAwaiter().GetResult();
}
///
/// Calls a sibling script.
///
/// The name of the script.
/// Script parameters, or null if none.
/// The script result, or null if none.
public Task CallScript(string scriptName, object? parameters = null)
{
if (_gateway == null)
throw new ScriptSandboxException(
$"CallScript(\"{scriptName}\") needs a deployed instance — " +
"bind one in Test Run to call sibling scripts.");
return _gateway.CallScriptAsync(scriptName, ScriptArgs.Normalize(parameters), CancellationToken.None);
}
}
///
/// Sandbox mirror of ScriptRuntimeContext.ScriptCallHelper —
/// Scripts.CallShared(...) . Compiles and runs the named shared script in
/// the same sandbox via the wired delegate.
///
public class SandboxScriptCallHelper
{
private readonly Func?, CancellationToken, Task>? _callShared;
///
/// Initializes a new instance of the SandboxScriptCallHelper.
///
/// Delegate for calling shared scripts, or null if not available.
public SandboxScriptCallHelper(
Func?, CancellationToken, Task>? callShared)
{
_callShared = callShared;
}
///
/// Calls a shared script.
///
/// The name of the shared script.
/// Script parameters, or null if none.
/// Cancellation token.
/// The script result, or null if none.
public Task CallShared(
string scriptName,
object? parameters = null,
CancellationToken cancellationToken = default)
{
if (_callShared == null)
throw new ScriptSandboxException(
$"Scripts.CallShared(\"{scriptName}\") — shared-script catalog not configured for Test Run.");
return _callShared(scriptName, ScriptArgs.Normalize(parameters), cancellationToken);
}
}
///
/// Sandbox mirror of ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts.AttributeAccessor —
/// scope-aware Attributes["X"] access anchored at a canonical-name prefix.
///
public class SandboxAttributeAccessor
{
private readonly SandboxInstanceContext _ctx;
///
/// The scope prefix for attribute resolution.
///
public string ScopePrefix { get; }
///
/// Initializes a new instance of the SandboxAttributeAccessor.
///
/// The sandbox instance context.
/// The scope prefix for attribute names.
public SandboxAttributeAccessor(SandboxInstanceContext ctx, string prefix)
{
_ctx = ctx;
ScopePrefix = prefix;
}
///
/// Resolves a key to its fully qualified name within the current scope.
///
/// The attribute key.
/// The fully qualified attribute name.
public string Resolve(string key) =>
ScopePrefix.Length == 0 ? key : ScopePrefix + "." + key;
///
/// Gets or sets an attribute value by key.
///
/// The attribute key.
/// The attribute value, or null if not found.
public object? this[string key]
{
get => _ctx.GetAttribute(Resolve(key)).GetAwaiter().GetResult();
set => _ctx.SetAttribute(Resolve(key), value?.ToString() ?? string.Empty);
}
///
/// Gets an attribute value asynchronously.
///
/// The attribute key.
/// The attribute value, or null if not found.
public Task GetAsync(string key) => _ctx.GetAttribute(Resolve(key));
///
/// Sets an attribute value asynchronously.
///
/// The attribute key.
/// The value to set, or null.
/// A task representing the operation.
public Task SetAsync(string key, object? value)
{
_ctx.SetAttribute(Resolve(key), value?.ToString() ?? string.Empty);
return Task.CompletedTask;
}
}
///
/// Sandbox mirror of ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts.CompositionAccessor —
/// a view of one composition: its attributes plus an invokable CallScript .
///
public class SandboxCompositionAccessor
{
private readonly SandboxInstanceContext _ctx;
///
/// The path to the composition within the instance hierarchy.
///
public string Path { get; }
///
/// Accessor for attributes within the composition.
///
public SandboxAttributeAccessor Attributes { get; }
///
/// Initializes a new instance of the SandboxCompositionAccessor.
///
/// The sandbox instance context.
/// The path to the composition within the instance hierarchy.
public SandboxCompositionAccessor(SandboxInstanceContext ctx, string path)
{
_ctx = ctx;
Path = path;
Attributes = new SandboxAttributeAccessor(ctx, path);
}
///
/// Resolves a script name to its fully qualified name within the composition.
///
/// The script name.
/// The fully qualified script name.
public string ResolveScript(string scriptName) =>
Path.Length == 0 ? scriptName : Path + "." + scriptName;
///
/// Calls a script within the composition.
///
/// The name of the script.
/// Script parameters, or null if none.
/// The script result, or null if none.
public Task CallScript(string scriptName, object? parameters = null)
=> _ctx.CallScript(ResolveScript(scriptName), parameters);
}
///
/// Sandbox mirror of ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts.ChildrenAccessor —
/// dictionary-style access to child compositions.
///
public class SandboxChildrenAccessor
{
private readonly SandboxInstanceContext _ctx;
private readonly string _selfPath;
///
/// Initializes a new instance of the SandboxChildrenAccessor.
///
/// The sandbox instance context.
/// The path to the parent composition.
public SandboxChildrenAccessor(SandboxInstanceContext ctx, string selfPath)
{
_ctx = ctx;
_selfPath = selfPath;
}
///
/// Gets a child composition by name.
///
/// The name of the child composition.
/// An accessor for the child composition.
public SandboxCompositionAccessor this[string compositionName]
{
get
{
var path = _selfPath.Length == 0
? compositionName
: _selfPath + "." + compositionName;
return new SandboxCompositionAccessor(_ctx, path);
}
}
}
///
/// Distinct exception so the Test Run pipeline can label sandbox-only
/// limitations differently from genuine runtime errors in user code.
///
public class ScriptSandboxException : Exception
{
///
/// Initializes a new instance of the ScriptSandboxException.
///
/// The exception message.
public ScriptSandboxException(string message) : base(message) { }
}