From 23cc1b022ea5b0341c1e982856f26734817433a1 Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou Date: Tue, 3 Feb 2026 12:12:15 +0000 Subject: [PATCH] auto-generated rate-limiting documentation --- .cargo/config.toml | 3 + Cargo.lock | 26 +++++-- Cargo.toml | 3 +- conduit-config/Cargo.toml | 5 ++ conduit-config/src/rate_limiting.rs | 13 ++++ conduit-macros/Cargo.toml | 22 ++++++ conduit-macros/src/doc_generators.rs | 91 ++++++++++++++++++++++++ conduit-macros/src/lib.rs | 16 +++++ conduit/src/service/rate_limiting/mod.rs | 2 - docs/configuration/rate-limiting.md | 19 +++-- flake.nix | 1 + nix/pkgs/book/default.nix | 4 ++ nix/pkgs/default/default.nix | 7 +- xtask/Cargo.toml | 12 ++++ xtask/src/main.rs | 3 + 15 files changed, 210 insertions(+), 17 deletions(-) create mode 100644 conduit-macros/Cargo.toml create mode 100644 conduit-macros/src/doc_generators.rs create mode 100644 conduit-macros/src/lib.rs create mode 100644 xtask/Cargo.toml create mode 100644 xtask/src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 4c64d1c8..df2e0f83 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,5 @@ [env] RUMA_UNSTABLE_EXHAUSTIVE_TYPES = "1" + +[alias] +xtask = "run --package xtask --" diff --git a/Cargo.lock b/Cargo.lock index 0415f3e1..bd883610 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -579,6 +579,7 @@ name = "conduit-config" version = "0.11.0-alpha" dependencies = [ "bytesize", + "conduit-macros", "humantime-serde", "reqwest", "ruma", @@ -588,6 +589,15 @@ dependencies = [ "url", ] +[[package]] +name = "conduit-macros" +version = "0.11.0-alpha" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -2296,9 +2306,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -2363,9 +2373,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -3252,9 +3262,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" dependencies = [ "proc-macro2", "quote", @@ -4331,6 +4341,10 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "xtask" +version = "0.11.0-alpha" + [[package]] name = "yansi" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 4031b64c..d44e4272 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] default-members = ["conduit"] -members = ["conduit", "conduit-config"] +members = ["conduit", "conduit-config", "conduit-macros", "xtask"] resolver = "2" [workspace.lints.rust] @@ -22,6 +22,7 @@ rust-version = "1.88.0" [workspace.dependencies] bytesize = "2" conduit-config.path = "conduit-config" +conduit-macros.path = "conduit-macros" reqwest = { version = "0.12", default-features = false } ruma.git = "https://github.com/ruma/ruma.git" rusty-s3 = "0.8" diff --git a/conduit-config/Cargo.toml b/conduit-config/Cargo.toml index c4628f0b..5a6202aa 100644 --- a/conduit-config/Cargo.toml +++ b/conduit-config/Cargo.toml @@ -20,6 +20,8 @@ thiserror.workspace = true rusty-s3.workspace = true # Proxy config reqwest.workspace = true +# Generating documentation +conduit-macros.workspace = true # default room version, server name, ignored keys [dependencies.ruma] @@ -30,5 +32,8 @@ workspace = true rocksdb = [] sqlite = [] +# Used to generate docs, shouldn't be used outside of xtask +doc-generators = ["conduit-macros/doc-generators"] + [lints] workspace = true diff --git a/conduit-config/src/rate_limiting.rs b/conduit-config/src/rate_limiting.rs index f650ea26..991301e0 100644 --- a/conduit-config/src/rate_limiting.rs +++ b/conduit-config/src/rate_limiting.rs @@ -189,7 +189,16 @@ pub enum Restriction { Federation(FederationRestriction), } +#[cfg(feature = "doc-generators")] +pub trait DocumentRestrictions: Sized { + fn variant_doc_comments() -> Vec<(Self, String)>; +} + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr( + feature = "doc-generators", + derive(conduit_macros::DocumentRestrictions) +)] #[serde(rename_all = "snake_case")] pub enum ClientRestriction { Registration, @@ -210,6 +219,10 @@ pub enum ClientRestriction { } #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr( + feature = "doc-generators", + derive(conduit_macros::DocumentRestrictions) +)] #[serde(rename_all = "snake_case")] pub enum FederationRestriction { Join, diff --git a/conduit-macros/Cargo.toml b/conduit-macros/Cargo.toml new file mode 100644 index 00000000..69aec5e0 --- /dev/null +++ b/conduit-macros/Cargo.toml @@ -0,0 +1,22 @@ +[package] +edition.workspace = true +homepage.workspace = true +name = "conduit-macros" +repository.workspace = true +rust-version.workspace = true +version = "0.11.0-alpha" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = { version = "2", features = ["full"] } + +[features] +default = ["doc-generators"] +doc-generators = [] + +[lints] +workspace = true diff --git a/conduit-macros/src/doc_generators.rs b/conduit-macros/src/doc_generators.rs new file mode 100644 index 00000000..befc0e19 --- /dev/null +++ b/conduit-macros/src/doc_generators.rs @@ -0,0 +1,91 @@ +use std::{fmt::Display, path::Path}; + +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::{ToTokens, quote}; +use syn::{Attribute, Expr, Field, Ident, ItemEnum, Lit, MetaNameValue, Variant, parse::Parse}; + +pub struct Restrictions { + ident: Ident, + variants: Vec, +} + +pub struct Restriction { + ident: Ident, + doc_comments: Vec, +} + +impl Parse for Restrictions { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let ItemEnum { + ident, + variants, + // Might be useful later. + 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(); + + Ok(Restriction { + ident, + doc_comments, + }) + }) + .collect::>>()?; + + Ok(Self { ident, variants }) + } +} + +/// 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 output = quote! { + impl DocumentRestrictions for #ident { + fn variant_doc_comments() -> Vec<(Self, String)> { + vec![#( (#variants), )*] + } + } + }; + + tokens.extend(output); + } +} + +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"); + + // `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) ) )) + } +} diff --git a/conduit-macros/src/lib.rs b/conduit-macros/src/lib.rs new file mode 100644 index 00000000..64777d44 --- /dev/null +++ b/conduit-macros/src/lib.rs @@ -0,0 +1,16 @@ +use proc_macro::TokenStream; +use quote::quote; + +#[cfg(feature = "doc-generators")] +mod doc_generators; + +#[cfg(feature = "doc-generators")] +#[proc_macro_derive(DocumentRestrictions)] +pub fn document_restrictions(item: TokenStream) -> TokenStream { + use doc_generators::Restrictions; + use syn::parse_macro_input; + + let restrictions = parse_macro_input!(item as Restrictions); + + quote! { #restrictions }.into() +} diff --git a/conduit/src/service/rate_limiting/mod.rs b/conduit/src/service/rate_limiting/mod.rs index 5716315b..98264f71 100644 --- a/conduit/src/service/rate_limiting/mod.rs +++ b/conduit/src/service/rate_limiting/mod.rs @@ -130,8 +130,6 @@ impl Service { }) } - //TODO: use checked and saturating arithmetic - /// Takes the target and request, and either accepts the request while adding to the /// bucket, or rejects the request, returning the duration that should be waited until /// the request should be retried. diff --git a/docs/configuration/rate-limiting.md b/docs/configuration/rate-limiting.md index ce3688a7..bfd9560f 100644 --- a/docs/configuration/rate-limiting.md +++ b/docs/configuration/rate-limiting.md @@ -34,11 +34,7 @@ should be part of the preset, you can contribute and change them! The overrides are split into `client` and `federation` sections, for limits that apply to the [client](https://spec.matrix.org/v1.17/client-server-api/) and [federation](https://spec.matrix.org/v1.17/server-server-api/) APIs respectively, which are both -then split into `target` and `global` sections, which apply to specific [targets](#targets) and globally respectively. - -### Restrictions - -{{#include ../../target/docs/rate-limiting.md:restrictions}} +then split into `target` and `global` sections, which apply to singular [targets](#targets) or to all of them respectively. ### Targets @@ -53,3 +49,16 @@ resources/requests each unique client can access within the configured timeframe For example, while on a small server you might allow for all logged-in users to send out 100 invites per day between them, you can set a cap of 5 for each individual user, not only so that they can't use up the entire global cap, but also prevent potential spam from being spread by that user alone. + +### Restrictions + +Restrictions are one-to-many mappings to endpoints that have potential for abuse. Like the overrides mentioned above, +they are split into `client` and `federation` restrictions. + +#### Client + +{{#include ../../target/docs/rate-limiting.md:client-restrictions}} + +#### Federation + +{{#include ../../target/docs/rate-limiting.md:federation-restrictions}} diff --git a/flake.nix b/flake.nix index 9ad4c928..0578ec88 100644 --- a/flake.nix +++ b/flake.nix @@ -29,6 +29,7 @@ workspaceMembers = (pkgs.lib.importTOML ./Cargo.toml).workspace.members; default = self.callPackage ./nix/pkgs/default {}; + xtask = self.callPackage ./nix/pkgs/default { pname = "xtask"; }; inherit inputs; diff --git a/nix/pkgs/book/default.nix b/nix/pkgs/book/default.nix index cc0464d6..6721103d 100644 --- a/nix/pkgs/book/default.nix +++ b/nix/pkgs/book/default.nix @@ -1,8 +1,10 @@ # Keep sorted { default , inputs +, lib , mdbook , stdenv +, xtask }: stdenv.mkDerivation { @@ -20,6 +22,7 @@ stdenv.mkDerivation { "debian/README.md" "docs" "README.md" + "target/docs" ]; }; @@ -28,6 +31,7 @@ stdenv.mkDerivation { ]; buildPhase = '' + ${lib.getExe xtask} generate-docs mdbook build mv public $out ''; diff --git a/nix/pkgs/default/default.nix b/nix/pkgs/default/default.nix index 2951d263..a404d6d2 100644 --- a/nix/pkgs/default/default.nix +++ b/nix/pkgs/default/default.nix @@ -12,6 +12,7 @@ , default-features ? true , features ? [] , profile ? "release" +, pname ? "conduit" }: let @@ -44,7 +45,7 @@ let commonAttrs = { inherit (craneLib.crateNameFromCargoToml { - cargoToml = "${inputs.self}/conduit/Cargo.toml"; + cargoToml = "${inputs.self}/${pname}/Cargo.toml"; }) pname version; @@ -77,7 +78,7 @@ craneLib.buildPackage ( commonAttrs // { env = buildDepsOnlyEnv; }); - cargoExtraArgs = "--locked " + cargoExtraArgs = "-p ${pname} --locked " + lib.optionalString (!default-features) "--no-default-features " @@ -94,5 +95,5 @@ craneLib.buildPackage ( commonAttrs // { env = buildPackageEnv; }; - meta.mainProgram = commonAttrs.pname; + meta.mainProgram = pname; }) diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 00000000..1fd9661d --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,12 @@ +[package] +edition.workspace = true +homepage.workspace = true +name = "xtask" +repository.workspace = true +rust-version.workspace = true +version = "0.11.0-alpha" + +[dependencies] + +[lints] +workspace = true diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 00000000..e7a11a96 --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}