Skip to content

[WIP] Feature/protocol json #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/is-node/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.js
*.js.map
*.d.ts
18 changes: 18 additions & 0 deletions packages/is-node/__tests__/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {isNode} from '../';

describe('isNode', () => {
it('should return true when running in a Node.JS environment', () => {
// jest only runs in node, so this will always be true
expect(isNode()).toBe(true);
});

it('should return false when the global process object does not exist', () => {
const process = global.process;
try {
delete global.process;
expect(isNode()).toBe(false);
} finally {
global.process = process;
}
});
});
3 changes: 3 additions & 0 deletions packages/is-node/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function isNode(): boolean {
return Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]';
}
19 changes: 19 additions & 0 deletions packages/is-node/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@aws/is-node",
"private": true,
"version": "0.0.1",
"description": "Provides a function for detecting if the host environment is Node.JS",
"scripts": {
"pretest": "tsc",
"test": "jest"
},
"author": "[email protected]",
"license": "UNLICENSED",
"main": "index.js",
"devDependencies": {
"@types/jest": "^19.2.2",
"@types/node": "^7.0.12",
"jest": "^19.0.2",
"typescript": "^2.3"
}
}
8 changes: 8 additions & 0 deletions packages/is-node/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": true,
"strict": true
}
}
4 changes: 4 additions & 0 deletions packages/protocol-json-body/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/node_modules/
*.js
*.js.map
*.d.ts
171 changes: 171 additions & 0 deletions packages/protocol-json-body/__tests__/JsonBody.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import {JsonBody} from "../lib/JsonBody";
import {
Blob,
List,
Map as MapShape,
Timestamp,
Structure,
} from "@aws/types";

describe('JsonBody', () => {
describe('structures', () => {
const structure: Structure = {
type: "structure",
required: [],
members: {
foo: {shape: {type: 'string'}},
bar: {shape: {type: 'string'}},
baz: {
shape: {type: 'string'},
locationName: 'quux',
},
}
};
const jsonBody = new JsonBody(jest.fn(), jest.fn());

it('should serialize known properties of a structure', () => {
const toSerialize = {foo: 'fizz', bar: 'buzz'};
expect(jsonBody.build(structure, toSerialize))
.toBe(JSON.stringify(toSerialize));
});

it('should ignore unknown properties', () => {
const toSerialize = {foo: 'fizz', bar: 'buzz'};
expect(jsonBody.build(structure, {...toSerialize, pop: 'weasel'}))
.toBe(JSON.stringify(toSerialize));
});

it('should serialize properties to the locationNames', () => {
expect(jsonBody.build(structure, {baz: 'value'}))
.toEqual(JSON.stringify({quux: 'value'}));
});
});

describe('lists', () => {
const listShape: List = {
type: 'list',
member: {shape: {type: 'string'}},
};
const jsonBody = new JsonBody(jest.fn(), jest.fn());

it('should serialize arrays', () => {
expect(jsonBody.build(listShape, ['foo', 'bar', 'baz']))
.toEqual(JSON.stringify(['foo', 'bar', 'baz']));
});

it('should serialize iterators', () => {
const iterator = function* () {
yield 'foo';
yield 'bar';
yield 'baz';
};

expect(jsonBody.build(listShape, iterator()))
.toEqual(JSON.stringify(['foo', 'bar', 'baz']));
});
});

describe('maps', () => {
const mapShape: MapShape = {
type: 'map',
key: {shape: {type: 'string'}},
value: {shape: {type: 'number'}}
};
const jsonBody = new JsonBody(jest.fn(), jest.fn());

it('should serialize objects', () => {
const object = {
foo: 0,
bar: 1,
baz: 2,
};

expect(jsonBody.build(mapShape, object))
.toEqual(JSON.stringify(object));
});

it('should serialize [key, value] iterables (like ES6 maps)', () => {
const iterator = function* () {
yield ['foo', 0];
yield ['bar', 1];
yield ['baz', 2];
};

expect(jsonBody.build(mapShape, iterator()))
.toEqual(JSON.stringify({
foo: 0,
bar: 1,
baz: 2,
}));
});
});

describe('blobs', () => {
const blobShape: Blob = {type: 'blob'};
const base64Encode = jest.fn(arg => arg);
const utf8Decode = jest.fn(arg => arg);
const jsonBody = new JsonBody(base64Encode, utf8Decode);

beforeEach(() => {
base64Encode.mockClear();
utf8Decode.mockClear();
});

it('should base64 encode ArrayBuffers', () => {
jsonBody.build(blobShape, new ArrayBuffer(2));

expect(base64Encode.mock.calls.length).toBe(1);
});

it('should base64 encode ArrayBufferViews', () => {
jsonBody.build(blobShape, Uint8Array.from([0]));

expect(base64Encode.mock.calls.length).toBe(1);
});

it('should utf8 decode and base64 encode strings', () => {
jsonBody.build(blobShape, 'foo' as any);

expect(base64Encode.mock.calls.length).toBe(1);
expect(utf8Decode.mock.calls.length).toBe(1);
});
});

describe('timestamps', () => {
const timestampShape: Timestamp = {type: "timestamp"};
const date = new Date('2017-05-22T19:33:14.175Z');
const timestamp = 1495481594;
const jsonBody = new JsonBody(jest.fn(), jest.fn());

it('should convert Date objects to epoch timestamps', () => {
expect(jsonBody.build(timestampShape, date))
.toBe(JSON.stringify(timestamp));
});

it('should convert date strings to epoch timestamps', () => {
expect(jsonBody.build(timestampShape, date.toUTCString() as any))
.toBe(JSON.stringify(timestamp));
});

it('should preserve numbers as epoch timestamps', () => {
expect(jsonBody.build(timestampShape, timestamp as any))
.toBe(JSON.stringify(timestamp));
});
});

describe('scalars', () => {
it('should echo back scalars in their JSON-ified form', () => {
const cases = [
[{type: 'string'}, 'string'],
[{type: 'number'}, 1],
[{type: 'boolean'}, true],
];
const jsonBody = new JsonBody(jest.fn(), jest.fn());

for (let [shape, scalar] of cases) {
expect(jsonBody.build(shape as any, scalar as any))
.toBe(JSON.stringify(scalar));
}
});
});
});
1 change: 1 addition & 0 deletions packages/protocol-json-body/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/JsonBody';
93 changes: 93 additions & 0 deletions packages/protocol-json-body/lib/JsonBody.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import {
Decoder,
Encoder,
SerializationModel,
} from "@aws/types";
import {epoch} from "@aws/protocol-timestamp";

