Compare commits
No commits in common. "61a3c10377495db325bb2371276423241b9a940c" and "56dfa16b84098cd29de2dc5fa5adf0ce5fcd0df6" have entirely different histories.
61a3c10377
...
56dfa16b84
15 changed files with 215 additions and 218 deletions
63
Cargo.lock
generated
63
Cargo.lock
generated
|
@ -247,17 +247,11 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.3"
|
||||
version = "4.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813"
|
||||
checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
@ -265,9 +259,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.2"
|
||||
version = "4.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
||||
checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
@ -277,9 +271,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.3"
|
||||
version = "4.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f"
|
||||
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
|
@ -517,15 +511,15 @@ checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
|||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.3"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
|
@ -792,13 +786,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.28.0"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
@ -824,9 +817,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.1"
|
||||
version = "0.32.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
|
||||
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
@ -939,9 +932,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.26"
|
||||
version = "0.11.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2"
|
||||
checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"base64",
|
||||
|
@ -1074,18 +1067,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.197"
|
||||
version = "1.0.196"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.197"
|
||||
version = "1.0.196"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1094,9 +1087,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.114"
|
||||
version = "1.0.113"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
|
||||
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -1220,9 +1213,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.10.1"
|
||||
version = "3.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
|
||||
checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
|
@ -1232,18 +1225,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.58"
|
||||
version = "1.0.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.58"
|
||||
version = "1.0.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -16,15 +16,15 @@ tokio = { version = "1.36.0", features = ["full"], optional = true }
|
|||
# dashmap = { version = "5.5.3", features = ["inline"]}
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde_json = "1.0.114"
|
||||
thiserror = "1.0.58"
|
||||
clap = { version = "4.5.3", features = ["derive"]}
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde_json = "1.0.113"
|
||||
thiserror = "1.0.57"
|
||||
clap = { version = "4.5.0", features = ["derive"]}
|
||||
axum = { version = "0.7.4", optional = true }
|
||||
reqwest = { version = "0.11.26", optional = true, default-features = false, features = ["rustls-tls", "blocking", "json", "gzip", "brotli", "deflate"] }
|
||||
tempfile = { version = "3.10.1", optional = true }
|
||||
reqwest = { version = "0.11.24", optional = true, default-features = false, features = ["rustls-tls", "blocking", "json", "gzip", "brotli", "deflate"] }
|
||||
tempfile = { version = "3.10.0", optional = true }
|
||||
which = { version = "6.0.0", optional = true }
|
||||
nix = { version = "0.28.0", optional = true, default-features = false, features = ["signal"] }
|
||||
nix = { version = "0.27.1", optional = true, default-features = false, features = ["signal"] }
|
||||
urlencoding = { version = "2.1.3"}
|
||||
|
||||
[profile.release] # 💛 @Ryze@equestria.social
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
//! Commands to run from the command line other than launching a server instance
|
||||
pub mod editor;
|
||||
pub mod fetch;
|
||||
pub mod init;
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
//! Command for editing the record associated with a provided query string
|
||||
//! in the system-defined editor
|
||||
|
||||
use crate::args_parser::SaveSettings;
|
||||
use crate::editor::open_in_editor::open_resource_in_editor;
|
||||
use crate::schema::lookup_handler::LookupHandler;
|
||||
|
@ -8,12 +5,7 @@ use crate::schema::resource::Resource;
|
|||
use std::path::PathBuf;
|
||||
use tracing::{error, info};
|
||||
|
||||
pub fn editor(
|
||||
database_path: PathBuf,
|
||||
save_settings: SaveSettings,
|
||||
resource_lookup: String,
|
||||
create_new_resource: bool,
|
||||
) {
|
||||
pub fn editor(database_path: PathBuf, save_settings: SaveSettings, resource_lookup: String, create_new_resource: bool) {
|
||||
let resources = LookupHandler::load(&database_path).unwrap();
|
||||
|
||||
let (index, resource) = match resources.lookup_with_index(resource_lookup.as_str()) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::args_parser::SaveSettings;
|
||||
use crate::editor::finger_remote::finger_many_fedi;
|
||||
use crate::schema::resource_list::ResourceList;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::PathBuf;
|
||||
use tracing::{debug, error, info, instrument, trace, warn};
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
//! Utilities for running a WebFinger query against a remote target
|
||||
|
||||
use crate::editor::finger_remote::FediFingerError::UrlBuildFailed;
|
||||
use crate::schema::resource::Resource;
|
||||
use reqwest::blocking::Client;
|
||||
|
@ -8,7 +6,7 @@ use std::fmt::Display;
|
|||
use thiserror::Error;
|
||||
use tracing::{debug, error, instrument};
|
||||
|
||||
/// Error type returned by [`finger_url`]
|
||||
/// Error type returned by `finger_url`
|
||||
#[derive(Debug, Error)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum FingerError {
|
||||
|
@ -20,7 +18,7 @@ pub enum FingerError {
|
|||
ParseFailed(reqwest::Error),
|
||||
}
|
||||
|
||||
/// Run a WebFinger request at the provided URL and parse it as a [`Resource`]
|
||||
/// Run a WebFinger request at the provided URL and parse it as a `Resource`
|
||||
#[instrument(skip(client))]
|
||||
pub fn finger_url(client: &Client, url: Url) -> Result<Resource, FingerError> {
|
||||
use FingerError::*;
|
||||
|
@ -33,7 +31,7 @@ pub fn finger_url(client: &Client, url: Url) -> Result<Resource, FingerError> {
|
|||
Ok(response)
|
||||
}
|
||||
|
||||
/// Error type returned by [`finger_fedi`]
|
||||
/// Error type returned by `finger_fedi`
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FediFingerError {
|
||||
#[error("Couldn't find the middle @ symbol")]
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
//! Utilities for editing records in a database stored on disk
|
||||
pub mod commands;
|
||||
pub mod finger_remote;
|
||||
pub mod open_in_editor;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
//! Utilities for opening a string in the system-defined editor
|
||||
|
||||
use crate::schema::resource::Resource;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
@ -8,7 +6,7 @@ use tempfile::NamedTempFile;
|
|||
use thiserror::Error;
|
||||
use tracing::{debug, instrument, trace, warn};
|
||||
|
||||
/// Error type returned by [`spawn_editor`]
|
||||
/// Error type returned by `spawn_editor`
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EditorSpawnError {
|
||||
#[error("failed to parse out absolute path of editor: {0}")]
|
||||
|
@ -29,8 +27,7 @@ pub enum EditorSpawnError {
|
|||
BufferReadbackFailed(std::io::Error),
|
||||
}
|
||||
|
||||
/// Spawn the system editor to edit the provided `buffer`.
|
||||
/// Returns the edited buffer or an error if something goes wrong
|
||||
/// Spawn the system editor to edit the provided `buffer`. Returns the edited buffer or an error if something goes wrong
|
||||
#[instrument(skip_all)]
|
||||
pub fn spawn_editor(buffer: impl AsRef<str> + Display) -> Result<String, EditorSpawnError> {
|
||||
use EditorSpawnError::*;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
//! Utility functions for attempting to reload a running server instance of the application
|
||||
|
||||
use crate::args_parser::ServerReloadOptions;
|
||||
use nix::sys::signal::{kill, Signal};
|
||||
use nix::unistd::Pid;
|
||||
|
@ -9,9 +7,8 @@ use std::str::FromStr;
|
|||
use thiserror::Error;
|
||||
use tracing::{error, info};
|
||||
|
||||
/// Error type returned by [`try_reload_server`] and [`load_pid`]
|
||||
#[derive(Debug, Error)]
|
||||
enum ServerReloadError {
|
||||
pub enum ServerReloadError {
|
||||
#[error("Couldn't find PID file at {0}")]
|
||||
PIDFileNotFound(String),
|
||||
#[error("Failed to open PID file: {0}")]
|
||||
|
@ -24,7 +21,6 @@ enum ServerReloadError {
|
|||
FailedToSendSignal(#[from] nix::errno::Errno),
|
||||
}
|
||||
|
||||
/// Tries to load and parse the PID file at the provided path
|
||||
fn load_pid(pid_file_path: &Path) -> Result<Pid, ServerReloadError> {
|
||||
use ServerReloadError::*;
|
||||
if !pid_file_path.exists() {
|
||||
|
@ -38,14 +34,12 @@ fn load_pid(pid_file_path: &Path) -> Result<Pid, ServerReloadError> {
|
|||
Ok(Pid::from_raw(pid))
|
||||
}
|
||||
|
||||
/// Tries to send a reload command to the server instance
|
||||
fn try_reload_server(pid_file_path: &Path) -> Result<(), ServerReloadError> {
|
||||
let pid = load_pid(pid_file_path)?;
|
||||
kill(pid, Signal::SIGHUP)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempts to reload an already running server instance of the application, logging an error if it was unsuccessful.
|
||||
pub fn reload(pid_file_path: PathBuf, options: ServerReloadOptions) {
|
||||
if options.reload_server {
|
||||
info!("Attempting to reload server...");
|
||||
|
|
22
src/main.rs
22
src/main.rs
|
@ -1,16 +1,16 @@
|
|||
use crate::args_parser::Command;
|
||||
use crate::editor::commands::editor::editor;
|
||||
use crate::editor::commands::init::init;
|
||||
use crate::editor::commands::list::list;
|
||||
use crate::editor::try_reload_server::reload;
|
||||
use clap::Parser;
|
||||
use tracing::error;
|
||||
use editor::commands::fetch::fetch;
|
||||
use editor::commands::query::query;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
mod args_parser;
|
||||
#[cfg(feature = "editor")]
|
||||
mod editor;
|
||||
#[cfg(feature = "editor")]
|
||||
use editor::{
|
||||
commands::{editor::editor, fetch::fetch, init::init, list::list, query::query},
|
||||
try_reload_server::reload,
|
||||
};
|
||||
mod schema;
|
||||
#[cfg(feature = "server")]
|
||||
mod server;
|
||||
|
@ -30,7 +30,6 @@ fn main() {
|
|||
..
|
||||
} = args;
|
||||
match run_mode {
|
||||
#[allow(unused_variables)] // For when the server feature is compiled out
|
||||
Command::Serve(params) => {
|
||||
#[cfg(not(feature = "server"))]
|
||||
{
|
||||
|
@ -39,7 +38,6 @@ fn main() {
|
|||
#[cfg(feature = "server")]
|
||||
server::init(data_paths, params);
|
||||
}
|
||||
#[cfg(feature = "editor")]
|
||||
Command::Fetch {
|
||||
save,
|
||||
handles,
|
||||
|
@ -56,7 +54,6 @@ fn main() {
|
|||
);
|
||||
reload(data_paths.pid_file_path, server_reload);
|
||||
}
|
||||
#[cfg(feature = "editor")]
|
||||
Command::Query {
|
||||
save,
|
||||
resource,
|
||||
|
@ -67,7 +64,6 @@ fn main() {
|
|||
|
||||
reload(data_paths.pid_file_path, server_reload);
|
||||
}
|
||||
#[cfg(feature = "editor")]
|
||||
Command::Editor {
|
||||
save,
|
||||
resource,
|
||||
|
@ -78,17 +74,11 @@ fn main() {
|
|||
|
||||
reload(data_paths.pid_file_path, server_reload);
|
||||
}
|
||||
#[cfg(feature = "editor")]
|
||||
Command::Init { force } => {
|
||||
init(data_paths.database_path, force);
|
||||
}
|
||||
#[cfg(feature = "editor")]
|
||||
Command::List {} => {
|
||||
list(data_paths.database_path);
|
||||
}
|
||||
#[allow(unreachable_patterns)] // This is a catch-all if the editor feature is disabled
|
||||
_ => {
|
||||
error!("The requested command is not supported by this build. Please rebuild with the \"editor\" feature enabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
//! Struct for storing WebFinger resources and running queries against them
|
||||
|
||||
use crate::schema::resource::Resource;
|
||||
use crate::schema::resource_list::{ResourceList, ResourceLoadError};
|
||||
use std::collections::HashMap;
|
||||
|
@ -27,31 +25,25 @@ pub enum DataLoadError {
|
|||
}
|
||||
|
||||
impl LookupHandler {
|
||||
/// Load and prepare a new [`LookupHandler`] from the file at `path`
|
||||
/// Load and prepare a new LookupHandler from the file at `path`
|
||||
#[instrument(level = "debug", skip(path))]
|
||||
pub fn load(path: impl AsRef<Path> + Debug) -> Result<Self, DataLoadError> {
|
||||
Self::build_from_resource_list(ResourceList::load(path)?)
|
||||
}
|
||||
|
||||
/// Load and prepare a new [`LookupHandler`] from the provided reader
|
||||
#[cfg(test)]
|
||||
fn load_from_reader(reader: impl std::io::Read) -> Result<Self, DataLoadError> {
|
||||
Self::build_from_resource_list(ResourceList::load_from_reader(reader)?)
|
||||
}
|
||||
|
||||
/// Build a new [`LookupHandler`] from the provided [`ResourceList`]
|
||||
fn build_from_resource_list(resources: ResourceList) -> Result<Self, DataLoadError> {
|
||||
debug!("Building lookup map...");
|
||||
|
||||
let mut lookup = HashMap::new();
|
||||
debug!("Building lookup map...");
|
||||
for (index, resource) in resources.0.iter().enumerate() {
|
||||
for lookup_to_add in resource.keys() {
|
||||
let lookup_to_add = lookup_to_add.to_lowercase();
|
||||
|
||||
debug!("Adding {lookup_to_add} for {}", resource.subject);
|
||||
let duplicate = lookup.insert(lookup_to_add.clone(), index);
|
||||
|
||||
if let Some(duplicate) = duplicate {
|
||||
if let Some(duplicate) = lookup.insert(lookup_to_add.clone(), index) {
|
||||
return Err(DataLoadError::DuplicateLookupFound {
|
||||
duplicated: lookup_to_add,
|
||||
subjects: [
|
||||
|
@ -62,33 +54,24 @@ impl LookupHandler {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("Aggregated {} lookup strings", lookup.len());
|
||||
Ok(LookupHandler { resources, lookup })
|
||||
}
|
||||
|
||||
/// Run the provided `resource_query` on the index and return a borrow of the returned resource
|
||||
#[instrument(level = "debug")]
|
||||
pub fn lookup(&self, resource_query: &str) -> Option<&Resource> {
|
||||
self.lookup_with_index(resource_query)
|
||||
.map(|(_index, resource)| resource)
|
||||
}
|
||||
|
||||
/// Run the provided `resource_query` on the index and
|
||||
/// return a borrow of the returned resource and its index in the database
|
||||
pub fn lookup_with_index(&self, resource_query: &str) -> Option<(usize, &Resource)> {
|
||||
let resource_query = resource_query.to_lowercase();
|
||||
|
||||
let resource_index = *self.lookup.get(resource_query.as_str())?;
|
||||
|
||||
let found_resource = &self.resources.0[resource_index];
|
||||
|
||||
debug!("Lookup for {resource_query} returned {found_resource:?}");
|
||||
Some((resource_index, found_resource))
|
||||
}
|
||||
|
||||
/// Convert the [`LookupHandler`] into its internal [`ResourceList`]
|
||||
#[allow(dead_code)]
|
||||
pub fn into_inner(self) -> ResourceList {
|
||||
self.resources
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
//! "Schema" of the internal representation of WebFinger data
|
||||
|
||||
pub mod lookup_handler;
|
||||
pub mod resource;
|
||||
pub mod resource_list;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
//! A single WebFinger resource and associated utility functions
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
|
@ -8,15 +6,11 @@ use tracing::debug;
|
|||
/// A single WebFinger resource
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Resource {
|
||||
/// The subject of this resource
|
||||
pub subject: String,
|
||||
/// Known aliases of the resource to also respond to
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub aliases: Option<Vec<String>>,
|
||||
/// List of properties associated with the resource
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub properties: Option<HashMap<String, Option<String>>>,
|
||||
/// Links associated with the resource
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub links: Option<Vec<Link>>,
|
||||
}
|
||||
|
@ -29,7 +23,6 @@ impl Display for Resource {
|
|||
}
|
||||
|
||||
impl Resource {
|
||||
/// Creates a new completely blank WebFinger resource with the provided subject
|
||||
pub fn new(subject: String) -> Self {
|
||||
Self {
|
||||
subject,
|
||||
|
@ -40,7 +33,7 @@ impl Resource {
|
|||
}
|
||||
|
||||
/// Returns the aliases of the given record. If the `aliases` field is
|
||||
/// entirely missing, returns an empty array.
|
||||
/// entirely missing, returns &[].
|
||||
pub fn keys(&self) -> impl Iterator<Item = &String> {
|
||||
let aliases = if let Some(aliases) = &self.aliases {
|
||||
aliases.as_slice()
|
||||
|
@ -51,18 +44,13 @@ impl Resource {
|
|||
aliases.iter().chain(std::iter::once(&self.subject))
|
||||
}
|
||||
|
||||
/// Replaces the current `subject` field of the WebFinger resource
|
||||
/// with a new value and pushes the old value into the aliases field
|
||||
/// if it is not already present there
|
||||
pub fn add_new_primary_subject(&mut self, mut subject: String) {
|
||||
if subject == self.subject {
|
||||
debug!("New and old subjects match, skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
debug!("Swapping new and old subject");
|
||||
std::mem::swap(&mut subject, &mut self.subject);
|
||||
|
||||
debug!("Pushing current subject into aliases");
|
||||
if let Some(ref mut aliases) = self.aliases {
|
||||
if !aliases.contains(&subject) {
|
||||
|
@ -80,45 +68,27 @@ impl Resource {
|
|||
/// and instead replaced with `Some(Default::default())`. Useful to provide an easier to
|
||||
/// extend version of the data for manual editing.
|
||||
pub fn as_completely_serializable(&self) -> Self {
|
||||
let clone_hashmap_with_option_value_as_complete =
|
||||
|props: &HashMap<String, Option<String>>| {
|
||||
HashMap::from_iter(
|
||||
props
|
||||
.iter()
|
||||
.map(|(key, value)| (key.clone(), Some(value.clone().unwrap_or_default()))),
|
||||
)
|
||||
};
|
||||
let clone_hashmap_with_option_value_as_complete = |props: &HashMap<String, Option<String>>| {
|
||||
HashMap::from_iter(props.iter().map(|(key, value)| {
|
||||
(key.clone(), Some(value.clone().unwrap_or_default()))
|
||||
}))
|
||||
};
|
||||
|
||||
Self {
|
||||
subject: self.subject.clone(),
|
||||
aliases: Some(self.aliases.clone().unwrap_or_default()),
|
||||
properties: Some(
|
||||
self.properties
|
||||
.as_ref()
|
||||
.map(clone_hashmap_with_option_value_as_complete)
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
links: Some(
|
||||
self.links
|
||||
.as_ref()
|
||||
.map(|links| {
|
||||
Vec::from_iter(links.iter().map(|link| {
|
||||
Link {
|
||||
rel: link.rel.clone(),
|
||||
media_type: Some(link.media_type.clone().unwrap_or_default()),
|
||||
href: Some(link.href.clone().unwrap_or_default()),
|
||||
titles: Some(link.titles.clone().unwrap_or_default()),
|
||||
properties: Some(
|
||||
link.properties
|
||||
.as_ref()
|
||||
.map(clone_hashmap_with_option_value_as_complete)
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
}
|
||||
}))
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
properties: Some(self.properties.as_ref().map(clone_hashmap_with_option_value_as_complete).unwrap_or_default()),
|
||||
links: Some(self.links.as_ref().map(|links| {
|
||||
Vec::from_iter(links.iter().map(|link| {
|
||||
Link {
|
||||
rel: link.rel.clone(),
|
||||
media_type: Some(link.media_type.clone().unwrap_or_default()),
|
||||
href: Some(link.href.clone().unwrap_or_default()),
|
||||
titles: Some(link.titles.clone().unwrap_or_default()),
|
||||
properties: Some(link.properties.as_ref().map(clone_hashmap_with_option_value_as_complete).unwrap_or_default()),
|
||||
}
|
||||
}))
|
||||
}).unwrap_or_default()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,50 +99,32 @@ impl Resource {
|
|||
Self {
|
||||
subject: self.subject,
|
||||
aliases: self.aliases.filter(|data| !data.is_empty()),
|
||||
properties: self
|
||||
.properties
|
||||
.filter(|data| !data.is_empty())
|
||||
.map(|mut data| {
|
||||
for value in data.values_mut() {
|
||||
if let Some(ref mut string) = value {
|
||||
if string.is_empty() {
|
||||
*value = None;
|
||||
}
|
||||
properties: self.properties.filter(|data| !data.is_empty()).map(|mut data| {
|
||||
for value in data.values_mut() {
|
||||
if let Some(ref mut string) = value {
|
||||
if string.is_empty() {
|
||||
*value = None;
|
||||
}
|
||||
}
|
||||
data
|
||||
}),
|
||||
links: self
|
||||
.links
|
||||
.filter(|links| !links.is_empty())
|
||||
.map(|mut links| {
|
||||
links.retain(|link| {
|
||||
// Empty `rel` is invalid, but short-circuiting here would delete records
|
||||
// that are only partially edited. Better to store invalid data than to delete
|
||||
// users' work.
|
||||
let mut is_default = link.rel.is_empty();
|
||||
is_default &= link
|
||||
.media_type
|
||||
.as_ref()
|
||||
.filter(|media_type| !media_type.is_empty())
|
||||
.is_none();
|
||||
is_default &= link.href.as_ref().filter(|href| !href.is_empty()).is_none();
|
||||
is_default &= link
|
||||
.titles
|
||||
.as_ref()
|
||||
.filter(|titles| !titles.is_empty())
|
||||
.is_none();
|
||||
is_default &= link
|
||||
.properties
|
||||
.as_ref()
|
||||
.filter(|titles| !titles.is_empty())
|
||||
.is_none();
|
||||
}
|
||||
data
|
||||
}),
|
||||
links: self.links.filter(|links| !links.is_empty()).map(|mut links| {
|
||||
links.retain(|link| {
|
||||
// Empty `rel` is invalid, but short-circuiting here would delete records
|
||||
// that are only partially edited. Better to store invalid data than to delete
|
||||
// users' work.
|
||||
let mut is_default = link.rel.is_empty();
|
||||
is_default &= link.media_type.as_ref().filter(|media_type| !media_type.is_empty()).is_none();
|
||||
is_default &= link.href.as_ref().filter(|href| !href.is_empty()).is_none();
|
||||
is_default &= link.titles.as_ref().filter(|titles| !titles.is_empty()).is_none();
|
||||
is_default &= link.properties.as_ref().filter(|titles| !titles.is_empty()).is_none();
|
||||
|
||||
is_default
|
||||
});
|
||||
is_default
|
||||
});
|
||||
|
||||
links
|
||||
}),
|
||||
links
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,13 +147,14 @@ pub struct Link {
|
|||
/// Functions to generate data for testing functions that manipulate `Resource` structs
|
||||
pub mod test_data {
|
||||
use crate::schema::resource::Resource;
|
||||
|
||||
/// A [`Resource`] with only the `subject` field set
|
||||
pub fn barebones_user() -> Resource {
|
||||
Resource::new("acct:user@domain.tld".to_string())
|
||||
Resource {
|
||||
subject: "acct:user@domain.tld".to_string(),
|
||||
aliases: None,
|
||||
properties: None,
|
||||
links: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// A default [`Resource`] with a single alias
|
||||
pub fn user_with_single_alias() -> Resource {
|
||||
Resource {
|
||||
subject: "acct:user@domain.tld".to_string(),
|
||||
|
@ -211,7 +164,6 @@ pub mod test_data {
|
|||
}
|
||||
}
|
||||
|
||||
/// A default [`Resource`] with a single alias that matches the subject
|
||||
pub fn user_with_matching_subject_and_alias() -> Resource {
|
||||
Resource {
|
||||
subject: "acct:user@domain.tld".to_string(),
|
||||
|
@ -287,7 +239,7 @@ mod tests {
|
|||
for data in [
|
||||
test_data::barebones_user(),
|
||||
test_data::user_with_matching_subject_and_alias(),
|
||||
test_data::user_with_single_alias(),
|
||||
test_data::user_with_single_alias()
|
||||
] {
|
||||
assert_eq!(data, data.as_completely_serializable().compress());
|
||||
}
|
||||
|
|
112
src/schema/resource_complete_serialisation.rs
Normal file
112
src/schema/resource_complete_serialisation.rs
Normal file
|
@ -0,0 +1,112 @@
|
|||
use crate::schema::resource::{Link, Resource};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Same as `Resource`, but doesn't have any of the skip_serializing_if fields and options set so the structure is more friendly to editing through the `editor` command
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ResourceComplete {
|
||||
subject: String,
|
||||
aliases: Vec<String>,
|
||||
properties: HashMap<String, Option<String>>,
|
||||
links: Vec<LinkComplete>,
|
||||
}
|
||||
|
||||
impl ResourceComplete {
|
||||
pub fn from_resource(value: Resource) -> Self {
|
||||
let Resource {
|
||||
subject,
|
||||
aliases,
|
||||
properties,
|
||||
links,
|
||||
} = value;
|
||||
Self {
|
||||
subject,
|
||||
aliases: aliases.unwrap_or_default(),
|
||||
properties: properties.unwrap_or_default(),
|
||||
links: links
|
||||
.map(|vec| vec.into_iter().map(LinkComplete::from_link).collect())
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_resource(self) -> Resource {
|
||||
let ResourceComplete {
|
||||
subject,
|
||||
aliases,
|
||||
properties,
|
||||
links,
|
||||
} = self;
|
||||
Resource {
|
||||
subject,
|
||||
aliases: if aliases.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(aliases)
|
||||
},
|
||||
properties: if properties.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(properties)
|
||||
},
|
||||
links: if links.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(links.into_iter().map(LinkComplete::into_link).collect())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `Link`, but doesn't have any of the skip_serializing_if fields set so the structure is more friendly to editing through the `editor` command
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct LinkComplete {
|
||||
rel: String,
|
||||
media_type: Option<String>,
|
||||
href: Option<String>,
|
||||
titles: HashMap<String, String>,
|
||||
properties: HashMap<String, Option<String>>,
|
||||
}
|
||||
|
||||
impl LinkComplete {
|
||||
fn from_link(value: Link) -> Self {
|
||||
let Link {
|
||||
rel,
|
||||
media_type,
|
||||
href,
|
||||
titles,
|
||||
properties,
|
||||
} = value;
|
||||
Self {
|
||||
rel,
|
||||
media_type,
|
||||
href,
|
||||
titles: titles.unwrap_or_default(),
|
||||
properties: properties.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_link(self) -> Link {
|
||||
let LinkComplete {
|
||||
rel,
|
||||
media_type,
|
||||
href,
|
||||
titles,
|
||||
properties,
|
||||
} = self;
|
||||
Link {
|
||||
rel,
|
||||
media_type,
|
||||
href,
|
||||
titles: if titles.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(titles)
|
||||
},
|
||||
properties: if properties.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(properties)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
//! An array of [`Resource`]s with a builtin way to serialise and deserialise to file and/or stream
|
||||
|
||||
use crate::args_parser::CollisionHandling;
|
||||
use crate::schema::resource::Resource;
|
||||
use std::collections::HashSet;
|
||||
|
@ -8,11 +6,9 @@ use std::path::Path;
|
|||
use thiserror::Error;
|
||||
use tracing::{debug, info, instrument, trace, warn};
|
||||
|
||||
/// A dynamic array of [`Resource`]s
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResourceList(pub Vec<Resource>);
|
||||
|
||||
/// Error type returned by [`ResourceList::load`] and [`ResourceList::load_from_reader`]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ResourceLoadError {
|
||||
#[error("failed to open the resource database: {0}")]
|
||||
|
@ -21,9 +17,7 @@ pub enum ResourceLoadError {
|
|||
FileParse(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
/// Error type returned by [`ResourceList::save`] and [`ResourceList::save_to_writer`]
|
||||
#[derive(Debug, Error)]
|
||||
#[allow(unused)]
|
||||
pub enum ResourceSaveError {
|
||||
#[error("Failed to open the resource database for writing: {0}")]
|
||||
FileOpen(std::io::Error),
|
||||
|
@ -34,7 +28,7 @@ pub enum ResourceSaveError {
|
|||
}
|
||||
|
||||
impl ResourceList {
|
||||
/// Loads the [`Resource`]s from the given `path`
|
||||
/// Loads the `Resource`s from the given `path`
|
||||
#[instrument(level = "debug")]
|
||||
pub fn load(path: impl AsRef<Path> + Debug) -> Result<Self, ResourceLoadError> {
|
||||
info!("Loading data from {path:?}...");
|
||||
|
@ -42,7 +36,7 @@ impl ResourceList {
|
|||
Self::load_from_reader(file)
|
||||
}
|
||||
|
||||
/// Loads the [`Resource`]s from the given reader
|
||||
/// Loads the `Resource`s from the given reader
|
||||
pub fn load_from_reader(reader: impl std::io::Read) -> Result<Self, ResourceLoadError> {
|
||||
let reader = std::io::BufReader::new(reader);
|
||||
debug!("Parsing as JSON...");
|
||||
|
@ -52,10 +46,8 @@ impl ResourceList {
|
|||
Ok(Self(resources))
|
||||
}
|
||||
|
||||
/// Save the [`Resource`]s to the given `path`, backing up the file if it is already present
|
||||
/// to `path.bak`
|
||||
#[instrument(level = "debug", skip(path, self))]
|
||||
pub fn save(&self, path: impl AsRef<Path>) -> Result<(), ResourceSaveError> {
|
||||
pub fn save(&self, path: impl AsRef<Path> + Debug) -> Result<(), ResourceSaveError> {
|
||||
info!("Creating backup before writing...");
|
||||
let path = path.as_ref();
|
||||
std::fs::rename(path, path.with_extension("bak"))
|
||||
|
@ -65,7 +57,6 @@ impl ResourceList {
|
|||
self.save_to_writer(file)
|
||||
}
|
||||
|
||||
/// Save the [`Resource`]s into the given `writer`
|
||||
pub fn save_to_writer(&self, writer: impl std::io::Write) -> Result<(), ResourceSaveError> {
|
||||
trace!("{self:?}");
|
||||
let writer = std::io::BufWriter::new(writer);
|
||||
|
@ -73,8 +64,7 @@ impl ResourceList {
|
|||
Ok(serde_json::to_writer(writer, &self.0)?)
|
||||
}
|
||||
|
||||
/// Merges `new_records` into the [`ResourceList`] with lookup collisions
|
||||
/// being handled as defined in `collision_handling`
|
||||
/// Merges `new_records` into the `ResourceList` with lookup collisions being handled as defined in `collision_handling`
|
||||
#[instrument(level = "debug", skip(self, new_records))]
|
||||
pub fn merge_records(
|
||||
&mut self,
|
||||
|
@ -84,7 +74,6 @@ impl ResourceList {
|
|||
debug!("Building hashset of already taken queries...");
|
||||
let unique_check: HashSet<String> =
|
||||
HashSet::from_iter(self.0.iter().flat_map(Resource::keys).cloned());
|
||||
|
||||
for record in new_records {
|
||||
let record_keys = HashSet::from_iter(record.keys().cloned());
|
||||
let collisions = unique_check
|
||||
|
|
Loading…
Reference in a new issue