Skip to content
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

feat(iotevents): support comparison operators #19329

Merged
merged 9 commits into from
Apr 8, 2022
12 changes: 6 additions & 6 deletions packages/@aws-cdk/aws-iotevents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,20 @@ const coldState = new iotevents.State({
stateName: 'cold',
});

// transit to coldState when temperature is 10
// transit to coldState when temperature is less than 15
warmState.transitionTo(coldState, {
eventName: 'to_coldState', // optional property, default by combining the names of the States
when: iotevents.Expression.eq(
when: iotevents.Expression.lt(
iotevents.Expression.inputAttribute(input, 'payload.temperature'),
iotevents.Expression.fromString('10'),
iotevents.Expression.fromString('15'),
),
executing: [new actions.LambdaInvokeAction(func)], // optional
});
// transit to warmState when temperature is 20
// transit to warmState when temperature is greater than or equal to 15
coldState.transitionTo(warmState, {
when: iotevents.Expression.eq(
when: iotevents.Expression.gte(
iotevents.Expression.inputAttribute(input, 'payload.temperature'),
iotevents.Expression.fromString('20'),
iotevents.Expression.fromString('15'),
),
});

Expand Down
67 changes: 62 additions & 5 deletions packages/@aws-cdk/aws-iotevents/lib/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,71 @@ export abstract class Expression {
* Create a expression for the Equal operator.
*/
public static eq(left: Expression, right: Expression): Expression {
return new BinaryOperationExpression(left, '==', right);
return new BinaryOperationExpression(left, '==', right, 9);
}

/**
* Create a expression for the Not Equal operator.
*/
public static neq(left: Expression, right: Expression): Expression {
return new BinaryOperationExpression(left, '!=', right, 9);
}

/**
* Create a expression for the Less Than operator.
*/
public static lt(left: Expression, right: Expression): Expression {
return new BinaryOperationExpression(left, '<', right, 10);
}

/**
* Create a expression for the Less Than Or Equal operator.
*/
public static lte(left: Expression, right: Expression): Expression {
return new BinaryOperationExpression(left, '<=', right, 10);
}

/**
* Create a expression for the Greater Than operator.
*/
public static gt(left: Expression, right: Expression): Expression {
return new BinaryOperationExpression(left, '>', right, 10);
}

/**
* Create a expression for the Greater Than Or Equal operator.
*/
public static gte(left: Expression, right: Expression): Expression {
return new BinaryOperationExpression(left, '>=', right, 10);
}

/**
* Create a expression for the AND operator.
*/
public static and(left: Expression, right: Expression): Expression {
return new BinaryOperationExpression(left, '&&', right);
return new BinaryOperationExpression(left, '&&', right, 5);
}

/**
* Create a expression for the OR operator.
*/
public static or(left: Expression, right: Expression): Expression {
return new BinaryOperationExpression(left, '||', right, 4);
}

constructor() {
}

/**
* This is called to evaluate the expression.
*
* @param parentPriority priority of the parent of this expression,
* used for determining whether or not to add parenthesis around the expression.
* This is intended to be set according to MDN rules, see
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#table
* for details
*/
public abstract evaluate(): string;
public abstract evaluate(parentPriority?: number): string;
}

class StringExpression extends Expression {
Expand All @@ -65,11 +113,20 @@ class BinaryOperationExpression extends Expression {
private readonly left: Expression,
private readonly operator: string,
private readonly right: Expression,
/**
* Indicates the priority of the operator.
* This is intended to be set according to MDN rules.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#table
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have adopted MDN as the operator priority rule. I think it can be used to whatever follow general rules and has concrete numbers.

*/
private readonly priority: number,
) {
super();
}

public evaluate() {
return `${this.left.evaluate()} ${this.operator} ${this.right.evaluate()}`;
public evaluate(parentPriority?: number) {
const expression = `${this.left.evaluate(this.priority)} ${this.operator} ${this.right.evaluate(this.priority)}`;
return parentPriority === undefined || parentPriority <= this.priority
? expression
: `(${expression})`;
}
}
112 changes: 19 additions & 93 deletions packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,14 +498,30 @@ test('cannot create transitions that transit to duprecated target state', () =>
});

describe('Expression', () => {
test('currentInput', () => {
const E = iotevents.Expression;
test.each([
['currentInput', (testInput: iotevents.IInput) => E.currentInput(testInput), 'currentInput("test-input")'],
['inputAttribute', (testInput: iotevents.IInput) => E.inputAttribute(testInput, 'json.path'), '$input.test-input.json.path'],
['eq', () => E.eq(E.fromString('"aaa"'), E.fromString('"bbb"')), '"aaa" == "bbb"'],
['neq', () => E.neq(E.fromString('"aaa"'), E.fromString('"bbb"')), '"aaa" != "bbb"'],
['lt', () => E.lt(E.fromString('5'), E.fromString('2')), '5 < 2'],
['lte', () => E.lte(E.fromString('5'), E.fromString('2')), '5 <= 2'],
['gt', () => E.gt(E.fromString('5'), E.fromString('2')), '5 > 2'],
['gte', () => E.gte(E.fromString('5'), E.fromString('2')), '5 >= 2'],
['and', () => E.and(E.fromString('true'), E.fromString('false')), 'true && false'],
['or', () => E.or(E.fromString('true'), E.fromString('false')), 'true || false'],
['operator priority', () => E.and(
E.and(E.fromString('false'), E.fromString('false')),
E.or(E.fromString('true'), E.fromString('true')),
), 'false && false && (true || true)'],
yamatatsu marked this conversation as resolved.
Show resolved Hide resolved
])('%s', (_, getExpression, expectedCondition) => {
// WHEN
new iotevents.DetectorModel(stack, 'MyDetectorModel', {
initialState: new iotevents.State({
stateName: 'test-state',
onEnter: [{
eventName: 'test-eventName',
condition: iotevents.Expression.currentInput(input),
condition: getExpression(input),
}],
}),
});
Expand All @@ -517,97 +533,7 @@ describe('Expression', () => {
Match.objectLike({
OnEnter: {
Events: [Match.objectLike({
Condition: 'currentInput("test-input")',
})],
},
}),
],
},
});
});

test('inputAttribute', () => {
// WHEN
new iotevents.DetectorModel(stack, 'MyDetectorModel', {
initialState: new iotevents.State({
stateName: 'test-state',
onEnter: [{
eventName: 'test-eventName',
condition: iotevents.Expression.inputAttribute(input, 'json.path'),
}],
}),
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::IoTEvents::DetectorModel', {
DetectorModelDefinition: {
States: [
Match.objectLike({
OnEnter: {
Events: [Match.objectLike({
Condition: '$input.test-input.json.path',
})],
},
}),
],
},
});
});

test('eq', () => {
// WHEN
new iotevents.DetectorModel(stack, 'MyDetectorModel', {
initialState: new iotevents.State({
stateName: 'test-state',
onEnter: [{
eventName: 'test-eventName',
condition: iotevents.Expression.eq(
iotevents.Expression.fromString('"aaa"'),
iotevents.Expression.fromString('"bbb"'),
),
}],
}),
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::IoTEvents::DetectorModel', {
DetectorModelDefinition: {
States: [
Match.objectLike({
OnEnter: {
Events: [Match.objectLike({
Condition: '"aaa" == "bbb"',
})],
},
}),
],
},
});
});

test('eq', () => {
// WHEN
new iotevents.DetectorModel(stack, 'MyDetectorModel', {
initialState: new iotevents.State({
stateName: 'test-state',
onEnter: [{
eventName: 'test-eventName',
condition: iotevents.Expression.and(
iotevents.Expression.fromString('true'),
iotevents.Expression.fromString('false'),
),
}],
}),
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::IoTEvents::DetectorModel', {
DetectorModelDefinition: {
States: [
Match.objectLike({
OnEnter: {
Events: [Match.objectLike({
Condition: 'true && false',
Condition: expectedCondition,
})],
},
}),
Expand Down