Skip to content

Commit 2a96cd7

Browse files
committed
Ensure expressions can be easily created inside a schema-driven data mapper
1 parent b3c5927 commit 2a96cd7

23 files changed

+402
-396
lines changed

packages/dynamodb-auto-marshaller/src/NumberValue.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const EXPECTED_TAG = `[object ${NUMBER_VALUE_TAG}]`;
33

44
export class NumberValue {
55
public readonly value: string;
6+
public readonly [Symbol.toStringTag] = NUMBER_VALUE_TAG;
67

78
constructor(value: string|number) {
89
this.value = value.toString().trim();
@@ -20,10 +21,6 @@ export class NumberValue {
2021
return Number(this.value);
2122
}
2223

23-
get [Symbol.toStringTag](): string {
24-
return NUMBER_VALUE_TAG;
25-
}
26-
2724
static isNumberValue(arg: any): arg is NumberValue {
2825
return Boolean(arg)
2926
&& Object.prototype.toString.call(arg) === EXPECTED_TAG;

packages/dynamodb-auto-marshaller/src/ObjectSet.ts

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
export abstract class ObjectSet<T> implements Set<T> {
2+
/**
3+
* Returns the string literal 'Set' for use by Object.prototype.toString.
4+
* This allows for identifying Sets without checking constructor identity.
5+
*/
6+
public readonly [Symbol.toStringTag]: 'Set' = 'Set';
7+
28
protected _values: Array<T> = [];
39

410
/**
@@ -110,12 +116,4 @@ export abstract class ObjectSet<T> implements Set<T> {
110116
[Symbol.iterator](): IterableIterator<T> {
111117
return this._values[Symbol.iterator]();
112118
}
113-
114-
/**
115-
* Returns the string literal 'Set' for use by Object.prototype.toString.
116-
* This allows for identifying Sets without checking constructor identity.
117-
*/
118-
get [Symbol.toStringTag](): 'Set' {
119-
return 'Set';
120-
}
121119
}

packages/dynamodb-expressions/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,8 @@
2020
},
2121
"peerDependencies": {
2222
"aws-sdk": "^2.7.0"
23+
},
24+
"dependencies": {
25+
"@aws/dynamodb-auto-marshaller": "^0.0.0"
2326
}
2427
}

packages/dynamodb-expressions/src/AttributeName.spec.ts

-150
This file was deleted.

packages/dynamodb-expressions/src/AttributeName.ts

-44
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import {AttributePath, PathElement} from "./AttributePath";
2+
3+
describe('AttributePath', () => {
4+
it('should convert a string path to a list of elements', () => {
5+
expect(
6+
new AttributePath('foo.bar.baz[3][4][2].fizz[0].buzz[1]').elements
7+
).toEqual([
8+
{type: 'AttributeName', name: 'foo'},
9+
{type: 'AttributeName', name: 'bar'},
10+
{type: 'AttributeName', name: 'baz'},
11+
{type: 'ListIndex', index: 3},
12+
{type: 'ListIndex', index: 4},
13+
{type: 'ListIndex', index: 2},
14+
{type: 'AttributeName', name: 'fizz'},
15+
{type: 'ListIndex', index: 0},
16+
{type: 'AttributeName', name: 'buzz'},
17+
{type: 'ListIndex', index: 1},
18+
]);
19+
});
20+
21+
it('should clone an iterable of elements passed to the constructor', () => {
22+
const elements: Array<PathElement> = [
23+
{type: 'AttributeName', name: 'foo'},
24+
{type: 'AttributeName', name: 'bar'},
25+
{type: 'AttributeName', name: 'baz'},
26+
{type: 'ListIndex', index: 3},
27+
{type: 'ListIndex', index: 4},
28+
{type: 'ListIndex', index: 2},
29+
{type: 'AttributeName', name: 'fizz'},
30+
{type: 'ListIndex', index: 0},
31+
{type: 'AttributeName', name: 'buzz'},
32+
{type: 'ListIndex', index: 1},
33+
];
34+
const path = new AttributePath(elements);
35+
36+
expect(path.elements).toEqual(elements);
37+
expect(path.elements).not.toBe(elements);
38+
39+
elements.shift();
40+
expect(path.elements).not.toEqual(elements);
41+
expect(path.elements.slice(1)).toEqual(elements);
42+
});
43+
44+
it('should allow attribute names with embedded control characters', () => {
45+
expect(new AttributePath('_bracket_\\[_period_\\._backslash_\\\\_unescaped_backslash_\\_.foo').elements).toEqual([
46+
{type: 'AttributeName', name: '_bracket_[_period_._backslash_\\_unescaped_backslash_\\_'},
47+
{type: 'AttributeName', name: 'foo'},
48+
]);
49+
});
50+
51+
describe('path correctness checking', () => {
52+
it(
53+
'should throw an error when a path begins with a control character',
54+
() => {
55+
expect(() => new AttributePath('[1]'))
56+
.toThrowError(/Invalid control character/);
57+
}
58+
);
59+
60+
it(
61+
'should throw an error when a list index access contains invalid characters',
62+
() => {
63+
expect(() => new AttributePath('foo[a]'))
64+
.toThrowError(/Invalid array index character/);
65+
}
66+
);
67+
68+
it(
69+
'should throw an error when a list index access contains no characters',
70+
() => {
71+
expect(() => new AttributePath('foo[]'))
72+
.toThrowError(/Invalid array index/);
73+
}
74+
);
75+
76+
it(
77+
'should throw an error when an identifier immediately follows a list index access',
78+
() => {
79+
expect(() => new AttributePath('foo[1]a'))
80+
.toThrowError(/Bare identifier encountered/);
81+
}
82+
);
83+
});
84+
85+
describe('::isAttributePath', () => {
86+
const ctor = AttributePath;
87+
88+
afterEach(() => {
89+
(AttributePath as any) = ctor;
90+
});
91+
92+
it('should return true for AttributePath objects', () => {
93+
expect(
94+
AttributePath.isAttributePath(new AttributePath('foo'))
95+
).toBe(true);
96+
});
97+
98+
it('should return false for scalar values', () => {
99+
for (let scalar of ['string', 123.234, true, null, void 0]) {
100+
expect(AttributePath.isAttributePath(scalar)).toBe(false);
101+
}
102+
});
103+
104+
it(
105+
'should return true for AttributePaths created with a different instance of the AttributePath constructor',
106+
() => {
107+
const {isAttributePath} = AttributePath;
108+
const path = new AttributePath('foo.bar');
109+
(AttributePath as any) = () => path;
110+
111+
expect(path).not.toBeInstanceOf(AttributePath);
112+
expect(isAttributePath(path)).toBe(true);
113+
}
114+
);
115+
});
116+
});

0 commit comments

Comments
 (0)