foobara 0.2.6 → 0.3.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/README.md +20 -7
  4. data/projects/builtin_types/lib/foobara/builtin_types.rb +1 -0
  5. data/projects/builtin_types/src/attributes/supported_processors/element_type_declarations.rb +1 -3
  6. data/projects/builtin_types/src/builtin_types.rb +6 -4
  7. data/projects/builtin_types/src/email/transformers/downcase.rb +0 -2
  8. data/projects/callback/src/block/concerns/keyword_argumentable_block.rb +2 -0
  9. data/projects/command/src/command_pattern_implementation/concerns/namespace.rb +0 -2
  10. data/projects/command/src/command_pattern_implementation/concerns/reflection.rb +0 -2
  11. data/projects/command_connectors/src/command_connector/concerns/reflection.rb +187 -0
  12. data/projects/command_connectors/src/command_connector.rb +89 -255
  13. data/projects/command_connectors/src/command_registry/exposed_command.rb +6 -4
  14. data/projects/command_connectors/src/command_registry.rb +50 -48
  15. data/projects/command_connectors/src/serializers/atomic_serializer.rb +4 -1
  16. data/projects/command_connectors/src/transformed_command.rb +104 -100
  17. data/projects/command_connectors/src/transformers/load_atoms_transformer.rb +2 -0
  18. data/projects/command_connectors/src/transformers/load_delegated_attributes_entities_pre_commit_transformer.rb +2 -0
  19. data/projects/detached_entity/lib/foobara/detached_entity.rb +2 -0
  20. data/projects/detached_entity/src/remove_sensitive_values_transformer_extensions.rb +64 -0
  21. data/projects/domain/src/domain_module_extension.rb +0 -21
  22. data/projects/model/src/concerns/types.rb +1 -0
  23. data/projects/model/src/extensions/type_declarations/handlers/extend_model_type_declaration/to_type_transformer.rb +1 -1
  24. data/projects/model_attribute_helpers/src/attribute_helpers.rb +4 -4
  25. data/projects/namespace/src/is_namespace.rb +1 -1
  26. data/projects/namespace/src/namespace/lookup_mode.rb +6 -0
  27. data/projects/nested_transactionable/lib/foobara/nested_transactionable.rb +0 -2
  28. data/projects/persistence/src/entity_base/transaction/concerns/state_transitions.rb +0 -2
  29. data/projects/persistence/src/entity_base.rb +1 -0
  30. data/projects/type_declarations/lib/foobara/type_declarations.rb +3 -1
  31. data/projects/type_declarations/src/remove_sensitive_values_transformer.rb +0 -59
  32. data/version.rb +1 -1
  33. metadata +3 -1
@@ -4,6 +4,7 @@ module Foobara
4
4
  class AlreadyConnectedError < StandardError; end
5
5
 
6
6
  include Concerns::Desugarizers
7
+ include Concerns::Reflection
7
8
 
8
9
  class << self
9
10
  def find_builtin_command_class(command_class_name)
@@ -144,6 +145,68 @@ module Foobara
144
145
  end
145
146
  end
146
147
 
148
+ def connect(*args, **opts)
149
+ args, opts = desugarize_connect_args(args, opts)
150
+
151
+ registerable = args.first
152
+
153
+ if opts.key?(:authenticator)
154
+ authenticator = opts[:authenticator]
155
+ authenticator = self.class.to_authenticator(authenticator)
156
+ opts = opts.merge(authenticator:)
157
+ end
158
+
159
+ case registerable
160
+ when Class
161
+ unless registerable < Command
162
+ # :nocov:
163
+ raise "Don't know how to register #{registerable} (#{registerable.class})"
164
+ # :nocov:
165
+ end
166
+
167
+ command_registry.register(*args, **opts)
168
+ when Module
169
+ if registerable.foobara_organization?
170
+ args = args[1..]
171
+ registerable.foobara_domains.map do |domain|
172
+ connect(domain, *args, **opts)
173
+ end.flatten
174
+ elsif registerable.foobara_domain?
175
+ args = args[1..]
176
+ connected = []
177
+
178
+ registerable = registerable.foobara_all_command(mode: Namespace::LookupMode::DIRECT)
179
+
180
+ registerable.each do |command_class|
181
+ unless command_class.abstract?
182
+ connected << connect(command_class, *args, **opts)
183
+ end
184
+ end
185
+
186
+ connected.flatten
187
+ else
188
+ # :nocov:
189
+ raise "Don't know how to register #{registerable} (#{registerable.class})"
190
+ # :nocov:
191
+ end
192
+ when Symbol, String
193
+ connect_delayed(*args, **opts)
194
+ else
195
+ # :nocov:
196
+ raise "Don't know how to register #{registerable} (#{registerable.class})"
197
+ # :nocov:
198
+ end
199
+ end
200
+
201
+ # TODO: maybe introduce a Runner interface?
202
+ def run(...)
203
+ process_delayed_connections
204
+
205
+ request = build_request(...)
206
+
207
+ run_request(request)
208
+ end
209
+
147
210
  def find_builtin_command_class(command_class_name)
