using Akka.Actor;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.InboundApi;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
namespace ZB.MOM.WW.ScadaBridge.InboundAPI;
///
/// C3/N3: a routed call failed because the target site never answered — the routed
/// Ask expired. Unreachability is typed at the Ask boundary from
/// (the communication layer's own signal: an
/// unreachable/contactless site has its enveloped message dropped so the Ask times
/// out caller-side) — it is never inferred from an error-message substring.
/// Derives from so existing callers that
/// catch the base type are unaffected, while
/// catches this more specific type to surface the spec's SITE_UNREACHABLE
/// error code instead of a generic SCRIPT_ERROR. A Success=false
/// response means the site answered (reachable by definition) and stays a
/// plain .
///
public sealed class SiteUnreachableException : InvalidOperationException
{
/// Initializes a new with the given message.
/// The routing-level failure message that indicated the site was unreachable.
public SiteUnreachableException(string message) : base(message) { }
/// Initializes a new wrapping the underlying Ask-timeout.
/// The routing-level failure message.
/// The underlying from the routed Ask.
public SiteUnreachableException(string message, Exception innerException) : base(message, innerException) { }
}
///
/// Route.To() helper for cross-site calls from inbound API scripts.
/// Resolves instance to site, routes via , blocks until
/// response or timeout. Site unreachable returns error (no store-and-forward).
///
/// The helper carries the executing method's
/// (the method-level timeout). Routed calls inherit that deadline by default, so a
/// natural script — Route.To("inst").Call("doWork", p) — is timeout-bounded
/// without the script having to thread a token explicitly.
///
public class RouteHelper
{
private readonly IInstanceLocator _instanceLocator;
private readonly IInstanceRouter _instanceRouter;
private readonly CancellationToken _deadlineToken;
private readonly CancellationToken _requestAbortedToken;
private readonly Guid? _parentExecutionId;
///
/// Initializes a new with no deadline token and no parent execution id.
///
/// Service to resolve the site id for a given instance code.
/// Service to route cross-site calls to the resolved site.
public RouteHelper(
IInstanceLocator instanceLocator,
IInstanceRouter instanceRouter)
: this(instanceLocator, instanceRouter, CancellationToken.None, CancellationToken.None, parentExecutionId: null)
{
}
private RouteHelper(
IInstanceLocator instanceLocator,
IInstanceRouter instanceRouter,
CancellationToken deadlineToken,
CancellationToken requestAbortedToken,
Guid? parentExecutionId)
{
_instanceLocator = instanceLocator;
_instanceRouter = instanceRouter;
_deadlineToken = deadlineToken;
_requestAbortedToken = requestAbortedToken;
_parentExecutionId = parentExecutionId;
}
///
/// Returns a whose routed calls inherit
/// (the executing method's timeout) by default.
/// calls this when it builds the script
/// context so the method timeout actually covers routed calls, as the design doc
/// requires.
///
/// The executing method's timeout cancellation token to inherit for routed calls.
/// A new inheriting the given deadline token.
public RouteHelper WithDeadline(CancellationToken deadlineToken) =>
new(_instanceLocator, _instanceRouter, deadlineToken, _requestAbortedToken, _parentExecutionId);
///
/// Returns a carrying the raw request-abort
/// token (a client disconnect) separately from the method deadline. Most
/// routed calls remain bounded by the method deadline (which already incorporates the
/// abort), but uses this token so its wait
/// is bounded by the WAIT timeout — not the generic method deadline (spec §6) — while
/// still being cancelled by a client disconnect.
///
/// The raw client-disconnect token, independent of the method timeout.
/// A new carrying the given request-abort token.
public RouteHelper WithRequestAborted(CancellationToken requestAbortedToken) =>
new(_instanceLocator, _instanceRouter, _deadlineToken, requestAbortedToken, _parentExecutionId);
///
/// Audit Log (ParentExecutionId): returns a whose
/// routed requests carry
/// as .
/// For an inbound API request this is the inbound request's own per-request
/// execution id, so the routed site script records the inbound request as its
/// parent. calls this when it builds the
/// script context.
///
/// The inbound request's execution id to stamp as the spawning parent on routed calls, or null for non-routed runs.
/// A new carrying the given parent execution id.
public RouteHelper WithParentExecutionId(Guid? parentExecutionId) =>
new(_instanceLocator, _instanceRouter, _deadlineToken, _requestAbortedToken, parentExecutionId);
///
/// Creates a route target for the specified instance.
///
/// The unique code of the instance to route calls to.
/// A bound to the specified instance.
public RouteTarget To(string instanceCode)
{
return new RouteTarget(
instanceCode, _instanceLocator, _instanceRouter, _deadlineToken, _requestAbortedToken, _parentExecutionId);
}
}
///
/// Represents a route target (an instance) for cross-site calls.
///
public class RouteTarget
{
private readonly string _instanceCode;
private readonly IInstanceLocator _instanceLocator;
private readonly IInstanceRouter _instanceRouter;
private readonly CancellationToken _deadlineToken;
private readonly CancellationToken _requestAbortedToken;
private readonly Guid? _parentExecutionId;
///
/// Initializes a new for the specified instance.
///
/// The unique code of the target instance.
/// Service to resolve the site id for the instance.
/// Service to route cross-site calls.
/// Cancellation token representing the method-level deadline.
/// Raw client-disconnect token, independent of the method timeout.
/// Optional parent execution id for audit correlation on routed calls.
internal RouteTarget(
string instanceCode,
IInstanceLocator instanceLocator,
IInstanceRouter instanceRouter,
CancellationToken deadlineToken,
CancellationToken requestAbortedToken,
Guid? parentExecutionId)
{
_instanceCode = instanceCode;
_instanceLocator = instanceLocator;
_instanceRouter = instanceRouter;
_deadlineToken = deadlineToken;
_requestAbortedToken = requestAbortedToken;
_parentExecutionId = parentExecutionId;
}
///
/// Calls a script on the remote instance. Synchronous from API caller's
/// perspective. may be a dictionary or an
/// anonymous object (new { name = "Bob" }) — see .
///
/// When is not supplied the
/// routed call inherits the executing method's timeout, so the call is bounded by
/// the method-level deadline with no token argument.
///
/// Name of the script to call on the remote instance.
/// Optional parameters passed to the script; may be a dictionary or anonymous object.
/// Optional cancellation token; defaults to the method deadline when not supplied.
/// A task that resolves to the remote script's return value, or null.
public async Task