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.
@@ -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, &blk)
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], { 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] })
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, &blk)
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, **additional_options, &blk|
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, &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)
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, &blk)
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
@@ -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: false) = build!(debug: debug, validate: validate).context(value)
27
- 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
28
31
 
29
- def build!(debug: false, validate: false) = @schema.to_lazy_graph(
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: false)
97
- 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
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, [builder, context]) do
112
- evaluate_context(builder, context, debug: debug, validate: validate)
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
- type: :success,
120
- result: context_result.resolve(*(query || ''))
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
- format_error_response('Invalid Module Argument', e.message)
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.evaluate_context(builder, context, debug: false, validate: false)
171
- builder.context(context, debug: debug, validate: validate)
172
- 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
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
- 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,27 +23,30 @@ 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
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| Dir[p] }.each do |file|
41
- 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 }
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
- 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
50
62
 
51
- require 'listen'
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
@@ -22,14 +22,14 @@ module LazyGraph
22
22
 
23
23
  def get(path)
24
24
  result = resolve(path)
25
- raise AbortError, result[:err], cause: result[:error] if result[:err]
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 AbortError, result[:err], cause: result[:error] if result[:err]
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 LazyGraph::AbortError => e
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
- LazyGraph.logger.error(e.message)
61
- LazyGraph.logger.error(e.backtrace.join("\n"))
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
- alias [] get
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
@@ -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
@@ -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? = @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 AbortError, "Input validation failed: #{e.message}", cause: e
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
- next if obj[key].is_a?(MissingValue)
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)