148
211
  self.class.find_builtin_command_class(command_class_name)
149
212
  end
@@ -166,6 +229,28 @@ module Foobara
166
229
  command_registry.foobara_lookup_command(name)
167
230
  end
168
231
 
232
+ def type_from_name(name)
233
+ Foobara.foobara_lookup_type(name, mode: Namespace::LookupMode::RELAXED)
234
+ end
235
+
236
+ def all_exposed_commands
237
+ process_delayed_connections
238
+
239
+ command_registry.foobara_all_command(mode: Namespace::LookupMode::ABSOLUTE_SINGLE_NAMESPACE)
240
+ end
241
+
242
+ def all_exposed_command_names
243
+ all_exposed_commands.map(&:full_command_name)
244
+ end
245
+
246
+ def command_connected?(original_command_class)
247
+ all_exposed_commands.any? do |command|
248
+ command.command_class == original_command_class
249
+ end
250
+ end
251
+
252
+ protected
253
+
169
254
  def request_to_command_class(request)
170
255
  action = request.action
171
256
  full_command_name = request.full_command_name
@@ -335,59 +420,6 @@ module Foobara
335
420
  delayed_connections.clear
336
421
  end
337
422
 
338
- def connect(*args, **opts)
339
- args, opts = desugarize_connect_args(args, opts)
340
-
341
- registerable = args.first
342
-
343
- if opts.key?(:authenticator)
344
- authenticator = opts[:authenticator]
345
- authenticator = self.class.to_authenticator(authenticator)
346
- opts = opts.merge(authenticator:)
347
- end
348
-
349
- case registerable
350
- when Class
351
- unless registerable < Command
352
- # :nocov:
353
- raise "Don't know how to register #{registerable} (#{registerable.class})"
354
- # :nocov:
355
- end
356
-
357
- command_registry.register(*args, **opts)
358
- when Module
359
- if registerable.foobara_organization?
360
- args = args[1..]
361
- registerable.foobara_domains.map do |domain|
362
- connect(domain, *args, **opts)
363
- end.flatten
364
- elsif registerable.foobara_domain?
365
- args = args[1..]
366
- connected = []
367
-
368
- registerable = registerable.foobara_all_command(mode: Namespace::LookupMode::DIRECT)
369
-
370
- registerable.each do |command_class|
371
- unless command_class.abstract?
372
- connected << connect(command_class, *args, **opts)
373
- end
374
- end
375
-
376
- connected.flatten
377
- else
378
- # :nocov:
379
- raise "Don't know how to register #{registerable} (#{registerable.class})"
380
- # :nocov:
381
- end
382
- when Symbol, String
383
- connect_delayed(*args, **opts)
384
- else
385
- # :nocov:
386
- raise "Don't know how to register #{registerable} (#{registerable.class})"
387
- # :nocov:
388
- end
389
- end
390
-
391
423
  def desugarize_connect_args(args, opts)
392
424
  if self.class.desugarizer
393
425
  self.class.desugarizer.process_value!([args, opts])
@@ -406,15 +438,6 @@ module Foobara
406
438
  end
407
439
  end
408
440
 
409
- # TODO: maybe introduce a Runner interface?
410
- def run(...)
411
- process_delayed_connections
412
-
413
- request = build_request(...)
414
-
415
- run_request(request)
416
- end
417
-
418
441
  def run_request(request)
419
442
  command_class = determine_command_class(request)
420
443
  request.command_class = command_class
@@ -443,12 +466,10 @@ module Foobara
443
466
  end
444
467
  end
445
468
  ensure
446
- request.use_transaction do
447
- if (request.response || request).outcome&.success?
448
- request.commit_transaction_if_open
449
- else
450
- request.rollback_transaction
451
- end
469
+ if (request.response || request).outcome&.success?
470
+ request.commit_transaction_if_open
471
+ else
472
+ request.rollback_transaction
452
473
  end
