Skip to content
Merged
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
27 changes: 15 additions & 12 deletions src/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,21 @@ const DEFAULT_RUST_CHAT_ID: i64 = -1001228598755;
pub const DEFAULT_OPENAI_BASE_URL: &str = "https://api.openai.com/v1/chat/completions";

// Compile each mention regex exactly once. The patterns are compile-time
// constants, so `expect` here can only fire on a developer typo — never at
// runtime — which is why it is an accepted panic site.
static RUST_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(RUST_REGEX).expect("RUST_REGEX must compile"));
static BLAZING_FAST_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(BLAZING_FAST_REGEX).expect("BLAZING_FAST_REGEX must compile"));
static GAYNESS_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(GAYNESS_REGEX).expect("GAYNESS_REGEX must compile"));
static CHAT_GPT_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(CHAT_GPT_REGEX).expect("CHAT_GPT_REGEX must compile"));
static URL_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(URL_REGEX).expect("URL_REGEX must compile"));
// constants, so a failure can only be a developer typo, never a runtime error.
static RUST_RE: LazyLock<Regex> = LazyLock::new(|| compile_regex(RUST_REGEX));
static BLAZING_FAST_RE: LazyLock<Regex> = LazyLock::new(|| compile_regex(BLAZING_FAST_REGEX));
static GAYNESS_RE: LazyLock<Regex> = LazyLock::new(|| compile_regex(GAYNESS_REGEX));
static CHAT_GPT_RE: LazyLock<Regex> = LazyLock::new(|| compile_regex(CHAT_GPT_REGEX));
static URL_RE: LazyLock<Regex> = LazyLock::new(|| compile_regex(URL_REGEX));

/// Compile a compile-time-constant regex pattern. The `expect` is the one
/// sanctioned panic path (guarded by the crate-level `unwrap_used`/`expect_used`
/// deny): a bad pattern is a source bug caught immediately by the tests, not a
/// runtime failure on user input.
#[allow(clippy::expect_used)]
pub(crate) fn compile_regex(pattern: &str) -> Regex {
Regex::new(pattern).expect("regex pattern must compile")
}

#[derive(Clone)]
pub struct GptParameters {
Expand Down
15 changes: 6 additions & 9 deletions src/chat_gpt_handler.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::fmt::Debug;
use std::sync::LazyLock;

use crate::boot::compile_regex;
use crate::chat_gpt_handler::BotProfile::{Fedor, Felix, Ferris};
use crate::chat_gpt_handler::ChatMessageRole::{System, User};
use crate::gpt_service::{ChatMessage, ChatMessageRole};
Expand Down Expand Up @@ -43,28 +44,24 @@ static BOT_PROFILES: LazyLock<Vec<BotConfiguration<'static>>> = LazyLock::new(||
vec![
BotConfiguration {
profile: Fedor,
mention_regex: Regex::new(r"(?i)(fedor|ф[её]дор|федя)")
.expect("Fedor mention regex must compile"),
mention_regex: compile_regex(r"(?i)(fedor|ф[её]дор|федя)"),
gpt_system_context: FEDOR_CHAT_GPT_SYSTEM_CONTEXT,
},
BotConfiguration {
profile: Felix,
mention_regex: Regex::new(r"(?i)(felix|феликс)")
.expect("Felix mention regex must compile"),
mention_regex: compile_regex(r"(?i)(felix|феликс)"),
gpt_system_context: FELIX_CHAT_GPT_SYSTEM_CONTEXT,
},
BotConfiguration {
profile: Ferris,
mention_regex: Regex::new(r"(?i)(feris|ferris|ферис|феррис)")
.expect("Ferris mention regex must compile"),
mention_regex: compile_regex(r"(?i)(feris|ferris|ферис|феррис)"),
gpt_system_context: FERRIS_CHAT_GPT_SYSTEM_CONTEXT,
},
]
});
const SUMMARY_REQUEST_REGEX: &str = r"(?i)([чш].о?\b.*\bпроисходит)";
static CHAT_SUMMARY_REQUEST_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(SUMMARY_REQUEST_REGEX).expect("SUMMARY_REQUEST_REGEX must compile")
});
static CHAT_SUMMARY_REQUEST_REGEX: LazyLock<Regex> =
LazyLock::new(|| compile_regex(SUMMARY_REQUEST_REGEX));

pub async fn handle_chat_gpt_question(
bot: Bot,
Expand Down
13 changes: 11 additions & 2 deletions src/chat_repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,21 @@ pub async fn get_bot_context(
timeout_cmd(connection_manager.lrange(key, 0, 11)).await
}

/// Serialize a value for storage in Redis. Serialization of these plain,
/// owned structs is infallible in practice, and `ToRedisArgs::write_redis_args`
/// has no channel to report an error, so the `expect` is a sanctioned panic
/// site (guarded by the crate-level `expect_used` deny).
#[allow(clippy::expect_used)]
fn to_redis_json<T: serde::Serialize>(value: &T) -> String {
serde_json::to_string(value).expect("value must serialize to JSON for Redis")
}

impl ToRedisArgs for ChatMessage {
fn write_redis_args<W>(&self, out: &mut W)
where
W: ?Sized + RedisWrite,
{
out.write_arg_fmt(serde_json::to_string(self).expect("Can't serialize Context as string"))
out.write_arg_fmt(to_redis_json(self))
}
}

Expand All @@ -38,7 +47,7 @@ impl ToRedisArgs for BotProfile {
where
W: ?Sized + RedisWrite,
{
out.write_arg_fmt(serde_json::to_string(self).expect("Can't serialize Context as string"))
out.write_arg_fmt(to_redis_json(self))
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
// Forbid panic-on-unexpected in production code: runtime failures must be
// modelled as errors (AppError / Result), not `unwrap`/`expect`/`panic!`. The
// few sanctioned panic sites (compile-time-constant regexes, infallible serde)
// carry a narrow `#[allow(clippy::expect_used)]`. Test code is exempt.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used, clippy::panic))]

pub mod bf_mention_handler;
pub mod boot;
pub mod chat_gpt_handler;
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]

use std::env;
use std::sync::Arc;

Expand Down
Loading