lazy_graph 0.1.6 → 0.2.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/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
|