Skip to content

Conversation

@munna0908
Copy link

@munna0908 munna0908 commented May 22, 2025

This PR introduces support for canonical CBOR serialization in compliance with RFC 8949.
Note that this implementation does not yet support serde features such as OptIn, OptOut, Strict, or serialize/deserialize pragma annotations

@munna0908 munna0908 requested review from dryajov, emizzle and gmega May 22, 2025 11:20
@munna0908 munna0908 self-assigned this May 22, 2025
Copy link

@gmega gmega left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides the comments, I strongly believe we need to deal with the nim-serde pragmas in some way, even if they're not supported. A few options:

  1. issue an error (preferably a compilation error, if possible) if you try to use an annotated type with CBOR. This is what I prefer. Safest, least likely to cause surprise;
  2. add documentation describing the behavior. Better than nothing, can still cause surprise, at least is written somewhere.

Either way, the documentation needs to be updated. I'm sure @emizzle will have better suggestions than mine as to how to make this integration as seamless as possible.

return failure(newCborError("Expected positive integer, got " & $c.kind))
let val = c.intVal.BiggestUInt

?c.next()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm honestly not very fond of this. Took me a bit to figure out what this was doing, and I don't think I've seen this being used like that elsewhere.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gmega In my opinion, this is the cleanest way to handle the "return on failure" scenario.
Based on my discussions with @dryajov, we hadn't used this approach earlier because it didn't support Futures. Now that it does, we’ll be using this syntax more extensively

let n = c.s.readData(buf[0].addr, buf.len)
if n != buf.len:
return failure(newCborError("truncated read of CBOR data"))
tryNext(c)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... I don't like those things that look like proc calls syntactically but then stuff a return into the code. It makes things very hard to read. It makes things look more like exception-based code (i.e. a tad cleaner), but it's surprising behavior when using result types IMHO.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you really want to do something like that, it has to at least look like an explicit return IMHO. Something like:

returnOnFail c.next()

and then have returnOnFail as your template.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed this template and replaced with ? template.


func arrayLen*(c: CborParser): int =
## Return the length of the array that the parser is positioned on.
assert(c.kind == CborEventKind.cborArray, $c.kind)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you decide to throw an exception here instead of using result as you did with bytesLen? Also, I don't see this proc being used anywhere (nor bytestLen)?

import ./errors
import ./deserializer

proc toJsonHook*(n: CborNode): JsonNode =
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd call this toJson and keep it uniform with nim-serde API.

except Exception as e:
return failure(e.msg)

proc writeCborHook*(str: Stream; dt: DateTime): ?!void =
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would probably rename all of those to toCbor.

func keysNotIn[T](json: JsonNode, obj: T): HashSet[string] =
let jsonKeys = json.keys.toSeq.toHashSet
let objKeys = obj.fieldKeys.toHashSet
difference(jsonKeys, objKeys)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't move those around for no reason. It's actually confusing cause I thought you were using them in the CBOR code, but then later realized you're not.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gmega I've moved this to serde/json/helper.nim to separate helper functions from the core logic. This helps improve the overall structure and maintainability of the code

import pkg/serde/cbor
import pkg/questionable
import pkg/questionable/results

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty awesome test!

@munna0908 munna0908 marked this pull request as ready for review June 1, 2025 18:43
@munna0908 munna0908 requested a review from gmega June 1, 2025 18:43
Copy link

@gmega gmega left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job, I think this is much better now. I think the documentation still needs work to be easier to follow (I'm also not sure I'm so fond of the separate READMEs), but I don't have the time to work on it now. :-( Plus - all the info is there, so good enough for now I suppose.

README.md Outdated
## Supported Wire Formats

Opt-in serialization by default:
nim-serde currently supports the following wire formats:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
nim-serde currently supports the following wire formats:
nim-serde currently supports the following serialization formats:

README.md Outdated
# nim-serde

Easy-to-use json serialization capabilities, and a drop-in replacement for `std/json`.
A serialization and deserialization library for Nim supporting multiple wire formats.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
A serialization and deserialization library for Nim supporting multiple wire formats.
A serialization and deserialization library for Nim supporting multiple formats.

I wouldn't say "wire" as those might well be serialized for other reasons, not just sending over the wire.

let userCborData = userStream.data
```

### Converting to CBOR with `toCbor`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean this is good enough as all the info needed to use the library is here. However: I'd probably use this as the initial example as it's the simplest API, and it's also what looks the most like the JSON API. Would then talk about the streaming API as a more advanced, CBOR-only feature and, finally, about working with CBOR node.


The nim-serde CBOR serialization API provides several ways to convert Nim values to CBOR.

### Basic Serialization with Stream API
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Basic Serialization with Stream API
### Stream API: Primitive Type and Sequence Serialization

Comment on lines 77 to 85
let jsonObj = %*{
"name": "John",
"age": 30,
"hobbies": ["reading", "coding"],
"address": {
"street": "123 Main St",
"city": "Anytown"
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let jsonObj = %*{
"name": "John",
"age": 30,
"hobbies": ["reading", "coding"],
"address": {
"street": "123 Main St",
"city": "Anytown"
}
}
let
age = 30
name = "John"
jsonObj = %*{
"name": name,
"age": age,
"hobbies": ["reading", "coding"],
"address": {
"street": "123 Main St",
"city": "Anytown"
}
}

@munna0908 munna0908 requested a review from gmega June 9, 2025 07:59
Copy link

@gmega gmega left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think the examples in the detailed docs are a bit confusing and suggested some edits, but I guess we're getting to diminishing returns on review rounds. Since @emizzle also thinks this is good enough, I'm comfortable approving it.

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.

3 participants