invoke method

Future invoke(
  1. String behaviorId,
  2. String touchpoint,
  3. String method, [
  4. Map<String, dynamic> args = const {},
])

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;
}