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
@@ -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)
|