///|
/// Stable error categories for embedding; full help is not put in errors.
pub fn DocoptError::category(self : DocoptError) -> String {
match self {
Language(_) => "language"
Arguments(_) => "arguments"
Limit(_) => "limit"
Conversion(_) => "conversion"
}
}
///|
pub fn DocoptError::message(self : DocoptError) -> String {
match self {
Language(s) | Arguments(s) | Limit(s) | Conversion(s) => s
}
}
///|
fn error_json(category : String, message : String) -> Json {
{ "status": "error", "category": Json(category), "message": Json(message) }
}
///|
fn request_bool(
fields : Map[String, Json],
key : String,
fallback : Bool,
) -> Bool raise DocoptError {
match fields.get(key) {
None => fallback
Some(True) => true
Some(False) => false
_ => raise Conversion(key + " must be a JSON boolean")
}
}
///|
fn Parser::request_checked(
self : Parser,
request : Json,
) -> Json raise DocoptError {
guard request is Object(fields) else {
raise Conversion("request must be an object")
}
for key, _ in fields {
guard ["argv", "help", "version", "options_first"].contains(key) else {
raise Conversion("unknown request field " + key)
}
}
guard fields.get("argv") is Some(Array(items)) else {
raise Conversion("argv must be an array")
}
guard items.length() <= self.limits.argv_count else {
raise Limit("too many argv items")
}
let argv = []
for item in items {
guard item is String(s) else {
raise Conversion("argv items must be strings")
}
argv.push(s)
}
let version = match fields.get("version") {
None | Some(Null) => None
Some(String(s)) => Some(s)
_ => raise Conversion("version must be a string or null")
}
match
self.run(
argv,
help=request_bool(fields, "help", true),
version~,
options_first=request_bool(fields, "options_first", false),
) {
Parsed(values) => { "status": "parsed", "values": values.to_json() }
Help(text) => { "status": "help", "text": Json(text) }
Version(text) => { "status": "version", "text": Json(text) }
}
}
///|
/// JSON embedding API with explicit status envelopes and strict field checking.
pub fn Parser::request(self : Parser, request : Json) -> Json {
self.request_checked(request) catch {
e => error_json(e.category(), e.message())
}
}
///|
/// Reject duplicate keys and deep JSON before interpreting the request.
pub fn Parser::request_json(self : Parser, text : String) -> Json {
if text.length() > self.limits.argv_units {
return error_json("limit", "JSON request too large")
}
let request = @json.parse(
text[:],
max_nesting_depth=16,
reject_duplicate_keys=true,
) catch {
_ => return error_json("json", "invalid JSON request")
}
self.request(request)
}