453
474
  end
454
475
 
@@ -497,192 +518,5 @@ module Foobara
497
518
 
498
519
  response
499
520
  end
500
-
501
- def type_from_name(name)
502
- Foobara.foobara_lookup_type(name, mode: Namespace::LookupMode::RELAXED)
503
- end
504
-
505
- def foobara_manifest
506
- Namespace.use command_registry do
507
- foobara_manifest_in_current_namespace
508
- end
509
- end
510
-
511
- # TODO: try to break this giant method up
512
- def foobara_manifest_in_current_namespace
513
- process_delayed_connections
514
-
515
- to_include = Set.new
516
-
517
- to_include << command_registry.global_organization
518
- to_include << command_registry.global_domain
519
-
520
- command_registry.foobara_each_command(mode: Namespace::LookupMode::ABSOLUTE_SINGLE_NAMESPACE) do |exposed_command|
521
- to_include << exposed_command
522
- end
523
-
524
- included = Set.new
525
-
526
- additional_to_include = Set.new
527
-
528
- h = {
529
- organization: {},
530
- domain: {},
531
- type: {},
532
- command: {},
533
- error: {}
534
- }
535
-
536
- if TypeDeclarations.include_processors?
537
- h.merge!(
538
- processor: {},
539
- processor_class: {}
540
- )
541
- end
542
-
543
- TypeDeclarations.with_manifest_context(to_include: additional_to_include, remove_sensitive: true) do
544
- until to_include.empty? && additional_to_include.empty?
545
- object = nil
546
-
547
- if to_include.empty?
548
- until additional_to_include.empty?
549
- o = additional_to_include.first
550
- additional_to_include.delete(o)
551
-
552
- if o.is_a?(::Module)
553
- if o.foobara_domain? || o.foobara_organization?
554
- unless o.foobara_root_namespace == command_registry
555
- next
556
- end
557
- elsif o.is_a?(::Class) && o < Foobara::Command
558
- next
559
- end
560
- elsif o.is_a?(Types::Type)
561
- if o.sensitive?
562
- # :nocov:
563
- raise UnexpectedSensitiveTypeInManifestError,
564
- "Unexpected sensitive type in manifest: #{o.scoped_full_path}. " \
565
- "Make sure these are not included."
566
- # :nocov:
567
- else
568
-
569
- mode = Namespace::LookupMode::ABSOLUTE_SINGLE_NAMESPACE
570
- domain_name = o.foobara_domain.scoped_full_name
571
-
572
- exposed_domain = command_registry.foobara_lookup_domain(domain_name, mode:)
573
-
574
- exposed_domain ||= command_registry.build_and_register_exposed_domain(domain_name)
575
-
576
- # Since we don't know which other domains/orgs creating this domain might have created,
577
- # we will just add them all to be included just in case
578
- command_registry.foobara_all_domain(mode:).each do |exposed_domain|
579
- additional_to_include << exposed_domain
580
- end
581
-
582
- command_registry.foobara_all_organization(mode:).each do |exposed_organization|
583
- additional_to_include << exposed_organization
584
- end
585
- end
586
- end
587
-
588
- object = o
589
- break
590
- end
591
- else
592
- object = to_include.first
593
- to_include.delete(object)
594
- end
595
-
596
- break unless object
597
- next if included.include?(object)
598
-
599
- manifest_reference = object.foobara_manifest_reference.to_sym
600
-
601
- category_symbol = command_registry.foobara_category_symbol_for(object)
602
-
603
- unless category_symbol
604
- # :nocov:
605
- raise "no category symbol for #{object}"
606
- # :nocov:
607
- end
608
-
609
- namespace = if object.is_a?(Types::Type)
610
- object.created_in_namespace
611
- else
612
- Foobara::Namespace.current
613
- end
614
-
615
- # TODO: do we really need to enter the namespace here for this?
616
- h[category_symbol][manifest_reference] = Foobara::Namespace.use namespace do
617
- object.foobara_manifest
618
- end
619
-
620
- included << object
621
- end
622
- end
623
-
624
- h[:domain].each_value do |domain_manifest|
625
- # TODO: hack, we need to trim types down to what is actually included in this manifest
626
- domain_manifest[:types] = domain_manifest[:types].select do |type_name|
627
- h[:type].key?(type_name.to_sym)
628
- end
629
- end
630
-
631
- h = normalize_manifest(h)
632
- patch_up_broken_parents_for_errors_with_missing_command_parents(h)
633
- end
634
-
635
- def normalize_manifest(manifest_hash)
636
- manifest_hash.map do |key, entries|
637
- [key, entries.sort.to_h]
638
- end.sort.to_h
639
- end
640
-
641
- def patch_up_broken_parents_for_errors_with_missing_command_parents(manifest_hash)
642
- root_manifest = Manifest::RootManifest.new(manifest_hash)
643
-
644
- error_category = {}
645
-
646
- root_manifest.errors.each do |error|
647
- error_manifest = if error.parent_category == :command &&
648
- !root_manifest.contains?(error.parent_name, error.parent_category)
649
- domain = error.domain
650
- index = domain.scoped_full_path.size
651
-
652
- fixed_scoped_path = error.scoped_full_path[index..]
653
- fixed_scoped_name = fixed_scoped_path.join("::")
654
- fixed_scoped_prefix = fixed_scoped_path[..-2]
655
- fixed_parent = [:domain, domain.reference]
656
-
657
- error.relevant_manifest.merge(
658
- parent: fixed_parent,
659
- scoped_path: fixed_scoped_path,
660
- scoped_name: fixed_scoped_name,
661
- scoped_prefix: fixed_scoped_prefix
662
- )
663
- else
664
- error.relevant_manifest
665
- end
666
-
667
- error_category[error.scoped_full_name.to_sym] = error_manifest
668
- end
669
-
670
- manifest_hash.merge(error: error_category)
671
- end
672
-
673
- def all_exposed_commands
674
- process_delayed_connections
675
-
676
- command_registry.foobara_all_command(mode: Namespace::LookupMode::ABSOLUTE_SINGLE_NAMESPACE)
677
- end
678
-
679
- def all_exposed_command_names
680
- all_exposed_commands.map(&:full_command_name)
681
- end
682
-
683
- def all_exposed_type_names
684
- # TODO: cache this or better yet cache #foobara_manifest
685
- foobara_manifest[:type].keys.sort.map(&:to_s)
686
- end
687
521
  end
