From c9fb83c0154f9b40e1ce464c1b9d55aa7a5d8b8a Mon Sep 17 00:00:00 2001 From: Leynos Date: Thu, 24 Jul 2025 08:58:07 +0100 Subject: [PATCH 1/5] Set up workspace and shared library --- .markdownlint-cli2.jsonc | 8 +- AGENTS.md | 4 +- Cargo.lock | 2951 +++++++++++++++++ Cargo.toml | 34 +- crates/comenq/Cargo.toml | 11 + {src => crates/comenq/src}/main.rs | 2 +- crates/comenqd/Cargo.toml | 17 + crates/comenqd/src/main.rs | 3 + ...havioural-testing-in-rust-with-cucumber.md | 3 +- docs/comenq-design.md | 561 +++- docs/roadmap.md | 77 +- src/lib.rs | 53 + tests/cucumber.rs | 8 + tests/features/comment_request.feature | 11 + tests/steps/comment_steps.rs | 67 + tests/steps/mod.rs | 2 + 16 files changed, 3614 insertions(+), 198 deletions(-) create mode 100644 Cargo.lock create mode 100644 crates/comenq/Cargo.toml rename {src => crates/comenq/src}/main.rs (96%) create mode 100644 crates/comenqd/Cargo.toml create mode 100644 crates/comenqd/src/main.rs create mode 100644 src/lib.rs create mode 100644 tests/cucumber.rs create mode 100644 tests/features/comment_request.feature create mode 100644 tests/steps/comment_steps.rs create mode 100644 tests/steps/mod.rs diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index b1ed206..f2fdd03 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -2,10 +2,8 @@ // Ignore MD013 (line length) inside tables since reflowing // Markdown tables often breaks formatting and readability. "config": { - "MD013": { - "line_length": 80, - "code_block_line_length": 120, - "tables": false - } + "MD013": false, + "MD029": false, + "MD040": false } } diff --git a/AGENTS.md b/AGENTS.md index c0fea37..e7ceb10 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,8 +9,8 @@ - **Clarity over cleverness.** Be concise, but favour explicit over terse or obscure idioms. Prefer code that's easy to follow. - **Use functions and composition.** Avoid repetition by extracting reusable - logic. Prefer generators or comprehensions, and declarative code to imperative - repetition when readable. + logic. Prefer generators or comprehensions, and declarative code to + imperative repetition when readable. - **Small, meaningful functions.** Functions must be small, clear in purpose, single responsibility, and obey command/query segregation. - **Clear commit messages.** Commit messages should be descriptive, explaining diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..55b8b04 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2951 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "comenq" +version = "0.1.0" +dependencies = [ + "clap", + "comenq-lib", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "comenq-lib" +version = "0.1.0" +dependencies = [ + "cucumber", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "comenqd" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "comenq-lib", + "octocrab", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tracing", + "tracing-subscriber", + "yaque", +] + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", +] + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "cucumber" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5063d8cf24f4998ad01cac265da468a15ca682a8f4f826d50e661964e8d9b8" +dependencies = [ + "anyhow", + "async-trait", + "clap", + "console", + "cucumber-codegen", + "cucumber-expressions", + "derive_more", + "drain_filter_polyfill", + "either", + "futures", + "gherkin", + "globwalk", + "humantime", + "inventory", + "itertools", + "lazy-regex", + "linked-hash-map", + "once_cell", + "pin-project", + "regex", + "sealed", + "smart-default", +] + +[[package]] +name = "cucumber-codegen" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01091e28d1f566c8b31b67948399d2efd6c0a8f6228a9785519ed7b73f7f0aef" +dependencies = [ + "cucumber-expressions", + "inflections", + "itertools", + "proc-macro2", + "quote", + "regex", + "syn", + "synthez", +] + +[[package]] +name = "cucumber-expressions" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d794fed319eea24246fb5f57632f7ae38d61195817b7eb659455aa5bdd7c1810" +dependencies = [ + "derive_more", + "either", + "nom", + "nom_locate", + "regex", + "regex-syntax 0.7.5", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "drain_filter_polyfill" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[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 = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gherkin" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b79820c0df536d1f3a089a2fa958f61cb96ce9e0f3f8f507f5a31179567755" +dependencies = [ + "heck 0.4.1", + "peg", + "quote", + "serde", + "serde_json", + "syn", + "textwrap", + "thiserror 1.0.69", + "typed-builder", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "globset" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2 0.6.0", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.9", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "inventory" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" +dependencies = [ + "rustversion", +] + +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "lazy-regex" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libredox" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" +dependencies = [ + "bitflags 2.9.1", + "libc", + "redox_syscall", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.59.0", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom_locate" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3" +dependencies = [ + "bytecount", + "memchr", + "nom", +] + +[[package]] +name = "notify" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486" +dependencies = [ + "bitflags 1.3.2", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "mio 0.8.11", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[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-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "octocrab" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a8a3df00728324ad654ecd1ed449a60157c55b7ff8c109af3a35989687c367" +dependencies = [ + "arc-swap", + "async-trait", + "base64", + "bytes", + "cfg-if", + "chrono", + "either", + "futures", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-timeout", + "hyper-util", + "jsonwebtoken", + "once_cell", + "percent-encoding", + "pin-project", + "secrecy", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "snafu", + "tokio", + "tower", + "tower-http", + "tracing", + "url", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "peg" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f76678828272f177ac33b7e2ac2e3e73cc6c1cd1e3e387928aa69562fa51367" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "636d60acf97633e48d266d7415a9355d4389cea327a193f87df395d88cd2b14d" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555b1514d2d99d78150d3c799d4c357a3e2c2a8062cd108e93a06d9057629c5" + +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64", + "serde", +] + +[[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.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sealed" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a8caec23b7800fb97971a1c6ae365b6239aaeddfb934d6265f8505e795699d" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +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 = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.12", + "time", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smart-default" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "snafu" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320b01e011bf8d5d7a4a4a4be966d9160968935849c83b918827f6a435e7f627" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1961e2ef424c1424204d3a5d6975f934f56b6d50ff5732382d84ebf460e147f7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "synthez" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d2c2202510a1e186e63e596d9318c91a8cbe85cd1a56a7be0c333e5f59ec8d" +dependencies = [ + "syn", + "synthez-codegen", + "synthez-core", +] + +[[package]] +name = "synthez-codegen" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f724aa6d44b7162f3158a57bccd871a77b39a4aef737e01bcdff41f4772c7746" +dependencies = [ + "syn", + "synthez-core", +] + +[[package]] +name = "synthez-core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bfa6ec52465e2425fd43ce5bbbe0f0b623964f7c63feb6b10980e816c654ea" +dependencies = [ + "proc-macro2", + "quote", + "sealed", + "syn", +] + +[[package]] +name = "sysinfo" +version = "0.28.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c2f3ca6693feb29a89724516f016488e9aafc7f37264f898593ee4b942f31b" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "winapi", +] + +[[package]] +name = "terminal_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio 1.0.4", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2 0.5.10", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[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", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "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 = "typed-builder" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe83c85a85875e8c4cb9ce4a890f05b23d38cd0d47647db7895d3d2a79566d2" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29a3151c41d0b13e3d011f98adc24434560ef06673a155a6c7f66b9879eecce2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[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.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[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-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[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-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[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.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yaque" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487f1a92dacd945cc5bc78a8193cc00b9a2cce3c07746ca51533f513843f40d2" +dependencies = [ + "futures", + "lazy_static", + "log", + "notify", + "rand", + "semver", + "sysinfo", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index ded5814..1dbc075 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,41 @@ [package] -name = "comenq" +name = "comenq-lib" version = "0.1.0" edition = "2024" +[lib] +path = "src/lib.rs" + [dependencies] +serde = { workspace = true } +serde_json = { workspace = true } + +[dev-dependencies] +cucumber = "0.20" +tokio = { workspace = true } + +[[test]] +name = "cucumber" +harness = false + +[workspace] +members = [ + "crates/comenq", + "crates/comenqd", +] +resolver = "2" + +[workspace.dependencies] +tokio = { version = "1.35", features = ["full"] } +clap = { version = "4.4", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +octocrab = "0.38" +yaque = "0.6" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +anyhow = "1.0" +thiserror = "1.0" [lints.clippy] pedantic = { level = "warn", priority = -1 } diff --git a/crates/comenq/Cargo.toml b/crates/comenq/Cargo.toml new file mode 100644 index 0000000..b4b002d --- /dev/null +++ b/crates/comenq/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "comenq" +version = "0.1.0" +edition = "2024" + +[dependencies] +tokio = { workspace = true } +clap = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +comenq-lib = { path = "../.." } diff --git a/src/main.rs b/crates/comenq/src/main.rs similarity index 96% rename from src/main.rs rename to crates/comenq/src/main.rs index 5315b59..b8cd009 100644 --- a/src/main.rs +++ b/crates/comenq/src/main.rs @@ -1,3 +1,3 @@ fn main() { println!("Hello from Comenq!"); -} \ No newline at end of file +} diff --git a/crates/comenqd/Cargo.toml b/crates/comenqd/Cargo.toml new file mode 100644 index 0000000..5819566 --- /dev/null +++ b/crates/comenqd/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "comenqd" +version = "0.1.0" +edition = "2024" + +[dependencies] +tokio = { workspace = true } +clap = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +octocrab = { workspace = true } +yaque = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +anyhow = { workspace = true } +thiserror = { workspace = true } +comenq-lib = { path = "../.." } diff --git a/crates/comenqd/src/main.rs b/crates/comenqd/src/main.rs new file mode 100644 index 0000000..e7848fa --- /dev/null +++ b/crates/comenqd/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello from Comenqd!"); +} diff --git a/docs/behavioural-testing-in-rust-with-cucumber.md b/docs/behavioural-testing-in-rust-with-cucumber.md index e7c1a89..ae8e91c 100644 --- a/docs/behavioural-testing-in-rust-with-cucumber.md +++ b/docs/behavioural-testing-in-rust-with-cucumber.md @@ -1114,7 +1114,6 @@ aligned with what is needed. [^18]: *Quickstart* — Cucumber Rust Book, accessed on 14 July 2025, -[^19]: *A Beginner’s Guide to Cucumber in Rust* — Florian Reinhard, accessed on 14 July 2025, @@ -1129,7 +1128,7 @@ aligned with what is needed. Stack Overflow, accessed on 14 July 2025, -[^23]: Data tables - Cucumber Rust Book, accessed on 14 July 2025, +[^23]: Data tables - Cucumber Rust Book, accessed on 14 July 2025, [^25]: Best practices for scenario writing | CucumberStudio Documentation diff --git a/docs/comenq-design.md b/docs/comenq-design.md index 103aaed..04f13ea 100644 --- a/docs/comenq-design.md +++ b/docs/comenq-design.md @@ -3,75 +3,130 @@ ## Section 1: System Architecture and Core Component Selection -This document presents a comprehensive architectural design and implementation guide for `comenq`, a robust service for enqueuing GitHub Pull Request comments. The system is designed to post comments with a mandatory cooling-off period, a critical feature for managing interactions with GitHub's API and avoiding secondary rate limits that penalize rapid, automated actions. The design prioritizes reliability, security, and operational simplicity, tailored for deployment on a resource-constrained Linux environment. +This document presents a comprehensive architectural design and implementation +guide for `comenq`, a robust service for enqueuing GitHub Pull Request +comments. The system is designed to post comments with a mandatory cooling-off +period, a critical feature for managing interactions with GitHub's API and +avoiding secondary rate limits that penalize rapid, automated actions. The +design prioritizes reliability, security, and operational simplicity, tailored +for deployment on a resource-constrained Linux environment. ### 1.1. Architectural Overview: The Client-Daemon Model -The fundamental architecture of the `comenq` system is based on the classic Unix client-daemon model. This design pattern is not merely a stylistic choice but a direct and necessary consequence of the core requirement to enforce a time-delayed, sequential processing of comments. A simple, ephemeral script cannot maintain the state and persistence required for this task. The system is therefore decomposed into two distinct, cooperating processes: +The fundamental architecture of the `comenq` system is based on the classic +Unix client-daemon model. This design pattern is not merely a stylistic choice +but a direct and necessary consequence of the core requirement to enforce a +time-delayed, sequential processing of comments. A simple, ephemeral script +cannot maintain the state and persistence required for this task. The system is +therefore decomposed into two distinct, cooperating processes: -1. `comenqd` **(The Daemon):** A long-running background process that serves as the system's engine. It is solely responsible for managing a persistent job queue, interacting with the GitHub API, and enforcing the 15-minute cooling-off period between posts. +1. `comenqd` **(The Daemon):** A long-running background process that serves as + the system's engine. It is solely responsible for managing a persistent job + queue, interacting with the GitHub API, and enforcing the 15-minute + cooling-off period between posts. -2. `comenq` **(The Client):** A lightweight command-line interface (CLI) tool. Its only function is to parse user input, connect to the `comenqd` daemon, and submit a new comment request for queuing. +2. `comenq` **(The Client):** A lightweight command-line interface (CLI) tool. + Its only function is to parse user input, connect to the `comenqd` daemon, + and submit a new comment request for queuing. -This separation of concerns, inspired by established systems like Docker which use a daemon-client model over a Unix socket , yields significant advantages: +This separation of concerns, inspired by established systems like Docker which +use a daemon-client model over a Unix socket , yields significant advantages: -- **Persistence and Statefulness:** The daemon can maintain the queue and its internal timer state across many client invocations, ensuring that the 15-minute delay is consistently enforced. +- **Persistence and Statefulness:** The daemon can maintain the queue and its + internal timer state across many client invocations, ensuring that the + 15-minute delay is consistently enforced. -- **Decoupling:** The user's interaction (via the CLI) is immediate. The user can submit a comment and receive confirmation that it has been enqueued without having to wait for it to be posted. The daemon handles the asynchronous processing in the background. +- **Decoupling:** The user's interaction (via the CLI) is immediate. The user + can submit a comment and receive confirmation that it has been enqueued + without having to wait for it to be posted. The daemon handles the + asynchronous processing in the background. -- **Robustness:** The daemon can be managed as a proper system service, with automatic restarts on failure, while the client remains a simple, stateless utility. +- **Robustness:** The daemon can be managed as a proper system service, with + automatic restarts on failure, while the client remains a simple, stateless + utility. The complete lifecycle of a request is illustrated in the following sequence: - 1. A user on the host machine invokes the `comenq` client via a command like `ssh mybox comenq owner/repo 123 "My comment"`. + 1. A user on the host machine invokes the `comenq` client via a command like + `ssh mybox comenq owner/repo 123 "My comment"`. - 2. The `comenq` client parses the command-line arguments. + 1. The `comenq` client parses the command-line arguments. - 3. The client establishes a connection to the `comenqd` daemon over a local Unix Domain Socket (UDS). + 1. The client establishes a connection to the `comenqd` daemon over a local + Unix Domain Socket (UDS). - 4. The client serializes the comment data into a predefined format (JSON) and transmits it to the daemon. + 1. The client serializes the comment data into a predefined format (JSON) and + transmits it to the daemon. - 5. The `comenqd` daemon, listening on the UDS, accepts the connection, reads the data, and deserializes it into a job request. + 1. The `comenqd` daemon, listening on the UDS, accepts the connection, reads + the data, and deserializes it into a job request. - 6. The daemon validates the request and pushes it onto a persistent, disk-backed queue. + 1. The daemon validates the request and pushes it onto a persistent, + disk-backed queue. - 7. The daemon immediately sends an acknowledgment of receipt back to the client, which then exits. + 1. The daemon immediately sends an acknowledgment of receipt back to the + client, which then exits. - 8. A separate, dedicated worker task within the daemon continuously monitors the queue. It dequeues one job at a time. + 1. A separate, dedicated worker task within the daemon continuously monitors + the queue. It dequeues one job at a time. - 9. The worker task uses an authenticated client to post the comment to the GitHub API. + 1. The worker task uses an authenticated client to post the comment to the + GitHub API. -10. Upon successful posting, the worker commits the job, permanently removing it from the queue. + 1. Upon successful posting, the worker commits the job, permanently removing + it from the queue. -11. The worker task then enters a 15-minute sleep state (the "cooling-off period"). + 2. The worker task then enters a 15-minute sleep state (the "cooling-off + period"). -12. After the sleep period elapses, the worker task returns to step 8, ready to process the next job in the queue. + 3. After the sleep period elapses, the worker task returns to step 8, ready to + process the next job in the queue. -This architecture ensures that comment posting is strictly serialized and paced, directly addressing the primary goal of avoiding API rate limits. +This architecture ensures that comment posting is strictly serialized and +paced, directly addressing the primary goal of avoiding API rate limits. ### 1.2. Core Technology Stack: Crate Selection and Justification -The selection of foundational Rust libraries (crates) is critical to building a robust and maintainable system. The following table outlines the chosen crates for each major component of the `comenq` service, along with a detailed justification for each selection based on an analysis of available tools and project requirements. - - - -

