This repository has been archived by the owner on Jul 20, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(initial): add initial mappers and data mapper chain
- Loading branch information
Per Kristian Kummermo
committed
May 2, 2018
1 parent
e4d96b9
commit 0853dab
Showing
17 changed files
with
9,343 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
{ | ||
"name": "@exploratoryengineering/data-mapper-chain", | ||
"version": "0.1.0", | ||
"description": "Simple data mapper library meant to be run in browser to ease data transformation for IoT devices in JS", | ||
"main": "index.ts", | ||
"scripts": { | ||
"commitmsg": "validate-commit-msg", | ||
"release": "standard-version --silent", | ||
"release:patch": "npm run release -- --release-as patch", | ||
"release:minor": "npm run release -- --release-as minor", | ||
"release:major": "npm run release -- --release-as major", | ||
"test": "npm run test:unit && npm run test:tslint", | ||
"test:unit": "del-cli test/coverage-jest && jest --config jest.config.json --forceExit", | ||
"test:tslint": "tslint \"src/**/*.ts\" -c tslint.json" | ||
}, | ||
"keywords": [ | ||
"iot", | ||
"data", | ||
"transform" | ||
], | ||
"author": "Per Kristian Kummermo", | ||
"license": "Apache-2.0", | ||
"devDependencies": { | ||
"@telenorfrontend/tslint-config-telenor": "^0.1.1", | ||
"@types/jest": "^22.2.3", | ||
"del-cli": "^1.1.0", | ||
"husky": "^0.14.3", | ||
"jest": "^22.4.3", | ||
"jest-cli": "^22.4.3", | ||
"jest-junit-reporter": "^1.1.0", | ||
"standard-version": "^4.3.0", | ||
"ts-jest": "^22.4.4", | ||
"tslint": "^5.9.1", | ||
"typescript": "^2.8.3", | ||
"validate-commit-msg": "^2.14.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
import DataMapperChain, { AVAILABLE_MAPPERS_TYPES } from "./DataMapperChain"; | ||
|
||
class MapperMock implements IMapper { | ||
initParams: any = {}; | ||
|
||
constructor(params = {}) { | ||
this.initParams = params; | ||
} | ||
|
||
transform(value: IDataValue): IDataValue { | ||
return { | ||
...value, ...{ | ||
name: "MOCK WAS HERE", | ||
}, | ||
}; | ||
} | ||
|
||
config(): IMapperConfig { | ||
return { | ||
ident: "MOCK", | ||
params: { | ||
p1: 1, | ||
p2: "2", | ||
}, | ||
}; | ||
} | ||
} | ||
|
||
describe("Data mapper chain", () => { | ||
let dataMapperChain: DataMapperChain; | ||
|
||
beforeEach(() => { | ||
dataMapperChain = new DataMapperChain(); | ||
dataMapperChain.addNewMapperType({ | ||
id: "MOCK", | ||
value: "My mock mapper", | ||
entity: MapperMock, | ||
}); | ||
}); | ||
|
||
describe("Initializing", () => { | ||
it("should initially have 0 mappers", () => { | ||
expect(dataMapperChain.mappers.length).toBe(0); | ||
}); | ||
|
||
it("should allow for init with mappers", () => { | ||
const mappers: IMapper[] = []; | ||
|
||
mappers.push(new MapperMock()); | ||
|
||
dataMapperChain = new DataMapperChain({ | ||
mappers: mappers, | ||
}); | ||
|
||
expect(dataMapperChain.mappers.length).toBe(1); | ||
}); | ||
|
||
it("should be correctly named upon init", () => { | ||
dataMapperChain = new DataMapperChain({ | ||
name: "test", | ||
}); | ||
|
||
expect(dataMapperChain.name).toBe("test"); | ||
}); | ||
}); | ||
|
||
describe("Config de-/serialization", () => { | ||
it("should correctly serialize when no mappers", () => { | ||
dataMapperChain.name = "Test"; | ||
dataMapperChain.mappers = []; | ||
|
||
const serializedConfig = dataMapperChain.serializeConfig(); | ||
expect(serializedConfig).toBe("{\"name\":\"Test\",\"mappers\":[]}"); | ||
}); | ||
|
||
it("should correctly serialize with mapper", () => { | ||
dataMapperChain.addMapper(new MapperMock()); | ||
|
||
const serializedConfig = dataMapperChain.serializeConfig(); | ||
expect(serializedConfig).toBe("{\"name\":\"\",\"mappers\":[{\"ident\":\"MOCK\",\"params\":{\"p1\":1,\"p2\":\"2\"}}]}"); | ||
}); | ||
|
||
it("should correctly deserialize with no mappers", () => { | ||
const serializedConfig = "{\"name\":\"Test\",\"mappers\":[]}"; | ||
|
||
dataMapperChain.loadConfig(serializedConfig); | ||
|
||
expect(dataMapperChain.name).toBe("Test"); | ||
}); | ||
|
||
it("should correctly deserialize with mapper and corresponding config", () => { | ||
const serializedConfig = "{\"name\":\"Test\",\"mappers\":[{\"ident\":\"MOCK\",\"params\":{\"p1\":1,\"p2\":\"2\"}}]}"; | ||
|
||
dataMapperChain.loadConfig(serializedConfig); | ||
|
||
expect(dataMapperChain.name).toBe("Test"); | ||
expect(dataMapperChain.mappers.length).toBe(1); | ||
expect((dataMapperChain.mappers[0] as MapperMock).initParams).toEqual({ | ||
p1: 1, | ||
p2: "2", | ||
}); | ||
}); | ||
}); | ||
|
||
describe("Mapper configuration", () => { | ||
it("should correctly add new mapper type", () => { | ||
dataMapperChain.addNewMapperType({ | ||
id: "MYNEWMOCKMAPPER", | ||
value: "My mock mapper", | ||
entity: MapperMock, | ||
}); | ||
|
||
expect(dataMapperChain.findMapperTypeById("MYNEWMOCKMAPPER")).not.toBe(undefined); | ||
}); | ||
|
||
it("should correctly return mapper initiated with params if type is present", () => { | ||
const mapperRes = dataMapperChain.getMapperByConfig({ | ||
ident: "MOCK", | ||
params: "params", | ||
}); | ||
|
||
expect(mapperRes).not.toBe(false); | ||
expect((mapperRes as MapperMock).initParams).toBe("params"); | ||
}); | ||
|
||
it("should correctly return false when no mapper type found", () => { | ||
const mapperRes = dataMapperChain.getMapperByConfig({ | ||
ident: "NONEXISTANT", | ||
params: {}, | ||
}); | ||
|
||
expect(mapperRes).toBe(false); | ||
}); | ||
}); | ||
|
||
describe("Data transformation", () => { | ||
it("should work as intended with no mappers", () => { | ||
const inputObj = { | ||
name: "name", | ||
value: "babe", | ||
}; | ||
|
||
const transformRes = dataMapperChain.mapData(inputObj); | ||
|
||
expect(transformRes).toEqual(inputObj); | ||
}); | ||
|
||
it("should allow for empty input in map data", () => { | ||
const transformRes = dataMapperChain.mapData(); | ||
expect(transformRes).toEqual({ | ||
name: "Unnamed data", | ||
value: "", | ||
}); | ||
}); | ||
|
||
it("should allow for partial input in map data", () => { | ||
const transformRes = dataMapperChain.mapData({ | ||
value: "my value", | ||
}); | ||
expect(transformRes).toEqual({ | ||
name: "Unnamed data", | ||
value: "my value", | ||
}); | ||
}); | ||
|
||
it("should correctly run transform on added mappers", () => { | ||
const inputObj = { | ||
name: "name", | ||
value: "babe", | ||
}; | ||
dataMapperChain.addMapper(new MapperMock()); | ||
const transformRes = dataMapperChain.mapData(inputObj); | ||
|
||
expect(transformRes).toEqual({ | ||
name: "MOCK WAS HERE", | ||
value: "babe", | ||
}); | ||
}); | ||
}); | ||
|
||
describe("Invalid input", () => { | ||
it("should correctly ignore serialized unknown mappers", () => { | ||
const serializedConfig = "{\"name\":\"Test\",\"mappers\":[{\"ident\":\"UNKNOWNMAPPER\",\"params\":{\"p1\":1,\"p2\":\"2\"}}]}"; | ||
dataMapperChain.loadConfig(serializedConfig); | ||
|
||
expect(dataMapperChain.name).toBe("Test"); | ||
expect(dataMapperChain.mappers.length).toBe(0); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import Base64 from "./sensor-data-mapper/Base64"; | ||
import Chunk from "./sensor-data-mapper/Chunk"; | ||
import FromJSON from "./sensor-data-mapper/FromJSON"; | ||
import HexToInt from "./sensor-data-mapper/HexToInt"; | ||
import Offset from "./sensor-data-mapper/Offset"; | ||
|
||
interface MapperType { | ||
id: string; | ||
value: string; | ||
entity: { new(params): IMapper }; | ||
} | ||
|
||
export let AVAILABLE_MAPPERS_TYPES: MapperType[] = [{ | ||
id: Chunk.ident, | ||
value: Chunk.description, | ||
entity: Chunk, | ||
}, { | ||
id: HexToInt.ident, | ||
value: HexToInt.description, | ||
entity: HexToInt, | ||
}, { | ||
id: Offset.ident, | ||
value: Offset.description, | ||
entity: Offset, | ||
}, { | ||
id: Base64.ident, | ||
value: Base64.description, | ||
entity: Base64, | ||
}, { | ||
id: FromJSON.ident, | ||
value: FromJSON.description, | ||
entity: FromJSON, | ||
}]; | ||
|
||
export default class DataMapperChain { | ||
mappers: IMapper[] = []; | ||
initialValue: IDataValue; | ||
name: string = ""; | ||
|
||
constructor({ | ||
mappers = [], | ||
name = "", | ||
} = {}) { | ||
this.name = name; | ||
this.mappers = mappers; | ||
} | ||
|
||
serializeConfig() { | ||
return JSON.stringify({ | ||
name: this.name, | ||
mappers: this.mappers.map((mapper) => { | ||
return mapper.config(); | ||
}), | ||
}); | ||
} | ||
|
||
loadConfig(configString: string) { | ||
const parsedConfig = JSON.parse(configString); | ||
this.name = parsedConfig.name; | ||
const mapperConfigs: IMapperConfig[] = parsedConfig.mappers; | ||
|
||
mapperConfigs.forEach((config) => { | ||
const mapper = this.getMapperByConfig(config); | ||
if (mapper) { | ||
this.addMapper(mapper); | ||
} | ||
}); | ||
} | ||
|
||
getMapperByConfig(config: IMapperConfig): IMapper | false { | ||
const mapperType = this.findMapperTypeById(config.ident); | ||
if (mapperType) { | ||
return new mapperType.entity(config.params); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
addMapper(mapper: IMapper) { | ||
this.mappers.push(mapper); | ||
} | ||
|
||
addNewMapperType(mapperType: MapperType) { | ||
const existingMapper = this.findMapperTypeById(mapperType.id); | ||
if (!existingMapper) { | ||
AVAILABLE_MAPPERS_TYPES.push(mapperType); | ||
} else { | ||
AVAILABLE_MAPPERS_TYPES.splice( | ||
AVAILABLE_MAPPERS_TYPES.indexOf(existingMapper), | ||
1, | ||
mapperType, | ||
); | ||
} | ||
} | ||
|
||
findMapperTypeById(id: string) { | ||
return AVAILABLE_MAPPERS_TYPES.find((mapperType) => { | ||
return mapperType.id === id; | ||
}); | ||
} | ||
|
||
mapData({ name = "Unnamed data", value = "" } = {}) { | ||
this.initialValue = { | ||
name: name, | ||
value: value, | ||
}; | ||
|
||
return this.mappers.reduce((curr, mapper, idx) => { | ||
return mapper.transform(curr); | ||
}, this.initialValue); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import Base64, { Base64Actions } from "./Base64"; | ||
|
||
describe("Base64 mapper", () => { | ||
let base64: Base64; | ||
const helloWorldBase64: string = "aGVsbG8gd29ybGQ="; | ||
const helloWorldString: string = "hello world"; | ||
|
||
beforeEach(() => { | ||
base64 = new Base64(); | ||
}); | ||
|
||
describe("Base64 encoding/decoding", () => { | ||
it("should correctly encode a string given", () => { | ||
base64.action = Base64Actions.ENCODE; | ||
const transformRes = base64.transform({ | ||
name: "data", | ||
value: helloWorldString, | ||
}); | ||
|
||
expect(transformRes).toEqual({ | ||
name: "data", | ||
value: helloWorldBase64, | ||
}); | ||
}); | ||
|
||
it("should correctly decode a base 64 string given", () => { | ||
base64.action = Base64Actions.DECODE; | ||
const transformRes = base64.transform({ | ||
name: "data", | ||
value: helloWorldBase64, | ||
}); | ||
|
||
expect(transformRes).toEqual({ | ||
name: "data", | ||
value: helloWorldString, | ||
}); | ||
}); | ||
}); | ||
|
||
describe("Invalid input", () => { | ||
it("should correctly return the given object if no value is present", () => { | ||
const inputObj = { | ||
name: "name", | ||
value: "", | ||
}; | ||
|
||
const transformRes = base64.transform(inputObj); | ||
|
||
expect(transformRes).toBe(inputObj); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.