diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index b1ed206..c9bb478 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -1,11 +1,11 @@ { - // 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 - } + }, + "MD029": { "style": "ordered" }, + "MD040": { "code_blocks": true } } } 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..e5aa72f 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 } @@ -20,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/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..367988a --- /dev/null +++ b/crates/comenqd/src/main.rs @@ -0,0 +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() { + 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 e7c1a89..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,92 +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, + -[^19]: *A Beginner’s Guide to Cucumber in Rust* — Florian Reinhard, accessed - 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 103aaed..39dc8b9 100644 --- a/docs/comenq-design.md +++ b/docs/comenq-design.md @@ -1,77 +1,133 @@ - # A Fault-Tolerant GitHub Comment Queuing Service in Rust ## 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[^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 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. +2. 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). +3. 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. +4. 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. +5. 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. +6. 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. +7. 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. +8. 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. +9. 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. +10. 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"). +11. 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. +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. +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, 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.[^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.[^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 -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 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.[^3] -The CLI will accept three required positional arguments, matching the user's requested invocation format: `comenq `. +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 be the core of the `comenq` client's `main.rs`. +The following code defines the `Args` struct that represents the CLI. This will +be the core of the `comenq` client's `main.rs`. -Rust - -``` +```rust // In src/bin/comenq/main.rs use clap::Parser; @@ -99,23 +155,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.[^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 +`///`) are automatically converted into help messages, which are displayed when +the user runs `comenq --help`. This feature makes the tool +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 +`#[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.[^3] ### 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. +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 - -``` +```rust // In src/lib.rs (or a dedicated lib crate) use serde::{Serialize, Deserialize}; @@ -133,23 +198,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.[^12] 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`. +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 - -``` +```rust // In src/bin/comenq/main.rs use clap::Parser; @@ -198,15 +277,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 { @@ -251,73 +327,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 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 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.[^7] 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.[^7] -- **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.[^7] -- **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.[^7] -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 initialised 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`.[^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` 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 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 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.[^7] 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.[^15] The correct invocation will be: -Rust - -``` +```rust octocrab.issues("owner", "repo").create_comment(pr_number, "body").await?; ``` @@ -325,31 +453,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 +518,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 @@ -398,7 +553,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 @@ -418,17 +573,22 @@ 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.[^16] 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 @@ -464,17 +624,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,38 +654,55 @@ 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. +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: +- **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 - - ``` +```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. -``` +```text comenq-project/ ├── Cargo.toml ├── src/ @@ -544,7 +725,7 @@ This is the root `Cargo.toml` for the workspace. Ini, TOML -``` +```toml [workspace] members = [ "crates/comenq", @@ -573,9 +754,7 @@ panic = "abort" ### 5.3. Source Code for Shared Library (`src/lib.rs`) -Rust - -``` +```rust // src/lib.rs use serde::{Deserialize, Serialize}; @@ -592,11 +771,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; @@ -623,15 +801,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, @@ -670,11 +845,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 +983,45 @@ async fn run_worker(config: Arc, mut rx: Receiver, octoc } ``` -## Works cited - - 1. A simple UNIX socket listener in Rust | Kyle M. Douglass, accessed on July 24, 2025, +### 5.6. Implementation Notes - 2. UnixSocket in tokio::net - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, +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. - 3. 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, - - 5. 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, - - 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, - -10. 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, - -12. 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, - -14. 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, - -16. 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, - -18. 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, - -20. 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, - -22. log - Rust - [Docs.rs](http://Docs.rs), accessed on July 24, 2025, - -23. 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, - -25. 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, - -27. 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, - -29. 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, - -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, +## Works cited -34. Rustysd: A systemd-compatible service manager written in rust - Reddit, accessed on July 24, 2025, \ No newline at end of file +[^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. diff --git a/docs/roadmap.md b/docs/roadmap.md index efee33e..9ac4728 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 +## 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 +## 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..7562f44 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,70 @@ +//! 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 = + serde_json::to_value(&request).unwrap_or_else(|e| panic!("serialisation failed: {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()); + } + + #[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/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..aee7070 --- /dev/null +++ b/tests/features/comment_request.feature @@ -0,0 +1,26 @@ +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 + + 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 new file mode 100644 index 0000000..b79f785 --- /dev/null +++ b/tests/steps/comment_steps.rs @@ -0,0 +1,96 @@ +#![allow(clippy::expect_used, reason = "simplify test failure output")] + +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 = + Some(serde_json::to_string(&req).expect("serialisation should succeed in test")); + } +} + +#[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).expect("test JSON should parse successfully"); + let exp = serde_json::json!({ + "owner": "octocat", + "repo": "hello-world", + "pr_number": 1, + "body": "Hi" + }); + assert_eq!(act, exp); + } + None => panic!("missing JSON output - test setup error"), + } +} + +#[given("invalid JSON")] +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() { + 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;