///|
/// A typed path avoids ambiguity between named members and indices.
pub(all) enum Segment {
Key(String)
Index(Int32)
} derive(Debug, Eq)
///|
/// Parse paths such as server.port, peers[0].host or peers.[0].host.
pub fn parse_path(path : String) -> Array[Segment] raise ConfigError {
if path.length() > Limits::default().max_input {
raise failure("input_limit", "path exceeds byte limit")
}
let r = Reader::new(@utf8.encode(path), Limits::default())
let result : Array[Segment] = []
while r.peek() != -1 {
if result.length() >= 256 {
raise r.error("path_limit", "path exceeds 256 segments")
}
if r.peek() == 91 {
r.advance()
let start = r.pos
while digit(r.peek()) {
r.advance()
}
if start == r.pos || r.peek() != 93 {
raise r.error("invalid_path", "expected nonnegative index and ]")
}
let index = @string.parse_int(@utf8.decode_lossy(r.data[start:r.pos])) catch {
_ => raise r.error("invalid_path", "index exceeds 32-bit range")
}
result.push(Index(index))
r.advance()
} else {
if !name_start(r.peek()) {
raise r.error("invalid_path", "expected member name")
}
result.push(Key(r.name()))
}
if r.peek() == 46 {
r.advance()
if r.peek() == -1 {
raise r.error("invalid_path", "trailing dot")
}
} else if r.peek() != -1 && r.peek() != 91 {
raise r.error("invalid_path", "expected dot or index")
}
}
result
}
///|
/// Retrieve a group member without implicit conversion.
pub fn Value::get_member(
self : Value,
name : String,
) -> Value raise ConfigError {
guard self is Group(items) else {
raise failure("type_mismatch", "expected group")
}
for (key, value) in items {
if key == name {
return value
}
}
raise failure("missing_member", "missing setting: " + name)
}
///|
/// Retrieve an array/list element. Negative indices are errors.
pub fn Value::at(self : Value, index : Int32) -> Value raise ConfigError {
let items = match self {
ScalarArray(v) | List(v) => v
_ => raise failure("type_mismatch", "expected array or list")
}
match items.get(index) {
Some(v) => v
None => raise failure("index_out_of_bounds", "index outside container")
}
}
///|
/// Look up a typed path. An empty path addresses the root.
pub fn Value::get_path(
self : Value,
path : ArrayView[Segment],
) -> Value raise ConfigError {
if path.length() > 256 {
raise failure("path_limit", "query path exceeds 256 segments")
}
let mut value = self
for part in path {
value = match part {
Key(name) => value.get_member(name)
Index(i) => value.at(i)
}
}
value
}
///|
/// Look up a dot/bracket path.
pub fn Value::lookup(self : Value, path : String) -> Value raise ConfigError {
self.get_path(parse_path(path))
}
///|
/// Strict 32-bit extraction.
pub fn Value::as_int(self : Value) -> Int32 raise ConfigError {
match self {
Integer(v) => v
_ => raise failure("type_mismatch", "expected integer")
}
}
///|
/// Strict 64-bit extraction (does not widen Integer automatically).
pub fn Value::as_int64(self : Value) -> Int64 raise ConfigError {
match self {
Integer64(v) => v
_ => raise failure("type_mismatch", "expected integer64")
}
}
///|
/// Strict floating point extraction.
pub fn Value::as_double(self : Value) -> Double raise ConfigError {
match self {
Float(v) => v
_ => raise failure("type_mismatch", "expected float")
}
}
///|
/// Strict boolean extraction.
pub fn Value::as_bool(self : Value) -> Bool raise ConfigError {
match self {
Boolean(v) => v
_ => raise failure("type_mismatch", "expected boolean")
}
}
///|
/// Strict byte string extraction, without UTF-8 decoding.
pub fn Value::as_bytes(self : Value) -> Bytes raise ConfigError {
match self {
Text(v) => v
_ => raise failure("type_mismatch", "expected string")
}
}
///|
/// Return a tree with an existing setting replaced, leaving source containers unchanged.
pub fn Value::replaced(
self : Value,
path : ArrayView[Segment],
replacement : Value,
) -> Value raise ConfigError {
if path.length() > 256 {
raise failure("path_limit", "replacement path exceeds 256 segments")
}
if path.length() == 0 {
return replacement
}
match path[0] {
Key(name) => {
guard self is Group(items) else {
raise failure("type_mismatch", "expected group")
}
let output = items.copy()
for i = 0; i < output.length(); i = i + 1 {
let (key, value) = output[i]
if key == name {
output[i] = (key, value.replaced(path[1:], replacement))
return Group(output)
}
}
raise failure("missing_member", "missing setting: " + name)
}
Index(index) => {
let old = self.at(index)
let changed = old.replaced(path[1:], replacement)
match self {
ScalarArray(items) => {
if changed.kind() != old.kind() {
raise failure(
"array_type", "replacement changes array element type",
)
}
let output = items.copy()
output[index] = changed
ScalarArray(output)
}
List(items) => {
let output = items.copy()
output[index] = changed
List(output)
}
_ => raise failure("type_mismatch", "expected array or list")
}
}
}
}