nim-serde/serde/cbor/README.md

380 lines
10 KiB
Markdown
Raw Normal View History

# nim-serde CBOR
2025-06-09 12:40:28 +05:30
The CBOR module in nim-serde provides serialization and deserialization for Nim values following [RFC 8949](https://www.rfc-editor.org/rfc/rfc8949.html). Unlike the JSON implementation, CBOR offers a stream-based API that enables direct binary serialization without intermediate representations.
> Note: While the JSON implementation supports serde modes via pragmas, the current CBOR implementation does not support the `serialize` and `deserialize` pragmas. The library will raise an assertion error if you try to use these pragmas with CBOR serialization.
## Table of Contents
- [nim-serde CBOR](#nim-serde-cbor)
- [Table of Contents](#table-of-contents)
- [Serialization API](#serialization-api)
2025-06-09 12:40:28 +05:30
- [Stream API: Primitive Type and Sequence Serialization](#stream-api-primitive-type-and-sequence-serialization)
- [Stream API: Object Serialization](#stream-api-object-serialization)
- [Stream API: Custom Type Serialization](#stream-api-custom-type-serialization)
- [Serialization without Stream API: `toCbor`](#serialization-without-stream-api-tocbor)
- [Working with CborNode](#working-with-cbornode)
- [Convenience Functions for CborNode](#convenience-functions-for-cbornode)
- [Deserialization API](#deserialization-api)
- [Basic Deserialization with `fromCbor`](#basic-deserialization-with-fromcbor)
- [Error Handling](#error-handling)
- [Parsing CBOR with `parseCbor`](#parsing-cbor-with-parsecbor)
- [Custom Type Deserialization](#custom-type-deserialization)
- [Implementation Details](#implementation-details)
- [Current Limitations](#current-limitations)
## Serialization API
The nim-serde CBOR serialization API provides several ways to convert Nim values to CBOR.
2025-06-09 12:40:28 +05:30
### Stream API: Primitive Type and Sequence Serialization
The `writeCbor` function writes Nim values to a stream in CBOR format:
```nim
import pkg/serde/cbor
import pkg/questionable/results
import std/streams
# Create a stream to write to
let stream = newStringStream()
# Basic types
discard stream.writeCbor(42) # Unsigned integer
discard stream.writeCbor(-10) # Negative integer
discard stream.writeCbor(3.14) # Float
discard stream.writeCbor("hello") # String
discard stream.writeCbor(true) # Boolean
# Arrays and sequences
discard stream.writeCbor(@[1, 2, 3]) # Sequence
# Get the serialized CBOR data
let cborData = stream.data
```
2025-06-09 12:40:28 +05:30
### Stream API: Object Serialization
Objects can be serialized to CBOR format using the stream API:
```nim
import pkg/serde/cbor
import pkg/questionable/results
import std/streams
type Person = object
name: string
age: int
isActive: bool
let person = Person(
name: "John",
age: 30,
isActive: true
)
# Serialize the object to CBOR
let stream = newStringStream()
discard stream.writeCbor(person)
# Get the serialized CBOR data
let cborData = stream.data
```
2025-06-09 12:40:28 +05:30
### Stream API: Custom Type Serialization
You can extend nim-serde to support custom types by defining your own `writeCbor` procs:
```nim
import pkg/serde/cbor
import pkg/questionable/results
import std/streams
import std/strutils
# Define a custom type
type
UserId = distinct int
# Custom serialization for UserId
proc writeCbor*(str: Stream, id: UserId): ?!void =
# Write as a CBOR text string with a prefix
str.writeCbor("user-" & $int(id))
# Test serialization
let userId = UserId(42)
let stream = newStringStream()
discard stream.writeCbor(userId)
let cborData = stream.data
# Test in object context
type User = object
id: UserId
name: string
let user = User(id: UserId(123), name: "John")
let userStream = newStringStream()
discard userStream.writeCbor(user)
let userCborData = userStream.data
```
2025-06-09 12:40:28 +05:30
### Serialization without Stream API: `toCbor`
The `toCbor` function can be used to directly convert a Nim value to CBOR binary data:
```nim
import pkg/serde/cbor
import pkg/questionable/results
type Person = object
name: string
age: int
isActive: bool
let person = Person(
name: "John",
age: 30,
isActive: true
)
# Convert to CBOR binary data
let result = toCbor(person)
assert result.isSuccess
let cborData = !result
```
### Working with CborNode
The `CborNode` type represents CBOR data in memory and can be manipulated directly:
```nim
import pkg/serde/cbor
import pkg/questionable/results
import std/tables
# Create CBOR nodes
let textNode = CborNode(kind: cborText, text: "hello")
let intNode = CborNode(kind: cborUnsigned, uint: 42'u64)
let floatNode = CborNode(kind: cborFloat, float: 3.14)
# Create an array
var arrayNode = CborNode(kind: cborArray)
arrayNode.seq = @[textNode, intNode, floatNode]
# Create a map with text keys and boolean values
var mapNode = CborNode(kind: cborMap)
mapNode.map = initOrderedTable[CborNode, CborNode]()
# Boolean values are represented as simple values (21 for true, 20 for false)
mapNode.map[CborNode(kind: cborText, text: "a")] = CborNode(kind: cborSimple, simple: 21) # true
mapNode.map[CborNode(kind: cborText, text: "b")] = CborNode(kind: cborSimple, simple: 20) # false
# Convert to CBOR binary data
let result = toCbor(mapNode)
assert result.isSuccess
let cborData = !result
```
### Convenience Functions for CborNode
The library provides convenience functions for creating CBOR nodes:
```nim
import pkg/serde/cbor
import pkg/questionable/results
# Initialize CBOR nodes
let bytesNode = initCborBytes(@[byte 1, byte 2, byte 3])
let textNode = initCborText("hello")
let arrayNode = initCborArray()
let mapNode = initCborMap()
# Convert values to CborNode
let intNodeResult = toCborNode(42)
assert intNodeResult.isSuccess
let intNode = !intNodeResult
let strNodeResult = toCborNode("hello")
assert strNodeResult.isSuccess
let strNode = !strNodeResult
let boolNodeResult = toCborNode(true)
assert boolNodeResult.isSuccess
let boolNode = !boolNodeResult
```
## Deserialization API
The nim-serde CBOR deserialization API provides ways to convert CBOR data back to Nim values.
### Basic Deserialization with `fromCbor`
The `fromCbor` function converts CBOR data to Nim values:
```nim
import pkg/serde/cbor
import pkg/questionable/results
import std/streams
# Create some CBOR data
let stream = newStringStream()
discard stream.writeCbor(42)
let cborData = stream.data
# Parse the CBOR data into a CborNode
try:
let node = parseCbor(cborData)
# Deserialize the CborNode to a Nim value
let intResult = int.fromCbor(node)
assert intResult.isSuccess
let value = !intResult
assert value == 42
# You can also deserialize to other types
# For example, if cborData contained a string:
# let strResult = string.fromCbor(node)
# assert strResult.isSuccess
# let strValue = !strResult
# Deserialize to an object
type Person = object
name: string
age: int
isActive: bool
let personResult = Person.fromCbor(node)
assert personResult.isSuccess
let person = !personResult
# Verify the deserialized data
assert person.name == "John"
assert person.age == 30
assert person.isActive == true
```
### Error Handling
Deserialization returns a `Result` type from the `questionable` library, allowing for safe error handling:
```nim
import pkg/serde/cbor
import pkg/questionable/results
# Invalid CBOR data for an integer
let invalidNode = CborNode(kind: cborText, text: "not an int")
let result = int.fromCbor(invalidNode)
# Check for failure
assert result.isFailure
echo result.error.msg
# Output: "deserialization to int failed: expected {cborUnsigned, cborNegative} but got cborText"
```
### Parsing CBOR with `parseCbor`
The `parseCbor` function parses CBOR binary data into a `CborNode`:
```nim
import pkg/serde/cbor
import pkg/questionable/results
# Parse CBOR data
let node = parseCbor(cborData)
# Check node type and access data
case node.kind
of cborUnsigned:
echo "Unsigned integer: ", node.uint
of cborNegative:
echo "Negative integer: ", node.int
of cborText:
echo "Text: ", node.text
of cborArray:
echo "Array with ", node.seq.len, " items"
of cborMap:
echo "Map with ", node.map.len, " pairs"
else:
echo "Other CBOR type: ", node.kind
```
### Custom Type Deserialization
You can extend nim-serde to support custom type deserialization by defining your own `fromCbor` procs:
```nim
import pkg/serde/cbor
import pkg/questionable/results
import std/strutils
# Define a custom type
type
UserId = distinct int
# Custom deserialization for UserId
proc fromCbor*(_: type UserId, n: CborNode): ?!UserId =
if n.kind != cborText:
return failure(newSerdeError("Expected string for UserId, got " & $n.kind))
let str = n.text
if str.startsWith("user-"):
let idStr = str[5..^1]
try:
let id = parseInt(idStr)
success(UserId(id))
except ValueError:
failure(newSerdeError("Invalid UserId format: " & str))
else:
failure(newSerdeError("UserId must start with 'user-' prefix"))
# Test deserialization
let node = parseCbor(cborData) # Assuming cborData contains a serialized UserId
let result = UserId.fromCbor(node)
assert result.isSuccess
assert int(!result) == 42
# Test deserialization in object context
type User = object
id: UserId
name: string
let userNode = parseCbor(userCborData) # Assuming userCborData contains a serialized User
let userResult = User.fromCbor(userNode)
assert userResult.isSuccess
assert int((!userResult).id) == 123
assert (!userResult).name == "John"
```
## Implementation Details
The CBOR serialization in nim-serde follows a stream-based approach:
```
# Serialization flow
Nim value → writeCbor → CBOR binary data
# Deserialization flow
CBOR binary data → parseCbor (CborNode) → fromCbor → Nim value
```
Unlike the JSON implementation which uses the `%` operator pattern, the CBOR implementation uses a hook-based approach:
1. The `writeCbor` function writes Nim values directly to a stream in CBOR format
2. Custom types can be supported by defining `writeCbor` procs for those types
3. The `toCbor` function provides a convenient way to convert values to CBOR binary data
For deserialization, the library parses CBOR data into a `CborNode` representation, which can then be converted to Nim values using the `fromCbor` function. This approach allows for flexible handling of CBOR data while maintaining type safety.
### Current Limitations
2025-06-09 12:40:28 +05:30
This implementation does not support the `serialize` and `deserialize` pragmas. The library will raise an assertion error if you try to use these pragmas with CBOR serialization.
```nim
import pkg/serde/cbor
type Person {.serialize(mode = OptOut).} = object # This will raise an assertion error
name: string
age: int
isActive: bool
```