///|
priv struct ContentType {
media_type : StringView
subtype : StringView
params : Map[StringView, StringView]
} derive(Show)
///|
fn parse_content_type(s : StringView) -> ContentType? {
fn dequote(value : StringView) -> StringView {
if value is ['"', .. rest, '"'] {
rest
} else {
value
}
}
let params = Map::new()
let (media_type, subtype, rest) = lexmatch s with longest {
(
"[ \t]*"
("[^ \t/;=]+" as media_type)
"[ \t]*"
"/"
"[ \t]*"
("[^ \t/;=]+" as subtype)
"[ \t]*",
rest
) => (media_type, subtype, rest)
_ => return None
}
for curr = rest {
lexmatch curr with longest {
("[ \t]+", rest) => continue rest
(
";"
"[ \t]*"
("[^ \t=;]+" as key)
"[ \t]*"
"="
"[ \t]*"
("(\"[^\"]*\"|[^ \t;]*)" as value)
"[ \t]*",
rest
) => {
params.set(key, dequote(value))
continue rest
}
(";" "[ \t]*", rest) => continue rest
"" => break
_ => break
}
}
Some({ media_type, subtype, params })
}
///|
test "parse_content_type" {
inspect(
parse_content_type("application/json; charset=utf-8"),
content=(
#|Some({media_type: "application", subtype: "json", params: {"charset": "utf-8"}})
),
)
}
///|
test "parse_content_type_with_quoted_params" {
inspect(
parse_content_type(
"multipart/form-data; boundary=\"foo;bar\"; charset = utf-8",
),
content=(
#|Some({media_type: "multipart", subtype: "form-data", params: {"boundary": "foo;bar", "charset": "utf-8"}})
),
)
}
///|
test "parse_content_type_invalid" {
inspect(
parse_content_type("application"),
content=(
#|None
),
)
}
///|
test "parse_form_data" {
inspect(
parse_form_data(b"name=John+Doe&age=30"),
content=(
#|{"name": "John Doe", "age": "30"}
),
)
}