lazy_graph 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- yields(new_object, &blk)
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], { type: :object, rule: rule, **match_case[:schema] })
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, &blk)
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, **additional_options, &blk|
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, &blk)
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, &blk)
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
@@ -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 build!(debug: false, validate: false) = @schema.to_lazy_graph(debug: debug, validate: validate, helpers: self.class.helper_modules)
27
- def context(value, debug: false, validate: false) = build!(debug: debug, validate: validate).context(value)
28
- def eval!(context, *value, debug: false, validate: false) = context(context, validate: validate, debug: debug).query(*value)
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: false)
91
- builder = cache_as(:graph, [modules, debug, validate].hash) do
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, [builder, context]) do
106
- evaluate_context(builder, context, debug: debug, validate: validate)
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] == :error
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
- type: :success,
115
- result: query_result
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.evaluate_context(builder, context, debug: false, validate: false)
164
- builder.context(context, debug: debug, validate: validate)
165
- rescue ArgumentError => e
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
- def self.bootstrap!(reload_paths: [], listen: ENV['RACK_ENV'] == 'development')
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.server
22
- LazyGraph::Server.new(
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 do |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| Dir[p] }.each do |file|
39
- load file
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
- return unless listen
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
- require 'listen'
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
@@ -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 query(paths)
17
- paths.is_a?(Array) ? paths.map { |path| resolve(path) } : resolve(paths)
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: HashUtils.strip_invalid(result),
36
- debug_trace: HashUtils.strip_invalid(debug_trace)
52
+ output: result,
53
+ debug_trace: debug_trace
37
54
  }
38
- rescue LazyGraph::AbortError => e
55
+ rescue AbortError, ValidationError => e
39
56
  {
40
- output: { err: e.message, status: :abort }
57
+ output: nil, err: e.message, status: :abort, error: e
41
58
  }
42
59
  rescue StandardError => e
43
- LazyGraph.logger.error(e.message)
44
- LazyGraph.logger.error(e.backtrace)
60
+ if @graph.debug?
61
+ LazyGraph.logger.error(e.message)
62
+ LazyGraph.logger.error(e.backtrace.join("\n"))
63
+ end
45
64
  {
46
- output: { err: e.message, backtrace: e.backtrace }
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
- alias [] query
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
@@ -0,0 +1,6 @@
1
+ module LazyGraph
2
+ module Environment
3
+ def self.development? = env == 'development'
4
+ def self.env = ENV.fetch('RACK_ENV', 'development')
5
+ end
6
+ end