parseSelector function
- IPLDNode node
Parse an IPLDNode (decoded from DAG-CBOR or DAG-JSON) into a typed Selector.
Accepts the compact spec keys (., a, f, i, r, R, |, &,
@, ~) and, leniently, their long-form names (matcher,
exploreAll, ...). Malformed shapes are rejected with
SelectorParseError.
Implementation
Selector parseSelector(IPLDNode node) {
if (node.kind != Kind.MAP) {
throw SelectorParseError('Selector must be a map');
}
final entries = node.mapValue.entries;
if (entries.length != 1) {
throw SelectorParseError(
'Selector map must contain exactly one key, found ${entries.length}',
);
}
final key = entries.first.key;
final value = entries.first.value;
switch (key) {
case '.':
case 'matcher':
return _parseMatcher(value);
case 'a':
case 'exploreAll':
final body = _requireMap(value, 'body of "exploreAll"');
return ExploreAll(next: _parseSelectorField(body, const ['>', 'next']));
case 'f':
case 'exploreFields':
final body = _requireMap(value, 'body of "exploreFields"');
final fieldsNode = _requireFieldAny(body, const ['f>', 'fields']);
final fieldsMap = _requireMap(fieldsNode, 'exploreFields fields');
final fields = <String, Selector>{};
for (final entry in fieldsMap.entries) {
fields[entry.key] = parseSelector(entry.value);
}
return ExploreFields(fields: fields);
case 'i':
case 'exploreIndex':
final body = _requireMap(value, 'body of "exploreIndex"');
return ExploreIndex(
index: _requireInt(
_requireFieldAny(body, const ['i', 'index']),
'exploreIndex index',
),
next: _parseSelectorField(body, const ['>', 'next']),
);
case 'r':
case 'exploreRange':
final body = _requireMap(value, 'body of "exploreRange"');
return ExploreRange(
start: _requireInt(
_requireFieldAny(body, const ['^', 'start']),
'exploreRange start',
),
end: _requireInt(
_requireFieldAny(body, const ['\$', 'end']),
'exploreRange end',
),
next: _parseSelectorField(body, const ['>', 'next']),
);
case 'R':
case 'exploreRecursive':
final body = _requireMap(value, 'body of "exploreRecursive"');
final limitNode = _requireFieldAny(body, const ['l', 'limit']);
final sequence = _parseSelectorField(body, const [':>', 'sequence']);
Condition? stopAt;
final stopAtNode = _optionalFieldAny(body, const ['!', 'stopAt']);
if (stopAtNode != null) {
stopAt = parseCondition(stopAtNode);
}
return ExploreRecursive(
limit: _parseRecursionLimit(limitNode),
sequence: sequence,
stopAt: stopAt,
);
case '@':
case 'exploreRecursiveEdge':
return const ExploreRecursiveEdge();
case '|':
case 'exploreUnion':
final membersList = _requireList(value, 'exploreUnion members');
return ExploreUnion(
members: membersList.values.map(parseSelector).toList(),
);
case '&':
case 'exploreConditional':
final body = _requireMap(value, 'body of "exploreConditional"');
Condition? condition;
final conditionNode = _optionalFieldAny(body, const ['&', 'condition']);
if (conditionNode != null) {
condition = parseCondition(conditionNode);
}
Selector? next;
final nextNode = _optionalFieldAny(body, const ['>', 'next']);
if (nextNode != null) {
next = parseSelector(nextNode);
}
return ExploreConditional(condition: condition, next: next);
case '~':
case 'exploreInterpretAs':
case 'interpretAs':
final body = _requireMap(value, 'body of "interpretAs"');
return ExploreInterpretAs(
adl: _requireString(
_requireFieldAny(body, const ['as', 'adl']),
'interpretAs as',
),
next: _parseSelectorField(body, const ['>', 'next']),
);
default:
throw SelectorParseError('Unknown selector key: "$key"');
}
}