-
Notifications
You must be signed in to change notification settings - Fork 919
refactor(experimental): add offsetCodec helper to @solana/codecs-core #2294
Conversation
|
This stack of pull requests is managed by Graphite. Learn more about stacking. Join @lorisleiva and the rest of your teammates on Graphite |
My initial thoughts are that it might be good to make the wrapping behavior of the pre/post offset optional, such that the codec will throw an error if the offset specified is greater or less than the size of the input. To support this, instead of having the preOffsetFunction and postOffsetFunction specified as the 2nd and 3rd function parameter, a configuration object may be passed instead. Otherwise, everything else looks good. This also gave me an idea of having a conditional codec, where a child codec provided would only be invoked if some conditions are true of the input, or of previously-decoded/encoded bytes. |
One other comment is, what do you think about calling this codec |
@lithdew Thanks for the feedback! Tbh I went back and forth multiple times on whether to throw or modulo byte overflows. The reason I went for the later is that it enables negative offsets so using An option could be nice though. So the API would look something like: const u32InTheMiddleCodec = offsetCodec(biggerU32Codec, {
preOffset: ({ preOffset }) => preOffset + 2,
postOffset: ({ postOffset }) => postOffset + 2,
wrapBytes: true, // false by default?
}); The only annoyance is it makes the simple // Before.
offsetCodec(innerCodec, () => -4);
// After.
offsetCodec(innerCodec, {
preOffset: () => -4,
wrapBytes: true,
}); A conditional codec could be fun to implement as well yeah. I guess its logic would be fairly close to the I'm not sure I like |
026d1fa
to
d8f2d3b
Compare
9b9a133
to
04f8d5c
Compare
d8f2d3b
to
a98fa64
Compare
04f8d5c
to
7c34d28
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mockCodec.write.mockImplementation((_value, _bytes, offset) => offset + innerCodecSize); | ||
mockCodec.read.mockImplementation((bytes, offset) => [bytes, offset + innerCodecSize]); | ||
|
||
const codec = codecFactory(mockCodec); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inlining all of these test cases (as separate, hand-hewn artisanal its()
) would reduce the debugging burden on the person who broke them. As it is, they're going to have to jump in and out of this code to figure out what the factory is, and what the expected values are.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks of the feedback. I've updated all the tests. Lmk what you think.
@steveluscher Following the conversation on this PR and talking to @joncinque about it, I'm gonna refactor the Instead of the So the API would look like this. What do you all think? offsetCodec(innerCoder, {
preOffset: ({ wrapBytes }) => wrapBytes(-4),
postOffset: ({ preOffset }) => preOffset,
}); |
a98fa64
to
fc48293
Compare
acb9554
to
bf99665
Compare
rules: { | ||
'jest/expect-expect': [ | ||
'error', | ||
{ assertFunctionNames: ['expect', 'expectNewPreOffset', 'expectNewPostOffset'] }, | ||
], | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@steveluscher Is this the best place to put this?
6da28d8
to
0534171
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lgtm! How does this bad boy work inside of an ArrayDecoder
? Can I use an OffsetDecoder
for each element in my array? In other words:
createArrayDecoder(
createOffsetDecoder(/* .. */)
)
If so, not sure if you want to add some test coverage on arrays.
@buffalojoec Yeah offsets will apply to each array items which can create some crazy use-cases haha. I'll add some array tests in sub-sequent PRs. |
Merge activity
|
0534171
to
be1301f
Compare
This PR adds a couple of tests for offsetted arrays as [suggested here](#2294 (review)).
🎉 This PR is included in version 1.91.2 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Because there has been no activity on this PR for 14 days since it was merged, it has been automatically locked. Please open a new issue if it requires a follow up. |
This PR adds a new
offsetCodec
helper function. See copy/pasted documentation below:Offsetting codecs
The
offsetCodec
function is a powerful codec primitive that allows you to move the offset of a given codec forward or backwards. It accepts one or two functions that takes the current offset and returns a new offset.To understand how this works, let's take our previous
biggerU32Codec
example which encodes au32
number inside an 8-byte buffer.Now, let's say we want to move the offset of that codec 2 bytes forward so that the encoded number sits in the middle of the buffer. To achieve, this we can use the
offsetCodec
helper and provide apreOffset
function that moves the "pre-offset" of the codec 2 bytes forward.We refer to this offset as the "pre-offset" because, once the inner codec is encoded or decoded, an additional offset will be returned which we refer to as the "post-offset". That "post-offset" is important as, unless we are reaching the end of our codec, it will be used by any further codecs to continue encoding or decoding data.
By default, that "post-offset" is simply the addition of the "pre-offset" and the size of the encoded or decoded inner data.
However, you may also provide a
postOffset
function to adjust the "post-offset". For instance, let's push the "post-offset" 2 bytes forward as well such that any further codecs will start doing their job at the end of our 8-byteu32
number.Both the
preOffset
andpostOffset
functions offer the following attributes:bytes
: The entire byte array being encoded or decoded.preOffset
: The original and unaltered pre-offset.wrapBytes
: A helper function that wraps the given offset around the byte array length. E.g.wrapBytes(-1)
will refer to the last byte of the byte array.Additionally, the post-offset function also provides the following attributes:
newPreOffset
: The new pre-offset after the pre-offset function has been applied.postOffset
: The original and unaltered post-offset.Note that you may also decide to ignore these attributes to achieve absolute offsets. However, relative offsets are usually recommended as they won't break your codecs when composed with other codecs.
Also note that any negative offset or offset that exceeds the size of the byte array will throw a
SolanaError
of codeSOLANA_ERROR__CODECS__OFFSET_OUT_OF_RANGE
.To avoid this, you may use the
wrapBytes
function to wrap the offset around the byte array length. For instance, here's how we can use thewrapBytes
function to move the pre-offset 4 bytes from the end of the byte array.As you can see, the
offsetCodec
helper allows you to jump all over the place with your codecs. This non-linear approach to encoding and decoding data allows you to achieve complex serialization strategies that would otherwise be impossible.As usual, the
offsetEncoder
andoffsetDecoder
functions can also be used to split your codec logic into tree-shakeable functions.