ci: enforce strict Clippy across game targets

This commit is contained in:
DeFiDude 2026-08-06 18:49:06 -06:00
parent 60d570dd22
commit a3da010b5c
9 changed files with 136 additions and 112 deletions

View file

@ -31,6 +31,22 @@ jobs:
- name: Check formatting
run: cargo fmt --all -- --check
- name: Lint all targets
run: cargo clippy --all-targets --all-features -- -D warnings
run: cargo clippy --all-targets --all-features --locked -- -D warnings
- name: Run tests
run: cargo test --all-features
run: cargo test --all-features --locked
msrv:
name: Rust 1.85 MSRV
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@1.85.0
with:
components: clippy
- uses: Swatinem/rust-cache@v2
with:
cache-bin: false
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Clippy (all targets and features)
run: cargo clippy --all-targets --all-features --locked -- -D warnings

2
clippy.toml Normal file
View file

@ -0,0 +1,2 @@
# Keep Clippy suggestions aligned with the public MSRV in Cargo.toml.
msrv = "1.85"

View file

@ -102,11 +102,11 @@ fn main() {
let result = router_b
.dispatch_incoming(&move_env, player_a, player_b)
.unwrap();
if let IncomingDispatch::Applied(result) = result
&& let Some(emit) = &result.emit
{
if let Some(ev_type) = emit.get("type") {
println!("B inbound event: {ev_type:?}");
if let IncomingDispatch::Applied(result) = result {
if let Some(emit) = &result.emit {
if let Some(ev_type) = emit.get("type") {
println!("B inbound event: {ev_type:?}");
}
}
}

View file

@ -36,10 +36,10 @@ fn main() {
// Player B receives the challenge
println!("\n=== Player B receives challenge ===");
let result = router.dispatch_incoming(&env, player_a, player_b).unwrap();
if let IncomingDispatch::Applied(result) = result
&& let Some(emit) = &result.emit
{
println!("Event: {:?}", emit.get("type"));
if let IncomingDispatch::Applied(result) = result {
if let Some(emit) = &result.emit {
println!("Event: {:?}", emit.get("type"));
}
}
// Player B accepts
@ -63,10 +63,10 @@ fn main() {
let result = router
.dispatch_incoming(&accept_env, player_b, player_a)
.unwrap();
if let IncomingDispatch::Applied(result) = result
&& let Some(emit) = &result.emit
{
println!("Event: {:?}", emit.get("type"));
if let IncomingDispatch::Applied(result) = result {
if let Some(emit) = &result.emit {
println!("Event: {:?}", emit.get("type"));
}
}
// Play some moves
@ -103,12 +103,12 @@ fn main() {
// Other player receives
let result = router.dispatch_incoming(&move_env, player, other).unwrap();
if let IncomingDispatch::Applied(result) = result
&& let Some(emit) = &result.emit
{
if let Some(payload_val) = emit.get("payload") {
if let Some(board_val) = payload_val.get("b") {
println!("Board: {}", board_val.as_str().unwrap_or("?"));
if let IncomingDispatch::Applied(result) = result {
if let Some(emit) = &result.emit {
if let Some(payload_val) = emit.get("payload") {
if let Some(board_val) = payload_val.get("b") {
println!("Board: {}", board_val.as_str().unwrap_or("?"));
}
}
}
}

View file

@ -43,10 +43,10 @@ static FORCED_COIN: Mutex<Option<bool>> = Mutex::new(None);
fn flip_responder_coin() -> bool {
#[cfg(any(test, feature = "test-helpers"))]
{
if let Ok(guard) = FORCED_COIN.lock()
&& let Some(v) = *guard
{
return v;
if let Ok(guard) = FORCED_COIN.lock() {
if let Some(v) = *guard {
return v;
}
}
}
use rand::RngCore;
@ -179,10 +179,10 @@ fn insufficient_material(board: &Board) -> bool {
for sq in board.pieces(Piece::Bishop) & black {
b_sq = Some(sq);
}
if let (Some(ws), Some(bs)) = (w_sq, b_sq)
&& square_is_light(ws) == square_is_light(bs)
{
return true;
if let (Some(ws), Some(bs)) = (w_sq, b_sq) {
if square_is_light(ws) == square_is_light(bs) {
return true;
}
}
}
false
@ -1198,22 +1198,25 @@ impl ChessApp {
// On claim, pre-terminate locally so UI reflects immediately.
if reason == R_THREEFOLD || reason == R_FIFTY_MOVE {
let moves = meta_string_list(&session.metadata, "moves");
if let Ok(board) = replay_moves(&moves)
&& claim_reason(&board, &moves) == Some(reason.as_str())
{
let _ =
SessionStateMachine::apply_command(&mut session, CMD_DRAW_ACCEPT, false);
session
.metadata
.insert("terminal".into(), JsonValue::String("draw".into()));
session
.metadata
.insert("terminal_reason".into(), JsonValue::String(reason.clone()));
session
.metadata
.insert("turn".into(), JsonValue::String("".into()));
session.clear_draw_offer();
completed_claim = true;
if let Ok(board) = replay_moves(&moves) {
if claim_reason(&board, &moves) == Some(reason.as_str()) {
let _ = SessionStateMachine::apply_command(
&mut session,
CMD_DRAW_ACCEPT,
false,
);
session
.metadata
.insert("terminal".into(), JsonValue::String("draw".into()));
session
.metadata
.insert("terminal_reason".into(), JsonValue::String(reason.clone()));
session
.metadata
.insert("turn".into(), JsonValue::String("".into()));
session.clear_draw_offer();
completed_claim = true;
}
}
}
if !completed_claim {
@ -1260,11 +1263,11 @@ impl ChessApp {
}
fn handle_draw_decline_out(&self, session_id: &str, identity_id: &str) -> OutgoingResult {
if let Some(mut session) = self.get_session(session_id, identity_id)
&& SessionStateMachine::apply_command(&mut session, CMD_DRAW_DECLINE, false).is_ok()
{
session.clear_draw_offer();
self.save_session(&session);
if let Some(mut session) = self.get_session(session_id, identity_id) {
if SessionStateMachine::apply_command(&mut session, CMD_DRAW_DECLINE, false).is_ok() {
session.clear_draw_offer();
self.save_session(&session);
}
}
OutgoingResult {
payload: HashMap::new(),
@ -1529,16 +1532,16 @@ impl GameApp for ChessApp {
sender_hash: &str,
identity_id: &str,
) -> IncomingResult {
if command != CMD_ERROR
&& let Err(message) = self.validate_incoming_payload(
if command != CMD_ERROR {
if let Err(message) = self.validate_incoming_payload(
session_id,
command,
payload,
identity_id,
sender_hash,
)
{
return error_result(ERR_PROTOCOL_ERROR, &message);
) {
return error_result(ERR_PROTOCOL_ERROR, &message);
}
}
match command {
CMD_CHALLENGE => {
@ -1820,14 +1823,13 @@ impl GameApp for ChessApp {
SessionStateMachine::check_expiry(&mut session, Some(&Self::ttl_policy()), None);
if session.draw_offered_by().is_none() {
session.clear_draw_offer();
} else if let Some(owner) = session.draw_offered_by()
&& owner != session.identity_id
&& owner != session.contact_hash
{
return Err(LrgpError::Validation {
code: ERR_PROTOCOL_ERROR.into(),
message: "draw offer owner is not a bound participant".into(),
});
} else if let Some(owner) = session.draw_offered_by() {
if owner != session.identity_id && owner != session.contact_hash {
return Err(LrgpError::Validation {
code: ERR_PROTOCOL_ERROR.into(),
message: "draw offer owner is not a bound participant".into(),
});
}
}
self.save_session(&session);
Ok(())

View file

@ -1106,11 +1106,12 @@ impl GameApp for FourInARowApp {
sender_hash: &str,
identity_id: &str,
) -> IncomingResult {
if command != CMD_ERROR
&& let Err(message) =
if command != CMD_ERROR {
if let Err(message) =
self.validate_wire_payload(session_id, command, payload, identity_id)
{
return error_result(ERR_PROTOCOL_ERROR, &message);
{
return error_result(ERR_PROTOCOL_ERROR, &message);
}
}
match command {
CMD_CHALLENGE => self.handle_challenge_in(session_id, sender_hash, identity_id),

View file

@ -758,11 +758,11 @@ impl TicTacToeApp {
}
fn handle_draw_offer_out(&self, session_id: &str, identity_id: &str) -> OutgoingResult {
if let Some(mut session) = self.get_session(session_id, identity_id)
&& SessionStateMachine::apply_command(&mut session, CMD_DRAW_OFFER, false).is_ok()
{
session.set_draw_offer(identity_id);
self.save_session(&session);
if let Some(mut session) = self.get_session(session_id, identity_id) {
if SessionStateMachine::apply_command(&mut session, CMD_DRAW_OFFER, false).is_ok() {
session.set_draw_offer(identity_id);
self.save_session(&session);
}
}
OutgoingResult {
payload: HashMap::new(),
@ -786,11 +786,11 @@ impl TicTacToeApp {
}
fn handle_draw_decline_out(&self, session_id: &str, identity_id: &str) -> OutgoingResult {
if let Some(mut session) = self.get_session(session_id, identity_id)
&& SessionStateMachine::apply_command(&mut session, CMD_DRAW_DECLINE, false).is_ok()
{
session.clear_draw_offer();
self.save_session(&session);
if let Some(mut session) = self.get_session(session_id, identity_id) {
if SessionStateMachine::apply_command(&mut session, CMD_DRAW_DECLINE, false).is_ok() {
session.clear_draw_offer();
self.save_session(&session);
}
}
OutgoingResult {
payload: HashMap::new(),
@ -1051,11 +1051,12 @@ impl GameApp for TicTacToeApp {
sender_hash: &str,
identity_id: &str,
) -> IncomingResult {
if command != CMD_ERROR
&& let Err(message) =
if command != CMD_ERROR {
if let Err(message) =
self.validate_incoming_payload(session_id, command, payload, identity_id)
{
return error_result(ERR_PROTOCOL_ERROR, &message);
{
return error_result(ERR_PROTOCOL_ERROR, &message);
}
}
match command {
CMD_CHALLENGE => {
@ -1270,14 +1271,13 @@ impl GameApp for TicTacToeApp {
// Pre-owner persisted records and stray owner metadata cannot
// safely authorize a response.
session.clear_draw_offer();
} else if let Some(owner) = session.draw_offered_by()
&& owner != session.identity_id
&& owner != session.contact_hash
{
return Err(LrgpError::Validation {
code: ERR_PROTOCOL_ERROR.into(),
message: "draw offer owner is not a bound participant".into(),
});
} else if let Some(owner) = session.draw_offered_by() {
if owner != session.identity_id && owner != session.contact_hash {
return Err(LrgpError::Validation {
code: ERR_PROTOCOL_ERROR.into(),
message: "draw offer owner is not a bound participant".into(),
});
}
}
self.save_session(&session);
Ok(())

View file

@ -234,10 +234,10 @@ impl ReplayDedup {
pub fn forget_scoped_nonce(&mut self, identity_id: &str, session_id: &str, nonce: &[u8]) {
let key = (identity_id.to_string(), session_id.to_string());
if let Some(cache) = self.by_session.get_mut(&key)
&& let Some(position) = cache.position(nonce)
{
cache.entries.remove(position);
if let Some(cache) = self.by_session.get_mut(&key) {
if let Some(position) = cache.position(nonce) {
cache.entries.remove(position);
}
}
if self
.by_session

View file

@ -135,12 +135,14 @@ impl LrgpRouter {
return Ok(IncomingDispatch::Replay);
}
if validated.command == CMD_CHALLENGE
&& let Some((owner_app, _)) =
if validated.command == CMD_CHALLENGE {
if let Some((owner_app, _)) =
self.find_session_owner(&validated.session_id, identity_id)
&& owner_app != validated.app_id
{
return Err(LrgpError::SessionExists(validated.session_id));
{
if owner_app != validated.app_id {
return Err(LrgpError::SessionExists(validated.session_id));
}
}
}
if validated.command == CMD_ERROR {
@ -319,12 +321,12 @@ impl LrgpRouter {
if session.contact_hash.is_empty() {
return Err(LrgpError::ParticipantRequired);
}
if let Some(recipient) = recipient_hash
&& recipient != session.contact_hash
{
return Err(LrgpError::UnauthorizedPeer {
session_id: session.session_id,
});
if let Some(recipient) = recipient_hash {
if recipient != session.contact_hash {
return Err(LrgpError::UnauthorizedPeer {
session_id: session.session_id,
});
}
}
if command != CMD_ERROR {
@ -362,15 +364,15 @@ impl LrgpRouter {
app.handle_outgoing(&effective_session_id, command, payload, identity_id)
};
if command == CMD_CHALLENGE
&& let Err(error) = app.bind_session_peer(
if command == CMD_CHALLENGE {
if let Err(error) = app.bind_session_peer(
&effective_session_id,
identity_id,
recipient_hash.expect("challenge recipient checked above"),
)
{
app.rollback_session(&effective_session_id, identity_id, snapshot);
return Err(error);
) {
app.rollback_session(&effective_session_id, identity_id, snapshot);
return Err(error);
}
}
let final_envelope = envelope::pack_envelope(
@ -501,9 +503,10 @@ impl LrgpRouter {
let _creation_guard = self.session_creation.lock().unwrap();
if let Some((owner_app, _)) =
self.find_session_owner(&session.session_id, &session.identity_id)
&& owner_app != session.app_id
{
return Err(LrgpError::SessionExists(session.session_id));
if owner_app != session.app_id {
return Err(LrgpError::SessionExists(session.session_id));
}
}
app.upsert_session(session)
}