twelvedata_ruby 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,90 +1,138 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TwelvedataRuby
4
+ # Base error class for all TwelvedataRuby errors
4
5
  class Error < StandardError
5
- DEFAULT_MSGS = {
6
- "EndpointError" => "Endpoint is not valid. %{invalid}",
7
- "EndpointNameError" => "`%{invalid}` is not a correct endpoint. Valid values are: `%{valid_names}`",
8
- "EndpointParametersKeysError" => "Invalid parameters found: `%{invalid}`. Valid parameters for `%{name}` "\
9
- "endpoint are: `%{parameters}`. Please see: `Twelvedata::Endpoint#parameters` for more details",
10
- "EndpointRequiredParametersError" => "Missing values for required parameters: `%{invalid}`. "\
11
- "`%{name}` endpoint required parameters are: `%{required}`.",
12
- "ResponseError" => "Encountered an error from the response"
6
+ # Default error messages for different error types
7
+ DEFAULT_MESSAGES = {
8
+ "EndpointError" => "Endpoint is not valid: %{invalid}",
9
+ "EndpointNameError" => "`%{invalid}` is not a valid endpoint. Valid endpoints: %{valid_names}",
10
+ "EndpointParametersKeysError" => "Invalid parameters: %{invalid}. Valid parameters for `%{name}`: %{parameters}",
11
+ "EndpointRequiredParametersError" => "Missing required parameters: %{invalid}.\
12
+ Required for `%{name}`: %{required}",
13
+ "ResponseError" => "Response error occurred",
14
+ "ConfigurationError" => "Configuration error: %{message}",
15
+ "NetworkError" => "Network error: %{message}",
13
16
  }.freeze
14
17
 
15
- attr_reader :attrs
18
+ attr_reader :attributes, :original_error
16
19
 
17
- def initialize(args={})
18
- @attrs = args[:attrs] || {}
19
- super((args[:message] || DEFAULT_MSGS[Utils.demodulize(self.class)]) % @attrs)
20
+ # Initialize error with message and attributes
21
+ #
22
+ # @param message [String, nil] Custom error message
23
+ # @param attributes [Hash] Error attributes for interpolation
24
+ # @param original_error [Exception, nil] Original exception that caused this error
25
+ def initialize(message: nil, attributes: {}, original_error: nil)
26
+ @attributes = attributes
27
+ @original_error = original_error
28
+
29
+ error_message = message || format_default_message
30
+ super(error_message)
31
+ end
32
+
33
+ private
34
+
35
+ def format_default_message
36
+ template = DEFAULT_MESSAGES[Utils.demodulize(self.class.name)]
37
+ return "An error occurred" unless template
38
+
39
+ format(template, **attributes)
40
+ rescue KeyError => e
41
+ "Error message template missing key: #{e.key}"
20
42
  end
21
43
  end
22
44
 
45
+ # Configuration-related errors
46
+ class ConfigurationError < Error; end
47
+
48
+ # Network-related errors
49
+ class NetworkError < Error; end
50
+
51
+ # Base class for endpoint-related errors
23
52
  class EndpointError < Error
24
- def initialize(**args)
25
- endpoint = args[:endpoint]
26
- super(
27
- attrs: {
28
- name: endpoint.name,
29
- invalid: args[:invalid],
30
- valid_names: endpoint.class.names.join(", "),
31
- parameters: endpoint&.parameters_keys&.send(:join, ", "),
32
- required: endpoint&.required_parameters&.send(:join, ", ")
33
- }
34
- )
53
+ def initialize(endpoint:, invalid:, **options)
54
+ attributes = {
55
+ name: endpoint.name,
56
+ invalid: invalid,
57
+ valid_names: endpoint.class.names.join(", "),
58
+ parameters: endpoint&.parameters_keys&.join(", "),
59
+ required: endpoint&.required_parameters&.join(", "),
60
+ }
61
+
62
+ super(attributes: attributes, **options)
35
63
  end
36
64
  end
37
65
 
66
+ # Error for invalid endpoint names
38
67
  class EndpointNameError < EndpointError; end
39
68
 
69
+ # Error for invalid endpoint parameters
40
70
  class EndpointParametersKeysError < EndpointError; end
41
71
 
72
+ # Error for missing required parameters
42
73
  class EndpointRequiredParametersError < EndpointError; end
43
74
 
75
+ # Base class for API response errors
44
76
  class ResponseError < Error
45
- API_ERROR_CODES_MAP = {
77
+ # Mapping of API error codes to specific error classes
78
+ API_ERROR_CODES = {
46
79
  400 => "BadRequestResponseError",
47
80
  401 => "UnauthorizedResponseError",
48
81
  403 => "ForbiddenResponseError",
49
82
  404 => "NotFoundResponseError",
50
83
  414 => "ParameterTooLongResponseError",
51
84
  429 => "TooManyRequestsResponseError",
52
- 500 => "InternalServerResponseError"
85
+ 500 => "InternalServerResponseError",
53
86
  }.freeze
54
- HTTP_ERROR_CODES_MAP = {
87
+
88
+ # Mapping of HTTP error codes to specific error classes
89
+ HTTP_ERROR_CODES = {
55
90
  404 => "PageNotFoundResponseError",
56
91
  }.freeze
57
92
 
58
- def self.error_code_klass(code, error_type=:api)
59
- error_type = :api unless %i[api http].member?(error_type)
60
-
61
- TwelvedataRuby::ResponseError.const_get("#{error_type.upcase}_ERROR_CODES_MAP")[code]
93
+ class << self
94
+ # Find appropriate error class for given code and type
95
+ #
96
+ # @param code [Integer] Error code
97
+ # @param error_type [Symbol] Type of error (:api or :http)
98
+ # @return [String, nil] Error class name
99
+ def error_class_for_code(code, error_type = :api)
100
+ case error_type
101
+ when :api
102
+ API_ERROR_CODES[code]
103
+ when :http
104
+ HTTP_ERROR_CODES[code]
105
+ end
106
+ end
62
107
  end
63
108
 
64
- attr_reader :json, :code, :request
65
-
66
- def initialize(json:, request:, attrs: nil, message: nil, code: nil)
67
- @json = json.is_a?(Hash) ? json : {}
68
- @code = code || @json[:code]
69
- @attrs = attrs || {}
109
+ attr_reader :json_data, :status_code, :request
110
+
111
+ # Initialize response error
112
+ #
113
+ # @param json_data [Hash] JSON response data
114
+ # @param request [Request] Original request object
115
+ # @param status_code [Integer] HTTP/API status code
116
+ # @param message [String, nil] Custom error message
117
+ def initialize(json_data: {}, request: nil, status_code: nil, message: nil, **options)
118
+ @json_data = json_data.is_a?(Hash) ? json_data : {}
119
+ @status_code = status_code || @json_data[:code]
70
120
  @request = request
71
- super(attrs: @attrs, message: "#{@json[:message] || message}")
121
+
122
+ error_message = message || @json_data[:message] || "Response error occurred"
123
+ super(message: error_message, **options)
72
124
  end
73
125
  end
74
126
 
127
+ # Specific API error classes
75
128
  class BadRequestResponseError < ResponseError; end
76
-
77
129
  class UnauthorizedResponseError < ResponseError; end
78
-
79
130
  class ForbiddenResponseError < ResponseError; end
80
-
81
131
  class NotFoundResponseError < ResponseError; end
82
-
83
- class PageNotFoundResponseError < ResponseError; end
84
-
85
132
  class ParameterTooLongResponseError < ResponseError; end
86
-
87
133
  class TooManyRequestsResponseError < ResponseError; end
134
+ class InternalServerResponseError < ResponseError; end
88
135
 
89
- class InternalServerResponseErro < ResponseError; end
136
+ # HTTP-specific error classes
137
+ class PageNotFoundResponseError < ResponseError; end
90
138
  end
@@ -1,54 +1,127 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "forwardable"
4
+
5
+ # Represents an API request to Twelve Data
4
6
  module TwelvedataRuby
5
7
  class Request
6
- extend Forwardable
8
+ extend Forwardable
7
9
 
8
- DEFAULT_HTTP_VERB = :get
10
+ # Default HTTP method for API requests
11
+ DEFAULT_HTTP_VERB = :get
9
12
 
10
- attr_reader :endpoint
13
+ def_delegators :endpoint, :name, :valid?, :query_params, :errors
11
14
 
12
- def initialize(name, **query_params)
13
- self.endpoint = Endpoint.new(name, **query_params)
14
- end
15
- def_delegators :endpoint, :name, :valid?, :query_params, :errors
15
+ attr_reader :endpoint
16
16
 
17
- def fetch
18
- Client.instance.fetch(self)
19
- end
17
+ # Initialize a new request
18
+ #
19
+ # @param name [Symbol, String] Endpoint name
20
+ # @param query_params [Hash] Query parameters for the request
21
+ def initialize(name, **query_params)
22
+ @endpoint = Endpoint.new(name, **query_params)
23
+ end
20
24
 
21
- def http_verb
22
- return_nil_unless_valid { endpoint.definition[:http_verb] || DEFAULT_HTTP_VERB }
23
- end
25
+ # Send the request using the client
26
+ #
27
+ # @return [Response, Hash, ResponseError] Response or error information
28
+ def fetch
29
+ Client.instance.fetch(self)
30
+ end
24
31
 
25
- def params
26
- {params: endpoint.query_params}
27
- end
32
+ # Get the HTTP verb for this request
33
+ #
34
+ # @return [Symbol, nil] HTTP verb or nil if invalid
35
+ def http_verb
36
+ return nil unless valid?
28
37
 
29
- def relative_url
30
- return_nil_unless_valid { name.to_s }
31
- end
38
+ endpoint.definition[:http_verb] || DEFAULT_HTTP_VERB
39
+ end
32
40
 
33
- def full_url
34
- return_nil_unless_valid { "#{Client.origin[:origin]}/#{relative_url}" }
35
- end
41
+ # Get request parameters formatted for HTTP client
42
+ #
43
+ # @return [Hash, nil] Parameters hash or nil if invalid
44
+ def params
45
+ return nil unless valid?
36
46
 
37
- def to_h
38
- return_nil_unless_valid { {http_verb: http_verb, relative_url: relative_url}.merge(params: params) }
39
- end
47
+ { params: endpoint.query_params }
48
+ end
40
49
 
41
- def to_a
42
- return_nil_unless_valid { [http_verb, relative_url, params] }
43
- end
44
- alias build to_a
50
+ # Get the relative URL path for this request
51
+ #
52
+ # @return [String, nil] URL path or nil if invalid
53
+ def relative_url
54
+ return nil unless valid?
55
+
56
+ name.to_s
57
+ end
45
58
 
46
- private
59
+ # Get the complete URL for this request
60
+ #
61
+ # @return [String, nil] Full URL or nil if invalid
62
+ def full_url
63
+ return nil unless valid?
47
64
 
48
- attr_writer :endpoint
65
+ "#{Client::BASE_URL}/#{relative_url}"
66
+ end
67
+
68
+ # Convert request to hash representation
69
+ #
70
+ # @return [Hash, nil] Request as hash or nil if invalid
71
+ def to_h
72
+ return nil unless valid?
73
+
74
+ {
75
+ http_verb: http_verb,
76
+ url: full_url,
77
+ params: query_params,
78
+ }
79
+ end
49
80
 
50
- def return_nil_unless_valid(&block)
51
- Utils.return_nil_unless_true(valid?) { block.call }
81
+ # Convert request to array format for HTTPX
82
+ #
83
+ # @return [Array, nil] Request as array or nil if invalid
84
+ def to_a
85
+ return nil unless valid?
86
+
87
+ [http_verb.to_s.upcase, full_url, params]
88
+ end
89
+ alias build to_a
90
+
91
+ # String representation of the request
92
+ #
93
+ # @return [String] Human-readable request description
94
+ def to_s
95
+ if valid?
96
+ "#{http_verb.to_s.upcase} #{full_url} with params: #{query_params}"
97
+ else
98
+ "Invalid request for endpoint: #{name}"
52
99
  end
53
100
  end
101
+
102
+ # Detailed inspection of the request
103
+ #
104
+ # @return [String] Detailed request information
105
+ def inspect
106
+ "#<#{self.class.name}:#{object_id} endpoint=#{name} valid=#{valid?} params=#{query_params.keys}>"
107
+ end
108
+
109
+ # Check equality with another request
110
+ #
111
+ # @param other [Request] Request to compare with
112
+ # @return [Boolean] True if requests are equivalent
113
+ def ==(other)
114
+ return false unless other.is_a?(self.class)
115
+
116
+ name == other.name && query_params == other.query_params
117
+ end
118
+ alias eql? ==
119
+
120
+ # Generate hash code for request
121
+ #
122
+ # @return [Integer] Hash code
123
+ def hash
124
+ [name, query_params].hash
125
+ end
126
+ end
54
127
  end