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
@@ -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,19 +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
|
-
yields(new_object, &
|
84
|
+
yields(new_object, &lambda {
|
85
|
+
blk&.call
|
86
|
+
extend&.call
|
87
|
+
})
|
80
88
|
|
81
89
|
object_names = @match_cases.map do |match_case|
|
82
90
|
rule = rule_from_when(match_case[:when_clause])
|
83
|
-
set_property(match_case[:name],
|
91
|
+
set_property(match_case[:name],
|
92
|
+
{ type: :object, rule: rule, rule_location: rule_location, **match_case[:schema] })
|
84
93
|
match_case[:name]
|
85
94
|
end
|
86
95
|
|
87
|
-
new_object[:rule] = rule_from_first_of(object_names)
|
96
|
+
new_object[:rule] = rule_from_first_of(object_names, conditions)
|
88
97
|
@match_cases = @prev_match_cases
|
89
98
|
required(name) if required && default.nil? && rule.nil?
|
90
99
|
pattern_property ? set_pattern_property(name, new_object) : set_property(name, new_object)
|
@@ -95,23 +104,32 @@ module LazyGraph
|
|
95
104
|
yields(@match_cases.last[:schema], &blk)
|
96
105
|
end
|
97
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
|
+
|
98
112
|
def object(
|
99
|
-
name = nil, required: false, pattern_property: false, rule: nil,
|
100
|
-
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
|
101
115
|
)
|
102
116
|
rule ||= rule_from_when(opts.delete(:when)) if opts[:when]
|
103
117
|
rule ||= rule_from_first_of(opts.delete(:first_of)) if opts[:first_of]
|
118
|
+
rule ||= rule_from_copy(copy)
|
104
119
|
new_object = {
|
105
120
|
type: :object,
|
106
121
|
properties: {},
|
107
122
|
additionalProperties: false,
|
108
|
-
|
109
123
|
**(!default.nil? ? { default: default } : {}),
|
110
124
|
**(description ? { description: description } : {}),
|
111
|
-
**(rule ? { rule: rule } : {}),
|
125
|
+
**(rule ? { rule: rule, rule_location: rule_location } : {}),
|
126
|
+
validate_presence: required,
|
112
127
|
**opts
|
113
128
|
}
|
114
|
-
yields(new_object, &
|
129
|
+
yields(new_object, &lambda {
|
130
|
+
blk&.call
|
131
|
+
extend&.call
|
132
|
+
})
|
115
133
|
required(name) if required && default.nil? && rule.nil?
|
116
134
|
pattern_property ? set_pattern_property(name, new_object) : set_property(name, new_object)
|
117
135
|
end
|
@@ -119,14 +137,15 @@ module LazyGraph
|
|
119
137
|
def primitive(
|
120
138
|
name = nil, type, required: false, pattern_property: false,
|
121
139
|
default: nil, description: nil, enum: nil,
|
122
|
-
rule: nil, **additional_options, &blk
|
140
|
+
rule: nil, copy: nil, **additional_options, &blk
|
123
141
|
)
|
142
|
+
rule ||= rule_from_copy(copy)
|
124
143
|
new_primitive = {
|
125
144
|
type: type,
|
126
145
|
**(enum ? { enum: enum } : {}),
|
127
146
|
**(!default.nil? ? { default: default } : {}),
|
128
147
|
**(description ? { description: description } : {}),
|
129
|
-
**(rule ? { rule: rule } : {}),
|
148
|
+
**(rule ? { rule: rule, rule_location: rule_location } : {}),
|
130
149
|
**additional_options
|
131
150
|
}
|
132
151
|
yields(new_primitive, &blk)
|
@@ -134,8 +153,9 @@ module LazyGraph
|
|
134
153
|
pattern_property ? set_pattern_property(name, new_primitive) : set_property(name, new_primitive)
|
135
154
|
end
|
136
155
|
|
137
|
-
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,
|
138
157
|
**opts, &blk)
|
158
|
+
rule ||= rule_from_copy(copy)
|
139
159
|
# Define the decimal schema supporting multiple formats
|
140
160
|
new_decimal = {
|
141
161
|
anyOf: [
|
@@ -156,7 +176,8 @@ module LazyGraph
|
|
156
176
|
type: :decimal,
|
157
177
|
**(!default.nil? ? { default: default } : {}),
|
158
178
|
**(description ? { description: description } : {}),
|
159
|
-
**(rule ? { rule: rule } : {}),
|
179
|
+
**(rule ? { rule: rule, rule_location: rule_location } : {}),
|
180
|
+
validate_presence: required,
|
160
181
|
**opts
|
161
182
|
}
|
162
183
|
yields(new_decimal, &blk)
|
@@ -164,8 +185,9 @@ module LazyGraph
|
|
164
185
|
pattern_property ? set_pattern_property(name, new_decimal) : set_property(name, new_decimal)
|
165
186
|
end
|
166
187
|
|
167
|
-
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,
|
168
189
|
**opts, &blk)
|
190
|
+
rule ||= rule_from_copy(copy)
|
169
191
|
new_timestamp = {
|
170
192
|
anyOf: [
|
171
193
|
{
|
@@ -183,7 +205,8 @@ module LazyGraph
|
|
183
205
|
type: :timestamp, # Custom extended type
|
184
206
|
**(!default.nil? ? { default: default } : {}),
|
185
207
|
**(description ? { description: description } : {}),
|
186
|
-
**(rule ? { rule: rule } : {}),
|
208
|
+
**(rule ? { rule: rule, rule_location: rule_location } : {}),
|
209
|
+
validate_presence: required,
|
187
210
|
**opts
|
188
211
|
}
|
189
212
|
yields(new_timestamp, &blk)
|
@@ -191,15 +214,17 @@ module LazyGraph
|
|
191
214
|
pattern_property ? set_pattern_property(name, new_timestamp) : set_property(name, new_timestamp)
|
192
215
|
end
|
193
216
|
|
194
|
-
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,
|
195
218
|
**opts, &blk)
|
219
|
+
rule ||= rule_from_copy(copy)
|
196
220
|
new_time = {
|
197
221
|
type: :time, # Custom extended type
|
198
222
|
# Matches HH:mm[:ss[.SSS]]
|
199
223
|
pattern: '^\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,3})?)?$',
|
200
224
|
**(!default.nil? ? { default: default } : {}),
|
201
225
|
**(description ? { description: description } : {}),
|
202
|
-
**(rule ? { rule: rule } : {}),
|
226
|
+
**(rule ? { rule: rule, rule_location: rule_location } : {}),
|
227
|
+
validate_presence: required,
|
203
228
|
**opts
|
204
229
|
}
|
205
230
|
yields(new_time, &blk)
|
@@ -207,8 +232,9 @@ module LazyGraph
|
|
207
232
|
pattern_property ? set_pattern_property(name, new_time) : set_property(name, new_time)
|
208
233
|
end
|
209
234
|
|
210
|
-
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,
|
211
236
|
**opts, &blk)
|
237
|
+
rule ||= rule_from_copy(copy)
|
212
238
|
new_date = {
|
213
239
|
anyOf: [
|
214
240
|
{
|
@@ -220,7 +246,8 @@ module LazyGraph
|
|
220
246
|
type: :date, # Custom extended type
|
221
247
|
**(!default.nil? ? { default: default } : {}),
|
222
248
|
**(description ? { description: description } : {}),
|
223
|
-
**(rule ? { rule: rule } : {}),
|
249
|
+
**(rule ? { rule: rule, rule_location: rule_location } : {}),
|
250
|
+
validate_presence: required,
|
224
251
|
**opts
|
225
252
|
}
|
226
253
|
yields(new_date, &blk)
|
@@ -231,10 +258,11 @@ module LazyGraph
|
|
231
258
|
%i[boolean string const integer number null].each do |type|
|
232
259
|
define_method(type) do |
|
233
260
|
name = nil, required: false, pattern_property: false,
|
234
|
-
default: nil, description: nil, enum: nil, rule: nil,
|
261
|
+
default: nil, description: nil, enum: nil, rule: nil, copy: nil,
|
262
|
+
**additional_options, &blk|
|
235
263
|
primitive(
|
236
264
|
name, type, required: required, pattern_property: pattern_property, enum: enum,
|
237
|
-
default: default, rule: rule, description: description,
|
265
|
+
default: default, rule: rule, rule_location: rule_location, description: description,
|
238
266
|
**additional_options, &blk
|
239
267
|
)
|
240
268
|
end
|
@@ -252,17 +280,18 @@ module LazyGraph
|
|
252
280
|
schema[:anyOf] = (schema[:any_of] || []).concat(any_of).uniq
|
253
281
|
end
|
254
282
|
|
255
|
-
def array(name = nil, required: false, pattern_property: false, default: nil, description: nil, rule: nil,
|
256
|
-
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)
|
257
286
|
new_array = {
|
258
287
|
type: :array,
|
259
288
|
**(!default.nil? ? { default: default } : {}),
|
260
289
|
**(description ? { description: description } : {}),
|
261
|
-
**(rule ? { rule: rule } : {}),
|
290
|
+
**(rule ? { rule: rule, rule_location: rule_location } : {}),
|
262
291
|
**opts,
|
263
292
|
items: { properties: {} }.tap do |items|
|
264
293
|
yields(items) do
|
265
|
-
send(type, :items, &
|
294
|
+
send(type, :items, extend: extend, &block)
|
266
295
|
end
|
267
296
|
end[:properties][:items]
|
268
297
|
}
|
@@ -275,6 +304,7 @@ module LazyGraph
|
|
275
304
|
end
|
276
305
|
|
277
306
|
def yields(other)
|
307
|
+
raise ArgumentError, 'Builder DSL used outside of rules module' unless schema
|
278
308
|
return unless block_given?
|
279
309
|
|
280
310
|
prev_schema = schema
|
@@ -283,6 +313,12 @@ module LazyGraph
|
|
283
313
|
self.schema = prev_schema
|
284
314
|
end
|
285
315
|
|
316
|
+
def rule_from_copy(copy)
|
317
|
+
return unless copy
|
318
|
+
|
319
|
+
"${#{copy}}"
|
320
|
+
end
|
321
|
+
|
286
322
|
def rule_from_when(when_clause)
|
287
323
|
inputs = when_clause.keys
|
288
324
|
conditions = when_clause
|
@@ -295,17 +331,19 @@ module LazyGraph
|
|
295
331
|
}
|
296
332
|
end
|
297
333
|
|
298
|
-
def rule_from_first_of(prop_list)
|
334
|
+
def rule_from_first_of(prop_list, conditions = nil)
|
335
|
+
prop_list += conditions.keys if conditions
|
299
336
|
{
|
300
337
|
inputs: prop_list,
|
301
|
-
calc: "itself.get_first_of(:#{prop_list.join(', :')})"
|
338
|
+
calc: "itself.get_first_of(:#{prop_list.join(', :')})",
|
339
|
+
**(conditions ? { conditions: conditions } : {})
|
302
340
|
}
|
303
341
|
end
|
304
342
|
|
305
343
|
def depends_on(*dependencies)
|
306
344
|
@resolved_dependencies ||= Hash.new do |h, k|
|
307
|
-
send(k) # Load dependency once
|
308
345
|
h[k] = true
|
346
|
+
send(k) # Load dependency once
|
309
347
|
end
|
310
348
|
dependencies.each(&@resolved_dependencies.method(:[]))
|
311
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: ENV.fetch('LAZY_GRAPH_GRAPH_CACHE_MAX_ENTRIES', 1000).to_i, 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: ENV.fetch('LAZY_GRAPH_CONTEXT_CACHE_MAX_ENTRIES', 5000).to_i, 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: ENV.fetch('LAZY_GRAPH_QUERY_CACHE_MAX_ENTRIES', 5000).to_i, 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,10 +23,13 @@ module LazyGraph
|
|
23
23
|
attr_accessor :schema
|
24
24
|
|
25
25
|
def initialize(schema: { type: 'object', properties: {} }) = @schema = schema
|
26
|
-
def context(value, debug: false, validate:
|
27
|
-
|
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
|
28
31
|
|
29
|
-
def build!(debug: false, validate:
|
32
|
+
def build!(debug: false, validate: true) = @schema.to_lazy_graph(
|
30
33
|
debug: debug,
|
31
34
|
validate: validate,
|
32
35
|
helpers: self.class.helper_modules,
|
@@ -38,6 +41,7 @@ module LazyGraph
|
|
38
41
|
module_body_func_name = :"_#{name}"
|
39
42
|
define_method(module_body_func_name, &blk)
|
40
43
|
define_method(name) do |**args, &inner_blk|
|
44
|
+
@path = @path.nil? ? "#{name}" : "#{@path}+#{name}" unless @path.to_s.include?(name.to_s)
|
41
45
|
send(module_body_func_name, **args, &inner_blk)
|
42
46
|
self
|
43
47
|
end
|
@@ -58,6 +62,8 @@ module LazyGraph
|
|
58
62
|
attr_reader :helper_modules
|
59
63
|
end
|
60
64
|
|
65
|
+
def self.register_helper_modules(*mods) = mods.each(&method(:register_helper_module))
|
66
|
+
|
61
67
|
def self.register_helper_module(mod)
|
62
68
|
(@helper_modules ||= []) << mod
|
63
69
|
end
|
@@ -70,6 +76,12 @@ module LazyGraph
|
|
70
76
|
@helper_modules = nil
|
71
77
|
end
|
72
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
|
+
|
73
85
|
def self.rules_modules
|
74
86
|
@rules_modules ||= {
|
75
87
|
type: :object,
|
@@ -93,8 +105,8 @@ module LazyGraph
|
|
93
105
|
}
|
94
106
|
end
|
95
107
|
|
96
|
-
def self.eval!(modules:, context:, query:, debug: false, validate:
|
97
|
-
|
108
|
+
def self.eval!(modules:, context:, query:, debug: false, validate: true)
|
109
|
+
graph = cache_as(:graph, [modules, debug, validate]) do
|
98
110
|
invalid_modules = modules.reject { |k, _v| rules_modules[:properties].key?(k.to_sym) }
|
99
111
|
return format_error_response('Invalid Modules', invalid_modules.keys.join(',')) unless invalid_modules.empty?
|
100
112
|
|
@@ -105,22 +117,23 @@ module LazyGraph
|
|
105
117
|
builder = build_modules(modules)
|
106
118
|
return builder if builder.is_a?(Hash)
|
107
119
|
|
108
|
-
builder
|
120
|
+
builder.build!(debug: debug, validate: validate)
|
109
121
|
end
|
110
122
|
|
111
|
-
context_result = cache_as(:context, [
|
112
|
-
|
123
|
+
context_result = cache_as(:context, [graph, context]) do
|
124
|
+
build_context(graph, context)
|
113
125
|
end
|
114
126
|
|
115
127
|
return context_result if context_result.is_a?(Hash) && context_result[:type].equal?(:error)
|
116
128
|
|
117
129
|
cache_as(:query, [context_result, query]) do
|
118
|
-
HashUtils.strip_missing(
|
119
|
-
|
120
|
-
|
121
|
-
|
130
|
+
HashUtils.strip_missing(
|
131
|
+
{
|
132
|
+
type: :success,
|
133
|
+
result: context_result.resolve(*(query || ''))
|
134
|
+
}
|
135
|
+
)
|
122
136
|
end
|
123
|
-
|
124
137
|
rescue SystemStackError => e
|
125
138
|
LazyGraph.logger.error(e.message)
|
126
139
|
LazyGraph.logger.error(e.backtrace.join("\n"))
|
@@ -139,9 +152,12 @@ module LazyGraph
|
|
139
152
|
cache.delete(cache.keys.first) while cache.size > max_size
|
140
153
|
end
|
141
154
|
|
155
|
+
def to_str
|
156
|
+
"LazyGraph(modules=#{@path})"
|
157
|
+
end
|
158
|
+
|
142
159
|
private_class_method def self.method_missing(method_name, *args, &block) = new.send(method_name, *args, &block)
|
143
160
|
private_class_method def self.respond_to_missing?(_, _ = false) = true
|
144
|
-
|
145
161
|
private_class_method def self.validate_modules(input_options)
|
146
162
|
JSON::Validator.validate!(rules_modules, input_options)
|
147
163
|
''
|
@@ -163,15 +179,19 @@ module LazyGraph
|
|
163
179
|
acc.send(k, **v.to_h.transform_keys(&:to_sym))
|
164
180
|
end
|
165
181
|
rescue ArgumentError => e
|
166
|
-
|
182
|
+
LazyGraph.logger.error(e.message)
|
167
183
|
LazyGraph.logger.error(e.backtrace.join("\n"))
|
184
|
+
format_error_response('Invalid Module Argument', e.message)
|
168
185
|
end
|
169
186
|
|
170
|
-
private_class_method def self.
|
171
|
-
|
172
|
-
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
|
173
194
|
format_error_response('Invalid Context Input', e.message)
|
174
|
-
LazyGraph.logger.error(e.backtrace.join("\n"))
|
175
195
|
end
|
176
196
|
end
|
177
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,27 +23,30 @@ 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
43
|
base.define_singleton_method(:reload_lazy_graphs!) do |clear_previous: false|
|
33
|
-
if clear_previous
|
34
|
-
each_builder do |builder|
|
35
|
-
builder.clear_rules_modules!
|
36
|
-
builder.clear_helper_modules!
|
37
|
-
end
|
38
|
-
end
|
44
|
+
each_builder(&:clear_caches!) if clear_previous
|
39
45
|
|
40
|
-
reload_paths.flat_map { |p|
|
41
|
-
|
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 }
|
42
50
|
rescue StandardError => e
|
43
51
|
LazyGraph.logger.error("Failed to load #{file}: #{e.message}")
|
44
52
|
end
|
@@ -46,14 +54,17 @@ module LazyGraph
|
|
46
54
|
|
47
55
|
base.reload_lazy_graphs! if reload_paths.any?
|
48
56
|
|
49
|
-
|
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
|
50
62
|
|
51
|
-
|
52
|
-
Listen.to(*reload_paths.map { |p| p.gsub(%r{(?:/\*\*)*/\*\.rb}, '') }) do
|
53
|
-
base.reload_lazy_graphs!(clear_previous: true)
|
54
|
-
end.start
|
63
|
+
CLI.invoke!(base.rack_app) if enable_server && bootstrap_script_name == $0
|
55
64
|
end
|
56
65
|
end
|
57
66
|
end
|
58
67
|
end
|
68
|
+
|
69
|
+
define_singleton_method(:bootstrap_app!, &BuilderGroup.method(:bootstrap!))
|
59
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
@@ -22,14 +22,14 @@ module LazyGraph
|
|
22
22
|
|
23
23
|
def get(path)
|
24
24
|
result = resolve(path)
|
25
|
-
raise
|
25
|
+
raise result[:error] if result[:err]
|
26
26
|
|
27
27
|
result[:output]
|
28
28
|
end
|
29
29
|
|
30
30
|
def debug(path)
|
31
31
|
result = resolve(path)
|
32
|
-
raise
|
32
|
+
raise result[:error] if result[:err]
|
33
33
|
|
34
34
|
result[:debug_trace]
|
35
35
|
end
|
@@ -52,13 +52,15 @@ module LazyGraph
|
|
52
52
|
output: result,
|
53
53
|
debug_trace: debug_trace
|
54
54
|
}
|
55
|
-
rescue
|
55
|
+
rescue AbortError, ValidationError => e
|
56
56
|
{
|
57
57
|
output: nil, err: e.message, status: :abort, error: e
|
58
58
|
}
|
59
59
|
rescue StandardError => e
|
60
|
-
|
61
|
-
|
60
|
+
if @graph.debug?
|
61
|
+
LazyGraph.logger.error(e.message)
|
62
|
+
LazyGraph.logger.error(e.backtrace.join("\n"))
|
63
|
+
end
|
62
64
|
{
|
63
65
|
output: nil, err: e.message, backtrace: e.backtrace, error: e
|
64
66
|
}
|
@@ -78,6 +80,8 @@ module LazyGraph
|
|
78
80
|
end
|
79
81
|
end
|
80
82
|
|
81
|
-
|
83
|
+
def [](*parts)
|
84
|
+
get(parts.map { |p| p.is_a?(Integer) ? "[#{p}]" : p.to_s }.join('.').gsub('.[', '['))
|
85
|
+
end
|
82
86
|
end
|
83
87
|
end
|
data/lib/lazy_graph/graph.rb
CHANGED
@@ -4,14 +4,16 @@ require 'json-schema'
|
|
4
4
|
|
5
5
|
module LazyGraph
|
6
6
|
# Represents a lazy graph structure based on JSON schema
|
7
|
-
VALIDATION_CACHE = {}
|
7
|
+
VALIDATION_CACHE = {}.compare_by_identity
|
8
8
|
METASCHEMA = JSON.load_file(File.join(__dir__, 'lazy-graph.json'))
|
9
9
|
|
10
10
|
class Graph
|
11
11
|
attr_reader :json_schema, :root_node, :validate
|
12
12
|
|
13
13
|
def context(input) = Context.new(self, input)
|
14
|
-
def debug? =
|
14
|
+
def debug? = !!@debug
|
15
|
+
alias input context
|
16
|
+
alias feed context
|
15
17
|
|
16
18
|
def initialize(input_schema, debug: false, validate: true, helpers: nil, namespace: nil)
|
17
19
|
@json_schema = HashUtils.deep_dup(input_schema, symbolize: true, signature: signature = [0]).merge(type: :object)
|
@@ -57,7 +59,7 @@ module LazyGraph
|
|
57
59
|
def validate!(input, schema = @json_schema)
|
58
60
|
JSON::Validator.validate!(schema, input)
|
59
61
|
rescue JSON::Schema::ValidationError => e
|
60
|
-
raise
|
62
|
+
raise ValidationError, "Input validation failed: #{e.message}", cause: e
|
61
63
|
end
|
62
64
|
|
63
65
|
def pretty_print(q)
|
@@ -57,12 +57,15 @@ module LazyGraph
|
|
57
57
|
when Hash
|
58
58
|
obj.each_with_object({}) do |(key, value), obj|
|
59
59
|
next if value.is_a?(MissingValue)
|
60
|
+
next if value.nil?
|
60
61
|
|
61
62
|
obj[key] = strip_missing(value, parent_list)
|
62
63
|
end
|
63
64
|
when Struct
|
64
65
|
obj.members.each_with_object({}) do |key, res|
|
65
|
-
|
66
|
+
value = obj.original_get(key)
|
67
|
+
next if value.is_a?(MissingValue)
|
68
|
+
next if value.nil?
|
66
69
|
next if obj.invisible.include?(key)
|
67
70
|
|
68
71
|
res[key] = strip_missing(obj[key], parent_list)
|