Class: DhanHQ::Client

Inherits:
Object
  • Object
show all
Includes:
RequestHelper, ResponseHelper
Defined in:
lib/DhanHQ/client.rb

Overview

The Client class provides a wrapper for HTTP requests to interact with the DhanHQ API. Responsible for:

  • Establishing and managing the HTTP connection
  • Handling authentication and request headers
  • Sending raw HTTP requests (GET, POST, PUT, DELETE)
  • Parsing JSON responses into HashWithIndifferentAccess
  • Handling standard HTTP errors (400, 401, 403, etc.)
  • Implementing Rate Limiting to avoid hitting API limits.

It supports GET, POST, PUT, and DELETE requests with JSON encoding/decoding. Credentials (access_token, client_id) are automatically added to each request.

Constant Summary

Constants included from ResponseHelper

ResponseHelper::STATUS_ERROR_FALLBACK

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from RequestHelper

#build_from_response

Constructor Details

#initialize(api_type:) ⇒ DhanHQ::Client

Initializes a new DhanHQ Client.

Establishes state and checks its one invariant; the HTTP connection is opened lazily on first use, so constructing a client does no network setup.

Examples:

Create a new client:

client = DhanHQ::Client.new(api_type: :order_api)

Parameters:

  • api_type (Symbol)

    Type of API (:order_api, :data_api, :non_trading_api)

Raises:

  • (DhanHQ::Error)

    If the rate limiter cannot be resolved for this API type.



58
59
60
61
62
63
64
# File 'lib/DhanHQ/client.rb', line 58

def initialize(api_type:)
  DhanHQ.ensure_configuration!
  # Shared per API type, so separate clients coordinate against one limit.
  @rate_limiter = RateLimiter.for(api_type)

  raise DhanHQ::Error, "RateLimiter initialization failed" unless @rate_limiter
end

Instance Attribute Details

#token_managerObject (readonly)

Returns the value of attribute token_manager.



45
46
47
# File 'lib/DhanHQ/client.rb', line 45

def token_manager
  @token_manager
end

Instance Method Details

#connectionFaraday::Connection

The Faraday connection used for HTTP requests.

Built on first use and rebuilt whenever the configured base URL changes — which happens when sandbox mode is toggled, or when a token endpoint hands back a different host mid-process.

Returns:

  • (Faraday::Connection)


73
74
75
76
77
78
79
# File 'lib/DhanHQ/client.rb', line 73

def connection
  current_url = DhanHQ.configuration.base_url
  return @connection if @connection && @last_base_url == current_url

  @last_base_url = current_url
  @connection = build_connection(current_url)
end

#delete(path, params = {}) ⇒ HashWithIndifferentAccess+

Convenience wrapper for issuing a DELETE request.

Parameters:

  • path (String)

    The API endpoint path.

  • params (Hash) (defaults to: {})

    Optional request payload (rare for DELETE).

Returns:

  • (HashWithIndifferentAccess, Array<HashWithIndifferentAccess>)

    Parsed JSON response.

See Also:



180
181
182
# File 'lib/DhanHQ/client.rb', line 180

def delete(path, params = {})
  request(:delete, path, params)
end

#enable_auto_token_management!(dhan_client_id:, pin:, totp_secret:) ⇒ Object



211
212
213
214
215
216
217
218
219
# File 'lib/DhanHQ/client.rb', line 211

def enable_auto_token_management!(dhan_client_id:, pin:, totp_secret:)
  @token_manager = Auth::TokenManager.new(
    dhan_client_id: dhan_client_id,
    pin: pin,
    totp_secret: totp_secret
  )

  @token_manager.generate!
end

#generate_access_token(dhan_client_id:, pin:, totp: nil, totp_secret: nil) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/DhanHQ/client.rb', line 184

def generate_access_token(dhan_client_id:, pin:, totp: nil, totp_secret: nil)
  token = Auth::TokenGenerator.new.generate(
    dhan_client_id: dhan_client_id,
    pin: pin,
    totp: totp,
    totp_secret: totp_secret
  )

  DhanHQ.configure do |config|
    config.access_token = token.access_token
    config.client_id = token.client_id if token.client_id.to_s.strip != ""
  end

  token
end

#get(path, params = {}) ⇒ HashWithIndifferentAccess+

Convenience wrapper for issuing a GET request.

