///|
fn encoded_segment(query : String) -> String {
let hex = "0123456789ABCDEF"
let result : Array[String] = []
for byte in @utf8.encode(query) {
let n = byte.to_int()
if (n >= 65 && n <= 90) ||
(n >= 97 && n <= 122) ||
(n >= 48 && n <= 57) ||
n == 45 ||
n == 46 ||
n == 95 ||
n == 126 {
result.push(String::from_array([n.unsafe_to_char()]))
} else {
result.push(
"%" +
String::from_array([hex[n / 16].to_int().unsafe_to_char()]) +
String::from_array([hex[n % 16].to_int().unsafe_to_char()]),
)
}
}
result.join("")
}
///|
/// The entry page URL for `query`: the query is trimmed, lowercased and
/// percent-encoded as one path segment under `/entri/`. A trailing
/// `/` on `base_url` is ignored.
pub fn entry_url(
query : String,
base_url~ : String = DEFAULT_BASE_URL,
) -> String {
base_url.trim_end(chars="/").to_owned() +
"/entri/" +
encoded_segment(query.trim().to_owned().to_lower())
}
///|
/// Looks up `query` on KBBI Daring: trims it, sends a GET request for its
/// `entry_url` through `client`, and parses the page with `parse`. A word
/// KBBI does not have gives `found: false`, not an error.
///
/// Raises `EmptyQuery` for a blank query (before any request), `Http` when
/// the transport fails, `Status` for a non-2xx response, and `Decode` when
/// the body is not valid UTF-8. Other client policy errors propagate unchanged.
/// The caller owns the client; its policies and body limits apply to the call.
pub async fn lookup(
client : @client.Client,
query : String,
base_url~ : String = DEFAULT_BASE_URL,
) -> LookupResult {
let trimmed = query.trim().to_owned()
if trimmed == "" {
raise EmptyQuery
}
let url = entry_url(trimmed, base_url~)
// Select before reading so an error status wins over an unreadable body.
let reader : @client.ResponsePlan[Result[Bytes, Int32], @client.Buffered] = @client.from_head(head => {
if @http.is_success(head.status) {
@client.ResponsePlan::new((_, bytes) => Ok(bytes))
} else {
@client.ignore_body().any_status().map(_ => Err(head.status))
}
},
)
let request = @client.get(url)
.header("User-Agent", "kbbi.mbt/0.2.0 (https://github.com/gaato/kbbi.mbt)")
.header("Accept", "text/html,application/xhtml+xml")
.response(reader)
let response = client.send(request) catch {
@http.TransportError(..) as err => raise Http(err)
err => raise err
}
let bytes = match response.body {
Ok(bytes) => bytes
Err(status) => raise Status(status)
}
let body = @utf8.decode(bytes) catch { _ => raise Decode("malformed UTF-8") }
parse(body, query=trimmed, url~)
}