diff --git a/Cargo.lock b/Cargo.lock index a1f62e04..6ea34c51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -507,6 +507,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -518,7 +519,18 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -853,7 +865,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn", @@ -1179,6 +1191,12 @@ 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 = "hermit-abi" version = "0.3.9" @@ -3290,12 +3308,6 @@ 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 = "subslice" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index 0a3563ef..11610828 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,17 @@ rust-version = "1.88.0" [workspace.dependencies] bytesize = "2" -clap = "4" +# Removing the `color` feature as it is not used in the admin room commands. +# `string` feature is added for some reason, not sure. +# Added `derive` feature to allow derive macros +clap = { version = "4", default-features = false, features = [ + "derive", + "error-context", + "help", + "std", + "string", + "usage", +] } conduit-config.path = "conduit-config" conduit-macros.path = "conduit-macros" reqwest = { version = "0.12", default-features = false } diff --git a/conduit-config/src/rate_limiting.rs b/conduit-config/src/rate_limiting.rs index 4db857c0..20e343e5 100644 --- a/conduit-config/src/rate_limiting.rs +++ b/conduit-config/src/rate_limiting.rs @@ -192,8 +192,12 @@ pub enum Restriction { #[cfg(feature = "doc-generators")] pub trait DocumentRestrictions: Sized { fn variant_doc_comments() -> Vec<(Self, String)>; + fn container_doc_comment() -> String; } +/// Applies for endpoints on the client-server API, which are used by clients, appservices, and +/// bots. Appservices can bypass rate-limiting though if `rate_limited` is set to `false` in their +/// registration file. #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr( feature = "doc-generators", @@ -201,26 +205,48 @@ pub trait DocumentRestrictions: Sized { )] #[serde(rename_all = "snake_case")] pub enum ClientRestriction { - /// Endpoint for registering a new user account. May be called multiples times for a single + /// For registering a new user account. May be called multiples times for a single /// registration if there are extra steps, e.g. providing a registration token. Registration, - /// Endpoint used for logging into an existing account. + /// For logging into an existing account. Login, + /// For checking whether a given registration token would allow the user to register an + /// account. RegistrationTokenValidity, + /// For sending an event to a room. + /// + /// Note that this is not used for state events, but for users who are unprivliged in a room, + /// the only state event they'll be able to send are ones to update their room profile. SendEvent, + /// For joining a room. Join, + /// For inviting a user to a room. Invite, + /// For knocking on a room. Knock, + /// For reporting a user, event, or room. SendReport, + + /// For adding an alias to a room. CreateAlias, + /// For downloading a media file. + /// + /// For rate-limiting based on the size of files downloaded, see the media rate-limiting + /// configuration. MediaDownload, + /// For uploading a media file. + /// + /// For rate-limiting based on the size of files uploaded, see the media rate-limiting + /// configuration. MediaCreate, } +/// Applies for endpoints on the federation API of this server, hence restricting how +/// many times other servers can use these endpoints on this server in a given timeframe. #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr( feature = "doc-generators", @@ -228,13 +254,19 @@ pub enum ClientRestriction { )] #[serde(rename_all = "snake_case")] pub enum FederationRestriction { + /// For joining a room. Join, + /// For knocking on a room. Knock, + /// For inviting a local user to a room. Invite, // Transactions should be handled by a completely dedicated rate-limiter - Transaction, - + /* /// For sending transactions of PDU/EDUs. + /// + /// + Transaction, */ + /// For downloading media. MediaDownload, } diff --git a/conduit-macros/src/doc_generators.rs b/conduit-macros/src/doc_generators.rs index 2f06762f..c179581d 100644 --- a/conduit-macros/src/doc_generators.rs +++ b/conduit-macros/src/doc_generators.rs @@ -1,15 +1,16 @@ use proc_macro2::TokenStream as TokenStream2; use quote::{ToTokens, quote}; -use syn::{Expr, Ident, ItemEnum, Lit, MetaNameValue, Variant, parse::Parse}; +use syn::{Attribute, Expr, Ident, ItemEnum, Lit, MetaNameValue, Variant, parse::Parse}; pub(super) struct Restrictions { ident: Ident, + doc_comment: String, variants: Vec, } struct Restriction { ident: Ident, - doc_comments: Vec, + doc_comment: String, } impl Parse for Restrictions { @@ -17,50 +18,65 @@ impl Parse for Restrictions { let ItemEnum { ident, variants, - // Might be useful later. - attrs: _, + attrs, .. } = ItemEnum::parse(input)?; let variants = variants .into_iter() .map(|Variant { attrs, ident, .. }| { - let doc_comments = attrs - .into_iter() - .filter_map(|attr| { - if let syn::Meta::NameValue(MetaNameValue { path, value, .. }) = attr.meta - && path.is_ident("doc") - && let Expr::Lit(lit) = value - && let Lit::Str(string) = lit.lit - { - Some(string.value().trim().to_owned() + "\n") - } else { - None - } - }) - .collect(); + let doc_comment = attrs_to_doc_comment(attrs); - Ok(Restriction { - ident, - doc_comments, - }) + Ok(Restriction { ident, doc_comment }) }) .collect::>>()?; - Ok(Self { ident, variants }) + let doc_comment = attrs_to_doc_comment(attrs); + + Ok(Self { + ident, + variants, + doc_comment, + }) } } +fn attrs_to_doc_comment(attrs: Vec) -> String { + attrs + .into_iter() + .filter_map(|attr| { + if let syn::Meta::NameValue(MetaNameValue { path, value, .. }) = attr.meta + && path.is_ident("doc") + && let Expr::Lit(lit) = value + && let Lit::Str(string) = lit.lit + { + Some(string.value().trim().to_owned()) + } else { + None + } + }) + .collect::>() + .join("\n") +} + /// Produces the following function on said restriction: /// - `variant_doc_comments`, returning each variant and it's doc comment. impl ToTokens for Restrictions { fn to_tokens(&self, tokens: &mut TokenStream2) { - let Self { ident, variants } = self; + let Self { + ident, + variants, + doc_comment, /* , doc_comments */ + } = self; let output = quote! { impl DocumentRestrictions for #ident { fn variant_doc_comments() -> Vec<(Self, String)> { vec![#((#variants)),*] } + + fn container_doc_comment() -> String { + #doc_comment.to_owned() + } } }; @@ -70,19 +86,9 @@ impl ToTokens for Restrictions { impl ToTokens for Restriction { fn to_tokens(&self, tokens: &mut TokenStream2) { - let Self { - ident, - doc_comments, - } = self; - - let doc_comment = doc_comments - .iter() - .map(|attr| attr.trim()) - .collect::>() - .join("\n"); + let Self { ident, doc_comment } = self; - // `String::from` because despite `doc_comment` being a `String`, the macro still ends up - // with a `&str` somehow. - tokens.extend(quote!( (Self::#ident, String::from(#doc_comment) ) )) + // `clone` because `to_tokens` takes a reference to self. + tokens.extend(quote!( (Self::#ident, #doc_comment.to_owned() ) )) } } diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index ed931538..b06bac2a 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -8,7 +8,7 @@ version = "0.11.0-alpha" [dependencies] # Processing commands -clap.workspace = true +clap = { workspace = true, features = ["color"] } # Documentation generation for the configuration conduit-config = { workspace = true, features = ["doc-generators"] } serde.workspace = true diff --git a/xtask/src/generate_docs.rs b/xtask/src/generate_docs.rs index 0483ced6..4e6236e6 100644 --- a/xtask/src/generate_docs.rs +++ b/xtask/src/generate_docs.rs @@ -10,24 +10,25 @@ use conduit_config::rate_limiting::{ pub fn run() { let mut markdown_text = String::new(); - markdown_text.push_str("\n"); - for (restriction, comment) in ClientRestriction::variant_doc_comments() { - markdown_text.push_str(&format!( - "##### {}\n{}\n", - restriction_to_string(&restriction), - comment - )); + macro_rules! push_restrictions { + ($restriction_kind:ident) => { + markdown_text.push_str(&format!("{}\n", $restriction_kind::container_doc_comment())); + for (restriction, comment) in $restriction_kind::variant_doc_comments() { + markdown_text.push_str(&format!( + "##### `{}`\n{}\n", + restriction_to_string(&restriction), + comment + )); + } + }; } + + markdown_text.push_str("\n"); + push_restrictions!(ClientRestriction); markdown_text.push_str("\n"); markdown_text.push_str("\n"); - for (restriction, comment) in FederationRestriction::variant_doc_comments() { - markdown_text.push_str(&format!( - "##### `{}`\n{}\n", - restriction_to_string(&restriction), - comment - )); - } + push_restrictions!(FederationRestriction); markdown_text.push_str("\n"); create_dir_all("./target/docs").unwrap();