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

[WIP] Choice rule payload validation #189

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

kbrock
Copy link
Member

@kbrock kbrock commented May 22, 2024

alt to #187

Depend upon:

Overview

Choice does not quite behave the same as the AWS States Language reference implementation.

If you specify a Path that points to nothing or the wrong type, aws raises a States.Runtime. In truth, the type checking is not the most consistent, but the missing path is always raised.

Changes:

  • Validate Next points to a valid state.
  • Validate Variable and compare key values are the correct data type.
  • Raise exceptions when Variable or compare key path values not not found or the wrong data type. It used to throw ruby exceptions.
  • Fix "IsPresent": true to detect values present rather than not null.
  • All "Is*" comparisons now support true and false values.
  • Support Choice with no Default provided.

@kbrock kbrock added the enhancement New feature or request label May 22, 2024
@kbrock kbrock requested review from agrare and Fryguy as code owners May 22, 2024 21:28
@kbrock
Copy link
Member Author

kbrock commented May 22, 2024

update:

  • cop: returning false instead of nil for bad lhv
  • cop: space in spec
  • rebased

@kbrock kbrock changed the title Choice rule payload validation [WIP] Choice rule payload validation May 23, 2024
@kbrock
Copy link
Member Author

kbrock commented May 23, 2024

wip: pre-compiling operations

@miq-bot miq-bot added the wip label May 23, 2024
@kbrock kbrock force-pushed the choice_rule_payload_validation branch from a0d0c26 to 0993b77 Compare May 23, 2024 04:47
@kbrock
Copy link
Member Author

kbrock commented May 23, 2024

update:

  • compiled expression into command tree
  • changed key comparison to not look for String, but rather (String)(GreaterThan)(Path). It has a few false positives, (IntegerMatchesPath) but they will all probably work.

This is a lot more involved but a lot more strict/stringent

update:

  • cops: hash rocket, indent, annotation
  • no longer escaping matches up front (can roll back if others want)

@kbrock kbrock force-pushed the choice_rule_payload_validation branch from 0993b77 to 07a1411 Compare May 23, 2024 04:58
@kbrock kbrock changed the title [WIP] Choice rule payload validation Choice rule payload validation May 23, 2024
@miq-bot miq-bot removed the wip label May 23, 2024
@kbrock
Copy link
Member Author

kbrock commented May 23, 2024

rubocop: command line and web have different opinions on whether a regex is freezable. one complains no matter what code I use here.

un-wip: this seems to work

@kbrock kbrock force-pushed the choice_rule_payload_validation branch 3 times, most recently from fdab00e to e1bce92 Compare May 23, 2024 19:55
@kbrock
Copy link
Member Author

kbrock commented May 23, 2024

update:

  • rebase
  • simplified down choice class lookup code

update:

  • added freeze back in

@Fryguy
Copy link
Member

Fryguy commented May 23, 2024

Can you add specs around these new classes? wondering if would catch the invalid namespace thing I found.

@kbrock kbrock force-pushed the choice_rule_payload_validation branch from e1bce92 to 4ff6108 Compare May 23, 2024 20:07
@kbrock
Copy link
Member Author

kbrock commented May 23, 2024

update:

  • change class reference

I commented out every line there, and the specs fail. So every one of those classes are tested

@kbrock
Copy link
Member Author

kbrock commented May 23, 2024

Discussion:
Instead of using a command pattern, we can change this to store the operation so we wouldn't need all these classes.

e.g.:

OPERATIONS = { "StringEquals" => "==" }
lhs.send(OPERATIONS[choice_value], rhs)

Comment on lines 33 to 34
values = COMPARE_RULE.match(key)
return [key, DATA_RULES[values[2]], !!values[3]] if values
Copy link
Member

Choose a reason for hiding this comment

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

I think this would read better if the match was decomposed or perhaps if named captures were used instead of using values[2], values[3], etc.

Some other quetsions

  • where is the prefix used (the (String|Numeric|Boolean|Timestamp) part)? It seems to be ignored here.
  • The caller does compare_key, klass = klass_params(payload), but this method returns 3 items, so is that a bug? Where did the 3rd param go with the path, and why isn't it being used?

Copy link
Member Author

Choose a reason for hiding this comment

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

the current pattern for the states/command objects to parse the payload themselves.
Here, we need to parse the string to

The old code ignored the first part, so I continued to ignore it. I can see doing type checks or conversions.

returning 2 vs 3 is probably a bug. thanks

Copy link
Member Author

Choose a reason for hiding this comment

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

  1. yes, we ignore the type prefix part and don't currently do data type conversion. (not sure if that is needed)
  2. changed this code. think all set.

@Fryguy
Copy link
Member

Fryguy commented May 23, 2024

Discussion: Instead of using a command pattern, we can change this to store the operation so we wouldn't need all these classes.

e.g.:

OPERATIONS = { "StringEquals" => "==" }
lhs.send(OPERATIONS[choice_value], rhs)

Maybe, but this all feels overly complicated to me personally. I don't see why each of these can't be a simple method (or even just the original code the way it was). Just curious what problem you were trying to solve. I could see creating the classes to encapsulate a true? + valid? pair, but all of the new classes just have a single method.

@kbrock kbrock changed the title Choice rule payload validation [WIP] Choice rule payload validation May 24, 2024
@kbrock kbrock added the wip label May 24, 2024
@kbrock kbrock force-pushed the choice_rule_payload_validation branch 2 times, most recently from 856fce4 to 1ceb8f4 Compare May 31, 2024 02:01
@kbrock
Copy link
Member Author

