imazen / imazen/imageflow-dotnet
Schema-driven code generation for versioned fluent API (V2/V3)
- Dominant language
- C#
- Stars
- 166
- Forks
- 29
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
The hand-written fluent API (~2000 LOC of BuildNode methods, encoder presets, enum converters, DTO serialization) mirrors types defined in the native imageflow Rust source. As Imageflow 3 drastically expands the API surface, maintaining hand-written parity is untenable. We need schema-driven code generation for versioned namespaces (`Imageflow.Fluent.V2`, `Imageflow.Fluent.V3`).
## Architecture
### Two schema sources
**V2 (current API, OpenAPI):** The native library embeds an OpenAPI 3.1 schema (94 component schemas) accessible via `imageflow_context_send_json("v1/schema/openapi/latest/get", "{}")`. The `Node` enum is a `oneOf` with serde-style tagged variants. `EncoderPreset` is similar. All types derive `Serialize`/`Deserialize` + utoipa `ToSchema`.
**V3 (new API, zennode):** The [zennode](https://github.com/imazen/zennode) metadata system provides much richer per-node information than OpenAPI alone:
- **`NodeSchema`**: id, label, description, group, role, params, tags, version, compat_version, json_key, coalesce info, format preferences
- **`ParamDesc`**: name, label, description, kind (11 types: Float/Int/U32/Bool/Str/Enum/FloatArray/Color/Json/Object/TaggedUnion), unit, section, slider mapping, kv_keys (RIAPI aliases), since_version, visible_when, optional, json_name, json_aliases
- **`ParamKind`**: carries min/max/default/identity/step for numerics, variants for enums, sub-params for objects, tagged variants for unions
- **`NodeRole`**: Decode, Geometry, Orient, Resize, Filter, Composite, Analysis, Encode, Meta, Canvas
- **`NodeGroup`**: Decode, Encode, Tone, ToneRange, ToneMap, Color, Detail, Effects, Geometry, Layout, Canvas, Composite, Quantize, Analysis, Hdr, Raw, Auto, Other
Each zen crate (zenfilters, zenresize, etc.) defines nodes via `#[derive(Node)]` with rich annotations:
```rust
#[derive(Node, Clone, Debug, Default)]
#[node(id = "zenfilters.exposure", group = Tone, role = Filter)]
#[node(label = "Exposure")]
#[node(format(preferred = OklabF32, alpha = Skip))]
#[node(coalesce = "fused_adjust")]
pub struct Exposure {
/// Exposure compensation in stops (+/-)
#[param(range(-5.0..=5.0), default = 0.0, identity = 0.0, step = 0.1)]
#[param(unit = "EV", section = "Main", slider = Linear)]
pub stops: f32,
}
```
This metadata drives:
- **Type-safe fluent builders** with parameter validation (ranges, required vs optional)
- **Enum definitions** with labels and descriptions
- **RIAPI querystring mapping** (every parameter can have multiple kv_keys aliases)
- **Conditional visibility** rules for UI-aware APIs
- **Documentation** from descriptions, units, labels, groups
### Versioned namespaces
- `Imageflow.Fluent` — existing hand-written V2 API (stays as-is during transition)
- `Imageflow.Fluent.V2` — generated from OpenAPI schema, replaces hand-written code. Drops deprecated methods.
- `Imageflow.Fluent.V3` — generated from zennode metadata, adds all new zen operations. Partial classes allow hand-written shims for non-schema nodes.
`new ImageJob()` gets deprecated in favor of:
- `ImageJob.V2(backend)` with backend selection (Latest, ExactOrFail, Closest)
- `ImageJob.V3()`
### Generator tool: `Imageflow.CodeGen`
A .NET console app in this repo that:
1. Loads `imageflow` native library via existing P/Invoke bindings
2. Calls schema endpoints to extract OpenAPI schema and RIAPI vocabulary from the DLL itself
3. For V3: calls zennode registry endpoints (new) to get `NodeSchema` metadata for all registered nodes
4. Reads a sidecar `node_semantics.json` for V2 graph-semantic enrichment (node role classification, C# name mappings, convenience method definitions)
5. Emits `.g.cs` files committed to the repo (not ephemeral — reviewable in diffs)
Fallback: when native lib isn't available, fetch schema from CI artifacts.
## What gets generated
### Per-node (both V2 and V3)
- **`BuildNode` fluent methods** — one per node variant, returning `BuildNode` for filters, using `NodeWithCanvas()` for canvas ops
- **Parameter validation** — V3 gets range validation from `ParamKind` metadata
- **Convenience overloads** — e.g., `ConstrainWithin(w, h)` as shortcut for `Constrain(mode=Within)`
- **XML doc comments** from schema descriptions
### Shared types
- **Enums** (Filter, ConstraintMode, CompositingMode, PixelFormat, etc.) with snake_case JSON conversion
- **Config DTOs** (Constraint, ResampleHints, Watermark, SecurityOptions) with `ToJsonNode()` methods
- **Tagged unions** (Color, RoundCornersMode, EncoderPreset) following serde's externally-tagged pattern
- **Encoder preset classes** implementing `IEncoderPreset`
- **Response types** for all endpoints (JobResult, ImageInfo, decode/encode results)
- **RIAPI types** (QueryStringSchema, validation results)
- **Schema endpoint wrappers** (SchemaClient with typed access to schema/RIAPI/validation)
### What stays hand-written
- `ImageJob` (I/O lifecycle, graph assembly, Decode/CreateCanvas/BuildCommandString)
- `BuildNode` class core (`To()`, `NodeWithCanvas()`, `Branch()`, `Encode()`)
- `BuildItemBase`, `BuildEndpoint`, `FinishJobBuilder`
- All I/O abstractions (MemorySource, StreamSource, BytesDestination, etc.)
- P/Invoke bindings (NativeMethods, JobContext)
- `SchemaClient` wrapper class (uses generated types, manages JobContext lifecycle)
## Generated output structure
```
src/Imageflow/Generated/
V2/
Enums.g.cs
ConfigTypes.g.cs
ColorTypes.g.cs
SecurityTypes.g.cs
EncoderPresets.g.cs
BuildNode.Operations.g.cs
BuildNode.Convenience.g.cs
ResponseTypes.g.cs
V3/
Enums.g.cs
Nodes.g.cs -- all zen node fluent methods
NodeParams.g.cs -- parameter DTOs with validation
EncoderPresets.g.cs
BuildNode.Operations.g.cs
ResponseTypes.g.cs
Shared/
SchemaTypes.g.cs -- RIAPI schema types
RiapiTypes.g.cs -- RIAPI vocabulary
```
## Constraints
- **AOT/trimming compatible**: all serialization via `JsonObject`/`JsonNode` (no reflection). Response deserialization via `[JsonSerializable]` context. No Newtonsoft.Json.
- **Generated code is committed**: appears in diffs, reviewable. Not ephemeral build artifacts.
- **Backward compatible**: V2 generated code must produce identical JSON to the current hand-written code (verified by JSON equivalence tests).
- **Package validation**: `EnablePackageValidation` catches binary API breaks.
## Schema endpoints already available in native lib
| Endpoint | Returns |
|----------|---------|
| `v1/schema/openapi/latest/get` | Full OpenAPI 3.1 schema as JSON string |
| `v1/schema/riapi/latest/get` | RIAPI querystring schema |
| `v1/schema/riapi/latest/list_keys` | List of 100+ supported RIAPI keys |
| `v1/schema/riapi/latest/validate` | Validate a querystring against schema |
| `v1/schema/list-schema-endpoints` | List all schema endpoints |
| `v1/schema/json/latest/v1/all` | JSON schemas for all V1 endpoints (with `json-schema` feature) |
## Why not alternatives
- **Roslyn Source Generator**: can't read external 112KB JSON or call native P/Invoke at compile time. Opaque output, hard to diff.
- **T4 Templates**: deprecated, poor tooling, no AOT awareness.
- **OpenAPI Generator CLI**: produces `NodeOneOf10ExpandCanvas`-style naming, uses Newtonsoft.Json, no graph-semantic awareness. Fine for other language bindings (TypeScript/Ruby), wrong tool for the fluent API.
- **Hand-written V3**: too many nodes (zenfilters alone has 30+), each with rich typed parameters. The metadata is the code — generating from it is the only maintainable path.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the proposed Imageflow.CodeGen .NET console app, the existing P/Invoke bindings, and the listed schema endpoints. Trace how V2 OpenAPI and V3 zennode metadata would become committed files under src/Imageflow/Generated; done requires generated V2 JSON to match the hand-written API, with equivalence tests and package validation passing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, rust
- Domain
- build-system, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100