Class: Plumb::TaggedHash

Inherits:
Object
  • Object
show all
Includes:
Composable
Defined in:
lib/plumb/tagged_hash.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Composable

#&, #/, #>>, #[], #absorb_input, #absorb_output, #as_node, #build, #check, #defer, #fusable_step?, #fuse_with, #generate, #idempotent?, included, #input_type, #invalid, #invoke, #match, #metadata, #not, #output_type, #pipeline, #policy, resolve_operand, #static, #subtype_identity, #to_json_schema, #to_mermaid, #to_plumb_type, #to_s, #transform, #value, #value_preserving?, #where, #with, wrap, #|

Methods included from Callable

#parse, #resolve

Constructor Details

#initialize(hash_type, key, children) ⇒ TaggedHash

Returns a new instance of TaggedHash.

Raises:

  • (ArgumentError)


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/plumb/tagged_hash.rb', line 14

def initialize(hash_type, key, children)
  @hash_type = hash_type
  @key = Key.wrap(key)
  @children = children

  raise ArgumentError, 'all types must be HashClass' if @children.size.zero? || @children.any? do |t|
    !t.is_a?(HashClass)
  end
  raise ArgumentError, "all types must define key #{@key}" unless @children.all? { |t| !!t.at_key(@key) }

  # types are assumed to have literal values for the index field :key
  @index = @children.each.with_object({}) do |t, memo|
    key_type = t.at_key(@key)
    # Accept either bare or base-constrained single-value tags.
    tag = Plumb::Subtyping.literal_value(key_type)
    if Undefined.equal?(tag)
      raise ParseError, "key type at :#{@key} #{key_type} must match a single literal value"
    end

    memo[tag] = t
  end

  freeze
end

Instance Attribute Details

#childrenObject (readonly)

Returns the value of attribute children.



9
10
11
# File 'lib/plumb/tagged_hash.rb', line 9

def children
  @children
end

#hash_typeObject (readonly)

Returns the value of attribute hash_type.



9
10
11
# File 'lib/plumb/tagged_hash.rb', line 9

def hash_type
  @hash_type
end

#keyObject (readonly)

Returns the value of attribute key.



9
10
11
# File 'lib/plumb/tagged_hash.rb', line 9

def key
  @key
end

Instance Method Details

#==(other) ⇒ Boolean

Equality includes the discriminator and base hash because #children contains only variants; comparing children alone would conflate distinct tagged hashes.

Parameters:

  • other (Object)

Returns:

  • (Boolean)


43
44
45
46
47
48
# File 'lib/plumb/tagged_hash.rb', line 43

def ==(other)
  other.instance_of?(self.class) &&
    key.eql?(other.key) &&
    hash_type == other.hash_type &&
    children == other.children
end

#accepted_typeTaggedHash

Relaxes each variant's non-discriminator fields to their accepted types.

Returns:



63
64
65
66
67
68
69
70
71
# File 'lib/plumb/tagged_hash.rb', line 63

def accepted_type
  relaxed = @children.map do |child|
    schema = child._schema.each_with_object({}) do |(k, field), h|
      h[k] = k.eql?(@key) ? field : Plumb::Subtyping.accepted_type(field)
    end
    child.class.new(schema:)
  end
  self.class.new(@hash_type, @key, relaxed)
end

#call(result) ⇒ Object



73
74
75
76
77
78
79
80
81
# File 'lib/plumb/tagged_hash.rb', line 73

def call(result)
  result = @hash_type.call(result)
  return result unless result.valid?

  child = @index[result.value[@key.to_sym]]
  return result.invalid!(errors: "expected :#{@key.to_sym} to be one of #{@index.keys.join(', ')}") unless child

  child.call(result)
end

#subtype_of?(other) ⇒ Boolean

A tagged hash is a subtype when every selectable variant is a subtype. The base hash only narrows those variants, so ignoring it here is conservative; comparing variants pairwise would be unsound across different discriminators.

Parameters:

Returns:

  • (Boolean)


55
56
57
58
59
# File 'lib/plumb/tagged_hash.rb', line 55

def subtype_of?(other)
  return true if self == other

  @children.all? { |variant| Plumb::Subtyping.subtype?(variant, other) }
end

#with_children(children) ⇒ Object

See Also:



12
# File 'lib/plumb/tagged_hash.rb', line 12

def with_children(children) = self.class.new(hash_type, key, children)