Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 24 additions & 22 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
name: Deploy Documentation
name: Docs

on:
push:
branches: [ main ]
paths:
- 'docs/**'
- 'mkdocs.yml'
- 'zensical.toml'
- '.github/workflows/docs.yml'
- 'pyproject.toml'
- 'uv.lock'
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches: [ main ]
types: [ opened, synchronize, reopened, ready_for_review ]
paths:
- 'docs/**'
- 'mkdocs.yml'
- 'zensical.toml'
- '.github/workflows/docs.yml'
- 'pyproject.toml'
- 'uv.lock'

permissions:
contents: read
Expand All @@ -22,46 +26,44 @@ permissions:

concurrency:
group: "pages"
cancel-in-progress: false
cancel-in-progress: true

jobs:
build:
if: github.event.pull_request.draft == false
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false

runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install pngquant
run: sudo apt-get update && sudo apt-get install -y pngquant

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
python-version-file: "pyproject.toml"

- name: Cache dependencies
uses: actions/cache@v4
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
key: mkdocs-material-${{ hashFiles('requirements.txt') }}
path: ~/.cache/pip
restore-keys: |
mkdocs-material-
enable-cache: true

- name: Install dependencies
run: |
pip install mkdocs-material
pip install mkdocs-minify-plugin
- name: Install the project
run: uv sync --locked --all-extras --dev

- name: Setup Pages
id: pages
uses: actions/configure-pages@v4
uses: actions/configure-pages@v5

- name: Build documentation
run: |
mkdocs build --clean
run: uv run zensical build --clean

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v4
with:
path: site

