Contributing to AvroSharp
Ground rules
Implement from the specification. AvroSharp is a clean-room implementation of the Apache Avro™ specification under the MIT license. Apache.Avro (Apache-2.0) may be read for design ideas and used as a test oracle, but its code must not be copied or ported into
src/. Code derived from Chr.Avro (MIT) must keep its copyright notice inTHIRD-PARTY-NOTICES.md.No reflection on serialization paths. Typed serialization is produced by source generators and must stay Native AOT and trimming compatible.
Performance is a feature. AvroSharp must be faster than Apache.Avro, and allocate no more, on every scenario in the benchmark suite. The one exception, single-value writes on Zen+ CPUs, is in design.md §11. Benchmarks run locally only, on request, on an idle machine (never in CI):
dotnet run -c Release --project bench/AvroSharp.Benchmarks -f net10.0 -- --filter '*' --runtimes net8.0 net9.0 net10.0 --memory --gateCodecs use fully managed libraries only. No native binaries and no P/Invoke.
Tests must exercise the product path. Write the test, revert the fix, and confirm the test fails.
Building and testing
dotnet build -c Release
dotnet test --solution AvroSharp.slnx -c Release -f net10.0
Locally, net10.0 is enough while working. CI runs net8.0, net9.0 and net10.0 on Linux and Windows, x64 and Arm64, and on Windows also net481, which tests the netstandard2.0 builds on .NET Framework (the add-ons too, except AvroSharp.KafkaFlow, which has no .NET Framework build; their test projects register the generated types first, as a .NET Framework application does). Run -f net481 locally too when a change touches the netstandard code paths.
AvroSharp.Confluent's, AvroSharp.KafkaFlow's and AvroSharp.Aws.Glue's tests need more:
- Redpanda: the tests against a real broker and registry need Docker with Linux containers. They are
[Explicit], so they run only when a filter names them:dotnet test --project tests/AvroSharp.Confluent.Tests --treenode-filter "/*/*/RedpandaTests/*", and AvroSharp.KafkaFlow's with--project tests/AvroSharp.KafkaFlow.Tests --treenode-filter "/*/*/KafkaFlowRedpandaTests/*". Otherwise they are neither run nor listed. - moto: AvroSharp.Aws.Glue's tests against moto, an AWS emulator in Docker, are
[Explicit]too:dotnet test --project tests/AvroSharp.Aws.Glue.Tests --treenode-filter "/*/*/MotoGlueTests/*". - Confluent's lowest supported version: the package depends on Confluent.SchemaRegistry [2.14.0, 3.0.0). The build uses the newest by default; to test against 2.14.0, build and test with
-p:ConfluentVersion=2.14.0(and an-p:ArtifactsPathof its own, to keep the main build). CI does this on the x64 Linux runner. - The other add-ons' lowest versions: likewise
-p:KafkaFlowVersion=4.0.0(withConfluentVersion=2.14.0),-p:AzureSchemaRegistryVersion=1.2.0and-p:AwsGlueVersion=4.0.0. CI runs all of them in one step.
CI runs them with the rest of the solution, and the Redpanda and moto tests in a step of their own on the x64 Linux runner. The Confluent and KafkaFlowEvents samples need a broker and a registry too: docker compose up -d --wait in samples/Confluent starts Redpanda for both.
Formatting is not checked in CI. To check or fix it locally:
dotnet format AvroSharp.slnx --verify-no-changes # check
dotnet format AvroSharp.slnx # fix
Native AOT smoke test:
dotnet publish tests/AvroSharp.AotSmoke -c Release -r win-x64 # or linux-x64
dotnet publish tests/AvroSharp.AotSmoke.Addons -c Release -r win-x64 # the add-on packages
The documentation site, with the same DocFX version and broken-link check as the docs workflow:
dotnet tool restore
dotnet tool run docfx docfx.json --serve # http://localhost:8080
Dev container
.devcontainer/ builds and tests as the Linux CI job does, with no local .NET setup. It has:
- SDKs: Ubuntu 24.04 (as
ubuntu-latest) with the .NET 8, 9 and 10 SDKs; - Native AOT: its prerequisites;
- Tools: the repository's local tools (DocFX, ReportGenerator);
- Environment: CI's variables;
- Cache: the NuGet cache in a volume, so rebuilding the container doesn't download it again.
Open the repository in it with VS Code (Dev Containers: Reopen in Container) or Rider, or create a GitHub Codespace from the repository page (Code > Codespaces). On Windows, use Dev Containers: Clone Repository in Container Volume, or a clone inside WSL. A clone on the Windows file system is slow when bind-mounted, and its permissions can break the build. Then run the Linux CI job's steps:
build/ci-local.sh
It runs, in CI's order:
- restore and build;
- the tests on net8.0, net9.0 and net10.0 with coverage;
- the Redpanda and moto tests, when Docker is available (the dev container has no Docker inside it, so there they are left out);
- the add-ons' tests against the lowest versions of their dependencies (Confluent 2.14.0, KafkaFlow 4.0.0, Azure.Data.SchemaRegistry 1.2.0, AWSSDK.Glue 4.0.0);
- the tests without hardware intrinsics;
- the coverage check (the summary is in
artifacts/coverage/SummaryGithub.md); - the samples;
- the Native AOT smoke tests, of the core packages and of the add-ons;
- pack, and the package consumers.
Behind a proxy that intercepts HTTPS, the image build and restores fail with certificate errors, because the container doesn't trust the proxy's root certificate the way the host does. Add the certificate in a local copy of the Dockerfile, and don't commit it: COPY proxy-root.crt /usr/local/share/ca-certificates/ and then RUN update-ca-certificates, right after FROM.
It takes about as long as the CI job. Passing it means passing the Linux CI job on the container's architecture: ubuntu-latest on x64, or ubuntu-24.04-arm on an Arm64 host such as an Apple silicon Mac.
It doesn't cover:
- Windows and .NET Framework: the Windows jobs and the net481 tests run in CI only.
- The other architecture: the container runs on the host's architecture, so only CI runs both.
- Fuzzing: the nightly libFuzzer runs are in
fuzz/README.md. The random-schema test runs with the other tests, on 100 schemas. - Benchmarks: these need a quiet, dedicated machine, not a container.
Public API
Every package's public API, the add-ons' (AvroSharp.Confluent, AvroSharp.KafkaFlow, AvroSharp.Azure.SchemaRegistry, AvroSharp.Aws.Glue and AvroSharp.Aws.Glue.Kafka) included, follows semantic versioning from 1.0.0: a breaking change waits for the next major version. The API reference shows it.
Public API is tracked with Microsoft.CodeAnalysis.PublicApiAnalyzers. Add new members to PublicAPI.Unshipped.txt (members that exist only on .NET 8 and later go in src/AvroSharp/PublicAPI/net8.0/); the build fails otherwise. Package validation also compares each package with its 1.0.0 release on nuget.org (PackageValidationBaselineVersion in src/Directory.Build.props), so a break fails the pack: it waits for 2.0.
Workflow
Branch, open a pull request, and merge (squash) once CI is green. main is never pushed to directly.
Releasing
Versions come from git tags through MinVer: v1.2.3, or v1.2.3-alpha.1 for a pre-release.
- Prepare a release pull request, and merge it:
- in
CHANGELOG.md, rename## [Unreleased]to## [1.2.3] - YYYY-MM-DD(the exact version, pre-release suffix included), start a new empty[Unreleased]section, and update the links at the bottom:[Unreleased]compares the new tag withHEAD, and the new version links to its release; - move the API listings to Shipped: the lines of each
PublicAPI.Unshipped.txt(andsrc/AvroSharp/PublicAPI/net8.0/) go into thePublicAPI.Shipped.txtbeside it, and the rules inAnalyzerReleases.Unshipped.mdgo intoAnalyzerReleases.Shipped.mdunder## Release 1.2.3, so a later change to shipped API is reported; - check that the package READMEs' status lines and
docs/roadmap.mdstill describe the release: package READMEs are packed into the immutable.nupkg.
- in
- Rehearse: run the Release workflow manually on
mainwithpublishoff. It tests on Linux and Windows (including net481), packs, and checks the release notes, but pushes nothing. - Tag the merge commit and push the tag:
git tag v1.2.3 && git push origin v1.2.3. The workflow tests again, packs, pushes the packages and symbols to nuget.org, and creates the GitHub release from the CHANGELOG section, marked as a pre-release when the version has a suffix. It fails if the tag and the packed version differ, or if the CHANGELOG has no section for the version.
The push uses nuget.org's trusted publishing, so no API key is stored. It needs, once:
- on nuget.org: a trusted publishing policy for the nuget.org account that owns the
AvroSharp*packages (zcsizmadia). The policy names repository ownerAvroSharp(the organization; the repository moved there fromzcsizmadiaon 2026-09-30), repositoryAvroSharp, workflow filerelease.ymland environmentnuget. - on GitHub: an environment named
nuget(Settings → Environments; add required reviewers there to approve each publish), and a repository variableNUGET_USERholding that nuget.org account or organization name.
The workflow asks GitHub for an OIDC token, and NuGet/login exchanges it for a key that is valid for about an hour.