فهرست منبع

feat(ci): add nightly rustfmt automation with flexible formatting policy (#1260)

* feat(ci): add nightly rustfmt automation with flexible formatting policy

- Add automated nightly rustfmt workflow that runs daily at midnight UTC
- Automatically creates PRs when nightly formatting changes are detected
- Switch CI checks to use stable rustfmt to reduce contributor friction
- Update DEVELOPMENT.md to document flexible formatting policy
- Allow PRs formatted with either stable or nightly rustfmt
tsk 1 هفته پیش
والد
کامیت
9354c2c698

+ 3 - 8
.github/workflows/ci.yml

@@ -34,16 +34,11 @@ jobs:
       - name: Rust Cache
         uses: Swatinem/rust-cache@v2
         with:
-          shared-key: "nightly-${{ steps.flake-hash.outputs.hash }}"
+          shared-key: "stable-${{ steps.flake-hash.outputs.hash }}"
       - name: Cargo fmt
-        run: |
-          nix develop -i -L .#nightly --command bash -c '
-            # Force use of Nix-provided rustfmt
-            export RUSTFMT=$(command -v rustfmt)
-            cargo fmt --check
-          '
+        run: nix develop -i -L .#stable --command cargo fmt --check
       - name: typos
-        run: nix develop -i -L .#nightly --command typos
+        run: nix develop -i -L .#stable --command typos
 
   examples:
     name: "Run examples"

+ 57 - 0
.github/workflows/nightly-rustfmt.yml

@@ -0,0 +1,57 @@
+name: Nightly rustfmt
+on:
+  schedule:
+    - cron: "0 0 * * *" # runs daily at 00:00 UTC
+  workflow_dispatch: # allows manual triggering
+
+permissions: {}
+
+jobs:
+  format:
+    name: Nightly rustfmt
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+      pull-requests: write
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          ref: main
+          persist-credentials: false
+          token: ${{ secrets.BACKPORT_TOKEN }}
+      - name: Get flake hash
+        id: flake-hash
+        run: echo "hash=$(sha256sum flake.lock | cut -d' ' -f1 | cut -c1-8)" >> $GITHUB_OUTPUT
+      - name: Install Nix
+        uses: DeterminateSystems/nix-installer-action@v17
+      - name: Nix Cache
+        uses: DeterminateSystems/magic-nix-cache-action@main
+        with:
+          diagnostic-endpoint: ""
+          use-flakehub: false
+      - name: Rust Cache
+        uses: Swatinem/rust-cache@v2
+        with:
+          shared-key: "nightly-${{ steps.flake-hash.outputs.hash }}"
+      - name: Run Nightly rustfmt
+        run: |
+          nix develop -i -L .#nightly --command bash -c '
+            # Force use of Nix-provided rustfmt
+            export RUSTFMT=$(command -v rustfmt)
+            cargo +nightly fmt
+          '
+          # Manually remove trailing whitespace
+          git ls-files -- '*.rs' -z | xargs -0 sed -E -i'' -e 's/[[:space:]]+$//'
+      - name: Get the current date
+        run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
+      - name: Create Pull Request
+        uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
+        with:
+          token: ${{ secrets.BACKPORT_TOKEN }}
+          author: Fmt Bot <bot@cashudevkit.org>
+          title: Automated nightly rustfmt (${{ env.date }})
+          body: |
+            Automated nightly `rustfmt` changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action
+          commit-message: ${{ env.date }} automated rustfmt nightly
+          labels: rustfmt
+          branch: automated-rustfmt-${{ env.date }}

+ 37 - 0
DEVELOPMENT.md

@@ -175,6 +175,43 @@ NOTE: if this command fails on macos change the nix channel to unstable (in the
 just format
 ```
 
+## Code Formatting
+
+CDK uses a flexible rustfmt policy to balance code quality with developer experience:
+
+### Formatting Requirements for PRs
+Pull requests can be formatted with **either stable or nightly** rustfmt - both are accepted:
+
+- **Stable rustfmt:** Standard Rust formatting (less strict)
+- **Nightly rustfmt:** More strict formatting with additional rules
+
+**Why both are accepted:**
+- We prefer nightly rustfmt's stricter formatting
+- We don't want to force contributors to install nightly Rust
+- This reduces friction for developers using stable toolchains
+
+```bash
+# Format with stable (default)
+just format
+
+# Format with nightly (if you have it installed)
+cargo +nightly fmt
+```
+
+The CI will check your PR with stable rustfmt, so as long as your code passes stable formatting, your PR will pass CI.
+
+### Automated Nightly Formatting
+To keep the codebase consistently formatted with nightly rustfmt over time:
+
+- **Daily Check:** Every night at midnight UTC, a GitHub Action runs nightly rustfmt on the `main` branch
+- **Automated PRs:** If nightly rustfmt produces formatting changes, a PR is automatically created with:
+  - Title: `Automated nightly rustfmt (YYYY-MM-DD)`
+  - Label: `rustfmt`
+  - Author: `Fmt Bot <bot@cashudevkit.org>`
+- **Review Process:** These automated PRs are reviewed and merged to keep the codebase aligned with nightly formatting
+
+This approach ensures the codebase gradually adopts nightly formatting improvements without blocking contributors who use stable Rust.
+
 
 ### Running Clippy
 ```bash

+ 4 - 4
crates/cdk-ffi/src/types/mint.rs

@@ -739,12 +739,12 @@ mod tests {
         let ffi_nuts: Nuts = cdk_nuts.clone().into();
 
         // Verify NUT04 settings
-        assert_eq!(ffi_nuts.nut04.disabled, false);
+        assert!(!ffi_nuts.nut04.disabled);
         assert_eq!(ffi_nuts.nut04.methods.len(), 1);
         assert_eq!(ffi_nuts.nut04.methods[0].description, Some(true));
 
         // Verify NUT05 settings
-        assert_eq!(ffi_nuts.nut05.disabled, false);
+        assert!(!ffi_nuts.nut05.disabled);
         assert_eq!(ffi_nuts.nut05.methods.len(), 1);
         assert_eq!(ffi_nuts.nut05.methods[0].amountless, Some(true));
 
@@ -969,8 +969,8 @@ mod tests {
         let ffi_nuts: Nuts = cdk_nuts.into();
 
         // Should have collected multiple units
-        assert!(ffi_nuts.mint_units.len() >= 1);
-        assert!(ffi_nuts.melt_units.len() >= 1);
+        assert!(!ffi_nuts.mint_units.is_empty());
+        assert!(!ffi_nuts.melt_units.is_empty());
     }
 
     #[test]

+ 1 - 1
crates/cdk-integration-tests/tests/bolt12.rs

@@ -385,7 +385,7 @@ async fn test_regtest_bolt12_mint_extra() -> Result<()> {
         Err(err) => match err {
             cdk::Error::TransactionUnbalanced(_, _, _) => (),
             err => {
-                bail!("Wrong mint error returned: {}", err.to_string());
+                bail!("Wrong mint error returned: {}", err);
             }
         },
         Ok(_) => {

+ 1 - 1
crates/cdk-integration-tests/tests/test_swap_flow.rs

@@ -861,7 +861,7 @@ async fn test_swap_state_transition_notifications() {
             cashu::NotificationPayload::ProofState(cashu::ProofState { y, state, .. }) => {
                 state_transitions
                     .entry(y.to_string())
-                    .or_insert_with(Vec::new)
+                    .or_default()
                     .push(state);
             }
             _ => panic!("Unexpected notification type"),

+ 0 - 2
crates/cdk/src/mint/melt/melt_saga/tests.rs

@@ -8,8 +8,6 @@
 //! - Concurrent operations
 //! - Failure handling
 
-#![cfg(test)]
-
 use cdk_common::mint::{MeltSagaState, OperationKind, Saga};
 use cdk_common::nuts::MeltQuoteState;
 use cdk_common::{Amount, ProofsMethods, State};

+ 7 - 8
crates/cdk/src/mint/swap/swap_saga/tests.rs

@@ -1,4 +1,3 @@
-#![cfg(test)]
 //! Unit tests for the swap saga implementation
 //!
 //! These tests verify the swap saga pattern using in-memory mints and databases,
@@ -933,7 +932,7 @@ async fn test_swap_saga_concurrent_swaps() {
     let task1 = tokio::spawn(async move {
         let db = mint1.localstore();
         let pubsub = mint1.pubsub_manager();
-        let saga = SwapSaga::new(&*mint1, db, pubsub);
+        let saga = SwapSaga::new(&mint1, db, pubsub);
 
         let saga = saga
             .setup_swap(&proofs1, &output_blinded_messages_1, None, verification1)
@@ -945,7 +944,7 @@ async fn test_swap_saga_concurrent_swaps() {
     let task2 = tokio::spawn(async move {
         let db = mint2.localstore();
         let pubsub = mint2.pubsub_manager();
-        let saga = SwapSaga::new(&*mint2, db, pubsub);
+        let saga = SwapSaga::new(&mint2, db, pubsub);
 
         let saga = saga
             .setup_swap(&proofs2, &output_blinded_messages_2, None, verification2)
@@ -957,7 +956,7 @@ async fn test_swap_saga_concurrent_swaps() {
     let task3 = tokio::spawn(async move {
         let db = mint3.localstore();
         let pubsub = mint3.pubsub_manager();
-        let saga = SwapSaga::new(&*mint3, db, pubsub);
+        let saga = SwapSaga::new(&mint3, db, pubsub);
 
         let saga = saga
             .setup_swap(&proofs3, &output_blinded_messages_3, None, verification3)
@@ -1976,19 +1975,19 @@ async fn test_operation_id_uniqueness_and_tracking() {
     {
         let pubsub = mint.pubsub_manager();
 
-        let saga_1 = SwapSaga::new(&*mint, db.clone(), pubsub.clone());
+        let saga_1 = SwapSaga::new(&mint, db.clone(), pubsub.clone());
         let _saga_1 = saga_1
             .setup_swap(&proofs_1, &outputs_1, None, verification_1)
             .await
             .expect("Swap 1 setup should succeed");
 
-        let saga_2 = SwapSaga::new(&*mint, db.clone(), pubsub.clone());
+        let saga_2 = SwapSaga::new(&mint, db.clone(), pubsub.clone());
         let _saga_2 = saga_2
             .setup_swap(&proofs_2, &outputs_2, None, verification_2)
             .await
             .expect("Swap 2 setup should succeed");
 
-        let saga_3 = SwapSaga::new(&*mint, db.clone(), pubsub.clone());
+        let saga_3 = SwapSaga::new(&mint, db.clone(), pubsub.clone());
         let _saga_3 = saga_3
             .setup_swap(&proofs_3, &outputs_3, None, verification_3)
             .await
@@ -2033,7 +2032,7 @@ async fn test_operation_id_uniqueness_and_tracking() {
     let verification = create_verification(amount);
 
     let pubsub = mint.pubsub_manager();
-    let new_saga = SwapSaga::new(&*mint, db, pubsub);
+    let new_saga = SwapSaga::new(&mint, db, pubsub);
 
     let result = new_saga
         .setup_swap(&proofs_1, &new_outputs_1, None, verification)

+ 2 - 9
crates/cdk/src/test_helpers/mint.rs

@@ -137,11 +137,7 @@ pub async fn mint_test_proofs(mint: &Mint, amount: Amount) -> Result<Proofs, Err
         sleep(Duration::from_secs(1)).await;
     }
 
-    let keysets = mint
-        .get_active_keysets()
-        .get(&CurrencyUnit::Sat)
-        .unwrap()
-        .clone();
+    let keysets = *mint.get_active_keysets().get(&CurrencyUnit::Sat).unwrap();
 
     let keys = mint
         .keyset_pubkeys(&keysets)?
@@ -151,10 +147,7 @@ pub async fn mint_test_proofs(mint: &Mint, amount: Amount) -> Result<Proofs, Err
         .keys
         .clone();
 
-    let fees: (u64, Vec<u64>) = (
-        0,
-        keys.iter().map(|a| a.0.to_u64()).collect::<Vec<_>>().into(),
-    );
+    let fees: (u64, Vec<u64>) = (0, keys.iter().map(|a| a.0.to_u64()).collect::<Vec<_>>());
 
     let premint_secrets =
         PreMintSecrets::random(keysets, amount, &SplitTarget::None, &fees.into()).unwrap();

+ 0 - 1
crates/cdk/src/test_helpers/mod.rs

@@ -1,4 +1,3 @@
-#![cfg(test)]
 //! Test helper utilities for CDK unit tests
 //!
 //! This module provides shared test utilities for creating test mints, wallets,

+ 9 - 1
flake.nix

@@ -184,7 +184,15 @@
 
             stable = pkgs.mkShell (
               {
-                shellHook = ''${_shellHook}'';
+                shellHook = ''
+                  ${_shellHook}
+                  # Needed for github ci
+                  export LD_LIBRARY_PATH=${
+                    pkgs.lib.makeLibraryPath [
+                      pkgs.zlib
+                    ]
+                  }:$LD_LIBRARY_PATH
+                '';
                 buildInputs = buildInputs ++ [ stable_toolchain ];
                 inherit nativeBuildInputs;
 

+ 4 - 4
justfile

@@ -59,7 +59,7 @@ test:
   if [ ! -f Cargo.toml ]; then
     cd {{invocation_directory()}}
   fi
-  cargo test --lib
+  cargo test --lib --workspace --exclude cdk-postgres
 
   # Run pure integration tests
   cargo test -p cdk-integration-tests --test mint 
@@ -155,11 +155,11 @@ test-nutshell:
     
 
 # run `cargo clippy` on everything
-clippy *ARGS="--locked --offline --workspace --all-targets":
-  cargo clippy {{ARGS}}
+clippy *ARGS="--workspace --all-targets":
+  cargo clippy {{ARGS}} -- -D warnings
 
 # run `cargo clippy --fix` on everything
-clippy-fix *ARGS="--locked --offline --workspace --all-targets":
+clippy-fix *ARGS="--workspace --all-targets":
   cargo clippy {{ARGS}} --fix
 
 typos: