Module: Hecks::Runtime::RefusalWording

Defined in:
lib/hecks/runtime/refusal_wording.rb

Overview

Every DomainRefusal wording that is not already data — given/ ensures/a declared invariant already carry their own description, read at dispatch time off the command or value object that declared them. These are different in kind: LANGUAGE-LEVEL refusals, the same wording for every domain, not authored per-bluebook.

Declared the same way in Vocabulary::RefusalTemplate (language/bluebook/vocabulary.bluebook) — spec/refusal_wording_ conformance_spec holds this table equal to the language, both directions. Hand-typed rather than read live off the meta-domain at every dispatch, the same reason Expression::Evaluator:: SIGN_TEST_OPERATORS is hand-typed beside Vocabulary::SignTest.

Constant Summary collapse

TEMPLATES =
{
  %w[NotFound creating_no_identity]             =>
                                                   "{command} creates a {aggregate} — pass {identity}:",
  %w[AlreadyExists creating_duplicate]          =>
                                                   "{command} creates a {aggregate} that already exists — {identity} {offered}",
  %w[AlreadyExists entity_duplicate]            =>
                                                   "a {entity} already exists on {aggregate} — {identity} {offered}",
  %w[NotFound acting_no_identity]               =>
                                                   "{command} acts on an existing {aggregate} — pass {identity}:",
  %w[NotFound record_missing]                   =>
                                                   "no {aggregate} with {identity} {offered}",
  %w[NotFound entity_parent_no_identity]        =>
                                                   "{command} acts on a {aggregate}'s {entity} — pass {identity}:",
  %w[UnknownVerb entity_unknown]                =>
                                                   "{aggregate} has no entity {entity}",
  %w[NotFound entity_element_no_identity]       =>
                                                   "{command} acts on one {entity} — pass {identity}:",
  %w[NotFound entity_element_missing]           =>
                                                   "no {entity} with {identity} {wants} on {aggregate} {parent_id}",
  %w[NotFound reference_target_missing]         =>
                                                   "no {target} with {heads} {key}",
  %w[NotFound read_model_reference_missing]     =>
                                                   "no {aggregate} with reference {offered}",
  %w[TypeMismatch read_model_object_reference]  =>
                                                   "{query} refused — a reference is an id, and {field} arrived as an object",
  %w[UnknownVerb no_query]                      =>
                                                   "{aggregate} has no query {query}",
  %w[UnknownVerb entity_query_missing]          =>
                                                   "{entity} has no query {query}",
  %w[UnknownVerb entity_holds_no_list]          =>
                                                   "{aggregate} holds no list of {entity}",
  %w[UnknownVerb entity_no_command]             =>
                                                   "{entity} has no command {command}",
  %w[UnknownVerb aggregate_no_command]          =>
                                                   "{aggregate} has no command {command}",
  %w[UnknownVerb port_no_operation]             =>
                                                   "{port} has no operation {operation}",
  %w[UnknownVerb no_domain]                     =>
                                                   "no domain {domain} loaded (verb {verb})",
  %w[UnknownVerb no_read_model]                 =>
                                                   "{domain} has no read model {query}",
  %w[UnknownVerb not_fully_qualified]           =>
                                                   "{verb} is not a fully-qualified verb (Domain::Aggregate.Command)",
  %w[UnknownVerb no_aggregate]                  =>
                                                   "{domain} has no aggregate {aggregate}",
  %w[LifecycleRefused transition_blocked]       =>
                                                   "{command} refused — {field} is {current}, and {command} moves it only from {allowed}",
  %w[TypeMismatch value_object_shape]           =>
                                                   "{name} is a {type} — pass its fields as an object, not {offered}",
  %w[TypeMismatch reference_as_object]          =>
                                                   "{command} refused — a reference is an id, and {attribute} arrived as an object{known_by}",
  %w[TypeMismatch multi_field_scalar]           =>
                                                   "{type} has multiple fields and cannot stand in for a scalar",
  %w[TypeMismatch composite_identity]           =>
                                                   "{type} is a composite identity — an identity must have exactly one field",
  %w[TypeMismatch numeric_field]                =>
                                                   "{type}.{field} expects {expected}, got {offered}",
  %w[TypeMismatch pattern_mismatch]             =>
                                                   "{type}.{field} must match {pattern}, got {offered}",
  %w[TypeMismatch arithmetic_amount]            =>
                                                   "{op} of {target} needs an Integer, got {offered}",
  %w[TypeMismatch arithmetic_current]           =>
                                                   "{op} of {target} needs an Integer {target}, got {offered}",
  %w[TypeMismatch arithmetic_shared_field]      =>
                                                   "{op} of {target} needs a value object with one shared Integer field",
  %w[UnknownArgument unknown_args]              =>
                                                   "{command} does not declare {unknown} — it takes {declared}",
  %w[AbsentArgument absent_args]                =>
                                                   "{command} was not given {absent} — it takes {declared}",
  %w[InvariantViolation closed_set_member]      =>
                                                   "{type} admits {admitted} — got {offered}",
  %w[InvariantViolation value_object_invariant] =>
                                                   "{name} invariant violated — {description} (given {offered})",
  %w[InvariantViolation admits_declared_set]    =>
                                                   "{name} admits {admits} — {admitted} — got {offered}",
  %w[InvariantViolation undeclared_set]         =>
                                                   "{name} admits {admits}, which this chapter does not declare — a closed set is named " \
                                                   "Aggregate::SetName, and it must be one the bluebook actually holds",
  %w[Unauthorized tenant_required]              =>
                                                   "{query} declares authorize with tenant: {field} — pass {field}: to name which " \
                                                   "{field} this ask is scoped to",
  %w[Unauthorized role_mismatch]                =>
                                                   "{command} refused — role: {role}, and the caller stated {caller_role}",
  %w[AttributeAbsent absent_read]               =>
                                                   "{aggregate} {field} is absent on this record — declared, not optional, and " \
                                                   "added since it was written. Backfill it in a translation (backfill :{field}, " \
                                                   "default: ...), or declare it optional: true",
  %w[ProjectionAbsent absent_read]              =>
                                                   "{aggregate} {field} is not yet projected on this record — declared via " \
                                                   "projects :{field}, but no rebuild sweep has populated it. Run the sweep, " \
                                                   "or read {reference}.{remote_field} directly if this rule cannot wait"
}.freeze

Class Method Summary collapse

Class Method Details

.render(refusal, site, **values) ⇒ Object

Plain text substitution, never expression syntax — a template is read, not evaluated. render computes the placeholder VALUES via whatever the call site already had (a joined list, a rendered identity reading, …) and this only replaces the markers.



115
116
117
118
119
120
121
# File 'lib/hecks/runtime/refusal_wording.rb', line 115

def render(refusal, site, **values)
  template = TEMPLATES.fetch([refusal, site]) do
    raise KeyError, "no refusal template for #{refusal}/#{site} — declare it in " \
                    "Vocabulary::RefusalTemplate first"
  end
  values.reduce(template) { |text, (key, value)| text.gsub("{#{key}}", value.to_s) }
end