Versioning and compatibility

What a version number of TcgDex.CSharpSdk promises. This page is the contract; the reasoning behind each rule is in DECISIONS (#36 and #39).

The package follows Semantic Versioning 2.0. Before 1.0.0 the minor number carries breaking changes (0.8.0 and 0.9.0 did), and the changelog says so under each one. From 1.0.0 the rules below hold.

What counts as the public API

Everything in the pinned surface, PublicApi.approved.cs. A change to it fails a test, so it cannot happen by accident. Anything internal, and anything in a namespace that is not in that file, is not public API and can change in any release.

What each number means

Release May contain Never contains
Patch (1.0.1) Bug fixes, documentation, dependency updates that change no documented behaviour and take no new major version of a dependency New public members, a change to documented behaviour
Minor (1.1.0) New public types, new members on classes (never on an interface, below), new overloads, new optional properties on CardFilter, CardQuery, TcgDexOptions and the models, new enum values, [Obsolete] markings Removing or renaming a public member, dropping a target framework, raising the minimum C# version, changing a signature (adding a parameter, even an optional one, is a signature change: it breaks compiled callers, so it ships as a new overload), changing what a documented call returns for the same input
Major (2.0.0) Anything above, plus removals and signature changes, new members on an interface, dropping a target framework, and raising the minimum C# version

What counts as a fix. A change that makes a call do what its documentation says is a patch, even when a caller had come to depend on the departure; the changelog says so. A change to observable behaviour the documentation never promised (how many requests a call makes, an ordering, a default) is not a defect fix: it ships in a minor release with a changelog entry. Before 1.0.0 this is not yet in force, and 0.9.0 is the example of what it rules out as a patch: StreamAsync began honouring the query's page size, which changes the request count, and it shipped as a breaking change.

Binary compatibility is the firm promise; source compatibility is best effort. One caveat: the assembly version tracks the package version rather than being pinned to the major, so a .NET Framework consumer (who gets the netstandard2.0 asset) sees a new assembly version on every release, patches included, and may need a binding redirect for it. This is a deliberate choice (DECISIONS #39), not pinned. A new overload can make an existing call that passes null ambiguous, a parameter rename breaks named arguments, and a nullability change can add a warning. These are avoided where possible and named in the changelog when they happen. They are not on their own a reason for a major version.

New members on a model or options type must be optional: a settable property with a default that keeps today's behaviour, never required, which would break every caller that constructs one. A model gaining a property because the TCGdex API gained a field is a minor release. Adding a property also changes what the model's value equality compares, which is expected.

Enums grow. TcgDexErrorKind, ImageQuality and ImageFormat may gain a value in a minor release, so a switch over one needs a default arm. A value the API newly returns in an existing field (a new rarity, a new category) is not a change to the SDK at all: those fields are text, not enums, for exactly this reason.

Interfaces

ITcgDexClient, ICardResource, ICardSetResource, ISeriesResource, ICatalogResource and IRandomResource exist so that you can inject and fake the client in your tests. They are for consuming, but none of them will gain a member, or a parameter, within a major version: adding one would break every class that implements the interface by hand, and that is a major change. Growth goes elsewhere: new options go on the sealed types the interfaces take (CardFilter, CardQuery), and ICatalogResource.GetValuesAsync and GetNumberValuesAsync reach an enumeration endpoint the SDK has no named method for yet. If neither can carry a change, it waits for the next major version.

ITcgDexResponseCache is the other kind: a deliberate extension point (Redis, disk) that you are expected to implement, and it likewise will not gain members within a major version. The types it passes around are the same kind of extension point: CachedResponse may gain optional properties but never a required one, and TcgDexCacheOptions, which you may subclass to override GetTimeToLive, may gain members. A new member on a class you can subclass may share a name with one you declared (compiler warning CS0108); that is not treated as a break.

Target frameworks

The package ships net10.0, net8.0 and netstandard2.0. NuGet picks the closest match, and netstandard2.0 serves .NET Framework and the older .NET versions; the table in getting started lists which framework gets which asset.

A target framework is dropped only in a major version, never in a minor or a patch, even after its runtime is out of support.

The reason is that a drop is not free for the people on it. The API is the same on every asset, so nothing stops compiling: a consumer on the dropped framework falls back to the netstandard2.0 asset. But for net8.0 (out of support 2026-11-10) that consumer would lose:

  • immediate cancellation while a response body is streaming (it becomes best-effort);
  • PooledConnectionLifetime, so a long-lived client can hold a connection and miss a DNS change;
  • the trimming and Native AOT declaration, which the netstandard2.0 asset cannot make;
  • and would gain the extra package dependencies that asset carries.

An upgrade within a major version should not cost anyone that, so net8.0 ships for as long as 1.x does, whatever its runtime's support status. The first major version after its end of life is where it can go.

Language version

Constructing the models (object initializers in your code, tests and mocks) needs C# 11 or later (the compiler in the .NET 7 SDK and Visual Studio 2022 17.4 onward), because their public types have required members, which an older compiler refuses. Reading a model you received is unaffected. This holds for every asset, including netstandard2.0, which carries the attributes required needs. Raising that minimum is a major-version change, since it can stop a consumer's build.

Deprecation

A public member is marked [Obsolete] in a minor release, with a message naming the replacement, and is removed no earlier than the next major release. There is no promise about how many minor releases lie between the two, only that the mark comes first.

Which versions get fixes

Only the latest release of the current major version; when 2.0.0 ships, 1.x gets no more fixes. This is a single-maintainer project, and holding a second line open would mean fixes arriving late on both. A security report is handled as SECURITY.md describes, and the fix ships as a patch release of the latest version.

What is not promised

  • The TCGdex service itself. This SDK reads a third-party API. It can change a field or add a rate limit without notice, and the SDK follows it; see api-info for what has been observed.
  • The wire requests are not API: a minor release may send a different, equivalent request, for example a different page size. The changelog records a change a caller can observe.
  • Exact log text and exception messages. Log categories, TcgDexApiException.Kind and the exception types are API; the wording is not.
  • A serialized model. The property names on the models are the JSON names the SDK reads, not a format it promises to write. If you serialize a model yourself, a minor release may add a property to the output.

How this is enforced

The pinned API file fails a test on any change to the source-level surface. And every pack validates the package against the previous release, on every target framework: a removed or changed public member, or a member that exists on one framework only, fails the build. The one check that is still by hand is moving that baseline to the newest release when a new one is prepared.