foobara 0.0.86 → 0.0.88

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 (25) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/projects/builtin_types/src/email/transformers/downcase.rb +0 -3
  4. data/projects/builtin_types/src/email/validator_base.rb +5 -4
  5. data/projects/builtin_types/src/string/supported_transformers/downcase.rb +5 -1
  6. data/projects/command/src/transformed_command.rb +146 -80
  7. data/projects/command_connectors/src/command_connector/response.rb +5 -5
  8. data/projects/command_connectors/src/command_connector.rb +51 -13
  9. data/projects/command_connectors/src/command_registry/exposed_command.rb +5 -0
  10. data/projects/command_connectors/src/response_mutator.rb +24 -0
  11. data/projects/command_connectors/src/serializers/errors_serializer.rb +2 -2
  12. data/projects/detached_entity/src/concerns/associations.rb +25 -2
  13. data/projects/domain/src/domain_module_extension.rb +6 -2
  14. data/projects/entity/src/sensitive_value_removers/entity.rb +20 -4
  15. data/projects/model/src/extensions/type_declarations/handlers/extend_registered_model_type_declaration.rb +1 -1
  16. data/projects/model/src/sensitive_value_removers/model.rb +3 -5
  17. data/projects/type_declarations/src/attributes_transformers/only.rb +32 -0
  18. data/projects/type_declarations/src/attributes_transformers/reject.rb +32 -0
  19. data/projects/type_declarations/src/handlers/registered_type_declaration/to_type_transformer.rb +1 -1
  20. data/projects/type_declarations/src/remove_sensitive_values_transformer.rb +16 -17
  21. data/projects/type_declarations/src/typed_transformer.rb +83 -69
  22. data/projects/type_declarations/src/with_registries.rb +0 -4
  23. data/projects/value/src/mutator.rb +29 -0
  24. metadata +14 -11
  25. data/projects/type_declarations/src/attributes_transformer.rb +0 -33
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e400689de8a729a2ed91fc3fd0da0e447e4d773ece5a6efc42e4ee555eb665ef
4
- data.tar.gz: 29f503d3d3263ad637add42716b757aba79688f98871a71cf9d011c72af28bd9
3
+ metadata.gz: 5abb6c21d367251512e93a28585ca52fbef92cbea5bd4b6f102f693ae7e42969
4
+ data.tar.gz: 2219f04cbb8cd7fa1b4f296fe229416cb344fd30bdd4fe529cb619b820a45f03
5
5
  SHA512:
6
- metadata.gz: 35490b924e4f50e2752936b26bbafd25e42b88c8be4a404e006befe58fb9b2b60be54eb54f039f4d5b2a455b409e256b5cbc8bc8496cc1a9e9bf94b52e17330f
7
- data.tar.gz: 51745cf6ea9e92c93e6c2a944073232cb07126f35c6f0d10574b56f5b46ae2f91aef909008ab09b9d6d49711a9dc31c0b3ef2a74dc1b0cac4fbb1efe26ecf84b
6
+ metadata.gz: df26075b6ad2825133cd77b08e6c5813756fe130244b4d21ca0c312a734380c40668ce5a066e102a2b321a66f08ddf51a9a2499af1c243a603db40c3057aa60a
7
+ data.tar.gz: 8c4ef51ae4cb5fc93c31e3b739f0b919c91cad78944cbeeea25d324f607020aff47ff7ae8166e24fd17dc9aff61dab9962b29833d505e77f1833a8af40a56c1e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ # [0.0.88] - 2025-03-28
2
+
3
+ - Implement response mutator concept
4
+ - Break up #request_to_response for easier overriding/extension
5
+ - Add AttributesTransformers::Reject
6
+ - Fix problem causing downcase/regex processors to explode on allows_nil types
7
+
8
+ # [0.0.87] - 2025-03-26
9
+
10
+ - TypedTransformer refactor to reduce confusion and bugs
11
+
1
12
  # [0.0.86] - 2025-03-23
2
13
 
3
14
  - Add an AttributesTransformer.only method to quickly get a TypedTransformer (helpful with inputs_transformers)
@@ -5,9 +5,6 @@ module Foobara
5
5
  # Seems like it might be cleaner to just assemble these parts in one place instead of in different files?
6
6
  # Hard to say.
7
7
  class Downcase < BuiltinTypes::String::SupportedTransformers::Downcase
8
- def always_applicable?
9
- true
10
- end
11
8
  end
12
9
  end
13
10
  end
@@ -27,10 +27,6 @@ module Foobara
27
27
  end
28
28
  end
29
29
 
30
- def always_applicable?
31
- true
32
- end
33
-
34
30
  def regex
35
31
  # :nocov:
36
32
  raise "subclass responsibility"
@@ -76,6 +72,11 @@ module Foobara
76
72
  class_name = "#{name}::#{Util.classify(symbol)}"
77
73
 
78
74
  Util.make_class(class_name, ValidatorBase) do
75
+ define_method :applicable? do |value|
76
+ # TODO: hmmm, I wonder how we can short-circuit these checks if :allows_nil matches??
77
+ value.is_a?(::String)
78
+ end
79
+
79
80
  define_method :regex do
80
81
  regex
81
82
  end
@@ -3,7 +3,11 @@ module Foobara
3
3
  # TODO: Rename to StringType to avoid needing to remember ::String elsewhere in the code
4
4
  module String
5
5
  module SupportedTransformers
6
- class Downcase < Value::Transformer.subclass(transform: :downcase.to_proc, name: "Downcase")
6
+ class Downcase < Value::Transformer.subclass(
7
+ transform: :downcase.to_proc,
8
+ name: "Downcase",
9
+ applicable?: ->(value) { value.respond_to?(:downcase) }
10
+ )
7
11
  end
