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, #where, #with, 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



137
138
139
# File 'lib/plumb/hash_class.rb', line 137

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
130
131
132
133
134
135
# 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 = nil # Do not allocate errors unless needed
  output = @inclusive ? input.dup : {}
  field_result = Result.valid(nil)

  _schema.each do |key, field|
    key_s = key.to_key
    if input.key?(key_s)
      r = field.call(field_result.reset(input[key_s]))
      output[key_s] = r.value
      unless r.valid?
        errors ||= {}
        errors[key_s] = r.errors
      end
    elsif !key.optional?
      r = field.call(BLANK_RESULT)
      output[key_s] = r.value unless r.value == Undefined
      unless r.valid?
        errors ||= {}
        errors[key_s] = r.errors
      end
    end
  end

  errors ? 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