# Rust coding guidelines

* Prioritize code correctness and clarity. Speed and efficiency are secondary priorities unless otherwise specified
* Do not write organizational comments that summarize the code. Comments should only be written to explain "why" the code is written in some way when the reason is tricky or non-obvious
* Prefer implementing functionality in existing files unless it is a new logical component. Avoid creating many small files
* Avoid using functions that panic like `unwrap()`, instead use mechanisms like `?` to propagate errors
* Be careful with operations like indexing which may panic if the indexes are out of bounds
* Never silently discard errors with `let _ =` on fallible operations. Always handle errors appropriately:
  - Propagate errors with `?` when the calling function should handle them
  - Use proper error logging when you need to ignore errors but want visibility
  - Use explicit error handling with `match` or `if let Err(...)` when you need custom logic
* When implementing async operations that may fail, ensure errors propagate appropriately with meaningful context
* Never create files with `mod.rs` paths - prefer `src/some_module.rs` instead of `src/some_module/mod.rs`

# Clorinde Architecture

Clorinde is a codegen tool that transforms PostgreSQL queries into type-safe Rust code. The architecture centers around parsing SQL queries, validating them against a live database, and generating corresponding Rust interfaces.

## Config

`Config` is the central configuration type that controls all aspects of code generation. It contains settings for:

* `queries: PathBuf` - Directory containing SQL query files
* `destination: PathBuf` - Where to write generated code
* `sync: bool` and `async: bool` - Whether to generate sync/async variants
* `types: Types` - Custom type mappings for PostgreSQL types
* `manifest: cargo_toml::Manifest` - Complete Cargo.toml configuration for generated crate

The `Config` can be created from a TOML file via `Config::from_file()` or built programmatically using `Config::builder()`. Example:

```rust
let config = Config::builder()
    .queries("sql/")
    .destination("generated/")
    .async(true)
    .build();
```

## Query

`Query` represents a parsed SQL query with its metadata. Key fields:

* `name: Span<String>` - The query identifier from `--! query_name` annotations
* `sql_str: String` - The cleaned SQL query text
* `bind_params: Vec<Span<String>>` - Named parameters like `:user_id`
* `param: QueryDataStruct` - Input parameter structure definition
* `row: QueryDataStruct` - Output row structure definition

Queries are parsed from SQL files that contain special annotations:

```sql
--: User(id, name?, email)

--! get_user(name?, email) : User
SELECT id, name, email FROM users WHERE name = :name;
```

## ClorindeType

`ClorindeType` represents the mapping between PostgreSQL types and Rust types:

* `Simple { pg_ty, rust_name, is_copy }` - Basic type mappings like `INT4 -> i32`
* `Array { inner }` - PostgreSQL arrays like `INT4[] -> Vec<i32>`
* `Domain { pg_ty, inner }` - PostgreSQL domains (essentially type aliases)
* `Custom { pg_ty, struct_name, is_copy, is_params }` - Custom types (enums, composites)

Key methods:
* `is_copy()` - Whether the type implements `Copy`
* `is_ref()` - Whether the type needs lifetime parameters
* `own_ty()` - Generate the owned Rust type string
* `param_ty()` - Generate the parameter type string

## PreparedQuery and PreparedField

After parsing and validation, queries are converted to `PreparedQuery`:

* `ident: Ident` - Normalized identifier (handles Rust keywords)
* `param: Option<(usize, Vec<usize>)>` - Parameter struct info
* `row: Option<(usize, Vec<usize>)>` - Row struct info
* `sql: String` - Final SQL query

`PreparedField` represents individual fields in parameter/row structs:

* `ident: Ident` - Field identifier
* `ty: Rc<ClorindeType>` - Field type information
* `is_nullable: bool` - Whether the field is `Option<T>`
* `attributes: Vec<String>` - Custom derive attributes

## GenCtx

`GenCtx` (Generation Context) controls how code is generated:

* `hierarchy: ModCtx` - Which module we're generating (Types, Queries, etc.)
* `is_async: bool` - Generate async or sync code
* `gen_derive: bool` - Include serialization derives

Used to generate context-appropriate type paths:

```rust
ctx.custom_ty_path("public", "User") // -> "crate::types::User" in queries
```

## Error Handling

Clorinde uses structured error handling with `miette` for user-friendly diagnostics:

* `Error` - Main error enum that wraps all error types transparently
* Each module has its own error type (e.g., `parser::error::Error`)
* Errors include source spans for precise error location reporting
* Use `error.report()` to get formatted error messages with source highlights

Example error creation:

```rust
Err(Box::new(Error::DuplicateFieldNullity {
    src: info.into(),
    name: field_name.clone(),
    first: first_span,
    second: second_span,
}))
```

## Module and ModuleInfo

`Module` represents a parsed query file:

* `info: ModuleInfo` - File metadata (path, source content)
* `types: Vec<TypeAnnotation>` - Named type definitions
* `queries: Vec<Query>` - SQL queries in the file

`ModuleInfo` contains source information needed for error reporting:

* `path: PathBuf` - File path
* `content: String` - File contents
* Used to create `miette::NamedSource` for error diagnostics

## Type Registration

`TypeRegistrar` manages the mapping between PostgreSQL and Rust types:

* Introspects database schema to discover custom types
* Builds `ClorindeType` instances for each PostgreSQL type
* Handles type dependencies and generates appropriate Rust code
* Supports custom type mappings via configuration

## Validation

The validation phase ensures queries are correct:

* `duplicate_nullable_ident()` - Check for duplicate field names
* `query_name_already_used()` - Ensure unique query names
* `nullable_column_name()` - Verify nullable annotations match actual columns
* All validation functions return `Result<(), Box<Error>>` for consistent error handling

## Code Generation Pipeline

The main generation flow:

1. **Read** - `read_query_modules()` reads SQL files from disk
2. **Parse** - `parse_query_module()` parses annotations and SQL
3. **Validate** - Check for conflicts and verify against database schema
4. **Prepare** - `prepare()` creates `PreparedQuery` instances with type information
5. **Generate** - `codegen::gen()` produces Rust code
6. **Persist** - Write generated code to destination directory

## Development Workflow

When working on Clorinde itself, you'll often need to update the generated code used in tests and examples after making changes to the code generation logic.

**Updating Generated Code:**

```bash
cargo run --package test_integration -- --apply-codegen
```

This command regenerates all the test code using the current version of Clorinde, ensuring that tests and examples stay in sync with code generation changes. Run this whenever you modify:

* Type mapping logic
* SQL parsing rules
* Code generation templates
* Error handling behavior
