Skip to content

util: add Tagged<T, "Name"> newtype helper; relax Quantity/Rational DecimalPlaces floor to 0 - #74

Merged
Yaraslaut merged 2 commits into
masterfrom
issue-cluster-d-util
Aug 12, 2026
Merged

util: add Tagged<T, "Name"> newtype helper; relax Quantity/Rational DecimalPlaces floor to 0#74
Yaraslaut merged 2 commits into
masterfrom
issue-cluster-d-util

Conversation

@Yaraslaut

Copy link
Copy Markdown
Member

Summary

Closes #49, closes #53.

Review

Ran contour-workflows:cpp-review against the branch diff (real skill invocation). No findings — schema/wire serialization at dp=0 verified correct by test, docs accurately updated.

Test plan

Targeted filter [tagged],[rational],[quantity],[forms]: 979 assertions / 190 cases, all passing. Full suite: 860/860 passing.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Yaraslaut
Yaraslaut force-pushed the issue-cluster-d-util branch 3 times, most recently from 766db66 to adfe4c2 Compare August 12, 2026 13:41
Yaraslau Tamashevich and others added 2 commits August 12, 2026 18:49
…scalars

Adds morph::util::Tagged<T, Tag> under include/morph/util/tagged.hpp: a
type-safe newtype for protocol scalars (pagination cursors, event/job ids,
tokens) that serializes transparently as its underlying T on the wire
(glz::meta maps straight to the payload; to_json_schema delegates to T's
own schema, with the tag surfacing only as the schema title). Two Tagged
specializations sharing T but differing by Tag are distinct, mutually
inconvertible C++ types.

hasValue() always returns true (Tagged is a required scalar, not an
optionally-empty field), so it satisfies morph::forms::EmptyCapableField
and composes with the existing forms optionality palette the same way
Quantity/Choice/Timestamp do.

Adds docs/spec/util/tagged.md and a forms.md cross-reference; adds
tests/test_tagged.cpp covering construction, type-safe identity, equality,
hasValue(), wire round-trip (scalar and struct-member), and schema shape.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Quantity<U, DeclaredDecimals> forbade DeclaredDecimals == 0, and
Rational's DecimalPlaces silently clamped a wire/constructor precision of
0 up to 1 -- incompatible with zero-decimal currencies (JPY, KRW) and
plain whole-number quantities, which had to borrow dp=1 and carry a
spurious fractional digit.

- rational.hpp: clampWireDecimalPlaces/clampDecimalPlaces now accept
  [0, kMaxDecimalPlaces] instead of [1, kMaxDecimalPlaces]; the debug
  assert and doc comments/invariants updated to match. Since dp is
  unsigned, only the upper bound can ever clamp now.
- quantity.hpp: Quantity's static_assert now permits DeclaredDecimals == 0.

Audited downstream decimal-formatting/parsing paths per the issue's
suggested direction: formatRationalDecimal, Rational::toDouble,
powerOfTen, the QML numeric-input codec (DynamicForm.qml,
CollectionView.qml), and the forms schema-merge layer already handled
dp == 0 correctly (loops/scale degenerate cleanly at 0); no further code
changes were needed there.

Updates docs/spec/util/rational.md and docs/spec/util/quantity_type.md to
state the new [0, kMaxDecimalPlaces] invariant and drop the "floor of 1"
limitation. Extends tests/test_rational.cpp (a new ZeroDecimalPlaces case
plus updated PrecisionCap/HostileInputClamps expectations),
tests/test_quantity.cpp (a Yen unit with defaultDecimals=0 and a
zero-decimal-currency test covering fromDouble rounding, formatting,
arithmetic, and wire round-trip), and tests/test_quantity_forms.cpp (a
QFWholeCount schema test asserting x-decimalPlaces:0 surfaces correctly).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Yaraslaut
Yaraslaut force-pushed the issue-cluster-d-util branch from f6ea410 to 557f9ab Compare August 12, 2026 15:54
@Yaraslaut
Yaraslaut merged commit e603806 into master Aug 12, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Quantity's DecimalPlaces has a floor of 1, incompatible with zero-decimal currencies No Tagged<T, "Name"> opaque-newtype helper for protocol scalars

1 participant