# Best Practices
Guidelines for using synta-codegen effectively in production environments.
## Schema design
### Use descriptive type names
**Good:**
```asn1
UserIdentifier ::= INTEGER (1..999999)
EmailAddress ::= IA5String (SIZE (5..255))
```
**Avoid:**
```asn1
UID ::= INTEGER -- Too short, unclear
String1 ::= IA5String -- Generic, unclear purpose
```
### Leverage constraints for validation
**Good:**
```asn1
Port ::= INTEGER (1..65535)
```asn1
Protocol ::= INTEGER {
tcp(6),
udp(17),
sctp(132)
}
```
Usage:
```rust
let proto = Protocol::TCP; // Type-safe constant
```
### Design for extensibility
```asn1
Request ::= SEQUENCE {
version INTEGER,
messageId INTEGER,
payload CHOICE {
v1 [0] RequestV1,
v2 [1] RequestV2
-- Future versions can be added without breaking compatibility
}
}
```
### Avoid deep nesting
**Good:**
```asn1
Address ::= SEQUENCE {
street IA5String,
city IA5String,
zip IA5String
}
User ::= SEQUENCE {
name IA5String,
address Address -- Separate named type
}
```
**Avoid:** deeply nested anonymous inline SEQUENCEs — they generate
`/* nested sequence */` comments in codegen and are harder to reference.
## Code generation
### Use build scripts for automation
```rust
// build.rs
fn main() {
println!("cargo:rerun-if-changed=schemas/");
// use synta-codegen library API — see codegen/overview.md
}
```
### Version control generated code
Committing generated code to version control makes changes reviewable in PRs
and eliminates a generation step in CI/CD.
Add to `.gitignore` exceptions:
```gitignore
# Don't ignore generated code
!src/generated/*.rs
```
### Document generation commands
**Makefile:**
```makefile
.PHONY: generate
generate:
synta-codegen schemas/base.asn1 --crate-imports -o src/base.rs
synta-codegen schemas/user.asn1 --crate-imports -o src/user.rs
cargo fmt
```
## Project organization
### Recommended directory structure
```
my-protocol/
├── Cargo.toml
├── build.rs # Auto-generation (optional)
├── schemas/ # ASN.1 source files
│ ├── base-types.asn1
│ ├── messages.asn1
│ └── extensions.asn1
├── src/
│ ├── lib.rs
│ ├── base_types.rs # Generated
│ ├── messages.rs # Generated
│ └── utils.rs # Hand-written utilities
└── tests/
└── integration.rs
```
### Separate generated and manual code
```
src/
├── generated/ # All generated code
│ ├── mod.rs
│ ├── user.rs
│ └── auth.rs
└── manual/ # Hand-written code
├── mod.rs
├── client.rs
└── server.rs
```
### Use feature flags appropriately
```toml
[features]
default = ["std"]
std = []
# PATTERN constraint validation: add regex and once_cell to your crate,
# not to synta (synta does not have a "regex" feature)
regex-validation = ["dep:regex", "dep:once_cell"]
[dependencies]
synta = { version = "0.1" }
regex = { version = "1.10", optional = true }
once_cell = { version = "1.19", optional = true }
```
## Performance
### Use `new_unchecked` for trusted data
```rust
// Trusted source (already validated in DB)
let user_id = UserId::new_unchecked(db_value);
// Untrusted source (network input)
let user_id = UserId::new(untrusted_value)?;
```
### Cache validated instances
```rust
use std::collections::HashMap;
let mut port_cache: HashMap<u16, Port> = HashMap::new();
let port = port_cache.entry(raw_value)
.or_insert_with(|| Port::new(raw_value).expect("invalid port"));
```
### Minimize constraint complexity
**Good:**
```asn1
ValidPort ::= INTEGER (1024..65535)
```
**Avoid (slower to validate):**
```asn1
### Reuse encode buffers for hot paths
```rust
let mut buffer = Vec::with_capacity(1024);
for item in &items {
buffer.clear();
let mut encoder = Encoder::new(Encoding::Der);
encoder.encode(item)?;
buffer.extend_from_slice(&encoder.finish()?);
send(&buffer);
}
```
## Testing
### Test roundtrip encoding
```rust
use synta::{Decoder, Encoder, Encoding};
#[test]
fn test_user_roundtrip() {
let user = User {
id: Integer::from(42),
username: IA5String::new("alice".to_string()).unwrap(),
active: Boolean::new(true),
};
let mut encoder = Encoder::new(Encoding::Der);
encoder.encode(&user).unwrap();
let encoded = encoder.finish().unwrap();
let mut decoder = Decoder::new(&encoded, Encoding::Der);
let decoded: User = decoder.decode().unwrap();
assert_eq!(user, decoded);
}
```
### Test constraint validation
```rust
#[test]
fn test_constraint_validation() {
assert!(UserId::new(Integer::from(100)).is_ok());
assert!(UserId::new(Integer::from(0)).is_err()); // too small
assert!(UserId::new(Integer::from(1000000)).is_err()); // too large
}
```
### Test with real-world data
```rust
use synta::{Decoder, Encoding};
use synta_certificate::Certificate;
#[test]
fn test_parse_real_certificate() {
let cert_bytes = include_bytes!("../test-data/real-cert.der");
let mut decoder = Decoder::new(cert_bytes, Encoding::Der);
let cert: Certificate = decoder.decode()
.expect("Failed to parse real certificate");
// cert.tbs_certificate.version is Option<Integer>
assert_eq!(cert.tbs_certificate.version.as_ref().map(|v| v.as_i64().unwrap()), Some(2));
}
```
### Fuzz testing
```rust
#[test]
fn fuzz_decode_doesnt_panic() {
use synta::{Decoder, Encoding};
for _ in 0..1000 {
let random_data: Vec<u8> = (0..100).map(|_| rand::random()).collect();
let _ = Decoder::new(&random_data, Encoding::Der).decode::<User>();
// May error, but must not panic
}
}
```
## Maintenance
### Use semantic versioning for schema changes
**Breaking changes:**
- Removing fields from SEQUENCE
- Changing field types
- Removing variants from CHOICE
- Tightening constraints
**Non-breaking changes:**
- Adding OPTIONAL fields
- Adding CHOICE variants
- Adding DEFAULT values
- Loosening constraints
### Regenerate after schema updates
```bash
make generate
cargo test
git diff src/generated/ # Review changes
git commit -am "Regenerate after schema update"
```
## Security
### Validate all external input
```rust,ignore
use synta::{Decoder, Encoding};
fn handle_network_message(bytes: &[u8]) -> Result<User> {
let user: User = Decoder::new(bytes, Encoding::Der).decode()?;
// Additional business logic validation
if user.id.value() == 0 {
return Err("User ID cannot be zero".into());
}
Ok(user)
}
```
### Use constrained types for security-sensitive fields
```asn1
Password ::= OCTET STRING (SIZE (8..128)) -- Enforce min/max length
Age ::= INTEGER (0..150) -- No negative or absurd values
```
### Limit resource usage
Use `DecoderConfig` when parsing untrusted data to prevent resource exhaustion:
```rust
use synta::{Decoder, DecoderConfig, Encoding};
let bytes: &[u8] = &[];
let config = DecoderConfig {
max_depth: 16,
max_sequence_elements: 1_000,
max_length: 1 * 1024 * 1024, // 1 MiB
};
let mut decoder = Decoder::new_with_config(bytes, Encoding::Der, &config);
```
### Sanitize error messages
```rust
fn safe_error(e: synta::Error) -> String {
match e {
synta::Error::InvalidEncoding { .. } => "Invalid data format".to_string(),
_ => "Internal error".to_string(),
}
}
```
## Summary checklist
**Schema design:**
- [ ] Descriptive type names
- [ ] Constraints for validation
- [ ] Named values for constants
- [ ] Extensibility (CHOICE variants, OPTIONAL fields)
- [ ] No deep nesting
**Code generation:**
- [ ] Build scripts for automation
- [ ] Generated code in version control
- [ ] Documented generation commands
**Performance:**
- [ ] `new_unchecked` for trusted data
- [ ] Cached validated instances
- [ ] Reused encode buffers
**Testing:**
- [ ] Roundtrip encode/decode test
- [ ] Constraint validation tests
- [ ] Real-world data tests
**Security:**
- [ ] All external input validated
- [ ] Constrained types for security fields
- [ ] `DecoderConfig` limits for untrusted input
- [ ] Error messages sanitized