foobara 0.2.7 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f5b2b0f1316a7d5530778566c64df6cffafb44dfdb99e5fd4797de2fbe3b0a2
4
- data.tar.gz: 9819421a3b7dafcb39ac4d3dadb3b7a8ee2967648abf9adad4cc2fd094769bf3
3
+ metadata.gz: 1889d43f30de1d85f60a3a6245feffbed013f4a2ad2b357fd9b61209e815ccd3
4
+ data.tar.gz: e57dfcd26cfb93c4f96084a61e33325644bddfd2d5d592f19e9c608f285244fe
5
5
  SHA512:
6
- metadata.gz: 388210bea30291de942b1b42b80c5105c31a8f74a039e33ac42700c85f8052e7f229390eb57214a15d7cda3ba76767fa4934cf8c8bb2dae187dbce22ee72a631
7
- data.tar.gz: bfb8114f0b127c60389fa83d0d6145178a6b3e6e28418ec2a7faaf645c16ff31d5075688edcd6005ea33fb67e02a76155286dc0cc4a93b5ad710787b2d93ad4d
6
+ metadata.gz: cd88755c0131cab9ee13fc46b38cafc779e5799aae3c33a8289198a809af9f9a021565ccaadc41d05b5a2c02aed22aad4e9ca0b3b664c0447e488ab8993c939e
7
+ data.tar.gz: b9c4090428c31817a87498f261a50a986594d90e8395dfd2250bd56c963373c44d10a8faa27ec0de7500c764ebfd90b6b0ee04d523d4cd7f061041727305e903
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # [0.3.0] - 2025-11-23
2
+
3
+ - Fix bug caused by exposing errors with organizations as parents
4
+ - Restructure code in some projects and make many more methods private
5
+
1
6
  # [0.2.7] - 2025-11-10
2
7
 
3
8
  - Fix CommandConnector authorization bug that can fail to close transactions
data/README.md CHANGED
@@ -1,12 +1,25 @@
1
- Foobara is a software framework to help you encapsulate your high-level
2
- domain operations in commands, and automatically expose machine-readable formal metadata about those
3
- commands so that integration code can be decoupled and abstracted away.
1
+ Foobara is a software framework that is command-centric and discoverable.
4
2
 
5
- This, as well as some other features of foobara, help manage domain complexity and produce
6
- more flexible systems.
3
+ Domain operations are
4
+ encapsulated in commands, which serve as the public interface to systems, and which automatically
5
+ provide machine-readable formal metadata about those commands.
7
6
 
