/// Shared parsing infrastructure: error construction and the `key` parser
/// (RFC 9651 §4.2.3.3), used by Parameters and Dictionary.
///|
/// Builds an [`SfError`] anchored at the cursor's current byte offset, with
/// a short surrounding-input context.
fn err_at(cursor : Cursor, kind : SfErrorKind) -> SfError {
SfError::make(
kind,
cursor.position(),
cursor.context_string(cursor.position(), 32),
)
}
///|
/// Builds an [`SfError`] anchored at `offset` with an explicit context.
fn err_with(
_cursor : Cursor,
kind : SfErrorKind,
offset : Int,
context : String,
) -> SfError {
SfError::make(kind, offset, context)
}
///|
/// Builds an end-of-input error at the cursor's position.
fn err_end(cursor : Cursor) -> SfError {
SfError::make(
UnexpectedEnd,
cursor.position(),
cursor.context_string(cursor.position(), 32),
)
}
///|
/// Decodes a buffer of ASCII bytes into a String (all SFV wire text is
/// ASCII except decoded Display String content).
fn bytes_to_ascii(b : Bytes) -> String {
@utf8.decode_lossy(b)
}
///|
/// Runs the top-level field parsing algorithm (RFC 9651 §4.2): discard
/// leading SP, parse by the given function, discard trailing SP, and
/// require that nothing remains. This enforces the field-value boundary
/// and produces [`TrailingInput`] errors for stray characters.
pub fn[T] parse_field_bytes(
input : Bytes,
limits : ParseLimits,
parse_fn : (Cursor, ParseLimits) -> Result[T, SfError],
) -> Result[T, SfError] {
if input.length() > limits.max_input_bytes {
return Err(SfError::make(InputTooLarge, 0, "input exceeds max_input_bytes"))
}
let cursor = Cursor::new(input)
cursor.skip_spaces()
match parse_fn(cursor, limits) {
Err(e) => Err(e)
Ok(value) => {
cursor.skip_spaces()
if !cursor.is_end() {
return Err(err_at(cursor, TrailingInput))
}
Ok(value)
}
}
}
///|
/// Parses an Item or Inner List (RFC 9651 §4.2.1.1).
pub fn parse_item_or_inner_list_cursor(
cursor : Cursor,
limits : ParseLimits,
) -> Result[ListMember, SfError] {
if cursor.peek() == Some(b'(') {
return parse_inner_list_cursor(cursor, limits).map(il => InnerListMember(il))
}
parse_item_cursor(cursor, limits).map(it => ItemMember(it))
}
///|
/// Parses a Structured Fields `key` (RFC 9651 §4.2.3.3).
///
/// Grammar: `key = ( lcalpha / "*" ) *( lcalpha / DIGIT / "_" / "-" / "." / "*" )`.
pub fn parse_key(cursor : Cursor) -> Result[String, SfError] {
let first = cursor.peek()
match first {
None => Err(err_end(cursor))
Some(b) =>
if !is_key_start(b) {
Err(err_at(cursor, InvalidKey))
} else {
let _ = cursor.consume()
let buf = @buffer.Buffer(size_hint=8)
buf.write_byte(b)
while true {
let next = cursor.peek()
match next {
None => break
Some(b2) => {
if !is_key_char(b2) {
break
}
let _ = cursor.consume()
buf.write_byte(b2)
}
}
}
Ok(bytes_to_ascii(buf.to_bytes()))
}
}
}