688
522
  end
@@ -126,10 +126,6 @@ module Foobara
126
126
  @transformed_command_class = nil
127
127
  end
128
128
 
129
- def _has_delegated_attributes?(type)
130
- type&.extends?(BuiltinTypes[:model]) && type.target_class&.has_delegated_attributes?
131
- end
132
-
133
129
  def full_command_name
134
130
  scoped_full_name
135
131
  end
@@ -191,6 +187,8 @@ module Foobara
191
187
  end
192
188
  end
193
189
 
190
+ private
191
+
194
192
  # TODO: what to do if the whole return type is sensitive? return nil?
195
193
  def result_has_sensitive_types?
196
194
  result_type = command_class.result_type
@@ -206,6 +204,10 @@ module Foobara
206
204
  command_class.result_type.has_sensitive_types?
207
205
  end
208
206
  end
207
+
208
+ def _has_delegated_attributes?(type)
209
+ type&.extends?(BuiltinTypes[:model]) && type.target_class&.has_delegated_attributes?
210
+ end
209
211
  end
210
212
  end
211
213
  end
@@ -32,45 +32,11 @@ module Foobara
32
32
  create_exposed_command(command_class, **)
33
33
  end
34
34
 
35
- def create_exposed_command(command_class, **)
36
- full_domain_name = command_class.domain.scoped_full_name
37
- exposed_domain = foobara_lookup_domain(full_domain_name,
38
- mode: Namespace::LookupMode::ABSOLUTE_SINGLE_NAMESPACE) ||
39
- build_and_register_exposed_domain(full_domain_name)
40
-
41
- exposed_command = create_exposed_command_without_domain(command_class, **)
42
-
43
- exposed_domain.foobara_register(exposed_command)
44
-
45
- exposed_command
46
- end
47
-
48
35
  # TODO: eliminate this method
49
36
  def create_exposed_command_without_domain(command_class, **)
50
37
  ExposedCommand.new(command_class, **apply_defaults(**))
51
38
  end
52
39
 
