From e7d2312a1815657cd8f4188bfb254ca6c1817a38 Mon Sep 17 00:00:00 2001 From: Karcsesz Date: Wed, 14 Feb 2024 19:35:19 +0100 Subject: [PATCH] Initial commit --- .gitignore | 5 + Cargo.lock | 1852 +++++++++++++++++++++++++++++++ Cargo.toml | 27 + src/args_parser.rs | 127 +++ src/editor/commands.rs | 3 + src/editor/commands/editor.rs | 16 + src/editor/commands/fetch.rs | 58 + src/editor/commands/query.rs | 10 + src/editor/finger_remote.rs | 75 ++ src/editor/mod.rs | 4 + src/editor/open_in_editor.rs | 82 ++ src/editor/try_reload_server.rs | 50 + src/main.rs | 50 + src/schema/lookup_handler.rs | 143 +++ src/schema/mod.rs | 3 + src/schema/resource.rs | 110 ++ src/schema/resource_list.rs | 115 ++ src/server/mod.rs | 152 +++ 18 files changed, 2882 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/args_parser.rs create mode 100644 src/editor/commands.rs create mode 100644 src/editor/commands/editor.rs create mode 100644 src/editor/commands/fetch.rs create mode 100644 src/editor/commands/query.rs create mode 100644 src/editor/finger_remote.rs create mode 100644 src/editor/mod.rs create mode 100644 src/editor/open_in_editor.rs create mode 100644 src/editor/try_reload_server.rs create mode 100644 src/main.rs create mode 100644 src/schema/lookup_handler.rs create mode 100644 src/schema/mod.rs create mode 100644 src/schema/resource.rs create mode 100644 src/schema/resource_list.rs create mode 100644 src/server/mod.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2bbf8c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target +.idea +*.pid +records.json +records.bak \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..db35839 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1852 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-compression" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.1.0", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "brotli" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fingerlink" +version = "0.1.0" +dependencies = [ + "axum", + "clap", + "nix", + "qpidfile", + "reqwest", + "serde", + "serde_json", + "tempfile", + "thiserror", + "tokio", + "tracing", + "tracing-subscriber", + "which", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "h2" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.11", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 1.0.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.11", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.0.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +dependencies = [ + "bytes", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.24", + "http 0.2.11", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.2", + "http 1.0.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.28", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "hyper 1.1.0", + "pin-project-lite", + "socket2", + "tokio", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qpidfile" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7070aeb856318f50286d9bd36e2035e457668b2f24db0633a5d4045d7b09924" + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "reqwest" +version = "0.11.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +dependencies = [ + "async-compression", + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.24", + "http 0.2.11", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" + +[[package]] +name = "web-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "which" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..19d85ba --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "fingerlink" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["server", "editor"] +server = ["tokio", "qpidfile", "axum"] +editor = ["reqwest", "tempfile", "which", "nix"] + +[dependencies] +qpidfile = { version = "0.9.2", optional = true } +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.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.24", optional = true, features = ["rustls", "blocking", "json", "gzip", "brotli", "deflate"] } +tempfile = { version = "3.10.0", optional = true } +which = { version = "6.0.0", optional = true } +nix = { version = "0.27.1", optional = true, default-features = false, features = ["signal"] } \ No newline at end of file diff --git a/src/args_parser.rs b/src/args_parser.rs new file mode 100644 index 0000000..e0d4103 --- /dev/null +++ b/src/args_parser.rs @@ -0,0 +1,127 @@ +use std::net::IpAddr; +use std::path::PathBuf; +use clap::{Args, Parser, Subcommand, ValueEnum}; +use tracing::Level; + +#[derive(Debug, Parser)] +#[command(name = "Fingerlink", version, about, long_about = None)] +pub struct MainCommand { + #[arg(long, value_enum, default_value = "info")] + pub log_level: LoggingLevel, + #[command(flatten)] + pub data_paths: DataPaths, + #[command(subcommand)] + pub run_mode: Command +} +impl MainCommand { + pub fn log_level(&self) -> Level { + match self.log_level { + LoggingLevel::Trace => Level::TRACE, + LoggingLevel::Debug => Level::DEBUG, + LoggingLevel::Info => Level::INFO, + LoggingLevel::Warning => Level::WARN, + LoggingLevel::Error => Level::ERROR + } + } +} + +#[derive(Args, Debug)] +pub struct DataPaths { + #[arg(long, short = 'd', default_value = "records.json")] + pub database_path: PathBuf, + #[arg(long, default_value = "server.pid")] + pub pid_file_path: PathBuf, +} + + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, ValueEnum)] +pub enum LoggingLevel { + Trace, + Debug, + Info, + Warning, + Error +} + +#[derive(Debug, Clone, Args)] +pub struct ServerParameters { + /// The IP address to bind to + #[arg(long, short = 'b', default_value = "127.0.0.1")] + pub bind_ip: IpAddr, + /// The port to listen on + #[arg(long, short = 'p', default_value = "8080")] + pub bind_port: u16, + /// Whether to ignore the PID file already existing. + #[arg(long)] + pub force_pid: bool, + /// Limits how many rel parameters will be processed in a single query + #[arg(long, default_value = "10")] + pub max_allowed_rels_in_request: usize +} + +#[derive(Debug, Args)] +pub struct SaveSettings { + /// Specify this flag to write the fetched records into the database + #[arg(long, short = 's')] + pub save: bool, + + /// Save behaviour when encountering a collision + #[arg(long, short = 'c', default_value = "overwrite-single-skip-multiple")] + pub collision_handling: CollisionHandling, +} + +#[derive(Debug, Subcommand)] +pub enum Command { + /// Serve the database + Serve (ServerParameters), + /// Fetch one or multiple handles from Fediverse-compatible software + Fetch { + #[command(flatten)] + save: SaveSettings, + /// The handles to process + handles: Vec, + /// Set this flag to treat HANDLES as a list of files that contain the handles to process + #[arg(long)] + handles_are_files: bool, + /// The domain to rewrite the acquired handles' subject domain to. If left unspecified, the domain is kept as-is + #[arg(long)] + new_domain: Option, + #[command(flatten)] + server_reload: ServerReloadOptions, + }, + /// Runs a single query against the database and returns the Resource associated with the value + Query { + /// The resource to query for + resource: String + }, + /// Open the resource in your system editor + Editor { + #[command(flatten)] + save: SaveSettings, + /// Query for the resource to edit + resource: String, + #[command(flatten)] + server_reload: ServerReloadOptions, + } +} + +#[derive(ValueEnum, Debug, Eq, PartialEq, Copy, Clone)] +pub enum CollisionHandling { + /// Terminate import process when encountering a collision. + /// Still inserts resources processed before detecting a collision + Terminate, + /// Skip adding new resource if it would collide with an existing resource + Skip, + /// Only overwrites if there's just a single resource the new item collides with + OverwriteSingleSkipMultiple, + /// Overwrites every already existing resource that the new item collides with + OverwriteMultiple + //TODO: Handlers to remove only the offending aliases, not the whole record? +} + +#[derive(Args, Debug)] +pub struct ServerReloadOptions { + /// Set this flag for the application to attempt to reload the running server process automaticallys + #[arg(long, short = 'r')] + pub reload_server: bool, +} \ No newline at end of file diff --git a/src/editor/commands.rs b/src/editor/commands.rs new file mode 100644 index 0000000..45bbe79 --- /dev/null +++ b/src/editor/commands.rs @@ -0,0 +1,3 @@ +pub mod fetch; +pub mod query; +pub mod editor; diff --git a/src/editor/commands/editor.rs b/src/editor/commands/editor.rs new file mode 100644 index 0000000..8ab43af --- /dev/null +++ b/src/editor/commands/editor.rs @@ -0,0 +1,16 @@ +use std::path::PathBuf; +use crate::args_parser::SaveSettings; +use crate::editor::open_in_editor::open_resource_in_editor; +use crate::schema::lookup_handler::LookupHandler; + +pub fn editor(database_path: PathBuf, save_settings: SaveSettings, resource: String) { + let resources = LookupHandler::load(&database_path).unwrap(); + let (index, resource) = resources.lookup_with_index(resource.as_str()) + .expect("Couldn't find a resource for that query"); + let resource = open_resource_in_editor(resource).unwrap(); + if save_settings.save { + let mut resources = resources.into_inner(); + resources.0[index] = resource; + resources.save(database_path).unwrap(); + } +} \ No newline at end of file diff --git a/src/editor/commands/fetch.rs b/src/editor/commands/fetch.rs new file mode 100644 index 0000000..ac75c8f --- /dev/null +++ b/src/editor/commands/fetch.rs @@ -0,0 +1,58 @@ +use std::io::{BufRead, BufReader}; +use std::path::PathBuf; +use tracing::{debug, error, info, instrument, trace, warn}; +use crate::args_parser::{DataPaths, SaveSettings}; +use crate::editor::finger_remote::finger_many_fedi; +use crate::schema::resource_list::ResourceList; + +#[instrument(skip_all)] +pub fn fetch(database_path: PathBuf, save_settings: SaveSettings, handles: impl Iterator, handles_are_files: bool, new_domain: Option) { + let handles = handles.flat_map(|handle| { + if handles_are_files { + match std::fs::File::open(&handle) { + Ok(file) => { + let mut file = BufReader::new(file); + let mut handles = vec![]; + let mut buf = String::new(); + while file.read_line(&mut buf).is_ok() { + handles.push(buf); + buf = String::new(); + } + info!("Read {} handles from {handle}", handles.len()); + handles + } + Err(e) => { + error!("Skipping file at {handle}: {e}"); + vec![] + } + } + } else {vec![handle]} + }); + let new_resources = finger_many_fedi(handles).map(|mut new_resource| { + info!("Fetched {}", new_resource.subject); + if let Some(new_domain) = &new_domain { + trace!("Processing {new_resource:?}"); + let current_subject = &new_resource.subject; + let (start, old_domain) = match current_subject.rsplit_once('@') { + None => { + warn!("Failed to parse the domain of {current_subject}, storing as-is."); + return new_resource + }, + Some(data) => data + }; + debug!("Replacing {old_domain} with {new_domain}"); + new_resource.add_new_primary_subject(format!("{start}@{new_domain}")); + trace!("{new_resource:?}"); + new_resource + } else { new_resource } + }); + if save_settings.save { + let mut resource_list = ResourceList::load(&database_path).unwrap(); + resource_list.merge_records(new_resources, save_settings.collision_handling); + resource_list.save(database_path).unwrap(); + } else { + for resource in new_resources { + println!("{resource}") + } + } +} \ No newline at end of file diff --git a/src/editor/commands/query.rs b/src/editor/commands/query.rs new file mode 100644 index 0000000..a68dfca --- /dev/null +++ b/src/editor/commands/query.rs @@ -0,0 +1,10 @@ +use std::io::stdout; +use std::path::PathBuf; +use crate::schema::lookup_handler::LookupHandler; + +pub fn query(database_path: PathBuf, handle: String) { + let data = LookupHandler::load(database_path).unwrap(); + let resource = data.lookup(handle.trim()).unwrap(); + serde_json::to_writer_pretty(stdout(), resource).unwrap(); + println!() +} \ No newline at end of file diff --git a/src/editor/finger_remote.rs b/src/editor/finger_remote.rs new file mode 100644 index 0000000..097272f --- /dev/null +++ b/src/editor/finger_remote.rs @@ -0,0 +1,75 @@ +use std::fmt::Display; +use reqwest::blocking::Client; +use reqwest::Url; +use thiserror::Error; +use tracing::{debug, error, instrument}; +use crate::editor::finger_remote::FediFingerError::UrlBuildFailed; +use crate::schema::resource::Resource; + +/// Error type returned by `finger_url` +#[derive(Debug, Error)] +#[allow(clippy::enum_variant_names)] +pub enum FingerError { + #[error("Failed to build request: {0}")] + RequestBuildFailed(reqwest::Error), + #[error("Failed to make request: {0}")] + RequestFailed(reqwest::Error), + #[error("Failed to parse response as JSON: {0}")] + ParseFailed(reqwest::Error), +} + +/// 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 { + use FingerError::*; + debug!("Fingering {url}..."); + let request = client.get(url) + .build().map_err(RequestBuildFailed)?; + let response = client.execute(request).map_err(RequestFailed)?; + debug!("Received response: {response:?}"); + let response = response.json::().map_err(ParseFailed)?; + debug!("Parsed response body: {response:?}"); + Ok(response) +} + +/// Error type returned by `finger_fedi` +#[derive(Debug, Error)] +pub enum FediFingerError { + #[error("Couldn't find the middle @ symbol")] + MissingMiddleAt, + #[error("Failed to build request URL")] + UrlBuildFailed, + #[error("Failed to run WebFinger request: {0}")] + FingerFailed(#[from] FingerError) +} + +/// Finger a Fediverse compatible handle +/// +/// Connects to the server specified in the handle's domain and requests the user's Resource +#[instrument(skip(client))] +pub fn finger_fedi(client: &Client, handle: &str) -> Result { + let handle = handle.strip_prefix('@').unwrap_or(handle); + let (_account, domain) = handle.split_once('@').ok_or(FediFingerError::MissingMiddleAt)?; + let account = format!("acct:{handle}"); + + let url = Url::parse(format!("https://{domain}/.well-known/webfinger?resource={account}").as_str()).map_err(|_e| UrlBuildFailed)?; + Ok(finger_url(client, url)?) +} + +/// Finger multiple Fediverse compatible handles +/// +/// Runs `finger_fedi` on the provided list of handles. If any requests fail, prints an error and continues. +pub fn finger_many_fedi(handles: impl Iterator + Display>) -> impl Iterator { + let client = Client::builder() + .user_agent(concat!("Fingerlink/", env!("CARGO_PKG_VERSION"))) + .build().unwrap(); //Safety: setting user_agent can't cause an error. + handles.filter_map(move |handle| { + match finger_fedi(&client, handle.as_ref()) { + Err(e) => { + error!("Failed to finger {handle}: {e}"); + None + } + Ok(data) => Some(data) + } + }) +} \ No newline at end of file diff --git a/src/editor/mod.rs b/src/editor/mod.rs new file mode 100644 index 0000000..4652316 --- /dev/null +++ b/src/editor/mod.rs @@ -0,0 +1,4 @@ +pub mod finger_remote; +pub mod open_in_editor; +pub mod try_reload_server; +pub mod commands; \ No newline at end of file diff --git a/src/editor/open_in_editor.rs b/src/editor/open_in_editor.rs new file mode 100644 index 0000000..af262eb --- /dev/null +++ b/src/editor/open_in_editor.rs @@ -0,0 +1,82 @@ +use std::fmt::{Debug, Display}; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::process::Command; +use tempfile::NamedTempFile; +use thiserror::Error; +use tracing::{debug, instrument, trace}; +use crate::schema::resource::Resource; + +/// Error type returned by `spawn_editor` +#[derive(Debug, Error)] +pub enum EditorSpawnError { + #[error("$EDITOR environment variable isn't set")] + NoEditorEnv, + #[error("failed to parse out absolute path of editor: {0}")] + InvalidEditorPath(which::Error), + #[error("failed to create temporary file for edit buffer: {0}")] + TempFileCreationFailed(std::io::Error), + #[error("failed to write buffer into temporary file: {0}")] + BufferWriteFailed(std::io::Error), + #[error("failed to spawn editor process: {0}")] + EditorSpawnFailed(std::io::Error), + #[error("failed to block until editor exits: {0}")] + EditorWaitFailed(std::io::Error), + #[error("editor failed to exit correctly")] + EditorExitCode, + #[error("failed to reopen buffer after editor returned: {0}")] + BufferSeekFailed(std::io::Error), + #[error("failed to read back the buffer from editor: {0}")] + BufferReadbackFailed(std::io::Error), +} + +/// 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 + Display) -> Result { + use EditorSpawnError::*; + trace!("Input buffer: {buffer}"); + let editor = option_env!("EDITOR").ok_or(NoEditorEnv)?; + debug!("$EDITOR is {editor}"); + let editor = which::which(editor).map_err(InvalidEditorPath)?; + debug!("$EDITOR's full path is {editor:?}"); + let mut temp_file = NamedTempFile::new().map_err(TempFileCreationFailed)?; + debug!("Created temporary file at {temp_file:?}"); + temp_file.write_all(buffer.as_ref().as_bytes()).map_err(BufferWriteFailed)?; + debug!("Written buffer"); + + let mut editor = Command::new(editor); + let editor = editor + .args([temp_file.path().as_os_str()]); + debug!("Spawning editor with command {editor:?}"); + let mut editor = editor.spawn().map_err(EditorSpawnFailed)?; + let editor = editor.wait().map_err(EditorWaitFailed)?; + debug!("Editor closed with {editor:?}"); + match editor.code() { + Some(0) => {/*All good*/} + None | Some(_) => {return Err(EditorExitCode)} + } + temp_file.seek(SeekFrom::Start(0)).map_err(BufferSeekFailed)?; + let mut buffer = Default::default(); + temp_file.read_to_string(&mut buffer).map_err(BufferReadbackFailed)?; + trace!("Read back buffer: {buffer}"); + Ok(buffer) +} + +/// Error type returned by `open_resource_in_editor` +#[derive(Debug, Error)] +pub enum ResourceEditingError { + #[error("Failed to pretty print resource")] + PrettyPrintFailed(serde_json::Error), + #[error("Downstream editor spawn failed: {0}")] + EditorSpawnError(#[from] EditorSpawnError), + #[error("Failed to parse edited JSON")] + ParseFailed(serde_json::Error) +} + +/// Opens the provided `resource` in the system editor and returns the edited version or an error if something goes wrong. +#[instrument(skip(resource))] +pub fn open_resource_in_editor(resource: &Resource) -> Result { + use ResourceEditingError::*; + let printed = serde_json::to_string_pretty(resource).map_err(PrettyPrintFailed)?; + let edited = spawn_editor(printed)?; + serde_json::from_str(edited.as_str()).map_err(ParseFailed) +} \ No newline at end of file diff --git a/src/editor/try_reload_server.rs b/src/editor/try_reload_server.rs new file mode 100644 index 0000000..71e389e --- /dev/null +++ b/src/editor/try_reload_server.rs @@ -0,0 +1,50 @@ +use std::path::{Path, PathBuf}; +use std::io::Read; +use std::str::FromStr; +use nix::sys::signal::{kill, Signal}; +use nix::unistd::Pid; +use thiserror::Error; +use tracing::{error, info}; +use crate::args_parser::ServerReloadOptions; + +#[derive(Debug, Error)] +pub enum ServerReloadError { + #[error("Couldn't find PID file at {0}")] + PIDFileNotFound(String), + #[error("Failed to open PID file: {0}")] + FailedToOpenPIDFile(std::io::Error), + #[error("Failed to read PID from file: {0}")] + FailedToReadPIDFile(std::io::Error), + #[error("Failed to parse PID: {0}")] + FailedToParsePID(#[from] std::num::ParseIntError), + #[error("Failed to send signal: {0}")] + FailedToSendSignal(#[from] nix::errno::Errno), +} + +fn load_pid(pid_file_path: &Path) -> Result { + use ServerReloadError::*; + if !pid_file_path.exists() { + return Err(PIDFileNotFound(pid_file_path.to_string_lossy().to_string())) + } + let mut file = std::fs::File::open(pid_file_path).map_err(FailedToOpenPIDFile)?; + let mut buffer = Default::default(); + file.read_to_string(&mut buffer).map_err(FailedToReadPIDFile)?; + let pid = FromStr::from_str(buffer.trim())?; + Ok(Pid::from_raw(pid)) +} + +fn try_reload_server(pid_file_path: &Path) -> Result<(), ServerReloadError> { + let pid = load_pid(pid_file_path)?; + kill(pid, Signal::SIGHUP)?; + Ok(()) +} + +pub fn reload(pid_file_path: PathBuf, options: ServerReloadOptions) { + if options.reload_server { + info!("Attempting to reload server..."); + match try_reload_server(pid_file_path.as_path()) { + Ok(_) => {info!("Server reloading!")} + Err(e) => {error!("Failed to reload server: {e}")} + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d3fb7f2 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,50 @@ +use clap::{Parser}; +use tracing::info; +use tracing_subscriber::util::SubscriberInitExt; +use crate::args_parser::Command; +use crate::editor::open_in_editor::open_resource_in_editor; +use editor::commands::fetch::fetch; +use editor::commands::query::query; +use crate::editor::commands::editor::editor; +use crate::editor::try_reload_server::reload; +use crate::schema::lookup_handler::LookupHandler; + +mod schema; +#[cfg(feature = "editor")] +mod editor; +#[cfg(feature = "server")] +mod server; +mod args_parser; + +#[cfg(all(not(feature = "editor"), not(feature = "server")))] +compile_error!("Please enable either the \"editor\" or the \"server\" feature"); + +fn main() { + let args = args_parser::MainCommand::parse(); + tracing_subscriber::FmtSubscriber::builder() + .with_max_level(args.log_level()) + .finish().init(); + let args_parser::MainCommand { data_paths, run_mode, .. } = args; + match run_mode { + Command::Serve(params) => { + #[cfg(not(feature = "server"))] + { + panic!("Server mode has not been compiled into this executable. Please rebuild with the \"server\" feature enabled.") + } + #[cfg(feature = "server")] + server::init(data_paths, params); + } + Command::Fetch { save, handles, handles_are_files, new_domain, server_reload } => { + fetch(data_paths.database_path, save, handles.into_iter(), handles_are_files, new_domain); + reload(data_paths.pid_file_path, server_reload); + } + Command::Query { resource } => { + query(data_paths.database_path, resource) + } + Command::Editor { save, resource, server_reload } => { + editor(data_paths.database_path, save, resource); + + reload(data_paths.pid_file_path, server_reload); + } + } +} diff --git a/src/schema/lookup_handler.rs b/src/schema/lookup_handler.rs new file mode 100644 index 0000000..0ed7e69 --- /dev/null +++ b/src/schema/lookup_handler.rs @@ -0,0 +1,143 @@ +use std::collections::HashMap; +use std::fmt::Debug; +use tracing::{debug, info, instrument}; +use std::path::Path; +use thiserror::Error; +use crate::schema::resource::Resource; +use crate::schema::resource_list::{ResourceList, ResourceLoadError}; + +/// Handles looking up resources based on the ?resource={} URL parameter +#[derive(Debug)] +pub struct LookupHandler { + resources: ResourceList, + lookup: HashMap, +} + +#[derive(Debug, Error)] +pub enum DataLoadError { + #[error("failed to load underlying resource: {0}")] + ResourceLoadError(#[from] ResourceLoadError), + #[error("duplicate lookup name \"{duplicated}\" found between subjects {subjects:?}")] + DuplicateLookupFound { + duplicated: String, + subjects: [String; 2], + }, +} + +impl LookupHandler { + /// Load and prepare a new LookupHandler from the file at `path` + #[instrument(level = "debug", skip(path))] + pub fn load(path: impl AsRef + Debug) -> Result { + Self::build_from_resource_list(ResourceList::load(path)?) + } + fn load_from_reader(reader: impl std::io::Read) -> Result { + Self::build_from_resource_list(ResourceList::load_from_reader(reader)?) + } + + fn build_from_resource_list(resources: ResourceList) -> Result { + 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); + if let Some(duplicate) = lookup.insert(lookup_to_add.clone(), index) { + return Err(DataLoadError::DuplicateLookupFound { + duplicated: lookup_to_add, + subjects: [ + resources.0[duplicate].subject.clone(), + resource.subject.clone(), + ], + }); + } + } + } + info!("Aggregated {} lookup strings", lookup.len()); + Ok(LookupHandler { resources, lookup }) + } + + #[instrument(level="debug")] + pub fn lookup(&self, resource_query: &str) -> Option<&Resource> { + self.lookup_with_index(resource_query).map(|(_index, resource)| resource) + } + + pub fn lookup_with_index(&self, resource_query: &str) -> Option<(usize, &Resource)> { + let resource_index = *self.lookup.get(resource_query)?; + let found_resource = &self.resources.0[resource_index]; + debug!("Lookup for {resource_query} returned {found_resource:?}"); + Some((resource_index, found_resource)) + } + + pub fn into_inner(self) -> ResourceList { + self.resources + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn invalid_json() { + let invalid = "{".as_bytes(); + let result = LookupHandler::load_from_reader(invalid); + if let Err(DataLoadError::ResourceLoadError(ResourceLoadError::FileParse(_))) = result { + // All good! + } else { + panic!("LookupHandler passed invalid JSON") + } + } + + #[test] + fn duplicate_subject() { + let duplicated = "[{\"subject\": \"testing\"},{\"subject\": \"testing\"}]".as_bytes(); + let result = LookupHandler::load_from_reader(duplicated); + if let Err(DataLoadError::DuplicateLookupFound { duplicated, subjects }) = result { + assert_eq!(duplicated, "testing".to_string()); + assert_eq!(subjects, ["testing".to_string(), "testing".to_string()]); + } else { + panic!("LookupHandler::load() returned {result:?}"); + }; + } + + #[test] + fn duplicate_alias() { + let duplicated = "[{\"subject\": \"testing\"},{\"subject\": \"more_testing\", \"aliases\": [\"testing\"]}]".as_bytes(); + let result = LookupHandler::load_from_reader(duplicated); + if let Err(DataLoadError::DuplicateLookupFound { duplicated, mut subjects }) = result { + assert_eq!(duplicated, "testing".to_string()); + subjects.sort(); // Because we don't care about order for testing purposes + assert_eq!(subjects, ["more_testing".to_string(), "testing".to_string()]); + } else { + panic!("LookupHandler::load() returned {result:?}"); + }; + } + + #[test] + fn successful_query() { + let data = "[{\"subject\":\"testing\"},{\"subject\":\"more_testing\"}]".as_bytes(); + let data = LookupHandler::load_from_reader(data).unwrap(); + for subject in ["testing", "more_testing"] { + assert_eq!(data.lookup(subject), Some(&Resource { + subject: subject.to_string(), + aliases: None, + properties: None, + links: None, + })); + } + } + + #[test] + fn successful_alias_query() { + let data = "[{\"subject\":\"testing\",\"aliases\":[\"alias1\",\"alias2\"]},{\"subject\":\"red herring\",\"aliases\":[\"alias\",\"1\", \"2\"]}]".as_bytes(); + let data = LookupHandler::load_from_reader(data).unwrap(); + for subject in ["alias1", "alias2"] { + assert_eq!(data.lookup(subject), Some(&Resource { + subject: "testing".to_string(), + aliases: Some(vec!["alias1".to_string(), "alias2".to_string()]), + properties: None, + links: None, + })); + } + } +} diff --git a/src/schema/mod.rs b/src/schema/mod.rs new file mode 100644 index 0000000..9f3d51f --- /dev/null +++ b/src/schema/mod.rs @@ -0,0 +1,3 @@ +pub mod resource; +pub mod lookup_handler; +pub mod resource_list; diff --git a/src/schema/resource.rs b/src/schema/resource.rs new file mode 100644 index 0000000..2b63348 --- /dev/null +++ b/src/schema/resource.rs @@ -0,0 +1,110 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fmt::{Debug, Display, Formatter}; +use tracing::debug; + +/// A single WebFinger resource +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Resource { + pub subject: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub aliases: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub properties: Option>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub links: Option>, +} + +impl Display for Resource { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let pretty = serde_json::to_string_pretty(self).unwrap(); + write!(f, "{pretty}") + } +} + +impl Resource { + /// Returns the aliases of the given record. If the `aliases` field is + /// entirely missing, returns &[]. + pub fn keys(&self) -> impl Iterator { + let aliases = if let Some(aliases) = &self.aliases { + aliases.as_slice() + } else { + &[] + }; + + aliases.iter().chain(std::iter::once(&self.subject)) + } + + pub fn add_new_primary_subject(&mut self, mut subject: String) { + 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) { + aliases.push(subject) + } else { + debug!("self.aliases already contained subject, skipping") + } + } else { + debug!("Empty self.aliases, creating new array."); + self.aliases = Some(vec![subject]) + } + } +} + +/// A link contained within a WebFinger resource +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Link { + pub rel: String, + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub media_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub href: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub titles: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub properties: Option>>, +} + +#[cfg(test)] +/// Functions to generate data for testing functions that manipulate `Resource` structs +pub mod test_data { + use crate::schema::resource::Resource; + pub fn barebones_user() -> Resource { + Resource { + subject: "acct:user@domain.tld".to_string(), + aliases: None, + properties: None, + links: None, + } + } + pub fn user_with_single_alias() -> Resource { + Resource { + subject: "acct:user@domain.tld".to_string(), + aliases: Some(vec![ + "https://domain.tld/@user".to_string() + ]), + properties: None, + links: None, + } + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn insert_new_primary_subject_into_barebones_user() { + let mut data = test_data::barebones_user(); + data.add_new_primary_subject("username".to_string()); + assert_eq!(data, Resource { + subject: "username".to_string(), + aliases: Some(vec![test_data::barebones_user().subject]), + properties: None, + links: None, + }) + + } +} \ No newline at end of file diff --git a/src/schema/resource_list.rs b/src/schema/resource_list.rs new file mode 100644 index 0000000..e7036c0 --- /dev/null +++ b/src/schema/resource_list.rs @@ -0,0 +1,115 @@ +use std::collections::HashSet; +use tracing::{debug, info, instrument, trace, warn}; +use std::path::Path; +use std::fmt::Debug; +use thiserror::Error; +use crate::args_parser::CollisionHandling; +use crate::schema::resource::Resource; + +#[derive(Debug, Clone)] +pub struct ResourceList(pub Vec); + +#[derive(Debug, Error)] +pub enum ResourceLoadError { + #[error("failed to open the resource database: {0}")] + FileOpen(#[from] std::io::Error), + #[error("failed to parse the resource database: {0}")] + FileParse(#[from] serde_json::Error), +} + +#[derive(Debug, Error)] +pub enum ResourceSaveError { + #[error("Failed to open the resource database for writing: {0}")] + FileOpen(std::io::Error), + #[error("Failed to create backup of database before writing: {0}")] + BackupFailed(std::io::Error), + #[error("Failed to write the resource database: {0}")] + FileSerialisation(#[from] serde_json::Error), +} + +impl ResourceList { + /// Loads the `Resource`s from the given `path` + #[instrument(level = "debug")] + pub fn load(path: impl AsRef + Debug) -> Result { + info!("Loading data from {path:?}..."); + let file = std::fs::File::open(path)?; + Self::load_from_reader(file) + } + + /// Loads the `Resource`s from the given reader + pub fn load_from_reader(reader: impl std::io::Read) -> Result { + let reader = std::io::BufReader::new(reader); + debug!("Parsing as JSON..."); + let resources: Vec = serde_json::from_reader(reader)?; + info!("Loaded {} resources", resources.len()); + trace!("{resources:?}"); + Ok(Self(resources)) + } + + #[instrument(level = "debug", skip(path, self))] + pub fn save(&self, path: impl AsRef + Debug) -> Result<(), ResourceSaveError> { + info!("Creating backup before writing..."); + let path = path.as_ref(); + std::fs::copy(path, path.with_extension("bak")).map_err(ResourceSaveError::BackupFailed)?; + info!("Writing data to {path:?}..."); + let file = std::fs::File::create(path).map_err(ResourceSaveError::FileOpen)?; + self.save_to_writer(file) + } + + pub fn save_to_writer(&self, writer: impl std::io::Write) -> Result<(), ResourceSaveError> { + trace!("{self:?}"); + let writer = std::io::BufWriter::new(writer); + debug!("Serialising JSON..."); + Ok(serde_json::to_writer(writer, &self.0)?) + } + + /// 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, new_records: impl Iterator, collision_handling: CollisionHandling) -> &ResourceList { + debug!("Building hashset of already taken queries..."); + let unique_check: HashSet = 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.intersection(&record_keys).collect::>(); + if !collisions.is_empty() { + warn!("Resource collision detected with {}: {collisions:?}", record.subject); + match collision_handling { + CollisionHandling::Skip => { + warn!("Skipping record..."); + continue + } + CollisionHandling::OverwriteSingleSkipMultiple => { + let mut collided_resources = + self.0.iter().enumerate() + .filter(|record| record.1.keys().any(|elem| collisions.contains(elem))); + if let Some((collided_index, collided_resource)) = collided_resources.next() { + if collided_resources.next().is_some() { + warn!("Resource collides with multiple records, skipping..."); + continue + } + warn!("Removing {}", collided_resource.subject); + self.0.remove(collided_index); + } + } + CollisionHandling::OverwriteMultiple => { + warn!("Overwriting already existing record(s) with new data..."); + self.0.retain(|record| { + if record.keys().any(|elem| collisions.contains(elem)) { + warn!("Removing {record:?}"); + false + } else {true} + }); + } + CollisionHandling::Terminate => { + warn!("Collision found, terminating merge process..."); + return self + } + } + } + info!("Inserting {}", record.subject); + self.0.push(record); + } + + self + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..6e1ea09 --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,152 @@ +use std::io::Read; +use std::net::SocketAddr; +use std::ops::DerefMut; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use tokio::sync::RwLock; +use axum::body::Body; +use axum::extract::{Request, State}; +use axum::http::StatusCode; +use axum::response::Response; +use axum::Router; +use axum::routing::get; +use tokio::select; +use tokio::signal::unix::SignalKind; +use tracing::{debug, error, info, instrument, trace}; +use crate::args_parser::{DataPaths, ServerParameters}; +use crate::schema::lookup_handler::LookupHandler; +use crate::schema::resource::Resource; + +pub fn init(data_paths: DataPaths, server_parameters: ServerParameters) { + let DataPaths { database_path, pid_file_path } = data_paths; + //TODO: Apparently you're supposed to keep this file around and verify that the PID in it is still active? + debug!("Attempting to install PID file at {pid_file_path:?}"); + if let Ok(mut file) = std::fs::File::open(&pid_file_path) { + if !server_parameters.force_pid { + let mut buffer = String::new(); + if file.read_to_string(&mut buffer).is_ok() { + panic!("Server already running with PID {} according to {pid_file_path:?}. If you wish to overwrite it, pass the --force-pid parameter.", buffer.trim()); + } + panic!("PID file already exists at path {pid_file_path:?}, but I was unable to open it. If you wish to try to overwrite it, pass the --force-pid parameter."); + } + info!("Found PID file at {pid_file_path:?}, overwriting..."); + } + let pid_file = qpidfile::Pidfile::new(pid_file_path).unwrap(); + + let runtime = match tokio::runtime::Runtime::new() { + Err(e) => { + error!("Failed to create tokio runtime: {e}"); + return; + } + Ok(runtime) => runtime + }; + if let Err(e) = runtime.block_on(async_main(database_path, server_parameters)) { + error!("Failed to block on server's async_main: {e}"); + } + // To ensure that the pid_file variable lives until the end of the function + drop(pid_file); +} + +#[instrument(skip_all)] +async fn async_main(db_path: PathBuf, server_parameters: ServerParameters) -> Result<(), std::io::Error> { + info!("Initialising server..."); + let datastore = Arc::new(RwLock::new(LookupHandler::load(&db_path).unwrap())); + let _handler = tokio::spawn(hangup_handler(db_path, datastore.clone())); + + let listen_address = SocketAddr::new(server_parameters.bind_ip, server_parameters.bind_port); + + let router = Router::new() + .route("/.well-known/webfinger", get(run_webfinger_query)) + .with_state(datastore); + + let listener = tokio::net::TcpListener::bind(listen_address).await.unwrap(); + axum::serve(listener, router) + .with_graceful_shutdown(graceful_shutdown_handler()) + .await +} + +async fn run_webfinger_query(State(datastore): State>>, request: Request) -> Result { + let uri = request.uri(); + info!("Received query with {uri}"); + let query = uri.query().ok_or(StatusCode::BAD_REQUEST)?; + debug!("Query string is {query}"); + let params = query.split('&').filter_map(|query_part| { + trace!("Processing part {query_part}"); + query_part.split_once('=') + }).collect::>(); + trace!("Query parts are {params:?}"); + + let mut resource_query_iter = params.iter().filter_map(|(key, value)| if key.trim() == "resource" {Some(value)} else {None}); + let resource_query = *resource_query_iter.next().ok_or(StatusCode::BAD_REQUEST)?; + if resource_query_iter.next().is_some() { + debug!("Multiple resource parameters provided, bailing"); + return Err(StatusCode::BAD_REQUEST) + } + + let resource = datastore.read().await.lookup(resource_query).ok_or(StatusCode::NOT_FOUND)?.clone(); + debug!("Found resource: {resource:?}"); + + let mut rels = params.into_iter().filter_map(|(key, value)| if key.trim() == "rel" {Some(value)} else {None}).peekable(); + + let response_body = if rels.peek().is_some() { + debug!("There were rel parameters in the query"); + if let Some(links) = resource.links { + debug!("Filtering links..."); + Resource { + links: Some(rels.filter_map(|rel| links.iter().find(|link| link.rel == rel).cloned()).collect()), + ..resource + } + } else { + resource + } + } else { + resource + }; + + debug!("Responding with {response_body:?}"); + + Response::builder() + .header("Content-Type", "application/jrd+json") + .body(Body::new(serde_json::to_string(&response_body).map_err(|e| { + error!("Server error occurred while serialising response body: {e}"); + StatusCode::INTERNAL_SERVER_ERROR + })?)) + .map_err(|e| { + error!("Server error occurred while building response: {e}"); + StatusCode::INTERNAL_SERVER_ERROR + }) +} + +#[instrument(skip_all)] +async fn hangup_handler(db_path: PathBuf, datastore: Arc>) { + let db_path = Path::new(&db_path); + debug!("Installing SIGHUP handler..."); + + let mut handler = tokio::signal::unix::signal(SignalKind::hangup()).unwrap(); + while handler.recv().await.is_some() { + info!("Received SIGHUP, reloading data.."); + match LookupHandler::load(db_path) { + Ok(mut handler) => { + info!("Data parsed, waiting for write lock on datastore..."); + let mut lock = datastore.write().await; + std::mem::swap(lock.deref_mut(), &mut handler); + info!("Data updated!"); + } + Err(error) => {error!("Failed to reload datastore: {error}")} + } + } +} + +async fn graceful_shutdown_handler() { + debug!("Installing graceful shutdown handler..."); + let mut sigint = tokio::signal::unix::signal(SignalKind::interrupt()).unwrap(); + let sigint = sigint.recv(); + let mut sigterm = tokio::signal::unix::signal(SignalKind::terminate()).unwrap(); + let sigterm = sigterm.recv(); + + select! { + biased; // We don't care about fairness in poll order + Some(()) = sigint => info!("SIGINT received, shutting down..."), + Some(()) = sigterm => info!("SIGTERM received, shutting down..."), + } +} \ No newline at end of file