Component/Concern

Selected Crate/Library

Key Features & Rationale

Alternative(s) Considered

Asynchronous Runtime

tokio

The de-facto standard for asynchronous programming in Rust. It provides a high-performance, multi-threaded scheduler and a comprehensive suite of utilities for I/O, networking, and timers, including the essential UnixListener, UnixStream, and time::sleep components.2 Its maturity and extensive ecosystem make it the definitive choice for the daemon's core.

async-std

CLI Argument Parsing

clap

The most popular and feature-rich CLI argument parsing library for Rust.4 The

derive feature offers an exceptionally ergonomic and declarative way to define the CLI's structure, automatically generating argument parsing, validation, and help text from a simple struct definition.6

argh, pico-args 4

GitHub API Client

octocrab

A modern, actively maintained, and extensible GitHub API client.9 It provides strongly-typed models for API responses and a builder pattern for requests, simplifying interaction with the GitHub REST API. Its static API and support for custom middleware are valuable for building robust clients.11

roctokit 12, manual

reqwest 13

Persistent Queue

yaque

A disk-backed, persistent queue designed for asynchronous environments.14 Its most critical feature is

transactional reads via its RecvGuard mechanism. This ensures that a dequeued item is automatically returned to the queue if the program panics or fails before the item is explicitly committed, providing an "at-least-once" delivery guarantee essential for reliability.14

queue-file 16,

v_queue 18

IPC Serialization

serde / serde_json

serde is the universal framework for serialization and deserialization in Rust. serde_json provides a straightforward implementation for the JSON data format, which is chosen for its human-readability (aiding in debugging) and widespread support.

bincode, prost

Systemd Integration

systemd (crate)

Provides native Rust bindings for interacting with the systemd journal and daemon notification APIs.19 While the primary deployment mechanism is a

.service file, this crate can be used for more advanced integration, such as sending readiness notifications.

systemctl (crate) 20

Logging

tracing / tracing-subscriber

A modern, structured, and asynchronous-aware logging and diagnostics framework. It is the standard choice for tokio-based applications, providing contextual information that is superior to traditional line-based logging.

log / env_logger 22

+The selection of foundational Rust libraries (crates) is critical to building a +robust and maintainable system. The following table outlines the chosen crates +for each major component of the `comenq` service, along with a detailed +justification for each selection based on an analysis of available tools and +project requirements. + +| Component/Concern | Selected Crate/Library | Key Features & Rationale | Alternative(s) Considered | +| -------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | ---------- | +| Asynchronous Runtime | tokio | The de-facto standard for asynchronous programming in Rust. It provides a high-performance, multi-threaded scheduler and a comprehensive suite of utilities for I/O, networking, and timers, including the essential UnixListener, UnixStream, and time::sleep components.2 Its maturity and extensive ecosystem make it the definitive choice for the daemon's core. | async-std | +| CLI Argument Parsing | clap | The most popular and feature-rich CLI argument parsing library for Rust.4 The | derive feature offers an exceptionally ergonomic and declarative way to define the CLI's structure, automatically generating argument parsing, validation, and help text from a simple struct definition.6 | argh, pico-args 4 | +| GitHub API Client | octocrab | A modern, actively maintained, and extensible GitHub API client.9 It provides strongly-typed models for API responses and a builder pattern for requests, simplifying interaction with the GitHub REST API. Its static API and support for custom middleware are valuable for building robust clients.11 | roctokit 12, manual | reqwest 13 | +| Persistent Queue | yaque | A disk-backed, persistent queue designed for asynchronous environments.14 Its most critical feature is | transactional reads via its RecvGuard mechanism. This ensures that a dequeued item is automatically returned to the queue if the program panics or fails before the item is explicitly committed, providing an "at-least-once" delivery guarantee essential for reliability.14 | queue-file 16, | v_queue 18 | +| IPC Serialization | serde / serde_json | serde is the universal framework for serialization and deserialization in Rust. serde_json provides a straightforward implementation for the JSON data format, which is chosen for its human-readability (aiding in debugging) and widespread support. | bincode, prost | +| Systemd Integration | systemd (crate) | Provides native Rust bindings for interacting with the systemd journal and daemon notification APIs.19 While the primary deployment mechanism is a | .service file, this crate can be used for more advanced integration, such as sending readiness notifications. | systemctl (crate) 20 | +| Logging | tracing / tracing-subscriber | A modern, structured, and asynchronous-aware logging and diagnostics framework. It is the standard choice for tokio-based applications, providing contextual information that is superior to traditional line-based logging. | log / env_logger 22 | +| | ## Section 2: Design of the `comenq` CLI Client -The `comenq` client is designed to be a simple, robust, and user-friendly tool. Its sole responsibility is to capture the user's intent from the command line and relay it securely to the `comenqd` daemon. The implementation will leverage `clap` for argument parsing and `tokio` for asynchronous communication over the Unix Domain Socket. +The `comenq` client is designed to be a simple, robust, and user-friendly tool. +Its sole responsibility is to capture the user's intent from the command line +and relay it securely to the `comenqd` daemon. The implementation will leverage +`clap` for argument parsing and `tokio` for asynchronous communication over the +Unix Domain Socket. ### 2.1. Defining the Command-Line Interface with `clap` -The command-line interface is the primary point of interaction for the user. A well-designed CLI is intuitive and self-documenting. We will use `clap`'s `derive` macro, which allows us to define the entire CLI structure declaratively within a Rust `struct`. This approach is highly recommended for its clarity and maintainability compared to the more verbose builder pattern.6 - -The CLI will accept three required positional arguments, matching the user's requested invocation format: `comenq `. +The command-line interface is the primary point of interaction for the user. A +well-designed CLI is intuitive and self-documenting. We will use `clap`'s +`derive` macro, which allows us to define the entire CLI structure +declaratively within a Rust `struct`. This approach is highly recommended for +its clarity and maintainability compared to the more verbose builder pattern.6 -The following code defines the `Args` struct that represents our CLI. This will be the core of the `comenq` client's `main.rs`. +The CLI will accept three required positional arguments, matching the user's +requested invocation format: `comenq `. -Rust +The following code defines the `Args` struct that represents our CLI. This will +be the core of the `comenq` client's `main.rs`. -``` +```rust // In src/bin/comenq/main.rs use clap::Parser; @@ -99,23 +154,32 @@ pub struct Args { } ``` -The `#[derive(Parser)]` attribute instructs `clap` to generate all the necessary parsing logic.7 The doc comments ( +The `#[derive(Parser)]` attribute instructs `clap` to generate all the +necessary parsing logic.7 The doc comments ( -`///`) are automatically converted into help messages, which are displayed when the user runs `comenq --help`. This feature makes the tool self-documenting.24 The +`///`) are automatically converted into help messages, which are displayed when +the user runs `comenq --help`. This feature makes the tool self-documenting.24 +The -`#[arg(...)]` attributes provide fine-grained control over each argument, such as defining a `default_value` for the socket path, making the client flexible for different environments.8 +`#[arg(...)]` attributes provide fine-grained control over each argument, such +as defining a `default_value` for the socket path, making the client flexible +for different environments.8 ### 2.2. Client-Daemon IPC Protocol -Effective communication between the client and daemon requires a clearly defined data contract. This ensures that both components have a shared understanding of the information being exchanged. +Effective communication between the client and daemon requires a clearly +defined data contract. This ensures that both components have a shared +understanding of the information being exchanged. #### 2.2.1. The `CommentRequest` Data Structure -A shared `CommentRequest` struct will serve as the message format. To be used by both the client and the daemon, this struct will reside in a shared library crate (e.g., `comenq-lib`). It must be serializable, so it will derive `serde::Serialize` for the client to encode it and `serde::Deserialize` for the daemon to decode it. - -Rust +A shared `CommentRequest` struct will serve as the message format. To be used +by both the client and the daemon, this struct will reside in a shared library +crate (e.g., `comenq-lib`). It must be serializable, so it will derive +`serde::Serialize` for the client to encode it and `serde::Deserialize` for the +daemon to decode it. -``` +```rust // In src/lib.rs (or a dedicated lib crate) use serde::{Serialize, Deserialize}; @@ -133,23 +197,37 @@ pub struct CommentRequest { #### 2.2.2. Serialization and Transport -The client will serialize the `CommentRequest` instance into a JSON string using the `serde_json` crate. JSON is selected for this purpose due to its excellent debugging characteristics (it is human-readable) and its robust, widespread support within the Rust ecosystem. +The client will serialize the `CommentRequest` instance into a JSON string +using the `serde_json` crate. JSON is selected for this purpose due to its +excellent debugging characteristics (it is human-readable) and its robust, +widespread support within the Rust ecosystem. -The serialized JSON data will be sent over a `tokio::net::UnixStream`. The choice of a Unix Domain Socket (UDS) is deliberate and carries significant advantages for this application: +The serialized JSON data will be sent over a `tokio::net::UnixStream`. The +choice of a Unix Domain Socket (UDS) is deliberate and carries significant +advantages for this application: -- **Performance:** For local Inter-Process Communication (IPC), UDS bypasses much of the TCP/IP stack overhead, resulting in lower latency and higher throughput. +- **Performance:** For local Inter-Process Communication (IPC), UDS bypasses + much of the TCP/IP stack overhead, resulting in lower latency and higher + throughput. -- **Security:** This is the most critical advantage. A UDS is an entity in the filesystem, like a file.25 This means it is subject to standard Unix filesystem permissions ( +- **Security:** This is the most critical advantage. A UDS is an entity in the + filesystem, like a file.25 This means it is subject to standard Unix + filesystem permissions ( - `chmod`, `chown`). The `comenqd` daemon can create the socket with permissions that restrict write access to a specific user or group. This provides a simple, powerful, and OS-integrated security model, preventing unauthorized local users or processes from injecting comments into the queue. This is inherently more secure than a `localhost` TCP socket, which any local user could connect to by default. + `chmod`, `chown`). The `comenqd` daemon can create the socket with + permissions that restrict write access to a specific user or group. This + provides a simple, powerful, and OS-integrated security model, preventing + unauthorized local users or processes from injecting comments into the queue. + This is inherently more secure than a `localhost` TCP socket, which any local + user could connect to by default. ### 2.3. Complete Client Implementation Blueprint -The following is a complete, commented blueprint for the `comenq` client's `main.rs` file. It integrates argument parsing via `clap` with asynchronous IPC via `tokio` and `UnixStream`. - -Rust +The following is a complete, commented blueprint for the `comenq` client's +`main.rs` file. It integrates argument parsing via `clap` with asynchronous IPC +via `tokio` and `UnixStream`. -``` +```rust // In src/bin/comenq/main.rs use clap::Parser; @@ -251,73 +329,125 @@ async fn main() { } ``` -This client is a self-contained, robust utility. It provides clear error messages for common failure modes, such as an invalid repository slug or the inability to connect to the daemon, guiding the user toward a resolution. +This client is a self-contained, robust utility. It provides clear error +messages for common failure modes, such as an invalid repository slug or the +inability to connect to the daemon, guiding the user toward a resolution. ## Section 3: Design of the `comenqd` Daemon -The `comenqd` daemon is the heart of the system. It is a stateful, asynchronous, long-running process responsible for all interactions with the persistent queue and the GitHub API. Its design is centered around the `tokio` runtime to handle concurrent operations efficiently. +The `comenqd` daemon is the heart of the system. It is a stateful, +asynchronous, long-running process responsible for all interactions with the +persistent queue and the GitHub API. Its design is centered around the `tokio` +runtime to handle concurrent operations efficiently. ### 3.1. The Asynchronous Core and Task Structure -The daemon's architecture is built on `tokio`'s cooperative multitasking model. Upon startup, the `main` function will initialize necessary resources (configuration, logger, queue) and then spawn two primary, independent asynchronous tasks that run concurrently for the lifetime of the daemon: +The daemon's architecture is built on `tokio`'s cooperative multitasking model. +Upon startup, the `main` function will initialize necessary resources +(configuration, logger, queue) and then spawn two primary, independent +asynchronous tasks that run concurrently for the lifetime of the daemon: -1. `task_listen_for_requests`**:** This task is the daemon's public-facing interface. It binds to the UDS and listens for incoming connections from `comenq` clients. Its sole job is to accept requests and place them into the queue as quickly as possible. +1. `task_listen_for_requests`**:** This task is the daemon's public-facing + interface. It binds to the UDS and listens for incoming connections from + `comenq` clients. Its sole job is to accept requests and place them into the + queue as quickly as possible. -2. `task_process_queue`**:** This is the main worker task. It operates in a serialized loop, pulling one job at a time from the queue, processing it (i.e., posting the comment to GitHub), and then observing the mandatory 15-minute cooldown period. +2. `task_process_queue`**:** This is the main worker task. It operates in a + serialized loop, pulling one job at a time from the queue, processing it + (i.e., posting the comment to GitHub), and then observing the mandatory + 15-minute cooldown period. -This concurrent design ensures that the daemon remains responsive to new client requests even while the worker task is in its long sleep phase. A request can be accepted and enqueued in milliseconds, while the worker task independently processes the queue at its own deliberate pace. +This concurrent design ensures that the daemon remains responsive to new client +requests even while the worker task is in its long sleep phase. A request can +be accepted and enqueued in milliseconds, while the worker task independently +processes the queue at its own deliberate pace. ### 3.2. The Persistent Job Queue with `yaque` -A core requirement for the daemon is fault tolerance. If the daemon or the entire server restarts, pending comments must not be lost. This rules out simple in-memory queues like `std::collections::VecDeque` 26 and necessitates a disk-backed, persistent solution. +A core requirement for the daemon is fault tolerance. If the daemon or the +entire server restarts, pending comments must not be lost. This rules out +simple in-memory queues like `std::collections::VecDeque` 26 and necessitates a +disk-backed, persistent solution. -The `yaque` crate is selected as the ideal queue implementation for this project.14 While other file-based queues exist 17, +The `yaque` crate is selected as the ideal queue implementation for this +project.14 While other file-based queues exist 17, -`yaque` offers a unique combination of features perfectly suited to this daemon's needs: +`yaque` offers a unique combination of features perfectly suited to this +daemon's needs: -- **Natively Asynchronous:** It is built on `mio` and integrates seamlessly with the `tokio` runtime without requiring blocking operations.14 +- **Natively Asynchronous:** It is built on `mio` and integrates seamlessly + with the `tokio` runtime without requiring blocking operations.14 -- **Persistence:** It stores queue data on the filesystem, ensuring durability across process restarts.14 +- **Persistence:** It stores queue data on the filesystem, ensuring durability + across process restarts.14 -- **Transactional Reads:** This is the most compelling feature. When an item is dequeued using `receiver.recv().await`, `yaque` returns a `RecvGuard`. The item is not permanently removed from the queue at this point. It is only removed when `guard.commit()` is explicitly called. If the `RecvGuard` is dropped without being committed (e.g., due to a program panic or an API error), the item is automatically and safely returned to the head of the queue. This "dead man's switch" mechanism provides a powerful "at-least-once" delivery guarantee, which is the cornerstone of the daemon's reliability.14 +- **Transactional Reads:** This is the most compelling feature. When an item is + dequeued using `receiver.recv().await`, `yaque` returns a `RecvGuard`. The + item is not permanently removed from the queue at this point. It is only + removed when `guard.commit()` is explicitly called. If the `RecvGuard` is + dropped without being committed (e.g., due to a program panic or an API + error), the item is automatically and safely returned to the head of the + queue. This "dead man's switch" mechanism provides a powerful "at-least-once" + delivery guarantee, which is the cornerstone of the daemon's reliability.14 -The queue will be initialized at a configurable path (e.g., `/var/lib/comenq/queue`) and will store the `CommentRequest` struct defined in the shared library. +The queue will be initialized at a configurable path (e.g., +`/var/lib/comenq/queue`) and will store the `CommentRequest` struct defined in +the shared library. ### 3.3. The UDS Listener and Request Ingestion (`task_listen_for_requests`) -This task is responsible for handling all client communication. It will be implemented as an asynchronous function spawned by the main `tokio` runtime. +This task is responsible for handling all client communication. It will be +implemented as an asynchronous function spawned by the main `tokio` runtime. Its workflow is as follows: -1. **Cleanup and Binding:** The task first attempts to remove any stale socket file from a previous run. It then creates and binds a `tokio::net::UnixListener` to the configured socket path.2 +1. **Cleanup and Binding:** The task first attempts to remove any stale socket + file from a previous run. It then creates and binds a + `tokio::net::UnixListener` to the configured socket path.2 -2. **Set Permissions:** After binding, it must set the permissions on the socket file to enforce the security model (e.g., `0o660`), allowing access only to the owner user and group. +2. **Set Permissions:** After binding, it must set the permissions on the + socket file to enforce the security model (e.g., `0o660`), allowing access + only to the owner user and group. -3. **Accept Loop:** The task enters an infinite `loop`, waiting for new client connections via `listener.accept().await`.30 +3. **Accept Loop:** The task enters an infinite `loop`, waiting for new client + connections via `listener.accept().await`.30 -4. **Spawn Connection Handler:** To ensure the listener is never blocked, upon accepting a new connection, it immediately spawns a new, short-lived `tokio` task to handle that specific client. +4. **Spawn Connection Handler:** To ensure the listener is never blocked, upon + accepting a new connection, it immediately spawns a new, short-lived `tokio` + task to handle that specific client. -5. **Handle Client:** This per-client task will read all data from the `UnixStream`, deserialize the received JSON into a `CommentRequest` struct, and then use the sender half of the `yaque` channel to enqueue the request. After enqueuing, the task terminates. +5. **Handle Client:** This per-client task will read all data from the + `UnixStream`, deserialize the received JSON into a `CommentRequest` struct, + and then use the sender half of the `yaque` channel to enqueue the request. + After enqueuing, the task terminates. -This design makes the request ingestion process highly concurrent and robust, capable of handling multiple simultaneous client connections without impacting the main worker loop. +This design makes the request ingestion process highly concurrent and robust, +capable of handling multiple simultaneous client connections without impacting +the main worker loop. ### 3.4. The GitHub Comment-Posting Worker (`task_process_queue`) -This task implements the core business logic of the service. It runs in a simple, infinite loop, ensuring that comments are processed one by one with the required delay. +This task implements the core business logic of the service. It runs in a +simple, infinite loop, ensuring that comments are processed one by one with the +required delay. #### 3.4.1. `octocrab` Initialization and API Usage -The `octocrab` client will be initialized once at daemon startup, using a Personal Access Token (PAT) securely loaded from the configuration file. +The `octocrab` client will be initialized once at daemon startup, using a +Personal Access Token (PAT) securely loaded from the configuration file. -A critical detail for a successful implementation is using the correct GitHub API endpoint. While one might intuitively look for a "create comment" method within the Pull Request API, general comments on a PR are, in fact, considered part of the underlying Issue. This non-obvious fact is highlighted in GitHub's own documentation patterns.31 Therefore, the correct +A critical detail for a successful implementation is using the correct GitHub +API endpoint. While one might intuitively look for a "create comment" method +within the Pull Request API, general comments on a PR are, in fact, considered +part of the underlying Issue. This non-obvious fact is highlighted in GitHub's +own documentation patterns.31 Therefore, the correct -`octocrab` method to use is `issues().create_comment()`, not a method on the `pulls()` handler.32 +`octocrab` method to use is `issues().create_comment()`, not a method on the +`pulls()` handler.32 The correct invocation will be: -Rust - -``` +```rust octocrab.issues("owner", "repo").create_comment(pr_number, "body").await?; ``` @@ -325,31 +455,54 @@ octocrab.issues("owner", "repo").create_comment(pr_number, "body").await?; The worker task's loop consists of the following steps: -1. **Dequeue Job:** It calls `receiver.recv().await?` to receive the next `CommentRequest` wrapped in a `yaque::RecvGuard`. This operation will block asynchronously until a job is available in the queue. +1. **Dequeue Job:** It calls `receiver.recv().await?` to receive the next + `CommentRequest` wrapped in a `yaque::RecvGuard`. This operation will block + asynchronously until a job is available in the queue. -2. **Post Comment:** It constructs and sends the API request to GitHub using the `octocrab` client and the data from the dequeued job. +2. **Post Comment:** It constructs and sends the API request to GitHub using + the `octocrab` client and the data from the dequeued job. 3. **Handle Result:** - - **On API Success:** The task immediately calls `guard.commit()` to finalize the transaction and permanently remove the job from the queue. It then logs the successful post. + - **On API Success:** The task immediately calls `guard.commit()` to + finalize the transaction and permanently remove the job from the queue. It + then logs the successful post. - - **On API Failure:** The task logs the error from the GitHub API. The `guard` is simply dropped. `yaque`'s transactional guarantee ensures the job is automatically returned to the queue, ready to be retried on the next iteration of the loop. For more advanced error handling, a retry counter could be added to the `CommentRequest` to prevent infinite loops for unfixable errors, eventually moving the job to a "dead-letter" queue. + - **On API Failure:** The task logs the error from the GitHub API. The + `guard` is simply dropped. `yaque`'s transactional guarantee ensures the + job is automatically returned to the queue, ready to be retried on the + next iteration of the loop. For more advanced error handling, a retry + counter could be added to the `CommentRequest` to prevent infinite loops + for unfixable errors, eventually moving the job to a "dead-letter" queue. -4. **Cooldown:** After successfully processing a job (or after a failed attempt), the task calls `tokio::time::sleep(Duration::from_secs(900)).await` to enforce the 15-minute cooling-off period. +4. **Cooldown:** After successfully processing a job (or after a failed + attempt), the task calls + `tokio::time::sleep(Duration::from_secs(900)).await` to enforce the + 15-minute cooling-off period. 5. The loop then repeats. -This workflow, built upon `yaque`'s transactional foundation, creates a highly resilient system that can tolerate both network failures and process crashes without losing data. +This workflow, built upon `yaque`'s transactional foundation, creates a highly +resilient system that can tolerate both network failures and process crashes +without losing data. ### 3.5. Daemon Configuration and Logging -For operational flexibility and security, the daemon's behavior must be controlled via a configuration file, not hard-coded values. A TOML file located at `/etc/comenqd/config.toml` is the conventional choice. +For operational flexibility and security, the daemon's behavior must be +controlled via a configuration file, not hard-coded values. A TOML file located +at `/etc/comenqd/config.toml` is the conventional choice. - - -

