Skip to content

Commit

Permalink
Add replacer
Browse files Browse the repository at this point in the history
  • Loading branch information
superman2211 committed Jan 31, 2023
1 parent 80754d2 commit beeadbc
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/olive-teachers-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@xobj/core": minor
---

Add replacer via function map and entries
6 changes: 6 additions & 0 deletions packages/core/src/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { BufferReader } from '@xobj/buffer';
import { decodeHeader } from './decoders/header';
import { DecodeMethod, DECODERS, decodeValue } from './decoders/index';
import { getReplacer, ReplacerMethod, ReplacerType } from './replacer';
import { FloatQuality, ValueType } from './types';
import { VERSION } from './version';

Expand All @@ -12,10 +13,12 @@ export interface DecodeContext {
readonly decoders: Map<ValueType, DecodeMethod>;
readonly version: number;
readonly floatQuality: FloatQuality;
readonly replacer: ReplacerMethod;
}

export interface DecodeOptions {
readonly customDecode?: DecodeMethod;
readonly replacer?: ReplacerType;
}

export function decode(buffer: ArrayBuffer, options?: DecodeOptions): any {
Expand All @@ -27,13 +30,16 @@ export function decode(buffer: ArrayBuffer, options?: DecodeOptions): any {
decoders.set(ValueType.CUSTOM, options.customDecode);
}

const replacer = getReplacer(options?.replacer);

const context: DecodeContext = {
reader,
values: [],
links: [],
decoders,
version: 0,
floatQuality: 'double',
replacer,
};

decodeHeader(context);
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/decoders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export const DECODERS = new Map<ValueType, DecodeMethod>([
]);

export function decodeValue(context: DecodeContext): any {
const { reader, decoders } = context;
const { reader, decoders, replacer } = context;

const type: ValueType = reader.readUintVar();

Expand All @@ -88,5 +88,6 @@ export function decodeValue(context: DecodeContext): any {
throw `Decoder method not found for type: ${type}`;
}

return decodeMethod(context);
const value = decodeMethod(context);
return replacer(value);
}
3 changes: 1 addition & 2 deletions packages/core/src/decoders/symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { DecodeContext } from '../decode';

export function decodeSymbol(context: DecodeContext): symbol {
const { links } = context;
// eslint-disable-next-line symbol-description
const symbol = Symbol();
const symbol = Symbol(0);
links.push(symbol);
return symbol;
}
6 changes: 6 additions & 0 deletions packages/core/src/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DetectMethod, DETECTORS } from './detectors/index';
import { EncodeMethod, ENCODERS, encodeValue } from './encoders/index';
import { encodeHeader } from './encoders/header';
import { FloatQuality, ValueType } from './types';
import { getReplacer, ReplacerMethod, ReplacerType } from './replacer';

export interface EncodeContext {
readonly writer: IBufferWriter,
Expand All @@ -12,13 +13,15 @@ export interface EncodeContext {
readonly detectors: DetectMethod[];
readonly encoders: Map<ValueType, EncodeMethod>,
readonly floatQuality: FloatQuality;
readonly replacer: ReplacerMethod;
}

export interface EncodeOptions {
readonly bufferSize?: number;
readonly customDetect?: DetectMethod;
readonly customEncode?: EncodeMethod;
readonly floatQuality?: FloatQuality;
readonly replacer?: ReplacerType;
}

export function encode(value: any, options?: EncodeOptions): ArrayBuffer {
Expand All @@ -38,13 +41,16 @@ export function encode(value: any, options?: EncodeOptions): ArrayBuffer {

const floatQuality = options?.floatQuality ?? 'double';

const replacer = getReplacer(options?.replacer);

const context: EncodeContext = {
writer,
values: [],
links: [],
detectors,
encoders,
floatQuality,
replacer,
};

encodeHeader(context);
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/encoders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ export const ENCODERS = new Map<ValueType, EncodeMethod>([
]);

export function encodeValue(value: any, context: EncodeContext) {
const { writer, encoders } = context;
const { writer, encoders, replacer } = context;

value = replacer(value);

const type = detectValue(value, context);
writer.writeUintVar(type);
Expand Down
34 changes: 34 additions & 0 deletions packages/core/src/replacer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export type ReplacerMethod = (value: any) => any;

export type MapEntries<K, V> = readonly (readonly [K, V])[];

export type ReplacerType = ReplacerMethod | Map<any, any> | MapEntries<any, any>;

export const defaultReplacer: ReplacerMethod = (value) => value;

export function replacerFromTable(table: Map<any, any>): ReplacerMethod {
return (value) => {
if (table.has(value)) return table.get(value);
return value;
};
}

export function getReplacer(replacerOption: ReplacerType | undefined): ReplacerMethod {
if (!replacerOption) {
return defaultReplacer;
}

if (typeof replacerOption === 'function') {
return replacerOption;
}

if (replacerOption instanceof Map) {
return replacerFromTable(replacerOption);
}

if (Array.isArray(replacerOption)) {
return replacerFromTable(new Map(replacerOption));
}

throw 'Incorrect replacer type. It must be a function, a map, or entries.';
}
104 changes: 104 additions & 0 deletions packages/core/test/replacer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/* eslint-disable no-undef */

import { encode } from '../src/encode';
import { decode } from '../src/decode';

describe('replacer', () => {
it('should replace function and symbol via function', () => {
const id = Symbol('id');

const source = {
x: 1,
update(value: number) {
this.x += value;
},
id,
};

const buffer = encode(source, {
replacer: (value) => {
if (value === id) return 'id-0';
if (value === source.update) return 345678;
return value;
},
});

const target = decode(buffer, {
replacer: (value) => {
if (value === 'id-0') return id;
if (value === 345678) return source.update;
return value;
},
});

expect(target).toEqual(source);
});

it('should replace function and symbol via map table', () => {
const id = Symbol('id');

const source = {
x: 1,
update(value: number) {
this.x += value;
},
id,
};

const buffer = encode(source, {
replacer: new Map<any, any>([
[id, 'id-0'],
[source.update, 345678],
]),
});

const target = decode(buffer, {
replacer: new Map<any, any>([
['id-0', id],
[345678, source.update],
]),
});

expect(target).toEqual(source);
});

it('should replace function and symbol via entries', () => {
const id = Symbol('id');

const source = {
x: 1,
update(value: number) {
this.x += value;
},
id,
};

const buffer = encode(source, {
replacer: [
[id, 'id-0'],
[source.update, 345678],
],
});

const target = decode(buffer, {
replacer: [
['id-0', id],
[345678, source.update],
],
});

expect(target).toEqual(source);
});

it('should throw an error when incorrect replacer', () => {
const source = { x: 1, y: 2 };

const buffer = encode(source);

const act = () => {
decode(buffer, { replacer: 'incorrect replacer' as any as Map<any, any> });
};

expect(act).toThrow('Incorrect replacer type. It must be a function, a map, or entries.');
});
});

0 comments on commit beeadbc

Please sign in to comment.