moai-adk 0.4.5__py3-none-any.whl → 0.20.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of moai-adk might be problematic. Click here for more details.
- moai_adk/__init__.py +1 -1
- moai_adk/__main__.py +74 -1
- moai_adk/cli/commands/__init__.py +1 -1
- moai_adk/cli/commands/analyze.py +119 -0
- moai_adk/cli/commands/backup.py +25 -1
- moai_adk/cli/commands/doctor.py +31 -5
- moai_adk/cli/commands/improve_user_experience.py +307 -0
- moai_adk/cli/commands/init.py +111 -10
- moai_adk/cli/commands/status.py +33 -3
- moai_adk/cli/commands/update.py +921 -130
- moai_adk/cli/commands/validate_links.py +120 -0
- moai_adk/cli/prompts/init_prompts.py +22 -87
- moai_adk/core/analysis/__init__.py +9 -0
- moai_adk/core/analysis/session_analyzer.py +388 -0
- moai_adk/core/analysis/tag_chain_analyzer.py +344 -0
- moai_adk/core/analysis/tag_chain_repair.py +879 -0
- moai_adk/core/config/__init__.py +19 -0
- moai_adk/core/config/migration.py +235 -0
- moai_adk/core/git/__init__.py +1 -1
- moai_adk/core/git/branch.py +1 -1
- moai_adk/core/git/commit.py +1 -1
- moai_adk/core/git/manager.py +1 -1
- moai_adk/core/issue_creator.py +313 -0
- moai_adk/core/mcp/setup.py +56 -0
- moai_adk/core/mcp/setup_old.py +296 -0
- moai_adk/core/project/backup_utils.py +1 -1
- moai_adk/core/project/checker.py +2 -2
- moai_adk/core/project/detector.py +211 -12
- moai_adk/core/project/initializer.py +85 -15
- moai_adk/core/project/phase_executor.py +76 -13
- moai_adk/core/project/validator.py +13 -13
- moai_adk/core/quality/__init__.py +1 -1
- moai_adk/core/quality/trust_checker.py +1 -1
- moai_adk/core/quality/validators/__init__.py +1 -1
- moai_adk/core/quality/validators/base_validator.py +1 -1
- moai_adk/core/tags/__init__.py +86 -0
- moai_adk/core/tags/auto_corrector.py +693 -0
- moai_adk/core/tags/ci_validator.py +463 -0
- moai_adk/core/tags/cli.py +283 -0
- moai_adk/core/tags/generator.py +109 -0
- moai_adk/core/tags/inserter.py +99 -0
- moai_adk/core/tags/mapper.py +126 -0
- moai_adk/core/tags/parser.py +76 -0
- moai_adk/core/tags/policy_validator.py +580 -0
- moai_adk/core/tags/pre_commit_validator.py +421 -0
- moai_adk/core/tags/reporter.py +956 -0
- moai_adk/core/tags/rollback_manager.py +525 -0
- moai_adk/core/tags/tags.py +149 -0
- moai_adk/core/tags/validator.py +897 -0
- moai_adk/core/template/__init__.py +1 -1
- moai_adk/core/template/backup.py +1 -1
- moai_adk/core/template/merger.py +50 -1
- moai_adk/core/template/processor.py +119 -13
- moai_adk/core/template_engine.py +268 -0
- moai_adk/templates/.claude/agents/alfred/backend-expert.md +348 -0
- moai_adk/templates/.claude/agents/alfred/cc-manager.md +209 -944
- moai_adk/templates/.claude/agents/alfred/database-expert.md +352 -0
- moai_adk/templates/.claude/agents/alfred/debug-helper.md +34 -5
- moai_adk/templates/.claude/agents/alfred/devops-expert.md +464 -0
- moai_adk/templates/.claude/agents/alfred/doc-syncer.md +38 -8
- moai_adk/templates/.claude/agents/alfred/format-expert.md +469 -0
- moai_adk/templates/.claude/agents/alfred/frontend-expert.md +357 -0
- moai_adk/templates/.claude/agents/alfred/git-manager.md +128 -9
- moai_adk/templates/.claude/agents/alfred/implementation-planner.md +104 -6
- moai_adk/templates/.claude/agents/alfred/project-manager.md +88 -16
- moai_adk/templates/.claude/agents/alfred/quality-gate.md +36 -9
- moai_adk/templates/.claude/agents/alfred/security-expert.md +270 -0
- moai_adk/templates/.claude/agents/alfred/skill-factory.md +865 -0
- moai_adk/templates/.claude/agents/alfred/spec-builder.md +214 -43
- moai_adk/templates/.claude/agents/alfred/tag-agent.md +111 -9
- moai_adk/templates/.claude/agents/alfred/tdd-implementer.md +309 -160
- moai_adk/templates/.claude/agents/alfred/trust-checker.md +36 -7
- moai_adk/templates/.claude/agents/alfred/ui-ux-expert.md +605 -0
- moai_adk/templates/.claude/commands/alfred/0-project.md +393 -966
- moai_adk/templates/.claude/commands/alfred/1-plan.md +651 -367
- moai_adk/templates/.claude/commands/alfred/2-run.md +388 -241
- moai_adk/templates/.claude/commands/alfred/3-sync.md +1921 -410
- moai_adk/templates/.claude/commands/alfred/9-feedback.md +153 -0
- moai_adk/templates/.claude/commands/alfred/release-new.md +3604 -0
- moai_adk/templates/.claude/hooks/alfred/core/project.py +484 -20
- moai_adk/templates/.claude/hooks/alfred/core/timeout.py +136 -0
- moai_adk/templates/.claude/hooks/alfred/core/ttl_cache.py +108 -0
- moai_adk/templates/.claude/hooks/alfred/core/version_cache.py +198 -0
- moai_adk/templates/.claude/hooks/alfred/handlers/__init__.py +14 -6
- moai_adk/templates/.claude/hooks/alfred/post_tool__enable_streaming_ui.py +50 -0
- moai_adk/templates/.claude/hooks/alfred/post_tool__log_changes.py +93 -0
- moai_adk/templates/.claude/hooks/alfred/post_tool__tag_auto_corrector.py +407 -0
- moai_adk/templates/.claude/hooks/alfred/pre_tool__auto_checkpoint.py +99 -0
- moai_adk/templates/.claude/hooks/alfred/pre_tool__realtime_tag_monitor.py +335 -0
- moai_adk/templates/.claude/hooks/alfred/pre_tool__tag_policy_validator.py +325 -0
- moai_adk/templates/.claude/hooks/alfred/session_end__cleanup.py +93 -0
- moai_adk/templates/.claude/hooks/alfred/session_start__auto_cleanup.py +580 -0
- moai_adk/templates/.claude/hooks/alfred/session_start__show_project_info.py +298 -0
- moai_adk/templates/.claude/hooks/alfred/shared/core/__init__.py +170 -0
- moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/checkpoint.py +3 -3
- moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/context.py +5 -5
- moai_adk/templates/.claude/hooks/alfred/shared/core/project.py +749 -0
- moai_adk/templates/.claude/hooks/alfred/shared/core/tags.py +230 -0
- moai_adk/templates/.claude/hooks/alfred/shared/core/version_cache.py +198 -0
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/__init__.py +21 -0
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/daily_analysis.py +351 -0
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/notification.py +154 -0
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/session.py +174 -0
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/tool.py +87 -0
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/user.py +61 -0
- moai_adk/templates/.claude/hooks/alfred/user_prompt__jit_load_docs.py +111 -0
- moai_adk/templates/.claude/hooks/alfred/utils/__init__.py +1 -0
- moai_adk/templates/.claude/hooks/alfred/utils/hook_config.py +94 -0
- moai_adk/templates/.claude/hooks/alfred/utils/timeout.py +161 -0
- moai_adk/templates/.claude/output-styles/alfred/alfred-moai-adk-beginner.md +267 -0
- moai_adk/templates/.claude/output-styles/alfred/keating-personal-tutor.md +440 -0
- moai_adk/templates/.claude/output-styles/alfred/r2d2-agentic-coding.md +583 -0
- moai_adk/templates/.claude/settings.json +96 -14
- moai_adk/templates/.claude/skills/moai-alfred-agent-guide/SKILL.md +70 -0
- moai_adk/templates/.claude/skills/moai-alfred-agent-guide/examples.md +62 -0
- moai_adk/templates/.claude/skills/moai-alfred-agent-guide/reference.md +242 -0
- moai_adk/templates/.claude/skills/moai-alfred-ask-user-questions/SKILL.md +237 -0
- moai_adk/templates/.claude/skills/moai-alfred-ask-user-questions/examples.md +871 -0
- moai_adk/templates/.claude/skills/moai-alfred-ask-user-questions/reference.md +653 -0
- moai_adk/templates/.claude/skills/moai-alfred-clone-pattern/README.md +162 -0
- moai_adk/templates/.claude/skills/moai-alfred-clone-pattern/SKILL.md +227 -0
- moai_adk/templates/.claude/skills/moai-alfred-clone-pattern/examples.md +354 -0
- moai_adk/templates/.claude/skills/moai-alfred-clone-pattern/reference.md +158 -0
- moai_adk/templates/.claude/skills/moai-alfred-code-reviewer/SKILL.md +179 -79
- moai_adk/templates/.claude/skills/moai-alfred-code-reviewer/examples.md +117 -0
- moai_adk/templates/.claude/skills/moai-alfred-code-reviewer/scripts/pre-review-check.sh +62 -0
- moai_adk/templates/.claude/skills/moai-alfred-config-schema/SKILL.md +132 -0
- moai_adk/templates/.claude/skills/moai-alfred-config-schema/examples.md +28 -0
- moai_adk/templates/.claude/skills/moai-alfred-config-schema/reference.md +444 -0
- moai_adk/templates/.claude/skills/moai-alfred-context-budget/SKILL.md +62 -0
- moai_adk/templates/.claude/skills/moai-alfred-context-budget/examples.md +28 -0
- moai_adk/templates/.claude/skills/moai-alfred-context-budget/reference.md +405 -0
- moai_adk/templates/.claude/skills/moai-alfred-dev-guide/SKILL.md +51 -0
- moai_adk/templates/.claude/skills/moai-alfred-dev-guide/examples.md +355 -0
- moai_adk/templates/.claude/skills/moai-alfred-dev-guide/reference.md +239 -0
- moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/SKILL.md +323 -0
- moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/examples.md +286 -0
- moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/reference.md +126 -0
- moai_adk/templates/.claude/skills/moai-alfred-issue-labels/SKILL.md +229 -0
- moai_adk/templates/.claude/skills/moai-alfred-issue-labels/examples.md +4 -0
- moai_adk/templates/.claude/skills/moai-alfred-issue-labels/reference.md +150 -0
- moai_adk/templates/.claude/skills/moai-alfred-language-detection/SKILL.md +87 -73
- moai_adk/templates/.claude/skills/moai-alfred-language-detection/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-alfred-language-detection/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-alfred-personas/README.md +42 -0
- moai_adk/templates/.claude/skills/moai-alfred-personas/SKILL.md +429 -0
- moai_adk/templates/.claude/skills/moai-alfred-personas/examples.md +520 -0
- moai_adk/templates/.claude/skills/moai-alfred-personas/reference.md +405 -0
- moai_adk/templates/.claude/skills/moai-alfred-practices/SKILL.md +89 -0
- moai_adk/templates/.claude/skills/moai-alfred-practices/examples.md +122 -0
- moai_adk/templates/.claude/skills/moai-alfred-practices/reference.md +369 -0
- moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/SKILL.md +508 -0
- moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/examples.md +481 -0
- moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/reference.md +100 -0
- moai_adk/templates/.claude/skills/moai-alfred-rules/SKILL.md +77 -0
- moai_adk/templates/.claude/skills/moai-alfred-rules/examples.md +265 -0
- moai_adk/templates/.claude/skills/moai-alfred-rules/reference.md +539 -0
- moai_adk/templates/.claude/skills/moai-alfred-session-state/SKILL.md +320 -0
- moai_adk/templates/.claude/skills/moai-alfred-session-state/examples.md +4 -0
- moai_adk/templates/.claude/skills/moai-alfred-session-state/reference.md +84 -0
- moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/README.md +137 -0
- moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/SKILL.md +219 -0
- moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/examples/validate-spec.sh +161 -0
- moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/examples.md +541 -0
- moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/reference.md +622 -0
- moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/SKILL.md +19 -0
- moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/examples.md +4 -0
- moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/reference.md +211 -0
- moai_adk/templates/.claude/skills/moai-alfred-workflow/SKILL.md +288 -0
- moai_adk/templates/.claude/skills/moai-cc-agents/SKILL.md +269 -0
- moai_adk/templates/.claude/skills/moai-cc-agents/templates/agent-template.md +32 -0
- moai_adk/templates/.claude/skills/moai-cc-claude-md/SKILL.md +298 -0
- moai_adk/templates/.claude/skills/moai-cc-claude-md/templates/CLAUDE-template.md +26 -0
- moai_adk/templates/.claude/skills/moai-cc-commands/SKILL.md +307 -0
- moai_adk/templates/.claude/skills/moai-cc-commands/templates/command-template.md +21 -0
- moai_adk/templates/.claude/skills/moai-cc-hooks/SKILL.md +252 -0
- moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/pre-bash-check.sh +19 -0
- moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/preserve-permissions.sh +19 -0
- moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/validate-bash-command.py +24 -0
- moai_adk/templates/.claude/skills/moai-cc-mcp-plugins/SKILL.md +199 -0
- moai_adk/templates/.claude/skills/moai-cc-mcp-plugins/templates/settings-mcp-template.json +39 -0
- moai_adk/templates/.claude/skills/moai-cc-memory/SKILL.md +316 -0
- moai_adk/templates/.claude/skills/moai-cc-memory/templates/session-summary-template.md +18 -0
- moai_adk/templates/.claude/skills/moai-cc-settings/SKILL.md +263 -0
- moai_adk/templates/.claude/skills/moai-cc-settings/templates/settings-complete-template.json +30 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/CHECKLIST.md +482 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/EXAMPLES.md +303 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/INTERACTIVE-DISCOVERY.md +524 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/METADATA.md +477 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/PARALLEL-ANALYSIS-REPORT.md +429 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/PYTHON-VERSION-MATRIX.md +391 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/SKILL-FACTORY-WORKFLOW.md +431 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/SKILL-UPDATE-ADVISOR.md +577 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/SKILL.md +273 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/STEP-BY-STEP-GUIDE.md +466 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/STRUCTURE.md +583 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/WEB-RESEARCH.md +526 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/reference.md +608 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/scripts/generate-structure.sh +328 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/scripts/validate-skill.sh +312 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/templates/SKILL_TEMPLATE.md +245 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/templates/examples-template.md +285 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/templates/reference-template.md +278 -0
- moai_adk/templates/.claude/skills/moai-cc-skill-factory/templates/scripts-template.sh +303 -0
- moai_adk/templates/.claude/skills/moai-cc-skills/SKILL.md +291 -0
- moai_adk/templates/.claude/skills/moai-cc-skills/templates/SKILL-template.md +15 -0
- moai_adk/templates/.claude/skills/moai-change-logger/SKILL.md +563 -0
- moai_adk/templates/.claude/skills/moai-design-systems/SKILL.md +802 -0
- moai_adk/templates/.claude/skills/moai-design-systems/examples.md +1238 -0
- moai_adk/templates/.claude/skills/moai-design-systems/reference.md +673 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/SKILL.md +234 -43
- moai_adk/templates/.claude/skills/moai-domain-backend/examples.md +1633 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/reference.md +660 -0
- moai_adk/templates/.claude/skills/moai-domain-cli-tool/SKILL.md +97 -69
- moai_adk/templates/.claude/skills/moai-domain-cli-tool/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-cli-tool/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-domain-data-science/SKILL.md +97 -72
- moai_adk/templates/.claude/skills/moai-domain-data-science/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-data-science/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-domain-database/SKILL.md +97 -74
- moai_adk/templates/.claude/skills/moai-domain-database/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-database/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-domain-devops/SKILL.md +98 -74
- moai_adk/templates/.claude/skills/moai-domain-devops/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-devops/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +102 -73
- moai_adk/templates/.claude/skills/moai-domain-frontend/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-domain-ml/SKILL.md +97 -73
- moai_adk/templates/.claude/skills/moai-domain-ml/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-ml/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-domain-mobile-app/SKILL.md +97 -67
- moai_adk/templates/.claude/skills/moai-domain-mobile-app/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-mobile-app/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-domain-security/SKILL.md +97 -79
- moai_adk/templates/.claude/skills/moai-domain-security/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-security/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-domain-web-api/SKILL.md +97 -71
- moai_adk/templates/.claude/skills/moai-domain-web-api/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-domain-web-api/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-essentials-debug/SKILL.md +265 -64
- moai_adk/templates/.claude/skills/moai-essentials-debug/examples.md +1064 -0
- moai_adk/templates/.claude/skills/moai-essentials-debug/reference.md +1047 -0
- moai_adk/templates/.claude/skills/moai-essentials-perf/SKILL.md +87 -78
- moai_adk/templates/.claude/skills/moai-essentials-perf/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-essentials-perf/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-essentials-refactor/SKILL.md +87 -70
- moai_adk/templates/.claude/skills/moai-essentials-refactor/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-essentials-refactor/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-essentials-review/SKILL.md +87 -86
- moai_adk/templates/.claude/skills/moai-essentials-review/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-essentials-review/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-foundation-ears/SKILL.md +80 -62
- moai_adk/templates/.claude/skills/moai-foundation-ears/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-foundation-ears/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-foundation-git/SKILL.md +207 -50
- moai_adk/templates/.claude/skills/moai-foundation-git/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-foundation-git/reference.md +29 -0
- moai_adk/templates/.claude/skills/moai-foundation-langs/SKILL.md +90 -71
- moai_adk/templates/.claude/skills/moai-foundation-langs/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-foundation-langs/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-foundation-specs/SKILL.md +78 -58
- moai_adk/templates/.claude/skills/moai-foundation-specs/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-foundation-specs/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-foundation-tags/SKILL.md +78 -51
- moai_adk/templates/.claude/skills/moai-foundation-tags/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-foundation-tags/reference.md +28 -0
- moai_adk/templates/.claude/skills/moai-foundation-trust/.!11330!examples.md +0 -0
- moai_adk/templates/.claude/skills/moai-foundation-trust/SKILL.md +253 -32
- moai_adk/templates/.claude/skills/moai-foundation-trust/examples.md +0 -0
- moai_adk/templates/.claude/skills/moai-foundation-trust/reference.md +1099 -0
- moai_adk/templates/.claude/skills/moai-jit-docs-enhanced/SKILL.md +460 -0
- moai_adk/templates/.claude/skills/moai-lang-c/SKILL.md +98 -74
- moai_adk/templates/.claude/skills/moai-lang-c/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-c/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-cpp/SKILL.md +98 -76
- moai_adk/templates/.claude/skills/moai-lang-cpp/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-cpp/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/SKILL.md +2358 -70
- moai_adk/templates/.claude/skills/moai-lang-csharp/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-lang-dart/SKILL.md +2962 -68
- moai_adk/templates/.claude/skills/moai-lang-dart/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-dart/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +1898 -70
- moai_adk/templates/.claude/skills/moai-lang-go/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-go/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +1465 -68
- moai_adk/templates/.claude/skills/moai-lang-java/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-java/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-javascript/SKILL.md +2364 -66
- moai_adk/templates/.claude/skills/moai-lang-javascript/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-javascript/reference.md +32 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/SKILL.md +1630 -69
- moai_adk/templates/.claude/skills/moai-lang-kotlin/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +89 -61
- moai_adk/templates/.claude/skills/moai-lang-php/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-php/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +735 -66
- moai_adk/templates/.claude/skills/moai-lang-python/examples.md +624 -0
- moai_adk/templates/.claude/skills/moai-lang-python/reference.md +316 -0
- moai_adk/templates/.claude/skills/moai-lang-r/SKILL.md +97 -73
- moai_adk/templates/.claude/skills/moai-lang-r/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-r/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-lang-ruby/SKILL.md +98 -73
- moai_adk/templates/.claude/skills/moai-lang-ruby/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-ruby/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +1834 -70
- moai_adk/templates/.claude/skills/moai-lang-rust/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +99 -74
- moai_adk/templates/.claude/skills/moai-lang-scala/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-lang-shell/SKILL.md +97 -74
- moai_adk/templates/.claude/skills/moai-lang-shell/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-shell/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-lang-sql/SKILL.md +98 -74
- moai_adk/templates/.claude/skills/moai-lang-sql/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-sql/reference.md +31 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/SKILL.md +1959 -69
- moai_adk/templates/.claude/skills/moai-lang-swift/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/reference.md +30 -0
- moai_adk/templates/.claude/skills/moai-lang-template/SKILL.md +348 -0
- moai_adk/templates/.claude/skills/moai-lang-template/VARIABLES.md +98 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +1230 -66
- moai_adk/templates/.claude/skills/moai-lang-typescript/examples.md +29 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/reference.md +34 -0
- moai_adk/templates/.claude/skills/moai-learning-optimizer/SKILL.md +575 -0
- moai_adk/templates/.claude/skills/moai-project-batch-questions/README.md +50 -0
- moai_adk/templates/.claude/skills/moai-project-batch-questions/SKILL.md +304 -0
- moai_adk/templates/.claude/skills/moai-project-batch-questions/examples.md +417 -0
- moai_adk/templates/.claude/skills/moai-project-batch-questions/reference.md +704 -0
- moai_adk/templates/.claude/skills/moai-project-config-manager/README.md +87 -0
- moai_adk/templates/.claude/skills/moai-project-config-manager/SKILL.md +552 -0
- moai_adk/templates/.claude/skills/moai-project-config-manager/examples.md +1109 -0
- moai_adk/templates/.claude/skills/moai-project-config-manager/reference.md +514 -0
- moai_adk/templates/.claude/skills/moai-project-config-manager/validate.py +106 -0
- moai_adk/templates/.claude/skills/moai-project-documentation/README.md +11 -0
- moai_adk/templates/.claude/skills/moai-project-documentation/SKILL.md +622 -0
- moai_adk/templates/.claude/skills/moai-project-documentation/examples.md +20 -0
- moai_adk/templates/.claude/skills/moai-project-documentation/reference.md +12 -0
- moai_adk/templates/.claude/skills/moai-project-language-initializer/README.md +152 -0
- moai_adk/templates/.claude/skills/moai-project-language-initializer/SKILL.md +285 -0
- moai_adk/templates/.claude/skills/moai-project-language-initializer/examples.md +333 -0
- moai_adk/templates/.claude/skills/moai-project-language-initializer/reference.md +386 -0
- moai_adk/templates/.claude/skills/moai-project-template-optimizer/README.md +49 -0
- moai_adk/templates/.claude/skills/moai-project-template-optimizer/SKILL.md +319 -0
- moai_adk/templates/.claude/skills/moai-project-template-optimizer/examples.md +58 -0
- moai_adk/templates/.claude/skills/moai-project-template-optimizer/reference.md +123 -0
- moai_adk/templates/.claude/skills/moai-session-info/SKILL.md +314 -0
- moai_adk/templates/.claude/skills/moai-streaming-ui/SKILL.md +552 -0
- moai_adk/templates/.claude/skills/moai-tag-policy-validator/SKILL.md +570 -0
- moai_adk/templates/.git-hooks/pre-commit +66 -0
- moai_adk/templates/.git-hooks/pre-push +255 -0
- moai_adk/templates/.github/workflows/c-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/cpp-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/csharp-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/dart-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/go-tag-validation.yml +130 -0
- moai_adk/templates/.github/workflows/java-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/javascript-tag-validation.yml +135 -0
- moai_adk/templates/.github/workflows/kotlin-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/moai-gitflow.yml +166 -3
- moai_adk/templates/.github/workflows/moai-release-create.yml +100 -0
- moai_adk/templates/.github/workflows/moai-release-pipeline.yml +188 -0
- moai_adk/templates/.github/workflows/php-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/python-tag-validation.yml +118 -0
- moai_adk/templates/.github/workflows/release.yml +118 -0
- moai_adk/templates/.github/workflows/ruby-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/rust-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/shell-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/spec-issue-sync.yml +338 -0
- moai_adk/templates/.github/workflows/swift-tag-validation.yml +11 -0
- moai_adk/templates/.github/workflows/tag-report.yml +269 -0
- moai_adk/templates/.github/workflows/tag-validation.yml +186 -0
- moai_adk/templates/.github/workflows/typescript-tag-validation.yml +154 -0
- moai_adk/templates/.mcp.json +31 -0
- moai_adk/templates/.moai/config.json +80 -7
- moai_adk/templates/CLAUDE.md +562 -546
- moai_adk/utils/banner.py +5 -5
- moai_adk/utils/common.py +294 -0
- moai_adk/utils/link_validator.py +235 -0
- moai_adk/utils/logger.py +8 -8
- moai_adk/utils/user_experience.py +451 -0
- moai_adk-0.20.1.dist-info/METADATA +233 -0
- moai_adk-0.20.1.dist-info/RECORD +404 -0
- moai_adk/templates/.claude/hooks/alfred/README.md +0 -230
- moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +0 -156
- moai_adk/templates/.claude/hooks/alfred/core/__init__.py +0 -85
- moai_adk/templates/.claude/hooks/alfred/handlers/notification.py +0 -25
- moai_adk/templates/.claude/hooks/alfred/handlers/session.py +0 -92
- moai_adk/templates/.claude/hooks/alfred/handlers/tool.py +0 -70
- moai_adk/templates/.claude/hooks/alfred/handlers/user.py +0 -41
- moai_adk/templates/.claude/output-styles/alfred/agentic-coding.md +0 -636
- moai_adk/templates/.claude/output-styles/alfred/moai-adk-learning.md +0 -692
- moai_adk/templates/.claude/output-styles/alfred/study-with-alfred.md +0 -470
- moai_adk/templates/.claude/skills/moai-alfred-debugger-pro/SKILL.md +0 -103
- moai_adk/templates/.claude/skills/moai-alfred-ears-authoring/SKILL.md +0 -103
- moai_adk/templates/.claude/skills/moai-alfred-git-workflow/SKILL.md +0 -95
- moai_adk/templates/.claude/skills/moai-alfred-performance-optimizer/SKILL.md +0 -105
- moai_adk/templates/.claude/skills/moai-alfred-refactoring-coach/SKILL.md +0 -97
- moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-validation/SKILL.md +0 -97
- moai_adk/templates/.claude/skills/moai-alfred-tag-scanning/SKILL.md +0 -90
- moai_adk/templates/.claude/skills/moai-alfred-trust-validation/SKILL.md +0 -99
- moai_adk/templates/.claude/skills/moai-alfred-tui-survey/SKILL.md +0 -87
- moai_adk/templates/.claude/skills/moai-alfred-tui-survey/examples.md +0 -62
- moai_adk/templates/.claude/skills/moai-claude-code/SKILL.md +0 -94
- moai_adk/templates/.claude/skills/moai-claude-code/examples.md +0 -513
- moai_adk/templates/.claude/skills/moai-claude-code/reference.md +0 -433
- moai_adk/templates/.claude/skills/moai-claude-code/templates/agent-full.md +0 -332
- moai_adk/templates/.claude/skills/moai-claude-code/templates/command-full.md +0 -384
- moai_adk/templates/.claude/skills/moai-claude-code/templates/plugin-full.json +0 -363
- moai_adk/templates/.claude/skills/moai-claude-code/templates/settings-full.json +0 -595
- moai_adk/templates/.claude/skills/moai-claude-code/templates/skill-full.md +0 -496
- moai_adk/templates/.claude/skills/moai-lang-clojure/SKILL.md +0 -100
- moai_adk/templates/.claude/skills/moai-lang-elixir/SKILL.md +0 -99
- moai_adk/templates/.claude/skills/moai-lang-haskell/SKILL.md +0 -100
- moai_adk/templates/.claude/skills/moai-lang-julia/SKILL.md +0 -98
- moai_adk/templates/.claude/skills/moai-lang-lua/SKILL.md +0 -98
- moai_adk/templates/.github/PULL_REQUEST_TEMPLATE.md +0 -69
- moai_adk/templates/.moai/memory/development-guide.md +0 -344
- moai_adk/templates/.moai/memory/gitflow-protection-policy.md +0 -220
- moai_adk/templates/.moai/memory/spec-metadata.md +0 -356
- moai_adk/templates/.moai/project/product.md +0 -161
- moai_adk/templates/.moai/project/structure.md +0 -156
- moai_adk/templates/.moai/project/tech.md +0 -227
- moai_adk/templates/__init__.py +0 -2
- moai_adk-0.4.5.dist-info/METADATA +0 -369
- moai_adk-0.4.5.dist-info/RECORD +0 -152
- {moai_adk-0.4.5.dist-info → moai_adk-0.20.1.dist-info}/WHEEL +0 -0
- {moai_adk-0.4.5.dist-info → moai_adk-0.20.1.dist-info}/entry_points.txt +0 -0
- {moai_adk-0.4.5.dist-info → moai_adk-0.20.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,100 +1,1864 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
2
|
name: moai-lang-rust
|
|
4
|
-
|
|
3
|
+
version: 2.0.0
|
|
4
|
+
created: 2025-11-06
|
|
5
|
+
updated: 2025-11-06
|
|
6
|
+
status: active
|
|
7
|
+
description: "Rust best practices with systems programming, performance-critical applications, and memory-safe patterns for 2025"
|
|
8
|
+
keywords: [rust, systems, performance, memory-safety, concurrency, web-services, embedded, blockchain]
|
|
5
9
|
allowed-tools:
|
|
6
10
|
- Read
|
|
11
|
+
- Write
|
|
12
|
+
- Edit
|
|
7
13
|
- Bash
|
|
14
|
+
- WebFetch
|
|
15
|
+
- WebSearch
|
|
8
16
|
---
|
|
9
17
|
|
|
10
|
-
# Rust
|
|
18
|
+
# Rust Development Mastery
|
|
19
|
+
|
|
20
|
+
**Modern Rust Development with 2025 Best Practices**
|
|
21
|
+
|
|
22
|
+
> Comprehensive Rust development guidance covering systems programming, high-performance applications, web services, embedded development, and production-safe code using the latest tools and methodologies.
|
|
23
|
+
|
|
24
|
+
## What It Does
|
|
25
|
+
|
|
26
|
+
- **Systems Programming**: OS-level utilities, embedded systems, low-level optimizations
|
|
27
|
+
- **High-Performance Web Services**: Actix-web, Axum, and async frameworks
|
|
28
|
+
- **Memory-Safe Concurrency**: Fearless concurrency with ownership and borrowing
|
|
29
|
+
- **CLI Applications**: Fast, safe command-line tools and utilities
|
|
30
|
+
- **Blockchain & Crypto**: Smart contracts, DeFi protocols, cryptographic applications
|
|
31
|
+
- **Game Development**: Game engines, real-time systems, graphics programming
|
|
32
|
+
- **DevOps & Infrastructure**: Monitoring tools, containerized applications, cloud services
|
|
11
33
|
|
|
12
|
-
##
|
|
13
|
-
| Field | Value |
|
|
14
|
-
| ----- | ----- |
|
|
15
|
-
| Allowed tools | Read (read_file), Bash (terminal) |
|
|
16
|
-
| Auto-load | On demand when language keywords are detected |
|
|
17
|
-
| Trigger cues | Rust code discussions, framework guidance, or file extensions such as .rs. |
|
|
18
|
-
| Tier | 3 |
|
|
34
|
+
## When to Use
|
|
19
35
|
|
|
20
|
-
|
|
36
|
+
### Perfect Scenarios
|
|
37
|
+
- **Performance-critical applications requiring zero-cost abstractions**
|
|
38
|
+
- **Systems programming and embedded development**
|
|
39
|
+
- **Web services requiring maximum throughput and security**
|
|
40
|
+
- **CLI tools and developer utilities**
|
|
41
|
+
- **Blockchain and cryptocurrency applications**
|
|
42
|
+
- **Game engines and real-time systems**
|
|
43
|
+
- **Applications requiring memory safety without garbage collection**
|
|
21
44
|
|
|
22
|
-
|
|
45
|
+
### Common Triggers
|
|
46
|
+
- "Create Rust web service"
|
|
47
|
+
- "Build Rust CLI tool"
|
|
48
|
+
- "Rust performance optimization"
|
|
49
|
+
- "Rust memory safety"
|
|
50
|
+
- "Systems programming with Rust"
|
|
51
|
+
- "Async Rust patterns"
|
|
23
52
|
|
|
24
|
-
##
|
|
53
|
+
## Tool Version Matrix (2025-11-06)
|
|
25
54
|
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
- Rust
|
|
55
|
+
### Core Rust
|
|
56
|
+
- **Rust**: 1.91.x (latest) / 1.89.x (LTS)
|
|
57
|
+
- **Cargo**: Built-in package manager and build tool
|
|
58
|
+
- **Rustup**: Rust version manager
|
|
59
|
+
- **Rust-analyzer**: Advanced LSP support
|
|
30
60
|
|
|
31
|
-
|
|
61
|
+
### Web Frameworks
|
|
62
|
+
- **Actix-web**: 4.9.x - High-performance web framework
|
|
63
|
+
- **Axum**: 0.8.x - Web framework from tokio team
|
|
64
|
+
- **Rocket**: 0.6.x - Web framework with macros
|
|
65
|
+
- **Poem**: 3.1.x - Modular web framework
|
|
66
|
+
- **Warp**: 0.4.x - Composable web server
|
|
32
67
|
|
|
33
|
-
|
|
34
|
-
- **
|
|
35
|
-
- **
|
|
36
|
-
- **
|
|
37
|
-
- Test coverage with `cargo tarpaulin` or `cargo llvm-cov`
|
|
68
|
+
### Async Runtime
|
|
69
|
+
- **Tokio**: 1.42.x - Asynchronous runtime
|
|
70
|
+
- **Async-std**: 1.13.x - Async standard library
|
|
71
|
+
- **Smol**: 2.0.x - Compact async runtime
|
|
38
72
|
|
|
39
|
-
|
|
40
|
-
- **
|
|
41
|
-
- **
|
|
42
|
-
- **
|
|
43
|
-
- **
|
|
73
|
+
### Database & Storage
|
|
74
|
+
- **SQLx**: 0.8.x - Async SQL toolkit
|
|
75
|
+
- **Diesel**: 2.2.x - ORM and query builder
|
|
76
|
+
- **Serde**: 1.0.x - Serialization framework
|
|
77
|
+
- **Redis**: 0.26.x - Redis client
|
|
44
78
|
|
|
45
|
-
|
|
46
|
-
- **
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
49
|
-
- **
|
|
79
|
+
### Testing Tools
|
|
80
|
+
- **Tokio-test**: 0.4.x - Async testing utilities
|
|
81
|
+
- **Mockall**: 0.13.x - Mocking framework
|
|
82
|
+
- **Cucumber-rs**: 0.22.x - BDD testing
|
|
83
|
+
- **Proptest**: 1.5.x - Property-based testing
|
|
50
84
|
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
85
|
+
### Development Tools
|
|
86
|
+
- **Clippy**: Built-in linter
|
|
87
|
+
- **rustfmt**: Built-in formatter
|
|
88
|
+
- **cargo-watch**: 8.5.x - File watcher
|
|
89
|
+
- **cargo-audit**: 0.21.x - Security audit
|
|
56
90
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- Use iterators over manual loops
|
|
61
|
-
- Avoid `unwrap()` in production code, use proper error handling
|
|
91
|
+
## Ecosystem Overview
|
|
92
|
+
|
|
93
|
+
### Project Setup (2025 Best Practice)
|
|
62
94
|
|
|
63
|
-
## Examples
|
|
64
95
|
```bash
|
|
65
|
-
|
|
96
|
+
# Modern Rust project with workspace structure
|
|
97
|
+
cargo new my-rust-project --bin
|
|
98
|
+
cd my-rust-project
|
|
99
|
+
|
|
100
|
+
# Create workspace structure
|
|
101
|
+
mkdir -p crates/{common,server,client}
|
|
102
|
+
mkdir -p src/{bin,lib}
|
|
103
|
+
mkdir -p tests/{integration,unit}
|
|
104
|
+
mkdir -p benches
|
|
105
|
+
mkdir -p examples
|
|
106
|
+
|
|
107
|
+
# Initialize workspace
|
|
108
|
+
echo '[workspace]
|
|
109
|
+
members = [
|
|
110
|
+
"crates/common",
|
|
111
|
+
"crates/server",
|
|
112
|
+
"crates/client",
|
|
113
|
+
]' > Cargo.toml
|
|
114
|
+
|
|
115
|
+
# Add workspace crates
|
|
116
|
+
cargo new --lib crates/common
|
|
117
|
+
cargo new --lib crates/server
|
|
118
|
+
cargo new --lib crates/client
|
|
119
|
+
|
|
120
|
+
# Install development tools
|
|
121
|
+
cargo install cargo-watch
|
|
122
|
+
cargo install cargo-audit
|
|
123
|
+
cargo install cargo-expand
|
|
124
|
+
cargo install cargo-flamegraph
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Modern Project Structure
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
my-rust-project/
|
|
131
|
+
├── Cargo.toml # Workspace configuration
|
|
132
|
+
├── Cargo.lock # Lock file
|
|
133
|
+
├── rust-toolchain.toml # Toolchain specification
|
|
134
|
+
├── .clippy.toml # Clippy configuration
|
|
135
|
+
├── .rustfmt.toml # Rustfmt configuration
|
|
136
|
+
├── deny.toml # Dependency deny list
|
|
137
|
+
├── crates/
|
|
138
|
+
│ ├── common/ # Shared utilities
|
|
139
|
+
│ │ ├── Cargo.toml
|
|
140
|
+
│ │ └── src/
|
|
141
|
+
│ ├── server/ # Server application
|
|
142
|
+
│ │ ├── Cargo.toml
|
|
143
|
+
│ │ └── src/
|
|
144
|
+
│ └── client/ # Client library
|
|
145
|
+
│ ├── Cargo.toml
|
|
146
|
+
│ └── src/
|
|
147
|
+
├── src/
|
|
148
|
+
│ ├── bin/ # Binary entry points
|
|
149
|
+
│ └── lib/ # Main library code
|
|
150
|
+
├── tests/
|
|
151
|
+
│ ├── integration/ # Integration tests
|
|
152
|
+
│ └── unit/ # Unit tests
|
|
153
|
+
├── benches/ # Performance benchmarks
|
|
154
|
+
├── examples/ # Example usage
|
|
155
|
+
├── docs/ # Documentation
|
|
156
|
+
└── scripts/ # Build and utility scripts
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Modern Rust Patterns
|
|
160
|
+
|
|
161
|
+
### Error Handling with `thiserror` and `anyhow`
|
|
162
|
+
|
|
163
|
+
```rust
|
|
164
|
+
// src/error.rs
|
|
165
|
+
use thiserror::Error;
|
|
166
|
+
|
|
167
|
+
#[derive(Error, Debug)]
|
|
168
|
+
pub enum AppError {
|
|
169
|
+
#[error("Database error: {0}")]
|
|
170
|
+
Database(#[from] sqlx::Error),
|
|
171
|
+
|
|
172
|
+
#[error("Validation error: {message}")]
|
|
173
|
+
Validation { message: String },
|
|
174
|
+
|
|
175
|
+
#[error("Not found: {entity} with id {id}")]
|
|
176
|
+
NotFound { entity: String, id: String },
|
|
177
|
+
|
|
178
|
+
#[error("Authentication failed")]
|
|
179
|
+
Authentication,
|
|
180
|
+
|
|
181
|
+
#[error("Authorization denied for {action}")]
|
|
182
|
+
Authorization { action: String },
|
|
183
|
+
|
|
184
|
+
#[error("Internal server error: {0}")]
|
|
185
|
+
Internal(#[from] anyhow::Error),
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
pub type Result<T> = std::result::Result<T, AppError>;
|
|
189
|
+
|
|
190
|
+
// Usage in application code
|
|
191
|
+
use anyhow::Context;
|
|
192
|
+
|
|
193
|
+
pub async fn get_user(id: &str) -> Result<User> {
|
|
194
|
+
let user = sqlx::query_as!(
|
|
195
|
+
User,
|
|
196
|
+
"SELECT * FROM users WHERE id = ?",
|
|
197
|
+
id
|
|
198
|
+
)
|
|
199
|
+
.fetch_one(&pool)
|
|
200
|
+
.await
|
|
201
|
+
.with_context(|| format!("Failed to fetch user with id: {}", id))?;
|
|
202
|
+
|
|
203
|
+
Ok(user)
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Async Programming with Tokio
|
|
208
|
+
|
|
209
|
+
```rust
|
|
210
|
+
// src/services/user_service.rs
|
|
211
|
+
use tokio::sync::{RwLock, broadcast};
|
|
212
|
+
use std::sync::Arc;
|
|
213
|
+
use uuid::Uuid;
|
|
214
|
+
|
|
215
|
+
pub struct UserService {
|
|
216
|
+
users: Arc<RwLock<std::collections::HashMap<Uuid, User>>>,
|
|
217
|
+
event_tx: broadcast::Sender<UserEvent>,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
impl UserService {
|
|
221
|
+
pub fn new() -> Self {
|
|
222
|
+
let (event_tx, _) = broadcast::channel(1000);
|
|
223
|
+
Self {
|
|
224
|
+
users: Arc::new(RwLock::new(std::collections::HashMap::new())),
|
|
225
|
+
event_tx,
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
pub async fn create_user(&self, user_data: CreateUserRequest) -> Result<User> {
|
|
230
|
+
let user_id = Uuid::new_v4();
|
|
231
|
+
let user = User {
|
|
232
|
+
id: user_id,
|
|
233
|
+
email: user_data.email.clone(),
|
|
234
|
+
name: user_data.name,
|
|
235
|
+
created_at: chrono::Utc::now(),
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Insert into user store
|
|
239
|
+
{
|
|
240
|
+
let mut users = self.users.write().await;
|
|
241
|
+
users.insert(user_id, user.clone());
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Emit event
|
|
245
|
+
let _ = self.event_tx.send(UserEvent::Created(user.clone()));
|
|
246
|
+
|
|
247
|
+
Ok(user)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
pub async fn get_user(&self, user_id: Uuid) -> Option<User> {
|
|
251
|
+
let users = self.users.read().await;
|
|
252
|
+
users.get(&user_id).cloned()
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
pub async fn list_users(&self) -> Vec<User> {
|
|
256
|
+
let users = self.users.read().await;
|
|
257
|
+
users.values().cloned().collect()
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
pub fn subscribe_events(&self) -> broadcast::Receiver<UserEvent> {
|
|
261
|
+
self.event_tx.subscribe()
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
#[derive(Debug, Clone)]
|
|
266
|
+
pub enum UserEvent {
|
|
267
|
+
Created(User),
|
|
268
|
+
Updated(User),
|
|
269
|
+
Deleted { id: Uuid },
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Concurrent processing example
|
|
273
|
+
pub async fn process_users_concurrently(
|
|
274
|
+
user_service: Arc<UserService>,
|
|
275
|
+
users: Vec<User>,
|
|
276
|
+
) -> Vec<Result<ProcessedUser>> {
|
|
277
|
+
let semaphore = Arc::new(tokio::sync::Semaphore::new(10)); // Limit concurrency
|
|
278
|
+
|
|
279
|
+
let tasks: Vec<_> = users
|
|
280
|
+
.into_iter()
|
|
281
|
+
.map(|user| {
|
|
282
|
+
let user_service = user_service.clone();
|
|
283
|
+
let semaphore = semaphore.clone();
|
|
284
|
+
|
|
285
|
+
tokio::spawn(async move {
|
|
286
|
+
let _permit = semaphore.acquire().await.unwrap();
|
|
287
|
+
process_single_user(&user_service, user).await
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
.collect();
|
|
291
|
+
|
|
292
|
+
let results = futures::future::join_all(tasks).await;
|
|
293
|
+
results.into_iter().map(|r| r.unwrap()).collect()
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Web Service with Axum
|
|
298
|
+
|
|
299
|
+
```rust
|
|
300
|
+
// src/main.rs
|
|
301
|
+
use axum::{
|
|
302
|
+
extract::{Path, State},
|
|
303
|
+
http::StatusCode,
|
|
304
|
+
response::IntoResponse,
|
|
305
|
+
routing::{get, post},
|
|
306
|
+
Json, Router,
|
|
307
|
+
};
|
|
308
|
+
use serde::{Deserialize, Serialize};
|
|
309
|
+
use std::net::SocketAddr;
|
|
310
|
+
use tower::ServiceBuilder;
|
|
311
|
+
use tower_http::{
|
|
312
|
+
cors::{Any, CorsLayer},
|
|
313
|
+
trace::TraceLayer,
|
|
314
|
+
};
|
|
315
|
+
use tracing::{info, Level};
|
|
316
|
+
use tracing_subscriber;
|
|
317
|
+
|
|
318
|
+
mod error;
|
|
319
|
+
mod handlers;
|
|
320
|
+
mod models;
|
|
321
|
+
mod services;
|
|
322
|
+
mod middleware;
|
|
323
|
+
|
|
324
|
+
use error::AppError;
|
|
325
|
+
use handlers::{create_user, get_user, health_check};
|
|
326
|
+
use services::UserService;
|
|
327
|
+
|
|
328
|
+
#[derive(Clone)]
|
|
329
|
+
pub struct AppState {
|
|
330
|
+
pub user_service: Arc<UserService>,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
#[tokio::main]
|
|
334
|
+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
335
|
+
// Initialize tracing
|
|
336
|
+
tracing_subscriber::fmt()
|
|
337
|
+
.with_max_level(Level::INFO)
|
|
338
|
+
.init();
|
|
339
|
+
|
|
340
|
+
// Initialize services
|
|
341
|
+
let user_service = Arc::new(UserService::new());
|
|
342
|
+
let app_state = AppState { user_service };
|
|
343
|
+
|
|
344
|
+
// Build application
|
|
345
|
+
let app = Router::new()
|
|
346
|
+
// Routes
|
|
347
|
+
.route("/health", get(health_check))
|
|
348
|
+
.route("/users", post(create_user))
|
|
349
|
+
.route("/users/:id", get(get_user))
|
|
350
|
+
// Middleware
|
|
351
|
+
.layer(
|
|
352
|
+
ServiceBuilder::new()
|
|
353
|
+
.layer(TraceLayer::new_for_http())
|
|
354
|
+
.layer(CorsLayer::new().allow_origin(Any))
|
|
355
|
+
.layer(axum::middleware::from_fn(middleware::request_logging)),
|
|
356
|
+
)
|
|
357
|
+
.with_state(app_state);
|
|
358
|
+
|
|
359
|
+
// Run server
|
|
360
|
+
let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
|
|
361
|
+
info!("Server listening on {}", addr);
|
|
362
|
+
|
|
363
|
+
axum::Server::bind(&addr)
|
|
364
|
+
.serve(app.into_make_service())
|
|
365
|
+
.await?;
|
|
366
|
+
|
|
367
|
+
Ok(())
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/handlers/user.rs
|
|
371
|
+
use axum::{extract::{Path, State}, Json};
|
|
372
|
+
use uuid::Uuid;
|
|
373
|
+
use crate::{error::AppError, models::User, services::UserService, AppState};
|
|
374
|
+
|
|
375
|
+
pub async fn create_user(
|
|
376
|
+
State(state): State<AppState>,
|
|
377
|
+
Json(payload): Json<CreateUserRequest>,
|
|
378
|
+
) -> Result<Json<User>, AppError> {
|
|
379
|
+
let user = state.user_service.create_user(payload).await?;
|
|
380
|
+
Ok(Json(user))
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
pub async fn get_user(
|
|
384
|
+
State(state): State<AppState>,
|
|
385
|
+
Path(user_id): Path<Uuid>,
|
|
386
|
+
) -> Result<Json<User>, AppError> {
|
|
387
|
+
let user = state
|
|
388
|
+
.user_service
|
|
389
|
+
.get_user(user_id)
|
|
390
|
+
.await
|
|
391
|
+
.ok_or(AppError::NotFound {
|
|
392
|
+
entity: "User".to_string(),
|
|
393
|
+
id: user_id.to_string(),
|
|
394
|
+
})?;
|
|
395
|
+
|
|
396
|
+
Ok(Json(user))
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
pub async fn health_check() -> impl IntoResponse {
|
|
400
|
+
Json(serde_json::json!({
|
|
401
|
+
"status": "healthy",
|
|
402
|
+
"timestamp": chrono::Utc::now(),
|
|
403
|
+
}))
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Performance Optimization
|
|
408
|
+
|
|
409
|
+
### Memory Management and Zero-Copy Patterns
|
|
410
|
+
|
|
411
|
+
```rust
|
|
412
|
+
// src/processing/data_processor.rs
|
|
413
|
+
use bytes::{Bytes, BytesMut, BufMut};
|
|
414
|
+
use std::io::{self, Read};
|
|
415
|
+
|
|
416
|
+
pub struct DataProcessor {
|
|
417
|
+
buffer: BytesMut,
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
impl DataProcessor {
|
|
421
|
+
pub fn new() -> Self {
|
|
422
|
+
Self {
|
|
423
|
+
buffer: BytesMut::with_capacity(8192),
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Zero-copy data processing
|
|
428
|
+
pub fn process_stream<R: Read>(&mut self, mut reader: R) -> io::Result<ProcessedData> {
|
|
429
|
+
self.buffer.clear();
|
|
430
|
+
|
|
431
|
+
// Read data into buffer
|
|
432
|
+
let bytes_read = reader.read(&mut self.buffer)?;
|
|
433
|
+
if bytes_read == 0 {
|
|
434
|
+
return Ok(ProcessedData::empty());
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Process without copying
|
|
438
|
+
let data = self.buffer.split().freeze();
|
|
439
|
+
self.process_bytes(data)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
fn process_bytes(&self, data: Bytes) -> io::Result<ProcessedData> {
|
|
443
|
+
let mut records = Vec::new();
|
|
444
|
+
|
|
445
|
+
// Process data without allocations
|
|
446
|
+
for line in data.split(|&b| b == b'\n') {
|
|
447
|
+
if line.is_empty() {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
let record = self.parse_record(line)?;
|
|
452
|
+
records.push(record);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
Ok(ProcessedData { records })
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
fn parse_record(&self, line: &[u8]) -> io::Result<Record> {
|
|
459
|
+
// Parse using slices instead of String allocations
|
|
460
|
+
let mut parts = line.split(|&b| b == b',');
|
|
461
|
+
|
|
462
|
+
let id_parts = parts.next().ok_or(io::ErrorKind::InvalidData)?;
|
|
463
|
+
let name_parts = parts.next().ok_or(io::ErrorKind::InvalidData)?;
|
|
464
|
+
|
|
465
|
+
Ok(Record {
|
|
466
|
+
id: std::str::from_utf8(id_parts)
|
|
467
|
+
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8"))?
|
|
468
|
+
.to_string(),
|
|
469
|
+
name: std::str::from_utf8(name_parts)
|
|
470
|
+
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8"))?
|
|
471
|
+
.to_string(),
|
|
472
|
+
})
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// SIMD-optimized processing
|
|
477
|
+
#[cfg(target_arch = "x86_64")]
|
|
478
|
+
use std::arch::x86_64::*;
|
|
479
|
+
|
|
480
|
+
pub fn fast_checksum(data: &[u8]) -> u64 {
|
|
481
|
+
#[target_feature(enable = "avx2")]
|
|
482
|
+
unsafe {
|
|
483
|
+
if is_x86_feature_detected!("avx2") {
|
|
484
|
+
checksum_avx2(data)
|
|
485
|
+
} else {
|
|
486
|
+
checksum_scalar(data)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
#[target_feature(enable = "avx2")]
|
|
492
|
+
unsafe fn checksum_avx2(data: &[u8]) -> u64 {
|
|
493
|
+
let chunks = data.chunks_exact(32);
|
|
494
|
+
let remainder = chunks.remainder();
|
|
495
|
+
|
|
496
|
+
let mut sum = _mm256_setzero_si256();
|
|
497
|
+
|
|
498
|
+
for chunk in chunks {
|
|
499
|
+
let chunk = _mm256_loadu_si256(chunk.as_ptr() as *const __m256i);
|
|
500
|
+
sum = _mm256_add_epi64(sum, _mm256_sad_epu8(chunk, _mm256_setzero_si256()));
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
let mut result = _mm256_extract_epi128(sum, 1) as u64 + _mm256_extract_epi128(sum, 0) as u64;
|
|
504
|
+
|
|
505
|
+
// Process remainder
|
|
506
|
+
for &byte in remainder {
|
|
507
|
+
result += byte as u64;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
result
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
fn checksum_scalar(data: &[u8]) -> u64 {
|
|
514
|
+
data.iter().map(|&b| b as u64).sum()
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Efficient Data Structures
|
|
519
|
+
|
|
520
|
+
```rust
|
|
521
|
+
// src/datastructures/high_performance_map.rs
|
|
522
|
+
use std::collections::HashMap;
|
|
523
|
+
use std::hash::{Hash, Hasher};
|
|
524
|
+
|
|
525
|
+
// Custom hash map optimized for string keys
|
|
526
|
+
pub struct StringKeyMap<V> {
|
|
527
|
+
inner: HashMap<Box<str>, V>,
|
|
528
|
+
key_pool: Vec<Box<str>>,
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
impl<V> StringKeyMap<V> {
|
|
532
|
+
pub fn new() -> Self {
|
|
533
|
+
Self {
|
|
534
|
+
inner: HashMap::new(),
|
|
535
|
+
key_pool: Vec::new(),
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
pub fn insert(&mut self, key: &str, value: V) -> Option<V> {
|
|
540
|
+
// Intern the key to avoid allocations for duplicate keys
|
|
541
|
+
let interned_key = self.intern_key(key);
|
|
542
|
+
self.inner.insert(interned_key, value)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
pub fn get(&self, key: &str) -> Option<&V> {
|
|
546
|
+
self.inner.get(key)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
pub fn get_mut(&mut self, key: &str) -> Option<&mut V> {
|
|
550
|
+
self.inner.get_mut(key)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
pub fn remove(&mut self, key: &str) -> Option<V> {
|
|
554
|
+
self.inner.remove(key)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
fn intern_key(&mut self, key: &str) -> Box<str> {
|
|
558
|
+
// Simple string interning - in production, use a proper interner
|
|
559
|
+
for interned in &self.key_pool {
|
|
560
|
+
if interned.as_ref() == key {
|
|
561
|
+
return interned.clone();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
let interned: Box<str> = key.into();
|
|
566
|
+
self.key_pool.push(interned.clone());
|
|
567
|
+
interned
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// High-performance ring buffer
|
|
572
|
+
pub struct RingBuffer<T> {
|
|
573
|
+
buffer: Vec<Option<T>>,
|
|
574
|
+
head: usize,
|
|
575
|
+
tail: usize,
|
|
576
|
+
size: usize,
|
|
577
|
+
capacity: usize,
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
impl<T> RingBuffer<T> {
|
|
581
|
+
pub fn new(capacity: usize) -> Self {
|
|
582
|
+
Self {
|
|
583
|
+
buffer: vec![None; capacity],
|
|
584
|
+
head: 0,
|
|
585
|
+
tail: 0,
|
|
586
|
+
size: 0,
|
|
587
|
+
capacity,
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
pub fn push(&mut self, item: T) -> bool {
|
|
592
|
+
if self.size >= self.capacity {
|
|
593
|
+
return false; // Buffer full
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
self.buffer[self.tail] = Some(item);
|
|
597
|
+
self.tail = (self.tail + 1) % self.capacity;
|
|
598
|
+
self.size += 1;
|
|
599
|
+
true
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
pub fn pop(&mut self) -> Option<T> {
|
|
603
|
+
if self.size == 0 {
|
|
604
|
+
return None;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
let item = self.buffer[self.head].take();
|
|
608
|
+
self.head = (self.head + 1) % self.capacity;
|
|
609
|
+
self.size -= 1;
|
|
610
|
+
item
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
pub fn is_empty(&self) -> bool {
|
|
614
|
+
self.size == 0
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
pub fn is_full(&self) -> bool {
|
|
618
|
+
self.size == self.capacity
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
pub fn len(&self) -> usize {
|
|
622
|
+
self.size
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
pub fn capacity(&self) -> usize {
|
|
626
|
+
self.capacity
|
|
627
|
+
}
|
|
628
|
+
}
|
|
66
629
|
```
|
|
67
630
|
|
|
68
|
-
##
|
|
69
|
-
- Language-specific source directories (e.g. `src/`, `app/`).
|
|
70
|
-
- Language-specific build/test configuration files (e.g. `package.json`, `pyproject.toml`, `go.mod`).
|
|
71
|
-
- Relevant test suites and sample data.
|
|
631
|
+
## Testing Strategies
|
|
72
632
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
633
|
+
### Comprehensive Unit Testing
|
|
634
|
+
|
|
635
|
+
```rust
|
|
636
|
+
// tests/unit/user_service_test.rs
|
|
637
|
+
use tokio_test;
|
|
638
|
+
use uuid::Uuid;
|
|
639
|
+
use crate::services::{UserService, CreateUserRequest};
|
|
640
|
+
use crate::models::User;
|
|
641
|
+
|
|
642
|
+
#[tokio::test]
|
|
643
|
+
async fn test_create_user() {
|
|
644
|
+
let service = UserService::new();
|
|
645
|
+
let request = CreateUserRequest {
|
|
646
|
+
email: "test@example.com".to_string(),
|
|
647
|
+
name: "Test User".to_string(),
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
let user = service.create_user(request).await.unwrap();
|
|
651
|
+
|
|
652
|
+
assert!(!user.id.to_string().is_empty());
|
|
653
|
+
assert_eq!(user.email, "test@example.com");
|
|
654
|
+
assert_eq!(user.name, "Test User");
|
|
655
|
+
assert!(user.created_at <= chrono::Utc::now());
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
#[tokio::test]
|
|
659
|
+
async fn test_get_user() {
|
|
660
|
+
let service = UserService::new();
|
|
661
|
+
let request = CreateUserRequest {
|
|
662
|
+
email: "test@example.com".to_string(),
|
|
663
|
+
name: "Test User".to_string(),
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
let created_user = service.create_user(request).await.unwrap();
|
|
667
|
+
let retrieved_user = service.get_user(created_user.id).await.unwrap();
|
|
668
|
+
|
|
669
|
+
assert_eq!(created_user.id, retrieved_user.id);
|
|
670
|
+
assert_eq!(created_user.email, retrieved_user.email);
|
|
671
|
+
assert_eq!(created_user.name, retrieved_user.name);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
#[tokio::test]
|
|
675
|
+
async fn test_get_nonexistent_user() {
|
|
676
|
+
let service = UserService::new();
|
|
677
|
+
let nonexistent_id = Uuid::new_v4();
|
|
678
|
+
|
|
679
|
+
let user = service.get_user(nonexistent_id).await;
|
|
680
|
+
assert!(user.is_none());
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
#[tokio::test]
|
|
684
|
+
async fn test_create_user_duplicate_email() {
|
|
685
|
+
let service = UserService::new();
|
|
686
|
+
let request = CreateUserRequest {
|
|
687
|
+
email: "duplicate@example.com".to_string(),
|
|
688
|
+
name: "User 1".to_string(),
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
// Create first user
|
|
692
|
+
service.create_user(request.clone()).await.unwrap();
|
|
693
|
+
|
|
694
|
+
// Try to create second user with same email
|
|
695
|
+
let result = service.create_user(request).await;
|
|
696
|
+
assert!(result.is_err());
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
#[tokio::test]
|
|
700
|
+
async fn test_user_events() {
|
|
701
|
+
let service = UserService::new();
|
|
702
|
+
let mut event_rx = service.subscribe_events();
|
|
703
|
+
|
|
704
|
+
let request = CreateUserRequest {
|
|
705
|
+
email: "events@example.com".to_string(),
|
|
706
|
+
name: "Event User".to_string(),
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
let created_user = service.create_user(request).await.unwrap();
|
|
710
|
+
|
|
711
|
+
// Should receive UserCreated event
|
|
712
|
+
let event = event_rx.recv().await.unwrap();
|
|
713
|
+
match event {
|
|
714
|
+
UserEvent::Created(user) => {
|
|
715
|
+
assert_eq!(user.id, created_user.id);
|
|
716
|
+
assert_eq!(user.email, created_user.email);
|
|
717
|
+
}
|
|
718
|
+
_ => panic!("Expected UserCreated event"),
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Property-based testing with proptest
|
|
723
|
+
use proptest::prelude::*;
|
|
724
|
+
|
|
725
|
+
proptest! {
|
|
726
|
+
#[test]
|
|
727
|
+
fn test_user_email_validation(email in "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}") {
|
|
728
|
+
prop_assert!(is_valid_email(&email));
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
#[test]
|
|
732
|
+
fn test_ring_buffer(
|
|
733
|
+
items in prop::collection::vec(any::<i32>(), 1..100),
|
|
734
|
+
capacity in 1usize..=100
|
|
735
|
+
) {
|
|
736
|
+
let mut buffer = RingBuffer::new(capacity);
|
|
737
|
+
|
|
738
|
+
for &item in &items {
|
|
739
|
+
if !buffer.push(item) {
|
|
740
|
+
break; // Buffer full
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
let popped_count = std::cmp::min(items.len(), capacity);
|
|
745
|
+
for (i, &expected_item) in items.iter().take(popped_count).enumerate() {
|
|
746
|
+
let popped = buffer.pop().unwrap();
|
|
747
|
+
prop_assert_eq!(popped, expected_item);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
prop_assert!(buffer.is_empty());
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
fn is_valid_email(email: &str) -> bool {
|
|
755
|
+
email.contains('@') && email.contains('.')
|
|
756
|
+
}
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
### Integration Testing with TestContainers
|
|
760
|
+
|
|
761
|
+
```rust
|
|
762
|
+
// tests/integration/api_test.rs
|
|
763
|
+
use axum_test::TestServer;
|
|
764
|
+
use serde_json::json;
|
|
765
|
+
use sqlx::PgPool;
|
|
766
|
+
use std::sync::Arc;
|
|
767
|
+
use tempfile::TempDir;
|
|
768
|
+
use tokio::time::{timeout, Duration};
|
|
769
|
+
|
|
770
|
+
use my_app::{create_app, AppState};
|
|
771
|
+
use my_app::services::{UserService, DatabaseUserService};
|
|
772
|
+
|
|
773
|
+
struct TestApp {
|
|
774
|
+
server: TestServer,
|
|
775
|
+
pool: PgPool,
|
|
776
|
+
_temp_dir: TempDir,
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async fn setup_test_app() -> TestApp {
|
|
780
|
+
// Create temporary directory for test database
|
|
781
|
+
let temp_dir = TempDir::new().unwrap();
|
|
782
|
+
|
|
783
|
+
// Setup test database
|
|
784
|
+
let database_url = format!("sqlite:{}", temp_dir.path().join("test.db").display());
|
|
785
|
+
let pool = sqlx::SqlitePool::connect(&database_url).await.unwrap();
|
|
786
|
+
|
|
787
|
+
// Run migrations
|
|
788
|
+
sqlx::migrate!("./migrations").run(&pool).await.unwrap();
|
|
789
|
+
|
|
790
|
+
// Create services
|
|
791
|
+
let db_service = DatabaseUserService::new(pool.clone());
|
|
792
|
+
let user_service = Arc::new(UserService::new(Box::new(db_service)));
|
|
793
|
+
|
|
794
|
+
let app_state = AppState { user_service };
|
|
795
|
+
let app = create_app(app_state);
|
|
796
|
+
|
|
797
|
+
let server = TestServer::new(app).unwrap();
|
|
798
|
+
|
|
799
|
+
TestApp {
|
|
800
|
+
server,
|
|
801
|
+
pool,
|
|
802
|
+
_temp_dir: temp_dir,
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
#[tokio::test]
|
|
807
|
+
async fn test_user_lifecycle() {
|
|
808
|
+
let test_app = setup_test_app().await;
|
|
809
|
+
|
|
810
|
+
// Create user
|
|
811
|
+
let create_response = test_app
|
|
812
|
+
.server
|
|
813
|
+
.post("/users")
|
|
814
|
+
.json(&json!({
|
|
815
|
+
"email": "integration@example.com",
|
|
816
|
+
"name": "Integration User"
|
|
817
|
+
}))
|
|
818
|
+
.await;
|
|
819
|
+
|
|
820
|
+
create_response.assert_status_ok();
|
|
821
|
+
|
|
822
|
+
let user: User = create_response.json();
|
|
823
|
+
assert_eq!(user.email, "integration@example.com");
|
|
824
|
+
assert_eq!(user.name, "Integration User");
|
|
825
|
+
|
|
826
|
+
// Get user
|
|
827
|
+
let get_response = test_app
|
|
828
|
+
.server
|
|
829
|
+
.get(&format!("/users/{}", user.id))
|
|
830
|
+
.await;
|
|
831
|
+
|
|
832
|
+
get_response.assert_status_ok();
|
|
833
|
+
|
|
834
|
+
let retrieved_user: User = get_response.json();
|
|
835
|
+
assert_eq!(retrieved_user.id, user.id);
|
|
836
|
+
assert_eq!(retrieved_user.email, user.email);
|
|
837
|
+
|
|
838
|
+
// List users
|
|
839
|
+
let list_response = test_app.server.get("/users").await;
|
|
840
|
+
list_response.assert_status_ok();
|
|
841
|
+
|
|
842
|
+
let users: Vec<User> = list_response.json();
|
|
843
|
+
assert_eq!(users.len(), 1);
|
|
844
|
+
assert_eq!(users[0].id, user.id);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
#[tokio::test]
|
|
848
|
+
async fn test_concurrent_user_creation() {
|
|
849
|
+
let test_app = setup_test_app().await;
|
|
850
|
+
|
|
851
|
+
let mut handles = vec![];
|
|
852
|
+
|
|
853
|
+
for i in 0..10 {
|
|
854
|
+
let server = test_app.server.clone();
|
|
855
|
+
let handle = tokio::spawn(async move {
|
|
856
|
+
server
|
|
857
|
+
.post("/users")
|
|
858
|
+
.json(&json!({
|
|
859
|
+
"email": format!("user{}@example.com", i),
|
|
860
|
+
"name": format!("User {}", i)
|
|
861
|
+
}))
|
|
862
|
+
.await
|
|
863
|
+
});
|
|
864
|
+
handles.push(handle);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
let responses = futures::future::join_all(handles).await;
|
|
868
|
+
|
|
869
|
+
for response in responses {
|
|
870
|
+
let response = response.unwrap();
|
|
871
|
+
response.assert_status_ok();
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Verify all users were created
|
|
875
|
+
let list_response = test_app.server.get("/users").await;
|
|
876
|
+
list_response.assert_status_ok();
|
|
877
|
+
|
|
878
|
+
let users: Vec<User> = list_response.json();
|
|
879
|
+
assert_eq!(users.len(), 10);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
#[tokio::test]
|
|
883
|
+
async fn test_request_timeout() {
|
|
884
|
+
let test_app = setup_test_app().await;
|
|
885
|
+
|
|
886
|
+
// Simulate slow operation
|
|
887
|
+
let response = test_app
|
|
888
|
+
.server
|
|
889
|
+
.post("/slow-endpoint")
|
|
890
|
+
.add_header("x-delay", "5000") // 5 second delay
|
|
891
|
+
.await;
|
|
892
|
+
|
|
893
|
+
// Should timeout before response
|
|
894
|
+
let result = timeout(Duration::from_secs(1), response).await;
|
|
895
|
+
assert!(result.is_err());
|
|
896
|
+
}
|
|
897
|
+
```
|
|
76
898
|
|
|
77
|
-
##
|
|
78
|
-
- When the language runtime or package manager is not installed.
|
|
79
|
-
- When the main language cannot be determined in a multilingual project.
|
|
899
|
+
## Security Best Practices
|
|
80
900
|
|
|
81
|
-
|
|
82
|
-
- Access to the project file is required using the Read/Grep tool.
|
|
83
|
-
- When used with `Skill("moai-foundation-langs")`, it is easy to share cross-language conventions.
|
|
901
|
+
### Input Validation and Sanitization
|
|
84
902
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
903
|
+
```rust
|
|
904
|
+
// src/security/validation.rs
|
|
905
|
+
use regex::Regex;
|
|
906
|
+
use std::collections::HashSet;
|
|
907
|
+
use once_cell::sync::Lazy;
|
|
88
908
|
|
|
89
|
-
|
|
90
|
-
-
|
|
909
|
+
static EMAIL_REGEX: Lazy<Regex> = Lazy::new(|| {
|
|
910
|
+
Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap()
|
|
911
|
+
});
|
|
91
912
|
|
|
92
|
-
|
|
913
|
+
static PASSWORD_REGEX: Lazy<Regex> = Lazy::new(|| {
|
|
914
|
+
Regex::new(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$").unwrap()
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
static SQL_INJECTION_PATTERNS: Lazy<Vec<Regex>> = Lazy::new(|| {
|
|
918
|
+
vec![
|
|
919
|
+
Regex::new(r"(?i)(union|select|insert|update|delete|drop|create|alter)").unwrap(),
|
|
920
|
+
Regex::new(r"(?i)(script|javascript|vbscript|onload|onerror)").unwrap(),
|
|
921
|
+
Regex::new(r"(--|;|\/\*|\*\/)").unwrap(),
|
|
922
|
+
]
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
pub struct Validator;
|
|
926
|
+
|
|
927
|
+
impl Validator {
|
|
928
|
+
pub fn validate_email(email: &str) -> Result<(), ValidationError> {
|
|
929
|
+
if email.len() > 254 {
|
|
930
|
+
return Err(ValidationError::EmailTooLong);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if !EMAIL_REGEX.is_match(email) {
|
|
934
|
+
return Err(ValidationError::InvalidEmailFormat);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
Ok(())
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
pub fn validate_password(password: &str) -> Result<(), ValidationError> {
|
|
941
|
+
if password.len() < 8 {
|
|
942
|
+
return Err(ValidationError::PasswordTooShort);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
if password.len() > 128 {
|
|
946
|
+
return Err(ValidationError::PasswordTooLong);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
if !PASSWORD_REGEX.is_match(password) {
|
|
950
|
+
return Err(ValidationError::WeakPassword);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Check for common weak passwords
|
|
954
|
+
if Self::is_common_password(password) {
|
|
955
|
+
return Err(ValidationError::CommonPassword);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
Ok(())
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
pub fn sanitize_input(input: &str) -> String {
|
|
962
|
+
let mut sanitized = String::with_capacity(input.len());
|
|
963
|
+
|
|
964
|
+
for ch in input.chars() {
|
|
965
|
+
match ch {
|
|
966
|
+
// Allow safe characters
|
|
967
|
+
'a'..='z' | 'A'..='Z' | '0'..='9' | ' ' | '-' | '_' | '.' | '@' => {
|
|
968
|
+
sanitized.push(ch);
|
|
969
|
+
}
|
|
970
|
+
// Replace dangerous characters
|
|
971
|
+
'<' => sanitized.push_str("<"),
|
|
972
|
+
'>' => sanitized.push_str(">"),
|
|
973
|
+
'&' => sanitized.push_str("&"),
|
|
974
|
+
'"' => sanitized.push_str("""),
|
|
975
|
+
'\'' => sanitized.push_str("'"),
|
|
976
|
+
_ => {
|
|
977
|
+
// Skip other characters
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
sanitized.trim().to_string()
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
pub fn detect_sql_injection(input: &str) -> bool {
|
|
986
|
+
let lowercase_input = input.to_lowercase();
|
|
987
|
+
|
|
988
|
+
for pattern in SQL_INJECTION_PATTERNS.iter() {
|
|
989
|
+
if pattern.is_match(&lowercase_input) {
|
|
990
|
+
return true;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
false
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
fn is_common_password(password: &str) -> bool {
|
|
998
|
+
static COMMON_PASSWORDS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
|
999
|
+
vec![
|
|
1000
|
+
"password", "123456", "123456789", "12345678", "12345",
|
|
1001
|
+
"1234567", "1234567890", "qwerty", "abc123", "password123",
|
|
1002
|
+
].into_iter().collect()
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
COMMON_PASSWORDS.contains(password.to_lowercase().as_str())
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
#[derive(Debug, thiserror::Error)]
|
|
1010
|
+
pub enum ValidationError {
|
|
1011
|
+
#[error("Email is too long (max 254 characters)")]
|
|
1012
|
+
EmailTooLong,
|
|
1013
|
+
|
|
1014
|
+
#[error("Invalid email format")]
|
|
1015
|
+
InvalidEmailFormat,
|
|
1016
|
+
|
|
1017
|
+
#[error("Password is too short (min 8 characters)")]
|
|
1018
|
+
PasswordTooShort,
|
|
1019
|
+
|
|
1020
|
+
#[error("Password is too long (max 128 characters)")]
|
|
1021
|
+
PasswordTooLong,
|
|
1022
|
+
|
|
1023
|
+
#[error("Password is too weak")]
|
|
1024
|
+
WeakPassword,
|
|
1025
|
+
|
|
1026
|
+
#[error("Password is too common")]
|
|
1027
|
+
CommonPassword,
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// Rate limiting
|
|
1031
|
+
use std::collections::HashMap;
|
|
1032
|
+
use std::time::{Duration, Instant};
|
|
1033
|
+
use tokio::sync::RwLock;
|
|
1034
|
+
|
|
1035
|
+
pub struct RateLimiter {
|
|
1036
|
+
requests: Arc<RwLock<HashMap<String, Vec<Instant>>>>,
|
|
1037
|
+
max_requests: usize,
|
|
1038
|
+
window: Duration,
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
impl RateLimiter {
|
|
1042
|
+
pub fn new(max_requests: usize, window: Duration) -> Self {
|
|
1043
|
+
Self {
|
|
1044
|
+
requests: Arc::new(RwLock::new(HashMap::new())),
|
|
1045
|
+
max_requests,
|
|
1046
|
+
window,
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
pub async fn is_allowed(&self, key: &str) -> bool {
|
|
1051
|
+
let now = Instant::now();
|
|
1052
|
+
let mut requests = self.requests.write().await;
|
|
1053
|
+
|
|
1054
|
+
let user_requests = requests.entry(key.to_string()).or_insert_with(Vec::new);
|
|
1055
|
+
|
|
1056
|
+
// Remove old requests outside the window
|
|
1057
|
+
user_requests.retain(|×tamp| now.duration_since(timestamp) < self.window);
|
|
1058
|
+
|
|
1059
|
+
if user_requests.len() >= self.max_requests {
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
user_requests.push(now);
|
|
1064
|
+
true
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
### Authentication and Authorization
|
|
1070
|
+
|
|
1071
|
+
```rust
|
|
1072
|
+
// src/security/auth.rs
|
|
1073
|
+
use argon2::{password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2};
|
|
1074
|
+
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
|
1075
|
+
use serde::{Deserialize, Serialize};
|
|
1076
|
+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
1077
|
+
use uuid::Uuid;
|
|
1078
|
+
|
|
1079
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
1080
|
+
pub struct Claims {
|
|
1081
|
+
pub sub: String, // Subject (user ID)
|
|
1082
|
+
pub email: String,
|
|
1083
|
+
pub role: String,
|
|
1084
|
+
pub exp: usize, // Expiration time
|
|
1085
|
+
pub iat: usize, // Issued at
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
pub struct AuthService {
|
|
1089
|
+
jwt_secret: String,
|
|
1090
|
+
token_expiry: Duration,
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
impl AuthService {
|
|
1094
|
+
pub fn new(jwt_secret: String, token_expiry: Duration) -> Self {
|
|
1095
|
+
Self {
|
|
1096
|
+
jwt_secret,
|
|
1097
|
+
token_expiry,
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
pub fn hash_password(password: &str) -> Result<String, AuthError> {
|
|
1102
|
+
let salt = SaltString::generate(&mut OsRng);
|
|
1103
|
+
let argon2 = Argon2::default();
|
|
1104
|
+
|
|
1105
|
+
let password_hash = argon2
|
|
1106
|
+
.hash_password(password.as_bytes(), &salt)
|
|
1107
|
+
.map_err(|e| AuthError::PasswordHashing(e.to_string()))?;
|
|
1108
|
+
|
|
1109
|
+
Ok(password_hash.to_string())
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
pub fn verify_password(password: &str, hash: &str) -> Result<bool, AuthError> {
|
|
1113
|
+
let parsed_hash = PasswordHash::new(hash)
|
|
1114
|
+
.map_err(|e| AuthError::InvalidHash(e.to_string()))?;
|
|
1115
|
+
|
|
1116
|
+
let argon2 = Argon2::default();
|
|
1117
|
+
|
|
1118
|
+
match argon2.verify_password(password.as_bytes(), &parsed_hash) {
|
|
1119
|
+
Ok(_) => Ok(true),
|
|
1120
|
+
Err(argon2::password_hash::Error::Password) => Ok(false),
|
|
1121
|
+
Err(e) => Err(AuthError::PasswordHashing(e.to_string())),
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
pub fn generate_token(&self, user_id: &str, email: &str, role: &str) -> Result<String, AuthError> {
|
|
1126
|
+
let now = SystemTime::now()
|
|
1127
|
+
.duration_since(UNIX_EPOCH)
|
|
1128
|
+
.map_err(|e| AuthError::TokenGeneration(e.to_string()))?;
|
|
1129
|
+
|
|
1130
|
+
let claims = Claims {
|
|
1131
|
+
sub: user_id.to_string(),
|
|
1132
|
+
email: email.to_string(),
|
|
1133
|
+
role: role.to_string(),
|
|
1134
|
+
exp: (now + self.token_expiry).as_secs() as usize,
|
|
1135
|
+
iat: now.as_secs() as usize,
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1138
|
+
let token = encode(
|
|
1139
|
+
&Header::default(),
|
|
1140
|
+
&claims,
|
|
1141
|
+
&EncodingKey::from_secret(self.jwt_secret.as_bytes()),
|
|
1142
|
+
)
|
|
1143
|
+
.map_err(|e| AuthError::TokenGeneration(e.to_string()))?;
|
|
1144
|
+
|
|
1145
|
+
Ok(token)
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
pub fn validate_token(&self, token: &str) -> Result<Claims, AuthError> {
|
|
1149
|
+
let validation = Validation::default();
|
|
1150
|
+
let decoded = decode::<Claims>(
|
|
1151
|
+
token,
|
|
1152
|
+
&DecodingKey::from_secret(self.jwt_secret.as_bytes()),
|
|
1153
|
+
&validation,
|
|
1154
|
+
)
|
|
1155
|
+
.map_err(|_| AuthError::InvalidToken)?;
|
|
1156
|
+
|
|
1157
|
+
Ok(decoded.claims)
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
pub fn refresh_token(&self, claims: &Claims) -> Result<String, AuthError> {
|
|
1161
|
+
self.generate_token(&claims.sub, &claims.email, &claims.role)
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
#[derive(Debug, thiserror::Error)]
|
|
1166
|
+
pub enum AuthError {
|
|
1167
|
+
#[error("Password hashing failed: {0}")]
|
|
1168
|
+
PasswordHashing(String),
|
|
1169
|
+
|
|
1170
|
+
#[error("Invalid password hash: {0}")]
|
|
1171
|
+
InvalidHash(String),
|
|
1172
|
+
|
|
1173
|
+
#[error("Token generation failed: {0}")]
|
|
1174
|
+
TokenGeneration(String),
|
|
1175
|
+
|
|
1176
|
+
#[error("Invalid token")]
|
|
1177
|
+
InvalidToken,
|
|
1178
|
+
|
|
1179
|
+
#[error("Token expired")]
|
|
1180
|
+
TokenExpired,
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// Authorization middleware for axum
|
|
1184
|
+
use axum::{
|
|
1185
|
+
extract::{Request, State},
|
|
1186
|
+
http::{header::AUTHORIZATION, StatusCode},
|
|
1187
|
+
middleware::Next,
|
|
1188
|
+
response::Response,
|
|
1189
|
+
};
|
|
1190
|
+
use std::sync::Arc;
|
|
1191
|
+
|
|
1192
|
+
pub async fn auth_middleware(
|
|
1193
|
+
State(auth_service): State<Arc<AuthService>>,
|
|
1194
|
+
mut request: Request,
|
|
1195
|
+
next: Next,
|
|
1196
|
+
) -> Result<Response, StatusCode> {
|
|
1197
|
+
let auth_header = request
|
|
1198
|
+
.headers()
|
|
1199
|
+
.get(AUTHORIZATION)
|
|
1200
|
+
.and_then(|header| header.to_str().ok())
|
|
1201
|
+
.and_then(|header| header.strip_prefix("Bearer "));
|
|
1202
|
+
|
|
1203
|
+
let token = match auth_header {
|
|
1204
|
+
Some(token) => token,
|
|
1205
|
+
None => return Err(StatusCode::UNAUTHORIZED),
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
let claims = match auth_service.validate_token(token) {
|
|
1209
|
+
Ok(claims) => claims,
|
|
1210
|
+
Err(_) => return Err(StatusCode::UNAUTHORIZED),
|
|
1211
|
+
};
|
|
1212
|
+
|
|
1213
|
+
// Add claims to request extensions
|
|
1214
|
+
request.extensions_mut().insert(claims);
|
|
1215
|
+
|
|
1216
|
+
Ok(next.run(request).await)
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
pub fn require_role(required_role: &'static str) -> impl Fn(Request, Next) -> Result<Response, StatusCode> {
|
|
1220
|
+
move |request: Request, next: Next| {
|
|
1221
|
+
let claims = request.extensions().get::<Claims>();
|
|
1222
|
+
|
|
1223
|
+
match claims {
|
|
1224
|
+
Some(claims) if claims.role == required_role => Ok(next.run(request)),
|
|
1225
|
+
Some(_) => Err(StatusCode::FORBIDDEN),
|
|
1226
|
+
None => Err(StatusCode::UNAUTHORIZED),
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
## Integration Patterns
|
|
1233
|
+
|
|
1234
|
+
### gRPC Service Implementation
|
|
1235
|
+
|
|
1236
|
+
```rust
|
|
1237
|
+
// src/grpc/user_service.rs
|
|
1238
|
+
use tonic::{transport::Server, Request, Response, Status, Code};
|
|
1239
|
+
use tonic_health::server::HealthReporter;
|
|
1240
|
+
|
|
1241
|
+
pub mod user {
|
|
1242
|
+
tonic::include_proto!("user");
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
use user::{
|
|
1246
|
+
user_service_server::{UserService, UserServiceServer},
|
|
1247
|
+
CreateUserRequest, CreateUserResponse,
|
|
1248
|
+
GetUserRequest, GetUserResponse,
|
|
1249
|
+
User as GrpcUser,
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
use crate::models::User;
|
|
1253
|
+
use crate::services::UserService as AppUserService;
|
|
1254
|
+
use crate::error::AppError;
|
|
1255
|
+
|
|
1256
|
+
pub struct GrpcUserService {
|
|
1257
|
+
app_service: Arc<AppUserService>,
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
impl GrpcUserService {
|
|
1261
|
+
pub fn new(app_service: Arc<AppUserService>) -> Self {
|
|
1262
|
+
Self { app_service }
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
#[tonic::async_trait]
|
|
1267
|
+
impl UserService for GrpcUserService {
|
|
1268
|
+
async fn create_user(
|
|
1269
|
+
&self,
|
|
1270
|
+
request: Request<CreateUserRequest>,
|
|
1271
|
+
) -> Result<Response<CreateUserResponse>, Status> {
|
|
1272
|
+
let req = request.into_inner();
|
|
1273
|
+
|
|
1274
|
+
let create_req = crate::services::CreateUserRequest {
|
|
1275
|
+
email: req.email,
|
|
1276
|
+
name: req.name,
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
match self.app_service.create_user(create_req).await {
|
|
1280
|
+
Ok(user) => {
|
|
1281
|
+
let grpc_user = GrpcUser {
|
|
1282
|
+
id: user.id.to_string(),
|
|
1283
|
+
email: user.email,
|
|
1284
|
+
name: user.name,
|
|
1285
|
+
created_at: Some(user.created_at.into()),
|
|
1286
|
+
};
|
|
1287
|
+
|
|
1288
|
+
let response = CreateUserResponse { user: Some(grpc_user) };
|
|
1289
|
+
Ok(Response::new(response))
|
|
1290
|
+
}
|
|
1291
|
+
Err(e) => {
|
|
1292
|
+
let status = match e {
|
|
1293
|
+
AppError::Validation { .. } => Status::invalid_argument(e.to_string()),
|
|
1294
|
+
AppError::DuplicateEmail => Status::already_exists(e.to_string()),
|
|
1295
|
+
_ => Status::internal(e.to_string()),
|
|
1296
|
+
};
|
|
1297
|
+
Err(status)
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
async fn get_user(
|
|
1303
|
+
&self,
|
|
1304
|
+
request: Request<GetUserRequest>,
|
|
1305
|
+
) -> Result<Response<GetUserResponse>, Status> {
|
|
1306
|
+
let req = request.into_inner();
|
|
1307
|
+
|
|
1308
|
+
let user_id = Uuid::parse_str(&req.id)
|
|
1309
|
+
.map_err(|_| Status::invalid_argument("Invalid user ID"))?;
|
|
1310
|
+
|
|
1311
|
+
match self.app_service.get_user(user_id).await {
|
|
1312
|
+
Some(user) => {
|
|
1313
|
+
let grpc_user = GrpcUser {
|
|
1314
|
+
id: user.id.to_string(),
|
|
1315
|
+
email: user.email,
|
|
1316
|
+
name: user.name,
|
|
1317
|
+
created_at: Some(user.created_at.into()),
|
|
1318
|
+
};
|
|
1319
|
+
|
|
1320
|
+
let response = GetUserResponse { user: Some(grpc_user) };
|
|
1321
|
+
Ok(Response::new(response))
|
|
1322
|
+
}
|
|
1323
|
+
None => Err(Status::not_found("User not found")),
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// Server setup
|
|
1329
|
+
pub async fn run_grpc_server(
|
|
1330
|
+
addr: std::net::SocketAddr,
|
|
1331
|
+
user_service: Arc<AppUserService>,
|
|
1332
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
1333
|
+
let grpc_service = GrpcUserService::new(user_service);
|
|
1334
|
+
|
|
1335
|
+
let (mut health_reporter, health_service) = tonic_health::server::health_reporter();
|
|
1336
|
+
health_reporter.set_serving::<UserServiceServer<GrpcUserService>>().await;
|
|
1337
|
+
|
|
1338
|
+
Server::builder()
|
|
1339
|
+
.add_service(health_service)
|
|
1340
|
+
.add_service(UserServiceServer::new(grpc_service))
|
|
1341
|
+
.serve(addr)
|
|
1342
|
+
.await?;
|
|
1343
|
+
|
|
1344
|
+
Ok(())
|
|
1345
|
+
}
|
|
1346
|
+
```
|
|
1347
|
+
|
|
1348
|
+
### Message Queue Integration
|
|
1349
|
+
|
|
1350
|
+
```rust
|
|
1351
|
+
// src/messaging/kafka_producer.rs
|
|
1352
|
+
use rdkafka::{
|
|
1353
|
+
config::ClientConfig,
|
|
1354
|
+
producer::{FutureProducer, FutureRecord},
|
|
1355
|
+
Client as KafkaClient,
|
|
1356
|
+
};
|
|
1357
|
+
use serde::{Deserialize, Serialize};
|
|
1358
|
+
use std::time::Duration;
|
|
1359
|
+
use tokio::time::timeout;
|
|
1360
|
+
|
|
1361
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
1362
|
+
pub struct Event {
|
|
1363
|
+
pub id: String,
|
|
1364
|
+
pub event_type: String,
|
|
1365
|
+
pub data: serde_json::Value,
|
|
1366
|
+
pub timestamp: chrono::DateTime<chrono::Utc>,
|
|
1367
|
+
pub version: u32,
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
pub struct EventProducer {
|
|
1371
|
+
producer: FutureProducer,
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
impl EventProducer {
|
|
1375
|
+
pub fn new(brokers: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
|
1376
|
+
let producer: FutureProducer = ClientConfig::new()
|
|
1377
|
+
.set("bootstrap.servers", brokers)
|
|
1378
|
+
.set("message.timeout.ms", "5000")
|
|
1379
|
+
.set("delivery.timeout.ms", "10000")
|
|
1380
|
+
.set("request.timeout.ms", "5000")
|
|
1381
|
+
.create()?;
|
|
1382
|
+
|
|
1383
|
+
Ok(Self { producer })
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
pub async fn publish_event(&self, topic: &str, event: Event) -> Result<(), Box<dyn std::error::Error>> {
|
|
1387
|
+
let event_json = serde_json::to_vec(&event)?;
|
|
1388
|
+
let key = Some(event.id.as_bytes());
|
|
1389
|
+
|
|
1390
|
+
let record = FutureRecord::to(topic)
|
|
1391
|
+
.key(key)
|
|
1392
|
+
.payload(&event_json)
|
|
1393
|
+
.headers(
|
|
1394
|
+
rdkafka::message::Headers::new()
|
|
1395
|
+
.add("event_type", &event.event_type)
|
|
1396
|
+
.add("version", &event.version.to_string())
|
|
1397
|
+
);
|
|
1398
|
+
|
|
1399
|
+
let delivery_future = self.producer.send(record, Duration::from_secs(0));
|
|
1400
|
+
|
|
1401
|
+
match timeout(Duration::from_secs(5), delivery_future).await {
|
|
1402
|
+
Ok(Ok((partition, offset))) => {
|
|
1403
|
+
tracing::info!(
|
|
1404
|
+
"Event published to partition {} at offset {}",
|
|
1405
|
+
partition,
|
|
1406
|
+
offset
|
|
1407
|
+
);
|
|
1408
|
+
Ok(())
|
|
1409
|
+
}
|
|
1410
|
+
Ok(Err((kafka_error, _))) => {
|
|
1411
|
+
tracing::error!("Failed to publish event: {}", kafka_error);
|
|
1412
|
+
Err(Box::new(kafka_error))
|
|
1413
|
+
}
|
|
1414
|
+
Err(_) => {
|
|
1415
|
+
tracing::error!("Timeout while publishing event");
|
|
1416
|
+
Err("Timeout while publishing event".into())
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// Event consumer
|
|
1423
|
+
use rdkafka::{
|
|
1424
|
+
config::ConsumerConfig,
|
|
1425
|
+
consumer::{StreamConsumer, Consumer},
|
|
1426
|
+
message::BorrowedMessage,
|
|
1427
|
+
};
|
|
1428
|
+
|
|
1429
|
+
pub struct EventConsumer {
|
|
1430
|
+
consumer: StreamConsumer,
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
impl EventConsumer {
|
|
1434
|
+
pub fn new(brokers: &str, group_id: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
|
1435
|
+
let consumer: StreamConsumer = ClientConfig::new()
|
|
1436
|
+
.set("group.id", group_id)
|
|
1437
|
+
.set("bootstrap.servers", brokers)
|
|
1438
|
+
.set("enable.auto.commit", "false")
|
|
1439
|
+
.set("auto.offset.reset", "earliest")
|
|
1440
|
+
.create()?;
|
|
1441
|
+
|
|
1442
|
+
Ok(Self { consumer })
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
pub async fn subscribe(&self, topics: &[&str]) -> Result<(), Box<dyn std::error::Error>> {
|
|
1446
|
+
self.consumer.subscribe(topics)?;
|
|
1447
|
+
Ok(())
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
pub async fn start_consuming<F, Fut>(&mut self, handler: F)
|
|
1451
|
+
where
|
|
1452
|
+
F: Fn(Event) -> Fut + Send + Sync + 'static,
|
|
1453
|
+
Fut: std::future::Future<Output = Result<(), Box<dyn std::error::Error>>> + Send,
|
|
1454
|
+
{
|
|
1455
|
+
while let Some(message_result = self.consumer.recv().await {
|
|
1456
|
+
match message_result {
|
|
1457
|
+
Ok(message) => {
|
|
1458
|
+
match self.handle_message(&message, &handler).await {
|
|
1459
|
+
Ok(_) => {
|
|
1460
|
+
// Commit message offset
|
|
1461
|
+
let _ = self.consumer.commit_message(&message, rdkafka::consumer::CommitMode::Async).await;
|
|
1462
|
+
}
|
|
1463
|
+
Err(e) => {
|
|
1464
|
+
tracing::error!("Error handling message: {}", e);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
Err(e) => {
|
|
1469
|
+
tracing::error!("Error receiving message: {}", e);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
async fn handle_message<F, Fut>(
|
|
1476
|
+
&self,
|
|
1477
|
+
message: &BorrowedMessage<'_>,
|
|
1478
|
+
handler: &F,
|
|
1479
|
+
) -> Result<(), Box<dyn std::error::Error>>
|
|
1480
|
+
where
|
|
1481
|
+
F: Fn(Event) -> Fut,
|
|
1482
|
+
Fut: std::future::Future<Output = Result<(), Box<dyn std::error::Error>>>,
|
|
1483
|
+
{
|
|
1484
|
+
let payload = message.payload().ok_or("Empty message payload")?;
|
|
1485
|
+
|
|
1486
|
+
let event: Event = serde_json::from_slice(payload)?;
|
|
1487
|
+
|
|
1488
|
+
handler(event).await
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
```
|
|
1492
|
+
|
|
1493
|
+
## Modern Development Workflow
|
|
1494
|
+
|
|
1495
|
+
### Configuration Management
|
|
1496
|
+
|
|
1497
|
+
```rust
|
|
1498
|
+
// src/config/mod.rs
|
|
1499
|
+
use serde::{Deserialize, Serialize};
|
|
1500
|
+
use std::path::Path;
|
|
1501
|
+
|
|
1502
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
1503
|
+
pub struct Config {
|
|
1504
|
+
pub server: ServerConfig,
|
|
1505
|
+
pub database: DatabaseConfig,
|
|
1506
|
+
pub auth: AuthConfig,
|
|
1507
|
+
pub kafka: KafkaConfig,
|
|
1508
|
+
pub logging: LoggingConfig,
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
1512
|
+
pub struct ServerConfig {
|
|
1513
|
+
pub host: String,
|
|
1514
|
+
pub port: u16,
|
|
1515
|
+
pub workers: usize,
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
1519
|
+
pub struct DatabaseConfig {
|
|
1520
|
+
pub url: String,
|
|
1521
|
+
pub max_connections: u32,
|
|
1522
|
+
pub min_connections: u32,
|
|
1523
|
+
pub connect_timeout: u64,
|
|
1524
|
+
pub acquire_timeout: u64,
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
1528
|
+
pub struct AuthConfig {
|
|
1529
|
+
pub jwt_secret: String,
|
|
1530
|
+
pub token_expiry_hours: u64,
|
|
1531
|
+
pub bcrypt_cost: u32,
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
1535
|
+
pub struct KafkaConfig {
|
|
1536
|
+
pub brokers: Vec<String>,
|
|
1537
|
+
pub group_id: String,
|
|
1538
|
+
pub topic_prefix: String,
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
1542
|
+
pub struct LoggingConfig {
|
|
1543
|
+
pub level: String,
|
|
1544
|
+
pub format: String,
|
|
1545
|
+
pub output: String,
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
impl Config {
|
|
1549
|
+
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
|
|
1550
|
+
// Try environment variable first
|
|
1551
|
+
if let Ok(config_path) = std::env::var("CONFIG_PATH") {
|
|
1552
|
+
return Self::load_from_file(&config_path);
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// Try default locations
|
|
1556
|
+
let default_paths = vec![
|
|
1557
|
+
"config.toml",
|
|
1558
|
+
"config.yaml",
|
|
1559
|
+
"/etc/my-app/config.toml",
|
|
1560
|
+
"/etc/my-app/config.yaml",
|
|
1561
|
+
];
|
|
1562
|
+
|
|
1563
|
+
for path in default_paths {
|
|
1564
|
+
if Path::new(path).exists() {
|
|
1565
|
+
return Self::load_from_file(path);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
// Fallback to environment variables
|
|
1570
|
+
Self::load_from_env()
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
fn load_from_file(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
|
1574
|
+
let content = std::fs::read_to_string(path)?;
|
|
1575
|
+
|
|
1576
|
+
if path.ends_with(".toml") {
|
|
1577
|
+
let config: Config = toml::from_str(&content)?;
|
|
1578
|
+
Ok(config)
|
|
1579
|
+
} else if path.ends_with(".yaml") || path.ends_with(".yml") {
|
|
1580
|
+
let config: Config = serde_yaml::from_str(&content)?;
|
|
1581
|
+
Ok(config)
|
|
1582
|
+
} else {
|
|
1583
|
+
Err("Unsupported config file format".into())
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
fn load_from_env() -> Result<Self, Box<dyn std::error::Error>> {
|
|
1588
|
+
Ok(Config {
|
|
1589
|
+
server: ServerConfig {
|
|
1590
|
+
host: std::env::var("SERVER_HOST").unwrap_or_else(|_| "0.0.0.0".to_string()),
|
|
1591
|
+
port: std::env::var("SERVER_PORT")
|
|
1592
|
+
.unwrap_or_else(|_| "8080".to_string())
|
|
1593
|
+
.parse()?,
|
|
1594
|
+
workers: std::env::var("SERVER_WORKERS")
|
|
1595
|
+
.unwrap_or_else(|_| "4".to_string())
|
|
1596
|
+
.parse()?,
|
|
1597
|
+
},
|
|
1598
|
+
database: DatabaseConfig {
|
|
1599
|
+
url: std::env::var("DATABASE_URL")?,
|
|
1600
|
+
max_connections: std::env::var("DATABASE_MAX_CONNECTIONS")
|
|
1601
|
+
.unwrap_or_else(|_| "10".to_string())
|
|
1602
|
+
.parse()?,
|
|
1603
|
+
min_connections: std::env::var("DATABASE_MIN_CONNECTIONS")
|
|
1604
|
+
.unwrap_or_else(|_| "1".to_string())
|
|
1605
|
+
.parse()?,
|
|
1606
|
+
connect_timeout: std::env::var("DATABASE_CONNECT_TIMEOUT")
|
|
1607
|
+
.unwrap_or_else(|_| "30".to_string())
|
|
1608
|
+
.parse()?,
|
|
1609
|
+
acquire_timeout: std::env::var("DATABASE_ACQUIRE_TIMEOUT")
|
|
1610
|
+
.unwrap_or_else(|_| "30".to_string())
|
|
1611
|
+
.parse()?,
|
|
1612
|
+
},
|
|
1613
|
+
auth: AuthConfig {
|
|
1614
|
+
jwt_secret: std::env::var("JWT_SECRET")?,
|
|
1615
|
+
token_expiry_hours: std::env::var("TOKEN_EXPIRY_HOURS")
|
|
1616
|
+
.unwrap_or_else(|_| "24".to_string())
|
|
1617
|
+
.parse()?,
|
|
1618
|
+
bcrypt_cost: std::env::var("BCRYPT_COST")
|
|
1619
|
+
.unwrap_or_else(|_| "12".to_string())
|
|
1620
|
+
.parse()?,
|
|
1621
|
+
},
|
|
1622
|
+
kafka: KafkaConfig {
|
|
1623
|
+
brokers: std::env::var("KAFKA_BROKERS")
|
|
1624
|
+
.unwrap_or_else(|_| "localhost:9092".to_string())
|
|
1625
|
+
.split(',')
|
|
1626
|
+
.map(|s| s.trim().to_string())
|
|
1627
|
+
.collect(),
|
|
1628
|
+
group_id: std::env::var("KAFKA_GROUP_ID")
|
|
1629
|
+
.unwrap_or_else(|_| "my-app".to_string()),
|
|
1630
|
+
topic_prefix: std::env::var("KAFKA_TOPIC_PREFIX")
|
|
1631
|
+
.unwrap_or_else(|_| "my-app".to_string()),
|
|
1632
|
+
},
|
|
1633
|
+
logging: LoggingConfig {
|
|
1634
|
+
level: std::env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string()),
|
|
1635
|
+
format: std::env::var("LOG_FORMAT").unwrap_or_else(|_| "json".to_string()),
|
|
1636
|
+
output: std::env::var("LOG_OUTPUT").unwrap_or_else(|_| "stdout".to_string()),
|
|
1637
|
+
},
|
|
1638
|
+
})
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
```
|
|
1642
|
+
|
|
1643
|
+
### Cargo Workspace Configuration
|
|
1644
|
+
|
|
1645
|
+
```toml
|
|
1646
|
+
# Cargo.toml (workspace root)
|
|
1647
|
+
[workspace]
|
|
1648
|
+
members = [
|
|
1649
|
+
"crates/common",
|
|
1650
|
+
"crates/server",
|
|
1651
|
+
"crates/client",
|
|
1652
|
+
"crates/cli",
|
|
1653
|
+
]
|
|
1654
|
+
|
|
1655
|
+
[workspace.package]
|
|
1656
|
+
version = "0.1.0"
|
|
1657
|
+
edition = "2021"
|
|
1658
|
+
authors = ["Your Name <your.email@example.com>"]
|
|
1659
|
+
license = "MIT OR Apache-2.0"
|
|
1660
|
+
repository = "https://github.com/yourorg/your-project"
|
|
1661
|
+
|
|
1662
|
+
[workspace.dependencies]
|
|
1663
|
+
# Async runtime
|
|
1664
|
+
tokio = { version = "1.42", features = ["full"] }
|
|
1665
|
+
tokio-util = "0.7"
|
|
1666
|
+
|
|
1667
|
+
# Web framework
|
|
1668
|
+
axum = "0.8"
|
|
1669
|
+
tower = "0.5"
|
|
1670
|
+
tower-http = "0.6"
|
|
1671
|
+
|
|
1672
|
+
# Database
|
|
1673
|
+
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "chrono", "uuid"] }
|
|
1674
|
+
diesel = { version = "2.2", features = ["postgres", "uuid", "chrono"] }
|
|
1675
|
+
|
|
1676
|
+
# Serialization
|
|
1677
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
1678
|
+
serde_json = "1.0"
|
|
1679
|
+
|
|
1680
|
+
# Error handling
|
|
1681
|
+
thiserror = "1.0"
|
|
1682
|
+
anyhow = "1.0"
|
|
1683
|
+
|
|
1684
|
+
# Logging
|
|
1685
|
+
tracing = "0.1"
|
|
1686
|
+
tracing-subscriber = { version = "0.3", features = ["json"] }
|
|
1687
|
+
|
|
1688
|
+
# Security
|
|
1689
|
+
argon2 = "0.5"
|
|
1690
|
+
jsonwebtoken = "9.3"
|
|
1691
|
+
uuid = { version = "1.11", features = ["v4", "serde"] }
|
|
1692
|
+
|
|
1693
|
+
# Configuration
|
|
1694
|
+
config = "0.14"
|
|
1695
|
+
serde_yaml = "0.9"
|
|
1696
|
+
|
|
1697
|
+
# Testing
|
|
1698
|
+
tokio-test = "0.4"
|
|
1699
|
+
mockall = "0.13"
|
|
1700
|
+
|
|
1701
|
+
[profile.release]
|
|
1702
|
+
lto = true
|
|
1703
|
+
codegen-units = 1
|
|
1704
|
+
panic = "abort"
|
|
1705
|
+
|
|
1706
|
+
[profile.dev]
|
|
1707
|
+
debug = true
|
|
1708
|
+
```
|
|
1709
|
+
|
|
1710
|
+
### GitHub Actions CI/CD
|
|
1711
|
+
|
|
1712
|
+
```yaml
|
|
1713
|
+
# .github/workflows/ci.yml
|
|
1714
|
+
name: CI
|
|
1715
|
+
|
|
1716
|
+
on:
|
|
1717
|
+
push:
|
|
1718
|
+
branches: [ main, develop ]
|
|
1719
|
+
pull_request:
|
|
1720
|
+
branches: [ main ]
|
|
1721
|
+
|
|
1722
|
+
env:
|
|
1723
|
+
CARGO_TERM_COLOR: always
|
|
1724
|
+
|
|
1725
|
+
jobs:
|
|
1726
|
+
test:
|
|
1727
|
+
name: Test Suite
|
|
1728
|
+
runs-on: ubuntu-latest
|
|
1729
|
+
|
|
1730
|
+
strategy:
|
|
1731
|
+
matrix:
|
|
1732
|
+
rust:
|
|
1733
|
+
- stable
|
|
1734
|
+
- beta
|
|
1735
|
+
- nightly
|
|
1736
|
+
features:
|
|
1737
|
+
- ""
|
|
1738
|
+
- "kafka,database"
|
|
1739
|
+
|
|
1740
|
+
steps:
|
|
1741
|
+
- uses: actions/checkout@v4
|
|
1742
|
+
|
|
1743
|
+
- name: Install Rust
|
|
1744
|
+
uses: dtolnay/rust-toolchain@master
|
|
1745
|
+
with:
|
|
1746
|
+
toolchain: ${{ matrix.rust }}
|
|
1747
|
+
components: rustfmt, clippy
|
|
1748
|
+
|
|
1749
|
+
- name: Cache dependencies
|
|
1750
|
+
uses: actions/cache@v4
|
|
1751
|
+
with:
|
|
1752
|
+
path: |
|
|
1753
|
+
~/.cargo/registry
|
|
1754
|
+
~/.cargo/git
|
|
1755
|
+
target
|
|
1756
|
+
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
1757
|
+
|
|
1758
|
+
- name: Install system dependencies
|
|
1759
|
+
run: |
|
|
1760
|
+
sudo apt-get update
|
|
1761
|
+
sudo apt-get install -y postgresql-client libpq-dev
|
|
1762
|
+
|
|
1763
|
+
- name: Check formatting
|
|
1764
|
+
run: cargo fmt --all -- --check
|
|
1765
|
+
|
|
1766
|
+
- name: Run clippy
|
|
1767
|
+
run: cargo clippy --all-targets --all-features -- -D warnings
|
|
1768
|
+
|
|
1769
|
+
- name: Run tests
|
|
1770
|
+
run: cargo test --workspace --features ${{ matrix.features }}
|
|
1771
|
+
|
|
1772
|
+
- name: Run doc tests
|
|
1773
|
+
run: cargo test --doc --features ${{ matrix.features }}
|
|
1774
|
+
|
|
1775
|
+
coverage:
|
|
1776
|
+
name: Code Coverage
|
|
1777
|
+
runs-on: ubuntu-latest
|
|
1778
|
+
|
|
1779
|
+
steps:
|
|
1780
|
+
- uses: actions/checkout@v4
|
|
1781
|
+
|
|
1782
|
+
- name: Install Rust
|
|
1783
|
+
uses: dtolnay/rust-toolchain@stable
|
|
1784
|
+
with:
|
|
1785
|
+
components: llvm-tools-preview
|
|
1786
|
+
|
|
1787
|
+
- name: Install cargo-llvm-cov
|
|
1788
|
+
run: cargo install cargo-llvm-cov
|
|
1789
|
+
|
|
1790
|
+
- name: Install system dependencies
|
|
1791
|
+
run: |
|
|
1792
|
+
sudo apt-get update
|
|
1793
|
+
sudo apt-get install -y postgresql-client libpq-dev
|
|
1794
|
+
|
|
1795
|
+
- name: Generate code coverage
|
|
1796
|
+
run: cargo llvm-cov --workspace --lcov --output-path lcov.info
|
|
1797
|
+
|
|
1798
|
+
- name: Upload coverage to Codecov
|
|
1799
|
+
uses: codecov/codecov-action@v4
|
|
1800
|
+
with:
|
|
1801
|
+
files: lcov.info
|
|
1802
|
+
fail_ci_if_error: true
|
|
1803
|
+
|
|
1804
|
+
security:
|
|
1805
|
+
name: Security Audit
|
|
1806
|
+
runs-on: ubuntu-latest
|
|
1807
|
+
|
|
1808
|
+
steps:
|
|
1809
|
+
- uses: actions/checkout@v4
|
|
1810
|
+
|
|
1811
|
+
- name: Install Rust
|
|
1812
|
+
uses: dtolnay/rust-toolchain@stable
|
|
1813
|
+
|
|
1814
|
+
- name: Install cargo-audit
|
|
1815
|
+
run: cargo install cargo-audit
|
|
1816
|
+
|
|
1817
|
+
- name: Security audit
|
|
1818
|
+
run: cargo audit
|
|
1819
|
+
|
|
1820
|
+
- name: Install cargo-deny
|
|
1821
|
+
run: cargo install cargo-deny
|
|
1822
|
+
|
|
1823
|
+
- name: Check dependencies
|
|
1824
|
+
run: cargo deny check
|
|
1825
|
+
|
|
1826
|
+
build:
|
|
1827
|
+
name: Build Release
|
|
1828
|
+
runs-on: ubuntu-latest
|
|
1829
|
+
|
|
1830
|
+
needs: [test, security]
|
|
1831
|
+
|
|
1832
|
+
steps:
|
|
1833
|
+
- uses: actions/checkout@v4
|
|
1834
|
+
|
|
1835
|
+
- name: Install Rust
|
|
1836
|
+
uses: dtolnay/rust-toolchain@stable
|
|
1837
|
+
|
|
1838
|
+
- name: Cache dependencies
|
|
1839
|
+
uses: actions/cache@v4
|
|
1840
|
+
with:
|
|
1841
|
+
path: |
|
|
1842
|
+
~/.cargo/registry
|
|
1843
|
+
~/.cargo/git
|
|
1844
|
+
target
|
|
1845
|
+
key: ${{ runner.os }}-cargo-release-${{ hashFiles('**/Cargo.lock') }}
|
|
1846
|
+
|
|
1847
|
+
- name: Build release
|
|
1848
|
+
run: cargo build --workspace --release
|
|
1849
|
+
|
|
1850
|
+
- name: Upload binary artifacts
|
|
1851
|
+
uses: actions/upload-artifact@v4
|
|
1852
|
+
with:
|
|
1853
|
+
name: release-binaries
|
|
1854
|
+
path: target/release/
|
|
1855
|
+
```
|
|
1856
|
+
|
|
1857
|
+
---
|
|
93
1858
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
1859
|
+
**Created by**: MoAI Language Skill Factory
|
|
1860
|
+
**Last Updated**: 2025-11-06
|
|
1861
|
+
**Version**: 2.0.0
|
|
1862
|
+
**Rust Target**: 1.91+ with latest language features
|
|
97
1863
|
|
|
98
|
-
|
|
99
|
-
- Enable automatic validation by matching your linter with the language's official style guide.
|
|
100
|
-
- Fix test/build pipelines with reproducible commands in CI.
|
|
1864
|
+
This skill provides comprehensive Rust development guidance with 2025 best practices, covering everything from basic memory safety to advanced systems programming and performance-critical applications.
|