Skip to content
Purview Dev logo

Diagnostics

Stable Value Objects Reviewed 2026-10-23 purview-dev/value-objects Star on GitHub 1 c-sharp csharp ddd domain-driven-design dotnet dto entity-framework immutable json roslyn scalar serialization single-case-union source-generator validation value-object value-objects zodsharp

Purview.ValueObjects ships a Roslyn analyzer and source generator that report the rules below. Every rule is enabled by default.

Suppress a rule the usual ways — #pragma warning disable VO1006, a NoWarn entry, or an .editorconfig severity override (dotnet_diagnostic.VO1006.severity = none). Errors cannot be downgraded to warnings: they mark shapes the generator cannot emit code for.

These govern the declaration shape a value object must have for the generator to emit anything.

Rule Severity Reported when Fix
VO1001 Error A [Scalar] or [ValueObject] type is not declared partial. Add partial. The code fix in Purview.ValueObjects.SourceGenerator.Refactorings does this for you.
VO1002 Error The value object is nested inside another type. Move it to its own top-level type. Nesting is not supported.
VO1003 Error The value object is generic. Remove the type parameters, or declare one concrete value object per closed type.
VO1004 Error A [Scalar] type does not declare the configured scalar property. Declare the property, or set ScalarAttribute.PropertyName to the one you have.
VO1006 Warning A [Scalar] type is not a readonly record struct. Prefer readonly record struct so equality, immutability and allocation behaviour match the contract.
VO1007 Warning ValueObjectDeserializationMode.Strict is set but no Create overload exists to re-validate through. Add the Create overload, or use the default Hydrate mode.
VO1008 Error [Scalar] and [ValueObject] are both applied to the same type. Pick one. A scalar wraps a single primitive; a value object composes members.
VO1016 Warning A value object member is mutable. Make the member readonly/init-only. A mutable member breaks immutability and makes equality unstable.
VO1022 Error A [Scalar<T>] type declares a member with the name of the property the generator owns. Remove the declaration, or use [Scalar] (without a type argument) to own the property yourself.
VO1028 Error [Scalar<T>(Nullable = true)] is combined with a declared Value property, so nullability has two sources. Remove the declared Value; convert to the manual [Scalar] form; or remove Nullable = true. The code fix offers all three.
VO1029 Error Explicit Nullable = false contradicts a declared nullable Value property. Remove the declared Value, or use the manual [Scalar] form and own the property.
VO1030 Error The automatic form’s declared Value has a type that does not match the scalar’s underlying type. Remove the declaration, or use the manual [Scalar] form with a matching type.

See ZodSharp validation for the integration itself.

Rule Severity Reported when Fix
VO1013 Warning OnValidate is declared but never invoked because ZodSchemaMode.InsteadOfHooks is set. Remove OnValidate, or choose a ZodSchemaMode that keeps the hooks.
VO1015 Error The ZodSharp SchemaName is not a valid C# identifier. Give it a valid identifier.

See Value object design for the built-in string normalization options.

Rule Severity Reported when Fix
VO1023 Warning Built-in string normalization is configured but the value object implements OnNormalize or declares its own Create. Remove the option, or move the normalization into the hook/Create.
VO1024 Warning Built-in string normalization is configured on a non-string scalar or member. Remove the option; it only applies to string values.
VO1025 Error [StringNormalize] is applied to a member of a [Scalar], or of a type that is neither [Scalar] nor [ValueObject], or to a property that is not a value member. Move the attribute to a string member of a complex [ValueObject]. On a scalar, set Trim/Casing on [Scalar] instead.
VO1026 Error An author-declared [assembly: ValueObjectDefaults] sets an option to the value the packaged targets already supplied from a ValueObjectsDefault* MSBuild property. Remove the argument from the assembly attribute, or set a different value.
VO1027 Error A [Scalar] or [ValueObject] sets an option to the effective assembly default. Remove the option from the attribute; it repeats the inherited value.

Restating an option that the assembly defaults (or ValueObjectsDefault* MSBuild properties) already set is a no-op. VO1026/VO1027 report that redundancy for assembly, [Scalar] and [ValueObject] options, so the configuration has a single source of truth. [StringNormalize] is a per-member override and is not included in that check.

See Entity Framework integration for the mapping model.

Rule Severity Reported when Fix
VO1009 Warning Entity Framework mapping is requested but Microsoft.EntityFrameworkCore is not referenced. Add the package reference, or stop requesting EF mapping.
VO1010 Warning EF auto-conversion was skipped because the value object’s underlying type is not mappable. Map it by hand with a custom ValueConverter.
VO1017 Warning EF JSON mapping is requested without the JSON converter. Register the JSON converter for the value object.
VO1018 Warning EF complex mapping cannot convert one of the members. Simplify the member, or map it explicitly.
VO1019 Warning EF complex-type mapping is requested but the project resolves to EF Core 7 or earlier. Target EF Core 8+, or use a different EntityFrameworkMapping.
VO1021 Warning EF key value generation is unavailable for the value object. Assign keys explicitly instead of relying on store generation.
VO1031 Warning A nullable scalar is used as a non-nullable property on an entity or complex-type-mapped [ValueObject]. EF Core maps that to a required column and cannot persist the scalar’s Null(). Declare the property as T?, or use a non-nullable scalar behind a nullable property. See Entity Framework.

VO1031 is scoped to types the compilation can see being mapped (a DbSet<T> on a DbContext, an IEntityTypeConfiguration<T> target, or a complex-type-mapped [ValueObject]). Plain DTOs are not reported: a DTO property of a nullable scalar is the supported way to represent the scalar’s Null() value. Entity types configured only in a referenced assembly, or built entirely at runtime, are not discovered; the rule stays a warning and can be suppressed when the non-nullable shape is deliberate.

VO1005, VO1011, VO1012, VO1014 and VO1020 were used during development and withdrawn before release. They are not reported by any version and are never reused, so the numbering is intentionally discontinuous. Treat an occurrence of one of these IDs as stale tooling or a stale suppression, and remove it.

VO1005 (“scalar constructor is missing”) is worth calling out, because it was listed as an Error in the analyzer catalogue and documented here as requiring you to declare a constructor. It never had a reporting site, and the requirement it described was the opposite of how the generator works: when a [Scalar] type declares no constructor taking the scalar’s underlying type, the generator emits a private one. You do not need to declare it, and nothing ever asked you to.

Rules are tracked in src/src/SourceGenerator/AnalyzerReleases.Shipped.md and AnalyzerReleases.Unshipped.md, which the Roslyn RS2008 catalogue rule enforces. A new or changed diagnostic must be recorded there in the same change, and the unshipped entries move into a new ## Release <version> block when that version ships.