Add support for querying NIP-05 on /.well-known/nostr.json?name=username
This commit is contained in:
parent
e1306608ef
commit
377da44eed
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
noose::sled::BanInfo,
|
||||
noose::user::{User, UserRow},
|
||||
noose::user::{User, UserRow, Nip05Profile},
|
||||
utils::{error::Error, structs::Subscription},
|
||||
};
|
||||
use nostr::secp256k1::XOnlyPublicKey;
|
||||
|
@ -25,10 +25,14 @@ pub enum Command {
|
|||
|
||||
// Old messages
|
||||
DbReqInsertUser(UserRow),
|
||||
DbReqGetUser(User),
|
||||
DbReqCreateAccount(XOnlyPublicKey, String, String),
|
||||
DbReqGetAccount(String),
|
||||
DbReqClear,
|
||||
// NIP-05 related messages
|
||||
DbReqGetUser(String),
|
||||
DbResUser(Nip05Profile),
|
||||
|
||||
|
||||
// DbResponse
|
||||
DbResRelayMessages(
|
||||
/* client_id*/ uuid::Uuid,
|
||||
|
@ -38,7 +42,6 @@ pub enum Command {
|
|||
DbResOk,
|
||||
DbResOkWithStatus(/* client_id */ uuid::Uuid, nostr::RelayMessage),
|
||||
DbResAccount, // TODO: Add Account DTO as a param
|
||||
DbResUser(UserRow),
|
||||
DbResEventCounts(/* client_id */ uuid::Uuid, nostr::RelayMessage),
|
||||
// Event Pipeline
|
||||
PipelineReqEvent(/* client_id */ uuid::Uuid, Box<nostr::Event>),
|
||||
|
|
|
@ -6,6 +6,8 @@ use crate::{
|
|||
use nostr::{Event, RelayMessage};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::user::Nip05Profile;
|
||||
|
||||
pub trait Noose: Send + Sync {
|
||||
async fn start(&mut self, pubsub: Arc<PubSub>) -> Result<(), Error>;
|
||||
|
||||
|
@ -14,4 +16,6 @@ pub trait Noose: Send + Sync {
|
|||
async fn find_event(&self, subscription: Subscription) -> Result<Vec<RelayMessage>, Error>;
|
||||
|
||||
async fn counts(&self, subscription: Subscription) -> Result<RelayMessage, Error>;
|
||||
|
||||
async fn get_nip05(&self, username: String) -> Result<Nip05Profile, Error>;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ impl MigrationRunner {
|
|||
let m_events_fts = include_str!("./1697410223576_events_fts.sql");
|
||||
let m_users = include_str!("./1697410294265_users.sql");
|
||||
let m_unattached_media = include_str!("./1697410480767_unattached_media.sql");
|
||||
let m_pragma = include_str!("./1697410424624_pragma.sql");
|
||||
let m_nip05 = include_str!("./1706575155557_nip05.sql");
|
||||
|
||||
let migrations = Migrations::new(vec![
|
||||
M::up(m_create_events),
|
||||
|
@ -20,7 +20,7 @@ impl MigrationRunner {
|
|||
M::up(m_events_fts),
|
||||
M::up(m_users),
|
||||
M::up(m_unattached_media),
|
||||
M::up(m_pragma),
|
||||
M::up(m_nip05),
|
||||
]);
|
||||
|
||||
match migrations.to_latest(connection) {
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
use super::{db::Noose, migrations::MigrationRunner};
|
||||
use super::{
|
||||
db::Noose,
|
||||
migrations::MigrationRunner,
|
||||
user::{Nip05Profile, Nip05Table, UserRow},
|
||||
};
|
||||
use crate::{
|
||||
bussy::{channels, Command, Message, PubSub},
|
||||
utils::{config::Config as ServiceConfig, error::Error, structs::Subscription},
|
||||
|
@ -149,27 +153,6 @@ impl sea_query::Iden for TagsTable {
|
|||
}
|
||||
}
|
||||
|
||||
// enum DeletedCoordinatesTable {
|
||||
// Table,
|
||||
// Coordinate,
|
||||
// CreatedAt,
|
||||
// }
|
||||
|
||||
// impl sea_query::Iden for DeletedCoordinatesTable {
|
||||
// fn unquoted(&self, s: &mut dyn std::fmt::Write) {
|
||||
// write!(
|
||||
// s,
|
||||
// "{}",
|
||||
// match self {
|
||||
// Self::Table => "deleted_coordinates",
|
||||
// Self::Coordinate => "coordinate",
|
||||
// Self::CreatedAt => "created_at",
|
||||
// }
|
||||
// )
|
||||
// .unwrap()
|
||||
// }
|
||||
// }
|
||||
|
||||
enum EventSeenByRelaysTable {
|
||||
Table,
|
||||
Id,
|
||||
|
@ -212,7 +195,22 @@ impl NostrSqlite {
|
|||
|
||||
async fn run_migrations(pool: &Pool) -> bool {
|
||||
let connection = pool.get().await.unwrap();
|
||||
connection.interact(MigrationRunner::up).await.unwrap()
|
||||
connection.interact(MigrationRunner::up).await.unwrap();
|
||||
connection
|
||||
.interact(|conn| {
|
||||
conn.pragma_update(None, "encoding", "UTF-8").unwrap();
|
||||
conn.pragma_update(None, "journal_mode", "WAL").unwrap();
|
||||
conn.pragma_update(None, "foreign_keys", "ON").unwrap();
|
||||
conn.pragma_update(None, "auto_vacuum", "FULL").unwrap();
|
||||
conn.pragma_update(None, "journal_size_limit", "32768")
|
||||
.unwrap();
|
||||
conn.pragma_update(None, "mmap_size", "17179869184")
|
||||
.unwrap();
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
async fn get_connection(&self) -> Result<Object, Error> {
|
||||
|
@ -1225,6 +1223,48 @@ impl NostrSqlite {
|
|||
|
||||
query_result
|
||||
}
|
||||
|
||||
async fn get_nip05_profile(&self, username: String) -> Result<Nip05Profile, Error> {
|
||||
let Ok(connection) = self.get_connection().await else {
|
||||
return Err(Error::internal_with_message("Unable to get DB connection"));
|
||||
};
|
||||
|
||||
let Ok(query_result) = connection
|
||||
.interact(
|
||||
move |conn: &mut rusqlite::Connection| -> Result<Nip05Profile, Error> {
|
||||
let (sql, value) = Query::select()
|
||||
.from(Nip05Table::Table)
|
||||
.columns([
|
||||
Nip05Table::Pubkey,
|
||||
Nip05Table::Username,
|
||||
Nip05Table::Relays,
|
||||
Nip05Table::JoinedAt,
|
||||
])
|
||||
.and_where(sea_query::Expr::col(Nip05Table::Username).eq(&username))
|
||||
.limit(1)
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
|
||||
let Ok(res) = conn.query_row(sql.as_str(), &*value.as_params(), |row| {
|
||||
let nip05_row: UserRow = row.into();
|
||||
let nip05 = Nip05Profile::from(nip05_row);
|
||||
|
||||
Ok(nip05)
|
||||
}) else {
|
||||
return Err(Error::not_found("user", username));
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
},
|
||||
)
|
||||
.await
|
||||
else {
|
||||
return Err(Error::internal_with_message(
|
||||
"Failed to execute query 'get_nip05_profile'",
|
||||
));
|
||||
};
|
||||
|
||||
query_result
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nostr_database::DatabaseError> for Error {
|
||||
|
@ -1351,6 +1391,7 @@ impl Noose for NostrSqlite {
|
|||
while let Ok(message) = subscriber.recv().await {
|
||||
log::info!("[Noose] received message: {:?}", message);
|
||||
let command = match message.content {
|
||||
// Relay Events
|
||||
Command::DbReqWriteEvent(client_id, event) => match self.write_event(event).await {
|
||||
Ok(status) => Command::DbResOkWithStatus(client_id, status),
|
||||
Err(e) => Command::ServiceError(e),
|
||||
|
@ -1375,6 +1416,11 @@ impl Noose for NostrSqlite {
|
|||
Err(e) => Command::ServiceError(e),
|
||||
}
|
||||
}
|
||||
// NIP-05
|
||||
Command::DbReqGetUser(username) => match self.get_nip05(username).await {
|
||||
Ok(user) => Command::DbResUser(user),
|
||||
Err(e) => Command::ServiceError(e),
|
||||
},
|
||||
_ => Command::Noop,
|
||||
};
|
||||
if command != Command::Noop {
|
||||
|
@ -1439,6 +1485,10 @@ impl Noose for NostrSqlite {
|
|||
Err(err) => Err(Error::internal_with_message(err.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_nip05(&self, username: String) -> Result<Nip05Profile, Error> {
|
||||
self.get_nip05_profile(username).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1708,4 +1758,14 @@ mod tests {
|
|||
|
||||
dbg!(res);
|
||||
}
|
||||
|
||||
// #[tokio::test]
|
||||
// async fn get_nip05() {
|
||||
// let config = Arc::new(ServiceConfig::new());
|
||||
// let db = NostrSqlite::new(config).await;
|
||||
|
||||
// let res = db.get_nip05("test".to_string()).await.unwrap();
|
||||
|
||||
// dbg!(serde_json::to_value(res).unwrap().to_string());
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -1,27 +1,103 @@
|
|||
use chrono::Utc;
|
||||
use regex::Regex;
|
||||
use rusqlite::Row;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use validator::{Validate, ValidationError};
|
||||
|
||||
lazy_static! {
|
||||
static ref VALID_CHARACTERS: Regex = Regex::new(r"^[a-zA-Z0-9\_]+$").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Validate)]
|
||||
pub enum Nip05Table {
|
||||
Table,
|
||||
Pubkey,
|
||||
Username,
|
||||
Relays,
|
||||
JoinedAt,
|
||||
}
|
||||
|
||||
impl sea_query::Iden for Nip05Table {
|
||||
fn unquoted(&self, s: &mut dyn std::fmt::Write) {
|
||||
write!(
|
||||
s,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Table => "nip05",
|
||||
Self::Pubkey => "pubkey",
|
||||
Self::Username => "username",
|
||||
Self::Relays => "relays",
|
||||
Self::JoinedAt => "joined_at",
|
||||
}
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct UserRow {
|
||||
pub pubkey: String,
|
||||
pub username: String,
|
||||
inserted_at: i64,
|
||||
admin: bool,
|
||||
relays: Vec<String>,
|
||||
joined_at: i64,
|
||||
}
|
||||
|
||||
impl UserRow {
|
||||
pub fn new(pubkey: String, username: String, admin: bool) -> Self {
|
||||
pub fn new(pubkey: String, username: String, relays: Vec<String>) -> Self {
|
||||
Self {
|
||||
pubkey,
|
||||
username,
|
||||
inserted_at: Utc::now().timestamp(),
|
||||
admin,
|
||||
relays,
|
||||
joined_at: nostr::Timestamp::now().as_i64(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Row<'_>> for UserRow {
|
||||
fn from(row: &Row) -> Self {
|
||||
let pubkey: String = row.get("pubkey").unwrap();
|
||||
let username: String = row.get("username").unwrap();
|
||||
let relays_raw: String = row.get("relays").unwrap_or_default();
|
||||
let joined_at: i64 = row.get("joined_at").unwrap();
|
||||
|
||||
let relays: Vec<String> = match serde_json::from_str(&relays_raw) {
|
||||
Ok(val) => val,
|
||||
Err(_) => vec![],
|
||||
};
|
||||
|
||||
Self {
|
||||
pubkey,
|
||||
username,
|
||||
relays,
|
||||
joined_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct Nip05Profile {
|
||||
names: HashMap<String, String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
relays: Option<HashMap<String, Vec<String>>>,
|
||||
}
|
||||
|
||||
impl From<UserRow> for Nip05Profile {
|
||||
fn from(value: UserRow) -> Self {
|
||||
let mut name: HashMap<String, String> = HashMap::new();
|
||||
name.insert(value.username, value.pubkey.clone());
|
||||
|
||||
let relay = match value.relays.is_empty() {
|
||||
true => None,
|
||||
false => {
|
||||
let mut relay: HashMap<String, Vec<String>> = HashMap::new();
|
||||
relay.insert(value.pubkey.clone(), value.relays);
|
||||
|
||||
Some(relay)
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
names: name,
|
||||
relays: relay,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,3 +117,33 @@ pub fn validate_pubkey(value: &str) -> Result<(), ValidationError> {
|
|||
Err(_) => Err(ValidationError::new("Unable to parse pubkey")),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Nip05Profile, UserRow};
|
||||
|
||||
#[test]
|
||||
fn make_nip05() {
|
||||
let user_row = UserRow::new(
|
||||
"npub1m2w0ckmkgj4wtvl8muwjynh56j3qd4nddca4exdg4mdrkepvfnhsmusy54".to_string(),
|
||||
"test_user".to_string(),
|
||||
vec![],
|
||||
);
|
||||
|
||||
let nip05 = Nip05Profile::from(user_row);
|
||||
|
||||
dbg!(&nip05);
|
||||
dbg!(serde_json::to_string(&nip05).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_relay_vec() {
|
||||
let relays_raw = "";
|
||||
let relays: Vec<String> = match serde_json::from_str(relays_raw) {
|
||||
Ok(val) => val,
|
||||
Err(_) => vec![],
|
||||
};
|
||||
|
||||
dbg!(relays);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,19 +6,24 @@ use warp::{Filter, Rejection, Reply};
|
|||
pub fn routes(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
let cors = warp::cors().allow_any_origin();
|
||||
|
||||
static_files().or(index(context)).with(cors)
|
||||
static_files().or(index(context)).with(&cors)
|
||||
}
|
||||
|
||||
fn index(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
// let real_client_ip = warp::header::optional::<std::net::SocketAddr>("X-Real-IP");
|
||||
let real_client_ip = warp::addr::remote();
|
||||
let cors = warp::cors().allow_any_origin();
|
||||
|
||||
let relay_information_document_path = warp::path::end().and(warp::header::header("Accept").and(with_context(context.clone())).and_then(handler::relay_config)).with(&cors);
|
||||
let nostr_relay_path = warp::path::end().and(warp::ws().and(with_context(context.clone()))
|
||||
.and(real_client_ip)
|
||||
.and_then(handler::ws_handler)
|
||||
.with(&cors));
|
||||
let relay_information_document_path = warp::path::end().and(
|
||||
warp::header::header("Accept")
|
||||
.and(with_context(context.clone()))
|
||||
.and_then(handler::relay_config),
|
||||
);
|
||||
let nostr_relay_path = warp::path::end().and(
|
||||
warp::ws()
|
||||
.and(with_context(context.clone()))
|
||||
.and(real_client_ip)
|
||||
.and_then(handler::ws_handler),
|
||||
);
|
||||
|
||||
relay_information_document_path.or(nostr_relay_path)
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ pub struct Nip05 {
|
|||
#[derive(Serialize, Deserialize, Debug, Validate, Clone)]
|
||||
pub struct UserQuery {
|
||||
#[validate(length(min = 1))]
|
||||
pub user: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Validate)]
|
||||
|
|
|
@ -45,14 +45,9 @@ pub async fn get_account(
|
|||
}
|
||||
|
||||
pub async fn get_user(user_query: UserQuery, context: Context) -> Result<impl Reply, Rejection> {
|
||||
let name = user_query.user;
|
||||
let mut subscriber = context.pubsub.subscribe(channels::MSG_NIP05).await;
|
||||
|
||||
let user = User {
|
||||
name: Some(name),
|
||||
pubkey: None,
|
||||
};
|
||||
let command = Command::DbReqGetUser(user);
|
||||
let command = Command::DbReqGetUser(user_query.name);
|
||||
context
|
||||
.pubsub
|
||||
.publish(
|
||||
|
@ -65,18 +60,12 @@ pub async fn get_user(user_query: UserQuery, context: Context) -> Result<impl Re
|
|||
.await;
|
||||
|
||||
if let Ok(message) = subscriber.recv().await {
|
||||
let mut response = json!({"names": {}, "relays": {}});
|
||||
match message.content {
|
||||
Command::DbResUser(user) => {
|
||||
response = json!({
|
||||
"names": {
|
||||
user.username: user.pubkey
|
||||
},
|
||||
"relays": {}
|
||||
});
|
||||
Command::DbResUser(profile) => {
|
||||
let response = serde_json::to_value(profile).unwrap();
|
||||
Ok(warp::reply::json(&response))
|
||||
}
|
||||
Command::ServiceError(e) => Ok(warp::reply::json(&response)),
|
||||
Command::ServiceError(e) => Err(warp::reject::custom(e)),
|
||||
_ => Err(warp::reject::custom(Error::internal_with_message(
|
||||
"Unhandeled message type",
|
||||
))),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
mod accounts;
|
||||
// mod accounts;
|
||||
pub mod dto;
|
||||
mod filter;
|
||||
mod handler;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::noose::user::User;
|
||||
|
||||
use super::accounts::create_account;
|
||||
// use super::accounts::create_account;
|
||||
use super::dto::{Account, UserQuery};
|
||||
use super::filter::{validate_body_filter, validate_query_filter};
|
||||
use super::handler::{get_account, get_user};
|
||||
|
@ -9,41 +9,49 @@ use crate::utils::structs::Context;
|
|||
use warp::{Filter, Rejection, Reply};
|
||||
|
||||
pub fn routes(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
let cors = warp::cors().allow_any_origin();
|
||||
let index = warp::path::end().map(|| warp::reply::html("<h1>SNEED!</h1>"));
|
||||
|
||||
index
|
||||
.or(nip05_get(context.clone()))
|
||||
.or(account_create(context.clone()))
|
||||
// .or(account_create(context.clone()))
|
||||
.with(&cors)
|
||||
}
|
||||
|
||||
fn well_known() -> impl Filter<Extract = (), Error = Rejection> + Clone {
|
||||
warp::get().and(warp::path(".well-known"))
|
||||
}
|
||||
|
||||
fn nostr_well_known() -> impl Filter<Extract = (), Error = Rejection> + Clone {
|
||||
well_known().and(warp::path("nostr.json"))
|
||||
}
|
||||
|
||||
pub fn nip05_get(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
warp::get()
|
||||
.and(warp::path(".well-known"))
|
||||
.and(warp::path("nostr.json"))
|
||||
nostr_well_known()
|
||||
.and(validate_query_filter::<UserQuery>())
|
||||
.and(with_context(context))
|
||||
.and(with_context(context.clone()))
|
||||
.and_then(get_user)
|
||||
}
|
||||
|
||||
pub fn account_create(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
warp::path("account")
|
||||
.and(warp::post())
|
||||
.and(validate_body_filter::<User>())
|
||||
.and(with_context(context))
|
||||
.and_then(create_account)
|
||||
}
|
||||
// pub fn account_create(
|
||||
// context: Context,
|
||||
// ) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
// warp::path("account")
|
||||
// .and(warp::post())
|
||||
// .and(validate_body_filter::<User>())
|
||||
// .and(with_context(context))
|
||||
// .and_then(create_account)
|
||||
// }
|
||||
|
||||
pub fn account_get(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
warp::path("account")
|
||||
.and(warp::get())
|
||||
.and(validate_body_filter::<Account>())
|
||||
.and(with_context(context))
|
||||
.and_then(get_account)
|
||||
}
|
||||
// pub fn account_get(
|
||||
// context: Context,
|
||||
// ) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
// warp::path("account")
|
||||
// .and(warp::get())
|
||||
// .and(validate_body_filter::<Account>())
|
||||
// .and(with_context(context))
|
||||
// .and_then(get_account)
|
||||
// }
|
||||
|
||||
// pub fn account_update(
|
||||
// context: Context,
|
||||
|
|
|
@ -43,7 +43,7 @@ impl Config {
|
|||
"contact": "klink@zhitno.st",
|
||||
"name": "zhitno.st",
|
||||
"description": "Very *special* nostr relay",
|
||||
"supported_nips": [ 1, 2, 9, 11, 12, 15, 16, 20, 22, 28, 33, 40, 45 ],
|
||||
"supported_nips": [ 1, 2, 9, 11, 12, 15, 16, 20, 22, 28, 33, 40, 45, 50 ],
|
||||
"software": "git+https://git.zhitno.st/Klink/sneedstr.git",
|
||||
"version": "0.1.1"
|
||||
})
|
||||
|
|
|
@ -8,13 +8,13 @@ use std::{
|
|||
use validator::ValidationErrors;
|
||||
use warp::{http::StatusCode, reject::Reject};
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Error {
|
||||
pub code: u16,
|
||||
pub message: String,
|
||||
/// Sneedstr version.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub sneedstr_version: Option<u16>,
|
||||
pub sneedstr_version: String,
|
||||
}
|
||||
|
||||
impl StdError for Error {
|
||||
|
@ -32,7 +32,7 @@ impl Error {
|
|||
Self {
|
||||
code: code.as_u16(),
|
||||
message,
|
||||
sneedstr_version: None,
|
||||
sneedstr_version: VERSION.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,12 +54,11 @@ impl Error {
|
|||
Self::new(StatusCode::BAD_REQUEST, message)
|
||||
}
|
||||
|
||||
pub fn not_found<S: Display>(resource: &str, identifier: S, service_version: u16) -> Self {
|
||||
pub fn not_found<S: Display>(resource: &str, identifier: S) -> Self {
|
||||
Self::new(
|
||||
StatusCode::NOT_FOUND,
|
||||
format!("{} not found by {}", resource, identifier),
|
||||
)
|
||||
.sneedstr_version(service_version)
|
||||
}
|
||||
|
||||
pub fn invalid_param<S: Display>(name: &str, value: S) -> Self {
|
||||
|
@ -77,11 +76,6 @@ impl Error {
|
|||
pub fn status_code(&self) -> StatusCode {
|
||||
StatusCode::from_u16(self.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
pub fn sneedstr_version(mut self, service_version: u16) -> Self {
|
||||
self.sneedstr_version = Some(service_version);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
|
@ -116,28 +110,36 @@ mod tests {
|
|||
#[test]
|
||||
fn test_to_string() {
|
||||
let err = Error::new(StatusCode::BAD_REQUEST, "invalid address".to_owned());
|
||||
assert_eq!(err.to_string(), "400 Bad Request: invalid address")
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"Error { code: 400, message: 'invalid address', sneedstr_version: \"0.1.1\" }"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_anyhow_error_as_internal_error() {
|
||||
let err = Error::from(anyhow::format_err!("hello"));
|
||||
assert_eq!(err.to_string(), "500 Internal Server Error: hello")
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"Error { code: 500, message: 'hello', sneedstr_version: \"0.1.1\" }"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_string_with_sneedstr_version() {
|
||||
let err =
|
||||
Error::new(StatusCode::BAD_REQUEST, "invalid address".to_owned()).sneedstr_version(123);
|
||||
let err = Error::new(StatusCode::BAD_REQUEST, "invalid address".to_owned());
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"400 Bad Request: invalid address\ndiem ledger version: 123"
|
||||
"Error { code: 400, message: 'invalid address', sneedstr_version: \"0.1.1\" }"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_internal_error() {
|
||||
let err = Error::internal(anyhow::format_err!("hello"));
|
||||
assert_eq!(err.to_string(), "500 Internal Server Error: hello")
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"Error { code: 500, message: 'hello', sneedstr_version: \"0.1.1\" }"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue