@@ -368,7 +368,11 @@ def initialize(x, y)
368368 @y = y
369369 end
370370
371- def from_allocate
371+ def _after_decode
372+ self
373+ end
374+
375+ def _before_encode
372376 self
373377 end
374378
@@ -1566,3 +1570,252 @@ def from_allocate
15661570 # dig on non-container should raise
15671571 assert_raise ( TypeError ) { lazy . dig ( "key" ) }
15681572end
1573+
1574+ # ============================================================================
1575+ # Registered tag hooks: _before_encode and _after_decode
1576+ # ============================================================================
1577+
1578+ assert ( 'CBOR registered tag: _after_decode hook called' ) do
1579+ class Person
1580+ attr_accessor :name , :age
1581+ native_ext_type :@name , CBOR ::Type ::String
1582+ native_ext_type :@age , CBOR ::Type ::Integer
1583+
1584+ def initialize
1585+ @loaded = false
1586+ end
1587+
1588+ def _after_decode
1589+ @loaded = true
1590+ self
1591+ end
1592+
1593+ def loaded?
1594+ @loaded
1595+ end
1596+ end
1597+
1598+ CBOR . register_tag ( 1000 , Person )
1599+
1600+ person = Person . new
1601+ person . name = "Alice"
1602+ person . age = 30
1603+
1604+ encoded = CBOR . encode ( person )
1605+ decoded = CBOR . decode ( encoded )
1606+
1607+ assert_true decoded . is_a? ( Person )
1608+ assert_equal "Alice" , decoded . name
1609+ assert_equal 30 , decoded . age
1610+ assert_true decoded . loaded?
1611+ end
1612+
1613+ assert ( 'CBOR registered tag: _before_encode hook called' ) do
1614+ class Item
1615+ attr_accessor :value
1616+ native_ext_type :@value , CBOR ::Type ::Integer
1617+
1618+ def initialize ( val )
1619+ @value = val
1620+ end
1621+
1622+ def _before_encode
1623+ @value = @value * 2 # Double before encoding
1624+ self
1625+ end
1626+ end
1627+
1628+ CBOR . register_tag ( 1001 , Item )
1629+
1630+ item = Item . new ( 5 )
1631+ encoded = CBOR . encode ( item )
1632+ decoded = CBOR . decode ( encoded )
1633+
1634+ assert_equal 10 , decoded . value
1635+ end
1636+
1637+ assert ( 'CBOR registered tag: both hooks in sequence' ) do
1638+ class Counter
1639+ attr_accessor :count
1640+ native_ext_type :@count , CBOR ::Type ::Integer
1641+
1642+ def initialize ( n )
1643+ @count = n
1644+ @before_encode_called = false
1645+ @after_decode_called = false
1646+ end
1647+
1648+ def _before_encode
1649+ @before_encode_called = true
1650+ @count = @count + 1
1651+ self
1652+ end
1653+
1654+ def _after_decode
1655+ @after_decode_called = true
1656+ @count = @count + 1
1657+ self
1658+ end
1659+
1660+ def before_encode_called?
1661+ @before_encode_called
1662+ end
1663+
1664+ def after_decode_called?
1665+ @after_decode_called
1666+ end
1667+ end
1668+
1669+ CBOR . register_tag ( 1002 , Counter )
1670+
1671+ counter = Counter . new ( 10 )
1672+
1673+ encoded = CBOR . encode ( counter )
1674+ assert_true counter . before_encode_called? # _before_encode was called during encode
1675+ decoded = CBOR . decode ( encoded )
1676+
1677+ assert_equal 12 , decoded . count # 10 + 1 (before_encode) + 1 (after_decode)
1678+ assert_true decoded . after_decode_called?
1679+ end
1680+
1681+ assert ( 'CBOR registered tag: _before_encode without _after_decode' ) do
1682+ class Price
1683+ attr_accessor :amount
1684+ native_ext_type :@amount , CBOR ::Type ::Integer
1685+
1686+ def initialize ( val )
1687+ @amount = val
1688+ end
1689+
1690+ def _before_encode
1691+ @amount = ( @amount * 100 ) . to_i # Convert to cents
1692+ self
1693+ end
1694+ end
1695+
1696+ CBOR . register_tag ( 1003 , Price )
1697+
1698+ price = Price . new ( 5 )
1699+ encoded = CBOR . encode ( price )
1700+ decoded = CBOR . decode ( encoded )
1701+
1702+ assert_equal 500 , decoded . amount
1703+ assert_true decoded . is_a? ( Price )
1704+ end
1705+
1706+ assert ( 'CBOR registered tag: _after_decode without _before_encode' ) do
1707+ class Config
1708+ attr_accessor :timeout
1709+ native_ext_type :@timeout , CBOR ::Type ::Integer
1710+
1711+ def initialize ( val )
1712+ @timeout = val
1713+ @validated = false
1714+ end
1715+
1716+ def _after_decode
1717+ @timeout = @timeout . abs
1718+ @validated = true
1719+ self
1720+ end
1721+
1722+ def validated?
1723+ @validated
1724+ end
1725+ end
1726+
1727+ CBOR . register_tag ( 1004 , Config )
1728+
1729+ config = Config . new ( 30 )
1730+ encoded = CBOR . encode ( config )
1731+ decoded = CBOR . decode ( encoded )
1732+
1733+ assert_equal 30 , decoded . timeout
1734+ assert_true decoded . validated?
1735+ end
1736+
1737+ assert ( 'CBOR registered tag: hook exception propagates' ) do
1738+ class Strict
1739+ attr_accessor :value
1740+ native_ext_type :@value , CBOR ::Type ::Integer
1741+
1742+ def initialize ( val )
1743+ @value = val
1744+ end
1745+
1746+ def _before_encode
1747+ raise "encode forbidden"
1748+ end
1749+ end
1750+
1751+ CBOR . register_tag ( 1005 , Strict )
1752+
1753+ strict = Strict . new ( 42 )
1754+ assert_raise ( RuntimeError ) { CBOR . encode ( strict ) }
1755+ end
1756+
1757+ assert ( 'CBOR registered tag: hook modifies multiple fields' ) do
1758+ class User
1759+ attr_accessor :username , :email
1760+ native_ext_type :@username , CBOR ::Type ::String
1761+ native_ext_type :@email , CBOR ::Type ::String
1762+
1763+ def initialize
1764+ @username = ""
1765+ @email = ""
1766+ end
1767+
1768+ def _after_decode
1769+ @username = @username . downcase
1770+ @email = @email . downcase
1771+ self
1772+ end
1773+ end
1774+
1775+ CBOR . register_tag ( 1006 , User )
1776+
1777+ user = User . new
1778+ user . username = "AlicE"
1779+ user . email = "Alice@Example.COM"
1780+
1781+ encoded = CBOR . encode ( user )
1782+ decoded = CBOR . decode ( encoded )
1783+
1784+ assert_equal "alice" , decoded . username
1785+ assert_equal "alice@example.com" , decoded . email
1786+ end
1787+
1788+ assert ( 'CBOR registered tag: lazy decode with _after_decode' ) do
1789+ class Data
1790+ attr_accessor :value
1791+ native_ext_type :@value , CBOR ::Type ::Integer
1792+
1793+ def initialize ( val )
1794+ @value = val
1795+ @materialized = false
1796+ end
1797+
1798+ def _after_decode
1799+ @materialized = true
1800+ self
1801+ end
1802+
1803+ def materialized?
1804+ @materialized
1805+ end
1806+ end
1807+
1808+ CBOR . register_tag ( 1007 , Data )
1809+
1810+ data = Data . new ( 99 )
1811+ encoded = CBOR . encode ( data )
1812+ lazy = CBOR . decode_lazy ( encoded )
1813+
1814+ # Lazy hasn't materialized yet
1815+ assert_false data . materialized?
1816+
1817+ # Calling .value triggers decode and _after_decode
1818+ materialized = lazy . value
1819+ assert_true materialized . materialized?
1820+ assert_equal 99 , materialized . value
1821+ end
0 commit comments