8
- You can watch a video that gives a good overview of what Foobara is and its goals here:
9
- [Introduction to the Foobara software framework](https://youtu.be/SSOmQqjNSVY)
7
+ This metadata makes your domain discoverable and can be used to abstract away integrations,
8
+ such as HTTP, CLI, MCP, whatever. It also makes your domain logic
9
+ forward-compatible with integrations you didn't know you needed or that might not even have
10
+ existed yet when you built your commands.
11
+
12
+ This metadata is also used to help export commands from one system and import them into another
13
+ as remote commands. This means you can write your domain logic and automatically get a Ruby SDK or Typescript SDK for
14
+ free.
15
+
16
+ This also helps with rearchitecting efforts since a remote command has the same interface
17
+ as the local command. Domain logic refactors are not required to relocate domain logic
18
+ in/out of other systems. And this also helps a system communicate and impose its mental model
19
+ on consuming systems.
20
+
21
+ You can use Foobara as a full standalone framework or you can use it with existing code
22
+ as a service-objects layer and leverage the integration features of Foobara when/if necessary.
10
23
 
11
24
  <!-- TOC -->
12
25
  * [Overview of Features/Concepts/Goals](#overview-of-featuresconceptsgoals)
@@ -66,6 +66,7 @@ module Foobara
66
66
 
67
67
  def reset_all
68
68
  Foobara.raise_if_production!("reset_all")
69
+
69
70
  builtin_types.each do |builtin_type|
70
71
  builtin_type.foobara_each do |scoped|
71
72
  if scoped.scoped_namespace == builtin_type
@@ -35,9 +35,7 @@ module Foobara
35
35
  return Outcome.error(
36
36
  build_error(
37
37
  attributes_hash,
38
- message: "Unexpected attributes #{
39
- unexpected_attributes
40
- }. Expected only #{allowed_attributes}",
38
+ message: "Unexpected attributes #{unexpected_attributes}. Expected only #{allowed_attributes}",
41
39
  context: {
42
40
  unexpected_attributes:,
43
41
  allowed_attributes:
@@ -5,10 +5,6 @@ module Foobara
5
5
 
6
6
  module BuiltinTypes
7
7
  class << self
8
- def global_type_declaration_handler_registry
9
- TypeDeclarations.global_type_declaration_handler_registry
10
- end
11
-
12
8
  # TODO: break this up
13
9
  # TODO: much of this behavior is helpful to non-builtin types as well.
14
10
  def build_and_register!(
@@ -158,6 +154,12 @@ module Foobara
158
154
  type
159
155
  end
160
156
 
157
+ private
158
+
159
+ def global_type_declaration_handler_registry
160
+ TypeDeclarations.global_type_declaration_handler_registry
161
+ end
162
+
161
163
  def install_type_declaration_extensions_for(processor_class)
162
164
  extension_module = Util.constant_value(processor_class, :TypeDeclarationExtension)
163
165
 
@@ -2,8 +2,6 @@ module Foobara
2
2
  module BuiltinTypes
3
3
  module Email
4
4
  module Transformers
5
- # Seems like it might be cleaner to just assemble these parts in one place instead of in different files?
6
- # Hard to say.
7
5
  class Downcase < BuiltinTypes::String::SupportedTransformers::Downcase
8
6
  end
9
7
  end
@@ -13,6 +13,8 @@ module Foobara
13
13
  end
14
14
  end
15
15
 
16
+ private
17
+
16
18
  def validate_original_block!
17
19
  super
18
20
 
@@ -59,8 +59,6 @@ module Foobara
59
59
  end
60
60
  end
61
61
  end
62
-
63
- foobara_delegate :type_for_declaration, to: :class
64
62
  end
65
63
  end
66
64
  end
@@ -167,8 +167,6 @@ module Foobara
167
167
  end
168
168
  end
169
169
  end
170
-
171
- foobara_delegate :type_for_declaration, to: :class
172
170
  end
173
171
  end
174
172
  end
@@ -0,0 +1,187 @@
1
+ module Foobara
2
+ class CommandConnector
3
+ module Concerns
4
+ module Reflection
5
+ include Concern
6
+
7
+ def foobara_manifest
8
+ Namespace.use command_registry do
9
+ foobara_manifest_in_current_namespace
10
+ end
11
+ end
12
+
13
+ # TODO: figure out how this is used
14
+ def all_exposed_type_names
15
+ # TODO: cache this or better yet cache #foobara_manifest
16
+ foobara_manifest[:type].keys.sort.map(&:to_s)
17
+ end
18
+
19
+ private
20
+
21
+ # TODO: try to break this giant method up
22
+ def foobara_manifest_in_current_namespace
23
+ process_delayed_connections
24
+
25
+ to_include = Set.new
26
+
27
+ to_include << command_registry.global_organization
28
+ to_include << command_registry.global_domain
29
+
30
+ command_registry.foobara_each_command(
31
+ mode: Namespace::LookupMode::ABSOLUTE_SINGLE_NAMESPACE
32
+ ) do |exposed_command|
33
+ to_include << exposed_command
34
+ end
35
+
36
+ included = Set.new
37
+
38
+ additional_to_include = Set.new
39
+
40
+ h = {
41
+ organization: {},
42
+ domain: {},
43
+ type: {},
44
+ command: {},
45
+ error: {}
46
+ }
47
+
48
+ if TypeDeclarations.include_processors?
49
+ h.merge!(
50
+ processor: {},
51
+ processor_class: {}
52
+ )
53
+ end
54
+
55
+ TypeDeclarations.with_manifest_context(to_include: additional_to_include, remove_sensitive: true) do
56
+ until to_include.empty? && additional_to_include.empty?
57
+ object = nil
58
+
59
+ if to_include.empty?
60
+ until additional_to_include.empty?
61
+ o = additional_to_include.first
62
+ additional_to_include.delete(o)
63
+
64
+ if o.is_a?(::Module)
65
+ if o.foobara_domain? || o.foobara_organization?
66
+ unless o.foobara_root_namespace == command_registry
67
+ next
68
+ end
69
+ elsif o.is_a?(::Class) && o < Foobara::Command
70
+ next
71
+ end
72
+ elsif o.is_a?(Types::Type)
73
+ if o.sensitive?
74
+ # :nocov:
75
+ raise UnexpectedSensitiveTypeInManifestError,
76
+ "Unexpected sensitive type in manifest: #{o.scoped_full_path}. " \
77
+ "Make sure these are not included."
78
+ # :nocov:
79
+ else
80
+
81
+ mode = Namespace::LookupMode::ABSOLUTE_SINGLE_NAMESPACE
82
+ domain_name = o.foobara_domain.scoped_full_name
83
+
84
+ exposed_domain = command_registry.foobara_lookup_domain(domain_name, mode:)
85
+
86
+ exposed_domain ||= command_registry.build_and_register_exposed_domain(domain_name)
87
+
88
+ # Since we don't know which other domains/orgs creating this domain might have created,
89
+ # we will just add them all to be included just in case
90
+ command_registry.foobara_all_domain(mode:).each do |exposed_domain|
91
+ additional_to_include << exposed_domain
92
+ end
93
+
94
+ command_registry.foobara_all_organization(mode:).each do |exposed_organization|
95
+ additional_to_include << exposed_organization
96
+ end
97
+ end
98
+ end
99
+
100
+ object = o
101
+ break
102
+ end
103
+ else
104
+ object = to_include.first
105
+ to_include.delete(object)
106
+ end
107
+
108
+ break unless object
109
+ next if included.include?(object)
110
+
111
+ manifest_reference = object.foobara_manifest_reference.to_sym
112
+
113
+ category_symbol = command_registry.foobara_category_symbol_for(object)
114
+
115
+ unless category_symbol
116
+ # :nocov:
117
+ raise "no category symbol for #{object}"
118
+ # :nocov:
119
+ end
120
+
121
+ namespace = if object.is_a?(Types::Type)
122
+ object.created_in_namespace
123
+ else
124
+ Foobara::Namespace.current
125
+ end
126
+
127
+ # TODO: do we really need to enter the namespace here for this?
128
+ h[category_symbol][manifest_reference] = Foobara::Namespace.use namespace do
129
+ object.foobara_manifest
130
+ end
131
+
132
+ included << object
133
+ end
134
+ end
135
+
136
+ h[:domain].each_value do |domain_manifest|
137
+ # TODO: hack, we need to trim types down to what is actually included in this manifest
138
+ domain_manifest[:types] = domain_manifest[:types].select do |type_name|
139
+ h[:type].key?(type_name.to_sym)
140
+ end
141
+ end
142
+
143
+ h = normalize_manifest(h)
144
+ patch_up_broken_parents_for_errors_with_missing_command_parents(h)
145
+ end
146
+
147
+ def normalize_manifest(manifest_hash)
148
+ manifest_hash.map do |key, entries|
149
+ [key, entries.sort.to_h]
150
+ end.sort.to_h
151
+ end
152
+
153
+ def patch_up_broken_parents_for_errors_with_missing_command_parents(manifest_hash)
154
+ root_manifest = Manifest::RootManifest.new(manifest_hash)
155
+
156
+ error_category = {}
157
+
158
+ root_manifest.errors.each do |error|
159
+ error_manifest = if (error.parent_category == :command || error.parent_category == :organization) &&
160
+ !root_manifest.contains?(error.parent_name, error.parent_category)
161
+ domain = error.domain
162
+ index = domain.scoped_full_path.size
163
+
164
+ fixed_scoped_path = error.scoped_full_path[index..]
165
+ fixed_scoped_name = fixed_scoped_path.join("::")
166
+ fixed_scoped_prefix = fixed_scoped_path[..-2]
167
+ fixed_parent = [:domain, domain.reference]
168
+
169
+ error.relevant_manifest.merge(
170
+ parent: fixed_parent,
171
+ scoped_path: fixed_scoped_path,
172
+ scoped_name: fixed_scoped_name,
173
+ scoped_prefix: fixed_scoped_prefix
174
+ )
175
+ else
176
+ error.relevant_manifest
177
+ end
178
+
179
+ error_category[error.scoped_full_name.to_sym] = error_manifest
180
+ end
181
+
182
+ manifest_hash.merge(error: error_category)
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -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
@@ -495,192 +518,5 @@ module Foobara
495
518
 
496
519
  response
497
520
  end
498
-
499
- def type_from_name(name)
500
- Foobara.foobara_lookup_type(name, mode: Namespace::LookupMode::RELAXED)
501
- end
502
-
503
- def foobara_manifest
504
- Namespace.use command_registry do
505
- foobara_manifest_in_current_namespace
506
- end
507
- end
508
-
509
- # TODO: try to break this giant method up
510
- def foobara_manifest_in_current_namespace
511
- process_delayed_connections
512
-
513
- to_include = Set.new
514
-
515
- to_include << command_registry.global_organization
516
- to_include << command_registry.global_domain
517
-
518
- command_registry.foobara_each_command(mode: Namespace::LookupMode::ABSOLUTE_SINGLE_NAMESPACE) do |exposed_command|
519
- to_include << exposed_command
520
- end
521
-
522
- included = Set.new
523
-
524
- additional_to_include = Set.new
525
-
526
- h = {
527
- organization: {},
528
- domain: {},
529
- type: {},
530
- command: {},
531
- error: {}
532
- }
533
-
534
- if TypeDeclarations.include_processors?
535
- h.merge!(
536
- processor: {},
537
- processor_class: {}
538
- )
539
- end
540
-
541
- TypeDeclarations.with_manifest_context(to_include: additional_to_include, remove_sensitive: true) do
542
- until to_include.empty? && additional_to_include.empty?
543
- object = nil
544
-
545
- if to_include.empty?
546
- until additional_to_include.empty?
547
- o = additional_to_include.first
548
- additional_to_include.delete(o)
549
-
550
- if o.is_a?(::Module)
551
- if o.foobara_domain? || o.foobara_organization?
552
- unless o.foobara_root_namespace == command_registry
553
- next
554
- end
555
- elsif o.is_a?(::Class) && o < Foobara::Command
556
- next
557
- end
558
- elsif o.is_a?(Types::Type)
559
- if o.sensitive?
560
- # :nocov:
561
- raise UnexpectedSensitiveTypeInManifestError,
562
- "Unexpected sensitive type in manifest: #{o.scoped_full_path}. " \
563
- "Make sure these are not included."
564
- # :nocov:
565
- else
566
-
567
- mode = Namespace::LookupMode::ABSOLUTE_SINGLE_NAMESPACE
568
- domain_name = o.foobara_domain.scoped_full_name
569
-
570
- exposed_domain = command_registry.foobara_lookup_domain(domain_name, mode:)
571
-
572
- exposed_domain ||= command_registry.build_and_register_exposed_domain(domain_name)
573
-
574
- # Since we don't know which other domains/orgs creating this domain might have created,
575
- # we will just add them all to be included just in case
576
- command_registry.foobara_all_domain(mode:).each do |exposed_domain|
577
- additional_to_include << exposed_domain
578
- end
579
-
580
- command_registry.foobara_all_organization(mode:).each do |exposed_organization|
581
- additional_to_include << exposed_organization
582
- end
583
- end
584
- end
585
-
586
- object = o
587
- break
588
- end
589
- else
590
- object = to_include.first
591
- to_include.delete(object)
592
- end
593
-
594
- break unless object
595
- next if included.include?(object)
596
-
597
- manifest_reference = object.foobara_manifest_reference.to_sym
598
-
599
- category_symbol = command_registry.foobara_category_symbol_for(object)
600
-
601
- unless category_symbol
602
- # :nocov:
603
- raise "no category symbol for #{object}"
604
- # :nocov:
605
- end
606
-
607
- namespace = if object.is_a?(Types::Type)
608
- object.created_in_namespace
609
- else
610
- Foobara::Namespace.current
611
- end
612
-
613
- # TODO: do we really need to enter the namespace here for this?
614
- h[category_symbol][manifest_reference] = Foobara::Namespace.use namespace do
615
- object.foobara_manifest
616
- end
617
-
618
- included << object
619
- end
620
- end
621
-
622
- h[:domain].each_value do |domain_manifest|
623
- # TODO: hack, we need to trim types down to what is actually included in this manifest
624
- domain_manifest[:types] = domain_manifest[:types].select do |type_name|
625
- h[:type].key?(type_name.to_sym)
626
- end
627
- end
628
-
629
- h = normalize_manifest(h)
630
- patch_up_broken_parents_for_errors_with_missing_command_parents(h)
631
- end
632
-
633
- def normalize_manifest(manifest_hash)
634
- manifest_hash.map do |key, entries|
635
- [key, entries.sort.to_h]
636
- end.sort.to_h
637
- end
638
-
639
- def patch_up_broken_parents_for_errors_with_missing_command_parents(manifest_hash)
640
- root_manifest = Manifest::RootManifest.new(manifest_hash)
641
-
642
- error_category = {}
643
-
644
- root_manifest.errors.each do |error|
645
- error_manifest = if error.parent_category == :command &&
646
- !root_manifest.contains?(error.parent_name, error.parent_category)
647
- domain = error.domain
648
- index = domain.scoped_full_path.size
649
-
650
- fixed_scoped_path = error.scoped_full_path[index..]
651
- fixed_scoped_name = fixed_scoped_path.join("::")
652
- fixed_scoped_prefix = fixed_scoped_path[..-2]
653
- fixed_parent = [:domain, domain.reference]
654
-
655
- error.relevant_manifest.merge(
656
- parent: fixed_parent,
657
- scoped_path: fixed_scoped_path,
658
- scoped_name: fixed_scoped_name,
659
- scoped_prefix: fixed_scoped_prefix
660
- )
661
- else
662
- error.relevant_manifest
663
- end
664
-
665
- error_category[error.scoped_full_name.to_sym] = error_manifest
666
- end
667
-
668
- manifest_hash.merge(error: error_category)
669
- end
670
-
671
- def all_exposed_commands
672
- process_delayed_connections
673
-
674
- command_registry.foobara_all_command(mode: Namespace::LookupMode::ABSOLUTE_SINGLE_NAMESPACE)
675
- end
676
-
677
- def all_exposed_command_names
678
- all_exposed_commands.map(&:full_command_name)
679
- end
680
-
681
- def all_exposed_type_names
682
- # TODO: cache this or better yet cache #foobara_manifest
683
- foobara_manifest[:type].keys.sort.map(&:to_s)
684
- end
685
521
  end
686
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
@@ -425,52 +425,6 @@ module Foobara
425
425
  manifest
426
426
  end
427
427
 
428
- def processors_to_manifest_symbols(processors)
429
- return nil if processors.nil? || processors.empty?
430
-
431
- to_include = TypeDeclarations.foobara_manifest_context_to_include || Set.new
432
- include_processors = TypeDeclarations.include_processors?
433
-
434
- processors.map do |processor|
435
- if processor.respond_to?(:scoped_path_set?) && processor.scoped_path_set?
436
- if include_processors
437
- to_include << processor
438
- end
439
- processor.foobara_manifest_reference
440
- elsif processor.is_a?(Value::Processor)
441
- klass = processor.class
442
-
443
- if klass.scoped_path_set?
444
- if include_processors
445
- to_include << klass
446
- end
447
- klass.foobara_manifest_reference
448
- # TODO: Delete this nocov block
449
- # TODO: make anonymous scoped path's have better names instead of random hexadecimal
450
- # :nocov:
451
- elsif processor.respond_to?(:symbol) && processor.symbol
452
- processor.symbol
453
- else
454
- name = klass.name
455
-
456
- while name.nil?
457
- klass = klass.superclass
458
- name = klass.name
459
- end
460
-
461
- "Anonymous#{Util.non_full_name(name)}"
462
- # :nocov:
463
- end
464
- elsif processor.is_a?(::Proc)
465
- "Proc"
466
- else
467
- # :nocov:
468
- "Unknown"
469
- # :nocov:
470
- end
471
- end
472
- end
473
-
474
428
  def inputs_transformer
475
429
  return @inputs_transformer if defined?(@inputs_transformer)
476
430
 
@@ -581,6 +535,54 @@ module Foobara
581
535
  end
582
536
  end
583
537
  end
538
+
539
+ private
540
+
541
+ def processors_to_manifest_symbols(processors)
542
+ return nil if processors.nil? || processors.empty?
543
+
544
+ to_include = TypeDeclarations.foobara_manifest_context_to_include || Set.new
545
+ include_processors = TypeDeclarations.include_processors?
546
+
547
+ processors.map do |processor|
548
+ if processor.respond_to?(:scoped_path_set?) && processor.scoped_path_set?
549
+ if include_processors
550
+ to_include << processor
551
+ end
552
+ processor.foobara_manifest_reference
553
+ elsif processor.is_a?(Value::Processor)
554
+ klass = processor.class
555
+
556
+ if klass.scoped_path_set?
557
+ if include_processors
558
+ to_include << klass
559
+ end
560
+ klass.foobara_manifest_reference
561
+ # TODO: Delete this nocov block
562
+ # TODO: make anonymous scoped path's have better names instead of random hexadecimal
563
+ # :nocov:
564
+ elsif processor.respond_to?(:symbol) && processor.symbol
565
+ processor.symbol
566
+ else
567
+ name = klass.name
568
+
569
+ while name.nil?
570
+ klass = klass.superclass
571
+ name = klass.name
572
+ end
573
+
574
+ "Anonymous#{Util.non_full_name(name)}"
575
+ # :nocov:
576
+ end
577
+ elsif processor.is_a?(::Proc)
578
+ "Proc"
579
+ else
580
+ # :nocov:
581
+ "Unknown"
582
+ # :nocov:
583
+ end
584
+ end
585
+ end
584
586
  end
585
587
 
586
588
  attr_accessor :command, :untransformed_inputs, :transformed_inputs, :outcome, :request
@@ -626,23 +628,6 @@ module Foobara
626
628
  request.authenticated_credential
627
629
  end
628
630
 
629
- def transform_inputs
630
- transformer = self.class.inputs_transformer
631
-
632
- self.transformed_inputs = if transformer&.applicable?(untransformed_inputs)
633
- outcome = transformer.process_value(untransformed_inputs)
634
-
635
- if outcome.success?
636
- outcome.result
637
- else
638
- self.outcome = outcome
639
- untransformed_inputs
640
- end
641
- else
642
- untransformed_inputs
643
- end
644
- end
645
-
646
631
  def inputs
647
632
  return @inputs if defined?(@inputs)
648
633
 
@@ -727,6 +712,60 @@ module Foobara
727
712
  end
728
713
  end
729
714
 
715
+ def result
716
+ outcome.result
717
+ end
718
+
719
+ def errors
720
+ outcome.errors
721
+ end
722
+
723
+ # TODO: kill this
724
+ def serialize_result(body)
725
+ if serializer
726
+ serializer.process_value!(body)
727
+ else
728
+ body
729
+ end
730
+ end
731
+
732
+ def raw_inputs
733
+ untransformed_inputs
734
+ end
735
+
736
+ def method_missing(method_name, ...)
737
+ if command.respond_to?(method_name)
738
+ command.send(method_name, ...)
739
+ else
740
+ # :nocov:
741
+ super
742
+ # :nocov:
743
+ end
744
+ end
745
+
746
+ def respond_to_missing?(method_name, private = false)
747
+ command.respond_to?(method_name, private) || super
748
+ end
749
+
750
+ private
751
+
752
+ def transform_inputs
753
+ transformer = self.class.inputs_transformer
754
+
755
+ self.transformed_inputs = if transformer&.applicable?(untransformed_inputs)
756
+ outcome = transformer.process_value(untransformed_inputs)
757
+
758
+ if outcome.success?
759
+ outcome.result
760
+ else
761
+ self.outcome = outcome
762
+ untransformed_inputs
763
+ end
764
+ else
765
+ untransformed_inputs
766
+ end
767
+ end
768
+
730
769
  def construct_command
731
770
  self.command = command_class.new(transformed_inputs)
732
771
  end
@@ -804,18 +843,6 @@ module Foobara
804
843
  end
805
844
  end
806
845
 
807
- def result
808
- outcome.result
809
- end
810
-
811
- def errors
812
- outcome.errors
813
- end
814
-
815
- def flush_transactions
816
- request.opened_transactions&.reverse&.each(&:flush!)
817
- end
818
-
819
846
  def transform_outcome
820
847
  if outcome.success?
821
848
  # can we do this while still in the transaction of the command???
@@ -825,31 +852,8 @@ module Foobara
825
852
  end
826
853
  end
827
854
 
828
- # TODO: kill this
829
- def serialize_result(body)
830
- if serializer
831
- serializer.process_value!(body)
832
- else
833
- body
834
- end
835
- end
836
-
837
- def raw_inputs
838
- untransformed_inputs
839
- end
840
-
841
- def method_missing(method_name, ...)
842
- if command.respond_to?(method_name)
843
- command.send(method_name, ...)
844
- else
845
- # :nocov:
846
- super
847
- # :nocov:
848
- end
849
- end
850
-
851
- def respond_to_missing?(method_name, private = false)
852
- command.respond_to?(method_name, private) || super
855
+ def flush_transactions
856
+ request.opened_transactions&.reverse&.each(&:flush!)
853
857
  end
854
858
  end
855
859
  end
@@ -6,6 +6,8 @@ module Foobara
6
6
  load_atoms(object)
7
7
  end
8
8
 
9
+ private
10
+
9
11
  def load_atoms(object)
10
12
  case object
11
13
  when Entity
@@ -14,6 +14,8 @@ module Foobara
14
14
  request
15
15
  end
16
16
 
17
+ private
18
+
17
19
  def load_delegated_attribute_entities(object)
18
20
  case object
19
21
  when Entity
@@ -18,7 +18,7 @@ module Foobara
18
18
  # foobara_primary_key_type (nil if not an entity type)
19
19
  # foobara_associations
20
20
  module AttributeHelpers
21
- include Foobara::Concern
21
+ include Concern
22
22
 
23
23
  module ClassMethods
24
24
  def foobara_has_primary_key?
@@ -46,15 +46,15 @@ module Foobara
46
46
 
47
47
  Namespace.use foobara_attributes_type.created_in_namespace do
48
48
  unless includes_primary_key
49
- declaration = Foobara::TypeDeclarations::Attributes.reject(declaration, foobara_primary_key_attribute)
49
+ declaration = TypeDeclarations::Attributes.reject(declaration, foobara_primary_key_attribute)
50
50
  end
51
51
 
52
52
  unless include_private
53
- declaration = Foobara::TypeDeclarations::Attributes.reject(declaration, *private_attribute_names)
53
+ declaration = TypeDeclarations::Attributes.reject(declaration, *private_attribute_names)
54
54
  end
55
55
 
56
56
  unless include_delegates
57
- declaration = Foobara::TypeDeclarations::Attributes.reject(declaration, *foobara_delegates.keys)
57
+ declaration = TypeDeclarations::Attributes.reject(declaration, *foobara_delegates.keys)
58
58
  end
59
59
 
60
60
  Domain.current.foobara_type_from_declaration(declaration)
@@ -181,7 +181,7 @@ module Foobara
181
181
 
182
182
  if candidates.size > 1
183
183
  # :nocov:
184
- raise AmbiguousNameError,
184
+ raise AmbiguousLookupError,
185
185
  "#{path} is ambiguous. Matches the following: #{candidates.map(&:scoped_full_name)}"
186
186
  # :nocov:
187
187
  end
@@ -30,6 +30,12 @@ module Foobara
30
30
  # Maybe use bitmasks for the above 3 places to look instead of a list of 7 lookup types? (There should be 8...)
31
31
  module LookupMode
32
32
  GENERAL = :general
33
+ # Relaxed will allow you to find registered entries without prefixes as long as it's not ambiguous
34
+ # So if there is a SomeOrg::SomeDomain::SomeCommand `GlobalOrganization.foobara_lookup(:SomeCommand)`
35
+ # will return nil but
36
+ # `GlobalOrganization.foobara_lookup(:SomeCommand, mode: Foobara::Namespace::LookupMode::RELAXED)`
37
+ # will return SomeOrg::SomeDomain::SomeCommand even though we didn't specify the necessary prefixes
38
+ # to navigate to it.
33
39
  RELAXED = :relaxed
34
40
  DIRECT = :direct
35
41
  STRICT = :strict
@@ -1,7 +1,5 @@
1
1
  module Foobara
2
2
  module NestedTransactionable
3
- include Concern
4
-
5
3
  class << self
6
4
  def relevant_entity_classes_for_type(type)
7
5
  entity_classes = []
data/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Foobara
2
2
  module Version
3
- VERSION = "0.2.7".freeze
3
+ VERSION = "0.3.0".freeze
4
4
  MINIMUM_RUBY_VERSION = ">= 3.4.0".freeze
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foobara
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
@@ -190,6 +190,7 @@ files:
190
190
  - projects/command_connectors/src/command_connector/commands/ping.rb
191
191
  - projects/command_connectors/src/command_connector/commands/query_git_commit_info.rb
192
192
  - projects/command_connectors/src/command_connector/concerns/desugarizers.rb
193
+ - projects/command_connectors/src/command_connector/concerns/reflection.rb
193
194
  - projects/command_connectors/src/command_connector/invalid_context_error.rb
194
195
  - projects/command_connectors/src/command_connector/no_command_found_error.rb
195
196
  - projects/command_connectors/src/command_connector/no_command_or_type_found_error.rb