Decimal Metadata Kind - #54
Merged
Merged
Conversation
Addresses #52. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EAYimZ1A2VK5zvEbK26kQa
Move Array and Object to 100/101 so later primitive kinds can be added without renumbering the complex kinds again, and require a test that enumerates MetadataKind to guard the IsPrimitive ordering invariant. Addresses #52. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EAYimZ1A2VK5zvEbK26kQa
Addresses #52. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EAYimZ1A2VK5zvEbK26kQa
Every MetadataKind dispatch site has a default arm or silent fall-through, so adding the enum member breaks nothing at compile time. Two sites degrade worse than the defect being fixed. Replace the "fails to compile" claim with a failure-mode table. Precision and scale metadata are ints, not decimals. The decimal path is CreateMetadataValue<T> behind ComparativeValue, LowerBoundary and UpperBoundary, so retarget the reachability example and the integration test at comparison and range checks. Addresses #52. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EAYimZ1A2VK5zvEbK26kQa
Signed-off-by: Kenny Pflug <kenny.pflug@live.de>
Equals and ToString unboxed the decimal payload with an unchecked cast while every neighbouring arm uses a safe cast. Match the reference with a pattern instead, so a decimal kind whose payload carries no boxed decimal is unequal and throws InvalidOperationException from ToString rather than NullReferenceException. The fallback message now covers both an unknown kind and an invalid payload. Two of the four MetadataKind tests could not fail: IsPrimitive is kind < Array and Array is 200, so asserting that primitives are below 200 was true by construction. Replace them with a pin on the numeric value of every declared member, which guards the reserved 6-199 range and the wire-visible numbering in one place. Assert the process bitness before the pinned Unsafe.SizeOf value, so a 32-bit run reports the assumption instead of an unexplained size. Addresses #52. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DNvxUqG5x5wF3d1vfzx3kj
Signed-off-by: Kenny Pflug <kenny.pflug@live.de>
The package release notes carried the breaking changes of this branch under the heading of 0.6.0, which was tagged and published in May. Move all package headings, the Version property, and the README badge to 0.7.0 - pre-1.0, a breaking change bumps the minor version. Also correct the comment on the decimal branch of GetStringAttribute. The checks above it are strict kind checks that a decimal cannot reach, so the explicit kind check guards against the branch being moved up rather than against the current order. Addresses #52. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DNvxUqG5x5wF3d1vfzx3kj
Minimum allowed line rate is |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #52.
Implemented as stated in the corresponding issue, following
ai-plans/0052-decimal-metadata-kind.md.MetadataValue.FromDecimalused to convert its input to invariant text and storeMetadataKind.String, so decimals serialized into JSON bodies as quoted strings while the OpenAPI document generated for the same endpoint described them as numbers. AnyIsGreaterThan(19.99m)orIsInRange(9.99m, 99.99m)check therefore produced aproblem+jsonbody that violated its own published contract. This introduces a dedicatedMetadataKind.Decimalbacked by a boxeddecimal.What changed
MetadataKind.Decimal = 5, appended to the primitive block.IsPrimitiveiskind < MetadataKind.Array, so membership is decided purely by ordering — declaringDecimalafterObjectwould have compiled cleanly and silently classified decimals as complex.ArrayandObjectmove to200/201, reserving6-199for future primitive kinds (CloudEvents'Binary,URI,URI-reference, andTimestampare all flattened intoStringtoday).Referenceslot. An inline 128-bit field would pushReferenceto offset 16 and grow everyMetadataValueby 8 bytes, including elements stored inline in arrays and objects.Unsafe.SizeOf<MetadataValue>()is pinned at its pre-existing 24 bytes so a future layout change has to be deliberate.MetadataKinddispatch site updated. All six have adefaultarm or a silent fall-through, so adding the member broke nothing at compile time:WriteMetadataValuewould have written JSONnull,Equalswould have made decimals unequal to themselves,GetHashCodewould have hashed the kind alone, and a decimal-valued CloudEventssubjectwould have becomenull. Each is now handled and covered by a test.decimal, which is scale-insensitive and consistent withdecimal.GetHashCode.Int64-then-Doublebehaviour and never producesMetadataKind.Decimal. This is documented on bothMetadataJsonReadertypes and pinned by tests. PreferringDecimalwould make the resulting kind depend on the magnitude of the value and would break round-tripping in the opposite direction.Breaking changes
Pre-1.0, and all of these are silent at compile time for downstream callers. They are listed in
PackageReleaseNotes:TryGetStringno longer returnstruefor decimals;Kindis no longerMetadataKind.String.19.50m,19.5m) now compare equal. Scale is still preserved for serialization.MetadataKind.ArrayandMetadataKind.Objectchange to200and201. Nothing in the solution castsMetadataKindto a numeric type and it is not exposed by the OpenAPI, Validation, or source-generation packages, but any consumer that persisted or transmitted the numeric value is affected.Verification
TreatWarningsAsErrors.reportgeneratorpath CI uses.problem+jsonbody carries unquoted numbers and that the generated OpenAPI document types the same properties asnumber.The last commit applies review findings: the decimal arms of
EqualsandToStringnow match the payload with a pattern instead of an unchecked cast, twoMetadataKindtests that could not fail were replaced by a pin on every member's numeric value, and the size pin asserts process bitness before its 64-bit literal.Follow-up
#53 — writing extension attributes as JSON numbers is outside the CloudEvents type system, which is closed and has no fractional type. This predates the PR (
Doubleand out-of-int32-rangeInt64behave the same way); it is not a regression from this change, butDecimaldid move from the accidentally conformant column into the same bucket.🤖 Generated with Claude Code
https://claude.ai/code/session_01DNvxUqG5x5wF3d1vfzx3kj