53
- def apply_defaults(
54
- inputs_transformers: nil,
55
- result_transformers: nil,
56
- errors_transformers: nil,
57
- pre_commit_transformers: nil,
58
- serializers: nil,
59
- allowed_rule: default_allowed_rule,
60
- authenticator: nil,
61
- **opts
62
- )
63
- opts.merge(
64
- inputs_transformers: [*inputs_transformers, *default_inputs_transformers],
65
- result_transformers: [*result_transformers, *default_result_transformers],
66
- errors_transformers: [*errors_transformers, *default_errors_transformers],
67
- pre_commit_transformers: [*pre_commit_transformers, *default_pre_commit_transformers],
68
- serializers: [*serializers, *default_serializers],
69
- allowed_rule: allowed_rule && to_allowed_rule(allowed_rule),
70
- authenticator: authenticator || self.authenticator
71
- )
72
- end
73
-
74
40
  def build_and_register_exposed_domain(domain_full_name)
75
41
  domain_module = if domain_full_name.to_s == ""
76
42
  GlobalDomain
@@ -211,6 +177,37 @@ module Foobara
211
177
  default_serializers << serializer
212
178
  end
213
179
 
180
+ def transformed_command_from_name(name)
181
+ foobara_lookup_command(name, mode: Namespace::LookupMode::RELAXED)&.transformed_command_class
182
+ end
183
+
184
+ def all_transformed_command_classes
185
+ foobara_all_command.map(&:transformed_command_class)
186
+ end
187
+
188
+ def each_transformed_command_class(&)
189
+ foobara_all_command.map(&:transformed_command_class).each(&)
190
+ end
191
+
192
+ def size
193
+ foobara_all_command.size
194
+ end
195
+
196
+ private
197
+
198
+ def create_exposed_command(command_class, **)
199
+ full_domain_name = command_class.domain.scoped_full_name
200
+ exposed_domain = foobara_lookup_domain(full_domain_name,
201
+ mode: Namespace::LookupMode::ABSOLUTE_SINGLE_NAMESPACE) ||
202
+ build_and_register_exposed_domain(full_domain_name)
203
+
204
+ exposed_command = create_exposed_command_without_domain(command_class, **)
205
+
206
+ exposed_domain.foobara_register(exposed_command)
207
+
208
+ exposed_command
209
+ end
210
+
214
211
  def to_allowed_rule(*args)
215
212
  symbol, object = case args.size
216
213
  when 1
@@ -289,20 +286,25 @@ module Foobara
289
286
  end
290
287
  end
291
288
 
292
- def transformed_command_from_name(name)
293
- foobara_lookup_command(name, mode: Namespace::LookupMode::RELAXED)&.transformed_command_class
294
- end
295
-
296
- def all_transformed_command_classes
297
- foobara_all_command.map(&:transformed_command_class)
298
- end
299
-
300
- def each_transformed_command_class(&)
301
- foobara_all_command.map(&:transformed_command_class).each(&)
302
- end
303
-
304
- def size
305
- foobara_all_command.size
289
+ def apply_defaults(
290
+ inputs_transformers: nil,
291
+ result_transformers: nil,
292
+ errors_transformers: nil,
293
+ pre_commit_transformers: nil,
294
+ serializers: nil,
295
+ allowed_rule: default_allowed_rule,
296
+ authenticator: nil,
297
+ **opts
298
+ )
299
+ opts.merge(
300
+ inputs_transformers: [*inputs_transformers, *default_inputs_transformers],
301
+ result_transformers: [*result_transformers, *default_result_transformers],
302
+ errors_transformers: [*errors_transformers, *default_errors_transformers],
303
+ pre_commit_transformers: [*pre_commit_transformers, *default_pre_commit_transformers],
304
+ serializers: [*serializers, *default_serializers],
305
+ allowed_rule: allowed_rule && to_allowed_rule(allowed_rule),
306
+ authenticator: authenticator || self.authenticator
307
+ )
306
308
  end
307
309
  end
308
310
  end
@@ -4,8 +4,11 @@ module Foobara
4
4
  # This seems to interpret "Atomic" as load the first entity you hit but not deeper entities.
5
5
  # Other interpretations it could have been:
6
6
  # 1) If top-level is an entity, load it and convert all nested entities to primary keys,
7
- # otherwise, convert all entities to primary keys
7
+ # otherwise, convert all entities to primary keys
8
8
  # 2) Once past the first model, all entities are cast to primary keys
9
+ #
10
+ # However, in the typescript-remote-command-generator, the logic is a little different...
11
+ # There, AtomModelGenerator always uses primary keys for all entities.
9
12
  class AtomicSerializer < SuccessSerializer
10
13
  def serialize(object)
11
14
  case object