forked from
atscan.net/plcbundle-rs
High-performance implementation of plcbundle written in Rust
1# plcbundle-rs Coding Rules
2
3## Core Design Principles
4
51. **Single Entry Point**: All operations go through `BundleManager`
62. **Options Pattern**: Complex operations use dedicated option structs
73. **Result Types**: Operations return structured result types, not raw tuples
84. **Streaming by Default**: Use iterators for large datasets
95. **NO DIRECT FILE ACCESS**: CLI commands and server code NEVER open bundle files directly
10
11## Critical Rules
12
13### File Access
14- ❌ **NEVER** use `std::fs::File::open` for bundle files in CLI commands or server code
15- ❌ **NEVER** use `std::fs::read` for bundle data in CLI commands or server code
16- ❌ **NEVER** use `std::fs::remove_file` for bundles in CLI commands or server code
17- ❌ **NEVER** access core components (Index, bundle_format, etc.) directly from CLI or server
18- ✅ **ALWAYS** use `BundleManager` methods for bundle operations
19- ✅ **ALWAYS** add new operations to `BundleManager` API if needed
20
21### API-First Development
22- All CLI commands in `src/bin/commands/` must use **ONLY** public `BundleManager` APIs
23- All server code must use **ONLY** public `BundleManager` APIs
24- If CLI or server code needs file access, add the method to `BundleManager` first
25- The `BundleManager` is the single source of truth for all bundle operations
26
27### Examples
28
29**❌ WRONG - Direct file access:**
30```rust
31let bundle_path = dir.join(format!("{:06}.jsonl.zst", bundle_num));
32std::fs::remove_file(&bundle_path)?;
33```
34
35**✅ CORRECT - Via BundleManager API:**
36```rust
37manager.delete_bundle_files(&[bundle_num])?;
38```
39
40## Architecture
41
42```
43CLI Commands / Server → BundleManager API → Internal Implementation
44 ↓ ↓ ↓
45 Uses Provides Opens files
46 Only Public API Directly
47```
48
49## When Adding New Features
50
511. Design the API method in `BundleManager` first
522. Document it in `docs/API.md`
533. Implement the method in `src/manager.rs`
544. Export types in `src/lib.rs` if needed
555. Use the API from CLI command or server
56
57## Reference Documentation
58
59- Full API design: `docs/API.md`
60- Bundle format: `docs/BUNDLE_FORMAT.md`
61- All public APIs are listed in `docs/API.md` Quick Reference
62
63## Common Patterns
64
65### Loading Bundles
66```rust
67// ❌ Don't open files directly
68let file = File::open(bundle_path)?;
69
70// ✅ Use the API
71let result = manager.load_bundle(bundle_num, LoadOptions::default())?;
72```
73
74### Getting Operations
75```rust
76// ❌ Don't parse files directly
77let json = std::fs::read_to_string(bundle_path)?;
78
79// ✅ Use the API
80let json = manager.get_operation_raw(bundle_num, position)?;
81```
82
83### Deleting Bundles
84```rust
85// ❌ Don't remove files directly
86std::fs::remove_file(bundle_path)?;
87
88// ✅ Use the API
89manager.delete_bundle_files(&[bundle_num])?;
90```
91
92### JSON Parsing
93- ✅ **ALWAYS** use `sonic_rs` for JSON parsing (performance-critical)
94- ❌ **NEVER** use `serde_json` for parsing JSON from bundles or operations
95- Use `sonic_rs::from_str` for parsing JSON strings
96- Use `sonic_rs::Value` and `JsonValueTrait` for accessing JSON values
97
98**Examples:**
99```rust
100// ✅ CORRECT - Use sonic_rs
101use sonic_rs::JsonValueTrait;
102if let Ok(value) = sonic_rs::from_str::<sonic_rs::Value>(&line) {
103 if let Some(did) = value.get("did").and_then(|v| v.as_str()) {
104 // ...
105 }
106}
107
108// ❌ WRONG - Don't use serde_json
109use serde_json::Value;
110let value: Value = serde_json::from_str(&line)?;
111```
112
113## Testing
114
115**IMPORTANT**: Always test compilation with all features enabled.
116
117When implementing features:
118- **Always run**: `cargo check --all-features` or `cargo build --all-features` before considering work complete
119- This ensures code compiles with both `cli` and `server` features enabled
120- It's very common that code compiles with default features but fails with all features
121- Fix any compilation errors that appear with all features enabled
122
123If writing actual tests:
124- Test through the public API
125- Don't test internal implementation details
126- CLI tests should use actual `BundleManager` instances
127
128## CLI Output Formatting
129
130### Bundle Numbers in Human-Readable Output
131- ✅ **ALWAYS** display bundle numbers **without leading zeros** in CLI output for humans
132- Use `{}` format, NOT `{:06}` format
133- Example: `"Bundle: 123"` not `"Bundle: 000123"`
134- This applies to all `println!`, `eprintln!`, and `log::*!` statements that display bundle numbers to users
135- Exception: File paths and internal identifiers may use zero-padded format if needed
136
137**Examples:**
138```rust
139// ✅ CORRECT - Human-readable output
140println!(" Last bundle: {}", bundle_num);
141log::info!("Bundle {} processed", bundle_num);
142
143// ❌ WRONG - Don't use leading zeros for humans
144println!(" Last bundle: {:06}", bundle_num);
145log::info!("Bundle {:06} processed", bundle_num);
146```
147
148## Command File Structure
149
150All command files (`src/cli/cmd_*.rs`) must follow a consistent structure:
151
1521. **Imports** - All `use` statements at the top
1532. **Command Definitions** - All `#[derive(Args)]`, `#[derive(Subcommand)]` structs and enums
1543. **Trait Implementations** - Any `impl` blocks for command structs (e.g., `impl HasGlobalFlags`)
1554. **Run Function** - The main `pub fn run()` function
1565. **Helper Functions** - All private helper functions at the end
157
158**Exception**: Helper functions that are referenced in struct definitions (e.g., `value_parser = parse_duration`) must be defined before the struct that uses them.
159
160**Examples:**
161
162**✅ CORRECT - Proper structure:**
163```rust
164use anyhow::Result;
165use clap::Args;
166use plcbundle::BundleManager;
167use std::path::PathBuf;
168
169#[derive(Args)]
170#[command(about = "Example command")]
171pub struct ExampleCommand {
172 // ...
173}
174
175impl HasGlobalFlags for ExampleCommand {
176 // ...
177}
178
179pub fn run(cmd: ExampleCommand, dir: PathBuf) -> Result<()> {
180 // ...
181}
182
183fn helper_function() {
184 // ...
185}
186```
187
188**❌ WRONG - Helper function before command struct:**
189```rust
190use anyhow::Result;
191use clap::Args;
192
193fn helper_function() {
194 // ...
195}
196
197#[derive(Args)]
198pub struct ExampleCommand {
199 // ...
200}
201```
202
203## Questions?
204
205If you're implementing a feature and need file access:
2061. Check if `BundleManager` already has the method
2072. If not, add it to `BundleManager` first
2083. Update `docs/API.md` with the new method
2094. Then use it from the CLI or server
210
211**Remember: The CLI and server are just thin wrappers around `BundleManager`!**
212