Skip to content

Commit 3f1edb3

Browse files
committed
Add validator implementations
These validators are referenced from generated code, and implement both constraint traits as well as the recursive validation of traits for more complex shapes.
1 parent 9ef729c commit 3f1edb3

File tree

6 files changed

+558
-1
lines changed

6 files changed

+558
-1
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,8 @@ wrapper/
2424

2525
# Visual Studio Code
2626
.vscode/*
27+
28+
# Integ test Yarn stuff
29+
smithy-typescript-integ-tests/dist
30+
smithy-typescript-integ-tests/node_modules
31+
smithy-typescript-integ-tests/yarn.lock

smithy-typescript-ssdk-libs/server-common/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
export * as httpbinding from "./httpbinding";
1717
export * from "./errors";
18+
export * from "./validation";
1819

1920
import { HttpRequest, HttpResponse } from "@aws-sdk/protocol-http";
2021
import { SmithyException } from "@aws-sdk/smithy-client";
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
export * from "./validators";
17+
18+
interface StandardValidationFailure<ConstraintBoundsType, FailureType> {
19+
memberName: string;
20+
constraintType: string;
21+
constraintValues: ArrayLike<ConstraintBoundsType>;
22+
failureValue: FailureType;
23+
}
24+
25+
export interface EnumValidationFailure extends StandardValidationFailure<string, string> {
26+
constraintType: "enum";
27+
constraintValues: string[];
28+
}
29+
30+
export interface LengthValidationFailure extends StandardValidationFailure<number | undefined, number> {
31+
constraintType: "length";
32+
constraintValues: [number, number] | [undefined, number] | [number, undefined];
33+
}
34+
35+
export interface PatternValidationFailure {
36+
memberName: string;
37+
constraintType: "pattern";
38+
constraintValues: string;
39+
failureValue: string;
40+
}
41+
42+
export interface RangeValidationFailure extends StandardValidationFailure<number | undefined, number> {
43+
constraintType: "range";
44+
constraintValues: [number, number] | [undefined, number] | [number, undefined];
45+
}
46+
47+
export class RequiredValidationFailure {
48+
memberName: string;
49+
constraintType = "required";
50+
51+
constructor(memberName: string) {
52+
this.memberName = memberName;
53+
}
54+
}
55+
56+
export interface UniqueItemsValidationFailure {
57+
memberName: string;
58+
constraintType: "uniqueItems";
59+
failureValue: Array<any>;
60+
}
61+
62+
export type ValidationFailure =
63+
| EnumValidationFailure
64+
| LengthValidationFailure
65+
| PatternValidationFailure
66+
| RangeValidationFailure
67+
| RequiredValidationFailure
68+
| UniqueItemsValidationFailure;
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
import { EnumValidator, LengthValidator, PatternValidator, RangeValidator, UniqueItemsValidator } from "./validators";
17+
18+
describe("enum validation", () => {
19+
const enumValidator = new EnumValidator(["apple", "banana", "orange"]);
20+
21+
it("does not fail when the enum value is found", () => {
22+
expect(enumValidator.validate("apple", "fruit")).toBeNull();
23+
});
24+
25+
it("fails when the enum value is not found", () => {
26+
expect(enumValidator.validate("kiwi", "fruit")).toEqual({
27+
constraintType: "enum",
28+
constraintValues: ["apple", "banana", "orange"],
29+
memberName: "fruit",
30+
failureValue: "kiwi",
31+
});
32+
});
33+
});
34+
35+
describe("length validation", () => {
36+
const threeLengthThings = ["foo", { a: 1, b: 2, c: 3 }, ["a", "b", "c"], new Uint8Array([13, 37, 42])];
37+
38+
for (const value of threeLengthThings) {
39+
describe(`for ${JSON.stringify(value)}`, () => {
40+
it("should succeed with min = 1", () => {
41+
expect(new LengthValidator(1).validate(value, "aThreeLengthThing")).toBeNull();
42+
});
43+
it("should succeed with min = 3", () => {
44+
expect(new LengthValidator(3).validate(value, "aThreeLengthThing")).toBeNull();
45+
});
46+
it("should succeed with max = 100", () => {
47+
expect(new LengthValidator(undefined, 100).validate(value, "aThreeLengthThing")).toBeNull();
48+
});
49+
it("should succeed with max = 3", () => {
50+
expect(new LengthValidator(undefined, 3).validate(value, "aThreeLengthThing")).toBeNull();
51+
});
52+
it("should succeed with min = 3 and max = 3", () => {
53+
expect(new LengthValidator(3, 3).validate(value, "aThreeLengthThing")).toBeNull();
54+
});
55+
it("should succeed with min = 1 and max = 128", () => {
56+
expect(new LengthValidator(1, 128).validate(value, "aThreeLengthThing")).toBeNull();
57+
});
58+
it("should fail with min = 4", () => {
59+
expect(new LengthValidator(4).validate(value, "aThreeLengthThing")).toEqual({
60+
constraintType: "length",
61+
constraintValues: [4, undefined],
62+
memberName: "aThreeLengthThing",
63+
failureValue: 3,
64+
});
65+
});
66+
it("should fail with max = 2", () => {
67+
expect(new LengthValidator(undefined, 2).validate(value, "aThreeLengthThing")).toEqual({
68+
constraintType: "length",
69+
constraintValues: [undefined, 2],
70+
memberName: "aThreeLengthThing",
71+
failureValue: 3,
72+
});
73+
});
74+
it("should fail with min = 1 and max = 2", () => {
75+
expect(new LengthValidator(1, 2).validate(value, "aThreeLengthThing")).toEqual({
76+
constraintType: "length",
77+
constraintValues: [1, 2],
78+
memberName: "aThreeLengthThing",
79+
failureValue: 3,
80+
});
81+
});
82+
});
83+
}
84+
});
85+
86+
describe("pattern validation", () => {
87+
it("does not match the entire string", () => {
88+
const validator = new PatternValidator("\\w+");
89+
expect(validator.validate("hello", "aField")).toBeNull();
90+
expect(validator.validate("!hello!", "aField")).toBeNull();
91+
});
92+
it("can be anchored", () => {
93+
const validator = new PatternValidator("^\\w+$");
94+
expect(validator.validate("hello", "aField")).toBeNull();
95+
expect(validator.validate("!hello!", "aField")).toEqual({
96+
constraintType: "pattern",
97+
constraintValues: "^\\w+$",
98+
failureValue: "!hello!",
99+
memberName: "aField",
100+
});
101+
});
102+
it("supports character class expressions", () => {
103+
const validator = new PatternValidator("^\\p{L}+$");
104+
expect(validator.validate("hello", "aField")).toBeNull();
105+
expect(validator.validate("!hello!", "aField")).toEqual({
106+
constraintType: "pattern",
107+
constraintValues: "^\\p{L}+$",
108+
failureValue: "!hello!",
109+
memberName: "aField",
110+
});
111+
});
112+
});
113+
114+
describe("range validation", () => {
115+
it("supports min-only constraints", () => {
116+
const validator = new RangeValidator(3);
117+
expect(validator.validate(3, "aField")).toBeNull();
118+
expect(validator.validate(4, "aField")).toBeNull();
119+
expect(validator.validate(1, "aField")).toEqual({
120+
constraintType: "range",
121+
constraintValues: [3, undefined],
122+
failureValue: 1,
123+
memberName: "aField",
124+
});
125+
});
126+
it("supports max-only constraints", () => {
127+
const validator = new RangeValidator(undefined, 3);
128+
expect(validator.validate(3, "aField")).toBeNull();
129+
expect(validator.validate(1, "aField")).toBeNull();
130+
expect(validator.validate(4, "aField")).toEqual({
131+
constraintType: "range",
132+
constraintValues: [undefined, 3],
133+
failureValue: 4,
134+
memberName: "aField",
135+
});
136+
});
137+
it("supports min-max constraints", () => {
138+
const validator = new RangeValidator(3, 5);
139+
expect(validator.validate(3, "aField")).toBeNull();
140+
expect(validator.validate(4, "aField")).toBeNull();
141+
expect(validator.validate(5, "aField")).toBeNull();
142+
expect(validator.validate(1, "aField")).toEqual({
143+
constraintType: "range",
144+
constraintValues: [3, 5],
145+
failureValue: 1,
146+
memberName: "aField",
147+
});
148+
expect(validator.validate(6, "aField")).toEqual({
149+
constraintType: "range",
150+
constraintValues: [3, 5],
151+
failureValue: 6,
152+
memberName: "aField",
153+
});
154+
});
155+
});
156+
157+
describe("uniqueItems", () => {
158+
const validator = new UniqueItemsValidator();
159+
describe("supports strings", () => {
160+
expect(validator.validate(["a", "b", "c"], "aField")).toBeNull();
161+
expect(validator.validate(["a", "a", "c", "a", "b", "b"], "aField")).toEqual({
162+
constraintType: "uniqueItems",
163+
failureValue: ["a", "b"],
164+
memberName: "aField",
165+
});
166+
});
167+
describe("supports numbers", () => {
168+
expect(validator.validate([1, 2, 3], "aField")).toBeNull();
169+
expect(validator.validate([1, 1, 3, 1, 1, 2.5, 2.5], "aField")).toEqual({
170+
constraintType: "uniqueItems",
171+
failureValue: [1, 2.5],
172+
memberName: "aField",
173+
});
174+
});
175+
describe("supports booleans, I guess", () => {
176+
expect(validator.validate([true, false], "aField")).toBeNull();
177+
expect(validator.validate([true, false, true], "aField")).toEqual({
178+
constraintType: "uniqueItems",
179+
failureValue: [true],
180+
memberName: "aField",
181+
});
182+
});
183+
});

0 commit comments

Comments
 (0)