File: map.rb

package info (click to toggle)
ruby-dry-types 1.2.2-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 504 kB
  • sloc: ruby: 3,059; makefile: 4
file content (136 lines) | stat: -rw-r--r-- 3,159 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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
136
# frozen_string_literal: true

module Dry
  module Types
    # Homogeneous mapping. It describes a hash with unknown keys that match a certain type.
    #
    # @example
    #   type = Dry::Types['hash'].map(
    #     Dry::Types['integer'].constrained(gteq: 1, lteq: 10),
    #     Dry::Types['string']
    #   )
    #
    #   type.(1 => 'right')
    #   # => {1 => 'right'}
    #
    #   type.('1' => 'wrong')
    #   # Dry::Types::MapError: "1" violates constraints (type?(Integer, "1") AND gteq?(1, "1") AND lteq?(10, "1") failed)
    #
    #   type.(11 => 'wrong')
    #   # Dry::Types::MapError: 11 violates constraints (lteq?(10, 11) failed)
    #
    # @api public
    class Map < Nominal
      def initialize(_primitive, key_type: Types['any'], value_type: Types['any'], meta: EMPTY_HASH)
        super(_primitive, key_type: key_type, value_type: value_type, meta: meta)
      end

      # @return [Type]
      #
      # @api public
      def key_type
        options[:key_type]
      end

      # @return [Type]
      #
      # @api public
      def value_type
        options[:value_type]
      end

      # @return [String]
      #
      # @api public
      def name
        'Map'
      end

      # @param [Hash] hash
      #
      # @return [Hash]
      #
      # @api private
      def call_unsafe(hash)
        try(hash) { |failure|
          raise MapError, failure.error.message
        }.input
      end

      # @param [Hash] hash
      #
      # @return [Hash]
      #
      # @api private
      def call_safe(hash)
        try(hash) { return yield }.input
      end

      # @param [Hash] hash
      #
      # @return [Result]
      #
      # @api public
      def try(hash)
        result = coerce(hash)
        return result if result.success? || !block_given?

        yield(result)
      end

      # @param meta [Boolean] Whether to dump the meta to the AST
      #
      # @return [Array] An AST representation
      #
      # @api public
      def to_ast(meta: true)
        [:map,
         [key_type.to_ast(meta: true),
          value_type.to_ast(meta: true),
          meta ? self.meta : EMPTY_HASH]]
      end

      # @return [Boolean]
      #
      # @api public
      def constrained?
        value_type.constrained?
      end

      private

      # @api private
      def coerce(input)
        unless primitive?(input)
          return failure(
            input, CoercionError.new("#{input.inspect} must be an instance of #{primitive}")
          )
        end

        output = {}
        failures = []

        input.each do |k, v|
          res_k = key_type.try(k)
          res_v = value_type.try(v)

          if res_k.failure?
            failures << res_k.error
          elsif output.key?(res_k.input)
            failures << CoercionError.new("duplicate coerced hash key #{res_k.input.inspect}")
          elsif res_v.failure?
            failures << res_v.error
          else
            output[res_k.input] = res_v.input
          end
        end

        if failures.empty?
          success(output)
        else
          failure(input, MultipleError.new(failures))
        end
      end
    end
  end
end