8
12
  end
9
13
  end
@@ -13,6 +13,10 @@ module Foobara
13
13
  :pre_commit_transformers,
14
14
  # TODO: get at least these serializers out of here...
15
15
  :serializers,
16
+ # TODO: probably should also get these mutators out of here. But where should they live?
17
+ # On exposed command? On the command registry? On the command connector?
18
+ # This is the easiest place to implement them but feels awkward.
19
+ :response_mutators,
16
20
  :allowed_rule,
17
21
  :requires_authentication,
18
22
  :authenticator
@@ -27,6 +31,7 @@ module Foobara
27
31
  errors_transformers:,
28
32
  pre_commit_transformers:,
29
33
  serializers:,
34
+ response_mutators:,
30
35
  allowed_rule:,
31
36
  requires_authentication:,
32
37
  authenticator:,
@@ -40,8 +45,7 @@ module Foobara
40
45
 
41
46
  remover = Namespace.use scoped_namespace do
42
47
  transformed_result_type = result_type_from_transformers(result_type, result_transformers)
43
-
44
- remover_class.new(transformed_result_type).tap do |r|
48
+ remover_class.new(from: transformed_result_type).tap do |r|
45
49
  r.scoped_path = ["SensitiveValueRemover", *transformed_result_type.scoped_full_path]
46
50
  end
47
51
  end
@@ -59,6 +63,7 @@ module Foobara
59
63
  klass.errors_transformers = Util.array(errors_transformers)
60
64
  klass.pre_commit_transformers = Util.array(pre_commit_transformers)
61
65
  klass.serializers = Util.array(serializers)
66
+ klass.response_mutators = Util.array(response_mutators)
62
67
  klass.allowed_rule = allowed_rule
63
68
  klass.requires_authentication = requires_authentication
64
69
  klass.authenticator = authenticator
@@ -71,25 +76,34 @@ module Foobara
71
76
  to: :command_class
72
77
 
73
78
  def inputs_type
74
- type = command_class.inputs_type
75
-
76
- inputs_transformers.reverse.each do |transformer|
77
- if transformer.is_a?(Class) && transformer < TypeDeclarations::TypedTransformer
78
- new_type = transformer.type(type)
79
-
80
- type = new_type if new_type
81
- end
82
- end
83
-
84
- type
79
+ return @inputs_type if defined?(@inputs_type)
80
+
81
+ @inputs_type = if inputs_transformer
82
+ if inputs_transformer.is_a?(Value::Processor::Pipeline)
83
+ inputs_transformer.processors.each do |transformer|
84
+ if transformer.is_a?(TypeDeclarations::TypedTransformer)
85
+ from_type = transformer.from_type
86
+ if from_type
87
+ @inputs_type = from_type
88
+ return from_type
89
+ end
90
+ end
91
+ end
92
+
93
+ command_class.inputs_type
94
+ else
95
+ inputs_transformer.from_type || command_class.inputs_type
96
+ end
97
+ else
98
+ command_class.inputs_type
99
+ end
85
100
  end
86
101
 
87
102
  def result_type_from_transformers(result_type, transformers)
88
- Util.array(transformers).each do |transformer|
103
+ transformers.reverse.each do |transformer|
89
104
  if transformer.is_a?(Class) && transformer < TypeDeclarations::TypedTransformer
90
- new_type = transformer.type(result_type)
91
-
92
- result_type = new_type if new_type
105
+ new_type = transformer.to_type
106
+ return new_type if new_type
93
107
  end
94
108
  end
95
109
 
@@ -100,6 +114,18 @@ module Foobara
100
114
  result_type_from_transformers(command_class.result_type, result_transformers)
101
115
  end
102
116
 
117
+ def result_type_for_manifest
118
+ return @result_type_for_manifest if defined?(@result_type_for_manifest)
119
+
120
+ mutated_result_type = result_type
121
+
122
+ response_mutators&.reverse&.each do |mutator|
123
+ mutated_result_type = mutator.instance.result_type_from(mutated_result_type)
124
+ end
125
+
126
+ @result_type_for_manifest = mutated_result_type
127
+ end
128
+
103
129
  def error_context_type_map
104
130
  @error_context_type_map ||= begin
105
131
  set = {}
@@ -199,12 +225,13 @@ module Foobara
199
225
  end
200
226
  end
201
227
 
228
+ response_mutators = self.response_mutators.map { |t| t.foobara_manifest(to_include:) }
229
+
202
230
  command_class.foobara_manifest(to_include:, remove_sensitive:).merge(
203
231
  Util.remove_blank(
204
232
  types_depended_on: types,
205
233
  inputs_type: inputs_type&.reference_or_declaration_data,
206
- # TODO: we need a way to unmask values in the result type that we want to expose
207
- result_type: result_type&.reference_or_declaration_data(remove_sensitive:),
234
+ result_type: result_type_for_manifest&.reference_or_declaration_data(remove_sensitive:),
208
235
  possible_errors: possible_errors_manifest(to_include:, remove_sensitive:),
209
236
  capture_unknown_error:,
210
237
  inputs_transformers:,
@@ -212,11 +239,94 @@ module Foobara
212
239
  errors_transformers:,
213
240
  pre_commit_transformers:,
214
241
  serializers:,
242
+ response_mutators:,
215
243
  requires_authentication:,
216
244
  authenticator: authenticator&.manifest
217
245
  )
218
246
  )
219
247
  end
