floe 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -1
- data/lib/floe/version.rb +1 -1
- data/lib/floe/workflow/choice_rule/data.rb +17 -5
- data/lib/floe/workflow/intrinsic_function/parser.rb +9 -1
- data/lib/floe/workflow/intrinsic_function/transformer.rb +76 -6
- data/lib/floe/workflow/path.rb +12 -1
- data/lib/floe/workflow/state.rb +21 -3
- data/lib/floe.rb +1 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: abe15498a790293b2a375c4538ed649bfa577edd1b7eaa38d02266fc4f7fc576
|
4
|
+
data.tar.gz: 733e0e6687ca143de8a74214f13b4a78118333a99f89a9a09a9998ab0319768a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5dfc65c86e68d39b7c9bbeedd20e2a3ea844f158d601bb72bf35595e6d6597ac6571f7bc9ad6802f8ed33d6753b4906e7745a3e46cb38790492869aa95e6a02
|
7
|
+
data.tar.gz: 913032cb3a042e8b3464f05c7e0e65401f8532b28ded1cebd57df310899dcc8bd1bc26d9307a6b824837474c0e3ee9144477147779e0b40b6fd9c9fecfc7bddc
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
4
4
|
|
5
5
|
## [Unreleased]
|
6
6
|
|
7
|
+
## [0.13.0] - 2024-08-12
|
8
|
+
### Added
|
9
|
+
- Choice rule payload validation path ([#253](https://github.com/ManageIQ/floe/pull/253))
|
10
|
+
- Intrinsics JsonToString and StringToJson ([#256](https://github.com/ManageIQ/floe/pull/256))
|
11
|
+
- Add States.Format intrinsic function ([#258](https://github.com/ManageIQ/floe/pull/258))
|
12
|
+
- Intrinsics States.JsonMerge ([#255](https://github.com/ManageIQ/floe/pull/255))
|
13
|
+
- Enable support for Hashes in States.Hash ([#260](https://github.com/ManageIQ/floe/pull/260))
|
14
|
+
|
7
15
|
## [0.12.0] - 2024-07-31
|
8
16
|
### Added
|
9
17
|
- Set Floe.logger.level if DEBUG env var set ([#234](https://github.com/ManageIQ/floe/pull/234))
|
@@ -225,7 +233,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
225
233
|
### Added
|
226
234
|
- Initial release
|
227
235
|
|
228
|
-
[Unreleased]: https://github.com/ManageIQ/floe/compare/v0.
|
236
|
+
[Unreleased]: https://github.com/ManageIQ/floe/compare/v0.13.0...HEAD
|
237
|
+
[0.13.0]: https://github.com/ManageIQ/floe/compare/v0.12.0...v0.13.0
|
229
238
|
[0.12.0]: https://github.com/ManageIQ/floe/compare/v0.11.3...v0.12.0
|
230
239
|
[0.11.3]: https://github.com/ManageIQ/floe/compare/v0.11.2...v0.11.3
|
231
240
|
[0.11.2]: https://github.com/ManageIQ/floe/compare/v0.11.1...v0.11.2
|
data/lib/floe/version.rb
CHANGED
@@ -5,14 +5,13 @@ module Floe
|
|
5
5
|
class ChoiceRule
|
6
6
|
class Data < Floe::Workflow::ChoiceRule
|
7
7
|
def true?(context, input)
|
8
|
+
return presence_check(context, input) if compare_key == "IsPresent"
|
9
|
+
|
8
10
|
lhs = variable_value(context, input)
|
9
11
|
rhs = compare_value(context, input)
|
10
12
|
|
11
|
-
validate!(lhs)
|
12
|
-
|
13
13
|
case compare_key
|
14
14
|
when "IsNull" then is_null?(lhs)
|
15
|
-
when "IsPresent" then is_present?(lhs)
|
16
15
|
when "IsNumeric" then is_numeric?(lhs)
|
17
16
|
when "IsString" then is_string?(lhs)
|
18
17
|
when "IsBoolean" then is_boolean?(lhs)
|
@@ -47,8 +46,21 @@ module Floe
|
|
47
46
|
|
48
47
|
private
|
49
48
|
|
50
|
-
def
|
51
|
-
|
49
|
+
def presence_check(context, input)
|
50
|
+
# Get the right hand side for {"Variable": "$.foo", "IsPresent": true} i.e.: true
|
51
|
+
# If true then return true when present.
|
52
|
+
# If false then return true when not present.
|
53
|
+
rhs = compare_value(context, input)
|
54
|
+
# Don't need the variable_value, just need to see if the path finds the value.
|
55
|
+
variable_value(context, input)
|
56
|
+
|
57
|
+
# The variable_value is present
|
58
|
+
# If rhs is true, then presence check was successful, return true.
|
59
|
+
rhs
|
60
|
+
rescue Floe::PathError
|
61
|
+
# variable_value is not present. (the path lookup threw an error)
|
62
|
+
# If rhs is false, then it successfully wasn't present, return true.
|
63
|
+
!rhs
|
52
64
|
end
|
53
65
|
|
54
66
|
def is_null?(value) # rubocop:disable Naming/PredicateName
|
@@ -54,6 +54,9 @@ module Floe
|
|
54
54
|
end
|
55
55
|
|
56
56
|
[
|
57
|
+
:states_format, "States.Format",
|
58
|
+
:states_string_to_json, "States.StringToJson",
|
59
|
+
:states_json_to_string, "States.JsonToString",
|
57
60
|
:states_array, "States.Array",
|
58
61
|
:states_array_partition, "States.ArrayPartition",
|
59
62
|
:states_array_contains, "States.ArrayContains",
|
@@ -64,6 +67,7 @@ module Floe
|
|
64
67
|
:states_base64_encode, "States.Base64Encode",
|
65
68
|
:states_base64_decode, "States.Base64Decode",
|
66
69
|
:states_hash, "States.Hash",
|
70
|
+
:states_json_merge, "States.JsonMerge",
|
67
71
|
:states_math_random, "States.MathRandom",
|
68
72
|
:states_math_add, "States.MathAdd",
|
69
73
|
:states_string_split, "States.StringSplit",
|
@@ -77,7 +81,10 @@ module Floe
|
|
77
81
|
end
|
78
82
|
|
79
83
|
rule(:expression) do
|
80
|
-
|
84
|
+
states_format |
|
85
|
+
states_string_to_json |
|
86
|
+
states_json_to_string |
|
87
|
+
states_array |
|
81
88
|
states_array_partition |
|
82
89
|
states_array_contains |
|
83
90
|
states_array_range |
|
@@ -87,6 +94,7 @@ module Floe
|
|
87
94
|
states_base64_encode |
|
88
95
|
states_base64_decode |
|
89
96
|
states_hash |
|
97
|
+
states_json_merge |
|
90
98
|
states_math_random |
|
91
99
|
states_math_add |
|
92
100
|
states_string_split |
|
@@ -9,6 +9,7 @@ module Floe
|
|
9
9
|
class IntrinsicFunction
|
10
10
|
class Transformer < Parslet::Transform
|
11
11
|
OptionalArg = Struct.new(:type)
|
12
|
+
VariadicArgs = Struct.new(:type)
|
12
13
|
|
13
14
|
class << self
|
14
15
|
def process_args(args, function, signature = nil)
|
@@ -37,20 +38,33 @@ module Floe
|
|
37
38
|
|
38
39
|
def check_arity(args, function, signature)
|
39
40
|
if signature.any?(OptionalArg)
|
40
|
-
|
41
|
-
signature_size = (
|
41
|
+
signature_required = signature.reject { |a| a.kind_of?(OptionalArg) }
|
42
|
+
signature_size = (signature_required.size..signature.size)
|
42
43
|
|
43
44
|
raise ArgumentError, "wrong number of arguments to #{function} (given #{args.size}, expected #{signature_size})" unless signature_size.include?(args.size)
|
45
|
+
elsif signature.any?(VariadicArgs)
|
46
|
+
signature_required = signature.reject { |a| a.kind_of?(VariadicArgs) }
|
47
|
+
|
48
|
+
raise ArgumentError, "wrong number of arguments to #{function} (given #{args.size}, expected at least #{signature_required.size})" unless args.size >= signature_required.size
|
44
49
|
else
|
45
50
|
raise ArgumentError, "wrong number of arguments to #{function} (given #{args.size}, expected #{signature.size})" unless signature.size == args.size
|
46
51
|
end
|
47
52
|
end
|
48
53
|
|
49
54
|
def check_types(args, function, signature)
|
55
|
+
# Adjust the signature for VariadicArgs to create a copy of the expected type for each given arg
|
56
|
+
if signature.last.kind_of?(VariadicArgs)
|
57
|
+
signature = signature[0..-2] + Array.new(args.size - signature.size + 1, signature.last.type)
|
58
|
+
end
|
59
|
+
|
50
60
|
args.zip(signature).each_with_index do |(arg, type), index|
|
51
61
|
type = type.type if type.kind_of?(OptionalArg)
|
52
62
|
|
53
|
-
|
63
|
+
if type.kind_of?(Array)
|
64
|
+
raise ArgumentError, "wrong type for argument #{index + 1} to #{function} (given #{arg.class}, expected one of #{type.join(", ")})" unless type.any? { |t| arg.kind_of?(t) }
|
65
|
+
else
|
66
|
+
raise ArgumentError, "wrong type for argument #{index + 1} to #{function} (given #{arg.class}, expected #{type})" unless arg.kind_of?(type)
|
67
|
+
end
|
54
68
|
end
|
55
69
|
end
|
56
70
|
end
|
@@ -63,6 +77,50 @@ module Floe
|
|
63
77
|
rule(:number => simple(:v)) { v.match(/[eE.]/) ? Float(v) : Integer(v) }
|
64
78
|
rule(:jsonpath => simple(:v)) { Floe::Workflow::Path.value(v.to_s, context, input) }
|
65
79
|
|
80
|
+
STATES_FORMAT_PLACEHOLDER = /(?<!\\)\{\}/.freeze
|
81
|
+
|
82
|
+
rule(:states_format => {:args => subtree(:args)}) do
|
83
|
+
args = Transformer.process_args(args(), "States.Format", [String, VariadicArgs[[String, TrueClass, FalseClass, Integer, Float, NilClass]]])
|
84
|
+
str, *rest = *args
|
85
|
+
|
86
|
+
# TODO: Handle templates with escaped characters, including invalid templates
|
87
|
+
# See https://states-language.net/#intrinsic-functions (number 6)
|
88
|
+
|
89
|
+
expected_args = str.scan(STATES_FORMAT_PLACEHOLDER).size
|
90
|
+
actual_args = rest.size
|
91
|
+
if expected_args != actual_args
|
92
|
+
raise ArgumentError, "number of arguments to States.Format do not match the occurrences of {} (given #{actual_args}, expected #{expected_args})"
|
93
|
+
end
|
94
|
+
|
95
|
+
rest.each do |arg|
|
96
|
+
str = str.sub(STATES_FORMAT_PLACEHOLDER, arg.nil? ? "null" : arg.to_s)
|
97
|
+
end
|
98
|
+
|
99
|
+
# TODO: Handle arguments that have escape characters within them but are interpolated
|
100
|
+
str.gsub!("\\'", "'")
|
101
|
+
str.gsub!("\\{", "{")
|
102
|
+
str.gsub!("\\}", "}")
|
103
|
+
str.gsub!("\\\\", "\\")
|
104
|
+
|
105
|
+
str
|
106
|
+
end
|
107
|
+
|
108
|
+
rule(:states_json_to_string => {:args => subtree(:args)}) do
|
109
|
+
args = Transformer.process_args(args(), "States.JsonToString", [Object])
|
110
|
+
json = args.first
|
111
|
+
|
112
|
+
JSON.generate(json)
|
113
|
+
end
|
114
|
+
|
115
|
+
rule(:states_string_to_json => {:args => subtree(:args)}) do
|
116
|
+
args = Transformer.process_args(args(), "States.StringToJson", [String])
|
117
|
+
str = args.first
|
118
|
+
|
119
|
+
JSON.parse(str)
|
120
|
+
rescue JSON::ParserError => e
|
121
|
+
raise ArgumentError, "invalid value for argument 1 to States.StringToJson (invalid json: #{e.message})"
|
122
|
+
end
|
123
|
+
|
66
124
|
rule(:states_array => {:args => subtree(:args)}) do
|
67
125
|
Transformer.process_args(args, "States.Array")
|
68
126
|
end
|
@@ -135,7 +193,7 @@ module Floe
|
|
135
193
|
rule(:states_hash => {:args => subtree(:args)}) do
|
136
194
|
args = Transformer.process_args(args(), "States.Hash", [Object, String])
|
137
195
|
data, algorithm = *args
|
138
|
-
|
196
|
+
|
139
197
|
if data.nil?
|
140
198
|
raise ArgumentError, "invalid value for argument 1 to States.Hash (given null, expected non-null)"
|
141
199
|
end
|
@@ -147,8 +205,20 @@ module Floe
|
|
147
205
|
|
148
206
|
require "openssl"
|
149
207
|
algorithm = algorithm.sub("-", "")
|
150
|
-
data = data
|
151
|
-
OpenSSL::Digest.hexdigest(algorithm, data)
|
208
|
+
data = JSON.generate(data) unless data.kind_of?(String)
|
209
|
+
OpenSSL::Digest.hexdigest(algorithm, data).force_encoding("UTF-8")
|
210
|
+
end
|
211
|
+
|
212
|
+
rule(:states_json_merge => {:args => subtree(:args)}) do
|
213
|
+
args = Transformer.process_args(args(), "States.JsonMerge", [Hash, Hash, [TrueClass, FalseClass]])
|
214
|
+
left, right, deep = *args
|
215
|
+
|
216
|
+
if deep
|
217
|
+
# NOTE: not implemented by AWS Step Functions and nuances not defined in docs
|
218
|
+
left.merge(right) { |_key, l, r| l.kind_of?(Hash) && r.kind_of?(Hash) ? l.merge(r) : r }
|
219
|
+
else
|
220
|
+
left.merge(right)
|
221
|
+
end
|
152
222
|
end
|
153
223
|
|
154
224
|
rule(:states_math_random => {:args => subtree(:args)}) do
|
data/lib/floe/workflow/path.rb
CHANGED
@@ -34,7 +34,18 @@ module Floe
|
|
34
34
|
return obj if path == "$"
|
35
35
|
|
36
36
|
results = JsonPath.on(obj, path)
|
37
|
-
results.count
|
37
|
+
case results.count
|
38
|
+
when 0
|
39
|
+
raise Floe::PathError, "Path [#{payload}] references an invalid value"
|
40
|
+
when 1
|
41
|
+
results.first
|
42
|
+
else
|
43
|
+
results
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
payload
|
38
49
|
end
|
39
50
|
end
|
40
51
|
end
|
data/lib/floe/workflow/state.rb
CHANGED
@@ -48,17 +48,27 @@ module Floe
|
|
48
48
|
return Errno::EAGAIN unless ready?(context)
|
49
49
|
|
50
50
|
finish(context)
|
51
|
+
rescue Floe::Error => e
|
52
|
+
mark_error(context, e)
|
51
53
|
end
|
52
54
|
|
53
55
|
def start(context)
|
56
|
+
mark_started(context)
|
57
|
+
end
|
58
|
+
|
59
|
+
def finish(context)
|
60
|
+
mark_finished(context)
|
61
|
+
end
|
62
|
+
|
63
|
+
def mark_started(context)
|
54
64
|
context.state["EnteredTime"] = Time.now.utc.iso8601
|
55
65
|
|
56
66
|
logger.info("Running state: [#{long_name}] with input [#{context.json_input}]...")
|
57
67
|
end
|
58
68
|
|
59
|
-
def
|
60
|
-
finished_time
|
61
|
-
entered_time
|
69
|
+
def mark_finished(context)
|
70
|
+
finished_time = Time.now.utc
|
71
|
+
entered_time = Time.parse(context.state["EnteredTime"])
|
62
72
|
|
63
73
|
context.state["FinishedTime"] ||= finished_time.iso8601
|
64
74
|
context.state["Duration"] = finished_time - entered_time
|
@@ -69,6 +79,14 @@ module Floe
|
|
69
79
|
0
|
70
80
|
end
|
71
81
|
|
82
|
+
def mark_error(context, exception)
|
83
|
+
# InputPath or OutputPath were bad.
|
84
|
+
context.next_state = nil
|
85
|
+
context.output = {"Error" => "States.Runtime", "Cause" => exception.message}
|
86
|
+
# Since finish threw an exception, super was never called. Calling that now.
|
87
|
+
mark_finished(context)
|
88
|
+
end
|
89
|
+
|
72
90
|
def ready?(context)
|
73
91
|
!context.state_started? || !running?(context)
|
74
92
|
end
|
data/lib/floe.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: floe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ManageIQ Developers
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: awesome_spawn
|