Expand All @@ -75,4 +77,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v4
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionPrefix>1.1.0</VersionPrefix>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/layeredcraft/optimized-enums</RepositoryUrl>
<RepositoryType>git</RepositoryType>
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.201" />
<PackageVersion Include="Scriban" Version="7.0.6" />
<PackageVersion Include="System.Text.Json" Version="10.0.5" />
<PackageVersion Include="Verify.SourceGenerators" Version="2.5.0" />
<PackageVersion Include="Verify.XunitV3" Version="31.13.5" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
Expand Down
8 changes: 6 additions & 2 deletions LayeredCraft.OptimizedEnums.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
<File Path="docs\changelog.md" />
<File Path="docs\contributing.md" />
<File Path="docs\index.md" />
<File Path="mkdocs.yml" />
<File Path="requirements.txt" />
<File Path="zensical.toml" />
</Folder>
<Folder Name="/docs/advanced/">
<File Path="docs\advanced\aot-trimming.md" />
Expand Down Expand Up @@ -32,6 +31,7 @@
</Folder>
<Folder Name="/docs/usage/">
<File Path="docs\usage\defining-enums.md" />
<File Path="docs\usage\json-serialization.md" />
<File Path="docs\usage\lookups.md" />
<File Path="docs\usage\string-values.md" />
</Folder>
Expand All @@ -46,15 +46,19 @@
<File Path="Directory.Build.props" />
<File Path="Directory.Packages.props" />
<File Path="global.json" />
<File Path="pyproject.toml" />
<File Path="uv.lock" />
<File Path="README.md" />
</Folder>
<Folder Name="/src/">
<Project Path="src/LayeredCraft.OptimizedEnums/LayeredCraft.OptimizedEnums.csproj" />
<Project Path="src/LayeredCraft.OptimizedEnums.Generator/LayeredCraft.OptimizedEnums.Generator.csproj" />
<Project Path="src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/LayeredCraft.OptimizedEnums.SystemTextJson.Generator.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/LayeredCraft.OptimizedEnums.Tests/LayeredCraft.OptimizedEnums.Tests.csproj" />
<Project Path="tests/LayeredCraft.OptimizedEnums.Generator.Tests/LayeredCraft.OptimizedEnums.Generator.Tests.csproj" />
<Project Path="tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests.csproj" />
<Project Path="tests/LayeredCraft.OptimizedEnums.Benchmarks/LayeredCraft.OptimizedEnums.Benchmarks.csproj" />
</Folder>
</Solution>
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
| Package | NuGet | Downloads |
|---------|-------|-----------|
| **LayeredCraft.OptimizedEnums** | [![NuGet](https://img.shields.io/nuget/v/LayeredCraft.OptimizedEnums.svg)](https://www.nuget.org/packages/LayeredCraft.OptimizedEnums) | [![Downloads](https://img.shields.io/nuget/dt/LayeredCraft.OptimizedEnums.svg)](https://www.nuget.org/packages/LayeredCraft.OptimizedEnums/) |
| **LayeredCraft.OptimizedEnums.SystemTextJson** | _coming soon_ | |
| **LayeredCraft.OptimizedEnums.SystemTextJson** | [![NuGet](https://img.shields.io/nuget/v/LayeredCraft.OptimizedEnums.SystemTextJson.svg)](https://www.nuget.org/packages/LayeredCraft.OptimizedEnums.SystemTextJson) | [![Downloads](https://img.shields.io/nuget/dt/LayeredCraft.OptimizedEnums.SystemTextJson.svg)](https://www.nuget.org/packages/LayeredCraft.OptimizedEnums.SystemTextJson/) |
| **LayeredCraft.OptimizedEnums.EFCore** | _coming soon_ | |
| **LayeredCraft.OptimizedEnums.Dapper** | _coming soon_ | |
| **LayeredCraft.OptimizedEnums.AutoFixture** | _coming soon_ | |
Expand Down Expand Up @@ -88,6 +88,37 @@ Benchmarks run on Apple M3 Max, .NET 9.0.8, BenchmarkDotNet v0.14.0.

All lookups are O(1) via statically-cached dictionaries. `Count` is a compile-time constant.

## JSON Serialization

Add `LayeredCraft.OptimizedEnums.SystemTextJson` for source-generated, zero-reflection `JsonConverter` support. One package is all you need — it pulls in the core package automatically:

```bash
dotnet add package LayeredCraft.OptimizedEnums.SystemTextJson
```

Decorate your class with `[OptimizedEnumJsonConverter]` and the generator emits a concrete, AOT-safe converter and wires it up via `[JsonConverter]`:

```csharp
using LayeredCraft.OptimizedEnums;
using LayeredCraft.OptimizedEnums.SystemTextJson;

[OptimizedEnumJsonConverter(OptimizedEnumJsonConverterType.ByName)]
public sealed partial class OrderStatus : OptimizedEnum<OrderStatus, int>
{
public static readonly OrderStatus Pending = new(1, nameof(Pending));
public static readonly OrderStatus Paid = new(2, nameof(Paid));
public static readonly OrderStatus Shipped = new(3, nameof(Shipped));

private OrderStatus(int value, string name) : base(value, name) { }
}
```

```json
{ "status": "Pending" }
```

Two strategies are available: `ByName` (serializes as the member name string) and `ByValue` (serializes as the underlying value). See the [JSON Serialization docs](https://layeredcraft.github.io/optimized-enums/usage/json-serialization/) for full details.

## Installation

```bash
Expand Down
41 changes: 41 additions & 0 deletions docs/advanced/diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,47 @@ public OrderStatus(int value, string name) : base(value, name) { }
#pragma warning restore OE0101
```

## SystemTextJson Diagnostics

The `LayeredCraft.OptimizedEnums.SystemTextJson` generator emits diagnostics with the `OE2xxx` prefix.

### OE2001 — Not an OptimizedEnum

**Message:** `The class '{0}' must inherit from OptimizedEnum<TEnum, TValue> to use [OptimizedEnumJsonConverter]`

**Cause:** `[OptimizedEnumJsonConverter]` was applied to a class that does not inherit from `OptimizedEnum<TEnum, TValue>`.

**Fix:** Remove the attribute, or make the class inherit from `OptimizedEnum<TEnum, TValue>`.

### OE2002 — Must Be Partial

**Message:** `The class '{0}' must be declared as partial for [OptimizedEnumJsonConverter] source generation`

**Cause:** A class decorated with `[OptimizedEnumJsonConverter]` is missing the `partial` keyword. The generator cannot stamp the `[JsonConverter]` attribute onto the class.

Comment thread
ncipollina marked this conversation as resolved.
**Fix:**
```csharp
// Before
[OptimizedEnumJsonConverter(OptimizedEnumJsonConverterType.ByName)]
public sealed class OrderStatus : OptimizedEnum<OrderStatus, int> { ... }

// After
[OptimizedEnumJsonConverter(OptimizedEnumJsonConverterType.ByName)]
public sealed partial class OrderStatus : OptimizedEnum<OrderStatus, int> { ... }
```

### OE2003 — Unknown Converter Type

**Message:** `The class '{0}' specifies an unknown OptimizedEnumJsonConverterType value '{1}'; valid values are ByName (0) and ByValue (1)`

**Cause:** An explicit integer cast was used to pass an undefined `OptimizedEnumJsonConverterType` value to `[OptimizedEnumJsonConverter]`.

**Fix:** Use only the defined enum members:
```csharp
[OptimizedEnumJsonConverter(OptimizedEnumJsonConverterType.ByName)] // or ByValue
public sealed partial class OrderStatus : OptimizedEnum<OrderStatus, int> { ... }
```

## Generator Not Running?

If you add the package but see no generated members, check:
Expand Down
7 changes: 7 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]

### Added
- `LayeredCraft.OptimizedEnums.SystemTextJson` package — source-generated, zero-reflection `JsonConverter` support
- `[OptimizedEnumJsonConverter]` attribute with `ByName` and `ByValue` strategies
- Emits a concrete non-generic `JsonConverter<T>` for each decorated class — no runtime reflection, full AOT/NativeAOT compatibility
- Stamps `[JsonConverter(typeof(...))]` on a generated partial class stub — no manual `JsonSerializerOptions` registration required
- Declares `LayeredCraft.OptimizedEnums` as a NuGet dependency — only one package reference needed
- `OE2001` diagnostic for classes not inheriting `OptimizedEnum<TEnum, TValue>`
- `OE2002` diagnostic for classes missing `partial`
- `OptimizedEnum<TEnum>` single-parameter base class for `int`-valued enums
- Inheritance-based generation trigger — `[OptimizedEnum]` attribute no longer required
- `Microsoft.CSharp.dll` bundled in `analyzers/dotnet/cs/` for Scriban dynamic dispatch
Expand Down
24 changes: 24 additions & 0 deletions docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,30 @@ The package bundles two assemblies:

Both are delivered automatically by the single NuGet package. No separate runtime package reference is needed.

## Optional: JSON Serialization

To add source-generated `System.Text.Json` converter support, install the SystemTextJson package. It declares the core package as a dependency, so only one `dotnet add` is needed:

=== ".NET CLI"

```bash
dotnet add package LayeredCraft.OptimizedEnums.SystemTextJson
```

=== "Package Manager"

```powershell
Install-Package LayeredCraft.OptimizedEnums.SystemTextJson
```

=== "PackageReference"

```xml
<PackageReference Include="LayeredCraft.OptimizedEnums.SystemTextJson" Version="x.x.x" />
```

See [JSON Serialization](../usage/json-serialization.md) for usage details.

## Verifying the Installation

After adding the package, define a type that inherits from `OptimizedEnum<TEnum, TValue>` and declare it `partial`. Build the project — the generator runs during compilation and produces the lookup members. You can inspect the generated output under `obj/` or via your IDE's "Go to definition" on any generated method.
Expand Down
27 changes: 27 additions & 0 deletions docs/getting-started/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,35 @@ public sealed partial class Color : OptimizedEnum<Color, string>
}
```

## 6. JSON Serialization

To serialize/deserialize your enum with `System.Text.Json`, install the SystemTextJson package (it pulls in the core package automatically):

```bash
dotnet add package LayeredCraft.OptimizedEnums.SystemTextJson
```

Then decorate your class with `[OptimizedEnumJsonConverter]`:

```csharp
using LayeredCraft.OptimizedEnums.SystemTextJson;

[OptimizedEnumJsonConverter(OptimizedEnumJsonConverterType.ByName)]
public sealed partial class OrderStatus : OptimizedEnum<OrderStatus, int>
{
public static readonly OrderStatus Pending = new(1, nameof(Pending));
public static readonly OrderStatus Paid = new(2, nameof(Paid));
public static readonly OrderStatus Shipped = new(3, nameof(Shipped));

private OrderStatus(int value, string name) : base(value, name) { }
}
```

`OrderStatus` now serializes as `"Pending"` / `"Paid"` / `"Shipped"` with no manual converter registration. See [JSON Serialization](../usage/json-serialization.md) for full details on `ByName` vs `ByValue` and AOT safety.

## Next Steps

- [Core Concepts — How It Works](../core-concepts/how-it-works.md)
- [Usage — Defining Enums](../usage/defining-enums.md)
- [Usage — JSON Serialization](../usage/json-serialization.md)
- [API Reference — Generated Members](../api-reference/generated-members.md)
Loading
Loading