Skip to content

feat(bindings/dotnet)!: generate operation options from core - #8440

Open
Fatorin wants to merge 2 commits into
apache:mainfrom
Fatorin:dotnet-options-codegen
Open

Fatorin wants to merge 2 commits into
apache:mainfrom
Fatorin:dotnet-options-codegen

Conversation

@Fatorin

@Fatorin Fatorin commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Closes #8436.

Rationale for this change

The .NET options classes are hand-written and have fallen behind core. This generates them from core/core/src/types/options.rs as described in the issue. As noted in the issue comment, options cross the FFI as one generated #[repr(C)] struct per operation instead of JSON, which avoids serializing on both sides of every call.

What changes are included in this PR?

  • dev/src/generate/dotnet.rs maps each core option shape to flat FFI fields and renders the public C# *Options classes, their interop structs, and the Rust mirrors with their conversion into core options. The parser in dev/src/generate/core_options.rs stays binding-agnostic.
  • Each operation takes one pointer to its options struct, null for defaults. C# writes the struct and everything it points to into one buffer per call (stack when it fits, native memory otherwise); Rust only borrows it and copies into core options before the call returns.
  • if_not_changed passes the same OpendalMetadata struct the binding returns, rebuilt in Rust by code generated from core Metadata.
  • Constructor options cross as key and value arrays.
  • Fills the missing options: IfVersionMatch/IfVersionNotMatch, IfNotChanged, ContentLengthHint, SuffixLength, IfNoneMatch on delete, and new CopyOptions/RenameOptions.

Tested on Windows against memory, fs, and the Silo image CI uses (versioned and plain buckets; net8.0 and net10.0).

Are there any user-facing changes?

Yes. The options above are now available, and Copy/CopyAsync and Rename/RenameAsync accept options. The .NET guides document suffix reads, conditional copy and rename, and IfNotChanged.

Breaking changes

Affected packages: bindings/dotnet

Migration:

  • CopyAsync and RenameAsync take an optional options argument before the cancellation token. Pass the token by name, e.g. CopyAsync(from, to, cancellationToken: ct).
  • IOptions is now internal and NativeOptionsHandle is removed; neither had a use outside the binding.

AI Usage Statement

  • Harness: Claude Code
  • Model: Claude Opus 5.5
  • Effort: medium
  • Role: Implemented the generator and binding changes, ran the tests and benchmarks, and drafted this description. I reviewed the design and the code.

Render the .NET operation options from core/core/src/types/options.rs so
new core options reach the binding through `just generate dotnet`
instead of hand-written mirrors. For each options struct the generator
emits the public `*Options` class, a `#[repr(C)]` mirror with its C#
interop struct, and the conversion into core options.

Each operation receives its options as one pointer to that struct, null
for the defaults, so no signature grows past clippy's argument limit and
each operation crosses the FFI once instead of building and releasing a
native options handle around the call. Optional values follow the
metadata mirror's `*_has_value` convention, strings are null-terminated
UTF-8, and string maps are parallel key and value arrays.

C# writes the struct and everything it points to into one buffer per
call, on the stack when it fits and in native memory otherwise. Native
code only borrows it and copies everything into core options before the
call returns, so async operations keep no reference to it.

This fills the options the binding was missing:

- `IfVersionMatch`/`IfVersionNotMatch` on read, stat, write, delete,
  and copy.
- `IfNotChanged` on write, delete, and copy. The metadata crosses the
  FFI in full as the same `OpendalMetadata` struct the binding returns,
  written and rebuilt by code generated from core `Metadata`, so the
  binding never tracks which members a condition reads.
- `ContentLengthHint` on read and `SuffixLength` for suffix reads.
- `IfNoneMatch` on delete.
- New `CopyOptions` and `RenameOptions`, accepted by `Copy`/`CopyAsync`
  and `Rename`/`RenameAsync`.

Constructor options cross as key and value arrays. A null option value
is reported as ConfigInvalid naming its key.

The .NET guides document suffix reads, conditional copy and rename, and
`IfNotChanged`.

BREAKING CHANGE: `CopyAsync` and `RenameAsync` take an optional options
argument before the cancellation token, matching the other operations.
Pass the token by name, e.g. `CopyAsync(from, to, cancellationToken: ct)`.
`IOptions` is now internal and `NativeOptionsHandle` is removed; both
exposed interop details that callers had no use for.
@github-actions github-actions Bot added bindings/dotnet breaking-changes releases-note/feat The PR implements a new feature or has a title that begins with "feat" size:XXL This PR changes 1000+ lines, ignoring generated files. labels Oct 29, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bindings/dotnet breaking-changes releases-note/feat The PR implements a new feature or has a title that begins with "feat" size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

new feature: generate options from core for .NET and other bindings

1 participant