///|
/// Expect header parsing errors (RFC 9110 Section 10.1.1)
pub(all) enum ExpectError {
Empty
InvalidFormat
InvalidToken
InvalidValue
} derive(Show, Eq)
///|
/// Expectation item
pub(all) struct Expectation {
token : String
value : String?
} derive(Show, Eq)
///|
pub fn Expectation::token(self : Expectation) -> String {
self.token
}
///|
pub fn Expectation::value(self : Expectation) -> String? {
self.value
}
///|
pub fn Expectation::is_100_continue(self : Expectation) -> Bool {
let lower = exp_to_lower_case(self.token)
lower == "100-continue"
}
///|
pub fn Expectation::to_string(self : Expectation) -> String {
match self.value {
Some(v) =>
if exp_needs_quoting(v) {
self.token + "=\"" + exp_escape_quotes(v) + "\""
} else {
self.token + "=" + v
}
None => self.token
}
}
///|
/// Expect header (RFC 9110 Section 10.1.1)
pub(all) struct Expect {
items : Array[Expectation]
} derive(Show, Eq)
///|
pub fn Expect::parse(input : String) -> Result[Expect, ExpectError] {
let s = exp_trim_string(input)
if s.length() == 0 {
return Err(Empty)
}
let parts = exp_split_with_quotes(s, 44) // ,
let items : Array[Expectation] = []
let mut part_idx = 0
while part_idx < parts.length() {
let part = exp_trim_string(parts[part_idx])
if part.length() == 0 {
return Err(InvalidFormat)
}
let parse_result = exp_parse_expectation(part)
match parse_result {
Err(e) => return Err(e)
Ok((token, value)) => {
if !exp_is_valid_token(token) {
return Err(InvalidToken)
}
let lower_token = exp_to_lower_case(token)
items.push({ token: lower_token, value })
part_idx = part_idx + 1
}
}
}
if items.length() == 0 {
return Err(Empty)
}
Ok({ items, })
}
///|
pub fn Expect::items(self : Expect) -> Array[Expectation] {
self.items
}
///|
pub fn Expect::has_100_continue(self : Expect) -> Bool {
let mut idx = 0
while idx < self.items.length() {
let item = self.items[idx]
if item.is_100_continue() {
return true
}
idx = idx + 1
}
false
}
///|
pub fn Expect::to_string(self : Expect) -> String {
let strs = exp_map_items(self.items, fn(e) { e.to_string() })
exp_join_strings(strs, ", ")
}
///|
/// Helper functions
///
///|
fn exp_parse_expectation(
input : String,
) -> Result[(String, String?), ExpectError] {
let eq_pos = exp_find_char_in(input, 0, 61) // =
match eq_pos {
Some(pos) => {
let token = exp_trim_string(exp_string_slice(input, 0, pos))
if token.length() == 0 {
return Err(InvalidFormat)
}
let value_str = exp_string_slice_from(input, pos + 1)
let value_result = exp_parse_value(value_str)
match value_result {
Ok(v) => Ok((token, Some(v)))
Err(e) => Err(e)
}
}
None => Ok((input, None))
}
}
///|
fn exp_parse_value(input : String) -> Result[String, ExpectError] {
let s = exp_trim_string(input)
if s.length() == 0 {
return Err(InvalidValue)
}
if exp_char_at(s, 0) == 34 { // "
let result = exp_parse_quoted_string(exp_string_slice_from(s, 1))
match result {
Ok((value, remaining)) => {
let rest = exp_trim_string(remaining)
if rest.length() != 0 {
return Err(InvalidValue)
}
Ok(value)
}
Err(e) => Err(e)
}
} else {
if !exp_is_valid_token(s) {
return Err(InvalidValue)
}
Ok(s)
}
}
///|
fn exp_parse_quoted_string(
input : String,
) -> Result[(String, String), ExpectError] {
let mut result = ""
let mut escaped = false
let mut idx = 0
let len = input.length()
while idx < len {
let ch = exp_char_at(input, idx)
if escaped {
result = result + Int::unsafe_to_char(ch).to_string()
escaped = false
idx = idx + 1
} else if ch == 92 { // \
escaped = true
idx = idx + 1
} else if ch == 34 { // "
return Ok((result, exp_string_slice_from(input, idx + 1)))
} else {
result = result + Int::unsafe_to_char(ch).to_string()
idx = idx + 1
}
}
Err(InvalidValue)
}
///|
fn exp_split_with_quotes(input : String, delimiter : Int) -> Array[String] {
let result : Array[String] = []
let mut start = 0
let mut in_quote = false
let mut escaped = false
let mut idx = 0
let len = input.length()
while idx < len {
let ch = exp_char_at(input, idx)
if escaped {
escaped = false
idx = idx + 1
continue
}
if ch == 92 && in_quote { // \
escaped = true
idx = idx + 1
continue
}
if ch == 34 { // "
in_quote = !in_quote
idx = idx + 1
continue
}
if ch == delimiter && !in_quote {
result.push(exp_string_slice(input, start, idx))
start = idx + 1
}
idx = idx + 1
}
result.push(exp_string_slice_from(input, start))
result
}
///|
fn exp_is_valid_token(s : String) -> Bool {
if s.length() == 0 {
return false
}
let mut idx = 0
while idx < s.length() {
if !exp_is_token_char(exp_char_at(s, idx)) {
return false
}
idx = idx + 1
}
true
}
///|
fn exp_is_token_char(b : Int) -> Bool {
b == 33 ||
b == 35 ||
b == 36 ||
b == 37 ||
b == 38 ||
b == 39 ||
b == 42 ||
b == 43 ||
b == 45 ||
b == 46 ||
b == 48 ||
b == 49 ||
b == 50 ||
b == 51 ||
b == 52 ||
b == 53 ||
b == 54 ||
b == 55 ||
b == 56 ||
b == 57 ||
(b >= 65 && b <= 90) ||
b == 94 ||
b == 95 ||
b == 96 ||
(b >= 97 && b <= 122) ||
b == 124 ||
b == 126
}
///|
fn exp_needs_quoting(s : String) -> Bool {
if s.length() == 0 {
return true
}
let mut idx = 0
while idx < s.length() {
if !exp_is_token_char(exp_char_at(s, idx)) {
return true
}
idx = idx + 1
}
false
}
///|
fn exp_escape_quotes(s : String) -> String {
let mut result = ""
let mut idx = 0
while idx < s.length() {
let ch = exp_char_at(s, idx)
if ch == 92 { // \
result = result + "\\\\"
} else if ch == 34 { // "
result = result + "\\\""
} else {
result = result + Int::unsafe_to_char(ch).to_string()
}
idx = idx + 1
}
result
}
///|
fn exp_to_lower_case(s : String) -> String {
str_to_lower_case(s)
}
///|
fn exp_char_at(s : String, idx : Int) -> Int {
str_char_at(s, idx)
}
///|
fn exp_find_char_in(s : String, start : Int, target : Int) -> Int? {
let mut idx = start
while idx < s.length() {
if exp_char_at(s, idx) == target {
return Some(idx)
}
idx = idx + 1
}
None
}
///|
fn exp_string_slice(s : String, start : Int, end : Int) -> String {
str_string_slice(s, start, end)
}
///|
fn exp_string_slice_from(s : String, start : Int) -> String {
str_string_slice_from(s, start)
}
///|
fn exp_trim_string(s : String) -> String {
str_trim_string(s)
}
///|
fn[T, U] exp_map_items(arr : Array[T], f : (T) -> U) -> Array[U] {
let result : Array[U] = []
let mut idx = 0
while idx < arr.length() {
result.push(f(arr[idx]))
idx = idx + 1
}
result
}
///|
fn exp_join_strings(strs : Array[String], sep : String) -> String {
let mut result = ""
let mut idx = 0
while idx < strs.length() {
if idx > 0 {
result = result + sep
}
result = result + strs[idx]
idx = idx + 1
}
result
}