diff --git a/packages/@aws-cdk/aws-iotevents/README.md b/packages/@aws-cdk/aws-iotevents/README.md index 9089fdec07cbd..db12291df9f12 100644 --- a/packages/@aws-cdk/aws-iotevents/README.md +++ b/packages/@aws-cdk/aws-iotevents/README.md @@ -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'), ), }); diff --git a/packages/@aws-cdk/aws-iotevents/lib/expression.ts b/packages/@aws-cdk/aws-iotevents/lib/expression.ts index fd686e9761802..bf4400c877206 100644 --- a/packages/@aws-cdk/aws-iotevents/lib/expression.ts +++ b/packages/@aws-cdk/aws-iotevents/lib/expression.ts @@ -31,14 +31,56 @@ 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() { @@ -46,8 +88,14 @@ export abstract class Expression { /** * 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 { @@ -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 + */ + 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})`; } } diff --git a/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts b/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts index e3844ce7915ba..4f52e157c9356 100644 --- a/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts +++ b/packages/@aws-cdk/aws-iotevents/test/detector-model.test.ts @@ -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)'], + ])('%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), }], }), }); @@ -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, })], }, }),