-
Notifications
You must be signed in to change notification settings - Fork 170
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
+446
−87
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
e2f5bbf
policy/conditional: define conditions in schema
davidor 75e8b95
policy/conditional: Add Operation
davidor 8f05835
spec/policy/conditional: add specs for Operation
davidor 8d4a69b
policy/conditional: replace engine with Condition
davidor e4cf1ab
policy/conditional: adapt to new config schema
davidor 577d2e7
spec/policy/conditional: adapt to new config schema
davidor 953b078
t/apicast-policy-conditional: adapt to new config schema
davidor 410ffaa
CHANGELOG: add conditional engine for conditional policy
davidor File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
local setmetatable = setmetatable | ||
local ipairs = ipairs | ||
local assert = assert | ||
|
||
local _M = {} | ||
|
||
local mt = { __index = _M } | ||
|
||
local default_combine_op = 'and' | ||
|
||
local function all_true(operations, context) | ||
for _, operation in ipairs(operations) do | ||
if not operation:evaluate(context) then | ||
return false | ||
end | ||
end | ||
|
||
return true | ||
end | ||
|
||
local function at_least_one_true(operations, context) | ||
for _, operation in ipairs(operations) do | ||
if operation:evaluate(context) then | ||
return true | ||
end | ||
end | ||
|
||
return false | ||
end | ||
|
||
local evaluate_func = { | ||
['and'] = all_true, | ||
['or'] = at_least_one_true, | ||
['true'] = function() return true end | ||
} | ||
|
||
function _M.new(operations, combine_op) | ||
local self = setmetatable({}, mt) | ||
|
||
if not operations then | ||
-- If there's nothing to evaluate, return true. | ||
self.evaluate_func = evaluate_func['true'] | ||
else | ||
self.evaluate_func = evaluate_func[combine_op or default_combine_op] | ||
end | ||
|
||
assert(self.evaluate_func, 'Unsupported combine op') | ||
|
||
self.operations = operations | ||
|
||
return self | ||
end | ||
|
||
function _M:evaluate(context) | ||
return self.evaluate_func(self.operations, context) | ||
end | ||
|
||
return _M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
local setmetatable = setmetatable | ||
local assert = assert | ||
local tostring = tostring | ||
|
||
local TemplateString = require 'apicast.template_string' | ||
|
||
local default_value_type = 'plain' | ||
|
||
local _M = {} | ||
|
||
local mt = { __index = _M } | ||
|
||
local function create_template(value, type) | ||
return TemplateString.new(value, type or default_value_type) | ||
end | ||
|
||
local evaluate_func = { | ||
['=='] = function(left, right) return tostring(left) == tostring(right) end, | ||
['!='] = function(left, right) return tostring(left) ~= tostring(right) end | ||
} | ||
|
||
--- Initialize an operation | ||
-- @tparam string left Left operand | ||
-- @tparam[opt] string left_type How to evaluate the left operand ('plain' or | ||
-- 'liquid'). Default: 'plain'. | ||
-- @tparam string op Operation | ||
-- @tparam[opt] string right Right operand | ||
-- @tparam string right_type How to evaluate the right operand ('plain' or | ||
-- 'liquid'). Default: 'plain'. | ||
function _M.new(left, left_type, op, right, right_type) | ||
local self = setmetatable({}, mt) | ||
|
||
self.evaluate_func = evaluate_func[op] | ||
assert(self.evaluate_func, 'Unsupported operation') | ||
|
||
self.templated_left = create_template(left, left_type) | ||
self.templated_right = create_template(right, right_type) | ||
|
||
return self | ||
end | ||
|
||
function _M:evaluate(context) | ||
local left_value = self.templated_left:render(context) | ||
local right_value = self.templated_right:render(context) | ||
|
||
return self.evaluate_func(left_value, right_value) | ||
end | ||
|
||
return _M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
local Condition = require('apicast.policy.conditional.condition') | ||
local Operation = require('apicast.policy.conditional.operation') | ||
|
||
describe('Engine', function() | ||
describe('.new', function() | ||
it('raises error with an unsupported operation', function() | ||
local res, err = pcall(Condition.new, {}, '<>') | ||
|
||
assert.is_falsy(res) | ||
assert.is_truthy(err) | ||
end) | ||
end) | ||
|
||
describe('.evaluate', function() | ||
it('combines operations with "and"', function() | ||
local true_condition = Condition.new( | ||
{ | ||
Operation.new('1', 'plain', '==', '1', 'plain'), | ||
Operation.new('2', 'plain', '==', '2', 'plain'), | ||
Operation.new('3', 'plain', '==', '3', 'plain') | ||
}, | ||
'and' | ||
) | ||
|
||
assert.is_true(true_condition:evaluate()) | ||
|
||
local false_condition = Condition.new( | ||
{ | ||
Operation.new('1', 'plain', '==', '1', 'plain'), | ||
Operation.new('2', 'plain', '==', '20', 'plain'), | ||
Operation.new('3', 'plain', '==', '3', 'plain') | ||
}, | ||
'and' | ||
) | ||
|
||
assert.is_false(false_condition:evaluate()) | ||
end) | ||
|
||
it('combines operations with "or"', function() | ||
local true_condition = Condition.new( | ||
{ | ||
Operation.new('1', 'plain', '==', '10', 'plain'), | ||
Operation.new('2', 'plain', '==', '20', 'plain'), | ||
Operation.new('3', 'plain', '==', '3', 'plain') | ||
}, | ||
'or' | ||
) | ||
|
||
assert.is_true(true_condition:evaluate()) | ||
|
||
local false_condition = Condition.new( | ||
{ | ||
Operation.new('1', 'plain', '==', '10', 'plain'), | ||
Operation.new('2', 'plain', '==', '20', 'plain'), | ||
Operation.new('3', 'plain', '==', '30', 'plain') | ||
}, | ||
'or' | ||
) | ||
|
||
assert.is_false(false_condition:evaluate()) | ||
end) | ||
|
||
it('combines operations with "and" by default', function() | ||
local true_condition = Condition.new( | ||
{ | ||
Operation.new('1', 'plain', '==', '1', 'plain'), | ||
Operation.new('2', 'plain', '==', '2', 'plain'), | ||
Operation.new('3', 'plain', '==', '3', 'plain') | ||
} | ||
) | ||
|
||
assert.is_true(true_condition:evaluate()) | ||
|
||
local false_condition = Condition.new( | ||
{ | ||
Operation.new('1', 'plain', '==', '1', 'plain'), | ||
Operation.new('2', 'plain', '==', '20', 'plain'), | ||
Operation.new('3', 'plain', '==', '3', 'plain') | ||
} | ||
) | ||
|
||
assert.is_false(false_condition:evaluate()) | ||
end) | ||
|
||
it('returns true when there are no operations', function() | ||
assert.is_true(Condition.new():evaluate()) | ||
end) | ||
end) | ||
end) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
local Operation = require('apicast.policy.conditional.operation') | ||
|
||
describe('Operation', function() | ||
describe('.new', function() | ||
it('raises error with an unsupported operation', function() | ||
local res, err = pcall(Operation.new, '1', 'plain', '<>', '1', 'plain') | ||
|
||
assert.is_falsy(res) | ||
assert.is_truthy(err) | ||
end) | ||
end) | ||
|
||
describe('.evaluate', function() | ||
it('evaluates ==', function() | ||
assert.is_true(Operation.new('1', 'plain', '==', '1', 'plain'):evaluate({})) | ||
assert.is_false(Operation.new('1', 'plain', '==', '2', 'plain'):evaluate({})) | ||
end) | ||
|
||
it('evaluates !=', function() | ||
assert.is_true(Operation.new('1', 'plain', '!=', '2', 'plain'):evaluate({})) | ||
assert.is_false(Operation.new('1', 'plain', '!=', '1', 'plain'):evaluate({})) | ||
end) | ||
|
||
it('evaluates values as plain text by default', function() | ||
assert.is_true(Operation.new('1', nil, '==', '1', nil):evaluate({})) | ||
assert.is_false(Operation.new('1', nil, '==', '2', nil):evaluate({})) | ||
end) | ||
|
||
it('evaluates liquid when indicated in the types', function() | ||
local context = { var_1 = '1', var_2 = '2' } | ||
|
||
local res_true = Operation.new( | ||
'{{ var_1 }}', 'liquid', '==', '1', 'plain' | ||
):evaluate(context) | ||
|
||
assert.is_true(res_true) | ||
|
||
local res_false = Operation.new( | ||
'{{ var_1 }}', 'liquid', '==', '{{ var_2 }}', 'liquid' | ||
):evaluate(context) | ||
|
||
assert.is_false(res_false) | ||
end) | ||
|
||
it('evaluates comparison ops without differentiating types', function() | ||
local context = { var_1 = 1 } | ||
|
||
local eq_int_and_string = Operation.new( | ||
'{{ var_1 }}', 'liquid', '==', '1', 'plain' | ||
):evaluate(context) | ||
|
||
assert.is_true(eq_int_and_string) | ||
|
||
local not_eq_int_and_string = Operation.new( | ||
'{{ var_1 }}', 'liquid', '!=', '1', 'plain' | ||
):evaluate(context) | ||
|
||
assert.is_false(not_eq_int_and_string) | ||
end) | ||
end) | ||
end) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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, becauseLiquidTemplateString:render()
returns a string.I included a test that checks this. Also, I added
tostring()
in the==
and!=
operations inOperation
to make this explicit.