///|
/// An unambiguous segment in a read-only Ion path query.
///
/// `Field` selects every matching ordered struct field, including a field whose
/// name is numeric. `Index` selects one element from a list or s-expression.
pub(all) enum PathSegment {
Field(String)
Index(Int)
} derive(Debug, Eq)
///|
/// Return every field with the requested name from a struct value.
pub fn select_field(
value : @model.IonValue,
name : String,
) -> Array[@model.IonValue] {
value.get_fields(name)
}
///|
/// Return an element by index from a list or s-expression.
pub fn select_index(value : @model.IonValue, index : Int) -> @model.IonValue? {
if index < 0 {
return None
}
for i, child in value.elements() {
if i == index {
return Some(child)
}
}
None
}
///|
/// Select values by alternating struct field names and non-negative indexes.
pub fn select_path(
value : @model.IonValue,
path : ArrayView[String],
) -> Array[@model.IonValue] {
let typed_path : Array[PathSegment] = []
for segment in path {
match parse_path_index(segment) {
Some(index) => typed_path.push(PathSegment::Index(index))
None => typed_path.push(PathSegment::Field(segment))
}
}
select_typed_path(value, typed_path[:])
}
///|
/// Return the first value selected by a path, if the path has a result.
pub fn select_first_path(
value : @model.IonValue,
path : ArrayView[String],
) -> @model.IonValue? {
match select_path(value, path) {
[first, ..] => Some(first)
_ => None
}
}
///|
/// Select values with explicit field and index path segments.
///
/// Unlike `select_path`, a `Field("0")` always means the struct field named
/// `"0"`, never list element zero. Repeated fields and document order are
/// preserved, while a missing field, invalid container kind, or out-of-range
/// index returns an empty result without changing the input.
pub fn select_typed_path(
value : @model.IonValue,
path : ArrayView[PathSegment],
) -> Array[@model.IonValue] {
select_typed_path_from_roots([value][:], path)
}
///|
/// Return the first value selected by an explicit path, if any.
pub fn select_first_typed_path(
value : @model.IonValue,
path : ArrayView[PathSegment],
) -> @model.IonValue? {
match select_typed_path(value, path) {
[first, ..] => Some(first)
_ => None
}
}
///|
/// Apply an explicit path to every root value in an Ion document.
///
/// Results retain document order and repeated-field order. An empty path copies
/// the document roots into the returned result, rather than exposing the input
/// array for mutation.
pub fn select_document_typed_path(
values : ArrayView[@model.IonValue],
path : ArrayView[PathSegment],
) -> Array[@model.IonValue] {
select_typed_path_from_roots(values, path)
}
///|
fn select_typed_path_from_roots(
roots : ArrayView[@model.IonValue],
path : ArrayView[PathSegment],
) -> Array[@model.IonValue] {
let mut current : Array[@model.IonValue] = []
for root in roots {
current.push(root)
}
for segment in path {
let next : Array[@model.IonValue] = []
for candidate in current {
match segment {
PathSegment::Field(name) =>
for child in select_field(candidate, name) {
next.push(child)
}
PathSegment::Index(index) =>
match select_index(candidate, index) {
Some(child) => next.push(child)
None => ()
}
}
}
current = next
}
current
}
///|
fn parse_path_index(text : String) -> Int? {
try @string.parse_int(text[:]) catch {
_ => None
} noraise {
index => if index >= 0 { Some(index) } else { None }
}
}