Module: DhanHQ::WriteResult

Defined in:
lib/DhanHQ/write_result.rb

Overview

Interprets the several shapes a write currently uses to signal failure.

The model write methods do not share a return contract. Depending on which class and which failure you hit, a rejected write comes back as nil, as false, or as a ErrorObject — and Models::AlertOrder.modify can return either of the first two from the same method. A caller cannot write one error branch.

Unifying those contracts is a breaking change for the applications that depend on this gem, so it is staged (see CHANGELOG). This module is step one: it puts the knowledge of "what does failure look like" in exactly one place, and backs the bang variants (+place!+, modify!, cancel!, …) that give callers a single, explicit failure mode to rescue today.

Examples:

Opting a call site into explicit failures

order = DhanHQ::Models::Order.place!(params)   # raises DhanHQ::OrderError
# instead of
order = DhanHQ::Models::Order.place(params)    # nil on failure

Constant Summary collapse

SUPPRESSION_KEY =

Thread-local flag set while a bang variant is calling through.

:dhanhq_suppress_write_deprecation

Class Method Summary collapse

Class Method Details

.describe(result, errors = nil) ⇒ String

Best available explanation for a failure.

Returns:

  • (String)


154
155
156
157
158
159
160
161
# File 'lib/DhanHQ/write_result.rb', line 154

def describe(result, errors = nil)
  return result.errors.to_s if result.is_a?(DhanHQ::ErrorObject)
  return errors.to_s if errors && !errors.empty?

  # `nil` and `false` carry nothing, so say which of the two it was rather than
  # inventing a cause.
  result.nil? ? "the API returned no record" : "the API rejected the request"
end

.errors_from(receiver) ⇒ Hash?

Validation errors carried by a model instance, when it has any.

Parameters:

  • receiver (Object)

Returns:

  • (Hash, nil)


144
145
146
147
148
149
# File 'lib/DhanHQ/write_result.rb', line 144

def errors_from(receiver)
  return nil if receiver.is_a?(Module)
  return nil unless receiver.respond_to?(:errors)

  receiver.errors
end

.failure?(result) ⇒ Boolean

Whether a write result represents a rejected or failed operation.

Parameters:

  • result (Object)

    Return value of a write method.

Returns:

  • (Boolean)


31
32
33
# File 'lib/DhanHQ/write_result.rb', line 31

def failure?(result)
  result.nil? || result == false || result.is_a?(DhanHQ::ErrorObject)
end

.module_label(mod) ⇒ String

Reads a module's declared name, falling back to to_s so an anonymous class still yields something readable rather than an object address.

Returns:

  • (String)


136
137
138
# File 'lib/DhanHQ/write_result.rb', line 136

def module_label(mod)
  mod.name || mod.to_s
end

.operation_label(receiver, name) ⇒ String

Label identifying the operation that failed, for the exception message.

Parameters:

  • receiver (Object)

    The class (for a class method) or instance.

  • name (Symbol)

    Method name.

Returns:

  • (String)

    e.g. "DhanHQ::Models::Order.place" or "…Order#modify".



126
127
128
129
130
# File 'lib/DhanHQ/write_result.rb', line 126

def operation_label(receiver, name)
  return "#{module_label(receiver)}.#{name}" if receiver.is_a?(Module)

  "#{module_label(receiver.class)}##{name}"
end

.report_ambiguous_failure(result, operation:) ⇒ Object

Reports, once per call site, that a non-bang write signalled failure through a value whose shape is going to change in 4.0.0.

Step two of the migration: the notice tells a maintainer which of their call sites still branch on the old return value, so they can move to the bang variant before the non-bang contract unifies on ErrorObject. Returns the result untouched — this observes, it never alters behaviour.

Silent when the write succeeded, when the caller opted out via config.warn_on_ambiguous_write_failure = false, or when reached through a bang variant (those callers have already migrated — see suppressing_deprecation).

Parameters:

  • result (Object)

    Return value of a non-bang write method.

  • operation (String)

    Operation label from operation_label.

Returns:

  • (Object)

    The result, unchanged.



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/DhanHQ/write_result.rb', line 74

def report_ambiguous_failure(result, operation:)
  return result unless failure?(result)
  return result if suppressed?
  return result unless DhanHQ.configuration&.warn_on_ambiguous_write_failure?

  DhanHQ::Deprecation.warn_once(
    operation,
    "#{operation} reported failure as #{shape_of(result)}. Write methods return " \
    "nil, false or a DhanHQ::ErrorObject inconsistently today and will all return " \
    "DhanHQ::ErrorObject in 4.0.0, which is truthy — an `if result` failure branch " \
    "will invert. Use #{operation}! to get a DhanHQ::OrderError instead, or set " \
    "config.warn_on_ambiguous_write_failure = false to silence this."
  )

  result
end

.shape_of(result) ⇒ String

Names the shape a failure arrived in, for the notice.

Returns:

  • (String)


114
115
116
117
118
119
# File 'lib/DhanHQ/write_result.rb', line 114

def shape_of(result)
  return "nil" if result.nil?
  return "false" if result == false

  "a DhanHQ::ErrorObject"
end

.success?(result) ⇒ Boolean

Parameters:

  • result (Object)

    Return value of a write method.

Returns:

  • (Boolean)


37
38
39
# File 'lib/DhanHQ/write_result.rb', line 37

def success?(result)
  !failure?(result)
end

.suppressed?Boolean

Returns:

  • (Boolean)


107
108
109
# File 'lib/DhanHQ/write_result.rb', line 107

def suppressed?
  Thread.current[SUPPRESSION_KEY] == true
end

.suppressing_deprecationObject

Runs the block with report_ambiguous_failure disabled on this thread.

Used by the bang variants: they call the non-bang method to get its result, and a caller who has already moved to place! does not need telling to move to place!. Thread-local so a concurrent thread still gets its own notices.

Returns:

  • (Object)

    The block's value.



98
99
100
101
102
103
104
# File 'lib/DhanHQ/write_result.rb', line 98

def suppressing_deprecation
  previous = Thread.current[SUPPRESSION_KEY]
  Thread.current[SUPPRESSION_KEY] = true
  yield
ensure
  Thread.current[SUPPRESSION_KEY] = previous
end

.unwrap!(result, operation:, error_class: DhanHQ::OrderError, errors: nil) ⇒ Object

Returns the result, or raises carrying whatever diagnostics the failure held.

Parameters:

  • result (Object)

    Return value of a write method.

  • operation (String)

    Human-readable operation name for the message, e.g. "DhanHQ::Models::Order.place".

  • error_class (Class) (defaults to: DhanHQ::OrderError)

    Exception to raise. Defaults to OrderError, which descends from Error, so existing rescue DhanHQ::Error handlers still catch it.

  • errors (Hash, nil) (defaults to: nil)

    Validation errors to report when the result itself carries none — typically a model's errors hash.

Returns:

  • (Object)

    The result, when it represents success.

Raises:



53
54
55
56
57
# File 'lib/DhanHQ/write_result.rb', line 53

def unwrap!(result, operation:, error_class: DhanHQ::OrderError, errors: nil)
  return result if success?(result)

  raise error_class, "#{operation} failed: #{describe(result, errors)}"
end