Module: Smartbill::Sdk::Transport

Defined in:
lib/smartbill/sdk/transport.rb,
lib/smartbill/sdk/transport/request.rb,
lib/smartbill/sdk/transport/rate_limiter.rb

Overview

Shared transport logic for the SmartBill clients.

The SmartBill API uses HTTP Basic Auth with username:token and returns JSON envelopes whose root key depends on the endpoint (sbcResponse, Response, sbcInvoicePaymentStatusResponse, sbcSeries, sbcTaxes, stocks, Fault). This module centralises request building, auth, envelope unwrapping and error mapping so the clients stay DRY.

Defined Under Namespace

Classes: RateLimiter, Request

Constant Summary collapse

DEFAULT_BASE_URL =
"https://ws.smartbill.ro/SBORO/api/"
DEFAULT_TIMEOUT =
30.0
ENVELOPE_KEYS =

Envelope root keys used by SmartBill responses.

%w[
  sbcResponse
  Response
  sbcInvoicePaymentStatusResponse
  sbcSeries
  sbcTaxes
  stocks
  Fault
].freeze
ERROR_FIELDS =

Fields that may carry an error message inside an envelope.

%w[errorText errorTextError].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Attribute Details

#body Serialized request body (may be nil).(Serializedrequestbody(may be nil)) ⇒ Object (readonly)

A request value object built by Smartbill::Sdk::Transport.build_request and sent by an adapter.



14
# File 'lib/smartbill/sdk/transport/request.rb', line 14

Request = Struct.new(:http_method, :url, :headers, :query, :body, keyword_init: true)

#headers Hash of HTTP headers.(HashofHTTPheaders.) ⇒ Object (readonly)

A request value object built by Smartbill::Sdk::Transport.build_request and sent by an adapter.



14
# File 'lib/smartbill/sdk/transport/request.rb', line 14

Request = Struct.new(:http_method, :url, :headers, :query, :body, keyword_init: true)

#http_method HTTP method ("GET", "POST", ...).(HTTPmethod("GET", "POST", ...)) ⇒ Object (readonly)

A request value object built by Smartbill::Sdk::Transport.build_request and sent by an adapter.



14
# File 'lib/smartbill/sdk/transport/request.rb', line 14

Request = Struct.new(:http_method, :url, :headers, :query, :body, keyword_init: true)

#query Hash of query parameters (may be nil).(Hashofqueryparameters(may be nil)) ⇒ Object (readonly)

A request value object built by Smartbill::Sdk::Transport.build_request and sent by an adapter.



14
# File 'lib/smartbill/sdk/transport/request.rb', line 14

Request = Struct.new(:http_method, :url, :headers, :query, :body, keyword_init: true)

#url Full URL (without query string).(FullURL(without query string)) ⇒ Object (readonly)

A request value object built by Smartbill::Sdk::Transport.build_request and sent by an adapter.



14
# File 'lib/smartbill/sdk/transport/request.rb', line 14

Request = Struct.new(:http_method, :url, :headers, :query, :body, keyword_init: true)

Class Method Details

.build_auth(username, token) ⇒ Object

Alias kept for parity with the Python SDK.



40
41
42
# File 'lib/smartbill/sdk/transport.rb', line 40

def self.build_auth(username, token)
  build_auth_header(username, token)
end

.build_auth_header(username, token) ⇒ Object

Build the Authorization: Basic … header value for username:token.



35
36
37
# File 'lib/smartbill/sdk/transport.rb', line 35

def self.build_auth_header(username, token)
  "Basic #{Base64.strict_encode64("#{username}:#{token}")}"
end

.build_request(method:, base_url:, path:, params: nil, json_body: nil, accept: "application/json", content_type: "application/json", auth_header: nil) ⇒ Object

Construct a Request with SmartBill default headers.



45
46
47
48
49
50
51
52
53
# File 'lib/smartbill/sdk/transport.rb', line 45

def self.build_request(method:, base_url:, path:, params: nil, json_body: nil,
                       accept: "application/json", content_type: "application/json",
                       auth_header: nil)
  url = path.start_with?("http") ? path : "#{base_url.chomp("/")}/#{path.delete_prefix("/")}"
  headers = { "Accept" => accept, "Content-Type" => content_type }
  headers["Authorization"] = auth_header if auth_header
  body = json_body ? JSON.generate(json_body) : nil
  Request.new(http_method: method.upcase, url: url, headers: headers, query: params, body: body)
end

.extract_error(envelope) ⇒ Object

Extract (error_text, message) from an envelope.



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/smartbill/sdk/transport.rb', line 78

def self.extract_error(envelope)
  return ["", nil] unless envelope.is_a?(Hash)

  error_text = ""
  ERROR_FIELDS.each do |field|
    value = envelope[field]
    next unless value.is_a?(String) && !value.strip.empty?

    error_text = value
    break
  end
  message = envelope["message"]
  message = nil unless message.is_a?(String)
  [error_text, message]
end

.handle_response(response, binary: false) ⇒ Object

Validate a Response and return its parsed payload.

Raises the appropriate Error subclass for auth / rate-limit / API errors. When binary is true, the raw body String is returned (used for PDF endpoints).



99
100
101
102
103
104
105
106
107
108
# File 'lib/smartbill/sdk/transport.rb', line 99

def self.handle_response(response, binary: false)
  status = response.status
  raise auth_error(response) if status == 401
  raise rate_limit_error if status == 403

  return response.body if binary && (200..299).cover?(status)
  return handle_success(response, status, binary: binary) if (200..299).cover?(status)

  raise api_error_from(response, status)
end

.parse_envelope(payload) ⇒ Object

Unwrap the SmartBill response envelope and return its inner object.

If the payload is a Hash whose only key is one of the known envelope keys, the value under that key is returned. Otherwise the payload is returned unchanged.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/smartbill/sdk/transport.rb', line 60

def self.parse_envelope(payload)
  return payload unless payload.is_a?(Hash)

  if payload.size == 1
    key, value = payload.first
    return value if ENVELOPE_KEYS.include?(key)
  end
  # Some envelopes nest under a known key with sibling fields.
  ENVELOPE_KEYS.each do |key|
    next unless payload.key?(key)

    inner = payload[key]
    return inner if inner.is_a?(Hash)
  end
  payload
end