diff --git a/docs/generated/changelog.html b/docs/generated/changelog.html index 1ec36b0ce..53a669bd1 100644 --- a/docs/generated/changelog.html +++ b/docs/generated/changelog.html @@ -28,6 +28,10 @@

Version 0.13.3

This can be used to set proper Expiry times when a device has fallen out of sync with the replica. +
  • + Fixes a candid bug where when decoding, optional fields could be skipped if the data on + the wire contains additional fields. +
  • Version 0.13.2

    diff --git a/packages/candid/src/idl.test.ts b/packages/candid/src/idl.test.ts index c25a55354..7ab98b6b0 100644 --- a/packages/candid/src/idl.test.ts +++ b/packages/candid/src/idl.test.ts @@ -660,3 +660,20 @@ test('should correctly decode expected optional fields with lower hash than requ upgrade: [true], }); }); + +test('should decode matching optional fields if wire type contains additional fields', () => { + const InputType = IDL.Record({ + a: IDL.Text, + b: IDL.Opt(IDL.Text), + }); + const OutputType = IDL.Record({ + b: IDL.Opt(IDL.Text), + }); + + const encoded = IDL.encode([InputType], [{ a: 'abc', b: ['123'] }]); + const decoded = IDL.decode([OutputType], encoded)[0]; + + expect(decoded).toEqual({ + b: ['123'], + }); +}); diff --git a/packages/candid/src/idl.ts b/packages/candid/src/idl.ts index 08766f828..c2de9b8b9 100644 --- a/packages/candid/src/idl.ts +++ b/packages/candid/src/idl.ts @@ -991,25 +991,26 @@ export class RecordClass extends ConstructType> { } const [expectKey, expectType] = this._fields[expectedRecordIdx]; - if (idlLabelToId(this._fields[expectedRecordIdx][0]) !== idlLabelToId(hash)) { - // the current field on the wire does not match the expected field - - // skip expected optional fields that are not present on the wire + const expectedId = idlLabelToId(this._fields[expectedRecordIdx][0]); + const actualId = idlLabelToId(hash); + if (expectedId === actualId) { + // the current field on the wire matches the expected field + x[expectKey] = expectType.decodeValue(b, type); + expectedRecordIdx++; + actualRecordIdx++; + } else if (actualId > expectedId) { + // The expected field does not exist on the wire if (expectType instanceof OptClass || expectType instanceof ReservedClass) { x[expectKey] = []; expectedRecordIdx++; - continue; + } else { + throw new Error('Cannot find required field ' + expectKey); } - - // skip unexpected interspersed fields present on the wire + } else { + // The field on the wire does not exist in the output type, so we can skip it type.decodeValue(b, type); actualRecordIdx++; - continue; } - - x[expectKey] = expectType.decodeValue(b, type); - expectedRecordIdx++; - actualRecordIdx++; } // initialize left over expected optional fields