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