///|
fn shell_quote(value : String) -> String {
"'" + value.replace_all(old="'", new="'\\''") + "'"
}
///|
fn printf_bytes(bytes : Bytes) -> String {
let out = StringBuilder()
for byte in bytes {
let n = byte.to_int()
out.write_string("\\0")
for shift in [6, 3, 0] {
out.write_char((48 + ((n >> shift) & 7)).unsafe_to_char())
}
}
out.to_string()
}
///|
/// Renders a POSIX-shell curl diagnostic for the raw request, before Client
/// defaults, cookies, retries or redirects. Sensitive header names are matched
/// case-insensitively. URL userinfo is redacted; query strings and byte bodies
/// are not. Set include_body=false to omit byte bodies as well.
///
/// Byte bodies use a quoted printf pipeline so NUL, quotes, newlines and an
/// initial @ remain bytes rather than shell syntax or a curl filename. Stream
/// bodies and body factories are never consumed/invoked; a comment marks their
/// omission. This function does not execute the command or validate the URL.
pub fn[T] ClientRequest::to_curl(
self : ClientRequest[T],
sensitive_headers~ : Array[String] = [
"authorization", "proxy-authorization", "cookie", "x-api-key", "api-key",
],
include_body~ : Bool = true,
) -> String {
let request = self.raw
let sensitive = sensitive_headers.map(String::to_lower)
let args = [
"curl",
"--globoff",
"--request",
shell_quote(request.meth.to_string()),
]
for (name, value) in request.headers.iter() {
args.push("--header")
args.push(
shell_quote(
name +
": " +
(if sensitive.contains(name.to_lower()) { "" } else { value }),
),
)
}
let parsed = Some(@http.Url::parse(request.url)) catch { _ => None }
let url = match parsed {
Some(url) if url.userinfo is Some(_) => ""
_ => request.url
}
args.push("--url")
args.push(shell_quote(url))
let mut prefix = ""
let mut comment = ""
if self.body_factory is Some(_) {
comment = " # body factory omitted"
} else {
match request.body {
Empty => ()
Bytes(bytes) =>
if include_body {
prefix = "printf '%b' " + shell_quote(printf_bytes(bytes)) + " | "
args.push("--data-binary")
args.push("@-")
if !request.headers.contains("content-type") {
args.push("--header")
args.push("'content-type:'")
}
} else {
comment = " # byte body omitted"
}
Stream(_) => comment = " # streaming body omitted"
@http.Body::.. => comment = " # unsupported body omitted"
}
}
prefix + args.join(" ") + comment
}