Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,405 changes: 1,986 additions & 1,419 deletions Cargo.lock

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,17 @@ struct_excessive_bools = "allow"
unused_self = "allow"

[workspace.dependencies]
anyhow = "1.0.52"
base64 = "0.13.0"
chrono = { version = "0.4.19", features = ["serde"] }
anyhow = "1.0.103"
base64 = "0.22.1"
chrono = { version = "0.4.45", features = ["serde"] }
gix = { version = "0.75.0", features = ["max-performance"] }
hex = "0.4.3"
rand = "0.10.1"
serde = { version = "1.0.133", features = ["derive"] }
serde_json = "1.0.75"
tracing = "0.1.29"
uuid = { version = "1", features = ["serde"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
sha2 = "0.11.0"
tracing = "0.1.44"
uuid = { version = "1.23.1", features = ["serde"] }

[patch.crates-io]
gitarena-macros = { path = "gitarena-macros" }
Expand Down
6 changes: 3 additions & 3 deletions gitarena-issues/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ hex = { workspace = true }
rand = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
sha2 = "0.11.0"
sha2 = { workspace = true }
tracing = { workspace = true }
uuid = { workspace = true, features = ["v4"] }

[dev-dependencies]
tempfile = "3.23.0"
which = "8.0.2"
tempfile = "3.27.0"
which = "8.0.4"
4 changes: 3 additions & 1 deletion gitarena-issues/src/author.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use anyhow::{Context, Result, bail};
use base64::Engine as _;
use base64::engine::general_purpose;
use chrono::Utc;
use gix::actor::Signature;
use gix::bstr::ByteSlice;
Expand Down Expand Up @@ -58,7 +60,7 @@ impl Author {
/// - `created_at`: Unix timestamp when the user was created
pub fn from_user(user_id: Uuid, username: &str, email: &str, created_at: i64) -> Self {
let nonce_hash = Sha256::digest(format!("gitarena-identity:{user_id}").as_bytes());
let nonce = base64::encode(&nonce_hash[..20]);
let nonce = general_purpose::STANDARD.encode(&nonce_hash[..20]);

let identity_json = build_identity_json(username, email, &nonce, created_at);

Expand Down
4 changes: 3 additions & 1 deletion gitarena-issues/src/operation.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::author::Author;
use anyhow::Result;
use base64::Engine as _;
use base64::engine::general_purpose;
use rand::RngExt;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
Expand Down Expand Up @@ -100,5 +102,5 @@ pub struct OperationPack {
#[must_use]
pub fn random_nonce() -> String {
let bytes: [u8; 20] = rand::rng().random();
base64::encode(bytes)
general_purpose::STANDARD.encode(bytes)
}
8 changes: 4 additions & 4 deletions gitarena-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ workspace = true
proc-macro = true

[dependencies]
proc-macro-error = "1.0.4"
proc-macro2 = "1.0.27"
quote = "1.0.9"
syn = { version = "1.0.73", features = ["full"] }
proc-macro-error2 = "2.0.0"
proc-macro2 = "1.0.106"
quote = "1.0.46"
syn = { version = "2.0.118", features = ["full"] }
77 changes: 29 additions & 48 deletions gitarena-macros/src/ipc_packet.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use proc_macro::TokenStream;
use proc_macro_error::{emit_call_site_error, emit_error};
use proc_macro_error2::{emit_call_site_error, emit_error};
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::spanned::Spanned;
use syn::{DeriveInput, Lit, Meta, NestedMeta, parse_macro_input};
use syn::{DeriveInput, LitInt, LitStr, parse_macro_input};

pub(crate) fn ipc_packet(input: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(input as DeriveInput);
Expand All @@ -13,60 +13,41 @@ pub(crate) fn ipc_packet(input: TokenStream) -> TokenStream {
let mut packet_id = None;

input.attrs.retain(|attribute| {
if let Ok(Meta::List(list)) = attribute.parse_meta() {
let ipc = list.path.segments.first().is_some_and(|segment| segment.ident == "ipc");

if ipc {
for args in list.nested {
if let NestedMeta::Meta(Meta::NameValue(pair)) = args
&& let Some(segment) = pair.path.segments.first()
{
let identifier = segment.ident.to_string();
let value = pair.lit;
if !attribute.path().is_ident("ipc") {
return true;
}

match identifier.as_str() {
"packet" => {
if let Lit::Str(value) = value {
category = Some(value.value());
} else {
emit_error! {
value.span(),
"packet requires a string argument"
}
}
}
"id" => {
if let Lit::Int(value) = value {
packet_id = if let Ok(id) = value.base10_parse::<u64>() {
Some(id)
} else {
emit_error! {
value.span(),
"id argument could not be parsed into u64"
}
if let Err(e) = attribute.parse_nested_meta(|meta| {
let ident = meta.path.get_ident().map(|i| i.to_string()).unwrap_or_default();
let value = meta.value()?;

None
};
} else {
emit_error! {
value.span(),
"id requires a int argument"
}
}
}
_ => emit_error! {
segment.span(),
"unknown identifier, expected `packet` or `id`"
},
match ident.as_str() {
"packet" => {
let s: LitStr = value.parse()?;
category = Some(s.value());
}
"id" => {
let n: LitInt = value.parse()?;
match n.base10_parse::<u64>() {
Ok(id) => {
packet_id = Some(id);
}
Err(_) => {
emit_error!(n.span(), "id argument could not be parsed into u64");
}
}
}

return false;
_ => {
emit_error!(meta.path.span(), "unknown identifier, expected `packet` or `id`");
}
}

Ok(())
}) {
emit_call_site_error!("{}", e);
}

true
false
});

if let (Some(category), Some(packet_id)) = (category, packet_id) {
Expand Down
2 changes: 1 addition & 1 deletion gitarena-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
use crate::route::route as internal_route;

use proc_macro::TokenStream;
use proc_macro_error::proc_macro_error;
use proc_macro_error2::proc_macro_error;

mod config;
mod ipc_packet;
mod route;

Check warning on line 11 in gitarena-macros/src/lib.rs

View workflow job for this annotation

GitHub Actions / build

this argument is passed by value, but not consumed in the function body

warning: this argument is passed by value, but not consumed in the function body --> gitarena-macros/src/route.rs:11:27 | 11 | pub(crate) fn route(args: TokenStream, input: TokenStream) -> TokenStream { | ^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.96.0/index.html#needless_pass_by_value = note: `-W clippy::needless-pass-by-value` implied by `-W clippy::pedantic` = help: to override `-W clippy::pedantic` add `#[allow(clippy::needless_pass_by_value)]` help: consider taking a reference instead | 11 | pub(crate) fn route(args: &TokenStream, input: TokenStream) -> TokenStream { | +

Check warning on line 11 in gitarena-macros/src/lib.rs

View workflow job for this annotation

GitHub Actions / build

this argument is passed by value, but not consumed in the function body

warning: this argument is passed by value, but not consumed in the function body --> gitarena-macros/src/route.rs:11:27 | 11 | pub(crate) fn route(args: TokenStream, input: TokenStream) -> TokenStream { | ^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.96.0/index.html#needless_pass_by_value = note: `-W clippy::needless-pass-by-value` implied by `-W clippy::pedantic` = help: to override `-W clippy::pedantic` add `#[allow(clippy::needless_pass_by_value)]` help: consider taking a reference instead | 11 | pub(crate) fn route(args: &TokenStream, input: TokenStream) -> TokenStream { | +

/// Creates resource handler, allowing multiple HTTP method guards.
/// This method is similar to the `actix_web` method `actix_web::route`
Expand All @@ -18,7 +18,7 @@
/// ```text
/// #[route("path", method = "HTTP_METHOD"[, attributes])]
/// ```
///

Check warning on line 21 in gitarena-macros/src/lib.rs

View workflow job for this annotation

GitHub Actions / build

redundant closure

warning: redundant closure --> gitarena-macros/src/ipc_packet.rs:21:51 | 21 | let ident = meta.path.get_ident().map(|i| i.to_string()).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `std::string::ToString::to_string` | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.96.0/index.html#redundant_closure_for_method_calls = note: `-W clippy::redundant-closure-for-method-calls` implied by `-W clippy::pedantic` = help: to override `-W clippy::pedantic` add `#[allow(clippy::redundant_closure_for_method_calls)]`

Check warning on line 21 in gitarena-macros/src/lib.rs

View workflow job for this annotation

GitHub Actions / build

redundant closure

warning: redundant closure --> gitarena-macros/src/ipc_packet.rs:21:51 | 21 | let ident = meta.path.get_ident().map(|i| i.to_string()).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `std::string::ToString::to_string` | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.96.0/index.html#redundant_closure_for_method_calls = note: `-W clippy::redundant-closure-for-method-calls` implied by `-W clippy::pedantic` = help: to override `-W clippy::pedantic` add `#[allow(clippy::redundant_closure_for_method_calls)]`
/// # Attributes
///
/// - `"path"` - Raw literal string with path for which to register handler.
Expand All @@ -43,7 +43,7 @@
}

#[proc_macro]
pub fn from_optional_config(input: TokenStream) -> TokenStream {

Check warning on line 46 in gitarena-macros/src/lib.rs

View workflow job for this annotation

GitHub Actions / build

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> gitarena-macros/src/route.rs:46:66 | 46 | if let Some(sanitized) = sanitize_first_argument(&s) { | ^^ help: change this to: `s` | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.96.0/index.html#needless_borrow = note: `#[warn(clippy::needless_borrow)]` on by default

Check warning on line 46 in gitarena-macros/src/lib.rs

View workflow job for this annotation

GitHub Actions / build

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> gitarena-macros/src/route.rs:46:66 | 46 | if let Some(sanitized) = sanitize_first_argument(&s) { | ^^ help: change this to: `s` | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.96.0/index.html#needless_borrow = note: `#[warn(clippy::needless_borrow)]` on by default
internal_from_optional_config(input)
}

Expand Down
70 changes: 39 additions & 31 deletions gitarena-macros/src/route.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,53 @@
use proc_macro::TokenStream;
use proc_macro_error::{abort, abort_call_site, abort_if_dirty, emit_error};
use proc_macro_error2::{abort, abort_call_site, abort_if_dirty, emit_error};
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use quote::{ToTokens, quote};
use syn::parse::Parser;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{AttributeArgs, FnArg, ItemFn, Lit, LitStr, Meta, NestedMeta, Pat, parse_macro_input};
use syn::{Expr, ExprAssign, ExprLit, FnArg, ItemFn, Lit, LitStr, Pat, Token, parse_macro_input};

#[allow(clippy::too_many_lines)]
pub(crate) fn route(args: TokenStream, input: TokenStream) -> TokenStream {
let mut args = parse_macro_input!(args as AttributeArgs);
let parser = Punctuated::<Expr, Token![,]>::parse_terminated;
let mut args: Vec<Expr> = match parser.parse(args.clone()) {
Ok(exprs) => exprs.into_iter().collect(),
Err(e) => return e.to_compile_error().into(),
};

let mut input = parse_macro_input!(input as ItemFn);

let mut error_type = ErrorDisplayType::Unset;
let mut error_type_index = 0;
let mut sanitized_first_arg = None;

for (index, meta) in args.iter().enumerate() {
match meta {
NestedMeta::Meta(meta) => {
if let Meta::NameValue(name_value) = meta {
if let Some(segment) = name_value.path.segments.first() {
for (index, expr) in args.iter().enumerate() {
match expr {
Expr::Assign(ExprAssign { left, right, .. }) => {
if let Expr::Path(path) = left.as_ref() {
if let Some(segment) = path.path.segments.first() {
let lowered = segment.ident.to_string().to_lowercase();

if lowered.as_str() == "err"
&& let Some(parsed_error_type) = match_error_type(&name_value.lit)
&& let Some(parsed_error_type) = match_error_type(right.as_ref())
{
error_type = parsed_error_type;
error_type_index = index;
}
} else {
emit_error! {
meta.span(),
path.path.span(),
"meta name cannot be empty"
}
}
}
}
NestedMeta::Lit(literal) if index == 0 => {
if let Some(meta) = sanitize_first_argument(literal) {
sanitized_first_arg = Some(meta);
Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) if index == 0 => {
if let Some(sanitized) = sanitize_first_argument(&s) {
sanitized_first_arg = Some(sanitized);
}
}
NestedMeta::Lit(_) => { /* ignored - actix web will error if the attribute is invalid */ }
_ => { /* ignored - actix web will error if the attribute is invalid */ }
}
}

Expand All @@ -55,8 +62,8 @@ pub(crate) fn route(args: TokenStream, input: TokenStream) -> TokenStream {
args.remove(error_type_index);

// This cannot be done inline (with &mut) because of https://github.com/rust-lang/rust/issues/59159
if let Some(meta) = sanitized_first_arg {
args.insert(0, meta);
if let Some(sanitized) = sanitized_first_arg {
args.insert(0, sanitized);
args.remove(1);
}

Expand Down Expand Up @@ -178,9 +185,9 @@ impl ToTokens for ErrorDisplayType {
}
}

fn match_error_type(input: &Lit) -> Option<ErrorDisplayType> {
if let Lit::Str(str) = input {
let value = str.value().to_lowercase();
fn match_error_type(input: &Expr) -> Option<ErrorDisplayType> {
if let Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) = input {
let value = s.value().to_lowercase();

return match value.as_str() {
"html" => Some(ErrorDisplayType::Html),
Expand Down Expand Up @@ -218,19 +225,20 @@ fn match_error_type(input: &Lit) -> Option<ErrorDisplayType> {

/// Transforms routes which are only a "/" to an empty string. This allows scoped routes to have index
/// pages without having to declare their route with a literal empty string (which is quite confusing).
fn sanitize_first_argument(literal: &Lit) -> Option<NestedMeta> {
if let Lit::Str(str) = literal {
let value = str.value();

if value.is_empty() {
emit_error! {
str.span(),
"route cannot be empty";
help = "if you want to match on index, use \"/\""
}
} else if value == "/" {
return Some(NestedMeta::Lit(Lit::Str(LitStr::new("", str.span()))));
fn sanitize_first_argument(str: &LitStr) -> Option<Expr> {
let value = str.value();

if value.is_empty() {
emit_error! {
str.span(),
"route cannot be empty";
help = "if you want to match on index, use \"/\""
}
} else if value == "/" {
return Some(Expr::Lit(ExprLit {
attrs: vec![],
lit: Lit::Str(LitStr::new("", str.span())),
}));
}

None
Expand Down
Loading
Loading