Module: Pangea::Internal::Type::Converter Private

Included in:
ArrayOf, BaseModel, Boolean, HashOf, Union, Unknown
Defined in:
lib/pangea/internal/type/converter.rb

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.coerce(target, value, state: {strictness: true, exactness: {yes: 0, no: 0, maybe: 0}, branched: 0}) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Based on ‘target`, transform `value` into `target`, to the extent possible:

  1. if the given ‘value` conforms to `target` already, return the given `value`

  2. if it’s possible and safe to convert the given ‘value` to `target`, then the converted value

  3. otherwise, the given ‘value` unaltered

coercion strategy when we have to decide between multiple possible conversion targets:

  • ‘true`: the conversion must be exact, with minimum coercion.

  • ‘false`: the conversion can be approximate, with some coercion.

  • ‘:strong`: the conversion must be exact, with no coercion, and raise an error if not possible.

The ‘exactness` is `Hash` with keys being one of `yes`, `no`, or `maybe`. For any given conversion attempt, the exactness will be updated based on how closely the value recursively matches the target type:

  • ‘yes`: the value can be converted to the target type with minimum coercion.

  • ‘maybe`: the value can be converted to the target type with some reasonable coercion.

  • ‘no`: the value cannot be converted to the target type.

See implementation below for more details.

@option state [Boolean, :strong] :strictness

@option state [Hash{Symbol=>Object}] :exactness

@option state [Integer] :branched

Parameters:

  • target (Pangea::Internal::Type::Converter, Class)
  • value (Object)
  • state (Hash{Symbol=>Object}) (defaults to: {strictness: true, exactness: {yes: 0, no: 0, maybe: 0}, branched: 0})

    The ‘strictness` is one of `true`, `false`, or `:strong`. This informs the

Returns:

  • (Object)


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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/pangea/internal/type/converter.rb', line 90

def coerce(
  target,
  value,
  state: {strictness: true, exactness: {yes: 0, no: 0, maybe: 0}, branched: 0}
)
  # rubocop:disable Lint/SuppressedException
  # rubocop:disable Metrics/BlockNesting

  strictness, exactness = state.fetch_values(:strictness, :exactness)

  case target
  in Pangea::Internal::Type::Converter
    return target.coerce(value, state: state)
  in Class
    if value.is_a?(target)
      exactness[:yes] += 1
      return value
    end

    case target
    in -> { _1 <= NilClass }
      exactness[value.nil? ? :yes : :maybe] += 1
      return nil
    in -> { _1 <= Integer }
      if value.is_a?(Integer)
        exactness[:yes] += 1
        return value
      elsif strictness == :strong && Integer(value, exception: false) != value
        message = "no implicit conversion of #{value.class} into #{target.inspect}"
        raise value.is_a?(Numeric) ? ArgumentError.new(message) : TypeError.new(message)
      else
        Kernel.then do
          return Integer(value).tap { exactness[:maybe] += 1 }
        rescue ArgumentError, TypeError
        end
      end
    in -> { _1 <= Float }
      if value.is_a?(Numeric)
        exactness[:yes] += 1
        return Float(value)
      elsif strictness == :strong
        message = "no implicit conversion of #{value.class} into #{target.inspect}"
        raise TypeError.new(message)
      else
        Kernel.then do
          return Float(value).tap { exactness[:maybe] += 1 }
        rescue ArgumentError, TypeError
        end
      end
    in -> { _1 <= String }
      case value
      in String | Symbol | Numeric
        exactness[value.is_a?(Numeric) ? :maybe : :yes] += 1
        return value.to_s
      in StringIO
        exactness[:yes] += 1
        return value.string
      else
        if strictness == :strong
          message = "no implicit conversion of #{value.class} into #{target.inspect}"
          raise TypeError.new(message)
        end
      end
    in -> { _1 <= Date || _1 <= Time }
      Kernel.then do
        return target.parse(value).tap { exactness[:yes] += 1 }
      rescue ArgumentError, TypeError => e
        raise e if strictness == :strong
      end
    in -> { _1 <= StringIO } if value.is_a?(String)
      exactness[:yes] += 1
      return StringIO.new(value.b)
    else
    end
  in Symbol
    case value
    in Symbol | String
      if value.to_sym == target
        exactness[:yes] += 1
        return target
      else
        exactness[:maybe] += 1
        return value
      end
    else
      if strictness == :strong
        message = "cannot convert non-matching #{value.class} into #{target.inspect}"
        raise ArgumentError.new(message)
      end
    end
  else
  end

  exactness[:no] += 1
  value

  # rubocop:enable Metrics/BlockNesting
  # rubocop:enable Lint/SuppressedException
end

.dump(target, value, state: {can_retry: true}) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parameters:

  • target (Pangea::Internal::Type::Converter, Class)
  • value (Object)
  • state (Hash{Symbol=>Object}) (defaults to: {can_retry: true})

    .

    @option state [Boolean] :can_retry

Returns:

  • (Object)


201
202
203
204
205
206
207
208
# File 'lib/pangea/internal/type/converter.rb', line 201

def dump(target, value, state: {can_retry: true})
  case target
  in Pangea::Internal::Type::Converter
    target.dump(value, state: state)
  else
    Pangea::Internal::Type::Unknown.dump(value, state: state)
  end
end

.type_info(spec) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/pangea/internal/type/converter.rb', line 210

def type_info(spec)
  case spec
  in Proc
    spec
  in Hash
    type_info(spec.slice(:const, :enum, :union).first&.last)
  in true | false
    -> { Pangea::Internal::Type::Boolean }
  in Pangea::Internal::Type::Converter | Class | Symbol
    -> { spec }
  in NilClass | Integer | Float
    -> { spec.class }
  end
end

Instance Method Details

#coerce(value, state:) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parameters:

  • value (Object)
  • state (Hash{Symbol=>Object})

    .

    @option state [Boolean, :strong] :strictness

    @option state [HashSymbol=>Object] :exactness

    @option state [Integer] :branched

Returns:

  • (Object)


20
# File 'lib/pangea/internal/type/converter.rb', line 20

def coerce(value, state:) = (raise NotImplementedError)

#dump(value, state:) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parameters:

  • value (Object)
  • state (Hash{Symbol=>Object})

    .

    @option state [Boolean] :can_retry

Returns:

  • (Object)


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/pangea/internal/type/converter.rb', line 31

def dump(value, state:)
  case value
  in Array
    value.map { Pangea::Internal::Type::Unknown.dump(_1, state: state) }
  in Hash
    value.transform_values { Pangea::Internal::Type::Unknown.dump(_1, state: state) }
  in Pangea::Internal::Type::BaseModel
    value.class.dump(value, state: state)
  in StringIO
    value.string
  in Pathname | IO
    state[:can_retry] = false if value.is_a?(IO)
    Pangea::Internal::Util::SerializationAdapter.new(value)
  else
    value
  end
end