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