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

JSON conditions for conditional policy #814

Merged
merged 8 commits into from
Jul 19, 2018
Merged

JSON conditions for conditional policy #814

merged 8 commits into from
Jul 19, 2018

Conversation

davidor
Copy link
Contributor

@davidor davidor commented Jul 18, 2018

Closes #744

This PR defines the conditions supported in the conditional policy. It also includes an engine that evaluates them.

The kinds of conditions supported are very basic (==, !=, and, or) and they can be expressed in JSON.

The alternative was to define a custom DSL. I implemented a prototype in this branch: https://github.com/3scale/apicast/tree/lpeg-grammar-condition-engine . This approach would allow us to express more complex conditions. However, the implementation is more complicated, and it would tie us to lpeg. The solution implemented in this PR should be enough for our current needs so it looks like a better option.

For now this only support == and !=. We can add more operations like starts_with or match_exactly, later. I'd rather agree on the approach and the basics first.

@davidor davidor requested a review from a team as a code owner July 18, 2018 15:09

function _M.evaluate(expression)
local match_attr = ngx.re.match(expression, [[^([\w]+)$]], 'oj')
function _M.evaluate(condition, context)
Copy link

Choose a reason for hiding this comment

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

cyclomatic complexity of function '_M.evaluate' is too high (8 > 7)

local res = Operation.new('1', 'plain', '!=', '2', 'plain'):evaluate({})
assert.is_true(res)

local res = Operation.new('1', 'plain', '!=', '1', 'plain'):evaluate({})
Copy link

Choose a reason for hiding this comment

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

variable 'res' was previously defined on line 23

local res = Operation.new('1', 'plain', '==', '1', 'plain'):evaluate({})
assert.is_true(res)

local res = Operation.new('1', 'plain', '==', '2', 'plain'):evaluate({})
Copy link

Choose a reason for hiding this comment

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

variable 'res' was previously defined on line 15

local res = Operation.new('1', nil, '==', '1', nil):evaluate({})
assert.is_true(res)

local res = Operation.new('1', nil, '==', '2', nil):evaluate({})
Copy link

Choose a reason for hiding this comment

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

variable 'res' was previously defined on line 31


assert.is_true(res)

local res = Operation.new(
Copy link

Choose a reason for hiding this comment

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

variable 'res' was previously defined on line 41

@davidor davidor force-pushed the json-conditions branch 3 times, most recently from d445a71 to 6242062 Compare July 18, 2018 15:59
Start with something simple. We will change if use cases that require
more complex operations appear.
@davidor davidor changed the title [WIP] JSON conditions for conditional policy JSON conditions for conditional policy Jul 18, 2018
@davidor davidor force-pushed the json-conditions branch 2 times, most recently from 27b0d1e to 8ec6e53 Compare July 19, 2018 12:38
function _M:evaluate(context)
if #self.operations == 0 then return true end

if self.combine_op == 'and' then
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be done at initialization time, right?

It would allow for better JIT if there would be no branching in this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right 👍
Fixed.

local left_value = self.templated_left:render(context)
local right_value = self.templated_right:render(context)

if self.op == '==' then
Copy link
Contributor

@mikz mikz Jul 19, 2018

Choose a reason for hiding this comment

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

I think we can have a dispatch table for operations like:

local operations = {
  ['=='] = function(left, right) return left == right end
}

And assign the comparator function during initialization for better JIT.

edit: that would allow us to get rid of the supported_ops variable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed the same way as the one above 👍

local context = { var_1 = '1', var_2 = '2' }

local res_true = Operation.new(
'{{ var_1 }}', 'liquid', '==', '1', 'plain'
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we going to handle types somehow? What if the variable comes from nginx and is actually a number ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In that example, even if var_1 was a number, the comparison would end up being between 2 strings, because LiquidTemplateString:render() returns a string.

I included a test that checks this. Also, I added tostring() in the == and != operations in Operation to make this explicit.

Copy link
Contributor

@mikz mikz left a comment

Choose a reason for hiding this comment

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

I think this is great for now 👍 🥇

There might be some details to clarify and improve, but that can be done later during some performance optimization phase.

The only real concern I have is regarding types. If everything is string based we might need to call tostring on the values coming from nginx like ports.

Copy link
Contributor

@mikz mikz left a comment

Choose a reason for hiding this comment

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

🥇 Excellent! As always 👍

@davidor davidor merged commit 123a170 into master Jul 19, 2018
@davidor davidor deleted the json-conditions branch July 19, 2018 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants