lazy_graph 0.1.3 → 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 +9 -9
- data/examples/shopping_cart_totals.rb +56 -0
- data/lib/lazy_graph/builder/dsl.rb +67 -30
- data/lib/lazy_graph/builder.rb +52 -25
- data/lib/lazy_graph/builder_group.rb +31 -18
- data/lib/lazy_graph/cli.rb +47 -0
- data/lib/lazy_graph/context.rb +37 -12
- data/lib/lazy_graph/environment.rb +6 -0
- data/lib/lazy_graph/graph.rb +28 -12
- data/lib/lazy_graph/hash_utils.rb +10 -5
- data/lib/lazy_graph/logger.rb +87 -0
- data/lib/lazy_graph/missing_value.rb +9 -2
- data/lib/lazy_graph/node/array_node.rb +14 -18
- data/lib/lazy_graph/node/derived_rules.rb +72 -17
- data/lib/lazy_graph/node/node_properties.rb +34 -9
- data/lib/lazy_graph/node/object_node.rb +41 -56
- data/lib/lazy_graph/node/symbol_hash.rb +15 -2
- data/lib/lazy_graph/node.rb +183 -94
- data/lib/lazy_graph/path_parser/path.rb +15 -7
- data/lib/lazy_graph/path_parser/path_group.rb +6 -0
- data/lib/lazy_graph/path_parser/path_part.rb +8 -0
- data/lib/lazy_graph/path_parser.rb +1 -1
- data/lib/lazy_graph/rack_app.rb +138 -0
- data/lib/lazy_graph/rack_server.rb +199 -0
- data/lib/lazy_graph/stack_pointer.rb +22 -14
- 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 -91
@@ -39,6 +39,8 @@ module LazyGraph
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def set_pattern_property(pattern, value)
|
42
|
+
raise 'Trying to set a property without a name' unless pattern.is_a?(String) || pattern.is_a?(Symbol)
|
43
|
+
|
42
44
|
pattern = pattern.to_sym
|
43
45
|
properties = schema[:patternProperties] ||= {}
|
44
46
|
properties[pattern] = \
|
@@ -51,6 +53,8 @@ module LazyGraph
|
|
51
53
|
end
|
52
54
|
|
53
55
|
def set_property(key, value)
|
56
|
+
raise 'Trying to set a property without a name' unless key.is_a?(String) || key.is_a?(Symbol)
|
57
|
+
|
54
58
|
key = key.to_sym
|
55
59
|
properties = schema[:properties] ||= {}
|
56
60
|
properties[key] = \
|
@@ -63,8 +67,8 @@ module LazyGraph
|
|
63
67
|
end
|
64
68
|
|
65
69
|
def object_conditional(
|
66
|
-
name = nil, required: false, pattern_property: false, rule: nil,
|
67
|
-
default: nil, description: nil, **opts, &blk
|
70
|
+
name = nil, required: false, pattern_property: false, rule: nil, copy: nil,
|
71
|
+
default: nil, description: nil, extend: nil, conditions: nil, **opts, &blk
|
68
72
|
)
|
69
73
|
new_object = {
|
70
74
|
type: :object,
|
@@ -72,20 +76,24 @@ module LazyGraph
|
|
72
76
|
additionalProperties: false,
|
73
77
|
**(!default.nil? ? { default: default } : {}),
|
74
78
|
**(description ? { description: description } : {}),
|
79
|
+
validate_presence: required,
|
75
80
|
**opts
|
76
81
|
}
|
77
82
|
@prev_match_cases = @match_cases
|
78
83
|
@match_cases = []
|
79
|
-
|
80
|
-
|
84
|
+
yields(new_object, &lambda {
|
85
|
+
blk&.call
|
86
|
+
extend&.call
|
87
|
+
})
|
81
88
|
|
82
89
|
object_names = @match_cases.map do |match_case|
|
83
90
|
rule = rule_from_when(match_case[:when_clause])
|
84
|
-
set_property(match_case[:name],
|
91
|
+
set_property(match_case[:name],
|
92
|
+
{ type: :object, rule: rule, rule_location: rule_location, **match_case[:schema] })
|
85
93
|
match_case[:name]
|
86
94
|
end
|
87
95
|
|
88
|
-
new_object[:rule] = rule_from_first_of(object_names)
|
96
|
+
new_object[:rule] = rule_from_first_of(object_names, conditions)
|
89
97
|
@match_cases = @prev_match_cases
|
90
98
|
required(name) if required && default.nil? && rule.nil?
|
91
99
|
pattern_property ? set_pattern_property(name, new_object) : set_property(name, new_object)
|
@@ -96,23 +104,32 @@ module LazyGraph
|
|
96
104
|
yields(@match_cases.last[:schema], &blk)
|
97
105
|
end
|
98
106
|
|
107
|
+
def rule_location
|
108
|
+
@dir ||= File.expand_path(File.dirname(__FILE__), '../../../../')
|
109
|
+
caller.find { |c| !c.include?(@dir) }.split(':').first(2)
|
110
|
+
end
|
111
|
+
|
99
112
|
def object(
|
100
|
-
name = nil, required: false, pattern_property: false, rule: nil,
|
101
|
-
default: nil, description: nil, **opts, &blk
|
113
|
+
name = nil, required: false, pattern_property: false, rule: nil, copy: nil,
|
114
|
+
default: nil, description: nil, extend: nil, **opts, &blk
|
102
115
|
)
|
103
116
|
rule ||= rule_from_when(opts.delete(:when)) if opts[:when]
|
104
117
|
rule ||= rule_from_first_of(opts.delete(:first_of)) if opts[:first_of]
|
118
|
+
rule ||= rule_from_copy(copy)
|
105
119
|
new_object = {
|
106
120
|
type: :object,
|
107
121
|
properties: {},
|
108
122
|
additionalProperties: false,
|
109
|
-
|
110
123
|
**(!default.nil? ? { default: default } : {}),
|
111
124
|
**(description ? { description: description } : {}),
|
112
|
-
**(rule ? { rule: rule } : {}),
|
125
|
+
**(rule ? { rule: rule, rule_location: rule_location } : {}),
|
126
|
+
validate_presence: required,
|
113
127
|
**opts
|
114
128
|
}
|
115
|
-
yields(new_object, &
|
129
|
+
yields(new_object, &lambda {
|
130
|
+
blk&.call
|
131
|
+
extend&.call
|
132
|
+
})
|
116
133
|
required(name) if required && default.nil? && rule.nil?
|
117
134
|
pattern_property ? set_pattern_property(name, new_object) : set_property(name, new_object)
|
118
135
|
end
|
@@ -120,14 +137,15 @@ module LazyGraph
|
|
120
137
|
def primitive(
|
121
138
|
name = nil, type, required: false, pattern_property: false,
|
122
139
|
default: nil, description: nil, enum: nil,
|
123
|
-
rule: nil, **additional_options, &blk
|
140
|
+
rule: nil, copy: nil, **additional_options, &blk
|
124
141
|
)
|
142
|
+
rule ||= rule_from_copy(copy)
|
125
143
|
new_primitive = {
|
126
144
|
type: type,
|
127
145
|
**(enum ? { enum: enum } : {}),
|
128
146
|
**(!default.nil? ? { default: default } : {}),
|
129
147
|
**(description ? { description: description } : {}),
|
130
|
-
**(rule ? { rule: rule } : {}),
|
148
|
+
**(rule ? { rule: rule, rule_location: rule_location } : {}),
|
131
149
|
**additional_options
|
132
150
|
}
|
133
151
|
yields(new_primitive, &blk)
|
@@ -135,8 +153,9 @@ module LazyGraph
|
|
135
153
|
pattern_property ? set_pattern_property(name, new_primitive) : set_property(name, new_primitive)
|
136
154
|
end
|
137
155
|
|
138
|
-
def decimal(name = nil, required: false, pattern_property: false, default: nil, description: nil, rule: nil,
|
156
|
+
def decimal(name = nil, required: false, pattern_property: false, default: nil, description: nil, rule: nil, copy: nil,
|
139
157
|
**opts, &blk)
|
158
|
+
rule ||= rule_from_copy(copy)
|
140
159
|
# Define the decimal schema supporting multiple formats
|
141
160
|
new_decimal = {
|
142
161
|
anyOf: [
|
@@ -157,7 +176,8 @@ module LazyGraph
|
|
157
176
|
type: :decimal,
|
158
177
|
**(!default.nil? ? { default: default } : {}),
|
159
178
|
**(description ? { description: description } : {}),
|
160
|
-
**(rule ? { rule: rule } : {}),
|
179
|
+
**(rule ? { rule: rule, rule_location: rule_location } : {}),
|
180
|
+
validate_presence: required,
|
161
181
|
**opts
|
162
182
|
}
|
163
183
|
yields(new_decimal, &blk)
|
@@ -165,8 +185,9 @@ module LazyGraph
|
|
165
185
|
pattern_property ? set_pattern_property(name, new_decimal) : set_property(name, new_decimal)
|
166
186
|
end
|
167
187
|
|
168
|
-
def timestamp(name = nil, required: false, pattern_property: false, default: nil, description: nil, rule: nil,
|
188
|
+
def timestamp(name = nil, required: false, pattern_property: false, default: nil, description: nil, rule: nil, copy: nil,
|
169
189
|
**opts, &blk)
|
190
|
+
rule ||= rule_from_copy(copy)
|
170
191
|
new_timestamp = {
|
171
192
|
anyOf: [
|
172
193
|
{
|
@@ -184,7 +205,8 @@ module LazyGraph
|
|
184
205
|
type: :timestamp, # Custom extended type
|
185
206
|
**(!default.nil? ? { default: default } : {}),
|
186
207
|
**(description ? { description: description } : {}),
|
187
|
-
**(rule ? { rule: rule } : {}),
|
208
|
+
**(rule ? { rule: rule, rule_location: rule_location } : {}),
|
209
|
+
validate_presence: required,
|
188
210
|
**opts
|
189
211
|
}
|
190
212
|
yields(new_timestamp, &blk)
|
@@ -192,15 +214,17 @@ module LazyGraph
|
|
192
214
|
pattern_property ? set_pattern_property(name, new_timestamp) : set_property(name, new_timestamp)
|
193
215
|
end
|
194
216
|
|
195
|
-
def time(name = nil, required: false, pattern_property: false, default: nil, description: nil, rule: nil,
|
217
|
+
def time(name = nil, required: false, pattern_property: false, default: nil, description: nil, rule: nil, copy: nil,
|
196
218
|
**opts, &blk)
|
219
|
+
rule ||= rule_from_copy(copy)
|
197
220
|
new_time = {
|
198
221
|
type: :time, # Custom extended type
|
199
222
|
# Matches HH:mm[:ss[.SSS]]
|
200
223
|
pattern: '^\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,3})?)?$',
|
201
224
|
**(!default.nil? ? { default: default } : {}),
|
202
225
|
**(description ? { description: description } : {}),
|
203
|
-
**(rule ? { rule: rule } : {}),
|
226
|
+
**(rule ? { rule: rule, rule_location: rule_location } : {}),
|
227
|
+
validate_presence: required,
|
204
228
|
**opts
|
205
229
|
}
|
206
230
|
yields(new_time, &blk)
|
@@ -208,8 +232,9 @@ module LazyGraph
|
|
208
232
|
pattern_property ? set_pattern_property(name, new_time) : set_property(name, new_time)
|
209
233
|
end
|
210
234
|
|
211
|
-
def date(name = nil, required: false, pattern_property: false, default: nil, description: nil, rule: nil,
|
235
|
+
def date(name = nil, required: false, pattern_property: false, default: nil, description: nil, rule: nil, copy: nil,
|
212
236
|
**opts, &blk)
|
237
|
+
rule ||= rule_from_copy(copy)
|
213
238
|
new_date = {
|
214
239
|
anyOf: [
|
215
240
|
{
|
@@ -221,7 +246,8 @@ module LazyGraph
|
|
221
246
|
type: :date, # Custom extended type
|
222
247
|
**(!default.nil? ? { default: default } : {}),
|
223
248
|
**(description ? { description: description } : {}),
|
224
|
-
**(rule ? { rule: rule } : {}),
|
249
|
+
**(rule ? { rule: rule, rule_location: rule_location } : {}),
|
250
|
+
validate_presence: required,
|
225
251
|
**opts
|
226
252
|
}
|
227
253
|
yields(new_date, &blk)
|
@@ -232,10 +258,11 @@ module LazyGraph
|
|
232
258
|
%i[boolean string const integer number null].each do |type|
|
233
259
|
define_method(type) do |
|
234
260
|
name = nil, required: false, pattern_property: false,
|
235
|
-
default: nil, description: nil, enum: nil, rule: nil,
|
261
|
+
default: nil, description: nil, enum: nil, rule: nil, copy: nil,
|
262
|
+
**additional_options, &blk|
|
236
263
|
primitive(
|
237
264
|
name, type, required: required, pattern_property: pattern_property, enum: enum,
|
238
|
-
default: default, rule: rule, description: description,
|
265
|
+
default: default, rule: rule, rule_location: rule_location, description: description,
|
239
266
|
**additional_options, &blk
|
240
267
|
)
|
241
268
|
end
|
@@ -253,17 +280,18 @@ module LazyGraph
|
|
253
280
|
schema[:anyOf] = (schema[:any_of] || []).concat(any_of).uniq
|
254
281
|
end
|
255
282
|
|
256
|
-
def array(name = nil, required: false, pattern_property: false, default: nil, description: nil, rule: nil,
|
257
|
-
type: :object, **opts, &
|
283
|
+
def array(name = nil, required: false, pattern_property: false, default: nil, description: nil, rule: nil, copy: nil,
|
284
|
+
type: :object, extend: nil, **opts, &block)
|
285
|
+
rule ||= rule_from_copy(copy)
|
258
286
|
new_array = {
|
259
287
|
type: :array,
|
260
288
|
**(!default.nil? ? { default: default } : {}),
|
261
289
|
**(description ? { description: description } : {}),
|
262
|
-
**(rule ? { rule: rule } : {}),
|
290
|
+
**(rule ? { rule: rule, rule_location: rule_location } : {}),
|
263
291
|
**opts,
|
264
292
|
items: { properties: {} }.tap do |items|
|
265
293
|
yields(items) do
|
266
|
-
send(type, :items, &
|
294
|
+
send(type, :items, extend: extend, &block)
|
267
295
|
end
|
268
296
|
end[:properties][:items]
|
269
297
|
}
|
@@ -276,6 +304,7 @@ module LazyGraph
|
|
276
304
|
end
|
277
305
|
|
278
306
|
def yields(other)
|
307
|
+
raise ArgumentError, 'Builder DSL used outside of rules module' unless schema
|
279
308
|
return unless block_given?
|
280
309
|
|
281
310
|
prev_schema = schema
|
@@ -284,6 +313,12 @@ module LazyGraph
|
|
284
313
|
self.schema = prev_schema
|
285
314
|
end
|
286
315
|
|
316
|
+
def rule_from_copy(copy)
|
317
|
+
return unless copy
|
318
|
+
|
319
|
+
"${#{copy}}"
|
320
|
+
end
|
321
|
+
|
287
322
|
def rule_from_when(when_clause)
|
288
323
|
inputs = when_clause.keys
|
289
324
|
conditions = when_clause
|
@@ -296,17 +331,19 @@ module LazyGraph
|
|
296
331
|
}
|
297
332
|
end
|
298
333
|
|
299
|
-
def rule_from_first_of(prop_list)
|
334
|
+
def rule_from_first_of(prop_list, conditions = nil)
|
335
|
+
prop_list += conditions.keys if conditions
|
300
336
|
{
|
301
337
|
inputs: prop_list,
|
302
|
-
calc: "itself.get_first_of(:#{prop_list.join(', :')})"
|
338
|
+
calc: "itself.get_first_of(:#{prop_list.join(', :')})",
|
339
|
+
**(conditions ? { conditions: conditions } : {})
|
303
340
|
}
|
304
341
|
end
|
305
342
|
|
306
343
|
def depends_on(*dependencies)
|
307
344
|
@resolved_dependencies ||= Hash.new do |h, k|
|
308
|
-
send(k) # Load dependency once
|
309
345
|
h[k] = true
|
346
|
+
send(k) # Load dependency once
|
310
347
|
end
|
311
348
|
dependencies.each(&@resolved_dependencies.method(:[]))
|
312
349
|
end
|
data/lib/lazy_graph/builder.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# Subclass LazyGraph::Builder to create new builder classes
|
3
4
|
# which can be used to easily build a rule-set to be used as a LazyGraph.
|
4
5
|
#
|
@@ -6,15 +7,14 @@ require_relative 'builder/dsl'
|
|
6
7
|
|
7
8
|
module LazyGraph
|
8
9
|
class Builder
|
9
|
-
|
10
10
|
# Cache up to a fixed number of graphs, context and queries
|
11
11
|
BUILD_CACHE_CONFIG = {
|
12
12
|
# Store up to 1000 graphs
|
13
|
-
graph: {size: 1000, cache: {}},
|
13
|
+
graph: { size: ENV.fetch('LAZY_GRAPH_GRAPH_CACHE_MAX_ENTRIES', 1000).to_i, cache: {} },
|
14
14
|
# Store up to 5000 configs
|
15
|
-
context: {size: 5000, cache: {}},
|
15
|
+
context: { size: ENV.fetch('LAZY_GRAPH_CONTEXT_CACHE_MAX_ENTRIES', 5000).to_i, cache: {} },
|
16
16
|
# Store up to 5000 queries
|
17
|
-
query: {size: 5000, cache: {}}
|
17
|
+
query: { size: ENV.fetch('LAZY_GRAPH_QUERY_CACHE_MAX_ENTRIES', 5000).to_i, cache: {} }
|
18
18
|
}.compare_by_identity.freeze
|
19
19
|
|
20
20
|
include DSL
|
@@ -23,15 +23,25 @@ module LazyGraph
|
|
23
23
|
attr_accessor :schema
|
24
24
|
|
25
25
|
def initialize(schema: { type: 'object', properties: {} }) = @schema = schema
|
26
|
-
def
|
27
|
-
|
28
|
-
def eval!(context, *value, debug: false,
|
26
|
+
def context(value, debug: false, validate: true) = build!(debug: debug, validate: validate).context(value)
|
27
|
+
|
28
|
+
def eval!(context, *value, debug: false,
|
29
|
+
validate: true) = context(context, validate: validate, debug: debug).get(*value)
|
30
|
+
alias feed context
|
31
|
+
|
32
|
+
def build!(debug: false, validate: true) = @schema.to_lazy_graph(
|
33
|
+
debug: debug,
|
34
|
+
validate: validate,
|
35
|
+
helpers: self.class.helper_modules,
|
36
|
+
namespace: self.class
|
37
|
+
)
|
29
38
|
|
30
39
|
def self.rules_module(name, schema = { type: 'object', properties: {} }, &blk)
|
31
40
|
rules_modules[:properties][name.to_sym] = { type: :object, properties: schema }
|
32
41
|
module_body_func_name = :"_#{name}"
|
33
42
|
define_method(module_body_func_name, &blk)
|
34
43
|
define_method(name) do |**args, &inner_blk|
|
44
|
+
@path = @path.nil? ? "#{name}" : "#{@path}+#{name}" unless @path.to_s.include?(name.to_s)
|
35
45
|
send(module_body_func_name, **args, &inner_blk)
|
36
46
|
self
|
37
47
|
end
|
@@ -52,6 +62,8 @@ module LazyGraph
|
|
52
62
|
attr_reader :helper_modules
|
53
63
|
end
|
54
64
|
|
65
|
+
def self.register_helper_modules(*mods) = mods.each(&method(:register_helper_module))
|
66
|
+
|
55
67
|
def self.register_helper_module(mod)
|
56
68
|
(@helper_modules ||= []) << mod
|
57
69
|
end
|
@@ -64,6 +76,12 @@ module LazyGraph
|
|
64
76
|
@helper_modules = nil
|
65
77
|
end
|
66
78
|
|
79
|
+
def self.clear_caches!
|
80
|
+
clear_rules_modules!
|
81
|
+
clear_helper_modules!
|
82
|
+
BUILD_CACHE_CONFIG.each_value { |v| v[:cache].clear }
|
83
|
+
end
|
84
|
+
|
67
85
|
def self.rules_modules
|
68
86
|
@rules_modules ||= {
|
69
87
|
type: :object,
|
@@ -87,8 +105,8 @@ module LazyGraph
|
|
87
105
|
}
|
88
106
|
end
|
89
107
|
|
90
|
-
def self.eval!(modules:, context:, query:, debug: false, validate:
|
91
|
-
|
108
|
+
def self.eval!(modules:, context:, query:, debug: false, validate: true)
|
109
|
+
graph = cache_as(:graph, [modules, debug, validate]) do
|
92
110
|
invalid_modules = modules.reject { |k, _v| rules_modules[:properties].key?(k.to_sym) }
|
93
111
|
return format_error_response('Invalid Modules', invalid_modules.keys.join(',')) unless invalid_modules.empty?
|
94
112
|
|
@@ -99,21 +117,23 @@ module LazyGraph
|
|
99
117
|
builder = build_modules(modules)
|
100
118
|
return builder if builder.is_a?(Hash)
|
101
119
|
|
102
|
-
builder
|
120
|
+
builder.build!(debug: debug, validate: validate)
|
103
121
|
end
|
104
122
|
|
105
|
-
context_result = cache_as(:context, [
|
106
|
-
|
123
|
+
context_result = cache_as(:context, [graph, context]) do
|
124
|
+
build_context(graph, context)
|
107
125
|
end
|
108
126
|
|
109
|
-
return context_result if context_result.is_a?(Hash) && context_result[:type]
|
110
|
-
|
111
|
-
query_result = cache_as(:query, [context_result, query]){ context_result.query(*(query || '')) }
|
127
|
+
return context_result if context_result.is_a?(Hash) && context_result[:type].equal?(:error)
|
112
128
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
129
|
+
cache_as(:query, [context_result, query]) do
|
130
|
+
HashUtils.strip_missing(
|
131
|
+
{
|
132
|
+
type: :success,
|
133
|
+
result: context_result.resolve(*(query || ''))
|
134
|
+
}
|
135
|
+
)
|
136
|
+
end
|
117
137
|
rescue SystemStackError => e
|
118
138
|
LazyGraph.logger.error(e.message)
|
119
139
|
LazyGraph.logger.error(e.backtrace.join("\n"))
|
@@ -132,9 +152,12 @@ module LazyGraph
|
|
132
152
|
cache.delete(cache.keys.first) while cache.size > max_size
|
133
153
|
end
|
134
154
|
|
155
|
+
def to_str
|
156
|
+
"LazyGraph(modules=#{@path})"
|
157
|
+
end
|
158
|
+
|
135
159
|
private_class_method def self.method_missing(method_name, *args, &block) = new.send(method_name, *args, &block)
|
136
160
|
private_class_method def self.respond_to_missing?(_, _ = false) = true
|
137
|
-
|
138
161
|
private_class_method def self.validate_modules(input_options)
|
139
162
|
JSON::Validator.validate!(rules_modules, input_options)
|
140
163
|
''
|
@@ -156,15 +179,19 @@ module LazyGraph
|
|
156
179
|
acc.send(k, **v.to_h.transform_keys(&:to_sym))
|
157
180
|
end
|
158
181
|
rescue ArgumentError => e
|
182
|
+
LazyGraph.logger.error(e.message)
|
183
|
+
LazyGraph.logger.error(e.backtrace.join("\n"))
|
159
184
|
format_error_response('Invalid Module Argument', e.message)
|
160
|
-
LazyGraph.logger.error(e.backtrace)
|
161
185
|
end
|
162
186
|
|
163
|
-
private_class_method def self.
|
164
|
-
|
165
|
-
rescue
|
187
|
+
private_class_method def self.build_context(graph, context)
|
188
|
+
graph.context(context)
|
189
|
+
rescue StandardError => e
|
190
|
+
if graph.debug?
|
191
|
+
LazyGraph.logger.error(e.message)
|
192
|
+
LazyGraph.logger.error(e.backtrace.join("\n"))
|
193
|
+
end
|
166
194
|
format_error_response('Invalid Context Input', e.message)
|
167
|
-
LazyGraph.logger.error(e.backtrace)
|
168
195
|
end
|
169
196
|
end
|
170
197
|
end
|
@@ -2,8 +2,13 @@ module LazyGraph
|
|
2
2
|
class BuilderGroup
|
3
3
|
# A builder group is simply a named colleciton of builders (each a subclass of LazyGraph::Builder)
|
4
4
|
# That can be reloaded in bulk, and exposed via a simple HTTP server interface.
|
5
|
-
|
5
|
+
# If a script file defining a builder group is run directly, it will start a Puma server
|
6
|
+
# to host itself
|
7
|
+
def self.bootstrap!(reload_paths: nil, exclude_paths: [], listen: Environment.development?, enable_server: true)
|
8
|
+
bootstrap_script_name = caller.drop_while { |r| r =~ /builder_group/ }[0][/[^:]+/]
|
9
|
+
reload_paths ||= File.join(File.dirname(bootstrap_script_name), '**/*.rb')
|
6
10
|
reload_paths = Array(reload_paths)
|
11
|
+
|
7
12
|
Module.new do
|
8
13
|
define_singleton_method(:included) do |base|
|
9
14
|
def base.each_builder(const = self, &blk)
|
@@ -18,40 +23,48 @@ module LazyGraph
|
|
18
23
|
end
|
19
24
|
end
|
20
25
|
|
21
|
-
def base.
|
22
|
-
|
26
|
+
def base.rack_app(**opts)
|
27
|
+
unless defined?(Rack)
|
28
|
+
raise AbortError,
|
29
|
+
'You must install rack first to be able to run a LazyGraph BuilderGroup as a server'
|
30
|
+
end
|
31
|
+
|
32
|
+
LazyGraph::RackApp.new(
|
23
33
|
routes: each_builder.map do |builder|
|
24
34
|
[
|
25
|
-
builder.to_s.downcase.gsub('::', '/').gsub(/^#{name.downcase}/, ''),
|
35
|
+
builder.to_s.downcase.gsub('::', '/').gsub(/^#{name.to_s.downcase}/, ''),
|
26
36
|
builder
|
27
37
|
]
|
28
|
-
end.to_h
|
38
|
+
end.to_h,
|
39
|
+
**opts
|
29
40
|
)
|
30
41
|
end
|
31
42
|
|
32
|
-
base.define_singleton_method(:reload_lazy_graphs!) do
|
33
|
-
each_builder
|
34
|
-
builder.clear_rules_modules!
|
35
|
-
builder.clear_helper_modules!
|
36
|
-
end
|
43
|
+
base.define_singleton_method(:reload_lazy_graphs!) do |clear_previous: false|
|
44
|
+
each_builder(&:clear_caches!) if clear_previous
|
37
45
|
|
38
|
-
reload_paths.flat_map { |p|
|
39
|
-
|
46
|
+
reload_paths.flat_map { |p|
|
47
|
+
Dir[p]
|
48
|
+
}.-([bootstrap_script_name]).sort_by { |file| file.count(File::SEPARATOR) }.each do |file|
|
49
|
+
load file unless exclude_paths.any? { |p| p =~ file }
|
40
50
|
rescue StandardError => e
|
41
51
|
LazyGraph.logger.error("Failed to load #{file}: #{e.message}")
|
42
52
|
end
|
43
53
|
end
|
44
54
|
|
45
|
-
base.reload_lazy_graphs!
|
55
|
+
base.reload_lazy_graphs! if reload_paths.any?
|
46
56
|
|
47
|
-
|
57
|
+
if listen && defined?(Listen)
|
58
|
+
Listen.to(*reload_paths.map { |p| p.gsub(%r{(?:/\*\*)*/\*\.rb}, '') }) do
|
59
|
+
base.reload_lazy_graphs!(clear_previous: true)
|
60
|
+
end.start
|
61
|
+
end
|
48
62
|
|
49
|
-
|
50
|
-
Listen.to(*reload_paths.map { |p| p.gsub(%r{(?:/\*\*)*/\*\.rb}, '') }) do
|
51
|
-
base.reload_lazy_graphs!
|
52
|
-
end.start
|
63
|
+
CLI.invoke!(base.rack_app) if enable_server && bootstrap_script_name == $0
|
53
64
|
end
|
54
65
|
end
|
55
66
|
end
|
56
67
|
end
|
68
|
+
|
69
|
+
define_singleton_method(:bootstrap_app!, &BuilderGroup.method(:bootstrap!))
|
57
70
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LazyGraph
|
4
|
+
class CLI
|
5
|
+
# A builder group rack-app can be exposed via a simple rack based HTTP server
|
6
|
+
# with routes corresponding to each builder class deeply nested
|
7
|
+
# withhin the builder group module.
|
8
|
+
def self.invoke!(rack_app)
|
9
|
+
require 'etc'
|
10
|
+
require 'optparse'
|
11
|
+
require 'rack'
|
12
|
+
|
13
|
+
options = {
|
14
|
+
port: 9292,
|
15
|
+
workers: (Environment.development? ? 1 : Etc.nprocessors)
|
16
|
+
}
|
17
|
+
|
18
|
+
OptionParser.new do |opts|
|
19
|
+
opts.banner = "Usage: #{$0} [options]"
|
20
|
+
|
21
|
+
opts.on('-p PORT', '--port PORT', Integer, "Set the port (default: #{options[:port]})") do |p|
|
22
|
+
options[:port] = p
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on('-w WORKERS', '--workers WORKERS', Integer,
|
26
|
+
"Set the number of workers (default: #{options[:workers]})") do |w|
|
27
|
+
options[:workers] = w
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on('-h', '--help', 'Print this help') do
|
31
|
+
puts opts
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
end.parse!
|
35
|
+
# A builder group can be exposed as a simple rack based HTTP server
|
36
|
+
# with routes corresponding to each builder class deeply nested
|
37
|
+
# withhin the builder group module.
|
38
|
+
|
39
|
+
RackServer.new(
|
40
|
+
Rack::Builder.new do
|
41
|
+
use Rack::Lint if Environment.development?
|
42
|
+
run rack_app
|
43
|
+
end
|
44
|
+
).start(port: options[:port], workers: options[:workers])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/lazy_graph/context.rb
CHANGED
@@ -11,17 +11,34 @@ module LazyGraph
|
|
11
11
|
graph.validate!(input) if [true, 'input'].include?(graph.validate)
|
12
12
|
@graph = graph
|
13
13
|
@input = input
|
14
|
+
@graph.root_node.properties.each_key do |key|
|
15
|
+
define_singleton_method(key) { get(key) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_json(path)
|
20
|
+
HashUtils.strip_missing(get(path))
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(path)
|
24
|
+
result = resolve(path)
|
25
|
+
raise result[:error] if result[:err]
|
26
|
+
|
27
|
+
result[:output]
|
14
28
|
end
|
15
29
|
|
16
|
-
def
|
17
|
-
|
30
|
+
def debug(path)
|
31
|
+
result = resolve(path)
|
32
|
+
raise result[:error] if result[:err]
|
33
|
+
|
34
|
+
result[:debug_trace]
|
18
35
|
end
|
19
36
|
|
20
37
|
def resolve(path)
|
21
38
|
@input = @graph.root_node.fetch_item({ input: @input }, :input, nil)
|
22
39
|
|
23
|
-
query = PathParser.parse(path, true)
|
24
|
-
stack = StackPointer.new(nil, @input, 0, :'$', nil)
|
40
|
+
query = PathParser.parse(path, strip_root: true)
|
41
|
+
stack = StackPointer.new(nil, @input, 0, 0, :'$', nil)
|
25
42
|
stack.root = stack
|
26
43
|
|
27
44
|
result = @graph.root_node.resolve(query, stack)
|
@@ -32,18 +49,20 @@ module LazyGraph
|
|
32
49
|
stack.frame[:DEBUG] = nil
|
33
50
|
end
|
34
51
|
{
|
35
|
-
output:
|
36
|
-
debug_trace:
|
52
|
+
output: result,
|
53
|
+
debug_trace: debug_trace
|
37
54
|
}
|
38
|
-
rescue
|
55
|
+
rescue AbortError, ValidationError => e
|
39
56
|
{
|
40
|
-
output:
|
57
|
+
output: nil, err: e.message, status: :abort, error: e
|
41
58
|
}
|
42
59
|
rescue StandardError => e
|
43
|
-
|
44
|
-
|
60
|
+
if @graph.debug?
|
61
|
+
LazyGraph.logger.error(e.message)
|
62
|
+
LazyGraph.logger.error(e.backtrace.join("\n"))
|
63
|
+
end
|
45
64
|
{
|
46
|
-
output:
|
65
|
+
output: nil, err: e.message, backtrace: e.backtrace, error: e
|
47
66
|
}
|
48
67
|
end
|
49
68
|
|
@@ -54,9 +73,15 @@ module LazyGraph
|
|
54
73
|
q.text 'graph='
|
55
74
|
q.pp(@graph)
|
56
75
|
end
|
76
|
+
q.group do
|
77
|
+
q.text 'input='
|
78
|
+
q.pp(@input)
|
79
|
+
end
|
57
80
|
end
|
58
81
|
end
|
59
82
|
|
60
|
-
|
83
|
+
def [](*parts)
|
84
|
+
get(parts.map { |p| p.is_a?(Integer) ? "[#{p}]" : p.to_s }.join('.').gsub('.[', '['))
|
85
|
+
end
|
61
86
|
end
|
62
87
|
end
|