248
+
249
+ def inputs_transformer
250
+ return @inputs_transformer if defined?(@inputs_transformer)
251
+
252
+ if inputs_transformers.empty?
253
+ @inputs_transformer = nil
254
+ return
255
+ end
256
+
257
+ @inputs_transformer = begin
258
+ transformers = transformers_to_processors(inputs_transformers,
259
+ command_class.inputs_type, direction: :to)
260
+
261
+ if transformers.size == 1
262
+ transformers.first
263
+ else
264
+ Value::Processor::Pipeline.new(processors: transformers)
265
+ end
266
+ end
267
+ end
268
+
269
+ def response_mutator
270
+ return @response_mutator if defined?(@response_mutator)
271
+
272
+ if response_mutators.empty?
273
+ @response_mutator = nil
274
+ return
275
+ end
276
+
277
+ @response_mutator = begin
278
+ transformers = transformers_to_processors(response_mutators, result_type, direction: :from)
279
+
280
+ if transformers.size == 1
281
+ transformers.first
282
+ else
283
+ Value::Processor::Pipeline.new(processors: transformers)
284
+ end
285
+ end
286
+ end
287
+
288
+ def result_transformer
289
+ return @result_transformer if defined?(@result_transformer)
290
+
291
+ if result_transformers.empty?
292
+ @result_transformer = nil
293
+ return
294
+ end
295
+
296
+ @result_transformer = begin
297
+ transformers = transformers_to_processors(result_transformers, command_class.result_type, direction: :from)
298
+
299
+ if transformers.size == 1
300
+ transformers.first
301
+ else
302
+ Value::Processor::Pipeline.new(processors: transformers)
303
+ end
304
+ end
305
+ end
306
+
307
+ # TODO: this is pretty messy with smells.
308
+ def transformers_to_processors(transformers, target_type, direction: :from, declaration_data: self)
309
+ transformers.map do |transformer|
310
+ if transformer.is_a?(Class)
311
+ if transformer < TypeDeclarations::TypedTransformer
312
+ transformer.new(direction => target_type).tap do |tx|
313
+ new_type = direction == :from ? tx.to_type : tx.from_type
314
+ target_type = new_type if new_type
315
+ end
316
+ else
317
+ transformer.new(declaration_data)
318
+ end
319
+ elsif transformer.is_a?(Value::Processor)
320
+ transformer
321
+ elsif transformer.respond_to?(:call)
322
+ Value::Transformer.create(transform: transformer)
323
+ else
324
+ # :nocov:
325
+ raise "Not sure how to apply #{inputs_transformer}"
326
+ # :nocov:
327
+ end
328
+ end
329
+ end
220
330
  end
221
331
 
222
332
  attr_accessor :command, :untransformed_inputs, :transformed_inputs, :outcome, :authenticated_user
@@ -259,49 +369,29 @@ module Foobara
259
369
  end
260
370
 
261
371
  def transform_inputs
262
- self.transformed_inputs = if inputs_transformer
263
- inputs_transformer.process_value!(untransformed_inputs)
372
+ self.transformed_inputs = if self.class.inputs_transformer
373
+ self.class.inputs_transformer.process_value!(untransformed_inputs)
264
374
  else
265
375
  untransformed_inputs
266
376
  end
267
377
  end
268
378
 
269
379
  def transform_result
270
- if result_transformer
271
- self.outcome = Outcome.success(result_transformer.process_value!(result))
380
+ if self.class.result_transformer
381
+ self.outcome = Outcome.success(self.class.result_transformer.process_value!(result))
272
382
  end
273
383
  end
274
384
 
385
+ def mutate_response(response)
386
+ self.class.response_mutator&.process_value!(response)
387
+ end
388
+
275
389
  def transform_errors
276
390
  if errors_transformer
277
391
  self.outcome = Outcome.errors(errors_transformer.process_value!(errors))
278
392
  end
279
393
  end
280
394
 
281
- def inputs_transformer
282
- return nil if inputs_transformers.empty?
283
-
284
- transformers = transformers_to_processors(inputs_transformers, command_class.inputs_type)
285
-
286
- if transformers.size == 1
287
- transformers.first
288
- else
289
- Value::Processor::Pipeline.new(processors: transformers)
290
- end
291
- end
292
-
293
- def result_transformer
294
- return nil if result_transformers.empty?
295
-
296
- transformers = transformers_to_processors(result_transformers, command_class.result_type)
297
-
298
- if transformers.size == 1
299
- transformers.first
300
- else
301
- Value::Processor::Pipeline.new(processors: transformers)
302
- end
303
- end
304
-
305
395
  # TODO: let's get this out of here...
306
396
  # we might want to have different serializers for different command instances of the same class.
307
397
  # but currently serializers is set on the class. Since this class should not be concerned with serialization, we
@@ -309,7 +399,7 @@ module Foobara
309
399
  def serializer
310
400
  return nil if serializers.empty?
311
401
 
312
- transformers = transformers_to_processors(serializers, nil)
402
+ transformers = self.class.transformers_to_processors(serializers, nil, declaration_data: self)
313
403
 
314
404
  if transformers.size == 1
315
405
  transformers.first
@@ -321,7 +411,8 @@ module Foobara
321
411
  def errors_transformer
322
412
  return nil if errors_transformers.empty?
323
413
 
324
- transformers = transformers_to_processors(errors_transformers, nil)
414
+ transformers = self.class.transformers_to_processors(errors_transformers, nil, direction: :from,
415
+ declaration_data: self)
325
416
 
326
417
  if transformers.size == 1
327
418
  transformers.first
@@ -334,7 +425,11 @@ module Foobara
334
425
  def pre_commit_transformer
335
426
  return nil if pre_commit_transformers.empty?
336
427
 
