twelvedata_ruby 0.1.1
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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +80 -0
- data/LICENSE.txt +21 -0
- data/README.md +125 -0
- data/Rakefile +12 -0
- data/bin/console +22 -0
- data/bin/setup +8 -0
- data/doc/TwelvedataRuby.html +353 -0
- data/doc/TwelvedataRuby/BadRequestResponseError.html +178 -0
- data/doc/TwelvedataRuby/Client.html +1443 -0
- data/doc/TwelvedataRuby/Endpoint.html +1478 -0
- data/doc/TwelvedataRuby/EndpointError.html +247 -0
- data/doc/TwelvedataRuby/EndpointNameError.html +167 -0
- data/doc/TwelvedataRuby/EndpointParametersKeysError.html +167 -0
- data/doc/TwelvedataRuby/EndpointRequiredParametersError.html +167 -0
- data/doc/TwelvedataRuby/Error.html +318 -0
- data/doc/TwelvedataRuby/ForbiddenResponseError.html +178 -0
- data/doc/TwelvedataRuby/InternalServerResponseErro.html +178 -0
- data/doc/TwelvedataRuby/NotFoundResponseError.html +178 -0
- data/doc/TwelvedataRuby/PageNotFoundResponseError.html +178 -0
- data/doc/TwelvedataRuby/ParameterTooLongResponseError.html +178 -0
- data/doc/TwelvedataRuby/Request.html +683 -0
- data/doc/TwelvedataRuby/Response.html +1622 -0
- data/doc/TwelvedataRuby/ResponseError.html +565 -0
- data/doc/TwelvedataRuby/TooManyRequestsResponseError.html +178 -0
- data/doc/TwelvedataRuby/UnauthorizedResponseError.html +178 -0
- data/doc/TwelvedataRuby/Utils.html +503 -0
- data/doc/_index.html +315 -0
- data/doc/class_list.html +51 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +58 -0
- data/doc/css/style.css +497 -0
- data/doc/file.README.html +194 -0
- data/doc/file_list.html +56 -0
- data/doc/frames.html +17 -0
- data/doc/index.html +194 -0
- data/doc/js/app.js +314 -0
- data/doc/js/full_list.js +216 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +707 -0
- data/doc/top-level-namespace.html +110 -0
- data/lib/twelvedata_ruby.rb +43 -0
- data/lib/twelvedata_ruby/client.rb +148 -0
- data/lib/twelvedata_ruby/endpoint.rb +271 -0
- data/lib/twelvedata_ruby/error.rb +90 -0
- data/lib/twelvedata_ruby/request.rb +54 -0
- data/lib/twelvedata_ruby/response.rb +132 -0
- data/lib/twelvedata_ruby/utils.rb +36 -0
- data/twelvedata_ruby.gemspec +37 -0
- metadata +201 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>
|
7
|
+
Top Level Namespace
|
8
|
+
|
9
|
+
— Documentation by YARD 0.9.26
|
10
|
+
|
11
|
+
</title>
|
12
|
+
|
13
|
+
<link rel="stylesheet" href="css/style.css" type="text/css" />
|
14
|
+
|
15
|
+
<link rel="stylesheet" href="css/common.css" type="text/css" />
|
16
|
+
|
17
|
+
<script type="text/javascript">
|
18
|
+
pathId = "";
|
19
|
+
relpath = '';
|
20
|
+
</script>
|
21
|
+
|
22
|
+
|
23
|
+
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
|
24
|
+
|
25
|
+
<script type="text/javascript" charset="utf-8" src="js/app.js"></script>
|
26
|
+
|
27
|
+
|
28
|
+
</head>
|
29
|
+
<body>
|
30
|
+
<div class="nav_wrap">
|
31
|
+
<iframe id="nav" src="class_list.html?1"></iframe>
|
32
|
+
<div id="resizer"></div>
|
33
|
+
</div>
|
34
|
+
|
35
|
+
<div id="main" tabindex="-1">
|
36
|
+
<div id="header">
|
37
|
+
<div id="menu">
|
38
|
+
|
39
|
+
<a href="_index.html">Index</a> »
|
40
|
+
|
41
|
+
|
42
|
+
<span class="title">Top Level Namespace</span>
|
43
|
+
|
44
|
+
</div>
|
45
|
+
|
46
|
+
<div id="search">
|
47
|
+
|
48
|
+
<a class="full_list_link" id="class_list_link"
|
49
|
+
href="class_list.html">
|
50
|
+
|
51
|
+
<svg width="24" height="24">
|
52
|
+
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
|
53
|
+
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
|
54
|
+
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
|
55
|
+
</svg>
|
56
|
+
</a>
|
57
|
+
|
58
|
+
</div>
|
59
|
+
<div class="clear"></div>
|
60
|
+
</div>
|
61
|
+
|
62
|
+
<div id="content"><h1>Top Level Namespace
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
</h1>
|
67
|
+
<div class="box_info">
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
</div>
|
80
|
+
|
81
|
+
<h2>Defined Under Namespace</h2>
|
82
|
+
<p class="children">
|
83
|
+
|
84
|
+
|
85
|
+
<strong class="modules">Modules:</strong> <span class='object_link'><a href="TwelvedataRuby.html" title="TwelvedataRuby (module)">TwelvedataRuby</a></span>
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
</p>
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
</div>
|
101
|
+
|
102
|
+
<div id="footer">
|
103
|
+
Generated on Tue Jul 13 08:56:46 2021 by
|
104
|
+
<a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
105
|
+
0.9.26 (ruby-3.0.1).
|
106
|
+
</div>
|
107
|
+
|
108
|
+
</div>
|
109
|
+
</body>
|
110
|
+
</html>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "twelvedata_ruby/utils"
|
4
|
+
require_relative "twelvedata_ruby/error"
|
5
|
+
require_relative "twelvedata_ruby/endpoint"
|
6
|
+
require_relative "twelvedata_ruby/request"
|
7
|
+
require_relative "twelvedata_ruby/response"
|
8
|
+
require_relative "twelvedata_ruby/client"
|
9
|
+
|
10
|
+
# The one module that all the classes and modules of this gem are namespaced
|
11
|
+
|
12
|
+
module TwelvedataRuby
|
13
|
+
# Holds the current version
|
14
|
+
# @return [String] version number
|
15
|
+
VERSION = "0.1.1"
|
16
|
+
|
17
|
+
# A convenient and clearer way of getting and overriding default attribute values of the singleton `Client.instance`
|
18
|
+
#
|
19
|
+
# @param [Hash] options the optional Hash object that may contain values to override the defaults
|
20
|
+
# @option options [Symbol, String] :apikey the private key from Twelvedata API key
|
21
|
+
# @option options [Integer, String] :connect_timeout milliseconds
|
22
|
+
#
|
23
|
+
# @example Passing a nil options
|
24
|
+
# TwelvedataRuby.client
|
25
|
+
#
|
26
|
+
# The singleton instance object returned will use the default values for its attributes
|
27
|
+
#
|
28
|
+
# @example Passing values of `:apikey` and `:connect_timeout`
|
29
|
+
# TwelvedataRuby.client(apikey: "my-twelvedata-apikey", connect_timeout: 3000)
|
30
|
+
#
|
31
|
+
# @example or, chain with other Client instance method
|
32
|
+
# TwelvedataRuby.client(apikey: "my-twelvedata-apikey", connect_timeout: 3000).quote(symbol: "IBM")
|
33
|
+
#
|
34
|
+
# In the last example, calling `#quote`, a valid API endpoint, an instance method with the same name
|
35
|
+
# was dynamically defined and then fired up an API request to Twelvedata.
|
36
|
+
#
|
37
|
+
# @return [Client] singleton instance
|
38
|
+
def self.client(**options)
|
39
|
+
client = Client.instance
|
40
|
+
client.options = (client.options || {}).merge(options)
|
41
|
+
client
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "httpx"
|
4
|
+
require "singleton"
|
5
|
+
|
6
|
+
module TwelvedataRuby
|
7
|
+
# Responsible of the actual communication -- sending a valid request
|
8
|
+
# and receiving the response -- of the API web server
|
9
|
+
class Client
|
10
|
+
include Singleton
|
11
|
+
# @return [String] the exported shell ENV variable name that holds the apikey
|
12
|
+
APIKEY_ENV_NAME = "TWELVEDATA_API_KEY"
|
13
|
+
# @return [Integer] CONNECT_TIMEOUT default connection timeout in milliseconds
|
14
|
+
CONNECT_TIMEOUT = 120
|
15
|
+
# @return [String] valid URI base url string of the API
|
16
|
+
BASE_URL = "https://api.twelvedata.com"
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def request(request_objects, opts={})
|
20
|
+
HTTPX.with(options.merge(opts)).request(build_requests(request_objects))
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_requests(requests)
|
24
|
+
Utils.to_a(requests).map(&:build)
|
25
|
+
end
|
26
|
+
|
27
|
+
def origin
|
28
|
+
@origin ||= {origin: BASE_URL}
|
29
|
+
end
|
30
|
+
|
31
|
+
def timeout
|
32
|
+
{timeout: {connect_timeout: instance.connect_timeout}}
|
33
|
+
end
|
34
|
+
|
35
|
+
def options
|
36
|
+
origin.merge(timeout)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @!attribute options
|
41
|
+
# @return [Hash] the options writeonly attribute that may contain values to override the default attribute values.
|
42
|
+
# This attribute writer was automatically called in @see TwelvedataRuby.client(**options).
|
43
|
+
# @see TwelvedataRuby.client
|
44
|
+
attr_writer :options
|
45
|
+
|
46
|
+
# @return [String] apikey value from the instance options Hash object
|
47
|
+
# but if nill use the value from +ENV[APIKEY_ENV_NAME]+
|
48
|
+
def apikey
|
49
|
+
Utils.empty_to_nil(options[:apikey]) || ENV[apikey_env_var_name]
|
50
|
+
end
|
51
|
+
|
52
|
+
# The writer method that can be used to pass manually the value of the +apikey+
|
53
|
+
# @param [String] apikey
|
54
|
+
# @return [String] +apikey+ value
|
55
|
+
def apikey=(apikey)
|
56
|
+
options[:apikey] = apikey
|
57
|
+
end
|
58
|
+
|
59
|
+
def connect_timeout
|
60
|
+
parse_connect_timeout(options[:connect_timeout])
|
61
|
+
end
|
62
|
+
|
63
|
+
def connect_timeout=(connect_timeout)
|
64
|
+
parse_connect_timeout(connect_timeout)
|
65
|
+
end
|
66
|
+
|
67
|
+
# The name of the ENVIRONMENT variable that may hold the value of the Twelve Data API key
|
68
|
+
# # @return [String] the ENV variable that will be used to fetch from ENV the value of the API key
|
69
|
+
def apikey_env_var_name
|
70
|
+
(options[:apikey_env_var_name] || APIKEY_ENV_NAME).upcase
|
71
|
+
end
|
72
|
+
|
73
|
+
# A setter helper method to configure the ENV variable name of the API key
|
74
|
+
# @param [String] apikey_env_var_name
|
75
|
+
# @return [String] the ENV variable name
|
76
|
+
# @see #apikey_env_var_name
|
77
|
+
def apikey_env_var_name=(apikey_env_var_name)
|
78
|
+
options[:apikey_env_var_name] = apikey_env_var_name
|
79
|
+
end
|
80
|
+
|
81
|
+
# The actual API fetch that transport the built request object.
|
82
|
+
# +Request#valid?+ guards the actual fetch and instead will return a Hash instance of endpoint errors.
|
83
|
+
# If +Request#valid?+ returns true, request object will be sent to the API and returned response will
|
84
|
+
# will be resolved which may or may not contain a kind of +ResponseError+ instance.
|
85
|
+
# @see Response.resolve for more details
|
86
|
+
|
87
|
+
# @param [Request] request built API request object that holds the endpoint payload
|
88
|
+
#
|
89
|
+
# @return [NilClass] +nil+ if @param +request+ is not truthy
|
90
|
+
# @return [Hash] :errors if the request is not valid will hold the endpoint errors details
|
91
|
+
# @see Endpoint#errors
|
92
|
+
# @return [Response] if +request+ is valid and received an actual response from the API server.
|
93
|
+
# The response object's #error may or may not return a kind of ResponseError
|
94
|
+
# @see Response#error
|
95
|
+
# @return [ResponseError] if the response received did not come from the API server itself.
|
96
|
+
#
|
97
|
+
def fetch(request)
|
98
|
+
return nil unless request
|
99
|
+
|
100
|
+
request.valid? ? Response.resolve(self.class.request(request), request) : {errors: request.errors}
|
101
|
+
end
|
102
|
+
|
103
|
+
# The entry point in dynamically defining instance methods based on the called the valid endpoint names.
|
104
|
+
# @param [String] endpoint_name valid API endpoint name to fetch
|
105
|
+
# @param [Hash] endpoint_params the optional/required valid query params of the API endpoint.
|
106
|
+
# If +:apikey+ key-value pair is present, the pair will override the +#apikey+ of singleton client instance
|
107
|
+
# If +:format+ key-value pair is present and is a valid parameter key and value can only be +:csv+ or +:json+
|
108
|
+
# If +:filename+ key-value is present and +:format+ is +:csv+, then this is will be added to the payload too.
|
109
|
+
# Otherwise, this will just discarded and will not be part of the payload
|
110
|
+
# If endpoint name and query params used are not valid, EndpointError instances will be returned
|
111
|
+
# actual API fetch will not happen. @see #fetch for the rest of the documentation
|
112
|
+
#
|
113
|
+
# @todo define all the method signatures of the endpoint methods that will meta-programatically defined at runtime.
|
114
|
+
def method_missing(endpoint_name, **endpoint_params, &_block)
|
115
|
+
try_fetch(endpoint_name, endpoint_params) || super
|
116
|
+
end
|
117
|
+
|
118
|
+
def options
|
119
|
+
@options || @options = {}
|
120
|
+
end
|
121
|
+
|
122
|
+
def respond_to_missing?(endpoint_name, _include_all=false)
|
123
|
+
Utils.return_nil_unless_true(Endpoint.valid_name?(endpoint_name)) {
|
124
|
+
define_endpoint_method(endpoint_name)
|
125
|
+
} || super
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def build_request(endpoint_name, endpoint_params)
|
131
|
+
Request.new(endpoint_name, **endpoint_params)
|
132
|
+
end
|
133
|
+
|
134
|
+
def try_fetch(endpoint_name, endpoint_params)
|
135
|
+
respond_to?(endpoint_name) ? fetch(build_request(endpoint_name, endpoint_params)) : nil
|
136
|
+
end
|
137
|
+
|
138
|
+
def define_endpoint_method(endpoint_name)
|
139
|
+
self.class.define_method(endpoint_name) do |**qparams|
|
140
|
+
fetch(build_request(__method__, qparams))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def parse_connect_timeout(milliseconds)
|
145
|
+
options[:connect_timeout] = Utils.to_d(milliseconds, CONNECT_TIMEOUT)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TwelvedataRuby
|
4
|
+
class Endpoint
|
5
|
+
DEFAULT_FORMAT = :json
|
6
|
+
VALID_FORMATS = [DEFAULT_FORMAT, :csv].freeze
|
7
|
+
|
8
|
+
DEFINITIONS = {
|
9
|
+
api_usage: {
|
10
|
+
parameters: {keys: %i[format]},
|
11
|
+
response: {keys: %i[timestamp current_usage plan_limit]}
|
12
|
+
},
|
13
|
+
stocks: {
|
14
|
+
parameters: {keys: %i[symbol exchange country type format]},
|
15
|
+
response: {data_keys: %i[symbol name currency exchange country type], collection: :data}
|
16
|
+
},
|
17
|
+
forex_pairs: {
|
18
|
+
parameters: {keys: %i[symbol currency_base currency_quote format]},
|
19
|
+
response: {data_keys: %i[symbol currency_group currency_base currency_quote], collection: :data}
|
20
|
+
},
|
21
|
+
cryptocurrencies: {
|
22
|
+
parameters: {keys: %i[symbol exchange currency_base currency_quote format]},
|
23
|
+
response: {data_keys: %i[symbol available_exchanges currency_base currency_quote], collection: :data}
|
24
|
+
},
|
25
|
+
etf: {
|
26
|
+
parameters: {keys: %i[symbol format]},
|
27
|
+
response: {data_keys: %i[symbol name currency exchange], collection: :data}
|
28
|
+
},
|
29
|
+
indices: {
|
30
|
+
parameters: {keys: %i[symbol country format]},
|
31
|
+
response: {data_keys: %i[symbol name country currency], collection: :data}
|
32
|
+
},
|
33
|
+
exchanges: {
|
34
|
+
parameters: {keys: %i[type name code country format]},
|
35
|
+
response: {data_keys: %i[name country code timezone], collection: :data}
|
36
|
+
},
|
37
|
+
cryptocurrency_exchanges: {
|
38
|
+
parameters: {keys: %i[name format]},
|
39
|
+
response: {data_keys: %i[name], collection: :data}
|
40
|
+
},
|
41
|
+
technical_indicators: {
|
42
|
+
parameters: {keys: []},
|
43
|
+
response: {
|
44
|
+
keys: %i[enable full_name description type overlay parameters output_values tinting]
|
45
|
+
}
|
46
|
+
},
|
47
|
+
symbol_search: {
|
48
|
+
parameters: {keys: %i[symbol outputsize], required: %i[symbol]},
|
49
|
+
response: {
|
50
|
+
data_keys: %i[symbol instrument_name exchange exchange_timezone instrument_type country],
|
51
|
+
collection: :data
|
52
|
+
}
|
53
|
+
},
|
54
|
+
earliest_timestamp: {
|
55
|
+
parameters: {keys: %i[symbol interval exchange]},
|
56
|
+
response: {keys: %i[datetime unix_time]}
|
57
|
+
},
|
58
|
+
time_series: {
|
59
|
+
parameters: {
|
60
|
+
keys: %i[symbol interval exchange country type outputsize format],
|
61
|
+
required: %i[symbol interval]
|
62
|
+
},
|
63
|
+
response: {
|
64
|
+
value_keys: %i[datetime open high low close volume],
|
65
|
+
collection: :values,
|
66
|
+
meta_keys: %i[symbol interval currency exchange_timezone exchange type]
|
67
|
+
}
|
68
|
+
},
|
69
|
+
quote: {
|
70
|
+
parameters: {
|
71
|
+
keys: %i[symbol interval exchange country volume_time_period type format],
|
72
|
+
required: %i[symbol],
|
73
|
+
},
|
74
|
+
response: {
|
75
|
+
keys: %i[
|
76
|
+
symbol
|
77
|
+
name
|
78
|
+
exchange
|
79
|
+
currency
|
80
|
+
datetime
|
81
|
+
open
|
82
|
+
high
|
83
|
+
low
|
84
|
+
close
|
85
|
+
volume
|
86
|
+
previous_close
|
87
|
+
change
|
88
|
+
percent_change
|
89
|
+
average_volume
|
90
|
+
fifty_two_week
|
91
|
+
]
|
92
|
+
}
|
93
|
+
},
|
94
|
+
price: {
|
95
|
+
parameters: {keys: %i[symbol exchange country type format], required: %i[symbol]},
|
96
|
+
response: {keys: %i[price]}
|
97
|
+
},
|
98
|
+
eod: {
|
99
|
+
parameters: {keys: %i[symbol exchange country type], required: %i[symbol]},
|
100
|
+
response: {keys: %i[symbol exchange currency datetime close]}
|
101
|
+
},
|
102
|
+
exchange_rate: {
|
103
|
+
parameters: {keys: %i[symbol format], required: %i[symbol]},
|
104
|
+
response: {keys: %i[symbol rate timestamp]}
|
105
|
+
},
|
106
|
+
currency_conversion: {
|
107
|
+
parameters: {keys: %i[symbol amount format], required: %i[symbol amount]},
|
108
|
+
response: {keys: %i[symbol rate amount timestamp]}
|
109
|
+
},
|
110
|
+
complex_data: {
|
111
|
+
parameters: {
|
112
|
+
keys: %i[symbols intervals start_date end_date dp order timezone methods name],
|
113
|
+
required: %i[symbols intervals start_date end_date]
|
114
|
+
},
|
115
|
+
response: {keys: %i[data status]},
|
116
|
+
http_verb: :post
|
117
|
+
},
|
118
|
+
earnings: {
|
119
|
+
parameters: {keys: %i[symbol exchange country type period outputsize format], required: %i[symbol]},
|
120
|
+
response: {keys: %i[date time eps_estimate eps_actual difference surprise_prc]}
|
121
|
+
},
|
122
|
+
earnings_calendar: {
|
123
|
+
parameters: {keys: %i[format]},
|
124
|
+
response: {
|
125
|
+
keys: %i[
|
126
|
+
symbol
|
127
|
+
name
|
128
|
+
currency
|
129
|
+
exchange
|
130
|
+
country
|
131
|
+
time
|
132
|
+
eps_estimate
|
133
|
+
eps_estimate
|
134
|
+
eps_actual
|
135
|
+
difference
|
136
|
+
surprise_prc
|
137
|
+
]
|
138
|
+
}
|
139
|
+
}
|
140
|
+
}.freeze
|
141
|
+
|
142
|
+
class << self
|
143
|
+
def definitions
|
144
|
+
@definitions ||= DEFINITIONS.transform_values {|v|
|
145
|
+
v.merge(
|
146
|
+
parameters: {
|
147
|
+
keys: v[:parameters][:keys].push(:apikey),
|
148
|
+
required: (v[:parameters][:required] || []).push(:apikey)
|
149
|
+
}
|
150
|
+
)
|
151
|
+
}.to_h
|
152
|
+
end
|
153
|
+
|
154
|
+
def names
|
155
|
+
@names ||= definitions.keys
|
156
|
+
end
|
157
|
+
|
158
|
+
def default_apikey_params
|
159
|
+
{apikey: Client.instance.apikey}
|
160
|
+
end
|
161
|
+
|
162
|
+
def valid_name?(name)
|
163
|
+
names.include?(name.to_sym)
|
164
|
+
end
|
165
|
+
|
166
|
+
def valid_params?(name, **params)
|
167
|
+
new(name, **params).valid?
|
168
|
+
end
|
169
|
+
alias valid? valid_params?
|
170
|
+
end
|
171
|
+
|
172
|
+
attr_reader :name, :query_params
|
173
|
+
|
174
|
+
def initialize(name, **query_params)
|
175
|
+
self.name = name
|
176
|
+
self.query_params = query_params
|
177
|
+
end
|
178
|
+
|
179
|
+
def definition
|
180
|
+
@definition ||= self.class.definitions[name]
|
181
|
+
end
|
182
|
+
|
183
|
+
def errors
|
184
|
+
(@errors || {}).compact
|
185
|
+
end
|
186
|
+
|
187
|
+
def name=(name)
|
188
|
+
assign_attribute(:name, name.to_s.downcase.to_sym)
|
189
|
+
end
|
190
|
+
|
191
|
+
def parameters
|
192
|
+
return @parameters if definition.nil? || @parameters
|
193
|
+
|
194
|
+
params = definition[:parameters]
|
195
|
+
params.push(:filename) if params.include?(:format) && query_parameters[:format] == :csv
|
196
|
+
params
|
197
|
+
end
|
198
|
+
|
199
|
+
def parameters_keys
|
200
|
+
keys = parameters&.send(:[], :keys)
|
201
|
+
keys.push(:filename) if keys && query_params && query_params[:format] == :csv
|
202
|
+
keys
|
203
|
+
end
|
204
|
+
|
205
|
+
def query_params_keys
|
206
|
+
query_params.keys
|
207
|
+
end
|
208
|
+
|
209
|
+
def query_params=(query_params)
|
210
|
+
if (parameters_keys || []).include?(:format) &&
|
211
|
+
!VALID_FORMATS.include?(query_params[:format])
|
212
|
+
query_params[:format] = DEFAULT_FORMAT
|
213
|
+
end
|
214
|
+
query_params.delete(:filename) if query_params[:filename] && query_params[:format] != :csv
|
215
|
+
assign_attribute(:query_params, self.class.default_apikey_params.merge(query_params.compact))
|
216
|
+
end
|
217
|
+
|
218
|
+
def required_parameters
|
219
|
+
parameters&.send(:[], :required)
|
220
|
+
end
|
221
|
+
|
222
|
+
def valid?
|
223
|
+
valid_name? && valid_query_params?
|
224
|
+
end
|
225
|
+
|
226
|
+
def valid_at_attributes?(*attrs)
|
227
|
+
errors.values_at(*attrs).compact.empty?
|
228
|
+
end
|
229
|
+
|
230
|
+
def valid_name?
|
231
|
+
valid_at_attributes?(:name)
|
232
|
+
end
|
233
|
+
|
234
|
+
def valid_query_params?
|
235
|
+
valid_at_attributes?(:parameters_keys, :required_parameters)
|
236
|
+
end
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
def assign_attribute(attr_name, value)
|
241
|
+
@parameters = nil
|
242
|
+
@definition = nil
|
243
|
+
instance_variable_set(:"@#{attr_name}", value)
|
244
|
+
send(:"validate_#{attr_name}")
|
245
|
+
send(attr_name)
|
246
|
+
end
|
247
|
+
|
248
|
+
def init_error(attr_name, invalid_values, error_klass=nil)
|
249
|
+
error_klass ||= Kernel.const_get("#{self.class.name}#{Utils.camelize(attr_name)}Error")
|
250
|
+
error_klass.new(endpoint: self, invalid: invalid_values)
|
251
|
+
end
|
252
|
+
|
253
|
+
def update_errors(attrib, invalids, klass=nil)
|
254
|
+
@errors = errors.merge(attrib => !invalids.nil? && !invalids.empty? ? init_error(attrib, invalids, klass) : nil)
|
255
|
+
end
|
256
|
+
|
257
|
+
def validate_name
|
258
|
+
is_valid = self.class.valid_name?(name)
|
259
|
+
invalid_name = name.nil? || name.empty? ? "a blank name" : name
|
260
|
+
update_errors(:name, is_valid ? nil : invalid_name)
|
261
|
+
validate_query_params if is_valid && query_params && !valid_query_params?
|
262
|
+
end
|
263
|
+
|
264
|
+
def validate_query_params
|
265
|
+
return update_errors(:required_parameters, "Invalid name", EndpointError) unless parameters_keys
|
266
|
+
|
267
|
+
update_errors(:required_parameters, required_parameters.difference(query_params_keys))
|
268
|
+
update_errors(:parameters_keys, query_params_keys.difference(parameters_keys))
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|