mirror of
https://github.com/girlbossceo/conduwuit.git
synced 2024-11-30 13:43:30 +00:00
elaborate error macro and apply at various callsites
Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
parent
b3f2288d07
commit
05efd9b044
|
@ -1,4 +1,4 @@
|
||||||
use conduit::{utils::time, warn, Error, Result};
|
use conduit::{utils::time, warn, Err, Result};
|
||||||
use ruma::events::room::message::RoomMessageEventContent;
|
use ruma::events::room::message::RoomMessageEventContent;
|
||||||
|
|
||||||
use crate::services;
|
use crate::services;
|
||||||
|
@ -88,11 +88,10 @@ pub(super) async fn restart(_body: Vec<&str>, force: bool) -> Result<RoomMessage
|
||||||
use conduit::utils::sys::current_exe_deleted;
|
use conduit::utils::sys::current_exe_deleted;
|
||||||
|
|
||||||
if !force && current_exe_deleted() {
|
if !force && current_exe_deleted() {
|
||||||
return Err(Error::Err(
|
return Err!(
|
||||||
"The server cannot be restarted because the executable was tampered with. If this is expected use --force \
|
"The server cannot be restarted because the executable changed. If this is expected use --force to \
|
||||||
to override."
|
override."
|
||||||
.to_owned(),
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
services().server.restart()?;
|
services().server.restart()?;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use conduit_core::Error;
|
use conduit_core::{err, Err};
|
||||||
use ruma::{OwnedRoomId, OwnedUserId, RoomId, UserId};
|
use ruma::{OwnedRoomId, OwnedUserId, RoomId, UserId};
|
||||||
use service::user_is_local;
|
use service::user_is_local;
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ pub(crate) fn get_room_info(id: &RoomId) -> (OwnedRoomId, u64, String) {
|
||||||
/// Parses user ID
|
/// Parses user ID
|
||||||
pub(crate) fn parse_user_id(user_id: &str) -> Result<OwnedUserId> {
|
pub(crate) fn parse_user_id(user_id: &str) -> Result<OwnedUserId> {
|
||||||
UserId::parse_with_server_name(user_id.to_lowercase(), services().globals.server_name())
|
UserId::parse_with_server_name(user_id.to_lowercase(), services().globals.server_name())
|
||||||
.map_err(|e| Error::Err(format!("The supplied username is not a valid username: {e}")))
|
.map_err(|e| err!("The supplied username is not a valid username: {e}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses user ID as our local user
|
/// Parses user ID as our local user
|
||||||
|
@ -41,7 +41,7 @@ pub(crate) fn parse_local_user_id(user_id: &str) -> Result<OwnedUserId> {
|
||||||
let user_id = parse_user_id(user_id)?;
|
let user_id = parse_user_id(user_id)?;
|
||||||
|
|
||||||
if !user_is_local(&user_id) {
|
if !user_is_local(&user_id) {
|
||||||
return Err(Error::Err(String::from("User does not belong to our server.")));
|
return Err!("User {user_id:?} does not belong to our server.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(user_id)
|
Ok(user_id)
|
||||||
|
@ -52,11 +52,11 @@ pub(crate) fn parse_active_local_user_id(user_id: &str) -> Result<OwnedUserId> {
|
||||||
let user_id = parse_local_user_id(user_id)?;
|
let user_id = parse_local_user_id(user_id)?;
|
||||||
|
|
||||||
if !services().users.exists(&user_id)? {
|
if !services().users.exists(&user_id)? {
|
||||||
return Err(Error::Err(String::from("User does not exist on this server.")));
|
return Err!("User {user_id:?} does not exist on this server.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if services().users.is_deactivated(&user_id)? {
|
if services().users.is_deactivated(&user_id)? {
|
||||||
return Err(Error::Err(String::from("User is deactivated.")));
|
return Err!("User {user_id:?} is deactivated.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(user_id)
|
Ok(user_id)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use axum_client_ip::InsecureClientIp;
|
use axum_client_ip::InsecureClientIp;
|
||||||
|
use conduit::{err, info, warn, Error, Result};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::{
|
api::{
|
||||||
client::{
|
client::{
|
||||||
|
@ -18,9 +19,8 @@ use ruma::{
|
||||||
},
|
},
|
||||||
uint, RoomId, ServerName, UInt, UserId,
|
uint, RoomId, ServerName, UInt, UserId,
|
||||||
};
|
};
|
||||||
use tracing::{error, info, warn};
|
|
||||||
|
|
||||||
use crate::{service::server_is_ours, services, Error, Result, Ruma};
|
use crate::{service::server_is_ours, services, Ruma};
|
||||||
|
|
||||||
/// # `POST /_matrix/client/v3/publicRooms`
|
/// # `POST /_matrix/client/v3/publicRooms`
|
||||||
///
|
///
|
||||||
|
@ -271,8 +271,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!("Invalid room join rule event in database: {}", e);
|
err!(Database(error!("Invalid room join rule event in database: {e}")))
|
||||||
Error::BadDatabase("Invalid room join rule event in database.")
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.transpose()?
|
.transpose()?
|
||||||
|
|
|
@ -8,7 +8,7 @@ use std::{
|
||||||
use conduit::{
|
use conduit::{
|
||||||
error,
|
error,
|
||||||
utils::math::{ruma_from_u64, ruma_from_usize, usize_from_ruma, usize_from_u64_truncated},
|
utils::math::{ruma_from_u64, ruma_from_usize, usize_from_ruma, usize_from_u64_truncated},
|
||||||
PduCount,
|
Err, PduCount,
|
||||||
};
|
};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::{
|
api::client::{
|
||||||
|
@ -545,8 +545,7 @@ async fn load_joined_room(
|
||||||
// Database queries:
|
// Database queries:
|
||||||
|
|
||||||
let Some(current_shortstatehash) = services().rooms.state.get_room_shortstatehash(room_id)? else {
|
let Some(current_shortstatehash) = services().rooms.state.get_room_shortstatehash(room_id)? else {
|
||||||
error!("Room {} has no state", room_id);
|
return Err!(Database(error!("Room {room_id} has no state")));
|
||||||
return Err(Error::BadDatabase("Room has no state"));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let since_shortstatehash = services()
|
let since_shortstatehash = services()
|
||||||
|
|
|
@ -1,3 +1,36 @@
|
||||||
|
//! Error construction macros
|
||||||
|
//!
|
||||||
|
//! These are specialized macros specific to this project's patterns for
|
||||||
|
//! throwing Errors; they make Error construction succinct and reduce clutter.
|
||||||
|
//! They are developed from folding existing patterns into the macro while
|
||||||
|
//! fixing several anti-patterns in the codebase.
|
||||||
|
//!
|
||||||
|
//! - The primary macros `Err!` and `err!` are provided. `Err!` simply wraps
|
||||||
|
//! `err!` in the Result variant to reduce `Err(err!(...))` boilerplate, thus
|
||||||
|
//! `err!` can be used in any case.
|
||||||
|
//!
|
||||||
|
//! 1. The macro makes the general Error construction easy: `return
|
||||||
|
//! Err!("something went wrong")` replaces the prior `return
|
||||||
|
//! Err(Error::Err("something went wrong".to_owned()))`.
|
||||||
|
//!
|
||||||
|
//! 2. The macro integrates format strings automatically: `return
|
||||||
|
//! Err!("something bad: {msg}")` replaces the prior `return
|
||||||
|
//! Err(Error::Err(format!("something bad: {msg}")))`.
|
||||||
|
//!
|
||||||
|
//! 3. The macro scopes variants of Error: `return Err!(Database("problem with
|
||||||
|
//! bad database."))` replaces the prior `return Err(Error::Database("problem
|
||||||
|
//! with bad database."))`.
|
||||||
|
//!
|
||||||
|
//! 4. The macro matches and scopes some special-case sub-variants, for example
|
||||||
|
//! with ruma ErrorKind: `return Err!(Request(MissingToken("you must provide
|
||||||
|
//! an access token")))`.
|
||||||
|
//!
|
||||||
|
//! 5. The macro fixes the anti-pattern of repeating messages in an error! log
|
||||||
|
//! and then again in an Error construction, often slightly different due to
|
||||||
|
//! the Error variant not supporting a format string. Instead `return
|
||||||
|
//! Err(Database(error!("problem with db: {msg}")))` logs the error at the
|
||||||
|
//! callsite and then returns the error with the same string. Caller has the
|
||||||
|
//! option of replacing `error!` with `debug_error!`.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! Err {
|
macro_rules! Err {
|
||||||
($($args:tt)*) => {
|
($($args:tt)*) => {
|
||||||
|
@ -7,36 +40,56 @@ macro_rules! Err {
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! err {
|
macro_rules! err {
|
||||||
(error!($($args:tt),+)) => {{
|
(Config($item:literal, $($args:expr),*)) => {{
|
||||||
$crate::error!($($args),+);
|
$crate::error!(config = %$item, $($args),*);
|
||||||
$crate::error::Error::Err(std::format!($($args),+))
|
$crate::error::Error::Config($item, $crate::format_maybe!($($args),*))
|
||||||
}};
|
}};
|
||||||
|
|
||||||
(debug_error!($($args:tt),+)) => {{
|
(Request(Forbidden($level:ident!($($args:expr),*)))) => {{
|
||||||
$crate::debug_error!($($args),+);
|
$crate::$level!($($args),*);
|
||||||
$crate::error::Error::Err(std::format!($($args),+))
|
$crate::error::Error::Request(
|
||||||
|
::ruma::api::client::error::ErrorKind::forbidden(),
|
||||||
|
$crate::format_maybe!($($args),*)
|
||||||
|
)
|
||||||
}};
|
}};
|
||||||
|
|
||||||
($variant:ident(error!($($args:tt),+))) => {{
|
(Request(Forbidden($($args:expr),*))) => {
|
||||||
$crate::error!($($args),+);
|
$crate::error::Error::Request(
|
||||||
$crate::error::Error::$variant(std::format!($($args),+))
|
::ruma::api::client::error::ErrorKind::forbidden(),
|
||||||
}};
|
$crate::format_maybe!($($args),*)
|
||||||
|
)
|
||||||
($variant:ident(debug_error!($($args:tt),+))) => {{
|
|
||||||
$crate::debug_error!($($args),+);
|
|
||||||
$crate::error::Error::$variant(std::format!($($args),+))
|
|
||||||
}};
|
|
||||||
|
|
||||||
(Config($item:literal, $($args:tt),+)) => {{
|
|
||||||
$crate::error!(config = %$item, $($args),+);
|
|
||||||
$crate::error::Error::Config($item, std::format!($($args),+))
|
|
||||||
}};
|
|
||||||
|
|
||||||
($variant:ident($($args:tt),+)) => {
|
|
||||||
$crate::error::Error::$variant(std::format!($($args),+))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
($string:literal$(,)? $($args:tt),*) => {
|
(Request($variant:ident($level:ident!($($args:expr),*)))) => {{
|
||||||
$crate::error::Error::Err(std::format!($string, $($args),*))
|
$crate::$level!($($args),*);
|
||||||
|
$crate::error::Error::Request(
|
||||||
|
::ruma::api::client::error::ErrorKind::$variant,
|
||||||
|
$crate::format_maybe!($($args),*)
|
||||||
|
)
|
||||||
|
}};
|
||||||
|
|
||||||
|
(Request($variant:ident($($args:expr),*))) => {
|
||||||
|
$crate::error::Error::Request(
|
||||||
|
::ruma::api::client::error::ErrorKind::$variant,
|
||||||
|
$crate::format_maybe!($($args),*)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
($variant:ident($level:ident!($($args:expr),*))) => {{
|
||||||
|
$crate::$level!($($args),*);
|
||||||
|
$crate::error::Error::$variant($crate::format_maybe!($($args),*))
|
||||||
|
}};
|
||||||
|
|
||||||
|
($variant:ident($($args:expr),*)) => {
|
||||||
|
$crate::error::Error::$variant($crate::format_maybe!($($args),*))
|
||||||
|
};
|
||||||
|
|
||||||
|
($level:ident!($($args:expr),*)) => {{
|
||||||
|
$crate::$level!($($args),*);
|
||||||
|
$crate::error::Error::Err($crate::format_maybe!($($args),*))
|
||||||
|
}};
|
||||||
|
|
||||||
|
($($args:expr),*) => {
|
||||||
|
$crate::error::Error::Err($crate::format_maybe!($($args),*))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ mod log;
|
||||||
mod panic;
|
mod panic;
|
||||||
mod response;
|
mod response;
|
||||||
|
|
||||||
use std::{any::Any, convert::Infallible, fmt};
|
use std::{any::Any, borrow::Cow, convert::Infallible, fmt};
|
||||||
|
|
||||||
pub use log::*;
|
pub use log::*;
|
||||||
|
|
||||||
|
@ -64,7 +64,9 @@ pub enum Error {
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Mxid(#[from] ruma::IdParseError),
|
Mxid(#[from] ruma::IdParseError),
|
||||||
#[error("{0}: {1}")]
|
#[error("{0}: {1}")]
|
||||||
BadRequest(ruma::api::client::error::ErrorKind, &'static str),
|
BadRequest(ruma::api::client::error::ErrorKind, &'static str), //TODO: remove
|
||||||
|
#[error("{0}: {1}")]
|
||||||
|
Request(ruma::api::client::error::ErrorKind, Cow<'static, str>),
|
||||||
#[error("from {0}: {1}")]
|
#[error("from {0}: {1}")]
|
||||||
Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError),
|
Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError),
|
||||||
#[error("Remote server {0} responded with: {1}")]
|
#[error("Remote server {0} responded with: {1}")]
|
||||||
|
@ -76,11 +78,9 @@ pub enum Error {
|
||||||
#[error("Arithmetic operation failed: {0}")]
|
#[error("Arithmetic operation failed: {0}")]
|
||||||
Arithmetic(&'static str),
|
Arithmetic(&'static str),
|
||||||
#[error("There was a problem with the '{0}' directive in your configuration: {1}")]
|
#[error("There was a problem with the '{0}' directive in your configuration: {1}")]
|
||||||
Config(&'static str, String),
|
Config(&'static str, Cow<'static, str>),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
BadDatabase(&'static str),
|
Database(Cow<'static, str>),
|
||||||
#[error("{0}")]
|
|
||||||
Database(String),
|
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
BadServerResponse(&'static str),
|
BadServerResponse(&'static str),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
|
@ -88,14 +88,11 @@ pub enum Error {
|
||||||
|
|
||||||
// unique / untyped
|
// unique / untyped
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Err(String),
|
Err(Cow<'static, str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
pub fn bad_database(message: &'static str) -> Self {
|
pub fn bad_database(message: &'static str) -> Self { crate::err!(Database(error!("{message}"))) }
|
||||||
error!("BadDatabase: {}", message);
|
|
||||||
Self::BadDatabase(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sanitizes public-facing errors that can leak sensitive information.
|
/// Sanitizes public-facing errors that can leak sensitive information.
|
||||||
pub fn sanitized_string(&self) -> String {
|
pub fn sanitized_string(&self) -> String {
|
||||||
|
@ -121,7 +118,7 @@ impl Error {
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::Federation(_, error) => response::ruma_error_kind(error).clone(),
|
Self::Federation(_, error) => response::ruma_error_kind(error).clone(),
|
||||||
Self::BadRequest(kind, _) => kind.clone(),
|
Self::BadRequest(kind, _) | Self::Request(kind, _) => kind.clone(),
|
||||||
_ => Unknown,
|
_ => Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,7 +126,7 @@ impl Error {
|
||||||
pub fn status_code(&self) -> http::StatusCode {
|
pub fn status_code(&self) -> http::StatusCode {
|
||||||
match self {
|
match self {
|
||||||
Self::Federation(_, ref error) | Self::RumaError(ref error) => error.status_code,
|
Self::Federation(_, ref error) | Self::RumaError(ref error) => error.status_code,
|
||||||
Self::BadRequest(ref kind, _) => response::bad_request_code(kind),
|
Self::BadRequest(ref kind, _) | Self::Request(ref kind, _) => response::bad_request_code(kind),
|
||||||
Self::Conflict(_) => http::StatusCode::CONFLICT,
|
Self::Conflict(_) => http::StatusCode::CONFLICT,
|
||||||
_ => http::StatusCode::INTERNAL_SERVER_ERROR,
|
_ => http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::{
|
||||||
|
|
||||||
use tokio::{runtime, sync::broadcast};
|
use tokio::{runtime, sync::broadcast};
|
||||||
|
|
||||||
use crate::{config::Config, log, Error, Result};
|
use crate::{config::Config, log, Err, Result};
|
||||||
|
|
||||||
/// Server runtime state; public portion
|
/// Server runtime state; public portion
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
|
@ -65,15 +65,15 @@ impl Server {
|
||||||
|
|
||||||
pub fn reload(&self) -> Result<()> {
|
pub fn reload(&self) -> Result<()> {
|
||||||
if cfg!(not(conduit_mods)) {
|
if cfg!(not(conduit_mods)) {
|
||||||
return Err(Error::Err("Reloading not enabled".into()));
|
return Err!("Reloading not enabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.reloading.swap(true, Ordering::AcqRel) {
|
if self.reloading.swap(true, Ordering::AcqRel) {
|
||||||
return Err(Error::Err("Reloading already in progress".into()));
|
return Err!("Reloading already in progress");
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.stopping.swap(true, Ordering::AcqRel) {
|
if self.stopping.swap(true, Ordering::AcqRel) {
|
||||||
return Err(Error::Err("Shutdown already in progress".into()));
|
return Err!("Shutdown already in progress");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.signal("SIGINT").inspect_err(|_| {
|
self.signal("SIGINT").inspect_err(|_| {
|
||||||
|
@ -84,7 +84,7 @@ impl Server {
|
||||||
|
|
||||||
pub fn restart(&self) -> Result<()> {
|
pub fn restart(&self) -> Result<()> {
|
||||||
if self.restarting.swap(true, Ordering::AcqRel) {
|
if self.restarting.swap(true, Ordering::AcqRel) {
|
||||||
return Err(Error::Err("Restart already in progress".into()));
|
return Err!("Restart already in progress");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.shutdown()
|
self.shutdown()
|
||||||
|
@ -93,7 +93,7 @@ impl Server {
|
||||||
|
|
||||||
pub fn shutdown(&self) -> Result<()> {
|
pub fn shutdown(&self) -> Result<()> {
|
||||||
if self.stopping.swap(true, Ordering::AcqRel) {
|
if self.stopping.swap(true, Ordering::AcqRel) {
|
||||||
return Err(Error::Err("Shutdown already in progress".into()));
|
return Err!("Shutdown already in progress");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.signal("SIGTERM")
|
self.signal("SIGTERM")
|
||||||
|
@ -102,7 +102,7 @@ impl Server {
|
||||||
|
|
||||||
pub fn signal(&self, sig: &'static str) -> Result<()> {
|
pub fn signal(&self, sig: &'static str) -> Result<()> {
|
||||||
if let Err(e) = self.signal.send(sig) {
|
if let Err(e) = self.signal.send(sig) {
|
||||||
return Err(Error::Err(format!("Failed to send signal: {e}")));
|
return Err!("Failed to send signal: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -5,7 +5,7 @@ use argon2::{
|
||||||
PasswordVerifier, Version,
|
PasswordVerifier, Version,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Error, Result};
|
use crate::{err, Error, Result};
|
||||||
|
|
||||||
const M_COST: u32 = Params::DEFAULT_M_COST; // memory size in 1 KiB blocks
|
const M_COST: u32 = Params::DEFAULT_M_COST; // memory size in 1 KiB blocks
|
||||||
const T_COST: u32 = Params::DEFAULT_T_COST; // nr of iterations
|
const T_COST: u32 = Params::DEFAULT_T_COST; // nr of iterations
|
||||||
|
@ -44,7 +44,7 @@ pub(super) fn verify_password(password: &str, password_hash: &str) -> Result<()>
|
||||||
.map_err(map_err)
|
.map_err(map_err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_err(e: password_hash::Error) -> Error { Error::Err(e.to_string()) }
|
fn map_err(e: password_hash::Error) -> Error { err!("{e}") }
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use conduit::Result;
|
use conduit::{err, Result};
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn result<T>(r: std::result::Result<T, rocksdb::Error>) -> Result<T, conduit::Error> {
|
pub(crate) fn result<T>(r: std::result::Result<T, rocksdb::Error>) -> Result<T, conduit::Error> {
|
||||||
|
@ -10,4 +10,7 @@ pub(crate) fn and_then<T>(t: T) -> Result<T, conduit::Error> { Ok(t) }
|
||||||
|
|
||||||
pub(crate) fn or_else<T>(e: rocksdb::Error) -> Result<T, conduit::Error> { Err(map_err(e)) }
|
pub(crate) fn or_else<T>(e: rocksdb::Error) -> Result<T, conduit::Error> { Err(map_err(e)) }
|
||||||
|
|
||||||
pub(crate) fn map_err(e: rocksdb::Error) -> conduit::Error { conduit::Error::Database(e.into_string()) }
|
pub(crate) fn map_err(e: rocksdb::Error) -> conduit::Error {
|
||||||
|
let string = e.into_string();
|
||||||
|
err!(Database(error!("{string}")))
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use axum::{
|
||||||
extract::{connect_info::IntoMakeServiceWithConnectInfo, Request},
|
extract::{connect_info::IntoMakeServiceWithConnectInfo, Request},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use conduit::{debug_error, error::infallible, trace, Error, Result, Server};
|
use conduit::{debug, debug_error, error::infallible, info, trace, warn, Err, Result, Server};
|
||||||
use hyper::{body::Incoming, service::service_fn};
|
use hyper::{body::Incoming, service::service_fn};
|
||||||
use hyper_util::{
|
use hyper_util::{
|
||||||
rt::{TokioExecutor, TokioIo},
|
rt::{TokioExecutor, TokioIo},
|
||||||
|
@ -23,7 +23,6 @@ use tokio::{
|
||||||
task::JoinSet,
|
task::JoinSet,
|
||||||
};
|
};
|
||||||
use tower::{Service, ServiceExt};
|
use tower::{Service, ServiceExt};
|
||||||
use tracing::{debug, info, warn};
|
|
||||||
|
|
||||||
type MakeService = IntoMakeServiceWithConnectInfo<Router, net::SocketAddr>;
|
type MakeService = IntoMakeServiceWithConnectInfo<Router, net::SocketAddr>;
|
||||||
|
|
||||||
|
@ -97,19 +96,19 @@ async fn init(server: &Arc<Server>) -> Result<UnixListener> {
|
||||||
|
|
||||||
let dir = path.parent().unwrap_or_else(|| Path::new("/"));
|
let dir = path.parent().unwrap_or_else(|| Path::new("/"));
|
||||||
if let Err(e) = fs::create_dir_all(dir).await {
|
if let Err(e) = fs::create_dir_all(dir).await {
|
||||||
return Err(Error::Err(format!("Failed to create {dir:?} for socket {path:?}: {e}")));
|
return Err!("Failed to create {dir:?} for socket {path:?}: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
let listener = UnixListener::bind(path);
|
let listener = UnixListener::bind(path);
|
||||||
if let Err(e) = listener {
|
if let Err(e) = listener {
|
||||||
return Err(Error::Err(format!("Failed to bind listener {path:?}: {e}")));
|
return Err!("Failed to bind listener {path:?}: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
let socket_perms = config.unix_socket_perms.to_string();
|
let socket_perms = config.unix_socket_perms.to_string();
|
||||||
let octal_perms = u32::from_str_radix(&socket_perms, 8).expect("failed to convert octal permissions");
|
let octal_perms = u32::from_str_radix(&socket_perms, 8).expect("failed to convert octal permissions");
|
||||||
let perms = std::fs::Permissions::from_mode(octal_perms);
|
let perms = std::fs::Permissions::from_mode(octal_perms);
|
||||||
if let Err(e) = fs::set_permissions(&path, perms).await {
|
if let Err(e) = fs::set_permissions(&path, perms).await {
|
||||||
return Err(Error::Err(format!("Failed to set socket {path:?} permissions: {e}")));
|
return Err!("Failed to set socket {path:?} permissions: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Listening at {:?}", path);
|
info!("Listening at {:?}", path);
|
||||||
|
|
|
@ -2,7 +2,7 @@ mod data;
|
||||||
|
|
||||||
use std::{collections::BTreeMap, sync::Arc};
|
use std::{collections::BTreeMap, sync::Arc};
|
||||||
|
|
||||||
use conduit::Result;
|
use conduit::{err, Result};
|
||||||
use data::Data;
|
use data::Data;
|
||||||
use futures_util::Future;
|
use futures_util::Future;
|
||||||
use regex::RegexSet;
|
use regex::RegexSet;
|
||||||
|
@ -171,7 +171,7 @@ impl Service {
|
||||||
.write()
|
.write()
|
||||||
.await
|
.await
|
||||||
.remove(service_name)
|
.remove(service_name)
|
||||||
.ok_or_else(|| crate::Error::Err("Appservice not found".to_owned()))?;
|
.ok_or(err!("Appservice not found"))?;
|
||||||
|
|
||||||
// remove the appservice from the database
|
// remove the appservice from the database
|
||||||
self.db.unregister_appservice(service_name)?;
|
self.db.unregister_appservice(service_name)?;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{panic::AssertUnwindSafe, sync::Arc, time::Duration};
|
use std::{panic::AssertUnwindSafe, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use conduit::{debug, debug_warn, error, trace, utils::time, warn, Error, Result, Server};
|
use conduit::{debug, debug_warn, error, trace, utils::time, warn, Err, Error, Result, Server};
|
||||||
use futures_util::FutureExt;
|
use futures_util::FutureExt;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::{Mutex, MutexGuard},
|
sync::{Mutex, MutexGuard},
|
||||||
|
@ -129,10 +129,7 @@ impl Manager {
|
||||||
/// Start the worker in a task for the service.
|
/// Start the worker in a task for the service.
|
||||||
async fn start_worker(&self, workers: &mut WorkersLocked<'_>, service: &Arc<dyn Service>) -> Result<()> {
|
async fn start_worker(&self, workers: &mut WorkersLocked<'_>, service: &Arc<dyn Service>) -> Result<()> {
|
||||||
if !self.server.running() {
|
if !self.server.running() {
|
||||||
return Err(Error::Err(format!(
|
return Err!("Service {:?} worker not starting during server shutdown.", service.name());
|
||||||
"Service {:?} worker not starting during server shutdown.",
|
|
||||||
service.name()
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Service {:?} worker starting...", service.name());
|
debug!("Service {:?} worker starting...", service.name());
|
||||||
|
|
|
@ -24,7 +24,7 @@ extern crate conduit_database as database;
|
||||||
|
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
pub(crate) use conduit::{config, debug_error, debug_info, debug_warn, utils, Config, Error, Result, Server};
|
pub(crate) use conduit::{config, debug_error, debug_warn, utils, Config, Error, Result, Server};
|
||||||
pub use conduit::{pdu, PduBuilder, PduCount, PduEvent};
|
pub use conduit::{pdu, PduBuilder, PduCount, PduEvent};
|
||||||
use database::Database;
|
use database::Database;
|
||||||
pub(crate) use service::{Args, Service};
|
pub(crate) use service::{Args, Service};
|
||||||
|
|
|
@ -171,7 +171,7 @@ impl Service {
|
||||||
.rooms
|
.rooms
|
||||||
.alias
|
.alias
|
||||||
.resolve_local_alias(room_alias)?
|
.resolve_local_alias(room_alias)?
|
||||||
.ok_or_else(|| err!(BadConfig("Room does not exist.")))?,
|
.ok_or_else(|| err!(Request(NotFound("Room does not exist."))))?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use conduit::{debug, error, trace, validated, warn, Error, Result};
|
use conduit::{debug, error, trace, validated, warn, Err, Result};
|
||||||
use data::Data;
|
use data::Data;
|
||||||
use ruma::{api::client::error::ErrorKind, EventId, RoomId};
|
use ruma::{EventId, RoomId};
|
||||||
|
|
||||||
use crate::services;
|
use crate::services;
|
||||||
|
|
||||||
|
@ -143,8 +143,11 @@ impl Service {
|
||||||
match services().rooms.timeline.get_pdu(&event_id) {
|
match services().rooms.timeline.get_pdu(&event_id) {
|
||||||
Ok(Some(pdu)) => {
|
Ok(Some(pdu)) => {
|
||||||
if pdu.room_id != room_id {
|
if pdu.room_id != room_id {
|
||||||
error!(?event_id, ?pdu, "auth event for incorrect room_id");
|
return Err!(Request(Forbidden(
|
||||||
return Err(Error::BadRequest(ErrorKind::forbidden(), "Evil event in db"));
|
"auth event {event_id:?} for incorrect room {} which is not {}",
|
||||||
|
pdu.room_id,
|
||||||
|
room_id
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
for auth_event in &pdu.auth_events {
|
for auth_event in &pdu.auth_events {
|
||||||
let sauthevent = services()
|
let sauthevent = services()
|
||||||
|
|
|
@ -10,7 +10,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use conduit::{
|
use conduit::{
|
||||||
debug, debug_error, debug_info, error, info, trace,
|
debug, debug_error, debug_info, err, error, info, trace,
|
||||||
utils::{math::continue_exponential_backoff_secs, MutexMap},
|
utils::{math::continue_exponential_backoff_secs, MutexMap},
|
||||||
warn, Error, Result,
|
warn, Error, Result,
|
||||||
};
|
};
|
||||||
|
@ -1382,11 +1382,8 @@ impl Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_room_version_id(create_event: &PduEvent) -> Result<RoomVersionId> {
|
fn get_room_version_id(create_event: &PduEvent) -> Result<RoomVersionId> {
|
||||||
let create_event_content: RoomCreateEventContent =
|
let create_event_content: RoomCreateEventContent = serde_json::from_str(create_event.content.get())
|
||||||
serde_json::from_str(create_event.content.get()).map_err(|e| {
|
.map_err(|e| err!(Database("Invalid create event: {e}")))?;
|
||||||
error!("Invalid create event: {}", e);
|
|
||||||
Error::BadDatabase("Invalid create event in db")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(create_event_content.room_version)
|
Ok(create_event_content.room_version)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use conduit::{Error, Result};
|
use conduit::{Err, Error, Result};
|
||||||
use ruma::{api::client::error::ErrorKind, CanonicalJsonObject, OwnedEventId, OwnedRoomId, RoomId};
|
use ruma::{api::client::error::ErrorKind, CanonicalJsonObject, OwnedEventId, OwnedRoomId, RoomId};
|
||||||
use serde_json::value::RawValue as RawJsonValue;
|
use serde_json::value::RawValue as RawJsonValue;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
@ -17,15 +17,12 @@ pub fn parse_incoming_pdu(pdu: &RawJsonValue) -> Result<(OwnedEventId, Canonical
|
||||||
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Invalid room id in pdu"))?;
|
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Invalid room id in pdu"))?;
|
||||||
|
|
||||||
let Ok(room_version_id) = services().rooms.state.get_room_version(&room_id) else {
|
let Ok(room_version_id) = services().rooms.state.get_room_version(&room_id) else {
|
||||||
return Err(Error::Err(format!("Server is not in room {room_id}")));
|
return Err!("Server is not in room {room_id}");
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok((event_id, value)) = gen_event_id_canonical_json(pdu, &room_version_id) else {
|
let Ok((event_id, value)) = gen_event_id_canonical_json(pdu, &room_version_id) else {
|
||||||
// Event could not be converted to canonical json
|
// Event could not be converted to canonical json
|
||||||
return Err(Error::BadRequest(
|
return Err!(Request(InvalidParam("Could not convert event to canonical json.")));
|
||||||
ErrorKind::InvalidParam,
|
|
||||||
"Could not convert event to canonical json.",
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((event_id, value, room_id))
|
Ok((event_id, value, room_id))
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use conduit::{checked, debug_info, utils::math::usize_from_f64};
|
use conduit::{checked, debug, debug_info, err, utils::math::usize_from_f64, warn, Error, Result};
|
||||||
use lru_cache::LruCache;
|
use lru_cache::LruCache;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::{
|
api::{
|
||||||
|
@ -27,9 +27,8 @@ use ruma::{
|
||||||
OwnedRoomId, OwnedServerName, RoomId, ServerName, UInt, UserId,
|
OwnedRoomId, OwnedServerName, RoomId, ServerName, UInt, UserId,
|
||||||
};
|
};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tracing::{debug, error, warn};
|
|
||||||
|
|
||||||
use crate::{services, Error, Result};
|
use crate::services;
|
||||||
|
|
||||||
pub struct CachedSpaceHierarchySummary {
|
pub struct CachedSpaceHierarchySummary {
|
||||||
summary: SpaceHierarchyParentSummary,
|
summary: SpaceHierarchyParentSummary,
|
||||||
|
@ -380,10 +379,7 @@ impl Service {
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
serde_json::from_str(s.content.get())
|
serde_json::from_str(s.content.get())
|
||||||
.map(|c: RoomJoinRulesEventContent| c.join_rule)
|
.map(|c: RoomJoinRulesEventContent| c.join_rule)
|
||||||
.map_err(|e| {
|
.map_err(|e| err!(Database(error!("Invalid room join rule event in database: {e}"))))
|
||||||
error!("Invalid room join rule event in database: {}", e);
|
|
||||||
Error::BadDatabase("Invalid room join rule event in database.")
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or(JoinRule::Invite);
|
.unwrap_or(JoinRule::Invite);
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
||||||
sync::{Arc, Mutex as StdMutex, Mutex},
|
sync::{Arc, Mutex as StdMutex, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
use conduit::{error, utils::math::usize_from_f64, warn, Error, Result};
|
use conduit::{err, error, utils::math::usize_from_f64, warn, Error, Result};
|
||||||
use data::Data;
|
use data::Data;
|
||||||
use lru_cache::LruCache;
|
use lru_cache::LruCache;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
|
@ -454,10 +454,7 @@ impl Service {
|
||||||
.map(|c: RoomJoinRulesEventContent| {
|
.map(|c: RoomJoinRulesEventContent| {
|
||||||
(c.join_rule.clone().into(), self.allowed_room_ids(c.join_rule))
|
(c.join_rule.clone().into(), self.allowed_room_ids(c.join_rule))
|
||||||
})
|
})
|
||||||
.map_err(|e| {
|
.map_err(|e| err!(Database(error!("Invalid room join rule event in database: {e}"))))
|
||||||
error!("Invalid room join rule event in database: {e}");
|
|
||||||
Error::BadDatabase("Invalid room join rule event in database.")
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or((SpaceRoomJoinRule::Invite, vec![])))
|
.unwrap_or((SpaceRoomJoinRule::Invite, vec![])))
|
||||||
|
@ -483,10 +480,8 @@ impl Service {
|
||||||
Ok(self
|
Ok(self
|
||||||
.room_state_get(room_id, &StateEventType::RoomCreate, "")?
|
.room_state_get(room_id, &StateEventType::RoomCreate, "")?
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
serde_json::from_str::<RoomCreateEventContent>(s.content.get()).map_err(|e| {
|
serde_json::from_str::<RoomCreateEventContent>(s.content.get())
|
||||||
error!("Invalid room create event in database: {e}");
|
.map_err(|e| err!(Database(error!("Invalid room create event in database: {e}"))))
|
||||||
Error::BadDatabase("Invalid room create event in database.")
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.and_then(|e| e.room_type))
|
.and_then(|e| e.room_type))
|
||||||
|
@ -499,10 +494,7 @@ impl Service {
|
||||||
.map_or(Ok(None), |s| {
|
.map_or(Ok(None), |s| {
|
||||||
serde_json::from_str::<RoomEncryptionEventContent>(s.content.get())
|
serde_json::from_str::<RoomEncryptionEventContent>(s.content.get())
|
||||||
.map(|content| Some(content.algorithm))
|
.map(|content| Some(content.algorithm))
|
||||||
.map_err(|e| {
|
.map_err(|e| err!(Database(error!("Invalid room encryption event in database: {e}"))))
|
||||||
error!("Invalid room encryption event in database: {e}");
|
|
||||||
Error::BadDatabase("Invalid room encryption event in database.")
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ mod data;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use conduit::{error, warn, Error, Result};
|
use conduit::{err, error, warn, Error, Result};
|
||||||
use data::Data;
|
use data::Data;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
|
@ -128,10 +128,8 @@ impl Service {
|
||||||
.account_data
|
.account_data
|
||||||
.get(Some(&predecessor.room_id), user_id, RoomAccountDataEventType::Tag)?
|
.get(Some(&predecessor.room_id), user_id, RoomAccountDataEventType::Tag)?
|
||||||
.map(|event| {
|
.map(|event| {
|
||||||
serde_json::from_str(event.get()).map_err(|e| {
|
serde_json::from_str(event.get())
|
||||||
warn!("Invalid account data event in db: {e:?}");
|
.map_err(|e| err!(Database(warn!("Invalid account data event in db: {e:?}"))))
|
||||||
Error::BadDatabase("Invalid account data event in db.")
|
|
||||||
})
|
|
||||||
}) {
|
}) {
|
||||||
services()
|
services()
|
||||||
.account_data
|
.account_data
|
||||||
|
@ -144,10 +142,8 @@ impl Service {
|
||||||
.account_data
|
.account_data
|
||||||
.get(None, user_id, GlobalAccountDataEventType::Direct.to_string().into())?
|
.get(None, user_id, GlobalAccountDataEventType::Direct.to_string().into())?
|
||||||
.map(|event| {
|
.map(|event| {
|
||||||
serde_json::from_str::<DirectEvent>(event.get()).map_err(|e| {
|
serde_json::from_str::<DirectEvent>(event.get())
|
||||||
warn!("Invalid account data event in db: {e:?}");
|
.map_err(|e| err!(Database(warn!("Invalid account data event in db: {e:?}"))))
|
||||||
Error::BadDatabase("Invalid account data event in db.")
|
|
||||||
})
|
|
||||||
}) {
|
}) {
|
||||||
let mut direct_event = direct_event?;
|
let mut direct_event = direct_event?;
|
||||||
let mut room_ids_updated = false;
|
let mut room_ids_updated = false;
|
||||||
|
@ -185,10 +181,8 @@ impl Service {
|
||||||
.into(),
|
.into(),
|
||||||
)?
|
)?
|
||||||
.map(|event| {
|
.map(|event| {
|
||||||
serde_json::from_str::<IgnoredUserListEvent>(event.get()).map_err(|e| {
|
serde_json::from_str::<IgnoredUserListEvent>(event.get())
|
||||||
warn!("Invalid account data event in db: {e:?}");
|
.map_err(|e| err!(Database(warn!("Invalid account data event in db: {e:?}"))))
|
||||||
Error::BadDatabase("Invalid account data event in db.")
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.map_or(false, |ignored| {
|
.map_or(false, |ignored| {
|
||||||
|
|
|
@ -6,7 +6,7 @@ mod sender;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use conduit::{Error, Result};
|
use conduit::{err, Result};
|
||||||
pub use resolve::{resolve_actual_dest, CachedDest, CachedOverride, FedDest};
|
pub use resolve::{resolve_actual_dest, CachedDest, CachedOverride, FedDest};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::{appservice::Registration, OutgoingRequest},
|
api::{appservice::Registration, OutgoingRequest},
|
||||||
|
@ -224,7 +224,7 @@ impl Service {
|
||||||
fn dispatch(&self, msg: Msg) -> Result<()> {
|
fn dispatch(&self, msg: Msg) -> Result<()> {
|
||||||
debug_assert!(!self.sender.is_full(), "channel full");
|
debug_assert!(!self.sender.is_full(), "channel full");
|
||||||
debug_assert!(!self.sender.is_closed(), "channel closed");
|
debug_assert!(!self.sender.is_closed(), "channel closed");
|
||||||
self.sender.send(msg).map_err(|e| Error::Err(e.to_string()))
|
self.sender.send(msg).map_err(|e| err!("{e}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,12 @@ use std::{
|
||||||
time::SystemTime,
|
time::SystemTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
use conduit::Err;
|
use conduit::{debug, debug_error, debug_info, debug_warn, trace, utils::rand, Err, Error, Result};
|
||||||
use hickory_resolver::{error::ResolveError, lookup::SrvLookup};
|
use hickory_resolver::{error::ResolveError, lookup::SrvLookup};
|
||||||
use ipaddress::IPAddress;
|
use ipaddress::IPAddress;
|
||||||
use ruma::{OwnedServerName, ServerName};
|
use ruma::{OwnedServerName, ServerName};
|
||||||
use tracing::{debug, error, trace};
|
|
||||||
|
|
||||||
use crate::{debug_error, debug_info, debug_warn, services, utils::rand, Error, Result};
|
use crate::services;
|
||||||
|
|
||||||
/// Wraps either an literal IP address plus port, or a hostname plus complement
|
/// Wraps either an literal IP address plus port, or a hostname plus complement
|
||||||
/// (colon-plus-port if it was specified).
|
/// (colon-plus-port if it was specified).
|
||||||
|
@ -346,10 +345,7 @@ fn handle_resolve_error(e: &ResolveError) -> Result<()> {
|
||||||
debug!("{e}");
|
debug!("{e}");
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
_ => {
|
_ => Err!(error!("DNS {e}")),
|
||||||
error!("DNS {e}");
|
|
||||||
Err(Error::Err(e.to_string()))
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ impl Service {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let response = serde_json::from_str::<CheckForUpdatesResponse>(&response.text().await?)
|
let response = serde_json::from_str::<CheckForUpdatesResponse>(&response.text().await?)
|
||||||
.map_err(|e| Error::Err(format!("Bad check for updates response: {e}")))?;
|
.map_err(|e| err!("Bad check for updates response: {e}"))?;
|
||||||
|
|
||||||
let mut last_update_id = self.last_check_for_updates_id()?;
|
let mut last_update_id = self.last_check_for_updates_id()?;
|
||||||
for update in response.updates {
|
for update in response.updates {
|
||||||
|
|
Loading…
Reference in a new issue