337
- transformers = transformers_to_processors(pre_commit_transformers, nil)
428
+ transformers = self.class.transformers_to_processors(
429
+ pre_commit_transformers,
430
+ nil,
431
+ declaration_data: self
432
+ )
338
433
 
339
434
  if transformers.size == 1
340
435
  transformers.first
@@ -343,29 +438,6 @@ module Foobara
343
438
  end
344
439
  end
345
440
 
346
- def transformers_to_processors(transformers, from_type)
347
- transformers.map do |transformer|
348
- if transformer.is_a?(Class)
349
- if transformer < TypeDeclarations::TypedTransformer
350
- transformer.new(from_type).tap do |tx|
351
- new_type = tx.type
352
- from_type = new_type if new_type
353
- end
354
- else
355
- transformer.new(self)
356
- end
357
- elsif transformer.is_a?(Value::Processor)
358
- transformer
359
- elsif transformer.respond_to?(:call)
360
- Value::Transformer.create(transform: transformer)
361
- else
362
- # :nocov:
363
- raise "Not sure how to apply #{inputs_transformer}"
364
- # :nocov:
365
- end
366
- end
367
- end
368
-
369
441
  def construct_command
370
442
  self.command = command_class.new(transformed_inputs)
371
443
  end
@@ -453,13 +525,7 @@ module Foobara
453
525
  end
454
526
 
455
527
  # TODO: kill this
456
- def serialize_result
457
- body = if outcome.success?
458
- outcome.result
459
- else
460
- outcome.errors
461
- end
462
-
528
+ def serialize_result(body)
463
529
  if serializer
464
530
  serializer.process_value!(body)
465
531
  else
@@ -1,14 +1,14 @@
1
1
  module Foobara
2
2
  class CommandConnector
3
3
  class Response
4
- attr_accessor :status,
5
- :body,
6
- :request
4
+ attr_accessor :request,
5
+ :status,
6
+ :body
7
7
 
8
- def initialize(status:, body:, request:)
8
+ def initialize(request:, status: nil, body: nil)
9
+ self.request = request
9
10
  self.status = status
10
11
  self.body = body
11
- self.request = request
12
12
  end
13
13
 
14
14
  foobara_delegate :command, :error, to: :request
@@ -95,6 +95,7 @@ module Foobara
95
95
  :add_default_errors_transformer,
96
96
  :add_default_pre_commit_transformer,
97
97
  :add_default_serializer,
98
+ :add_default_response_mutator,
98
99
  :allowed_rule,
99
100
  :allowed_rules,
100
101
  :transform_command_class,
@@ -232,16 +233,34 @@ module Foobara
232
233
  end
233
234
 
234
235
  def request_to_response(request)
235
- command = request.command
236
- outcome = command.outcome
236
+ self.class::Response.new(request:)
237
+ end
238
+
239
+ def set_response_status(response)
240
+ outcome = response.request.outcome
241
+ response.status = outcome.success? ? 0 : 1
242
+ end
237
243
 
238
- status = outcome.success? ? 0 : 1
244
+ def set_response_body(response)
245
+ outcome = response.request.outcome
246
+ # response.body = outcome.success? ? outcome.result : outcome.error_collection
247
+ response.body = outcome.success? ? outcome.result : outcome.error_collection
248
+ end
239
249
 
240
- # TODO: feels awkward to call this here... Maybe use result/errors transformers instead??
241
- # Or call the serializer here??
242
- body = command.respond_to?(:serialize_result) ? command.serialize_result : request.response_body
250
+ def mutate_response(response)
251
+ command = response.command
243
252
 
244
- self.class::Response.new(request:, status:, body:)
253
+ if command.respond_to?(:mutate_response)
254
+ command.mutate_response(response)
255
+ end
256
+ end
257
+
258
+ def serialize_response_body(response)
259
+ command = response.command
260
+
261
+ if command.respond_to?(:serialize_result)
262
+ response.body = command.serialize_result(response.body)
263
+ end
245
264
  end
246
265
 
247
266
  def initialize(authenticator: nil, default_serializers: nil)
@@ -343,7 +362,12 @@ module Foobara
343
362
 
344
363
  def build_response(request)
345
364
  response = request_to_response(request)
346
- response.request = request
365
+
366
+ set_response_status(response)
367
+ set_response_body(response)
368
+ mutate_response(response)
369
+ serialize_response_body(response)
370
+
347
371
  response
348
372
  end
349
373
 
@@ -387,14 +411,28 @@ module Foobara
387
411
  additional_to_include.delete(o)
388
412
 
389
413
  if o.is_a?(::Module)
390
- if o.foobara_domain? || o.foobara_organization? || (o.is_a?(::Class) && o < Foobara::Command)
414
+ if o.foobara_domain? || o.foobara_organization?
415
+ unless o.foobara_root_namespace == command_registry
416
+ next
417
+ end
418
+ elsif o.is_a?(::Class) && o < Foobara::Command
391
419
  next
392
420
  end
393
- elsif o.is_a?(Types::Type) && remove_sensitive && o.sensitive?
394
- # :nocov:
395
- raise UnexpectedSensitiveTypeInManifestError,
396
- "Unexpected sensitive type in manifest: #{o.scoped_full_path}. Make sure these are not included."
421
+ elsif o.is_a?(Types::Type)
422
+ if remove_sensitive && o.sensitive?
423
+ # :nocov:
424
+ raise UnexpectedSensitiveTypeInManifestError,
425
+ "Unexpected sensitive type in manifest: #{o.scoped_full_path}. Make sure these are not included."
397
426
  # :nocov:
427
+ else
428
+ domain_name = o.foobara_domain.scoped_full_name
429
+
430
+ unless command_registry.foobara_registered?(domain_name)
431
+ domain = command_registry.build_and_register_exposed_domain(domain_name)
432
+ additional_to_include << domain
433
+ additional_to_include << domain.foobara_organization
434
+ end
435
+ end
398
436
  end
399
437
 
400
438
  object = o
@@ -12,6 +12,7 @@ module Foobara
12
12
  :errors_transformers,
13
13
  :pre_commit_transformers,
14
14
  :serializers,
15
+ :response_mutators,
15
16
  :allowed_rule,
16
17
  :requires_authentication,
17
18
  :authenticator,
@@ -27,6 +28,7 @@ module Foobara
27
28
  result_transformers: nil,
28
29
  errors_transformers: nil,
29
30
  pre_commit_transformers: nil,
31
+ response_mutators: nil,
30
32
  serializers: nil,
31
33
  allowed_rule: nil,
32
34
  requires_authentication: nil,
@@ -76,6 +78,7 @@ module Foobara
76
78
  self.result_transformers = result_transformers
77
79
  self.errors_transformers = errors_transformers
78
80
  self.pre_commit_transformers = pre_commit_transformers
81
+ self.response_mutators = response_mutators
79
82
  self.serializers = serializers
80
83
  self.allowed_rule = allowed_rule
81
84
  self.requires_authentication = requires_authentication
@@ -111,6 +114,7 @@ module Foobara
111
114
  result_transformers,
112
115
  errors_transformers,
113
116
  pre_commit_transformers,
117
+ response_mutators,
114
118
  serializers,
115
119
  allowed_rule,
116
120
  requires_authentication,
@@ -130,6 +134,7 @@ module Foobara
130
134
  result_transformers:,
131
135
  errors_transformers:,
132
136
  pre_commit_transformers:,
137
+ response_mutators:,
133
138
  serializers:,
134
139
  allowed_rule:,
135
140
  requires_authentication:,
@@ -0,0 +1,24 @@
1
+ module Foobara
2
+ module CommandConnectors
3
+ class ResponseMutator < Foobara::Value::Mutator
4
+ def result_type_declaration_from(_result_type)
5
+ # :nocov:
6
+ raise "subclass responsibility"
7
+ # :nocov:
8
+ end
9
+
10
+ def result_type_from(result_type)
11
+ declaration = result_type_declaration_from(result_type)
12
+ Domain.current.foobara_type_from_declaration(declaration)
13
+ end
14
+
15
+ def mutate
16
+ # :nocov:
17
+ raise "subclass responsibility"
18
+ # :nocov:
19
+ end
20
+
21
+ alias response declaration_data
22
+ end
23
+ end
24
+ end
@@ -4,12 +4,12 @@ module Foobara
4
4
  module CommandConnectors
5
5
  module Serializers
6
6
  class ErrorsSerializer < Serializer
7
- # TODO: always_applicable? instead?
8
7
  def always_applicable?
9
8
  !request.outcome.success?
10
9
  end
11
10
 
12
- def serialize(errors)
11
+ def serialize(error_collection)
12
+ errors = error_collection.errors
13
13
  errors.map(&:to_h)
14
14
  end
15
15
  end
@@ -123,6 +123,29 @@ module Foobara
123
123
  end
124
124
  end
125
125
 
126
+ def construct_deep_associations(
127
+ type = attributes_type,
128
+ path = DataPath.new,
129
+ result = {},
130
+ remove_sensitive: false
131
+ )
132
+ associations = construct_associations(type, path, result, remove_sensitive:)
133
+
134
+ deep = {}
135
+
136
+ associations.each_pair do |data_path, association_type|
137
+ deep[data_path] = association_type
138
+
139
+ entity_class = association_type.target_class
140
+
141
+ entity_class.deep_associations(remove_sensitive:).each_pair do |sub_data_path, sub_type|
142
+ deep["#{data_path}.#{sub_data_path}"] = sub_type
143
+ end
144
+ end
145
+
146
+ deep
147
+ end
148
+
126
149
  # TODO: this big switch is a problem. Hard to create new types in other projects without being able
127
150
  # to modify this switch. Figure out what to do.