Parameter

Type

Description

Default Value

github_token

String

The GitHub Personal Access Token (PAT) used for authentication. This is a required field.

(none)

socket_path

PathBuf

The filesystem path for the Unix Domain Socket.

/run/comenq/comenq.sock

queue_path

PathBuf

The directory path for the persistent yaque queue data.

/var/lib/comenq/queue

log_level

String

The minimum log level to record (e.g., "info", "debug", "trace").

info

cooldown_period_seconds

u64

The cooling-off period in seconds after each comment post.

900

+| Parameter | Type | Description | Default Value | +| ----------------------- | ------- | ----------------------------------------------------------------------------------------- | ----------------------- | +| github_token | String | The GitHub Personal Access Token (PAT) used for authentication. This is a required field. | (none) | +| socket_path | PathBuf | The filesystem path for the Unix Domain Socket. | /run/comenq/comenq.sock | +| queue_path | PathBuf | The directory path for the persistent yaque queue data. | /var/lib/comenq/queue | +| log_level | String | The minimum log level to record (e.g., "info", "debug", "trace"). | info | +| cooldown_period_seconds | u64 | The cooling-off period in seconds after each comment post. | 900 | -Robust logging is non-negotiable for a background process. The `tracing` crate with `tracing-subscriber` will be used to provide structured, asynchronous logging. Key events to be logged include: +Robust logging is non-negotiable for a background process. The `tracing` crate +with `tracing-subscriber` will be used to provide structured, asynchronous +logging. Key events to be logged include: - Daemon startup and shutdown. @@ -367,28 +520,32 @@ Robust logging is non-negotiable for a background process. The `tracing` crate w - Entering and exiting the cooldown period. -When run as a `systemd` service, these logs will be automatically captured by the system's journal, making them easily accessible for administrators via `journalctl`. +When run as a `systemd` service, these logs will be automatically captured by +the system's journal, making them easily accessible for administrators via +`journalctl`. ## Section 4: Deployment and Operationalization -A well-designed application is only useful if it can be deployed and managed reliably. This section provides a practical guide to installing, configuring, and running the `comenqd` daemon as a robust system service on a modern Linux distribution using `systemd`. +A well-designed application is only useful if it can be deployed and managed +reliably. This section provides a practical guide to installing, configuring, +and running the `comenqd` daemon as a robust system service on a modern Linux +distribution using `systemd`. ### 4.1. Compilation and Installation -First, the project should be compiled in release mode to produce optimized binaries. - -Bash +First, the project should be compiled in release mode to produce optimized +binaries. -``` +```bash # From the root of the Rust project workspace cargo build --release ``` -After a successful build, the resulting binaries must be installed to standard locations in the filesystem. A simple installation script would perform the following actions: - -Bash +After a successful build, the resulting binaries must be installed to standard +locations in the filesystem. A simple installation script would perform the +following actions: -``` +```bash #!/bin/bash set -e @@ -418,11 +575,16 @@ chmod 770 /etc/comenqd echo "Installation complete. Please create /etc/comenqd/config.toml" ``` -This script establishes the necessary user and directory structure with security in mind, ensuring the daemon runs with the principle of least privilege. +This script establishes the necessary user and directory structure with +security in mind, ensuring the daemon runs with the principle of least +privilege. ### 4.2. Creating a `systemd` Service Unit -Running the daemon directly in a terminal is suitable for development but not for production. A `systemd` service unit file automates the daemon's lifecycle management, including startup on boot, automatic restarts on failure, and integration with the system's logging infrastructure.33 +Running the daemon directly in a terminal is suitable for development but not +for production. A `systemd` service unit file automates the daemon's lifecycle +management, including startup on boot, automatic restarts on failure, and +integration with the system's logging infrastructure.33 The following `comenq.service` file should be placed in `/etc/systemd/system/`: @@ -464,17 +626,21 @@ WantedBy=multi-user.target **Analysis of Directives:** -- `User=comenq`, `Group=comenq`: Ensures the process runs as the unprivileged `comenq` user. +- `User=comenq`, `Group=comenq`: Ensures the process runs as the unprivileged + `comenq` user. -- `Restart=on-failure`: Instructs `systemd` to automatically restart the daemon if it exits with a non-zero status code (e.g., due to a panic). +- `Restart=on-failure`: Instructs `systemd` to automatically restart the daemon + if it exits with a non-zero status code (e.g., due to a panic). -- **Hardening Directives:** The block of `Protect*` and `Restrict*` directives significantly sandboxes the process, limiting its access to the host system. For example, `ProtectSystem=strict` makes most of the OS filesystem read-only to the daemon, and `PrivateTmp=true` gives it a private `/tmp` directory. These are modern best practices for securing system services. +- **Hardening Directives:** The block of `Protect*` and `Restrict*` directives + significantly sandboxes the process, limiting its access to the host system. + For example, `ProtectSystem=strict` makes most of the OS filesystem read-only + to the daemon, and `PrivateTmp=true` gives it a private `/tmp` directory. + These are modern best practices for securing system services. Once the file is in place, the service can be enabled and started: -Bash - -``` +```bash # Reload systemd to recognize the new service file sudo systemctl daemon-reload @@ -490,36 +656,53 @@ sudo systemctl status comenq.service ### 4.3. Security Posture and Best Practices -Security is a primary consideration in the design and deployment of this service. - -- **GitHub Token Security:** The GitHub Personal Access Token is the most sensitive piece of information. It must be created with the minimum necessary scopes (e.g., `public_repo` if only public repositories are targeted, or `repo` for private ones). The configuration file containing this token, `/etc/comenqd/config.toml`, must have its permissions strictly controlled: +Security is a primary consideration in the design and deployment of this +service. - Bash +- **GitHub Token Security:** The GitHub Personal Access Token is the most + sensitive piece of information. It must be created with the minimum necessary + scopes (e.g., `public_repo` if only public repositories are targeted, or + `repo` for private ones). The configuration file containing this token, + `/etc/comenqd/config.toml`, must have its permissions strictly controlled: - ``` +```bash sudo touch /etc/comenqd/config.toml sudo chown root:comenq /etc/comenqd/config.toml sudo chmod 640 /etc/comenqd/config.toml ``` - This ensures that only the `root` user and members of the `comenq` group can read the file. Since the daemon runs as `comenq`, it can read its configuration, but other unprivileged users on the system cannot. + This ensures that only the `root` user and members of the `comenq` group can + read the file. Since the daemon runs as `comenq`, it can read its + configuration, but other unprivileged users on the system cannot. -- **Filesystem Permissions:** The permissions set by the installation script are crucial: +- **Filesystem Permissions:** The permissions set by the installation script + are crucial: - - `/var/lib/comenq`: The daemon's state directory is owned exclusively by `comenq`, preventing other users from tampering with the persistent queue. + - `/var/lib/comenq`: The daemon's state directory is owned exclusively by + `comenq`, preventing other users from tampering with the persistent queue. - - `/run/comenq/comenq.sock`: The UDS is created in a directory also owned by `comenq`. The daemon should create the socket with permissions `0o660`, allowing read/write access for the `comenq` user and group. Other users on the system who are not in the `comenq` group will be denied access at the filesystem level, providing a robust and simple authentication mechanism for the client. + - `/run/comenq/comenq.sock`: The UDS is created in a directory also owned by + `comenq`. The daemon should create the socket with permissions `0o660`, + allowing read/write access for the `comenq` user and group. Other users on + the system who are not in the `comenq` group will be denied access at the + filesystem level, providing a robust and simple authentication mechanism + for the client. -By adhering to these deployment and security practices, `comenq` transitions from a piece of software into a well-behaved, secure, and manageable system service. +By adhering to these deployment and security practices, `comenq` transitions +from a piece of software into a well-behaved, secure, and manageable system +service. ## Section 5: Complete Source Code and Project Manifest -This final section provides the complete source code and project configuration, enabling a developer to build, install, and run the `comenq` service immediately. +This final section provides the complete source code and project configuration, +enabling a developer to build, install, and run the `comenq` service +immediately. ### 5.1. Project Structure -The project is organized as a Rust workspace to facilitate code sharing between the client and daemon binaries. +The project is organized as a Rust workspace to facilitate code sharing between +the client and daemon binaries. ``` comenq-project/ @@ -573,9 +756,7 @@ panic = "abort" ### 5.3. Source Code for Shared Library (`src/lib.rs`) -Rust - -``` +```rust // src/lib.rs use serde::{Deserialize, Serialize}; @@ -592,11 +773,10 @@ pub struct CommentRequest { ### 5.4. Source Code for `comenq` (Client) -The `crates/comenq/Cargo.toml` would simply list the workspace dependencies. The source code is as follows: +The `crates/comenq/Cargo.toml` would simply list the workspace dependencies. +The source code is as follows: -Rust - -``` +```rust // crates/comenq/src/main.rs use clap::Parser; use std::path::PathBuf; @@ -670,11 +850,10 @@ async fn main() { ### 5.5. Source Code for `comenqd` (Daemon) -The `crates/comenqd/Cargo.toml` would list the workspace dependencies. The daemon source is more complex, integrating all components. - -Rust +The `crates/comenqd/Cargo.toml` would list the workspace dependencies. The +daemon source is more complex, integrating all components. -``` +```rust // crates/comenqd/src/main.rs use anyhow::Result; use clap::Parser; @@ -809,72 +988,128 @@ async fn run_worker(config: Arc, mut rx: Receiver, octoc } ``` +### 5.6. Implementation Notes + +The repository initialises the workspace with `comenq-lib` at the root and two +binary crates under `crates/`. `CommentRequest` resides in the library and +derives both `Serialize` and `Deserialize`. The binaries currently contain stub +`main` functions awaiting further implementation. + ## Works cited - 1. A simple UNIX socket listener in Rust | Kyle M. Douglass, accessed on July 24, 2025, + 1. A simple UNIX socket listener in Rust | Kyle M. Douglass, accessed on July + 24, 2025, + - 2. UnixSocket in tokio::net - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, + 1. UnixSocket in tokio::net - Rust - [Docs.rs](http://Docs.rs), accessed on + July 24, 2025, + - 3. UnixStream in tokio::net - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, + 1. UnixStream in tokio::net - Rust - [Docs.rs](http://Docs.rs), accessed on + July 24, 2025, + - 4. Picking an argument parser - Rain's Rust CLI recommendations, accessed on July 24, 2025, + 1. Picking an argument parser - Rain's Rust CLI recommendations, accessed on + July 24, 2025, + - 5. clap-rs/clap: A full featured, fast Command Line Argument ... - GitHub, accessed on July 24, 2025, + 1. clap-rs/clap: A full featured, fast Command Line Argument … - GitHub, + accessed on July 24, 2025, - 6. Parsing command line arguments - Command Line Applications in Rust, accessed on July 24, 2025, + 1. Parsing command line arguments - Command Line Applications in Rust, + accessed on July 24, 2025, + - 7. clap - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, + 1. clap - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, + - 8. clap::\_derive::\_tutorial - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, + 1. clap::\_derive::\_tutorial - Rust - [Docs.rs](http://Docs.rs), accessed on + July 24, 2025, + - 9. XAMPPRocky/octocrab: A modern, extensible GitHub API Client for Rust., accessed on July 24, 2025, + 1. XAMPPRocky/octocrab: A modern, extensible GitHub API Client for Rust., + accessed on July 24, 2025, -10. octocrab - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, + 1. octocrab - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, + -11. octocrab/examples/custom\_[client.rs](http://client.rs) at main - GitHub, accessed on July 24, 2025, + 2. octocrab/examples/custom\_[client.rs](http://client.rs) at main - GitHub, + accessed on July 24, 2025, + -12. fussybeaver/roctokit: A rust client library for the GitHub v3 API, accessed on July 24, 2025, + 3. fussybeaver/roctokit: A rust client library for the GitHub v3 API, accessed + on July 24, 2025, -13. Calling a Web API - Rust Cookbook - GitHub Pages, accessed on July 24, 2025, + 4. Calling a Web API - Rust Cookbook - GitHub Pages, accessed on July 24, + 2025, + -14. Yaque is yet another disk-backed persistent queue for Rust. - GitHub, accessed on July 24, 2025, + 5. Yaque is yet another disk-backed persistent queue for Rust. - GitHub, + accessed on July 24, 2025, -15. yaque - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, + 6. yaque - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, + -16. queue_file - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, + 7. queue_file - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, + -17. queue-file - [crates.io](http://crates.io): Rust Package Registry, accessed on July 24, 2025, + 8. queue-file - [crates.io](http://crates.io): Rust Package Registry, accessed + on July 24, 2025, -18. semantic-machines/v-queue: simple file based queue on Rust - GitHub, accessed on July 24, 2025, + 9. semantic-machines/v-queue: simple file based queue on Rust - GitHub, + accessed on July 24, 2025, -19. codyps/rust-systemd: Rust interface to systemd c apis - GitHub, accessed on July 24, 2025, +10. codyps/rust-systemd: Rust interface to systemd c apis - GitHub, accessed on + July 24, 2025, -20. systemctl - [crates.io](http://crates.io): Rust Package Registry, accessed on July 24, 2025, +11. systemctl - [crates.io](http://crates.io): Rust Package Registry, accessed + on July 24, 2025, -21. systemctl - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, +12. systemctl - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, + -22. log - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, +13. log - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, + -23. Parsing arguments with Clap - Rust Adventure, accessed on July 24, 2025, +14. Parsing arguments with Clap - Rust Adventure, accessed on July 24, 2025, + -24. clap::\_derive - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, +15. clap::\_derive - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, + 2025, -25. Unix sockets, the basics in Rust - Emmanuel Bosquet, accessed on July 24, 2025, +16. Unix sockets, the basics in Rust - Emmanuel Bosquet, accessed on July 24, + 2025, -26. Mastering Queues: The Art of Ordered Processing in Rust | by Murat ..., accessed on July 24, 2025, +17. Mastering Queues: The Art of Ordered Processing in Rust | by Murat …, + accessed on July 24, 2025, + -27. Working with Queues in Rust - Basillica - Medium, accessed on July 24, 2025, +18. Working with Queues in Rust - Basillica - Medium, accessed on July 24, + 2025, + -28. queue - Keywords - [crates.io](http://crates.io): Rust Package Registry, accessed on July 24, 2025, +19. queue - Keywords - [crates.io](http://crates.io): Rust Package Registry, + accessed on July 24, 2025, -29. Connection issues with client/server communicating over socket - help - Rust Users Forum, accessed on July 24, 2025, +20. Connection issues with client/server communicating over socket - help - + Rust Users Forum, accessed on July 24, 2025, + -30. Example of reading from a Unix socket · Issue #9 · tokio-rs/tokio-uds - GitHub, accessed on July 24, 2025, +21. Example of reading from a Unix socket · Issue #9 · tokio-rs/tokio-uds - + GitHub, accessed on July 24, 2025, + -31. Working with Comments | GitHub API - LFE Documentation, accessed on July 24, 2025, +22. Working with Comments | GitHub API - LFE Documentation, accessed on July + 24, 2025, -32. PullRequestHandler in octocrab::pulls - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, +23. PullRequestHandler in octocrab::pulls - Rust - [Docs.rs](http://Docs.rs), + accessed on July 24, 2025, + -33. KillingSpark/rustysd: A service manager that is able to run "traditional" systemd services, written in rust - GitHub, accessed on July 24, 2025, +24. KillingSpark/rustysd: A service manager that is able to run "traditional" + systemd services, written in rust - GitHub, accessed on July 24, 2025, + -34. Rustysd: A systemd-compatible service manager written in rust - Reddit, accessed on July 24, 2025, \ No newline at end of file +25. Rustysd: A systemd-compatible service manager written in rust - Reddit, + accessed on July 24, 2025, + diff --git a/docs/roadmap.md b/docs/roadmap.md index efee33e..d5f2a83 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -2,72 +2,101 @@ ## Milestone 1: Project Scaffolding and Shared Library -- [ ] Set up the Rust workspace with two binary crates (`comenq`, `comenqd`) and a shared library crate (`comenq-lib`), as outlined in the project structure. +- [x] Set up the Rust workspace with two binary crates (`comenq`, `comenqd`) + and a shared library crate (`comenq-lib`), as outlined in the project + structure. -- [ ] Define the shared `CommentRequest` struct in the library crate, deriving `serde::Serialize` and `serde::Deserialize` for IPC. +- [x] Define the shared `CommentRequest` struct in the library crate, deriving + `serde::Serialize` and `serde::Deserialize` for IPC. -- [ ] Populate the root `Cargo.toml` with the specified workspace dependencies (`tokio`, `clap`, `serde`, `octocrab`, `yaque`, etc.). +- [x] Populate the root `Cargo.toml` with the specified workspace dependencies + (`tokio`, `clap`, `serde`, `octocrab`, `yaque`, etc.). ## Milestone 2: `comenq` CLI Client -- [ ] Implement the CLI argument parsing using `clap`'s derive macro to define the `Args` struct (`repo_slug`, `pr_number`, `comment_body`, `socket`). +- [ ] Implement the CLI argument parsing using `clap`'s derive macro to define + the `Args` struct (`repo_slug`, `pr_number`, `comment_body`, `socket`). - [ ] Add validation for the `owner/repo` slug format. -- [ ] Implement the client's `main` function to connect to the daemon's Unix Domain Socket using `tokio::net::UnixStream`. +- [ ] Implement the client's `main` function to connect to the daemon's Unix + Domain Socket using `tokio::net::UnixStream`. -- [ ] Serialize the `CommentRequest` payload to JSON and write it to the socket stream. +- [ ] Serialize the `CommentRequest` payload to JSON and write it to the socket + stream. -- [ ] Implement robust error handling and user feedback for connection failures or serialization errors. +- [ ] Implement robust error handling and user feedback for connection failures + or serialization errors. ## Milestone 3: `comenqd` Daemon Core -- [ ] Implement configuration loading from a TOML file (`/etc/comenqd/config.toml`) for parameters like `github_token`, `socket_path`, and `queue_path`. +- [ ] Implement configuration loading from a TOML file + (`/etc/comenqd/config.toml`) for parameters like `github_token`, + `socket_path`, and `queue_path`. -- [ ] Set up structured logging using the `tracing` and `tracing-subscriber` crates. +- [ ] Set up structured logging using the `tracing` and `tracing-subscriber` + crates. -- [ ] Initialize the `yaque` persistent queue at the path specified in the configuration. +- [ ] Initialize the `yaque` persistent queue at the path specified in the + configuration. -- [ ] Structure the daemon's `main` function to spawn the two primary, long-running `tokio` tasks: the UDS listener and the queue worker. +- [ ] Structure the daemon's `main` function to spawn the two primary, + long-running `tokio` tasks: the UDS listener and the queue worker. ## Milestone 4: `comenqd` Daemon - UDS Listener Task - [ ] Implement the `run_listener` async task. -- [ ] Bind a `tokio::net::UnixListener` to the configured socket path, ensuring any stale socket file is removed first. +- [ ] Bind a `tokio::net::UnixListener` to the configured socket path, ensuring + any stale socket file is removed first. - [ ] Set the socket file permissions to `0o660` to enforce the security model. -- [ ] Create an acceptance loop (`listener.accept().await`) that spawns a new task for each incoming client connection. +- [ ] Create an acceptance loop (`listener.accept().await`) that spawns a new + task for each incoming client connection. -- [ ] Implement the `handle_client` task to read the JSON payload, deserialize it into a `CommentRequest`, and enqueue it using the `yaque` sender. +- [ ] Implement the `handle_client` task to read the JSON payload, deserialize + it into a `CommentRequest`, and enqueue it using the `yaque` sender. ## Milestone 5: `comenqd` Daemon - Queue Worker Task - [ ] Implement the `run_worker` async task. -- [ ] Initialize the `octocrab` client with the GitHub PAT from the configuration. +- [ ] Initialize the `octocrab` client with the GitHub PAT from the + configuration. -- [ ] Create the main worker loop that dequeues jobs one at a time using `yaque`'s transactional `receiver.recv().await`. +- [ ] Create the main worker loop that dequeues jobs one at a time using + `yaque`'s transactional `receiver.recv().await`. -- [ ] Implement the logic to post a comment to GitHub using the correct `octocrab` method: `issues().create_comment()`. +- [ ] Implement the logic to post a comment to GitHub using the correct + `octocrab` method: `issues().create_comment()`. - [ ] On successful API post, explicitly commit the job using `guard.commit()`. -- [ ] On API failure, log the error and allow the `RecvGuard` to be dropped, automatically requeuing the job. +- [ ] On API failure, log the error and allow the `RecvGuard` to be dropped, + automatically requeuing the job. -- [ ] After processing each job (successfully or not), enforce the 15-minute (900 seconds) cooling-off period using `tokio::time::sleep`. +- [ ] After processing each job (successfully or not), enforce the 15-minute + (900 seconds) cooling-off period using `tokio::time::sleep`. ## Milestone 6: Deployment and Operationalization -- [ ] Write an installation script (`install.sh`) to compile release binaries and place them in standard system locations (`/usr/local/bin`, `/usr/local/sbin`). +- [ ] Write an installation script (`install.sh`) to compile release binaries + and place them in standard system locations (`/usr/local/bin`, + `/usr/local/sbin`). -- [ ] The script should create a dedicated system user (`comenq`) and the necessary directories (`/etc/comenqd`, `/var/lib/comenq`, `/run/comenq`) with secure permissions. +- [ ] The script should create a dedicated system user (`comenq`) and the + necessary directories (`/etc/comenqd`, `/var/lib/comenq`, `/run/comenq`) with + secure permissions. - [ ] Create a `systemd` service unit file (`comenq.service`) for the daemon. -- [ ] Configure the service to run as the `comenq` user, restart on failure, and include security hardening directives (`ProtectSystem`, `PrivateTmp`, etc.). +- [ ] Configure the service to run as the `comenq` user, restart on failure, + and include security hardening directives (`ProtectSystem`, `PrivateTmp`, + etc.). -- [ ] Document the process for securely creating the configuration file and setting its permissions (`chmod 640`). +- [ ] Document the process for securely creating the configuration file and + setting its permissions (`chmod 640`). -- [ ] Update the `README.md` and other documentation to reflect the final implementation and usage instructions. \ No newline at end of file +- [ ] Update the `README.md` and other documentation to reflect the final + implementation and usage instructions. diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..681c17e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,53 @@ +//! Shared types for the Comenq project. +//! +//! This library defines data structures exchanged between the client +//! and daemon. + +use serde::{Deserialize, Serialize}; + +/// Request sent from the client to the daemon. +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct CommentRequest { + /// Repository owner. + pub owner: String, + /// Repository name. + pub repo: String, + /// Pull request number. + pub pr_number: u64, + /// Comment body. + pub body: String, +} + +#[cfg(test)] +mod tests { + use super::CommentRequest; + use serde_json::{self, json}; + + #[test] + fn serialises_to_json() { + let request = CommentRequest { + owner: "octocat".into(), + repo: "hello-world".into(), + pr_number: 1, + body: "Hi".into(), + }; + let value = match serde_json::to_value(&request) { + Ok(v) => v, + Err(e) => panic!("failed to serialise: {e}"), + }; + let expected = json!({ + "owner": "octocat", + "repo": "hello-world", + "pr_number": 1, + "body": "Hi" + }); + assert_eq!(value, expected); + } + + #[test] + fn fails_to_parse_invalid_json() { + let data = "{ invalid json }"; + let result: Result = serde_json::from_str(data); + assert!(result.is_err()); + } +} diff --git a/tests/cucumber.rs b/tests/cucumber.rs new file mode 100644 index 0000000..37c2b5f --- /dev/null +++ b/tests/cucumber.rs @@ -0,0 +1,8 @@ +mod steps; +use cucumber::World as _; +use steps::CommentWorld; + +#[tokio::main] +async fn main() { + CommentWorld::run("tests/features").await; +} diff --git a/tests/features/comment_request.feature b/tests/features/comment_request.feature new file mode 100644 index 0000000..c1a4d6e --- /dev/null +++ b/tests/features/comment_request.feature @@ -0,0 +1,11 @@ +Feature: CommentRequest serialisation + + Scenario: serialising a valid request + Given a default comment request + When it is serialised + Then the JSON is correct + + Scenario: parsing invalid JSON + Given invalid JSON + When it is parsed + Then an error is returned diff --git a/tests/steps/comment_steps.rs b/tests/steps/comment_steps.rs new file mode 100644 index 0000000..8e9e813 --- /dev/null +++ b/tests/steps/comment_steps.rs @@ -0,0 +1,67 @@ +use comenq_lib::CommentRequest; +use cucumber::{World, given, then, when}; + +#[derive(Debug, Default, World)] +pub struct CommentWorld { + request: Option, + json: Option, + result: Option>, +} + +#[given("a default comment request")] +fn a_default_comment_request(world: &mut CommentWorld) { + world.request = Some(CommentRequest { + owner: "octocat".into(), + repo: "hello-world".into(), + pr_number: 1, + body: "Hi".into(), + }); +} + +#[when("it is serialised")] +fn it_is_serialised(world: &mut CommentWorld) { + if let Some(req) = world.request.take() { + world.json = match serde_json::to_string(&req) { + Ok(json) => Some(json), + Err(e) => panic!("serialisation failed: {e}"), + }; + } +} + +#[then("the JSON is correct")] +fn the_json_is(world: &mut CommentWorld) { + match world.json.take() { + Some(actual) => { + let act: serde_json::Value = serde_json::from_str(&actual) + .unwrap_or_else(|e| panic!("parse actual failed: {e}")); + let exp = serde_json::json!({ + "owner": "octocat", + "repo": "hello-world", + "pr_number": 1, + "body": "Hi" + }); + assert_eq!(act, exp); + } + None => panic!("missing json output"), + } +} + +#[given("invalid JSON")] +fn invalid_json(world: &mut CommentWorld) { + world.json = Some("{ invalid json }".to_string()); +} + +#[when("it is parsed")] +fn it_is_parsed(world: &mut CommentWorld) { + if let Some(json) = world.json.clone() { + world.result = Some(serde_json::from_str(&json)); + } +} + +#[then("an error is returned")] +fn an_error_is_returned(world: &mut CommentWorld) { + match world.result.take() { + Some(res) => assert!(res.is_err()), + None => panic!("no parse result"), + } +} diff --git a/tests/steps/mod.rs b/tests/steps/mod.rs new file mode 100644 index 0000000..3b60aa8 --- /dev/null +++ b/tests/steps/mod.rs @@ -0,0 +1,2 @@ +pub mod comment_steps; +pub use comment_steps::CommentWorld; From 7d947fa423da6f10b88141bfb86966b0b5f17d47 Mon Sep 17 00:00:00 2001 From: Leynos Date: Thu, 24 Jul 2025 12:16:57 +0100 Subject: [PATCH 2/5] Fix footnote formatting --- docs/comenq-design.md | 233 +++++++++++++++++------------------------- 1 file changed, 94 insertions(+), 139 deletions(-) diff --git a/docs/comenq-design.md b/docs/comenq-design.md index 04f13ea..1562b18 100644 --- a/docs/comenq-design.md +++ b/docs/comenq-design.md @@ -30,7 +30,7 @@ therefore decomposed into two distinct, cooperating processes: and submit a new comment request for queuing. This separation of concerns, inspired by established systems like Docker which -use a daemon-client model over a Unix socket , yields significant advantages: +use a daemon-client model over a Unix socket[^1], yields significant advantages: - **Persistence and Statefulness:** The daemon can maintain the queue and its internal timer state across many client invocations, ensuring that the @@ -76,10 +76,10 @@ The complete lifecycle of a request is illustrated in the following sequence: 1. Upon successful posting, the worker commits the job, permanently removing it from the queue. - 2. The worker task then enters a 15-minute sleep state (the "cooling-off + 1. The worker task then enters a 15-minute sleep state (the "cooling-off period"). - 3. After the sleep period elapses, the worker task returns to step 8, ready to + 1. After the sleep period elapses, the worker task returns to step 8, ready to process the next job in the queue. This architecture ensures that comment posting is strictly serialized and @@ -95,12 +95,12 @@ project requirements. | Component/Concern | Selected Crate/Library | Key Features & Rationale | Alternative(s) Considered | | -------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | ---------- | -| Asynchronous Runtime | tokio | The de-facto standard for asynchronous programming in Rust. It provides a high-performance, multi-threaded scheduler and a comprehensive suite of utilities for I/O, networking, and timers, including the essential UnixListener, UnixStream, and time::sleep components.2 Its maturity and extensive ecosystem make it the definitive choice for the daemon's core. | async-std | -| CLI Argument Parsing | clap | The most popular and feature-rich CLI argument parsing library for Rust.4 The | derive feature offers an exceptionally ergonomic and declarative way to define the CLI's structure, automatically generating argument parsing, validation, and help text from a simple struct definition.6 | argh, pico-args 4 | -| GitHub API Client | octocrab | A modern, actively maintained, and extensible GitHub API client.9 It provides strongly-typed models for API responses and a builder pattern for requests, simplifying interaction with the GitHub REST API. Its static API and support for custom middleware are valuable for building robust clients.11 | roctokit 12, manual | reqwest 13 | -| Persistent Queue | yaque | A disk-backed, persistent queue designed for asynchronous environments.14 Its most critical feature is | transactional reads via its RecvGuard mechanism. This ensures that a dequeued item is automatically returned to the queue if the program panics or fails before the item is explicitly committed, providing an "at-least-once" delivery guarantee essential for reliability.14 | queue-file 16, | v_queue 18 | +| Asynchronous Runtime | tokio | The de-facto standard for asynchronous programming in Rust. It provides a high-performance, multi-threaded scheduler and a comprehensive suite of utilities for I/O, networking, and timers, including the essential UnixListener, UnixStream, and time::sleep components.[^2] Its maturity and extensive ecosystem make it the definitive choice for the daemon's core. | async-std | +| CLI Argument Parsing | clap | The most popular and feature-rich CLI argument parsing library for Rust.[^4] The | derive feature offers an exceptionally ergonomic and declarative way to define the CLI's structure, automatically generating argument parsing, validation, and help text from a simple struct definition.[^6] | argh, pico-args 4 | +| GitHub API Client | octocrab | A modern, actively maintained, and extensible GitHub API client.[^9] It provides strongly-typed models for API responses and a builder pattern for requests, simplifying interaction with the GitHub REST API. Its static API and support for custom middleware are valuable for building robust clients.[^11] | roctokit 12, manual | reqwest 13 | +| Persistent Queue | yaque | A disk-backed, persistent queue designed for asynchronous environments.[^14] Its most critical feature is | transactional reads via its RecvGuard mechanism. This ensures that a dequeued item is automatically returned to the queue if the program panics or fails before the item is explicitly committed, providing an "at-least-once" delivery guarantee essential for reliability.[^14] | queue-file 16, | v_queue 18 | | IPC Serialization | serde / serde_json | serde is the universal framework for serialization and deserialization in Rust. serde_json provides a straightforward implementation for the JSON data format, which is chosen for its human-readability (aiding in debugging) and widespread support. | bincode, prost | -| Systemd Integration | systemd (crate) | Provides native Rust bindings for interacting with the systemd journal and daemon notification APIs.19 While the primary deployment mechanism is a | .service file, this crate can be used for more advanced integration, such as sending readiness notifications. | systemctl (crate) 20 | +| Systemd Integration | systemd (crate) | Provides native Rust bindings for interacting with the systemd journal and daemon notification APIs.[^19] While the primary deployment mechanism is a | .service file, this crate can be used for more advanced integration, such as sending readiness notifications. | systemctl (crate) 20 | | Logging | tracing / tracing-subscriber | A modern, structured, and asynchronous-aware logging and diagnostics framework. It is the standard choice for tokio-based applications, providing contextual information that is superior to traditional line-based logging. | log / env_logger 22 | | | @@ -118,7 +118,8 @@ The command-line interface is the primary point of interaction for the user. A well-designed CLI is intuitive and self-documenting. We will use `clap`'s `derive` macro, which allows us to define the entire CLI structure declaratively within a Rust `struct`. This approach is highly recommended for -its clarity and maintainability compared to the more verbose builder pattern.6 +its clarity and maintainability compared to the more verbose builder +pattern.[^6] The CLI will accept three required positional arguments, matching the user's requested invocation format: `comenq `. @@ -155,15 +156,15 @@ pub struct Args { ``` The `#[derive(Parser)]` attribute instructs `clap` to generate all the -necessary parsing logic.7 The doc comments ( +necessary parsing logic.[^7] The doc comments ( `///`) are automatically converted into help messages, which are displayed when -the user runs `comenq --help`. This feature makes the tool self-documenting.24 -The +the user runs `comenq --help`. This feature makes the tool +self-documenting.[^24] The `#[arg(...)]` attributes provide fine-grained control over each argument, such as defining a `default_value` for the socket path, making the client flexible -for different environments.8 +for different environments.[^8] ### 2.2. Client-Daemon IPC Protocol @@ -211,7 +212,7 @@ advantages for this application: throughput. - **Security:** This is the most critical advantage. A UDS is an entity in the - filesystem, like a file.25 This means it is subject to standard Unix + filesystem, like a file.[^25] This means it is subject to standard Unix filesystem permissions ( `chmod`, `chown`). The `comenqd` daemon can create the socket with @@ -370,16 +371,16 @@ simple in-memory queues like `std::collections::VecDeque` 26 and necessitates a disk-backed, persistent solution. The `yaque` crate is selected as the ideal queue implementation for this -project.14 While other file-based queues exist 17, +project.[^14] While other file-based queues exist 17, `yaque` offers a unique combination of features perfectly suited to this daemon's needs: - **Natively Asynchronous:** It is built on `mio` and integrates seamlessly - with the `tokio` runtime without requiring blocking operations.14 + with the `tokio` runtime without requiring blocking operations.[^14] - **Persistence:** It stores queue data on the filesystem, ensuring durability - across process restarts.14 + across process restarts.[^14] - **Transactional Reads:** This is the most compelling feature. When an item is dequeued using `receiver.recv().await`, `yaque` returns a `RecvGuard`. The @@ -388,7 +389,7 @@ daemon's needs: dropped without being committed (e.g., due to a program panic or an API error), the item is automatically and safely returned to the head of the queue. This "dead man's switch" mechanism provides a powerful "at-least-once" - delivery guarantee, which is the cornerstone of the daemon's reliability.14 + delivery guarantee, which is the cornerstone of the daemon's reliability.[^14] The queue will be initialized at a configurable path (e.g., `/var/lib/comenq/queue`) and will store the `CommentRequest` struct defined in @@ -403,14 +404,14 @@ Its workflow is as follows: 1. **Cleanup and Binding:** The task first attempts to remove any stale socket file from a previous run. It then creates and binds a - `tokio::net::UnixListener` to the configured socket path.2 + `tokio::net::UnixListener` to the configured socket path.[^2] 2. **Set Permissions:** After binding, it must set the permissions on the socket file to enforce the security model (e.g., `0o660`), allowing access only to the owner user and group. 3. **Accept Loop:** The task enters an infinite `loop`, waiting for new client - connections via `listener.accept().await`.30 + connections via `listener.accept().await`.[^30] 4. **Spawn Connection Handler:** To ensure the listener is never blocked, upon accepting a new connection, it immediately spawns a new, short-lived `tokio` @@ -440,10 +441,10 @@ A critical detail for a successful implementation is using the correct GitHub API endpoint. While one might intuitively look for a "create comment" method within the Pull Request API, general comments on a PR are, in fact, considered part of the underlying Issue. This non-obvious fact is highlighted in GitHub's -own documentation patterns.31 Therefore, the correct +own documentation patterns.[^31] Therefore, the correct `octocrab` method to use is `issues().create_comment()`, not a method on the -`pulls()` handler.32 +`pulls()` handler.[^32] The correct invocation will be: @@ -584,7 +585,7 @@ privilege. Running the daemon directly in a terminal is suitable for development but not for production. A `systemd` service unit file automates the daemon's lifecycle management, including startup on boot, automatic restarts on failure, and -integration with the system's logging infrastructure.33 +integration with the system's logging infrastructure.[^33] The following `comenq.service` file should be placed in `/etc/systemd/system/`: @@ -997,119 +998,73 @@ derives both `Serialize` and `Deserialize`. The binaries currently contain stub ## Works cited - 1. A simple UNIX socket listener in Rust | Kyle M. Douglass, accessed on July - 24, 2025, - - - 1. UnixSocket in tokio::net - Rust - [Docs.rs](http://Docs.rs), accessed on - July 24, 2025, - - - 1. UnixStream in tokio::net - Rust - [Docs.rs](http://Docs.rs), accessed on - July 24, 2025, - - - 1. Picking an argument parser - Rain's Rust CLI recommendations, accessed on - July 24, 2025, - - - 1. clap-rs/clap: A full featured, fast Command Line Argument … - GitHub, - accessed on July 24, 2025, - - 1. Parsing command line arguments - Command Line Applications in Rust, - accessed on July 24, 2025, - - - 1. clap - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, - - - 1. clap::\_derive::\_tutorial - Rust - [Docs.rs](http://Docs.rs), accessed on - July 24, 2025, - - - 1. XAMPPRocky/octocrab: A modern, extensible GitHub API Client for Rust., - accessed on July 24, 2025, - - 1. octocrab - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, - - - 2. octocrab/examples/custom\_[client.rs](http://client.rs) at main - GitHub, - accessed on July 24, 2025, - - - 3. fussybeaver/roctokit: A rust client library for the GitHub v3 API, accessed - on July 24, 2025, - - 4. Calling a Web API - Rust Cookbook - GitHub Pages, accessed on July 24, - 2025, - - - 5. Yaque is yet another disk-backed persistent queue for Rust. - GitHub, - accessed on July 24, 2025, - - 6. yaque - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, - - - 7. queue_file - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, - - - 8. queue-file - [crates.io](http://crates.io): Rust Package Registry, accessed - on July 24, 2025, - - 9. semantic-machines/v-queue: simple file based queue on Rust - GitHub, - accessed on July 24, 2025, - -10. codyps/rust-systemd: Rust interface to systemd c apis - GitHub, accessed on - July 24, 2025, - -11. systemctl - [crates.io](http://crates.io): Rust Package Registry, accessed - on July 24, 2025, - -12. systemctl - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, - - -13. log - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, - - -14. Parsing arguments with Clap - Rust Adventure, accessed on July 24, 2025, - - -15. clap::\_derive - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, - 2025, - -16. Unix sockets, the basics in Rust - Emmanuel Bosquet, accessed on July 24, - 2025, - -17. Mastering Queues: The Art of Ordered Processing in Rust | by Murat …, - accessed on July 24, 2025, - - -18. Working with Queues in Rust - Basillica - Medium, accessed on July 24, - 2025, - - -19. queue - Keywords - [crates.io](http://crates.io): Rust Package Registry, - accessed on July 24, 2025, - -20. Connection issues with client/server communicating over socket - help - - Rust Users Forum, accessed on July 24, 2025, - - -21. Example of reading from a Unix socket · Issue #9 · tokio-rs/tokio-uds - - GitHub, accessed on July 24, 2025, - - -22. Working with Comments | GitHub API - LFE Documentation, accessed on July - 24, 2025, - -23. PullRequestHandler in octocrab::pulls - Rust - [Docs.rs](http://Docs.rs), - accessed on July 24, 2025, - - -24. KillingSpark/rustysd: A service manager that is able to run "traditional" - systemd services, written in rust - GitHub, accessed on July 24, 2025, - - -25. Rustysd: A systemd-compatible service manager written in rust - Reddit, - accessed on July 24, 2025, - +[^1]: A simple UNIX socket listener in Rust | Kyle M. Douglass, accessed on July + 24, 2025, + +[^2]: UnixSocket in tokio::net - Rust - [Docs.rs](http://Docs.rs), accessed on + July 24, 2025, + + July 24, 2025, + +[^4]: Picking an argument parser - Rain's Rust CLI recommendations, accessed on + July 24, 2025, + + accessed on July 24, 2025, +[^6]: Parsing command line arguments - Command Line Applications in Rust, + accessed on July 24, 2025, + +[^7]: clap - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, + +[^8]: clap::\_derive::\_tutorial - Rust - [Docs.rs](http://Docs.rs), accessed on + July 24, 2025, + +[^9]: XAMPPRocky/octocrab: A modern, extensible GitHub API Client for Rust., + accessed on July 24, 2025, + +[^11]: octocrab/examples/custom\_[client.rs](http://client.rs) at main - GitHub, + accessed on July 24, 2025, + + accessed + on July 24, 2025, + 2025, + +[^14]: Yaque is yet another disk-backed persistent queue for Rust. - GitHub, + accessed on July 24, 2025, + + + accessed + on July 24, 2025, + accessed on July 24, 2025, + +[^19]: codyps/rust-systemd: Rust interface to systemd c apis - GitHub, accessed + on + July 24, 2025, + accessed + on July 24, 2025, + + + +[^24]: clap::\_derive - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, + 2025, +[^25]: Unix sockets, the basics in Rust - Emmanuel Bosquet, accessed on July 24, + 2025, + accessed on July 24, 2025, + + 2025, + + accessed on July 24, 2025, + Rust Users Forum, accessed on July 24, 2025, + +[^30]: Example of reading from a Unix socket · Issue #9 · tokio-rs/tokio-uds - + GitHub, accessed on July 24, 2025, + +[^31]: Working with Comments | GitHub API - LFE Documentation, accessed on July + 24, 2025, +[^32]: PullRequestHandler in octocrab::pulls - Rust - [Docs.rs](http://Docs.rs), + accessed on July 24, 2025, + +[^33]: KillingSpark/rustysd: A service manager that is able to run "traditional" + systemd services, written in rust - GitHub, accessed on July 24, 2025, + + accessed on July 24, 2025, + From cd0f6975895e9a13b51f13d05baac1f69ba0c172 Mon Sep 17 00:00:00 2001 From: Leynos Date: Thu, 24 Jul 2025 12:39:40 +0100 Subject: [PATCH 3/5] Refine tests and documentation --- crates/comenqd/src/main.rs | 9 +- ...havioural-testing-in-rust-with-cucumber.md | 162 ++++++------- docs/comenq-design.md | 218 ++++++++---------- docs/roadmap.md | 4 +- src/lib.rs | 25 +- tests/features/comment_request.feature | 15 ++ tests/steps/comment_steps.rs | 31 ++- 7 files changed, 257 insertions(+), 207 deletions(-) diff --git a/crates/comenqd/src/main.rs b/crates/comenqd/src/main.rs index e7848fa..367988a 100644 --- a/crates/comenqd/src/main.rs +++ b/crates/comenqd/src/main.rs @@ -1,3 +1,10 @@ +//! Entry point for the Comenqd daemon binary. +//! Spawns the background service that processes `CommentRequest`s received +//! from the CLI client and coordinates persistence. + +use tracing::info; + fn main() { - println!("Hello from Comenqd!"); + tracing_subscriber::fmt::init(); + info!("Comenqd daemon started"); } diff --git a/docs/behavioural-testing-in-rust-with-cucumber.md b/docs/behavioural-testing-in-rust-with-cucumber.md index ae8e91c..c474549 100644 --- a/docs/behavioural-testing-in-rust-with-cucumber.md +++ b/docs/behavioural-testing-in-rust-with-cucumber.md @@ -45,17 +45,17 @@ A Gherkin document is line-oriented, with most lines beginning with a specific keyword. The primary keywords give structure and meaning to the specifications.[^7] -| Keyword | Purpose | Simple Example | -| ---------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------- | -| Feature | Provides a high-level description of a software feature and groups related scenarios.[^3] | Feature: User Authentication | -| Scenario | Describes a single, concrete example of the feature's behaviour.[^3] | Scenario: Successful login with valid credentials | -| Given | Sets the initial context or preconditions for a scenario.[^5] | Given the user is on the login page | -| When | Describes the key action or event that triggers the behaviour being tested.[^1] | When the user enters their username and password | -| Then | Specifies the expected outcome or result of the action.[^9] | Then the user should be redirected to the dashboard | -| And, But | Used to add more steps to a Given, When, or Then clause without repetition, improving readability.[^3] | And the user's name should be displayed | -| Background | Defines a set of steps that run before every Scenario in a Feature, used for common setup.[^6] | Background: Given a registered user "Alice" exists | -| Scenario Outline | A template for running the same Scenario multiple times with different data sets.[^3] | Scenario Outline: Login with various credentials | -| Examples | A data table that provides the values for a Scenario Outline.[^3] | username | password | outcome | +| Keyword | Purpose | Simple Example | +| ---------------- | ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- | +| Feature | Provides a high-level description of a software feature and groups related scenarios.[^3] | Feature: User Authentication | +| Scenario | Describes a single, concrete example of the feature's behaviour.[^3] | Scenario: Successful login with valid credentials | +| Given | Sets the initial context or preconditions for a scenario.[^5] | Given the user is on the login page | +| When | Describes the key action or event that triggers the behaviour being tested.[^1] | When the user enters their username and password | +| Then | Specifies the expected outcome or result of the action.[^9] | Then the user should be redirected to the dashboard | +| And, But | Used to add more steps to a Given, When, or Then clause without repetition, improving readability.[^3] | And the user's name should be displayed | +| Background | Defines a set of steps that run before every Scenario in a Feature, used for common setup.[^6] | Background: Given a registered user "Alice" exists | +| Scenario Outline | A template for running the same Scenario multiple times with different data sets.[^3] | Scenario Outline: Login with various credentials | +| Examples | A data table that provides the values for a Scenario Outline.[^3] | Examples table header: username / password / outcome | ### 1.3 The Given-When-Then Idiom: A Universal Test Pattern @@ -109,8 +109,8 @@ console.[^13] | [dependencies] | tokio | The async runtime. Required with features like macros and rt-multi-thread.[^13] | | [dev-dependencies] | cucumber | The main testing framework crate.[^16] | | [dev-dependencies] | futures | Often needed for async operations, particularly with older examples or for specific combinators.[^18] | -| [[test]] | name | The name of the test-runner file (e.g., "cucumber"). This must match the filename in tests/. | -| [[test]] | harness | Must be set to `false` so cucumber can manage test execution and output.[^14] | +| \[[test]\] | name | The name of the test-runner file (e.g., "cucumber"). This must match the filename in tests/. | +| \[[test]\] | harness | Must be set to `false` so cucumber can manage test execution and output.[^14] | Here is a complete `Cargo.toml` configuration snippet: @@ -869,11 +869,11 @@ Test code should be organized in the same way as application code. `tests/features/authentication/`, `tests/features/product_catalog/`, etc. -- **Step Definitions:** Mirror the feature file structure in your `tests/steps/ - ` directory. Create a Rust module for each feature area (e.g., `tests/steps/ - authentication_steps.rs`, `tests/steps/catalog_steps.rs`). This prevents - having a single, massive step definition file and makes it easier to find the - code corresponding to a Gherkin step. +- **Step Definitions:** Mirror the feature file structure in your + `tests/steps/` directory. Create a Rust module for each feature area (e.g., + `tests/steps/ authentication_steps.rs`, `tests/steps/catalog_steps.rs`). This + prevents having a single, massive step definition file and makes it easier to + find the code corresponding to a Gherkin step. ## Part 7: Common Pitfalls and Troubleshooting @@ -1069,91 +1069,91 @@ aligned with what is needed. #### **Works cited** -[^1]: "Given When Then" Framework: a step-by-step guide with examples — Miro, - accessed on 14 July 2025, - +\[^1\]: "Given When Then" Framework: a step-by-step guide with examples — Miro, +accessed on 14 July 2025, -[^2]: *Is it acceptable to write a "Given When Then When Then" test in - Gherkin?* — Stack Overflow, accessed on 14 July 2025, - +\[^2\]: *Is it acceptable to write a "Given When Then When Then" test in +Gherkin?* — Stack Overflow, accessed on 14 July 2025, + -[^3]: *Gherkin in Testing: A Beginner's Guide* — Rafał Buczyński, Medium, - accessed on 14 July 2025, - +\[^3\]: *Gherkin in Testing: A Beginner's Guide* — Rafał Buczyński, Medium, +accessed on 14 July 2025, + -[^5]: *Given When Then* — Martin Fowler, accessed on 14 July 2025, - +\[^5\]: *Given When Then* — Martin Fowler, accessed on 14 July 2025, + -[^6]: How To Start Writing Gherkin Test Scenarios? - - [Selleo.com](http://Selleo.com), accessed on 14 July 2025, - +\[^6\]: How To Start Writing Gherkin Test Scenarios? - +[Selleo.com](http://Selleo.com), accessed on 14 July 2025, + -[^7]: *Reference — Cucumber*, accessed on 14 July 2025, - +\[^7\]: *Reference — Cucumber*, accessed on 14 July 2025, + -[^9]: Given-When-Then - Wikipedia, accessed on 14 July 2025, - +\[^9\]: Given-When-Then - Wikipedia, accessed on 14 July 2025, + -[^11]: *Writing scenarios with Gherkin syntax* — CucumberStudio Documentation, - accessed on 14 July 2025, - +\[^11\]: *Writing scenarios with Gherkin syntax* — CucumberStudio +Documentation, accessed on 14 July 2025, + -[^12]: *Cucumber Rust Book — Introduction*, accessed on 14 July 2025, - +\[^12\]: *Cucumber Rust Book — Introduction*, accessed on 14 July 2025, + -[^13]: Rust BDD tests with Cucumber - DEV Community, accessed on 14 July 2025 - +\[^13\]: Rust BDD tests with Cucumber - DEV Community, accessed on 14 July 2025 + -[^14]: *Cucumber-rs* — fully-native Cucumber testing framework for Rust with no - external test runners or dependencies. GitHub, accessed on 14 July 2025, - +\[^14\]: *Cucumber-rs* — fully-native Cucumber testing framework for Rust with +no external test runners or dependencies. GitHub, accessed on 14 July 2025, + -[^16]: cucumber - Rust - [Docs.rs](http://Docs.rs), accessed on 14 July 2025, - +\[^16\]: cucumber - Rust - [Docs.rs](http://Docs.rs), accessed on 14 July 2025, + -[^18]: *Quickstart* — Cucumber Rust Book, accessed on 14 July 2025, - +\[^18\]: *Quickstart* — Cucumber Rust Book, accessed on 14 July 2025, + - on 14 July 2025, - +```text +on 14 July 2025, + +``` -[^20]: Quickstart - Cucumber Rust Book, accessed on 14 July 2025, - +\[^20\]: Quickstart - Cucumber Rust Book, accessed on 14 July 2025, + -[^21]: Common Pitfalls and Troubleshooting in Cucumber - GeeksforGeeks, accessed - on July 14, 2025, - +\[^21\]: Common Pitfalls and Troubleshooting in Cucumber - GeeksforGeeks, +accessed on July 14, 2025, + -[^22]: How to do error handling in Rust and what are the common pitfalls? - - Stack Overflow, accessed on 14 July 2025, - +\[^22\]: How to do error handling in Rust and what are the common pitfalls? - +Stack Overflow, accessed on 14 July 2025, + -[^23]: Data tables - Cucumber Rust Book, accessed on 14 July 2025, - +\[^23\]: Data tables — Cucumber Rust Book, accessed on 14 July 2025, + -[^25]: Best practices for scenario writing | CucumberStudio Documentation +\[^25\]: Best practices for scenario writing | CucumberStudio Documentation -[^26]: Cucumber Best Practices to follow for efficient BDD Testing | by - KailashPathak - Medium, accessed on 14 July 2025, - +\[^26\]: Cucumber Best Practices to follow for efficient BDD Testing | by +KailashPathak - Medium, accessed on 14 July 2025, + -[^27]: Rust Solutions - WireMock, accessed on 14 July 2025, - +\[^27\]: Rust Solutions - WireMock, accessed on 14 July 2025, + -[^30]: Common Challenges in Cucumber Testing and How to Overcome Them - Medium, - accessed on July 14, 2025, - +\[^30\]: Common Challenges in Cucumber Testing and How to Overcome Them - +Medium, accessed on July 14, 2025, + -[^31]: Cucumber in cucumber - Rust - [Docs.rs](http://Docs.rs), accessed on - 14 July 2025, - +\[^31\]: Cucumber in cucumber - Rust - [Docs.rs](http://Docs.rs), accessed on +14 July 2025, -[^32]: CLI (command-line interface) - Cucumber Rust Book, accessed on - 14 July 2025, +\[^32\]: CLI (command-line interface) - Cucumber Rust Book, accessed on 14 July +2025, -[^33]: Continuous Integration - Cucumber, accessed on 14 July 2025, - +\[^33\]: Continuous Integration - Cucumber, accessed on 14 July 2025, + -[^35]: Setting up effective CI/CD for Rust projects - a short primer - - [shuttle.dev](http://shuttle.dev), accessed on 14 July 2025, - +\[^35\]: Setting up effective CI/CD for Rust projects - a short primer - +[shuttle.dev](http://shuttle.dev), accessed on 14 July 2025, + diff --git a/docs/comenq-design.md b/docs/comenq-design.md index 1562b18..f878ccb 100644 --- a/docs/comenq-design.md +++ b/docs/comenq-design.md @@ -1,4 +1,3 @@ - # A Fault-Tolerant GitHub Comment Queuing Service in Rust ## Section 1: System Architecture and Core Component Selection @@ -47,40 +46,40 @@ use a daemon-client model over a Unix socket[^1], yields significant advantages: The complete lifecycle of a request is illustrated in the following sequence: - 1. A user on the host machine invokes the `comenq` client via a command like - `ssh mybox comenq owner/repo 123 "My comment"`. +1. A user on the host machine invokes the `comenq` client via a command like + `ssh mybox comenq owner/repo 123 "My comment"`. - 1. The `comenq` client parses the command-line arguments. +2. The `comenq` client parses the command-line arguments. - 1. The client establishes a connection to the `comenqd` daemon over a local - Unix Domain Socket (UDS). +3. The client establishes a connection to the `comenqd` daemon over a local + Unix Domain Socket (UDS). - 1. The client serializes the comment data into a predefined format (JSON) and - transmits it to the daemon. +4. The client serializes the comment data into a predefined format (JSON) and + transmits it to the daemon. - 1. The `comenqd` daemon, listening on the UDS, accepts the connection, reads - the data, and deserializes it into a job request. +5. The `comenqd` daemon, listening on the UDS, accepts the connection, reads + the data, and deserializes it into a job request. - 1. The daemon validates the request and pushes it onto a persistent, - disk-backed queue. +6. The daemon validates the request and pushes it onto a persistent, + disk-backed queue. - 1. The daemon immediately sends an acknowledgment of receipt back to the - client, which then exits. +7. The daemon immediately sends an acknowledgment of receipt back to the + client, which then exits. - 1. A separate, dedicated worker task within the daemon continuously monitors - the queue. It dequeues one job at a time. +8. A separate, dedicated worker task within the daemon continuously monitors + the queue. It dequeues one job at a time. - 1. The worker task uses an authenticated client to post the comment to the - GitHub API. +9. The worker task uses an authenticated client to post the comment to the + GitHub API. - 1. Upon successful posting, the worker commits the job, permanently removing - it from the queue. +10. Upon successful posting, the worker commits the job, permanently removing + it from the queue. - 1. The worker task then enters a 15-minute sleep state (the "cooling-off - period"). +11. The worker task then enters a 15-minute sleep state (the "cooling-off + period"). - 1. After the sleep period elapses, the worker task returns to step 8, ready to - process the next job in the queue. +12. After the sleep period elapses, the worker task returns to step 8, ready to + process the next job in the queue. This architecture ensures that comment posting is strictly serialized and paced, directly addressing the primary goal of avoiding API rate limits. @@ -93,16 +92,16 @@ for each major component of the `comenq` service, along with a detailed justification for each selection based on an analysis of available tools and project requirements. -| Component/Concern | Selected Crate/Library | Key Features & Rationale | Alternative(s) Considered | +| Component/Concern | Selected Crate/Library | Key Features & Rationale | Alternative(s) Considered | | -------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | ---------- | -| Asynchronous Runtime | tokio | The de-facto standard for asynchronous programming in Rust. It provides a high-performance, multi-threaded scheduler and a comprehensive suite of utilities for I/O, networking, and timers, including the essential UnixListener, UnixStream, and time::sleep components.[^2] Its maturity and extensive ecosystem make it the definitive choice for the daemon's core. | async-std | -| CLI Argument Parsing | clap | The most popular and feature-rich CLI argument parsing library for Rust.[^4] The | derive feature offers an exceptionally ergonomic and declarative way to define the CLI's structure, automatically generating argument parsing, validation, and help text from a simple struct definition.[^6] | argh, pico-args 4 | -| GitHub API Client | octocrab | A modern, actively maintained, and extensible GitHub API client.[^9] It provides strongly-typed models for API responses and a builder pattern for requests, simplifying interaction with the GitHub REST API. Its static API and support for custom middleware are valuable for building robust clients.[^11] | roctokit 12, manual | reqwest 13 | -| Persistent Queue | yaque | A disk-backed, persistent queue designed for asynchronous environments.[^14] Its most critical feature is | transactional reads via its RecvGuard mechanism. This ensures that a dequeued item is automatically returned to the queue if the program panics or fails before the item is explicitly committed, providing an "at-least-once" delivery guarantee essential for reliability.[^14] | queue-file 16, | v_queue 18 | -| IPC Serialization | serde / serde_json | serde is the universal framework for serialization and deserialization in Rust. serde_json provides a straightforward implementation for the JSON data format, which is chosen for its human-readability (aiding in debugging) and widespread support. | bincode, prost | -| Systemd Integration | systemd (crate) | Provides native Rust bindings for interacting with the systemd journal and daemon notification APIs.[^19] While the primary deployment mechanism is a | .service file, this crate can be used for more advanced integration, such as sending readiness notifications. | systemctl (crate) 20 | -| Logging | tracing / tracing-subscriber | A modern, structured, and asynchronous-aware logging and diagnostics framework. It is the standard choice for tokio-based applications, providing contextual information that is superior to traditional line-based logging. | log / env_logger 22 | -| | +| Asynchronous Runtime | tokio | The de-facto standard for asynchronous programming in Rust. It provides a high-performance, multithreaded scheduler and a comprehensive suite of utilities for I/O, networking, and timers, including the essential UnixListener, UnixStream, and time::sleep components.[^2] Its maturity and extensive ecosystem make it the definitive choice for the daemon's core. | async-std | +| CLI Argument Parsing | clap | The most popular and feature-rich CLI argument parsing library for Rust.[^4] The | derive feature offers an exceptionally ergonomic and declarative way to define the CLI's structure, automatically generating argument parsing, validation, and help text from a simple struct definition.[^6] | argh, pico-args 4 | +| GitHub API Client | octocrab | A modern, actively maintained, and extensible GitHub API client.[^9] It provides strongly-typed models for API responses and a builder pattern for requests, simplifying interaction with the GitHub REST API. Its static API and support for custom middleware are valuable for building robust clients.[^11] | roctokit 12, manual | reqwest 13 | +| Persistent Queue | yaque | A disk-backed, persistent queue designed for asynchronous environments.[^14] Its most critical feature is | transactional reads via its RecvGuard mechanism. This ensures that a dequeued item is automatically returned to the queue if the program panics or fails before the item is explicitly committed, providing an "at-least-once" delivery guarantee essential for reliability.[^14] | queue-file 16, | v_queue 18 | +| IPC Serialization | serde / serde_json | serde is the universal framework for serialization and deserialization in Rust. serde_json provides a straightforward implementation for the JSON data format, which is chosen for its human-readability (aiding in debugging) and widespread support. | bincode, prost | +| Systemd Integration | systemd (crate) | Provides native Rust bindings for interacting with the systemd journal and daemon notification APIs.[^19] While the primary deployment mechanism is a | .service file, this crate can be used for more advanced integration, such as sending readiness notifications. | systemctl (crate) 20 | +| Logging | tracing / tracing-subscriber | A modern, structured, and asynchronous-aware logging and diagnostics framework. It is the standard choice for tokio-based applications, providing contextual information that is superior to traditional line-based logging. | log / env_logger 22 | +| | ## Section 2: Design of the `comenq` CLI Client @@ -115,16 +114,15 @@ Unix Domain Socket. ### 2.1. Defining the Command-Line Interface with `clap` The command-line interface is the primary point of interaction for the user. A -well-designed CLI is intuitive and self-documenting. We will use `clap`'s -`derive` macro, which allows us to define the entire CLI structure -declaratively within a Rust `struct`. This approach is highly recommended for -its clarity and maintainability compared to the more verbose builder +well-designed CLI is intuitive and self-documenting. The `clap` `derive` macro +defines the entire CLI structure declaratively within a Rust `struct`, +providing clarity and maintainability compared to the more verbose builder pattern.[^6] The CLI will accept three required positional arguments, matching the user's requested invocation format: `comenq `. -The following code defines the `Args` struct that represents our CLI. This will +The following code defines the `Args` struct that represents the CLI. This will be the core of the `comenq` client's `main.rs`. ```rust @@ -344,7 +342,7 @@ runtime to handle concurrent operations efficiently. ### 3.1. The Asynchronous Core and Task Structure The daemon's architecture is built on `tokio`'s cooperative multitasking model. -Upon startup, the `main` function will initialize necessary resources +Upon startup, the `main` function will initialise necessary resources (configuration, logger, queue) and then spawn two primary, independent asynchronous tasks that run concurrently for the lifetime of the daemon: @@ -391,7 +389,7 @@ daemon's needs: queue. This "dead man's switch" mechanism provides a powerful "at-least-once" delivery guarantee, which is the cornerstone of the daemon's reliability.[^14] -The queue will be initialized at a configurable path (e.g., +The queue will be initialised at a configurable path (e.g., `/var/lib/comenq/queue`) and will store the `CommentRequest` struct defined in the shared library. @@ -434,7 +432,7 @@ required delay. #### 3.4.1. `octocrab` Initialization and API Usage -The `octocrab` client will be initialized once at daemon startup, using a +The `octocrab` client will be initialised once at daemon startup, using a Personal Access Token (PAT) securely loaded from the configuration file. A critical detail for a successful implementation is using the correct GitHub @@ -591,7 +589,7 @@ The following `comenq.service` file should be placed in `/etc/systemd/system/`: Ini, TOML -``` +```ini [Unit] Description=GitHub Comment Enqueuing Daemon Documentation=https://github.com/your-repo/comenq @@ -671,11 +669,11 @@ service. sudo chown root:comenq /etc/comenqd/config.toml sudo chmod 640 /etc/comenqd/config.toml - ``` +``` - This ensures that only the `root` user and members of the `comenq` group can - read the file. Since the daemon runs as `comenq`, it can read its - configuration, but other unprivileged users on the system cannot. +This ensures that only the `root` user and members of the `comenq` group can +read the file. Since the daemon runs as `comenq`, it can read its +configuration, but other unprivileged users on the system cannot. - **Filesystem Permissions:** The permissions set by the installation script are crucial: @@ -705,7 +703,7 @@ immediately. The project is organized as a Rust workspace to facilitate code sharing between the client and daemon binaries. -``` +```text comenq-project/ ├── Cargo.toml ├── src/ @@ -728,7 +726,7 @@ This is the root `Cargo.toml` for the workspace. Ini, TOML -``` +```toml [workspace] members = [ "crates/comenq", @@ -998,73 +996,57 @@ derives both `Serialize` and `Deserialize`. The binaries currently contain stub ## Works cited -[^1]: A simple UNIX socket listener in Rust | Kyle M. Douglass, accessed on July - 24, 2025, - -[^2]: UnixSocket in tokio::net - Rust - [Docs.rs](http://Docs.rs), accessed on - July 24, 2025, - - July 24, 2025, - -[^4]: Picking an argument parser - Rain's Rust CLI recommendations, accessed on - July 24, 2025, - - accessed on July 24, 2025, -[^6]: Parsing command line arguments - Command Line Applications in Rust, - accessed on July 24, 2025, - -[^7]: clap - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, - -[^8]: clap::\_derive::\_tutorial - Rust - [Docs.rs](http://Docs.rs), accessed on - July 24, 2025, - -[^9]: XAMPPRocky/octocrab: A modern, extensible GitHub API Client for Rust., - accessed on July 24, 2025, - -[^11]: octocrab/examples/custom\_[client.rs](http://client.rs) at main - GitHub, - accessed on July 24, 2025, - - accessed - on July 24, 2025, - 2025, - -[^14]: Yaque is yet another disk-backed persistent queue for Rust. - GitHub, - accessed on July 24, 2025, - - - accessed - on July 24, 2025, - accessed on July 24, 2025, - -[^19]: codyps/rust-systemd: Rust interface to systemd c apis - GitHub, accessed - on - July 24, 2025, - accessed - on July 24, 2025, - - - -[^24]: clap::\_derive - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, - 2025, -[^25]: Unix sockets, the basics in Rust - Emmanuel Bosquet, accessed on July 24, - 2025, - accessed on July 24, 2025, - - 2025, - - accessed on July 24, 2025, - Rust Users Forum, accessed on July 24, 2025, - -[^30]: Example of reading from a Unix socket · Issue #9 · tokio-rs/tokio-uds - - GitHub, accessed on July 24, 2025, - -[^31]: Working with Comments | GitHub API - LFE Documentation, accessed on July - 24, 2025, -[^32]: PullRequestHandler in octocrab::pulls - Rust - [Docs.rs](http://Docs.rs), - accessed on July 24, 2025, - -[^33]: KillingSpark/rustysd: A service manager that is able to run "traditional" - systemd services, written in rust - GitHub, accessed on July 24, 2025, - - accessed on July 24, 2025, - +\[^1\]: A simple UNIX socket listener in Rust | Kyle M. Douglass, accessed on +July 24, 2025, + +\[^2\]: UnixSocket in tokio::net - Rust - [Docs.rs](http://Docs.rs), accessed +on July 24, 2025, + July 24, 2025, + \[^4\]: Picking +an argument parser - Rain's Rust CLI recommendations, accessed on July 24, +2025, accessed +on July 24, 2025, \[^6\]: Parsing command +line arguments - Command Line Applications in Rust, accessed on July 24, 2025, + \[^7\]: clap - Rust - +[Docs.rs](http://Docs.rs), accessed on July 24, 2025, + \[^8\]: clap::\_derive::\_tutorial - Rust - +[Docs.rs](http://Docs.rs), accessed on July 24, 2025, + \[^9\]: +XAMPPRocky/octocrab: A modern, extensible GitHub API Client for Rust., accessed +on July 24, 2025, + \[^11\]: +octocrab/examples/custom\_[client.rs](http://client.rs) at main - GitHub, +accessed on July 24, 2025, + +accessed on July 24, 2025, 2025, + +\[^14\]: Yaque is yet another disk-backed persistent queue for Rust. - GitHub, +accessed on July 24, 2025, + accessed on July 24, 2025, + accessed on July 24, 2025, + \[^19\]: codyps/rust-systemd: +Rust interface to systemd c apis - GitHub, accessed on July 24, 2025, + accessed on July 24, 2025, + + + + \[^24\]: clap::\_derive - Rust - [Docs.rs](http://Docs.rs), accessed on July +24, 2025, \[^25\]: +Unix sockets, the basics in Rust - Emmanuel Bosquet, accessed on July 24, 2025, + accessed on July 24, 2025, + + 2025, +accessed on July 24, 2025, Rust Users +Forum, accessed on July 24, 2025, + + \[^30\]: Example of reading from a Unix socket · Issue #9 · tokio-rs/tokio-uds + +- GitHub, accessed on July 24, 2025, + \[^31\]: Working with Comments +| GitHub API - LFE Documentation, accessed on July 24, 2025, \[^32\]: PullRequestHandler in octocrab::pulls - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, | +2025, + +\[^33\]: KillingSpark/rustysd: A service manager that is able to run +"traditional" systemd services, written in rust - GitHub, accessed on July 24, +2025, accessed on July 24, 2025, + diff --git a/docs/roadmap.md b/docs/roadmap.md index d5f2a83..9ac4728 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -43,7 +43,7 @@ - [ ] Structure the daemon's `main` function to spawn the two primary, long-running `tokio` tasks: the UDS listener and the queue worker. -## Milestone 4: `comenqd` Daemon - UDS Listener Task +## Milestone 4: `comenqd` Daemon — UDS Listener Task - [ ] Implement the `run_listener` async task. @@ -58,7 +58,7 @@ - [ ] Implement the `handle_client` task to read the JSON payload, deserialize it into a `CommentRequest`, and enqueue it using the `yaque` sender. -## Milestone 5: `comenqd` Daemon - Queue Worker Task +## Milestone 5: `comenqd` Daemon — Queue Worker Task - [ ] Implement the `run_worker` async task. diff --git a/src/lib.rs b/src/lib.rs index 681c17e..7562f44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,10 +31,8 @@ mod tests { pr_number: 1, body: "Hi".into(), }; - let value = match serde_json::to_value(&request) { - Ok(v) => v, - Err(e) => panic!("failed to serialise: {e}"), - }; + let value = + serde_json::to_value(&request).unwrap_or_else(|e| panic!("serialisation failed: {e}")); let expected = json!({ "owner": "octocat", "repo": "hello-world", @@ -50,4 +48,23 @@ mod tests { let result: Result = serde_json::from_str(data); assert!(result.is_err()); } + + #[test] + fn fails_to_parse_missing_fields() { + let data = r#"{"owner": "octocat"}"#; + let result: Result = serde_json::from_str(data); + assert!(result.is_err()); + } + + #[test] + fn fails_to_parse_incorrect_field_types() { + let data = r#"{ + "owner": "octocat", + "repo": "hello-world", + "pr_number": "not a number", + "body": "Hi" + }"#; + let result: Result = serde_json::from_str(data); + assert!(result.is_err()); + } } diff --git a/tests/features/comment_request.feature b/tests/features/comment_request.feature index c1a4d6e..aee7070 100644 --- a/tests/features/comment_request.feature +++ b/tests/features/comment_request.feature @@ -9,3 +9,18 @@ Feature: CommentRequest serialisation Given invalid JSON When it is parsed Then an error is returned + + Scenario: parsing JSON missing the owner field + Given valid JSON missing the 'owner' field + When it is parsed + Then an error is returned + + Scenario: parsing JSON missing the repo field + Given valid JSON missing the 'repo' field + When it is parsed + Then an error is returned + + Scenario: parsing JSON missing all required fields + Given valid JSON missing all required fields + When it is parsed + Then an error is returned diff --git a/tests/steps/comment_steps.rs b/tests/steps/comment_steps.rs index 8e9e813..72d1edd 100644 --- a/tests/steps/comment_steps.rs +++ b/tests/steps/comment_steps.rs @@ -42,7 +42,7 @@ fn the_json_is(world: &mut CommentWorld) { }); assert_eq!(act, exp); } - None => panic!("missing json output"), + None => panic!("missing JSON output - test setup error"), } } @@ -51,6 +51,35 @@ fn invalid_json(world: &mut CommentWorld) { world.json = Some("{ invalid json }".to_string()); } +#[given("valid JSON missing the 'owner' field")] +fn valid_json_missing_owner(world: &mut CommentWorld) { + world.json = Some( + r#"{ + "repo": "hello-world", + "pr_number": 1, + "body": "Hi" + }"# + .to_string(), + ); +} + +#[given("valid JSON missing the 'repo' field")] +fn valid_json_missing_repo(world: &mut CommentWorld) { + world.json = Some( + r#"{ + "owner": "octocat", + "pr_number": 1, + "body": "Hi" + }"# + .to_string(), + ); +} + +#[given("valid JSON missing all required fields")] +fn valid_json_missing_all_required_fields(world: &mut CommentWorld) { + world.json = Some(r#"{"body": "Hi"}"#.to_string()); +} + #[when("it is parsed")] fn it_is_parsed(world: &mut CommentWorld) { if let Some(json) = world.json.clone() { From 1a8a6c84c8c03d865e93a36029518cec6051200f Mon Sep 17 00:00:00 2001 From: Leynos Date: Thu, 24 Jul 2025 21:32:05 +0100 Subject: [PATCH 4/5] Fix design doc issues and lint config --- .markdownlint-cli2.jsonc | 6 +-- Cargo.toml | 2 +- docs/comenq-design.md | 93 +++++++++++++++++++--------------------- 3 files changed, 47 insertions(+), 54 deletions(-) diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index f2fdd03..6a746b6 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -1,9 +1,7 @@ { - // Ignore MD013 (line length) inside tables since reflowing - // Markdown tables often breaks formatting and readability. "config": { "MD013": false, - "MD029": false, - "MD040": false + "MD029": { "style": "ordered" }, + "MD040": { "code_blocks": true } } } diff --git a/Cargo.toml b/Cargo.toml index 1dbc075..e5aa72f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ print_stderr = "deny" # 3. panic-prone operations unwrap_used = "deny" -expect_used = "deny" +expect_used = "warn" indexing_slicing = "deny" string_slice = "deny" integer_division = "deny" diff --git a/docs/comenq-design.md b/docs/comenq-design.md index f878ccb..06d7db1 100644 --- a/docs/comenq-design.md +++ b/docs/comenq-design.md @@ -95,11 +95,11 @@ project requirements. | Component/Concern | Selected Crate/Library | Key Features & Rationale | Alternative(s) Considered | | -------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | ---------- | | Asynchronous Runtime | tokio | The de-facto standard for asynchronous programming in Rust. It provides a high-performance, multithreaded scheduler and a comprehensive suite of utilities for I/O, networking, and timers, including the essential UnixListener, UnixStream, and time::sleep components.[^2] Its maturity and extensive ecosystem make it the definitive choice for the daemon's core. | async-std | -| CLI Argument Parsing | clap | The most popular and feature-rich CLI argument parsing library for Rust.[^4] The | derive feature offers an exceptionally ergonomic and declarative way to define the CLI's structure, automatically generating argument parsing, validation, and help text from a simple struct definition.[^6] | argh, pico-args 4 | -| GitHub API Client | octocrab | A modern, actively maintained, and extensible GitHub API client.[^9] It provides strongly-typed models for API responses and a builder pattern for requests, simplifying interaction with the GitHub REST API. Its static API and support for custom middleware are valuable for building robust clients.[^11] | roctokit 12, manual | reqwest 13 | -| Persistent Queue | yaque | A disk-backed, persistent queue designed for asynchronous environments.[^14] Its most critical feature is | transactional reads via its RecvGuard mechanism. This ensures that a dequeued item is automatically returned to the queue if the program panics or fails before the item is explicitly committed, providing an "at-least-once" delivery guarantee essential for reliability.[^14] | queue-file 16, | v_queue 18 | +| CLI Argument Parsing | clap | The most popular and feature-rich CLI argument parsing library for Rust.[^3] The | derive feature offers an exceptionally ergonomic and declarative way to define the CLI's structure, automatically generating argument parsing, validation, and help text from a simple struct definition.[^3] | argh, pico-args 4 | +| GitHub API Client | octocrab | A modern, actively maintained, and extensible GitHub API client.[^5] It provides strongly typed models for API responses and a builder pattern for requests, simplifying interaction with the GitHub REST API. Its static API and support for custom middleware are valuable for building robust clients.[^3] | roctokit 12, manual | reqwest 13 | +| Persistent Queue | yaque | A disk-backed, persistent queue designed for asynchronous environments.[^7] Its most critical feature is | transactional reads via its RecvGuard mechanism. This ensures that a dequeued item is automatically returned to the queue if the program panics or fails before the item is explicitly committed, providing an "at-least-once" delivery guarantee essential for reliability.[^7] | queue-file 16, | v_queue 18 | | IPC Serialization | serde / serde_json | serde is the universal framework for serialization and deserialization in Rust. serde_json provides a straightforward implementation for the JSON data format, which is chosen for its human-readability (aiding in debugging) and widespread support. | bincode, prost | -| Systemd Integration | systemd (crate) | Provides native Rust bindings for interacting with the systemd journal and daemon notification APIs.[^19] While the primary deployment mechanism is a | .service file, this crate can be used for more advanced integration, such as sending readiness notifications. | systemctl (crate) 20 | +| Systemd Integration | systemd (crate) | Provides native Rust bindings for interacting with the systemd journal and daemon notification APIs.[^8] While the primary deployment mechanism is a | .service file, this crate can be used for more advanced integration, such as sending readiness notifications. | systemctl (crate) 20 | | Logging | tracing / tracing-subscriber | A modern, structured, and asynchronous-aware logging and diagnostics framework. It is the standard choice for tokio-based applications, providing contextual information that is superior to traditional line-based logging. | log / env_logger 22 | | | @@ -117,7 +117,7 @@ The command-line interface is the primary point of interaction for the user. A well-designed CLI is intuitive and self-documenting. The `clap` `derive` macro defines the entire CLI structure declaratively within a Rust `struct`, providing clarity and maintainability compared to the more verbose builder -pattern.[^6] +pattern.[^3] The CLI will accept three required positional arguments, matching the user's requested invocation format: `comenq `. @@ -154,15 +154,15 @@ pub struct Args { ``` The `#[derive(Parser)]` attribute instructs `clap` to generate all the -necessary parsing logic.[^7] The doc comments ( +necessary parsing logic.[^5] The doc comments ( `///`) are automatically converted into help messages, which are displayed when the user runs `comenq --help`. This feature makes the tool -self-documenting.[^24] The +self-documenting.[^10] The `#[arg(...)]` attributes provide fine-grained control over each argument, such as defining a `default_value` for the socket path, making the client flexible -for different environments.[^8] +for different environments.[^3] ### 2.2. Client-Daemon IPC Protocol @@ -210,7 +210,7 @@ advantages for this application: throughput. - **Security:** This is the most critical advantage. A UDS is an entity in the - filesystem, like a file.[^25] This means it is subject to standard Unix + filesystem, like a file.[^12] This means it is subject to standard Unix filesystem permissions ( `chmod`, `chown`). The `comenqd` daemon can create the socket with @@ -275,15 +275,12 @@ async fn main() { // 2. Validate and parse the 'owner/repo' slug. let parts: Vec<&str> = args.repo_slug.split('/').collect(); - if parts.len()!= 2 | - -| parts.is_empty() | -| parts.is_empty() { + if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() { eprintln!("Error: Invalid repository format. Please use 'owner/repo'."); process::exit(1); } - let owner = parts.to_string(); - let repo = parts.to_string(); + let owner = parts[0].to_string(); + let repo = parts[1].to_string(); // 3. Construct the request payload. let request = CommentRequest { @@ -369,16 +366,16 @@ simple in-memory queues like `std::collections::VecDeque` 26 and necessitates a disk-backed, persistent solution. The `yaque` crate is selected as the ideal queue implementation for this -project.[^14] While other file-based queues exist 17, +project.[^7] While other file-based queues exist 17, `yaque` offers a unique combination of features perfectly suited to this daemon's needs: - **Natively Asynchronous:** It is built on `mio` and integrates seamlessly - with the `tokio` runtime without requiring blocking operations.[^14] + with the `tokio` runtime without requiring blocking operations.[^7] - **Persistence:** It stores queue data on the filesystem, ensuring durability - across process restarts.[^14] + across process restarts.[^7] - **Transactional Reads:** This is the most compelling feature. When an item is dequeued using `receiver.recv().await`, `yaque` returns a `RecvGuard`. The @@ -387,7 +384,7 @@ daemon's needs: dropped without being committed (e.g., due to a program panic or an API error), the item is automatically and safely returned to the head of the queue. This "dead man's switch" mechanism provides a powerful "at-least-once" - delivery guarantee, which is the cornerstone of the daemon's reliability.[^14] + delivery guarantee, which is the cornerstone of the daemon's reliability.[^7] The queue will be initialised at a configurable path (e.g., `/var/lib/comenq/queue`) and will store the `CommentRequest` struct defined in @@ -409,7 +406,7 @@ Its workflow is as follows: only to the owner user and group. 3. **Accept Loop:** The task enters an infinite `loop`, waiting for new client - connections via `listener.accept().await`.[^30] + connections via `listener.accept().await`.[^13] 4. **Spawn Connection Handler:** To ensure the listener is never blocked, upon accepting a new connection, it immediately spawns a new, short-lived `tokio` @@ -439,10 +436,10 @@ A critical detail for a successful implementation is using the correct GitHub API endpoint. While one might intuitively look for a "create comment" method within the Pull Request API, general comments on a PR are, in fact, considered part of the underlying Issue. This non-obvious fact is highlighted in GitHub's -own documentation patterns.[^31] Therefore, the correct +own documentation patterns.[^7] Therefore, the correct `octocrab` method to use is `issues().create_comment()`, not a method on the -`pulls()` handler.[^32] +`pulls()` handler.[^15] The correct invocation will be: @@ -554,7 +551,7 @@ install -D -m 755 target/release/comenqd /usr/local/sbin/comenqd # Create a dedicated, non-login user for the daemon # The -r flag creates a system user -if! id -u comenq >/dev/null 2>&1; then +if ! id -u comenq >/dev/null 2>&1; then useradd -r -s /usr/sbin/nologin -d /var/lib/comenq -c "comenq Daemon User" comenq fi @@ -583,7 +580,7 @@ privilege. Running the daemon directly in a terminal is suitable for development but not for production. A `systemd` service unit file automates the daemon's lifecycle management, including startup on boot, automatic restarts on failure, and -integration with the system's logging infrastructure.[^33] +integration with the system's logging infrastructure.[^16] The following `comenq.service` file should be placed in `/etc/systemd/system/`: @@ -802,15 +799,12 @@ async fn main() { let args = Args::parse(); let parts: Vec<&str> = args.repo_slug.split('/').collect(); - if parts.len()!= 2 | - -| parts.is_empty() | -| parts.is_empty() { + if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() { eprintln!("Error: Invalid repository format. Please use 'owner/repo'."); process::exit(1); } - let owner = parts.to_string(); - let repo = parts.to_string(); + let owner = parts[0].to_string(); + let repo = parts[1].to_string(); let request = CommentRequest { owner, @@ -996,57 +990,58 @@ derives both `Serialize` and `Deserialize`. The binaries currently contain stub ## Works cited -\[^1\]: A simple UNIX socket listener in Rust | Kyle M. Douglass, accessed on +[^1]: A simple UNIX socket listener in Rust | Kyle M. Douglass, accessed on July 24, 2025, -\[^2\]: UnixSocket in tokio::net - Rust - [Docs.rs](http://Docs.rs), accessed +[^2]: UnixSocket in tokio::net - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, July 24, 2025, - \[^4\]: Picking + [^3]: Picking an argument parser - Rain's Rust CLI recommendations, accessed on July 24, 2025, accessed -on July 24, 2025, \[^6\]: Parsing command -line arguments - Command Line Applications in Rust, accessed on July 24, 2025, - \[^7\]: clap - Rust - +on July 24, 2025, [^4]: Parsing command line +arguments - Command Line Applications in Rust, accessed on July 24, 2025, + [^9]: clap - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, - \[^8\]: clap::\_derive::\_tutorial - Rust - + [^11]: clap::\_derive::\_tutorial - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, - \[^9\]: + [^5]: XAMPPRocky/octocrab: A modern, extensible GitHub API Client for Rust., accessed on July 24, 2025, - \[^11\]: + [^6]: octocrab/examples/custom\_[client.rs](http://client.rs) at main - GitHub, accessed on July 24, 2025, accessed on July 24, 2025, 2025, -\[^14\]: Yaque is yet another disk-backed persistent queue for Rust. - GitHub, +[^7]: Yaque is yet another disk-backed persistent queue for Rust. - GitHub, accessed on July 24, 2025, accessed on July 24, 2025, accessed on July 24, 2025, - \[^19\]: codyps/rust-systemd: -Rust interface to systemd c apis - GitHub, accessed on July 24, 2025, + [^8]: codyps/rust-systemd: Rust +interface to systemd c apis - GitHub, accessed on July 24, 2025, accessed on July 24, 2025, - \[^24\]: clap::\_derive - Rust - [Docs.rs](http://Docs.rs), accessed on July -24, 2025, \[^25\]: -Unix sockets, the basics in Rust - Emmanuel Bosquet, accessed on July 24, 2025, + [^10]: clap::\_derive - Rust - [Docs.rs](http://Docs.rs), accessed on July +24, 2025, [^12]: Unix +sockets, the basics in Rust - Emmanuel Bosquet, accessed on July 24, 2025, accessed on July 24, 2025, 2025, accessed on July 24, 2025, Rust Users Forum, accessed on July 24, 2025, - \[^30\]: Example of reading from a Unix socket · Issue #9 · tokio-rs/tokio-uds + [^13]: Example of reading from a Unix socket · Issue #9 · tokio-rs/tokio-uds - GitHub, accessed on July 24, 2025, - \[^31\]: Working with Comments -| GitHub API - LFE Documentation, accessed on July 24, 2025, \[^32\]: PullRequestHandler in octocrab::pulls - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, | + [^14]: Working with Comments +| GitHub API - LFE Documentation, accessed on July 24, 2025, [^15]: PullRequestHandler in octocrab::pulls - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, | 2025, -\[^33\]: KillingSpark/rustysd: A service manager that is able to run + +[^16]: KillingSpark/rustysd: A service manager that is able to run "traditional" systemd services, written in rust - GitHub, accessed on July 24, 2025, accessed on July 24, 2025, From aad73091b25f5eb4ae71ef0b69006a3dc0d79af8 Mon Sep 17 00:00:00 2001 From: Leynos Date: Fri, 25 Jul 2025 11:28:48 +0100 Subject: [PATCH 5/5] Fix lint issues and design doc formatting --- .markdownlint-cli2.jsonc | 6 ++- docs/comenq-design.md | 92 ++++++++++++++---------------------- tests/steps/comment_steps.rs | 12 ++--- 3 files changed, 47 insertions(+), 63 deletions(-) diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index 6a746b6..c9bb478 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -1,6 +1,10 @@ { "config": { - "MD013": false, + "MD013": { + "line_length": 80, + "code_block_line_length": 120, + "tables": false + }, "MD029": { "style": "ordered" }, "MD040": { "code_blocks": true } } diff --git a/docs/comenq-design.md b/docs/comenq-design.md index 06d7db1..39dc8b9 100644 --- a/docs/comenq-design.md +++ b/docs/comenq-design.md @@ -92,6 +92,7 @@ for each major component of the `comenq` service, along with a detailed justification for each selection based on an analysis of available tools and project requirements. + | Component/Concern | Selected Crate/Library | Key Features & Rationale | Alternative(s) Considered | | -------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | ---------- | | Asynchronous Runtime | tokio | The de-facto standard for asynchronous programming in Rust. It provides a high-performance, multithreaded scheduler and a comprehensive suite of utilities for I/O, networking, and timers, including the essential UnixListener, UnixStream, and time::sleep components.[^2] Its maturity and extensive ecosystem make it the definitive choice for the daemon's core. | async-std | @@ -102,6 +103,7 @@ project requirements. | Systemd Integration | systemd (crate) | Provides native Rust bindings for interacting with the systemd journal and daemon notification APIs.[^8] While the primary deployment mechanism is a | .service file, this crate can be used for more advanced integration, such as sending readiness notifications. | systemctl (crate) 20 | | Logging | tracing / tracing-subscriber | A modern, structured, and asynchronous-aware logging and diagnostics framework. It is the standard choice for tokio-based applications, providing contextual information that is superior to traditional line-based logging. | log / env_logger 22 | | | + ## Section 2: Design of the `comenq` CLI Client @@ -343,12 +345,12 @@ Upon startup, the `main` function will initialise necessary resources (configuration, logger, queue) and then spawn two primary, independent asynchronous tasks that run concurrently for the lifetime of the daemon: -1. `task_listen_for_requests`**:** This task is the daemon's public-facing +1. `task_listen_for_requests`: This task is the daemon's public-facing interface. It binds to the UDS and listens for incoming connections from `comenq` clients. Its sole job is to accept requests and place them into the queue as quickly as possible. -2. `task_process_queue`**:** This is the main worker task. It operates in a +2. `task_process_queue`: This is the main worker task. It operates in a serialized loop, pulling one job at a time from the queue, processing it (i.e., posting the comment to GitHub), and then observing the mandatory 15-minute cooldown period. @@ -990,58 +992,36 @@ derives both `Serialize` and `Deserialize`. The binaries currently contain stub ## Works cited -[^1]: A simple UNIX socket listener in Rust | Kyle M. Douglass, accessed on -July 24, 2025, - -[^2]: UnixSocket in tokio::net - Rust - [Docs.rs](http://Docs.rs), accessed -on July 24, 2025, - July 24, 2025, - [^3]: Picking -an argument parser - Rain's Rust CLI recommendations, accessed on July 24, -2025, accessed -on July 24, 2025, [^4]: Parsing command line -arguments - Command Line Applications in Rust, accessed on July 24, 2025, - [^9]: clap - Rust - -[Docs.rs](http://Docs.rs), accessed on July 24, 2025, - [^11]: clap::\_derive::\_tutorial - Rust - -[Docs.rs](http://Docs.rs), accessed on July 24, 2025, - [^5]: -XAMPPRocky/octocrab: A modern, extensible GitHub API Client for Rust., accessed -on July 24, 2025, - [^6]: -octocrab/examples/custom\_[client.rs](http://client.rs) at main - GitHub, -accessed on July 24, 2025, - -accessed on July 24, 2025, 2025, - -[^7]: Yaque is yet another disk-backed persistent queue for Rust. - GitHub, -accessed on July 24, 2025, - accessed on July 24, 2025, - accessed on July 24, 2025, - [^8]: codyps/rust-systemd: Rust -interface to systemd c apis - GitHub, accessed on July 24, 2025, - accessed on July 24, 2025, - - - - [^10]: clap::\_derive - Rust - [Docs.rs](http://Docs.rs), accessed on July -24, 2025, [^12]: Unix -sockets, the basics in Rust - Emmanuel Bosquet, accessed on July 24, 2025, - accessed on July 24, 2025, - - 2025, -accessed on July 24, 2025, Rust Users -Forum, accessed on July 24, 2025, - - [^13]: Example of reading from a Unix socket · Issue #9 · tokio-rs/tokio-uds - -- GitHub, accessed on July 24, 2025, - [^14]: Working with Comments -| GitHub API - LFE Documentation, accessed on July 24, 2025, [^15]: PullRequestHandler in octocrab::pulls - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, | -2025, - - +[^1]: A simple UNIX socket listener in Rust | Kyle M. Douglass. Accessed on + July 24, 2025. + +[^2]: UnixSocket in tokio::net - Docs.rs. Accessed on July 24, 2025. + +[^3]: Picking an argument parser - Rain's Rust CLI recommendations. Accessed on + July 24, 2025. + Accessed + on July 24, 2025. +[^5]: clap - Docs.rs. Accessed on July 24, 2025. + + +[^7]: XAMPPRocky/octocrab: A modern, extensible GitHub API Client for Rust. + Accessed on July 24, 2025. +[^8]: octocrab/examples/custom_client.rs at main - GitHub. Accessed on July 24, + 2025. + + July 24, 2025. +[^10]: codyps/rust-systemd: Rust interface to systemd c apis - GitHub. Accessed + on July 24, 2025. + +[^12]: Unix sockets, the basics in Rust - Emmanuel Bosquet. Accessed on July + 24, 2025. +[^13]: Example of reading from a Unix socket · Issue #9 · tokio-rs/tokio-uds - + GitHub. Accessed on July 24, 2025. + 24, 2025. + +[^15]: PullRequestHandler in octocrab::pulls - Docs.rs. Accessed on July 24, + 2025. + [^16]: KillingSpark/rustysd: A service manager that is able to run -"traditional" systemd services, written in rust - GitHub, accessed on July 24, -2025, accessed on July 24, 2025, - + "traditional" systemd services, written in rust - GitHub. Accessed on + July 24, 2025. diff --git a/tests/steps/comment_steps.rs b/tests/steps/comment_steps.rs index 72d1edd..b79f785 100644 --- a/tests/steps/comment_steps.rs +++ b/tests/steps/comment_steps.rs @@ -1,3 +1,5 @@ +#![allow(clippy::expect_used, reason = "simplify test failure output")] + use comenq_lib::CommentRequest; use cucumber::{World, given, then, when}; @@ -21,10 +23,8 @@ fn a_default_comment_request(world: &mut CommentWorld) { #[when("it is serialised")] fn it_is_serialised(world: &mut CommentWorld) { if let Some(req) = world.request.take() { - world.json = match serde_json::to_string(&req) { - Ok(json) => Some(json), - Err(e) => panic!("serialisation failed: {e}"), - }; + world.json = + Some(serde_json::to_string(&req).expect("serialisation should succeed in test")); } } @@ -32,8 +32,8 @@ fn it_is_serialised(world: &mut CommentWorld) { fn the_json_is(world: &mut CommentWorld) { match world.json.take() { Some(actual) => { - let act: serde_json::Value = serde_json::from_str(&actual) - .unwrap_or_else(|e| panic!("parse actual failed: {e}")); + let act: serde_json::Value = + serde_json::from_str(&actual).expect("test JSON should parse successfully"); let exp = serde_json::json!({ "owner": "octocat", "repo": "hello-world",