Skip to content

Conversation

@kbrock
Copy link
Member

@kbrock kbrock commented May 22, 2024

alt to #187

Depend upon:

Overview

Remove un-necessary string allocations. and symbol lookups

Most of the changes have already been pulled out and merged.
So if we don't like this, then that is fine.


Leaving this for legacy reasons

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 Fryguy and agrare 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

@kbrock kbrock force-pushed the choice_rule_payload_validation branch from 49a5ec5 to e5455a3 Compare September 5, 2025 19:58
@kbrock
Copy link
Member Author

kbrock commented Sep 5, 2025

update:

  • rebased (to make merge-able)

update:

  • converted "string".to_sym to :"string"

un-WIP: this is working, no more dependencies. want to either merge or just drop

@kbrock kbrock changed the title [WIP] Choice rule payload validation Remove unnecessary string allocations for operator lookups Sep 5, 2025
@kbrock kbrock removed the wip label Sep 5, 2025
class Data < Floe::Workflow::ChoiceRule
TYPES = ["String", "Numeric", "Boolean", "Timestamp", "Present", "Null"].freeze
COMPARES = ["Equals", "LessThan", "GreaterThan", "LessThanEquals", "GreaterThanEquals", "Matches"].freeze
OPERATIONS = TYPES.each_with_object({}) { |dt, a| a[dt] = :"is_#{dt.downcase}?" } \
Copy link
Member

Choose a reason for hiding this comment

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

Line continuation is not needed

Suggested change
OPERATIONS = TYPES.each_with_object({}) { |dt, a| a[dt] = :"is_#{dt.downcase}?" } \
OPERATIONS = TYPES.each_with_object({}) { |dt, a| a[dt] = :"is_#{dt.downcase}?" }

Copy link
Member Author

Choose a reason for hiding this comment

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

strange, thought I did this a while back. must have rebased it out of here.
It is back

Comment on lines -14 to +16
attr_reader :variable, :compare_key, :operation, :type, :compare_predicate, :path
attr_reader :variable, :compare_key, :operator, :type, :compare_predicate, :path
Copy link
Member

Choose a reason for hiding this comment

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

This is changing the "public" api - not a big deal if it's internal, but is anything else using this?

Copy link
Member Author

@kbrock kbrock Sep 9, 2025

Choose a reason for hiding this comment

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

we've been pretty lax on public and private api for these models.

Every attribute here is internal to this class only.

In a separate PR, do we want to circle through and mark private/public?
Would it buy us anything, or will it just make debugging more difficult?

Come to think of it, I think only Workflow and Context have a public api.

Copy link
Member

Choose a reason for hiding this comment

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

No it's fine, I just wanted to make sure that was accounted for or not.

@Fryguy
Copy link
Member

Fryguy commented Sep 8, 2025

This PR got renamed? Having trouble following the history here.

@kbrock kbrock force-pushed the choice_rule_payload_validation branch from e5455a3 to afab6ce Compare September 9, 2025 20:22
@kbrock
Copy link
Member Author

kbrock commented Sep 9, 2025

This was a PR with a ton of stuff. almost all of the stuff had been extracted (as you can see from the punch list at the top)
The only thing left in a minimal (and not mandatory) change to not build the names of the operations at runtime, but rather just use a lookup.

Originally, the type checks (IsString) and operations/compares (StringGreaterThan) stored in TYPES and COMPARES were hashes from the keys to the method names. As we extracted out the PRs, this hash structure was lost.

I am not a fan of the non-grep style of send("#{var1}#{var2}"). I guess OPERATIONS is essentially just that, thought defined up front. Alternately, explicitly listing the operations in the hash form of TYPES and COMPARES may make sense.


update:

  • rebased
  • spelling fixes in comments
  • some descriptions around the checks and parameters
  • removed unnecessary line continuation

@kbrock kbrock force-pushed the choice_rule_payload_validation branch from afab6ce to b783324 Compare September 10, 2025 14:14
@kbrock kbrock changed the title Remove unnecessary string allocations for operator lookups Choice rule payload validation Sep 10, 2025
elsif (match_value = TYPE_CHECK.match(key))
@compare_key = key
_operator, type = match_value.captures
_is, @operator = match_value.captures
Copy link
Member

@Fryguy Fryguy Sep 10, 2025

Choose a reason for hiding this comment

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