Parameters:

  • path (String)

    The API endpoint path.

  • params (Hash) (defaults to: {})

    Query parameters for the request.

Returns:

  • (HashWithIndifferentAccess, Array<HashWithIndifferentAccess>)

    Parsed JSON response.

See Also:



147
148
149
# File 'lib/DhanHQ/client.rb', line 147

def get(path, params = {})
  request(:get, path, params)
end

#post(path, params = {}) ⇒ HashWithIndifferentAccess+

Convenience wrapper for issuing a POST request.

Parameters:

  • path (String)

    The API endpoint path.

  • params (Hash) (defaults to: {})

    JSON payload for the request.

Returns:

  • (HashWithIndifferentAccess, Array<HashWithIndifferentAccess>)

    Parsed JSON response.

See Also:



158
159
160
# File 'lib/DhanHQ/client.rb', line 158

def post(path, params = {})
  request(:post, path, params)
end

#put(path, params = {}) ⇒ HashWithIndifferentAccess+

Convenience wrapper for issuing a PUT request.

Parameters:

  • path (String)

    The API endpoint path.

  • params (Hash) (defaults to: {})

    JSON payload for the request.

Returns:

  • (HashWithIndifferentAccess, Array<HashWithIndifferentAccess>)

    Parsed JSON response.

See Also:



169
170
171
# File 'lib/DhanHQ/client.rb', line 169

def put(path, params = {})
  request(:put, path, params)
end

#renew_access_tokenObject



200
201
202
203
204
205
206
207
208
209
# File 'lib/DhanHQ/client.rb', line 200

def renew_access_token
  token = Auth::TokenRenewal.new.renew

  DhanHQ.configure do |config|
    config.access_token = token.access_token
    config.client_id = token.client_id if token.client_id.to_s.strip != ""
  end

  token
end

#request(method, path, payload, retries: 3) ⇒ HashWithIndifferentAccess+

Sends an HTTP request to the API with automatic retry for transient errors.

Parameters:

  • method (Symbol)

    The HTTP method (:get, :post, :put, :delete)

  • path (String)

    The API endpoint path.

  • payload (Hash)

    The request parameters or body.

  • retries (Integer) (defaults to: 3)

    Number of retries for transient errors (default: 3)

Returns:

  • (HashWithIndifferentAccess, Array<HashWithIndifferentAccess>)

    Parsed JSON response.

Raises:



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/DhanHQ/client.rb', line 89

def request(method, path, payload, retries: 3)
  payload = with_correlation_id(method, path, payload)
  return dry_run.response_for(method, path, payload) if dry_run.simulates?(method, path)

  @token_manager&.ensure_valid_token!
  @rate_limiter.throttle!

  # A non-idempotent write is not safely retryable: the API has no idempotency
  # key, so a request that timed out may already have reached the exchange.
  effective_retries = retryable_write?(method, path) ? retries : 0

  with_auth_retry do
    with_transient_retry(retries: effective_retries) do
      response = connection.send(method, path) do |req|
        req.headers.merge!(build_headers(path))
        prepare_payload(req, payload, method, path)
      end
      handle_response(response)
    end
  end
end

#with_auth_retryObject



111
112
113
114
115
116
117
118
119
120
121
# File 'lib/DhanHQ/client.rb', line 111

def with_auth_retry
  yield
rescue DhanHQ::InvalidAuthenticationError, DhanHQ::InvalidTokenError,
       DhanHQ::TokenExpiredError, DhanHQ::AuthenticationFailedError => e
  config = DhanHQ.configuration
  raise unless config&.access_token_provider

  config.on_token_expired&.call(e)
  DhanHQ.logger&.warn("[DhanHQ::Client] Auth failure (#{e.class}), retrying once with fresh token")
  yield
end

#with_transient_retry(retries:) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/DhanHQ/client.rb', line 123

def with_transient_retry(retries:)
  attempt = 0
  begin
    yield
  rescue *RETRYABLE_SDK_ERRORS, *RETRYABLE_TRANSPORT_ERRORS => e
    attempt += 1
    raise exhausted_error(e, retries) if attempt > retries

    backoff = calculate_backoff(attempt)
    DhanHQ.logger&.warn(
      "[DhanHQ::Client] Transient failure (#{e.class}), retrying in #{backoff}s (attempt #{attempt}/#{retries})"
    )
    sleep(backoff)
    retry
  end
end