macro_rules! admin_endpoints { [ $(@special $special_endpoint:ident,)* $($endpoint:ident,)* ] => { paste! { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum AdminApiRequest { $( $special_endpoint( [<$special_endpoint Request>] ), )* $( $endpoint( [<$endpoint Request>] ), )* } #[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum AdminApiResponse { $( $endpoint( [<$endpoint Response>] ), )* } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum TaggedAdminApiResponse { $( $endpoint( [<$endpoint Response>] ), )* } impl AdminApiRequest { pub fn name(&self) -> &'static str { match self { $( Self::$special_endpoint(_) => stringify!($special_endpoint), )* $( Self::$endpoint(_) => stringify!($endpoint), )* } } } impl AdminApiResponse { pub fn tagged(self) -> TaggedAdminApiResponse { match self { $( Self::$endpoint(res) => TaggedAdminApiResponse::$endpoint(res), )* } } } $( impl From< [< $endpoint Request >] > for AdminApiRequest { fn from(req: [< $endpoint Request >]) -> AdminApiRequest { AdminApiRequest::$endpoint(req) } } impl TryFrom for [< $endpoint Response >] { type Error = TaggedAdminApiResponse; fn try_from(resp: TaggedAdminApiResponse) -> Result< [< $endpoint Response >], TaggedAdminApiResponse> { match resp { TaggedAdminApiResponse::$endpoint(v) => Ok(v), x => Err(x), } } } )* impl RequestHandler for AdminApiRequest { type Response = AdminApiResponse; async fn handle(self, garage: &Arc, admin: &Admin) -> Result { match self { $( AdminApiRequest::$special_endpoint(_) => Err( Error::Common(CommonError::BadRequest( concat!(stringify!($special_endpoint), " cannot be used outside of the HTTP Admin API").into() )) ), )* $( AdminApiRequest::$endpoint(req) => Ok(AdminApiResponse::$endpoint(req.handle(garage, admin).await?)), )* } } } } }; } macro_rules! local_admin_endpoints { [ $($endpoint:ident,)* ] => { paste! { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum LocalAdminApiRequest { $( $endpoint( [] ), )* } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum LocalAdminApiResponse { $( $endpoint( [] ), )* } $( pub type [< $endpoint Request >] = MultiRequest< [< Local $endpoint Request >] >; pub type [< $endpoint RequestBody >] = [< Local $endpoint Request >]; pub type [< $endpoint Response >] = MultiResponse< [< Local $endpoint Response >] >; impl From< [< Local $endpoint Request >] > for LocalAdminApiRequest { fn from(req: [< Local $endpoint Request >]) -> LocalAdminApiRequest { LocalAdminApiRequest::$endpoint(req) } } impl TryFrom for [< Local $endpoint Response >] { type Error = LocalAdminApiResponse; fn try_from(resp: LocalAdminApiResponse) -> Result< [< Local $endpoint Response >], LocalAdminApiResponse> { match resp { LocalAdminApiResponse::$endpoint(v) => Ok(v), x => Err(x), } } } impl RequestHandler for [< $endpoint Request >] { type Response = [< $endpoint Response >]; async fn handle(self, garage: &Arc, admin: &Admin) -> Result { let to = find_matching_nodes(garage, self.node.as_str())?; let resps = garage.system.rpc_helper().call_many(&admin.endpoint, &to, AdminRpc::Internal(self.body.into()), RequestStrategy::with_priority(PRIO_NORMAL), ).await?; let mut ret = [< $endpoint Response >] { success: HashMap::new(), error: HashMap::new(), }; for (node, resp) in resps { match resp { Ok(AdminRpcResponse::InternalApiOkResponse(r)) => { match [< Local $endpoint Response >]::try_from(r) { Ok(r) => { ret.success.insert(hex::encode(node), r); } Err(_) => { ret.error.insert(hex::encode(node), "returned invalid value".to_string()); } } } Ok(AdminRpcResponse::ApiErrorResponse{error_code, http_code, message}) => { ret.error.insert(hex::encode(node), format!("{} ({}): {}", error_code, http_code, message)); } Ok(_) => { ret.error.insert(hex::encode(node), "returned invalid value".to_string()); } Err(e) => { ret.error.insert(hex::encode(node), e.to_string()); } } } Ok(ret) } } )* impl LocalAdminApiRequest { pub fn name(&self) -> &'static str { match self { $( Self::$endpoint(_) => stringify!($endpoint), )* } } } impl RequestHandler for LocalAdminApiRequest { type Response = LocalAdminApiResponse; async fn handle(self, garage: &Arc, admin: &Admin) -> Result { Ok(match self { $( LocalAdminApiRequest::$endpoint(req) => LocalAdminApiResponse::$endpoint(req.handle(garage, admin).await?), )* }) } } } }; } pub(crate) use admin_endpoints; pub(crate) use local_admin_endpoints;