If we don't need the Is, you can also just not capture it in the first place. i.e.

- TYPE_CHECK = /^(Is)(#{TYPES.join("|")})$/
+ TYPE_CHECK = /^Is(#{TYPES.join("|")})$/

Copy link
Member Author

@kbrock kbrock Sep 11, 2025

Choose a reason for hiding this comment

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

@Fryguy Yes, totally agree that the Is does not need to be captured, and it is probably more resource intensive.

I like the parallelism:

  • operations has (String)(Equals), (String)(GreaterThan)(Path).
  • type check has (Is)(String)

Where we have a type (i.e.: String) and a verb (e.g.: Is, Equals, GreaterThan).

OPERATIONS = TYPES.each_with_object({}) { |dt, a| a[dt] = :"is_#{dt.downcase}?" } \
.merge(COMPARES.each_with_object({}) { |op, a| a[op] = :"op_#{op.downcase}?" }).freeze
# e.g.: (Is)(String), (Is)(Present)
TYPE_CHECK = /^(Is)(#{TYPES.join("|")})$/
Copy link
Member

Choose a reason for hiding this comment

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

I just noticed this now, so not for this PR, but instead of TYPES.join(|), you should probably use Regex.union(TYPES). Same goes for the OPERATION constant

Copy link
Member Author

Choose a reason for hiding this comment

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

irb(main):006> /Is(#{['a', 'b'].join("|")})/
=> /Is(a|b)/
irb(main):008> /Is(#{Regexp.union(['a', 'b'])})/
=> /Is((?-mix:a|b))/

Are these the same thing?

Copy link
Member Author

Choose a reason for hiding this comment

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

I have a separate PR all set for this

@Fryguy
Copy link
Member

Fryguy commented Sep 10, 2025

I'm fine with the PR rename now that the scope has been narrowed - it was just confusing because the opening PR had this list of dependent PRs that seemed unrelated.

@Fryguy Fryguy closed this Sep 10, 2025
@Fryguy Fryguy reopened this Sep 10, 2025
@kbrock
Copy link
Member Author

kbrock commented Sep 11, 2025

@Fryguy I think there is a question that asks, do we want to even do this?

A while ago, we used to have hashes that were manually created:

COMPARES = {"GreaterThan" => "is_gt?", ...}

I liked that simple lookup. Currently, those are arrays not hashes and we manually generate the methods on the fly.

This PR suggests auto generating a hash, but maybe that is just overkill/un-necessary.

Do we think a static hash makes this any more sense/grepable/readable? Or is there another way to make this more accessible? (Just put the operation comment at the beginning of every op_gt? kind of method?).

Performance wise, allocation difference will be negligible. Not sure the best way to benchmark this - but doesn't seem worth the work to gauge this.

@kbrock kbrock force-pushed the choice_rule_payload_validation branch 2 times, most recently from 38a20cf to fe8a2db Compare September 12, 2025 15:22
@kbrock kbrock force-pushed the choice_rule_payload_validation branch from fe8a2db to a5d6f21 Compare September 12, 2025 15:28
@kbrock
Copy link
Member Author

kbrock commented Sep 12, 2025

update

  • remove line continuation (could have sworn I did this a while ago)
  • removed (Is) capture

@Fryguy Fryguy merged commit 948fbf1 into ManageIQ:master Sep 12, 2025
5 checks passed
@Fryguy Fryguy assigned Fryguy and unassigned agrare Sep 12, 2025
@kbrock kbrock deleted the choice_rule_payload_validation branch September 15, 2025 16:05
kbrock added a commit to kbrock/floe that referenced this pull request Sep 17, 2025
- No longer access workflow#payload out side of Workflow (ManageIQ#321)
- Cache choice rule operations  (ManageIQ#189)

- Fix `Context#==` to compare contents (ManageIQ#317)
- Declare active support dependency (ManageIQ#316)
agrare added a commit that referenced this pull request Oct 24, 2025
Added
- Declare active support dependency (#316)
- Add Context equality comparison (#317)
- Choice rule payload validation (#189)

Changed
- In ChoiceRule use Regexp.union (#323)
- Remove special case for choice rule IsPresent (#322)
- Rename data operation parameters (#324)
- Refactor workflow state check to not use payload (#326)

Fixed
- Fix missing parameters in item_batcher_spec (#325)
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants