Repository: MaskingTechnology/jitar (ts)

Issue:
Buffer serialization
Currently, buffer objects are serialized using the class serializer. This works, but results into a `depricated` warning because it uses the constructor for the reconstruction. To avoid this warning (and future issues), we need to add a serializer for the buffer that uses the recommended `Buffer.from()` function the constructor.

Pull request:
feat: buffer serialization
Fixes #614 

Changes proposed in this pull request:
- Buffer serializer

@MaskingTechnology/jitar


Patch:
diff --git a/packages/serialization/src/SerializerBuilder.ts b/packages/serialization/src/SerializerBuilder.ts
index b033fd7..44fc1c7 100644
--- a/packages/serialization/src/SerializerBuilder.ts
+++ b/packages/serialization/src/SerializerBuilder.ts
@@ -3,6 +3,7 @@ import Serializer from './Serializer';
 import ClassResolver from './interfaces/ClassResolver';
 import ArraySerializer from './serializers/ArraySerializer';
 import BigIntSerializer from './serializers/BigIntSerializer';
+import BufferSerializer from './serializers/BufferSerializer';
 import ClassSerializer from './serializers/ClassSerializer';
 import DateSerializer from './serializers/DateSerializer';
 import ErrorSerializer from './serializers/ErrorSerializer';
@@ -37,6 +38,7 @@ export default class SerializerBuilder
         serializer.addSerializer(new MapSerializer());
         serializer.addSerializer(new ArraySerializer());
         serializer.addSerializer(new TypedArraySerializer());
+        serializer.addSerializer(new BufferSerializer());
 
         return serializer;
     }
diff --git a/packages/serialization/src/serializers/BufferSerializer.ts b/packages/serialization/src/serializers/BufferSerializer.ts
new file mode 100644
index 0000000..a241980
--- /dev/null
+++ b/packages/serialization/src/serializers/BufferSerializer.ts
@@ -0,0 +1,39 @@
+
+import ValueSerializer from '../ValueSerializer';
+import SerializedBuffer from '../types/serialized/SerializedBuffer';
+import InvalidBufferString from './errors/InvalidBufferString';
+
+export default class BufferSerializer extends ValueSerializer
+{
+    canSerialize(value: unknown): boolean
+    {
+        return value instanceof Buffer;
+    }
+
+    canDeserialize(value: unknown): boolean
+    {
+        const buffer = value as SerializedBuffer;
+
+        return buffer instanceof Object
+            && buffer.serialized === true
+            && buffer.name === 'Buffer'
+            && typeof buffer.base64 === 'string';
+    }
+
+    async serialize(buffer: Buffer): Promise<SerializedBuffer>
+    {
+        return { serialized: true, name: 'Buffer', base64: buffer.toString('base64') };
+    }
+
+    async deserialize(object: SerializedBuffer): Promise<Buffer>
+    {
+        try
+        {
+            return Buffer.from(object.base64, 'base64');
+        }
+        catch
+        {
+            throw new InvalidBufferString(object.base64);
+        }
+    }
+}
diff --git a/packages/serialization/src/serializers/errors/InvalidBufferString.ts b/packages/serialization/src/serializers/errors/InvalidBufferString.ts
new file mode 100644
index 0000000..c1e51b0
--- /dev/null
+++ b/packages/serialization/src/serializers/errors/InvalidBufferString.ts
@@ -0,0 +1,8 @@
+
+export default class InvalidBufferString extends Error
+{
+    constructor(bufferString: string)
+    {
+        super(`Invalid Buffer string '${bufferString}'`);
+    }
+}
diff --git a/packages/serialization/src/types/serialized/SerializedBuffer.ts b/packages/serialization/src/types/serialized/SerializedBuffer.ts
new file mode 100644
index 0000000..e75a611
--- /dev/null
+++ b/packages/serialization/src/types/serialized/SerializedBuffer.ts
@@ -0,0 +1,9 @@
+
+import Serialized from '../Serialized';
+
+type SerializedBuffer = Serialized &
+{
+    base64: string
+};
+
+export default SerializedBuffer;


