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