invoke method
Invoke method on touchpoint with args, applying the world's
latency, failure schedule, and corpus (or certified adapter). The
invocation is recorded in the play ledger.
Implementation
Future<dynamic> invoke(
String behaviorId,
String touchpoint,
String method, [
Map<String, dynamic> args = const {},
]) async {
final t = manifest.touchpointNamed(touchpoint);
if (t == null) {
throw WorldProgramError(
'behavior "$behaviorId" targets unknown touchpoint "$touchpoint" '
'--> fix: declare it in the External Dependencies table and '
're-run `zfa simulate init ${manifest.scenario}`.',
);
}
if (t.methods.where((m) => m.name == method).isEmpty) {
throw WorldProgramError(
'behavior "$behaviorId" targets method "$touchpoint.$method" the '
'declared contract does not pin '
'("${t.contract}") --> fix: align the behavior program with the '
'declared contract.',
);
}
final call = _nextCall(touchpoint, method);
// 1. Latency: the world binding samples the bands and advances the
// virtual clock; the real binding injects nothing.
final int latencyMs;
final String bandLabel;
if (binding == WorldBinding.world) {
final bands = manifest.latency[touchpoint] ?? WorldLatencyBands.certified;
final sample = _latency.sample(touchpoint, call, bands);
latencyMs = sample.ms;
bandLabel = sample.band.name;
clock.advance(latencyMs);
} else {
latencyMs = 0;
bandLabel = 'none';
}
// 2. Failure schedule (world binding only).
if (binding == WorldBinding.world) {
final storm = stormAt(manifest.storms, touchpoint, method, call);
if (storm != null) {
final failure = StormFailure(
storm: storm,
kind: StormFailure.kindOf(storm.failure),
);
switch (failure.kind) {
case StormFailureKind.http:
_plays.add(
WorldPlay(
behavior: behaviorId,
touchpoint: touchpoint,
method: method,
call: call,
latencyMs: latencyMs,
band: bandLabel,
virtualMs: clock.nowMs,
outcome: 'failure',
detail: 'storm ${storm.name}: ${failure.label}',
),
);
throw SimulatedHttpException(
failure.httpStatus,
_httpMethodFor(method),
'/$touchpoint/$method',
'failure storm ${storm.name} (${storm.kind})',
);
case StormFailureKind.auth:
_plays.add(
WorldPlay(
behavior: behaviorId,
touchpoint: touchpoint,
method: method,
call: call,
latencyMs: latencyMs,
band: bandLabel,
virtualMs: clock.nowMs,
outcome: 'failure',
detail: 'storm ${storm.name}: ${failure.label}',
),
);
throw SimulatedAuthException(
failure.authCode,
'failure storm ${storm.name} (${storm.kind})',
);
case StormFailureKind.partial:
final fixture = _deepCopy(
manifest.corpusFixture(touchpoint, method),
);
if (fixture is Map<String, dynamic>) {
fixture[kPartialWriteMarker] = true;
}
_plays.add(
WorldPlay(
behavior: behaviorId,
touchpoint: touchpoint,
method: method,
call: call,
latencyMs: latencyMs,
band: bandLabel,
virtualMs: clock.nowMs,
outcome: 'partial',
detail: 'storm ${storm.name}: partial-write marker',
),
);
return fixture;
case StormFailureKind.unknown:
_plays.add(
WorldPlay(
behavior: behaviorId,
touchpoint: touchpoint,
method: method,
call: call,
latencyMs: latencyMs,
band: bandLabel,
virtualMs: clock.nowMs,
outcome: 'failure',
detail: 'storm ${storm.name}: ${storm.kind}',
),
);
throw SimulatedHttpException(
500,
_httpMethodFor(method),
'/$touchpoint/$method',
'failure storm ${storm.name} (${storm.kind})',
);
}
}
}
// 3. Serve the response.
final result = await _serve(t, method, args);
_plays.add(
WorldPlay(
behavior: behaviorId,
touchpoint: touchpoint,
method: method,
call: call,
latencyMs: latencyMs,
band: bandLabel,
virtualMs: clock.nowMs,
outcome: 'ok',
detail: 'served ${shapeOf(result)}',
),
);
return result;
}