Test patch:
diff --git a/packages/serialization/test/serializers/BufferSerializer.spec.ts b/packages/serialization/test/serializers/BufferSerializer.spec.ts
new file mode 100644
index 0000000..47c1122
--- /dev/null
+++ b/packages/serialization/test/serializers/BufferSerializer.spec.ts
@@ -0,0 +1,80 @@
+
+import { describe, expect, it } from 'vitest';
+
+import BufferSerializer from '../../src/serializers/BufferSerializer';
+
+import { BUFFERS } from './fixtures';
+
+const serializer = new BufferSerializer();
+
+describe('serializers/BufferSerializer', () =>
+{
+    describe('.canSerialize(value)', () =>
+    {
+        it('should tell it can serialize a buffer', () =>
+        {
+            const supportsBuffer = serializer.canSerialize(BUFFERS.VALID);
+
+            expect(supportsBuffer).toBeTruthy();
+        });
+
+        it('should tell it cannot serialize others', () =>
+        {
+            const supportsNonObject = serializer.canSerialize(BUFFERS.NON_OBJECT);
+            const supportsNonBuffer = serializer.canSerialize(BUFFERS.NON_BUFFER);
+
+            expect(supportsNonObject).toBeFalsy();
+            expect(supportsNonBuffer).toBeFalsy();
+        });
+    });
+
+    describe('.canDeserialize(value)', () =>
+    {
+        it('should tell it can deserialize a buffer', () =>
+        {
+            const supportsBuffer = serializer.canDeserialize(BUFFERS.VALID_SERIALIZED);
+
+            expect(supportsBuffer).toBeTruthy();
+        });
+
+        it('should tell it cannot deserialize others', () =>
+        {
+            const supportsNonObject = serializer.canDeserialize(BUFFERS.NON_OBJECT);
+            const supportsNotSerialized = serializer.canDeserialize(BUFFERS.NOT_SERIALIZED);
+            const supportsInvalidName = serializer.canDeserialize(BUFFERS.INVALID_NAME);
+            const supportsInvalidBufferValue = serializer.canDeserialize(BUFFERS.INVALID_BUFFER_VALUE);
+
+            expect(supportsNonObject).toBeFalsy();
+            expect(supportsNotSerialized).toBeFalsy();
+            expect(supportsInvalidName).toBeFalsy();
+            expect(supportsInvalidBufferValue).toBeFalsy();
+        });
+    });
+
+    describe('.serialize(buffer)', () =>
+    {
+        it('should serialize a buffer', async () =>
+        {
+            const resultValidBuffer = await serializer.serialize(BUFFERS.VALID);
+
+            expect(resultValidBuffer).toStrictEqual(BUFFERS.VALID_SERIALIZED);
+        });
+    });
+
+    describe('.deserialize(object)', () =>
+    {
+        it('should deserialize a buffer', async () =>
+        {
+            const resultValidBuffer = await serializer.deserialize(BUFFERS.VALID_SERIALIZED);
+
+            expect(resultValidBuffer).toStrictEqual(BUFFERS.VALID);
+        });
+
+        it('should ignore invalid characters in buffer string', async () =>
+        {
+            const resultInvalidBuffer = await serializer.deserialize(BUFFERS.INVALID_BUFFER_STRING);
+
+            expect(resultInvalidBuffer).toStrictEqual(BUFFERS.VALID);
+        });
+    });
+});
diff --git a/packages/serialization/test/serializers/fixtures/buffers.fixture.ts b/packages/serialization/test/serializers/fixtures/buffers.fixture.ts
new file mode 100644
index 0000000..2e83fa3
--- /dev/null
+++ b/packages/serialization/test/serializers/fixtures/buffers.fixture.ts
@@ -0,0 +1,26 @@
+
+const VALID_BASE64_DATA = 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABEElEQVR42mL8//8/AyUYIIgB';
+const INVALID_BASE64_DATA = `##${VALID_BASE64_DATA}??`;
+
+const validBuffer = Buffer.from(VALID_BASE64_DATA, 'base64');
+
+const serializedValidBuffer = { serialized: true, name: 'Buffer', base64: VALID_BASE64_DATA };
+
+const nonObject = 42;
+const nonBuffer = new Map();
+const notSerialized = { name: 'Buffer', base64: '' };
+const invalidName = { serialized: true, name: 'Map', base64: VALID_BASE64_DATA };
+const invalidBufferValue = { serialized: true, name: 'Buffer', base64: true };
+const invalidBufferString = { serialized: true, name: 'Buffer', base64: INVALID_BASE64_DATA };
+
+export const BUFFERS =
+{
+    VALID: validBuffer,
+    VALID_SERIALIZED: serializedValidBuffer,
+    NON_OBJECT: nonObject,
+    NON_BUFFER: nonBuffer,
+    NOT_SERIALIZED: notSerialized,
+    INVALID_NAME: invalidName,
+    INVALID_BUFFER_VALUE: invalidBufferValue,
+    INVALID_BUFFER_STRING: invalidBufferString
+};
diff --git a/packages/serialization/test/serializers/fixtures/index.ts b/packages/serialization/test/serializers/fixtures/index.ts
index 31f5306..d5f30f2 100644
--- a/packages/serialization/test/serializers/fixtures/index.ts
+++ b/packages/serialization/test/serializers/fixtures/index.ts
@@ -1,6 +1,7 @@
 
 export * from './arrays.fixture';
 export * from './bigIntegers.fixture';
+export * from './buffers.fixture';
 export * from './classes.fixture';
 export * from './classResolver.fixture';
 export * from './dates.fixture';