SessionSearchQuery.fromArgs constructor

SessionSearchQuery.fromArgs(
  1. Map<Object?, Object?> args
)

Parses the tool arguments; throws FormatException with a one-line model-readable reason when a value is invalid.

Implementation

factory SessionSearchQuery.fromArgs(Map<Object?, Object?> args) {
  final mode = SessionSearchMode.fromName(args['mode']);
  final scope = SessionSearchScope.fromName(args['scope']);
  final rawQuery = args['query'];
  final query = rawQuery is String ? rawQuery.trim() : '';
  if (mode == SessionSearchMode.search && query.isEmpty) {
    throw const FormatException(
      'query is required (or pass mode: "map" for the archive structure)',
    );
  }
  if (query.length > maxSessionSearchQueryChars) {
    throw FormatException(
      'query is too long (${query.length} chars — max '
      '$maxSessionSearchQueryChars); search for a narrower phrase',
    );
  }
  final regex = args['regex'] == true;
  if (regex && query.isNotEmpty) {
    try {
      RegExp(query);
    } on FormatException catch (error) {
      throw FormatException('invalid regex: ${error.message}');
    }
    if (_hasNestedQuantifier(query)) {
      throw const FormatException(
        'regex has a nested quantifier (a "(a+)+"-shape) — catastrophic '
        'backtracking risk; rephrase the pattern without a quantifier '
        'directly on a quantified group',
      );
    }
  }
  var kinds = const <String>{};
  final rawKinds = args['kinds'];
  if (rawKinds is List) {
    kinds = {
      for (final kind in rawKinds)
        if (kind is String && kind.trim().isNotEmpty) kind.trim(),
    };
  }
  DateTime? parseStamp(String label, Object? value) {
    if (value == null) return null;
    final text = value is String ? value.trim() : '$value';
    if (text.isEmpty) return null;
    final parsed = DateTime.tryParse(text);
    if (parsed == null) {
      throw FormatException(
        '$label must be an ISO-8601 timestamp, got: $text',
      );
    }
    return parsed;
  }

  final maxHitsRaw = args['maxHits'];
  var maxHits = defaultSessionSearchMaxHits;
  if (maxHitsRaw is int && maxHitsRaw > 0) {
    maxHits = maxHitsRaw.clamp(1, maxSessionSearchMaxHits);
  }
  final continuationRaw = args['continuation'];
  final continuation = continuationRaw is int && continuationRaw > 0
      ? continuationRaw
      : 0;
  return SessionSearchQuery(
    mode: mode,
    scope: scope,
    query: query,
    kinds: kinds,
    before: parseStamp('before', args['before']),
    after: parseStamp('after', args['after']),
    regex: regex,
    maxHits: maxHits,
    continuation: continuation,
  );
}