128
151
  def construct_associations(
@@ -224,8 +247,8 @@ module Foobara
224
247
  types = types&.reject(&:sensitive?)
225
248
  end
226
249
 
227
- types.any? do |type|
228
- contains_associations?(type, false, remove_sensitive:)
250
+ types.any? do |key_or_value_type|
251
+ contains_associations?(key_or_value_type, false, remove_sensitive:)
229
252
  end
230
253
  end
231
254
  end
@@ -231,9 +231,13 @@ module Foobara
231
231
  end
232
232
  end
233
233
 
234
- def foobara_type_from_declaration(...)
234
+ def foobara_type_from_declaration(*args, **opts, &block)
235
+ if opts.empty? && block.nil? && args.size == 1 && args.first.is_a?(Types::Type)
236
+ return args.first
237
+ end
238
+
235
239
  Foobara::Namespace.use self do
236
- foobara_type_builder.type_for_declaration(...)
240
+ foobara_type_builder.type_for_declaration(*args, **opts, &block)
237
241
  end
238
242
  end
239
243
 
@@ -3,12 +3,28 @@ module Foobara
3
3
  module SensitiveValueRemovers
4
4
  class Entity < DetachedEntity::SensitiveValueRemovers::DetachedEntity
5
5
  def transform(record)
6
- sanitized_record = super
6
+ if record.loaded? || record.created?
7
+ sanitized_record = super
7
8
 
8
- sanitized_record.is_loaded = record.loaded?
9
- sanitized_record.is_persisted = record.persisted?
9
+ sanitized_record.is_loaded = record.loaded?
10
+ sanitized_record.is_persisted = record.persisted?
10
11
 
11
- sanitized_record
12
+ sanitized_record
13
+ elsif record.persisted?
14
+ # We will assume that we do not need to clean up the primary key itself as
15
+ # we will assume we don't allow sensitive primary keys for now.
16
+ sanitized_record = to_type.target_class.build(record.class.primary_key_attribute => record.primary_key)
17
+
18
+ sanitized_record.is_persisted = true
19
+ sanitized_record.is_loaded = false
20
+ sanitized_record.is_built = false
21
+
22
+ sanitized_record
23
+ else
24
+ # :nocov:
25
+ raise "Not sure what to do with a record that isn't loaded, created, or persisted"
26
+ # :nocov:
27
+ end
12
28
  end
13
29
 
14
30
  def build_method
@@ -12,7 +12,7 @@ module Foobara
12
12
  return false if type_symbol == expected_type_symbol
13
13
 
14
14
  if type_registered?(type_symbol)
15
- type = type_for_declaration(type_symbol)
15
+ type = lookup_type!(type_symbol)
16
16
  type.extends?(BuiltinTypes[expected_type_symbol])
17
17
  end
18
18
  end
@@ -5,12 +5,10 @@ module Foobara
5
5
  def transform(record)
6
6
  attributes_type = from_type.element_types
7
7
 
8
- sanitized_attributes, changed = sanitize_value(attributes_type, record.attributes)
8
+ sanitized_attributes, _changed = sanitize_value(attributes_type, record.attributes)
9
9
 
10
- if changed
11
- type.target_class.send(build_method, sanitized_attributes)
12
- else
13
- record
10
+ Namespace.use(to_type.created_in_namespace) do
11
+ to_type.target_class.send(build_method, sanitized_attributes)
14
12
  end
15
13
  end
16
14
 
@@ -0,0 +1,32 @@
1
+ module Foobara
2
+ class AttributesTransformers < TypeDeclarations::TypedTransformer
3
+ class << self
4
+ def only(*attribute_names)
5
+ transformer_class = Class.new(Only)
6
+ transformer_class.only_attributes = attribute_names
7
+
8
+ transformer_class
9
+ end
10
+ end
11
+
12
+ class Only < AttributesTransformers
13
+ class << self
14
+ attr_accessor :only_attributes
15
+ end
16
+
17
+ def to_type_declaration
18
+ from_declaration = from_type.declaration_data
19
+ TypeDeclarations::Attributes.only(from_declaration, *self.class.only_attributes)
20
+ end
21
+
22
+ def transform(inputs)
23
+ inputs = Util.symbolize_keys(inputs)
24
+ inputs.slice(*allowed_keys)
25
+ end
26
+
27
+ def allowed_keys
28
+ to_type.element_types.keys
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ module Foobara
2
+ class AttributesTransformers < TypeDeclarations::TypedTransformer
3
+ class << self
4
+ def reject(*attribute_names)
5
+ transformer_class = Class.new(Reject)
6
+ transformer_class.reject_attributes = attribute_names
7
+
8
+ transformer_class
9
+ end
10
+ end
11
+
12
+ class Reject < AttributesTransformers
13
+ class << self
14
+ attr_accessor :reject_attributes
15
+ end
16
+
17
+ def to_type_declaration
18
+ from_declaration = from_type.declaration_data
19
+ TypeDeclarations::Attributes.reject(from_declaration, *self.class.reject_attributes)
20
+ end
21
+
22
+ def transform(inputs)
23
+ inputs = Util.symbolize_keys(inputs)
24
+ inputs.slice(*allowed_keys)
25
+ end
26
+
27
+ def allowed_keys
28
+ to_type.element_types.keys
29
+ end
30
+ end
31
+ end
32
+ end
@@ -15,7 +15,7 @@ module Foobara
15
15
  end
16
16
 
17
17
  def registered_type(strict_type_declaration)
18
- lookup_absolute_type!(type_symbol(strict_type_declaration), mode: Namespace::LookupMode::ABSOLUTE)
18
+ lookup_type!(type_symbol(strict_type_declaration))
19
19
  end
20
20
 
21
21
  def target_classes(strict_type_declaration)
@@ -3,24 +3,21 @@ require_relative "typed_transformer"
3
3
  module Foobara
4
4
  module TypeDeclarations
5
5
  class RemoveSensitiveValuesTransformer < TypedTransformer
6
- class << self
7
- def type_declaration(type_declaration)
8
- if type_declaration.is_a?(Types::Type)
9
- type_declaration = type_declaration.declaration_data
10
- end
6
+ def from(...)
7
+ super.tap do
8
+ associations = Foobara::DetachedEntity.construct_deep_associations(from_type)
9
+
10
+ associations&.values&.reverse&.each do |entity_type|
11
+ declaration = entity_type.declaration_data
12
+ sanitized_type_declaration = TypeDeclarations.remove_sensitive_types(declaration)
11
13
 
12
- TypeDeclarations.remove_sensitive_types(type_declaration)
14
+ Domain.current.foobara_type_from_declaration(sanitized_type_declaration)
15
+ end
13
16
  end
14
17
  end
15
18
 
16
- attr_accessor :namespace
17
-
18
- def initialize(...)
19
- super
20
-
21
- self.namespace = Namespace.current
22
-
23
- type
19
+ def to_type_declaration
20
+ TypeDeclarations.remove_sensitive_types(from_type.declaration_data)
24
21
  end
25
22
 
26
23
  def transform(_value)
@@ -31,9 +28,11 @@ module Foobara
31
28
 
32
29
  def sanitize_value(type, value)
33
30
  if type.has_sensitive_types?
34
- remover_class = TypeDeclarations.sensitive_value_remover_class_for_type(type)
35
- remover = Namespace.use(namespace) { remover_class.new(type) }
36
- sanitized_value = remover.process_value!(value)
31
+ sanitized_value = Namespace.use to_type.created_in_namespace do
32
+ remover_class = TypeDeclarations.sensitive_value_remover_class_for_type(type)
33
+ remover = remover_class.new(from: type)
34
+ remover.process_value!(value)
35
+ end
37
36
 
38
37
  [sanitized_value, sanitized_value != value]
39
38
  else
@@ -3,87 +3,101 @@ module Foobara
3
3
  # TODO: this should instead be a processor and have its own possible_errors
4
4
  class TypedTransformer < Value::Transformer
5
5
  class << self
6
- # A,B,C,D
7
- # let's say this transformer is C...
8
- # If we are a noop, then we are going to output D.type and we expect B.type
9
- # We obviously have a problem if D is incompatible with our output type.
10
- # We need to know B.output_type in order to say what we are going to output.
11
- #
12
- # Conversely, we need to know what D expects in order to say what we expect to receive (sometimes)
13
- #
14
- # So logic is... For C to say what its type is, it must know B's output_type.
15
- # or... I guess for an inputs transformer, we need to know what D expects as its type, right?
16
- # since we have an obligation be compatible with it.
17
- #
18
- # Use case 1: command line interface gets awkward with models
19
- # 1. Command takes model A
20
- # 2. we want an inputs transformer that takes A.attributes_type
21
- # 3. Therefore its type is A.attributes_type
22
- # 4. And also, its output type is A.attributes_type in this case since there's no need to actually create the
23
- # models.
24
- # 5. So to tell our type, we must know the type of what comes next.
25
- #
26
- # Use case 2:
27
- # 1. Command takes foo: :integer but we want to take bar: :string
28
- # 2. transformer has this hard-coded knowledge.
29
- # 3. we don't need to receive either types to answer our input and output types.
30
- #
31
- # Use case 3: Changing a record into its primary key
32
- # 1. Command has result type of A which is an Entity
33
- # 2. transformer takes an A record and returns record.primary_key
34
- # 3. To know the output type, we need to know the result type of the previous type.
35
- # 4. To know the input type, we need to know the type of the previous transformer since they are the same.
36
- # (however, by convention we can just use nil in this case.)
37
- #
38
- # Use case 4: document upload
39
- # 1. Command takes input stream plus some document info
40
- # 2. controller action receives temporary file path
41
- # 3. transformer opens input stream and replaces file path with input stream
42
- # 4. In this case, we have hard-coded types.
43
- #
44
- # Challenge: we seem to not know in advance if the transformer needs to know what comes before it or what comes
45
- # after it. Unless we are writing a one-off transformer then we have hard-coded knowledge.
46
- #
47
- # Seems like input transformer really needs to know what comes next, the target type.
48
- # Seems like output transformer might require to know what came previously
49
- #
50
- # Plan:
51
- # 1. Both inputs transformer and result have similar structure... they have a relevant type that they transform.
52
- # The difference is that the result takes previous steps output and transforms it to a different type, whereas
53
- # the input transformer needs to know what comes next in order to communicate its types.
54
- # So we might be able to get away with a transformed_type that accepts the from_type. And the calling code can
55
- # interpret how it goes. This might create some awkwardness or confusion at least when creating one of the
56
- # two types of transformer.
57
- def type_declaration(_from_type)
58
- # :nocov:
59
- nil
60
- # :nocov:
6
+ def requires_declaration_data?
7
+ false
61
8
  end
62
9
 
63
- def type(from_type)
64
- dec = type_declaration(from_type)
10
+ def requires_parent_declaration_data?
11
+ false
12
+ end
65
13
 
66
- if dec
67
- if dec.is_a?(Types::Type)
68
- dec
69
- else
70
- Domain.current.foobara_type_from_declaration(dec)
71
- end
72
- end
14
+ def from(...)
15
+ @from_type = Domain.current.foobara_type_from_declaration(...)
73
16
  end
17
+
18
+ def to(...)
19
+ @to_type = Domain.current.foobara_type_from_declaration(...)
20
+ end
21
+
22
+ attr_reader :from_type, :to_type
23
+ end
24
+
25
+ def from_type_declaration
26
+ nil
27
+ end
28
+
29
+ def to_type_declaration
30
+ nil
31
+ end
32
+
33
+ def from_type
34
+ return @from_type if defined?(@from_type)
35
+
36
+ @from_type = self.class.from_type || if from_type_declaration
37
+ Domain.current.foobara_type_from_declaration(from_type_declaration)
38
+ end
39
+ end
40
+
41
+ def to_type
42
+ return @to_type if defined?(@to_type)
43
+
44
+ @to_type = self.class.to_type || if to_type_declaration
45
+ Domain.current.foobara_type_from_declaration(to_type_declaration)
46
+ end
47
+ end
48
+
49
+ def has_to_type?
50
+ !!to_type
74
51
  end
75
52
 
76
- alias from_type declaration_data
53
+ def has_from_type?
54
+ !!from_type
55
+ end
56
+
57
+ def from(...)
58
+ @from_type = Domain.current.foobara_type_from_declaration(...)
59
+ end
60
+
61
+ def to(...)
62
+ @to_type = Domain.current.foobara_type_from_declaration(...)
63
+ end
77
64
 
78
- def type
79
- return @type if @type
65
+ def initialize(from: nil, to: nil)
66
+ super()
67
+
68
+ if from
69
+ self.from from
70
+ end
71
+
72
+ if to
73
+ self.to to
74
+ end
80
75
 
81
- @type = self.class.type(from_type)
76
+ # we want to force these to be created now in the current name space if they are declarations
77
+ from_type
78
+ to_type
82
79
  end
83
80
 
84
81
  def process_value(value)
82
+ if has_from_type?
83
+ outcome = Namespace.use from_type.created_in_namespace do
84
+ from_type.process_value(value)
85
+ end
86
+
87
+ return outcome unless outcome.success?
88
+
89
+ value = outcome.result
90
+ end
91
+
85
92
  output = transform(value)
86
- Outcome.success(output)
93
+
94
+ if has_to_type?
95
+ Namespace.use to_type.created_in_namespace do
96
+ to_type.process_value(output)
97
+ end
98
+ else
99
+ Outcome.success(output)
100
+ end
87
101
  end
88
102
  end
89
103
  end
@@ -12,10 +12,6 @@ module Foobara
12
12
  Domain.current.foobara_type_builder.type_declaration_handler_for(...)
13
13
  end
14
14
 
15
- def lookup_absolute_type!(*, **opts, &)
16
- Foobara::Namespace.global.foobara_lookup_type!(*, **opts.merge(mode: Namespace::LookupMode::ABSOLUTE), &)
17
- end
18
-
19
15
  def lookup_type!(...)
20
16
  Foobara::Namespace.current.foobara_lookup_type!(...)
21
17
  end
@@ -0,0 +1,29 @@
1
+ module Foobara
2
+ module Value
3
+ class Mutator < Processor
4
+ class << self
5
+ def foobara_manifest(to_include: Set.new, remove_sensitive: false)
6
+ super.merge(processor_type: :mutator)
7
+ end
8
+
9
+ def error_classes
10
+ []
11
+ end
12
+ end
13
+
14
+ def mutate(_value)
15
+ # :nocov:
16
+ raise "subclass responsibility"
17
+ # :nocov:
18
+ end
19
+
20
+ def process_value(value)
21
+ if applicable?(value)
22
+ mutate(value)
23
+ end
24
+
25
+ Outcome.success(value)
26
+ end
27
+ end
28
+ end
29
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foobara
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.86
4
+ version: 0.0.88
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-23 00:00:00.000000000 Z
10
+ date: 2025-03-28 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bigdecimal
@@ -27,30 +27,30 @@ dependencies:
27
27
  name: foobara-lru-cache
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - ">="
30
+ - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '0'
32
+ version: 0.0.2
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - ">="
37
+ - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '0'
39
+ version: 0.0.2
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: foobara-util
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - ">="
44
+ - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '0'
46
+ version: 0.0.11
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - ">="
51
+ - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '0'
53
+ version: 0.0.11
54
54
  description: A command-centric and discoverable software framework with a focus on
55
55
  domain concepts and abstracting away integration code
56
56
  email:
@@ -179,6 +179,7 @@ files:
179
179
  - projects/command_connectors/src/command_registry/exposed_command.rb
180
180
  - projects/command_connectors/src/command_registry/exposed_domain.rb
181
181
  - projects/command_connectors/src/command_registry/exposed_organization.rb
182
+ - projects/command_connectors/src/response_mutator.rb
182
183
  - projects/command_connectors/src/serializer.rb
183
184
  - projects/command_connectors/src/serializers/aggregate_serializer.rb
184
185
  - projects/command_connectors/src/serializers/atomic_serializer.rb
@@ -359,7 +360,8 @@ files:
359
360
  - projects/thread_parent/src/thread_parent.rb
360
361
  - projects/type_declarations/lib/foobara/type_declarations.rb
361
362
  - projects/type_declarations/src/attributes.rb
362
- - projects/type_declarations/src/attributes_transformer.rb
363
+ - projects/type_declarations/src/attributes_transformers/only.rb
364
+ - projects/type_declarations/src/attributes_transformers/reject.rb
363
365
  - projects/type_declarations/src/caster.rb
364
366
  - projects/type_declarations/src/desugarizer.rb
365
367
  - projects/type_declarations/src/dsl/attributes.rb
@@ -418,6 +420,7 @@ files:
418
420
  - projects/value/lib/foobara/value.rb
419
421
  - projects/value/src/caster.rb
420
422
  - projects/value/src/data_error.rb
423
+ - projects/value/src/mutator.rb
421
424
  - projects/value/src/processor.rb
422
425
  - projects/value/src/processor/casting.rb
423
426
  - projects/value/src/processor/multi.rb
@@ -1,33 +0,0 @@
1
- module Foobara
2
- class AttributesTransformer < TypeDeclarations::TypedTransformer
3
- class << self
4
- attr_accessor :only_attributes
5
-
6
- def type_declaration(from_type)
7
- from_declaration = from_type.declaration_data
8
- to_declaration = TypeDeclarations::Attributes.only(from_declaration, *only_attributes)
9
-
10
- if TypeDeclarations.declarations_equal?(from_declaration, to_declaration)
11
- from_type
12
- else
13
- from_type.foobara_domain.foobara_type_from_declaration(to_declaration)
14
- end
15
- end
16
-
17
- def only(*attribute_names)
18
- transformer_class = Class.new(self)
19
- transformer_class.only_attributes = attribute_names
20
-
21
- transformer_class
22
- end
23
- end
24
-
25
- def transform(inputs)
26
- inputs.slice(*allowed_keys)
27
- end
28
-
29
- def allowed_keys
30
- type.element_types.keys
31
- end
32
- end
33
- end