lazy_graph 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +282 -47
- data/Rakefile +12 -1
- data/examples/converter.rb +41 -0
- data/examples/performance_tests.rb +4 -4
- data/examples/shopping_cart_totals.rb +56 -0
- data/lib/lazy_graph/builder/dsl.rb +67 -29
- data/lib/lazy_graph/builder.rb +43 -23
- data/lib/lazy_graph/builder_group.rb +29 -18
- data/lib/lazy_graph/cli.rb +47 -0
- data/lib/lazy_graph/context.rb +10 -6
- data/lib/lazy_graph/environment.rb +6 -0
- data/lib/lazy_graph/graph.rb +5 -3
- data/lib/lazy_graph/hash_utils.rb +4 -1
- data/lib/lazy_graph/logger.rb +87 -0
- data/lib/lazy_graph/missing_value.rb +8 -0
- data/lib/lazy_graph/node/array_node.rb +3 -2
- data/lib/lazy_graph/node/derived_rules.rb +60 -18
- data/lib/lazy_graph/node/node_properties.rb +16 -3
- data/lib/lazy_graph/node/object_node.rb +9 -5
- data/lib/lazy_graph/node/symbol_hash.rb +15 -2
- data/lib/lazy_graph/node.rb +97 -39
- data/lib/lazy_graph/rack_app.rb +138 -0
- data/lib/lazy_graph/rack_server.rb +199 -0
- data/lib/lazy_graph/version.rb +1 -1
- data/lib/lazy_graph.rb +6 -8
- metadata +115 -11
- data/lib/lazy_graph/server.rb +0 -96
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require_relative 'environment'
|
3
|
+
|
4
|
+
module LazyGraph
|
5
|
+
class << self
|
6
|
+
attr_accessor :logger
|
7
|
+
end
|
8
|
+
|
9
|
+
module Logger
|
10
|
+
COLORIZED_LOGS = !ENV['DISABLED_COLORIZED_LOGS'] && ENV.fetch('RACK_ENV', 'development') == 'development'
|
11
|
+
|
12
|
+
module_function
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_accessor :color_enabled, :structured
|
16
|
+
end
|
17
|
+
|
18
|
+
def structured
|
19
|
+
return @structured if defined?(@structured)
|
20
|
+
@structured = !LazyGraph::Environment.development?
|
21
|
+
end
|
22
|
+
|
23
|
+
def default_logger
|
24
|
+
logger = ::Logger.new($stdout)
|
25
|
+
self.color_enabled ||= Logger::COLORIZED_LOGS
|
26
|
+
if self.color_enabled
|
27
|
+
logger.formatter = proc do |severity, datetime, progname, message|
|
28
|
+
light_gray_timestamp = "\e[90m[##{Process.pid}] #{datetime.strftime('%Y-%m-%dT%H:%M:%S.%6N')}\e[0m" # Light gray timestamp
|
29
|
+
"#{light_gray_timestamp} \e[1m#{severity}\e[0m #{progname}: #{message}\n"
|
30
|
+
end
|
31
|
+
elsif self.structured
|
32
|
+
logger.formatter = proc do |severity, datetime, progname, message|
|
33
|
+
"#{{severity:, datetime:, progname:, **(message.is_a?(Hash) ? message : {message: }) }.to_json}\n"
|
34
|
+
end
|
35
|
+
else
|
36
|
+
logger.formatter = proc do |severity, datetime, progname, message|
|
37
|
+
"[##{Process.pid}] #{datetime.strftime('%Y-%m-%dT%H:%M:%S.%6N')} #{severity} #{progname}: #{message}\n"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
logger
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_color_string(&blk)
|
44
|
+
return unless block_given?
|
45
|
+
|
46
|
+
instance_eval(&blk)
|
47
|
+
end
|
48
|
+
|
49
|
+
def colorize(text, color_code)
|
50
|
+
@color_enabled ? "\e[#{color_code}m#{text}\e[0m" : text
|
51
|
+
end
|
52
|
+
|
53
|
+
def green(text)
|
54
|
+
colorize(text, 32) # Green for success
|
55
|
+
end
|
56
|
+
|
57
|
+
def red(text)
|
58
|
+
colorize(text, 31) # Red for errors
|
59
|
+
end
|
60
|
+
|
61
|
+
def yellow(text)
|
62
|
+
colorize(text, 33) # Yellow for warnings or debug
|
63
|
+
end
|
64
|
+
|
65
|
+
def blue(text)
|
66
|
+
colorize(text, 34) # Blue for info
|
67
|
+
end
|
68
|
+
|
69
|
+
def light_gray(text)
|
70
|
+
colorize(text, 90) # Light gray for faded text
|
71
|
+
end
|
72
|
+
|
73
|
+
def orange(text)
|
74
|
+
colorize(text, '38;5;214')
|
75
|
+
end
|
76
|
+
|
77
|
+
def bold(text)
|
78
|
+
colorize(text, 1) # Bold text
|
79
|
+
end
|
80
|
+
|
81
|
+
def dim(text)
|
82
|
+
colorize(text, 2) # Italic text
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
self.logger = Logger.default_logger
|
87
|
+
end
|
@@ -14,6 +14,14 @@ module LazyGraph
|
|
14
14
|
def to_h = nil
|
15
15
|
def +(other) = other
|
16
16
|
def respond_to_missing?(_method_name, _include_private = false) = true
|
17
|
+
def to_i = 0
|
18
|
+
def to_f = 0.0
|
19
|
+
|
20
|
+
def ==(other)
|
21
|
+
return true if other.nil?
|
22
|
+
|
23
|
+
super
|
24
|
+
end
|
17
25
|
|
18
26
|
def method_missing(method, *args, &block)
|
19
27
|
return super if method == :to_ary
|
@@ -14,7 +14,8 @@ module LazyGraph
|
|
14
14
|
should_recycle = stack_memory,
|
15
15
|
**
|
16
16
|
)
|
17
|
-
input = stack_memory.frame
|
17
|
+
return MissingValue() unless input = stack_memory.frame
|
18
|
+
|
18
19
|
@visited[input.object_id >> 2 ^ path.shifted_id] ||= begin
|
19
20
|
path_next = path.next
|
20
21
|
if (path_segment = path.segment).is_a?(PathParser::PathGroup)
|
@@ -61,7 +62,7 @@ module LazyGraph
|
|
61
62
|
end
|
62
63
|
|
63
64
|
def cast(value)
|
64
|
-
value
|
65
|
+
Array(value)
|
65
66
|
end
|
66
67
|
end
|
67
68
|
end
|
@@ -44,9 +44,10 @@ module LazyGraph
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def interpret_derived_proc(derived)
|
47
|
-
src, requireds, optionals, keywords,
|
48
|
-
|
49
|
-
@src =
|
47
|
+
src, requireds, optionals, keywords, loc = DerivedRules.extract_expr_from_source_location(derived.source_location)
|
48
|
+
body = src.body&.slice || ''
|
49
|
+
@src = body.lines.map(&:strip)
|
50
|
+
offset = src.slice.lines.length - body.lines.length
|
50
51
|
inputs, conditions = parse_args_with_conditions(requireds, optionals, keywords)
|
51
52
|
|
52
53
|
{
|
@@ -54,11 +55,11 @@ module LazyGraph
|
|
54
55
|
mtime: File.mtime(derived.source_location.first),
|
55
56
|
conditions: conditions,
|
56
57
|
calc: instance_eval(
|
57
|
-
"->(#{inputs.keys.map { |k| "#{k}=self.#{k}" }.join(', ')}){ #{
|
58
|
+
"->(#{inputs.keys.map { |k| "#{k}=self.#{k}" }.join(', ')}){ #{body}}",
|
58
59
|
# rubocop:disable:next-line
|
59
60
|
derived.source_location.first,
|
60
61
|
# rubocop:enable
|
61
|
-
derived.source_location.last
|
62
|
+
derived.source_location.last + offset
|
62
63
|
)
|
63
64
|
}
|
64
65
|
end
|
@@ -78,13 +79,21 @@ module LazyGraph
|
|
78
79
|
[keywords, conditions.any? ? conditions : nil]
|
79
80
|
end
|
80
81
|
|
82
|
+
def self.get_file_body(file_path)
|
83
|
+
@file_body_cache ||= {}
|
84
|
+
if @file_body_cache[file_path]&.last.to_i < File.mtime(file_path).to_i
|
85
|
+
@file_body_cache[file_path] = [IO.readlines(file_path), File.mtime(file_path).to_i]
|
86
|
+
end
|
87
|
+
@file_body_cache[file_path]&.first
|
88
|
+
end
|
89
|
+
|
81
90
|
def self.extract_expr_from_source_location(source_location)
|
82
91
|
@derived_proc_cache ||= {}
|
83
92
|
mtime = File.mtime(source_location.first).to_i
|
84
|
-
|
85
93
|
if @derived_proc_cache[source_location]&.last.to_i.< mtime
|
86
94
|
@derived_proc_cache[source_location] = begin
|
87
|
-
source_lines =
|
95
|
+
source_lines = get_file_body(source_location.first)
|
96
|
+
|
88
97
|
proc_line = source_location.last - 1
|
89
98
|
first_line = source_lines[proc_line]
|
90
99
|
until first_line =~ /(?:lambda|proc|->)/ || proc_line.zero?
|
@@ -133,15 +142,16 @@ module LazyGraph
|
|
133
142
|
inputs = derived[:inputs]
|
134
143
|
case inputs
|
135
144
|
when Symbol, String
|
136
|
-
if
|
145
|
+
if !derived[:calc]
|
137
146
|
@src ||= inputs
|
138
147
|
input_hash = {}
|
139
148
|
@input_mapper = {}
|
140
|
-
|
149
|
+
calc = inputs.gsub(PLACEHOLDER_VAR_REGEX) do |match|
|
141
150
|
sub = input_hash[match[2...-1]] ||= "a#{::SecureRandom.hex(8)}"
|
142
151
|
@input_mapper[sub.to_sym] = match[2...-1].to_sym
|
143
152
|
sub
|
144
153
|
end
|
154
|
+
derived[:calc] = calc unless calc == input_hash.values.first
|
145
155
|
input_hash.invert
|
146
156
|
else
|
147
157
|
{ inputs.to_s.gsub(/[^(?:[A-Za-z][A-Za-z0-9_])]/, '__') => inputs.to_s.freeze }
|
@@ -168,9 +178,20 @@ module LazyGraph
|
|
168
178
|
|
169
179
|
def parse_rule_string(derived)
|
170
180
|
calc_str = derived[:calc]
|
171
|
-
|
172
|
-
|
173
|
-
|
181
|
+
node_path = path
|
182
|
+
|
183
|
+
src = <<~RUBY, @rule_location&.first, @rule_location&.last.to_i - 2
|
184
|
+
->{
|
185
|
+
begin
|
186
|
+
#{calc_str}
|
187
|
+
rescue StandardError => e;
|
188
|
+
LazyGraph.logger.error("Exception in \#{calc_str} => \#{node_path}. \#{e.message}")
|
189
|
+
raise e
|
190
|
+
end
|
191
|
+
}
|
192
|
+
RUBY
|
193
|
+
|
194
|
+
instance_eval(*src)
|
174
195
|
rescue SyntaxError
|
175
196
|
missing_value = MissingValue { "Syntax error in #{derived[:src]}" }
|
176
197
|
-> { missing_value }
|
@@ -201,18 +222,39 @@ module LazyGraph
|
|
201
222
|
(segment == name ? parent.parent : @parent).find_resolver_for(segment)
|
202
223
|
end
|
203
224
|
|
225
|
+
def rule_definition_backtrace
|
226
|
+
if @rule_location && @rule_location.size >= 2
|
227
|
+
rule_file, rule_line = @rule_location
|
228
|
+
rule_entry = "#{rule_file}:#{rule_line}:in `rule`"
|
229
|
+
else
|
230
|
+
rule_entry = 'unknown_rule_location'
|
231
|
+
end
|
232
|
+
|
233
|
+
current_backtrace = caller.reverse.take_while { |line| !line.include?('/lib/lazy_graph/') }.reverse
|
234
|
+
[rule_entry] + current_backtrace
|
235
|
+
end
|
236
|
+
|
204
237
|
def map_derived_inputs_to_paths(inputs)
|
205
238
|
inputs.values.map.with_index do |path, idx|
|
206
239
|
segments = path.parts.map.with_index do |segment, i|
|
207
240
|
if segment.is_a?(PathParser::PathGroup) &&
|
208
|
-
segment.options.length == 1 &&
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
nil
|
241
|
+
segment.options.length == 1 && !((resolver = resolver_for(segment.options.first)) || segment.options.first.segment.part.to_s =~ /\d+/)
|
242
|
+
raise(ValidationError.new(
|
243
|
+
"Invalid dependency in #{@path}: #{segment.options.first.to_path_str} cannot be resolved."
|
244
|
+
).tap { |e| e.set_backtrace(rule_definition_backtrace) })
|
213
245
|
end
|
246
|
+
|
247
|
+
resolver ? [i, resolver] : nil
|
214
248
|
end.compact
|
215
|
-
|
249
|
+
resolver = resolver_for(path)
|
250
|
+
|
251
|
+
unless resolver
|
252
|
+
raise(ValidationError.new(
|
253
|
+
"Invalid dependency in #{@path}: #{path.to_path_str} cannot be resolved."
|
254
|
+
).tap { |e| e.set_backtrace(rule_definition_backtrace) })
|
255
|
+
end
|
256
|
+
|
257
|
+
[path, resolver, idx, segments.any? ? segments : nil]
|
216
258
|
end
|
217
259
|
end
|
218
260
|
end
|
@@ -8,14 +8,27 @@ module LazyGraph
|
|
8
8
|
members.each { |k| self[k] = kws[k].then { |v| v.nil? ? MissingValue::BLANK : v } }
|
9
9
|
end
|
10
10
|
|
11
|
+
members.each do |m|
|
12
|
+
define_method(m) do
|
13
|
+
self[m]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :original_get, :[]
|
18
|
+
|
11
19
|
define_method(:key?) do |x|
|
12
|
-
!
|
20
|
+
!original_get(x).equal?(MissingValue::BLANK)
|
13
21
|
end
|
14
22
|
|
15
23
|
define_method(:[]=) do |key, val|
|
16
24
|
super(key, val)
|
17
25
|
end
|
18
26
|
|
27
|
+
define_method(:[]) do |key|
|
28
|
+
res = original_get(key)
|
29
|
+
res.is_a?(MissingValue) ? nil : res
|
30
|
+
end
|
31
|
+
|
19
32
|
define_method(:members) do
|
20
33
|
members
|
21
34
|
end
|
@@ -34,7 +47,7 @@ module LazyGraph
|
|
34
47
|
|
35
48
|
def ==(other)
|
36
49
|
return super if other.is_a?(self.class)
|
37
|
-
return to_h.eql?(other.to_h) if other.respond_to?(:to_h)
|
50
|
+
return to_h.eql?(other.to_h.keep_if { |_, v| !v.nil? }) if other.respond_to?(:to_h)
|
38
51
|
|
39
52
|
super
|
40
53
|
end
|
@@ -47,7 +60,7 @@ module LazyGraph
|
|
47
60
|
|
48
61
|
def get_first_of(*props)
|
49
62
|
key = props.find do |prop|
|
50
|
-
!
|
63
|
+
!original_get(prop).is_a?(MissingValue)
|
51
64
|
end
|
52
65
|
key ? self[key] : MissingValue::BLANK
|
53
66
|
end
|
@@ -22,7 +22,7 @@ module LazyGraph
|
|
22
22
|
path_next = path.next
|
23
23
|
|
24
24
|
if (path_segment = path.segment).is_a?(PathParser::PathGroup)
|
25
|
-
return path_segment.options.each_with_object(
|
25
|
+
return path_segment.options.each_with_object(SymbolHash.new) do |part, object|
|
26
26
|
resolve(part.merge(path_next), stack_memory, nil, preserve_keys: object)
|
27
27
|
end
|
28
28
|
end
|
@@ -31,7 +31,7 @@ module LazyGraph
|
|
31
31
|
node.fetch_and_resolve(path_next, input, key, stack_memory)
|
32
32
|
end
|
33
33
|
if @complex_pattern_properties_a.any?
|
34
|
-
input.
|
34
|
+
input.keys.each do |key|
|
35
35
|
node = !@properties[key] && @complex_pattern_properties_a.find do |(pattern, _value)|
|
36
36
|
pattern.match?(key)
|
37
37
|
end&.last
|
@@ -40,12 +40,12 @@ module LazyGraph
|
|
40
40
|
node.fetch_and_resolve(path_next, input, key, stack_memory)
|
41
41
|
end
|
42
42
|
end
|
43
|
-
input
|
43
|
+
cast(input)
|
44
44
|
elsif (prop = @properties[segment])
|
45
45
|
prop.fetch_and_resolve(path_next, input, segment, stack_memory, preserve_keys)
|
46
46
|
elsif (_, prop = @pattern_properties.find { |(key, _val)| key.match?(segment) })
|
47
47
|
prop.fetch_and_resolve(path_next, input, segment, stack_memory, preserve_keys)
|
48
|
-
elsif input
|
48
|
+
elsif input&.key?(segment)
|
49
49
|
prop = @properties[segment] = lazy_init_node!(input[segment], segment)
|
50
50
|
@properties_a = @properties.to_a
|
51
51
|
prop.fetch_and_resolve(path_next, input, segment, stack_memory, preserve_keys)
|
@@ -93,7 +93,11 @@ module LazyGraph
|
|
93
93
|
)
|
94
94
|
end
|
95
95
|
define_singleton_method(:cast, lambda { |val|
|
96
|
-
val.is_a?(
|
96
|
+
if val.is_a?(MissingValue)
|
97
|
+
val
|
98
|
+
else
|
99
|
+
val.is_a?(@property_class) ? val : @property_class.new(val.to_h)
|
100
|
+
end
|
97
101
|
})
|
98
102
|
end
|
99
103
|
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
module LazyGraph
|
2
2
|
class ObjectNode < Node
|
3
3
|
class SymbolHash < ::Hash
|
4
|
-
def initialize(input_hash)
|
4
|
+
def initialize(input_hash = {})
|
5
5
|
super
|
6
|
-
merge!(input_hash)
|
6
|
+
merge!(input_hash.transform_keys(&:to_sym))
|
7
|
+
compare_by_identity
|
7
8
|
end
|
8
9
|
|
9
10
|
def []=(key, value)
|
@@ -21,6 +22,18 @@ module LazyGraph
|
|
21
22
|
else super(key.to_s.to_sym)
|
22
23
|
end
|
23
24
|
end
|
25
|
+
|
26
|
+
def method_missing(name, *args, &block)
|
27
|
+
if key?(name)
|
28
|
+
self[name]
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def respond_to_missing?(name, include_private = false)
|
35
|
+
key?(name) || super
|
36
|
+
end
|
24
37
|
end
|
25
38
|
end
|
26
39
|
end
|
data/lib/lazy_graph/node.rb
CHANGED
@@ -61,14 +61,18 @@ module LazyGraph
|
|
61
61
|
@depth = parent ? parent.depth + 1 : 0
|
62
62
|
@root = parent ? parent.root : self
|
63
63
|
@rule = node[:rule]
|
64
|
+
@rule_location = node[:rule_location]
|
64
65
|
@type = node[:type]
|
66
|
+
@validate_presence = node[:validate_presence]
|
65
67
|
@helpers = helpers
|
66
|
-
@invisible = debug ? false : node[:invisible]
|
68
|
+
@invisible = debug.eql?(true) ? false : node[:invisible]
|
67
69
|
@visited = {}.compare_by_identity
|
68
70
|
@namespace = namespace
|
69
71
|
|
70
72
|
instance_variable_set("@is_#{@type}", true)
|
71
73
|
define_singleton_method(:cast, build_caster)
|
74
|
+
define_singleton_method(:trace!, proc { |*| }) unless @debug
|
75
|
+
|
72
76
|
define_missing_value_proc!
|
73
77
|
|
74
78
|
@has_default = node.key?(:default)
|
@@ -96,7 +100,7 @@ module LazyGraph
|
|
96
100
|
)
|
97
101
|
end
|
98
102
|
|
99
|
-
def fetch_and_resolve(path, input, segment, stack_memory, preserve_keys = nil
|
103
|
+
def fetch_and_resolve(path, input, segment, stack_memory, preserve_keys = nil)
|
100
104
|
item = fetch_item(input, segment, stack_memory)
|
101
105
|
unless @simple || item.is_a?(MissingValue)
|
102
106
|
item = resolve(
|
@@ -104,6 +108,9 @@ module LazyGraph
|
|
104
108
|
stack_memory.push(item, segment)
|
105
109
|
)
|
106
110
|
end
|
111
|
+
|
112
|
+
item = cast(item) if @simple
|
113
|
+
|
107
114
|
preserve_keys ? preserve_keys[segment] = item : item
|
108
115
|
end
|
109
116
|
|
@@ -111,7 +118,13 @@ module LazyGraph
|
|
111
118
|
if @is_decimal
|
112
119
|
->(value) { value.is_a?(BigDecimal) ? value : value.to_d }
|
113
120
|
elsif @is_date
|
114
|
-
|
121
|
+
lambda { |value|
|
122
|
+
if value.is_a?(String)
|
123
|
+
Date.parse(value)
|
124
|
+
else
|
125
|
+
value.is_a?(Symbol) ? Date.parse(value.to_s) : value
|
126
|
+
end
|
127
|
+
}
|
115
128
|
elsif @is_boolean
|
116
129
|
lambda do |value|
|
117
130
|
if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
@@ -131,6 +144,8 @@ module LazyGraph
|
|
131
144
|
value
|
132
145
|
end
|
133
146
|
end
|
147
|
+
elsif @is_string
|
148
|
+
lambda(&:to_s)
|
134
149
|
else
|
135
150
|
->(value) { value }
|
136
151
|
end
|
@@ -206,7 +221,8 @@ module LazyGraph
|
|
206
221
|
def resolve_relative_input(stack_memory, path)
|
207
222
|
input_frame_pointer = path.absolute? ? stack_memory.root : stack_memory.ptr_at(depth - 1)
|
208
223
|
input_frame_pointer.recursion_depth += 1
|
209
|
-
|
224
|
+
|
225
|
+
return cast(input_frame_pointer.frame[path.first_path_segment.part]) if @simple
|
210
226
|
|
211
227
|
fetch_and_resolve(
|
212
228
|
path.absolute? ? path.next.next : path.next, input_frame_pointer.frame, path.first_path_segment.part, input_frame_pointer
|
@@ -235,11 +251,8 @@ module LazyGraph
|
|
235
251
|
if stack.recursion_depth >= 8
|
236
252
|
input_id = key.object_id >> 2 ^ input.object_id << 28
|
237
253
|
if @resolution_stack.key?(input_id)
|
238
|
-
|
239
|
-
stack
|
240
|
-
output: :"#{stack}.#{key}",
|
241
|
-
exception: 'Infinite Recursion Detected during dependency resolution'
|
242
|
-
)
|
254
|
+
trace!(stack, exception: 'Infinite Recursion Detected during dependency resolution') do
|
255
|
+
{ output: :"#{stack}.#{key}" }
|
243
256
|
end
|
244
257
|
return MissingValue { "Infinite Recursion in #{stack} => #{key}" }
|
245
258
|
end
|
@@ -260,7 +273,10 @@ module LazyGraph
|
|
260
273
|
break missing_value = MissingValue { key } unless resolver
|
261
274
|
|
262
275
|
part = resolver.resolve_relative_input(stack, parts[index].options.first)
|
263
|
-
|
276
|
+
if part.is_a?(MissingValue)
|
277
|
+
raise_presence_validation_error!(stack, key, parts[index].options.first) if @validate_presence
|
278
|
+
break missing_value = part
|
279
|
+
end
|
264
280
|
|
265
281
|
part_sym = part.to_s.to_sym
|
266
282
|
parts_identity ^= part_sym.object_id << index
|
@@ -271,15 +287,12 @@ module LazyGraph
|
|
271
287
|
|
272
288
|
result = missing_value || cast(resolver.resolve_relative_input(stack, path))
|
273
289
|
|
274
|
-
if
|
275
|
-
stack
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
calc: @src
|
280
|
-
)
|
290
|
+
if result.nil? || result.is_a?(MissingValue)
|
291
|
+
raise_presence_validation_error!(stack, key, path) if @validate_presence
|
292
|
+
input[key] = MissingValue { key }
|
293
|
+
else
|
294
|
+
input[key] = result
|
281
295
|
end
|
282
|
-
input[key] = result.nil? ? MissingValue { key } : result
|
283
296
|
end
|
284
297
|
|
285
298
|
def derive_item!(input, key, stack)
|
@@ -292,7 +305,10 @@ module LazyGraph
|
|
292
305
|
break missing_value = MissingValue { key } unless resolver
|
293
306
|
|
294
307
|
part = resolver.resolve_relative_input(stack, parts[index].options.first)
|
295
|
-
|
308
|
+
if part.is_a?(MissingValue)
|
309
|
+
raise_presence_validation_error!(stack, key, parts[index].options.first) if @validate_presence
|
310
|
+
break missing_value = part
|
311
|
+
end
|
296
312
|
|
297
313
|
part_sym = part.to_s.to_sym
|
298
314
|
parts_identity ^= part_sym.object_id << (index * 8)
|
@@ -300,8 +316,28 @@ module LazyGraph
|
|
300
316
|
end
|
301
317
|
path = @path_cache[parts_identity] ||= PathParser::Path.new(parts: parts) unless missing_value
|
302
318
|
end
|
303
|
-
result =
|
304
|
-
|
319
|
+
result = begin
|
320
|
+
missing_value || resolver.resolve_relative_input(stack, path)
|
321
|
+
rescue AbortError, ValidationError => e
|
322
|
+
raise e
|
323
|
+
rescue StandardError => e
|
324
|
+
ex = e
|
325
|
+
LazyGraph.logger.error("Error in #{self.path}")
|
326
|
+
LazyGraph.logger.error(e)
|
327
|
+
LazyGraph.logger.error(e.backtrace.take_while do |line|
|
328
|
+
!line.include?('lazy_graph/node.rb')
|
329
|
+
end.join("\n"))
|
330
|
+
|
331
|
+
MissingValue { "#{key} raised exception: #{e.message}" }
|
332
|
+
end
|
333
|
+
|
334
|
+
if result.nil? || result.is_a?(MissingValue)
|
335
|
+
raise_presence_validation_error!(stack, key, path) if @validate_presence
|
336
|
+
|
337
|
+
@node_context[i] = nil
|
338
|
+
else
|
339
|
+
@node_context[i] = result
|
340
|
+
end
|
305
341
|
end
|
306
342
|
|
307
343
|
@node_context[:itself] = input
|
@@ -316,12 +352,21 @@ module LazyGraph
|
|
316
352
|
if conditions_passed
|
317
353
|
output = begin
|
318
354
|
cast(@fixed_result || @node_context.process!)
|
319
|
-
rescue
|
355
|
+
rescue AbortError, ValidationError => e
|
320
356
|
raise e
|
321
357
|
rescue StandardError => e
|
322
358
|
ex = e
|
323
359
|
LazyGraph.logger.error(e)
|
324
|
-
LazyGraph.logger.error(e.backtrace.
|
360
|
+
LazyGraph.logger.error(e.backtrace.take_while do |line|
|
361
|
+
!line.include?('lazy_graph/node.rb')
|
362
|
+
end.join("\n"))
|
363
|
+
|
364
|
+
if ENV['LAZYGRAPH_OPEN_ON_ERROR'] && !@revealed_src
|
365
|
+
require 'shellwords'
|
366
|
+
@revealed_src = true
|
367
|
+
`sh -c \"$EDITOR '#{Shellwords.escape(e.backtrace.first[/.*:/][...-1])}'\" `
|
368
|
+
end
|
369
|
+
|
325
370
|
MissingValue { "#{key} raised exception: #{e.message}" }
|
326
371
|
end
|
327
372
|
|
@@ -330,24 +375,37 @@ module LazyGraph
|
|
330
375
|
MissingValue { key }
|
331
376
|
end
|
332
377
|
|
333
|
-
if
|
334
|
-
stack
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
end }
|
344
|
-
else
|
345
|
-
{}
|
346
|
-
end
|
347
|
-
)
|
348
|
-
)
|
378
|
+
if conditions_passed
|
379
|
+
trace!(stack, exception: ex) do
|
380
|
+
{
|
381
|
+
output: :"#{stack}.#{key}",
|
382
|
+
result: HashUtils.deep_dup(result),
|
383
|
+
inputs: @node_context.to_h.except(:itself, :stack_ptr).transform_keys { |k| @input_mapper&.[](k) || k },
|
384
|
+
calc: @src,
|
385
|
+
**(@conditions ? { conditions: @conditions } : {})
|
386
|
+
}
|
387
|
+
end
|
349
388
|
end
|
389
|
+
|
350
390
|
result
|
351
391
|
end
|
392
|
+
|
393
|
+
def trace!(stack, exception: nil)
|
394
|
+
return if @debug == 'exceptions' && !exception
|
395
|
+
|
396
|
+
trace_opts = {
|
397
|
+
**yield,
|
398
|
+
**(exception ? { exception: exception } : {})
|
399
|
+
}
|
400
|
+
|
401
|
+
return if @debug.is_a?(Regexp) && !(@debug =~ trace_opts[:output])
|
402
|
+
|
403
|
+
stack.log_debug(**trace_opts)
|
404
|
+
end
|
405
|
+
|
406
|
+
def raise_presence_validation_error!(stack, key, path)
|
407
|
+
raise ValidationError,
|
408
|
+
"Missing required value for #{stack}.#{key} at #{path.to_path_str}"
|
409
|
+
end
|
352
410
|
end
|
353
411
|
end
|