imazen / imazen/imageflow-dotnet

Schema-driven code generation for versioned fluent API (V2/V3)

Open
#67 0 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.