diff --git a/src/traft/failover.rs b/src/traft/failover.rs
index 9d9671b49f518f80960b025e19f4fd13bf8f7940..6bd719f3442a057af99de2d9474fc17353b841b4 100644
--- a/src/traft/failover.rs
+++ b/src/traft/failover.rs
@@ -115,7 +115,7 @@ fn raft_update_peer(
         super::PeerChange::Grade(grade) => {
             tlog!(Warning, "attempt to change grade by peer";
                 "instance_id" => instance_id,
-                "grade" => grade.to_str(),
+                "grade" => grade.as_str(),
             );
             false
         }
diff --git a/src/traft/mod.rs b/src/traft/mod.rs
index 2fb0f117cdf3ac279b46e54205424bec313ace44..2f3350e9eab207f5d361a8445d9a78b0e1c7a7d7 100644
--- a/src/traft/mod.rs
+++ b/src/traft/mod.rs
@@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize};
 use std::any::Any;
 use std::collections::HashMap;
 use std::convert::TryFrom;
-use std::fmt::{Debug, Display};
+use std::fmt::Debug;
 use std::time::Duration;
 use uuid::Uuid;
 
@@ -834,35 +834,24 @@ pub struct SyncRaftResponse {
 impl Encode for SyncRaftResponse {}
 
 ///////////////////////////////////////////////////////////////////////////////
-/// Activity state of an instance.
-#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)]
-pub enum Grade {
-    // Instance has gracefully shut down or has not been started yet.
-    Offline,
-    // Instance has synced by commit index.
-    RaftSynced,
-    // Instance is active and is handling requests.
-    Online,
-    // Instance has permanently removed from cluster.
-    Expelled,
-}
-
-impl Grade {
-    const fn to_str(&self) -> &str {
-        match self {
-            Self::Offline => "Offline",
-            Self::RaftSynced => "RaftSynced",
-            Self::Online => "Online",
-            Self::Expelled => "Expelled",
-        }
+crate::define_str_enum! {
+    /// Activity state of an instance.
+    pub enum Grade {
+        // Instance has gracefully shut down or has not been started yet.
+        Offline = "Offline",
+        // Instance has synced by commit index.
+        RaftSynced = "RaftSynced",
+        // Instance is active and is handling requests.
+        Online = "Online",
+        // Instance has permanently removed from cluster.
+        Expelled = "Expelled",
     }
+    FromStr::Err = UnknownGrade;
 }
 
-impl Display for Grade {
-    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-        f.write_str(self.to_str())
-    }
-}
+#[derive(thiserror::Error, Debug)]
+#[error("unknown grade {0:?}")]
+pub struct UnknownGrade(pub String);
 
 impl Default for Grade {
     fn default() -> Self {
@@ -870,29 +859,22 @@ impl Default for Grade {
     }
 }
 
-#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)]
-pub enum TargetGrade {
-    // Instance should be configured up
-    Online,
-    // Instance should be gracefully shut down
-    Offline,
-    // Instance should be removed from cluster
-    Expelled,
-}
-impl TargetGrade {
-    const fn to_str(&self) -> &str {
-        match self {
-            Self::Online => "Online",
-            Self::Offline => "Offline",
-            Self::Expelled => "Expelled",
-        }
-    }
-}
-impl Display for TargetGrade {
-    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-        f.write_str(self.to_str())
+crate::define_str_enum! {
+    pub enum TargetGrade {
+        // Instance should be configured up
+        Online = "Online",
+        // Instance should be gracefully shut down
+        Offline = "Offline",
+        // Instance should be removed from cluster
+        Expelled = "Expelled",
     }
+    FromStr::Err = UnknownTargetGrade;
 }
+
+#[derive(thiserror::Error, Debug)]
+#[error("unknown target grade {0:?}")]
+pub struct UnknownTargetGrade(pub String);
+
 impl Default for TargetGrade {
     fn default() -> Self {
         Self::Online