Class: Plumb::HashClass

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

Constant Summary collapse

NOT_A_HASH =
{ _: 'must be a Hash' }.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Composable

#>>, #as_node, #build, #check, #children, #defer, #generate, included, #invalid, #invoke, #match, #metadata, #not, #pipeline, #policy, #static, #to_json_schema, #to_s, #transform, #value, wrap, #|

Methods included from Callable

#parse, #resolve

Constructor Details

#initialize(schema: BLANK_HASH, inclusive: false) ⇒ HashClass

Returns a new instance of HashClass.



17
18
19
20
21
# File 'lib/plumb/hash_class.rb', line 17

def initialize(schema: BLANK_HASH, inclusive: false)
  @_schema = wrap_keys_and_values(schema)
  @inclusive = inclusive
  freeze
end

Instance Attribute Details

#_schemaObject (readonly)

Returns the value of attribute _schema.



15
16
17
# File 'lib/plumb/hash_class.rb', line 15

def _schema
  @_schema
end

Instance Method Details

#&(other) ⇒ Object

Raises:

  • (ArgumentError)


59
60
61
62
63
64
65
66
67
68
# File 'lib/plumb/hash_class.rb', line 59

def &(other)
  raise ArgumentError, "expected a HashClass, got #{other.class}" unless other.is_a?(HashClass)

  intersected_keys = other._schema.keys & _schema.keys
  intersected = intersected_keys.each.with_object({}) do |k, memo|
    memo[k] = other.at_key(k)
  end

  self.class.new(schema: intersected, inclusive: @inclusive)
end

#+(other) ⇒ Object

Hash#merge keeps the left-side key in the new hash if they match via #hash and #eql? we need to keep the right-side key, because even if the key name is the same, it’s optional flag might have changed



48
49
50
51
52
53
54
55
56
57
# File 'lib/plumb/hash_class.rb', line 48

def +(other)
  other_schema = case other
                 when HashClass then other._schema
                 when ::Hash then other
                 else
                   raise ArgumentError, "expected a HashClass or Hash, got #{other.class}"
                 end

  self.class.new(schema: merge_rightmost_keys(_schema, other_schema), inclusive: @inclusive)
end

#==(other) ⇒ Object



131
132
133
# File 'lib/plumb/hash_class.rb', line 131

def ==(other)
  other.is_a?(self.class) && other._schema == _schema
end

#at_key(a_key) ⇒ Object



78
79
80
# File 'lib/plumb/hash_class.rb', line 78

def at_key(a_key)
  _schema[Key.wrap(a_key)]
end

#call(result) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/plumb/hash_class.rb', line 106

def call(result)
  return result.invalid(errors: NOT_A_HASH) unless result.value.is_a?(::Hash)
  return result unless _schema.any?

  input = result.value
  errors = {}
  field_result = result.dup
  initial = {}
  initial = initial.merge(input) if @inclusive
  output = _schema.each.with_object(initial) do |(key, field), ret|
    key_s = key.to_sym
    if input.key?(key_s)
      r = field.call(field_result.reset(input[key_s]))
      errors[key_s] = r.errors unless r.valid?
      ret[key_s] = r.value
    elsif !key.optional?
      r = field.call(BLANK_RESULT)
      errors[key_s] = r.errors unless r.valid?
      ret[key_s] = r.value unless r.value == Undefined
    end
  end

  errors.any? ? result.invalid(output, errors:) : result.valid(output)
end

#filteredObject



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/plumb/hash_class.rb', line 84

def filtered
  op = lambda do |result|
    return result.invalid(errors: 'must be a Hash') unless result.value.is_a?(::Hash)
    return result unless _schema.any?

    input = result.value
    field_result = BLANK_RESULT.dup
    output = _schema.each.with_object({}) do |(key, field), ret|
      key_s = key.to_sym
      if input.key?(key_s)
        r = field.call(field_result.reset(input[key_s]))
        ret[key_s] = r.value if r.valid?
      elsif !key.optional?
        r = field.call(BLANK_RESULT)
        ret[key_s] = r.value if r.valid?
      end
    end
    result.valid(output)
  end
  Step.new(op, [_inspect, 'filtered'].join('.'))
end

#inclusiveObject



74
75
76
# File 'lib/plumb/hash_class.rb', line 74

def inclusive
  self.class.new(schema: _schema, inclusive: true)
end

#schema(*args) ⇒ Object Also known as: []

A Hash type with a specific schema. Option 1: a Hash representing schema

Types::Hash[name: Types::String.present, age?: Types::Integer]

Option 2: a Map with pre-defined types for all keys and values

Types::Hash[Types::String, Types::Integer]


31
32
33
34
35
36
37
38
39
40
# File 'lib/plumb/hash_class.rb', line 31

def schema(*args)
  case args
  in [::Hash => hash]
    self.class.new(schema: _schema.merge(wrap_keys_and_values(hash)), inclusive: @inclusive)
  in [key_type, value_type]
    HashMap.new(Composable.wrap(key_type), Composable.wrap(value_type))
  else
    raise ::ArgumentError, "unexpected value to Types::Hash#schema #{args.inspect}"
  end
end

#tagged_by(key, *types) ⇒ Object



70
71
72
# File 'lib/plumb/hash_class.rb', line 70

def tagged_by(key, *types)
  TaggedHash.new(self, key, types)
end

#to_hObject



82
# File 'lib/plumb/hash_class.rb', line 82

def to_h = _schema