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
32 changes: 32 additions & 0 deletions .github/RELEASE_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!--
Copy the content below into the "Describe this release" field when drafting
a new GitHub Release. GitHub does not support auto-populating Release
bodies; this file is a manual template.

All three packages ship at the same version (lockstep). If a package has
no changes in this release, keep its section and write "No changes;
rebuilt against core vX.Y.Z." so consumers can tell at a glance.

Delete sections that don't apply (Breaking changes, Upgrade notes) — they
are optional.
-->

## Kalicz.StrongTypes

-

## Kalicz.StrongTypes.EfCore

-

## Kalicz.StrongTypes.FsCheck

-

## Breaking changes

-

## Upgrade notes

-
60 changes: 40 additions & 20 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ on:
pull_request:
branches:
- main
release:
types: [published]

permissions:
contents: read
Expand Down Expand Up @@ -48,36 +50,54 @@ jobs:
**/TestResults/**/*.trx
retention-days: 5

- name: Pack
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: |
dotnet pack ./src/StrongTypes/StrongTypes.csproj -c $config -o out
dotnet pack ./src/StrongTypes.EfCore/StrongTypes.EfCore.csproj -c $config -o out

- name: Upload NuGet package
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
uses: actions/upload-artifact@v7
with:
name: nuget-package
path: out/*.nupkg
retention-days: 1

publish:
needs: build
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
# Gate on main: a Release drafted through the UI carries the selected
# branch in target_commitish. Only releases targeted at main publish.
if: github.event_name == 'release' && github.event.release.target_commitish == 'main'
runs-on: ubuntu-latest
environment: Nuget.org

env:
config: 'Release'
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true

steps:
- name: Download NuGet package
uses: actions/download-artifact@v8
with:
name: nuget-package
- name: Checkout
uses: actions/checkout@v6

- name: Setup .NET 10
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x

- name: Parse version from tag
id: version
run: |
TAG="${GITHUB_REF_NAME}"
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"

# GeneratePackageOnBuild=true in the csproj files means `dotnet build`
# produces the nupkg as a side-effect; no separate `dotnet pack` step
# needed. -p:Version cascades into referenced projects (EfCore's
# ProjectReference to core), so EfCore's nuspec pins its core
# dependency to the same version.
# Build the whole solution; non-publishable projects (tests, internal
# source generators, analyzers) set IsPackable=false so they produce no
# nupkg. GeneratePackageOnBuild=true on publishable csprojs means the
# build itself emits .nupkg files into PackageOutputPath. New publishable
# packages are picked up automatically by setting the same two properties.
- name: Build and pack
env:
NOTES: ${{ github.event.release.body }}
VERSION: ${{ steps.version.outputs.version }}
run: |
dotnet build StrongTypes.slnx \
-c $config \
-p:Version="$VERSION" \
-p:PackageReleaseNotes="$NOTES" \
-p:PackageOutputPath="$PWD/out"

- name: Publish packages
run: dotnet nuget push ./*.nupkg --skip-duplicate --source nuget.org --api-key ${{secrets.NUGET_TOKEN}}
run: dotnet nuget push ./out/*.nupkg --source nuget.org --api-key ${{ secrets.NUGET_TOKEN }}
3 changes: 3 additions & 0 deletions StrongTypes.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
<Folder Name="/EF Core package/">
<Project Path="src/StrongTypes.EfCore/StrongTypes.EfCore.csproj" />
</Folder>
<Folder Name="/FsCheck package/">
<Project Path="src/StrongTypes.FsCheck/StrongTypes.FsCheck.csproj" />
</Folder>
<Folder Name="/Main Package/">
<Project Path="src/StrongTypes.Analyzers/StrongTypes.Analyzers.csproj" />
<Project Path="src/StrongTypes.Analyzers.Tests/StrongTypes.Analyzers.Tests.csproj" />
Expand Down
5 changes: 1 addition & 4 deletions src/StrongTypes.EfCore/StrongTypes.EfCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>CS1591</NoWarn>

<Version>0.3.0</Version>
<AssemblyVersion>0.3.0</AssemblyVersion>
<FileVersion>0.3.0</FileVersion>
<Version>0.0.0-dev</Version>
<PackageId>Kalicz.StrongTypes.EfCore</PackageId>
<Authors>KaliCZ</Authors>
<Copyright>Copyright © 2026 KaliCZ</Copyright>
Expand All @@ -21,7 +19,6 @@
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/KaliCZ/StrongTypes.git</RepositoryUrl>
<PackageReadmeFile>readme.md</PackageReadmeFile>
<PackageReleaseNotes>0.3.0: Initial release. Ships NonEmptyString and numeric-wrapper value converters. Single-call configuration via optionsBuilder.UseStrongTypes().</PackageReleaseNotes>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
#nullable enable

using System.Collections.Generic;
using FsCheck;
using FsCheck.Fluent;

namespace StrongTypes.Tests;
namespace StrongTypes.FsCheck;

/// <summary>
/// Shared FsCheck arbitraries for the test project. Reference via
/// Shared FsCheck arbitraries for Kalicz.StrongTypes. Reference via
/// <c>[Properties(Arbitrary = new[] { typeof(Generators) })]</c>
/// on a test class. Add new arbitraries here rather than creating
/// per-feature generator classes.
/// on a test class.
/// </summary>
public static class Generators
{
Expand Down Expand Up @@ -80,7 +76,7 @@ public static class Generators
/// <summary>
/// <see cref="Maybe{String}"/> with ~20% chance of <see cref="Maybe{T}.None"/>.
/// FsCheck's default <c>string</c> generator never produces <c>null</c>, so
/// the None branch has to be injected explicitly via <see cref="Gen.Frequency"/>.
/// the None branch has to be injected explicitly via <c>Gen.Frequency</c>.
/// Empty and whitespace strings are kept as valid <c>Some</c> values — use
/// <see cref="MaybeNonEmptyString"/> when you want the non-empty invariant.
/// </summary>
Expand Down
34 changes: 34 additions & 0 deletions src/StrongTypes.FsCheck/StrongTypes.FsCheck.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>14.0</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>CS1591</NoWarn>

<Version>0.0.0-dev</Version>
<PackageId>Kalicz.StrongTypes.FsCheck</PackageId>
<Authors>KaliCZ</Authors>
<Copyright>Copyright © 2026 KaliCZ</Copyright>
<Description>FsCheck arbitraries for Kalicz.StrongTypes. Drop-in property-test support for NonEmptyString, Positive&lt;T&gt;, NonNegative&lt;T&gt;, Negative&lt;T&gt;, NonPositive&lt;T&gt;, Maybe&lt;T&gt;, and NonEmptyEnumerable&lt;T&gt; via a single [Properties(Arbitrary = new[] { typeof(Generators) })] attribute.</Description>
<PackageTags>StrongTypes, FsCheck, PropertyTesting, Arbitrary, Generators</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageProjectUrl>https://github.com/KaliCZ/StrongTypes</PackageProjectUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/KaliCZ/StrongTypes.git</RepositoryUrl>
<PackageReadmeFile>readme.md</PackageReadmeFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FsCheck" Version="3.3.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StrongTypes\StrongTypes.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="readme.md" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>
49 changes: 49 additions & 0 deletions src/StrongTypes.FsCheck/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Kalicz.StrongTypes.FsCheck

FsCheck arbitraries for [Kalicz.StrongTypes](https://www.nuget.org/packages/Kalicz.StrongTypes).
Lets you write property tests against code that takes or returns `NonEmptyString`,
`Positive<T>`, `NonNegative<T>`, `Negative<T>`, `NonPositive<T>`, `Maybe<T>`, and
`NonEmptyEnumerable<T>` without hand-rolling generators that re-derive each type's
invariants.

## Install

```powershell
dotnet add package Kalicz.StrongTypes.FsCheck
```

## Register

Register everything with one attribute on your test class:

```csharp
using FsCheck.Xunit;
using StrongTypes.FsCheck;

[Properties(Arbitrary = new[] { typeof(Generators) })]
public class MyTests
{
[Property]
public void NonEmptyString_round_trips_through_json(NonEmptyString value)
{
// value is guaranteed non-null, non-empty, non-whitespace
}

[Property]
public void Positive_stays_positive(Positive<int> value)
{
Assert.True(value.Value > 0);
}
}
```

## What ships

- `NonEmptyString` — filtered to non-null, non-whitespace values
- `NullableNonEmptyString` — ~10% null injection
- `Positive<int>`, `Negative<int>`, `NonNegative<int>`, `NonPositive<int>`
- `Maybe<int>`, `Maybe<string>`, `Maybe<NonEmptyString>`, `Maybe<Positive<int>>` —
~20% `None` injection
- `NonEmptyEnumerable<int>`

Version matches the core `Kalicz.StrongTypes` package you install alongside it.
1 change: 1 addition & 0 deletions src/StrongTypes.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using StrongTypes.FsCheck;
1 change: 1 addition & 0 deletions src/StrongTypes.Tests/StrongTypes.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StrongTypes\StrongTypes.csproj" />
<ProjectReference Include="..\StrongTypes.FsCheck\StrongTypes.FsCheck.csproj" />
</ItemGroup>
</Project>
5 changes: 1 addition & 4 deletions src/StrongTypes/StrongTypes.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>CS1591</NoWarn>
<Version>0.3.0</Version>
<AssemblyVersion>0.3.0</AssemblyVersion>
<FileVersion>0.3.0</FileVersion>
<Version>0.0.0-dev</Version>
<PackageId>Kalicz.StrongTypes</PackageId>
<Description>A C# library that reduces boilerplate and prevents bugs through stronger typing. Continuation of FuncSharp.</Description>
<Authors>KaliCZ</Authors>
Expand All @@ -13,7 +11,6 @@
<PackageProjectUrl>https://github.com/KaliCZ/StrongTypes</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageReleaseNotes>0.3.0: Generic numeric types: Positive&lt;T&gt;, NonNegative&lt;T&gt;, Negative&lt;T&gt;, NonPositive&lt;T&gt;. Added a full E2E capability for these types and NonEmptyString - API JSON serialization and EF Core converters in a separate Kalicz.StrongTypes.EfCore.</PackageReleaseNotes>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/KaliCZ/StrongTypes</RepositoryUrl>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
Expand Down