Skip to content

Commit

Permalink
Merge pull request #2 from fossapps/feat/with_headers
Browse files Browse the repository at this point in the history
feat(searlie): add encode and decode with headers
  • Loading branch information
cyberhck authored Nov 1, 2019
2 parents 5e70925 + f88b728 commit 233d49f
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 13 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const serialization = new Searilie(new CSVCompressor());
console.log(serialization.encode([{a: "kick", b: 51}, {a: "cat", b: 92}])); // Bkick,51;cat,92
```

## deserialization
## Deserialization
the first character on encoded payload denotes which compressor was used, we need to use the same compressor to ensure we don't load everything at once, we don't import everything and check it for you.
```typescript
import {Searilie, ValueType} from "./src/Searilie"
Expand All @@ -45,3 +45,14 @@ const serialization = new Searilie(new CSVCompressor());
console.log(serialization.decode("Bkick,51;cat,92", {a: ValueType.String, b: ValueType.Number})); // [{a: "kick", b: 51}, {a: "cat", b: 92}]
console.log(serialization.decode("Bkick,51;cat,92", {myKey: ValueType.String, newKey: ValueType.Number})); // [{myKey: "kick", newKey: 51}, {myKey: "cat", newKey: 92}]
```

## With headers
by trading off some character spaces, we can also encode data with keys so we don't need to provide schema while decoding

```typescript
import {Searilie} from "./src/Searilie"
import {CSVCompressor} from "./src/adapters/CSVCompressor";
const serialization = new Searilie(new CSVCompressor());
console.log(serialization.encodeWithHeaders([{a: "kick", b: 51}, {a: "cat", b: 92}])); // Ba,|b:kick,51;cat,92
console.log(serialization.decodeUsingHeaders("Ba,|b:kick,51;cat,92")); // [{a: "kick", b: 51}, {a: "cat", b: 92}]
```
42 changes: 36 additions & 6 deletions src/Searilie.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {TinyCompressor} from "./adapters/TinyCompressor";
import {CSVCompressor, TinyCompressor} from "./adapters";
import {HEADER_SEPARATOR, INTEGER_IDENTIFIER, PAYLOAD_SEPARATOR} from "./constants";
import {IAdapter, IObject, Searilie, ValueType} from "./Searilie";

describe("Searilie", () => {
Expand All @@ -8,20 +9,18 @@ describe("Searilie", () => {
});
it("should throw error if identifier mismatches", () => {
const mockAdapter: IAdapter = {
deserialize: (): IObject[] => [{a: "2"}],
getIdentifier: () => "A",
serialize: () => "something"
};
} as any;
const serializer = new Searilie(mockAdapter);
expect(serializer.encode([{a: "s"}])).toBe("Asomething");
expect(() => serializer.decode("Bs", {a: ValueType.String})).toThrowError("adapter mismatched");
});
it("should deserialize using adapter", () => {
const mockAdapter: IAdapter = {
deserialize: (): IObject[] => [{a: 2}],
getIdentifier: () => "Z",
serialize: () => "something"
};
getIdentifier: () => "Z"
} as any;
const serializer = new Searilie(mockAdapter);
expect(serializer.decode("Z23", {a: ValueType.Number})).toStrictEqual([{a: 2}]);
});
Expand All @@ -30,4 +29,35 @@ describe("Searilie", () => {
expect(serializer.encode([{a: "h", b: 2}])).toEqual("Ah2");
expect(serializer.decode("Ah2", {a: ValueType.String, b: ValueType.Number})).toStrictEqual([{a: "h", b: 2}]);
});
describe("encode with headers", () => {
it("should have headers while encoding", () => {
const serializer = new Searilie(new TinyCompressor());
expect(serializer.encodeWithHeaders([{a: "h", b: 2}])).toBe(`Aa${HEADER_SEPARATOR}${INTEGER_IDENTIFIER}b${PAYLOAD_SEPARATOR}h2`);
});
});
describe("decode using headers", () => {
it("should throw error if wrong adapter", () => {
const adapter: IAdapter = {
getIdentifier: jest.fn(() => "Z")
} as any;
const serializer = new Searilie(adapter);
expect(() => serializer.decodeUsingHeaders(`Aa,${INTEGER_IDENTIFIER}b:h2`)).toThrow("adapter mismatched");
});
it("should be able to decode properly", () => {
const adapter: IAdapter = {
deserialize: jest.fn(() => [{a: 2}]),
getIdentifier: jest.fn(() => "A")
} as any;
const serializer = new Searilie(adapter);
expect(serializer.decodeUsingHeaders(`Aa,${INTEGER_IDENTIFIER}b:h2`)).toStrictEqual([{a: 2}]);
});
});
describe("decode and encode with headers", () => {
it("should decode and encode properly", () => {
const tinySerializer = new Searilie(new TinyCompressor());
const csv = new Searilie(new CSVCompressor());
expect(tinySerializer.encodeWithHeaders([{a: 2, b: 5}, {a: 3, b: 8}])).toBe(`A${INTEGER_IDENTIFIER}a${HEADER_SEPARATOR}${INTEGER_IDENTIFIER}b${PAYLOAD_SEPARATOR}2538`);
expect(csv.encodeWithHeaders([{a: "23", b: 2}, {a: "35", b: 100}])).toBe(`Ba${HEADER_SEPARATOR}${INTEGER_IDENTIFIER}b${PAYLOAD_SEPARATOR}23,2;35,100`);
});
});
});
60 changes: 54 additions & 6 deletions src/Searilie.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {HEADER_SEPARATOR, INTEGER_IDENTIFIER, PAYLOAD_SEPARATOR} from "./constants";

export interface IObject {
[key: string]: number | string;
}
Expand All @@ -20,20 +22,66 @@ export interface IAdapter {
deserialize(text: string, schema: ISchema): IObject[];
getIdentifier(): TIdentifier;
}
interface IPayloadInfo {
identifier: TIdentifier;
payload: string;
}

export class Searilie {
constructor(private adapter: IAdapter) {}
private static getIdentifierAndPayload(text: string): IPayloadInfo {
if (text.length === 0) {
throw new Error("Invalid payload");
}
const [firstCharacter, ...rest] = text;
return {
identifier: firstCharacter.toUpperCase() as TIdentifier,
payload: rest.join("")
};
}
public encodeWithHeaders(object: IObject[]): string {
const headers = this.getHeaders(object);
const encodedData = this.adapter.serialize(object);
return `${this.adapter.getIdentifier()}${headers}${PAYLOAD_SEPARATOR}${encodedData}`;
}

public getSchemaFromHeaders(text: string): ISchema {
const schema: ISchema = {};
const headers = text.split(":")[0];
headers.split(",").forEach((x) => {
const [first, ...rest] = x;
if (first === INTEGER_IDENTIFIER) {
schema[rest.join("")] = ValueType.Number;
} else {
schema[x] = ValueType.String;
}
});
return schema;
}
public decodeUsingHeaders(value: string): IObject[] {
const schema = this.getSchemaFromHeaders(value.split(":")[0]);
const {payload, identifier} = Searilie.getIdentifierAndPayload(value);
if (this.adapter.getIdentifier() !== identifier) {
throw new Error("adapter mismatched");
}
return this.adapter.deserialize(payload, schema);
}
public encode(object: IObject[]): string {
return `${this.adapter.getIdentifier()}${this.adapter.serialize(object)}`;
}

public decode(value: string, schema: ISchema): IObject[] {
if (value.length === 0) {
throw new Error("Invalid payload");
}
const [firstCharacter, ...rest] = value;
if (this.adapter.getIdentifier() !== firstCharacter) {
const {payload, identifier} = Searilie.getIdentifierAndPayload(value);
if (this.adapter.getIdentifier() !== identifier) {
throw new Error("adapter mismatched");
}
return this.adapter.deserialize(rest.join(""), schema);
return this.adapter.deserialize(payload, schema);
}

private getHeaders(objects: IObject[]): string {
return Object.keys(objects[0])
.sort()
.map((x) => typeof objects[0][x] === "number" ? `${INTEGER_IDENTIFIER}${x}` : x)
.join(HEADER_SEPARATOR);
}
}
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const INTEGER_IDENTIFIER = "|";
export const HEADER_SEPARATOR = ",";
export const PAYLOAD_SEPARATOR = "_";

0 comments on commit 233d49f

Please sign in to comment.