type Scalar = string|number|boolean|null;

interface Json {
[x: string]: Scalar|Json|JsonArray;
}

interface JsonArray extends Array<Scalar|Json|JsonArray> {}

type JsonValue = Scalar|Json|JsonArray;

export class JsonBody {
constructor(
private readonly base64Encoder: Encoder,
private readonly utf8Decoder: Decoder
) {}

public build(shape: SerializationModel, input: object): string {
return JSON.stringify(this.format(shape, input));
}

private format(shape: SerializationModel, input: any): JsonValue {
if (shape.type === 'structure') {
const data: Json = {};
// The validator should have ensured that this input is an object
for (let key of Object.keys(input)) {
if (
input[key] === undefined ||
input[key] === null ||
!(key in shape.members)
) {
continue;
}

const {locationName = key} = shape.members[key];
data[locationName] = this.format(
shape.members[key].shape,
input[key]
);
}

return data;
} else if (shape.type === 'list') {
const data: JsonArray = [];
// The validator should have ensured that this input is an iterable
for (let element of input) {
data.push(this.format(shape.member.shape, element));
}

return data;
} else if (shape.type === 'map') {
const data: Json = {};
// The validator should have ensured that this input is a
// [key, value] iterable...
if (typeof input[Symbol.iterator] === 'function') {
for (let [key, value] of input) {
data[key] = this.format(shape.value.shape, value);
}
return data;
}

// ... or an object
for (let key of Object.keys(input)) {
data[key] = this.format(shape.value.shape, input[key]);
}
return data;
} else if (shape.type === 'blob') {
// The validator should have ensured this is an ArrayBuffer,
// ArrayBufferView, or string
if (typeof input === 'string') {
input = this.utf8Decoder(input);
} else if (ArrayBuffer.isView(input)) {
input = new Uint8Array(input.buffer);
} else {
input = new Uint8Array(input);
}

return this.base64Encoder(input);
} else if (shape.type === 'timestamp') {
// The validator should have ensured this is a date, number, or
// string
return epoch(input);
}

return input;
}
}
24 changes: 24 additions & 0 deletions packages/protocol-json-body/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@aws/protocol-json-body",
"private": true,
"version": "0.0.1",
"description": "A marshaller and unmarshaller for the body portion of AWS's JSON and REST-JSON protocols",
"main": "lib/index.js",
"scripts": {
"prepublishOnly": "tsc",
"pretest": "tsc",
"test": "jest"
},
"author": "[email protected]",
"license": "UNLICENSED",
"dependencies": {
"@aws/types": "^0.0.1",
"@aws/protocol-timestamp": "^0.0.1"
},
"devDependencies": {
"@types/jest": "^19.2.2",
"@types/node": "^7.0.12",
"jest": "^19.0.2",
"typescript": "^2.3"
}
}
10 changes: 10 additions & 0 deletions packages/protocol-json-body/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": true,
"sourceMap": true,
"strict": true,
"downlevelIteration": true
}
}
4 changes: 4 additions & 0 deletions packages/protocol-timestamp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/node_modules/
*.js
*.js.map
*.d.ts
Loading