kbrock commented May 31, 2024

sorry - this is for previous commit

update:

  • rebase
  • dropped comparison classes and put into an operation's hash.
  • No longer modify parent ChoiceRule class
  • storing path or value in ref to simplify the initialize code

@kbrock
Copy link
Member Author

kbrock commented May 31, 2024

update

  • went back to all the is_{type} methods
  • dropped all separate classes
  • more pedantic at build time (fewer invalid cases go through

Comment on lines 25 to 12
return presence_check(context, input) if compare_key == "IsPresent"

lhs = variable_value(context, input)
rhs = compare_value(context, input)

validate!(lhs)

Copy link
Member Author

Choose a reason for hiding this comment

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

Before:

  • fetches a value, and verifies what ever came out is not null

After:

  • fetches a value and verifies it existed in the input hash

when "IsTimestamp" then is_timestamp?(lhs)
when "IsNull" then is_null?(lhs, rhs)
when "IsNumeric" then is_numeric?(lhs, rhs)
when "IsString" then is_string?(lhs, rhs)
Copy link
Member Author

Choose a reason for hiding this comment

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

Before: "IsString" => "*" verified it is a string.
After: "IsString" => true works that way, but "IsString" => false does the opposite
(across all checks)

Comment on lines 111 to 87
def variable_value(context, input)
fetch_path("Variable", variable, context, input)
end
Copy link
Member Author

Choose a reason for hiding this comment

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

(moved over from ChoiceRule since this is data specific)
Before:

  • convert @variable to a Path at runtime and fetch value.
    After:
  • @variable is a Path`, so bad Path detected at initialization time
  • Path not present in input is a runtime error
  • Data type of input is checked.

Comment on lines 125 to 91
def parse_compare_key
@compare_key = payload.keys.detect { |key| key.match?(TYPE_CHECK) || key.match?(OPERATION) }
parser_error!("requires a compare key") unless compare_key

@type, _operator, @path = OPERATION.match(compare_key)&.captures
# TYPE_CHECK doesn't match this regex, so @path = @type = nil
# @path.nil? means this the compare_value will always be a static value (true or false)
# @type.nil? means we won't type check the variable or compare value
end
Copy link
Member Author

Choose a reason for hiding this comment

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

Yea, this got more complicated.

After:

  • Can detect bad compare values at initialization time.
  • Can detect bad paths at initialization time.
  • Store type checking information (only for operations. bad type in the type check are informational (true false) rather than errors.

results.count < 2 ? results.first : results
case results.count
when 0
raise Floe::PathError, "Path [#{payload}] references an invalid value"
Copy link
Member Author

Choose a reason for hiding this comment

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

This one change has big consequences.

Before: A path pointing to nothing would just treat the value as nil
After: A path pointing to nothing is a runtime error

rescue Floe::ExecutionError => e
mark_error(context, e)
Copy link
Member Author

Choose a reason for hiding this comment

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

This was detected because tests running against a state would work, but running against a workflow would not.

Before:

  • Errors needed to be caught by each State
  • Errors resulted in a state that was not complete.
  • Errors did not always get their way into output.

After:

  • runtime errors always set the context accordingly.

Comment on lines 59 to 60
def finish(context)
mark_finished(context)
end
Copy link
Member Author

Choose a reason for hiding this comment

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

The code for finish hasn't changed, it just now resides in mark_finished.

Reasoning: If State throws an error, it almost always threw it in finish. When an error is thrown, super.finish is not called. This means no logging and FinishedTime is never set -- so the state looks like it is not finished.

Now, we catch errors thrown, and ensure super.finish is called. Easiest way to do this is to move that code into a separate method -- mark_finish.

@kbrock kbrock force-pushed the choice_rule_payload_validation branch from 893c61d to 1dfe07e Compare August 2, 2024 21:05
@kbrock kbrock added the wip label Aug 9, 2024
@kbrock kbrock changed the title Choice rule payload validation [WIP] Choice rule payload validation Aug 9, 2024
@kbrock
Copy link
Member Author

kbrock commented Aug 9, 2024

WIP: piece by piece pulling out PRs from this

@kbrock kbrock force-pushed the choice_rule_payload_validation branch from 1dfe07e to b60a866 Compare August 21, 2024 14:18
@kbrock kbrock force-pushed the choice_rule_payload_validation branch from b60a866 to 44a8a3e Compare August 23, 2024 20:59
@kbrock kbrock force-pushed the choice_rule_payload_validation branch 3 times, most recently from 1f374b2 to 13a4d7c Compare October 15, 2024 20:43
@kbrock
Copy link
Member Author

kbrock commented Oct 15, 2024

update:

  • fix guard clause - ensured bad operators found in initializer not runtime

update:

  • fix } spacing cop

@kbrock kbrock force-pushed the choice_rule_payload_validation branch from 13a4d7c to a786b40 Compare November 6, 2024 19:37
@miq-bot
Copy link
Member

miq-bot commented Nov 6, 2024

Checked commit kbrock@a786b40 with ruby 3.1.5, rubocop 1.56.3, haml-lint 0.51.0, and yamllint
1 file checked, 0 offenses detected
Everything looks fine. 🍰

@miq-bot
Copy link
Member

miq-bot commented Dec 9, 2024

This pull request is not mergeable. Please rebase and repush.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working enhancement New feature or request unmergeable wip
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants