// The error model. A plain gRPC failure surfaces as `Refused`; etcd's own two
// revision conditions carry the offending revision so callers can match them
// directly. The transport layer builds these from a gRPC `StatusError`.
///|
/// A gRPC status: the numeric code (one of the 17 gRPC codes) and the server's
/// message. Surfaces when a call ends other than OK.
#derive(Eq)
pub(all) struct Status {
code : Int32
message : String
}
///|
pub extend Status with Eq::{not_equal, equal}
///|
/// The message that comes with a `Compacted` or `FutureRev` error: the revision
/// the server rejected.
pub fn Status::to_string(self : Status) -> String {
"rpc error: code = " + self.code.to_string() + " desc = " + self.message
}
///|
/// The error type every client method raises. A plain gRPC failure is `Refused`;
/// etcd's revision conditions (`Compacted`, `FutureRev`) carry the revision so a
/// caller can react without scraping a string.
pub(all) suberror EtcdError {
Refused(Status)
Compacted(Int64)
FutureRev(Int64)
}
///|
pub impl Show for EtcdError with fn output(self, logger) {
logger.write_string(self.message())
}
///|
pub extend EtcdError with Show::{to_string, output}
///|
/// The error a failed call ended with, from the status its transport reported. Both
/// revision conditions are sent as `OUT_OF_RANGE` over gRPC and as `UNKNOWN` by the
/// lock and election services, so they are told apart by the message text — the way
/// etcd's own client does — rather than by the code.
pub fn EtcdError::of(code : Int32, message : String) -> EtcdError {
if message.contains("required revision has been compacted") {
return Compacted(extract_rev(message))
}
if message.contains("required revision is a future revision") {
return FutureRev(extract_rev(message))
}
Refused(Status::{ code, message, })
}
///|
/// The trailing decimal integer of an etcd error message, such as the revision in
/// `etcdserver: mvcc: required revision has been compacted: 12345`.
fn extract_rev(message : String) -> Int64 {
let chars = message.to_array()
let n = chars.length()
let mut end = -1
let mut i = n - 1
while i >= 0 {
let digit = chars[i].to_int()
if digit >= 48 && digit <= 57 {
end = i + 1
break
}
i = i - 1
}
if end < 0 {
return 0L
}
let mut start = end - 1
while start >= 0 && chars[start].to_int() >= 48 && chars[start].to_int() <= 57 {
start = start - 1
}
let mut value : Int64 = 0L
let mut k = start + 1
while k < end {
value = value * 10L + Int64::from_int(chars[k].to_int() - 48)
k = k + 1
}
value
}
///|
pub fn EtcdError::message(self : EtcdError) -> String {
match self {
Refused(s) => s.to_string()
Compacted(rev) =>
"etcdserver: mvcc: required revision has been compacted: " +
rev.to_string()
FutureRev(rev) =>
"etcdserver: mvcc: required revision is a future revision: " +
rev.to_string()
}
}