Add Rust code health gate
This commit is contained in:
@@ -46,6 +46,9 @@ jobs:
|
||||
- name: Check formatting
|
||||
run: cargo fmt --all --check
|
||||
|
||||
- name: Check Rust code health
|
||||
run: scripts/check-rust-code-health.sh
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo clippy --workspace --all-targets --all-features --jobs "$CARGO_BUILD_JOBS" -- -D warnings
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# Rust Code Health
|
||||
|
||||
## Цель
|
||||
|
||||
В Community-репозитории нужно удерживать Rust-код в состоянии, где модули можно безопасно менять без разрастания скрытых связей.
|
||||
|
||||
Основные риски сейчас:
|
||||
|
||||
- крупные production-файлы с тестами внутри;
|
||||
- интеграционные сценарии, написанные рядом с кодом;
|
||||
- смешение HTTP, registry, runtime и test fixtures в одном модуле;
|
||||
- отсутствие автоматического контроля размера файлов.
|
||||
|
||||
## Инструменты
|
||||
|
||||
В Rust нет одного прямого аналога Python `import-linter`, который закрывает cohesion, coupling и connascence на уровне workspace.
|
||||
|
||||
Используемая база:
|
||||
|
||||
- `cargo fmt` — единый стиль.
|
||||
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` — локальные ошибки, smell-и и часть API-антипаттернов.
|
||||
- `cargo test --workspace --all-targets` — unit и integration-style тесты.
|
||||
- `scripts/check-rust-code-health.sh` — локальный repo-level gate для размера Rust-файлов.
|
||||
|
||||
Что можно добавить позже:
|
||||
|
||||
- `cargo-deny` для лицензий, security advisories и duplicate dependencies.
|
||||
- `cargo-machete` для поиска неиспользуемых зависимостей.
|
||||
- `cargo-udeps` для более строгой проверки зависимостей, если nightly допустим в отдельной job.
|
||||
- `cargo-modules` или `cargo-guppy` для анализа графа модулей и зависимостей между crate-ами.
|
||||
|
||||
## Правила размера
|
||||
|
||||
Новые Rust-файлы не должны быть больше `1000` строк.
|
||||
|
||||
Существующие крупные файлы зафиксированы baseline-ом в `scripts/check-rust-code-health.sh`. Они не должны расти дальше. При изменении таких файлов предпочтительно:
|
||||
|
||||
- выносить вложенный `mod tests` в `tests/`, если тест проверяет публичное поведение, БД, HTTP или несколько модулей сразу;
|
||||
- выделять route/service/runtime helper в отдельный модуль;
|
||||
- оставлять рядом с production-кодом только короткие unit-тесты приватной логики.
|
||||
|
||||
Текущие крупные файлы:
|
||||
|
||||
- `apps/admin-api/src/service.rs`
|
||||
- `apps/admin-api/src/app.rs`
|
||||
- `apps/mcp-server/src/main.rs`
|
||||
- `crates/crank-runtime/src/executor.rs`
|
||||
- `crates/crank-registry/src/postgres/mod.rs`
|
||||
- `crates/crank-community-mcp/src/app.rs`
|
||||
- `crates/crank-registry/src/postgres/operation.rs`
|
||||
|
||||
## Правила тестов
|
||||
|
||||
Рядом с кодом допустимы:
|
||||
|
||||
- маленькие pure unit tests;
|
||||
- проверки приватных parser/mapper/helper-функций;
|
||||
- тесты без сети, БД и поднятых серверов.
|
||||
|
||||
В `tests/` нужно выносить:
|
||||
|
||||
- PostgreSQL integration tests;
|
||||
- admin-api HTTP сценарии;
|
||||
- fake upstream servers;
|
||||
- publish/test-run/YAML roundtrip flow;
|
||||
- MCP Streamable HTTP сценарии.
|
||||
|
||||
Целевая структура:
|
||||
|
||||
```text
|
||||
apps/admin-api/tests/
|
||||
crates/crank-registry/tests/
|
||||
crates/crank-runtime/tests/
|
||||
crates/crank-community-mcp/tests/
|
||||
```
|
||||
|
||||
## Архитектурные границы
|
||||
|
||||
Базовое правило направления зависимостей:
|
||||
|
||||
```text
|
||||
apps/* -> crates/*
|
||||
crank-community-mcp -> crank-registry + crank-runtime + crank-core
|
||||
crank-runtime -> crank-core + crank-mapping + crank-schema + adapters
|
||||
crank-registry -> crank-core
|
||||
crank-adapter-rest -> crank-core + crank-mapping + crank-schema
|
||||
crank-core -> no app crates
|
||||
```
|
||||
|
||||
Если потребуется строгая автоматическая проверка границ, добавим отдельный script на основе `cargo metadata` или `cargo-guppy`.
|
||||
@@ -59,3 +59,15 @@ shell env file.
|
||||
|
||||
- `REGISTRY_USERNAME -> DEPLOY_REGISTRY_USER`
|
||||
- `REGISTRY_PASSWORD -> DEPLOY_REGISTRY_TOKEN`
|
||||
|
||||
## `check-rust-code-health.sh`
|
||||
|
||||
Проверяет базовые правила сопровождаемости Rust-кода:
|
||||
|
||||
- новые `.rs` файлы не больше 1000 строк;
|
||||
- существующие крупные файлы не должны расти сверх текущего baseline;
|
||||
- крупные inline test modules подсвечиваются как debt.
|
||||
|
||||
```bash
|
||||
./scripts/check-rust-code-health.sh
|
||||
```
|
||||
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
DEFAULT_MAX_LINES=1000
|
||||
|
||||
# Existing debt baseline. New files must stay under DEFAULT_MAX_LINES. These
|
||||
# files are allowed to exist, but CI fails if they grow further.
|
||||
declare -A FILE_LIMITS=(
|
||||
["apps/admin-api/src/service.rs"]=4112
|
||||
["apps/admin-api/src/app.rs"]=3823
|
||||
["apps/mcp-server/src/main.rs"]=3505
|
||||
["crates/crank-runtime/src/executor.rs"]=2829
|
||||
["crates/crank-registry/src/postgres/mod.rs"]=2154
|
||||
["crates/crank-community-mcp/src/app.rs"]=1647
|
||||
["crates/crank-registry/src/postgres/operation.rs"]=1022
|
||||
)
|
||||
|
||||
status=0
|
||||
|
||||
echo "Rust code health: checking file size limits"
|
||||
|
||||
while IFS= read -r file; do
|
||||
rel="${file#"$ROOT_DIR"/}"
|
||||
lines="$(wc -l < "$file" | tr -d ' ')"
|
||||
limit="${FILE_LIMITS[$rel]:-$DEFAULT_MAX_LINES}"
|
||||
|
||||
if (( lines > limit )); then
|
||||
echo "error: $rel has $lines lines; limit is $limit" >&2
|
||||
status=1
|
||||
fi
|
||||
done < <(
|
||||
find "$ROOT_DIR" \
|
||||
-path "$ROOT_DIR/target" -prune -o \
|
||||
-path "$ROOT_DIR/apps/ui/node_modules" -prune -o \
|
||||
-name '*.rs' -print | sort
|
||||
)
|
||||
|
||||
echo "Rust code health: checking new large inline test modules"
|
||||
|
||||
while IFS=: read -r file line _; do
|
||||
rel="${file#"$ROOT_DIR"/}"
|
||||
if [[ -n "${FILE_LIMITS[$rel]:-}" ]]; then
|
||||
continue
|
||||
fi
|
||||
lines="$(wc -l < "$file" | tr -d ' ')"
|
||||
if (( lines > DEFAULT_MAX_LINES / 2 )); then
|
||||
echo "warning: $rel contains inline tests and has $lines lines; prefer tests/ for integration-style tests" >&2
|
||||
fi
|
||||
done < <(
|
||||
grep -RIn --include='*.rs' '^[[:space:]]*mod tests[[:space:]]*{' \
|
||||
"$ROOT_DIR/apps" "$ROOT_DIR/crates" || true
|
||||
)
|
||||
|
||||
if (( status != 0 )); then
|
||||
cat >&2 <<'EOF'
|
||||
|
||||
Rust code health failed.
|
||||
|
||||
Rules:
|
||||
- new Rust files must stay at or below 1000 lines;
|
||||
- existing large files are pinned to the current baseline and must not grow;
|
||||
- when touching a large file, prefer extracting modules or moving integration-style tests to tests/.
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
exit "$status"
|
||||
Reference in New Issue
Block a user