justimmo_client 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +15 -0
  3. data/.gitignore +15 -0
  4. data/.gitlab-ci.yml +67 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +90 -0
  7. data/.ruby-version +1 -0
  8. data/Gemfile +21 -0
  9. data/LICENSE +21 -0
  10. data/README.md +72 -0
  11. data/Rakefile +14 -0
  12. data/bin/console +21 -0
  13. data/bin/setup +8 -0
  14. data/examples/client.rb +16 -0
  15. data/justimmo_client.gemspec +41 -0
  16. data/lib/justimmo_client/api/v1/models/city.rb +16 -0
  17. data/lib/justimmo_client/api/v1/models/country.rb +44 -0
  18. data/lib/justimmo_client/api/v1/models/employee.rb +45 -0
  19. data/lib/justimmo_client/api/v1/models/federal_state.rb +15 -0
  20. data/lib/justimmo_client/api/v1/models/file.rb +64 -0
  21. data/lib/justimmo_client/api/v1/models/geo_location.rb +47 -0
  22. data/lib/justimmo_client/api/v1/models/image.rb +10 -0
  23. data/lib/justimmo_client/api/v1/models/justimmo_base.rb +15 -0
  24. data/lib/justimmo_client/api/v1/models/realty.rb +82 -0
  25. data/lib/justimmo_client/api/v1/models/realty_area.rb +49 -0
  26. data/lib/justimmo_client/api/v1/models/realty_category.rb +13 -0
  27. data/lib/justimmo_client/api/v1/models/realty_marketing.rb +12 -0
  28. data/lib/justimmo_client/api/v1/models/realty_price.rb +94 -0
  29. data/lib/justimmo_client/api/v1/models/realty_room_count.rb +35 -0
  30. data/lib/justimmo_client/api/v1/models/realty_type.rb +12 -0
  31. data/lib/justimmo_client/api/v1/models/realty_usage.rb +24 -0
  32. data/lib/justimmo_client/api/v1/models/region.rb +12 -0
  33. data/lib/justimmo_client/api/v1/representers/json/attachment_image_representer.rb +16 -0
  34. data/lib/justimmo_client/api/v1/representers/json/attachment_representer.rb +15 -0
  35. data/lib/justimmo_client/api/v1/representers/json/contact_representer.rb +30 -0
  36. data/lib/justimmo_client/api/v1/representers/json/justimmo_representer.rb +13 -0
  37. data/lib/justimmo_client/api/v1/representers/json/location_representer.rb +14 -0
  38. data/lib/justimmo_client/api/v1/representers/json/realty_category_representer.rb +21 -0
  39. data/lib/justimmo_client/api/v1/representers/json/realty_detail_representer.rb +69 -0
  40. data/lib/justimmo_client/api/v1/representers/json.rb +10 -0
  41. data/lib/justimmo_client/api/v1/representers/xml/city_representer.rb +18 -0
  42. data/lib/justimmo_client/api/v1/representers/xml/contact_representer.rb +15 -0
  43. data/lib/justimmo_client/api/v1/representers/xml/country_representer.rb +15 -0
  44. data/lib/justimmo_client/api/v1/representers/xml/employee_list_representer.rb +15 -0
  45. data/lib/justimmo_client/api/v1/representers/xml/employee_representer.rb +46 -0
  46. data/lib/justimmo_client/api/v1/representers/xml/federal_state_representer.rb +17 -0
  47. data/lib/justimmo_client/api/v1/representers/xml/geo_location_representer.rb +24 -0
  48. data/lib/justimmo_client/api/v1/representers/xml/justimmo_representer.rb +13 -0
  49. data/lib/justimmo_client/api/v1/representers/xml/realty_area_representer.rb +28 -0
  50. data/lib/justimmo_client/api/v1/representers/xml/realty_category_representer.rb +15 -0
  51. data/lib/justimmo_client/api/v1/representers/xml/realty_detail_representer.rb +92 -0
  52. data/lib/justimmo_client/api/v1/representers/xml/realty_list_representer.rb +85 -0
  53. data/lib/justimmo_client/api/v1/representers/xml/realty_price_representer.rb +53 -0
  54. data/lib/justimmo_client/api/v1/representers/xml/realty_representer.rb +32 -0
  55. data/lib/justimmo_client/api/v1/representers/xml/realty_room_count_representer.rb +22 -0
  56. data/lib/justimmo_client/api/v1/representers/xml/realty_type_representer.rb +14 -0
  57. data/lib/justimmo_client/api/v1/representers/xml/region_representer.rb +14 -0
  58. data/lib/justimmo_client/api/v1/representers/xml.rb +11 -0
  59. data/lib/justimmo_client/api/v1/requests/employee_request.rb +24 -0
  60. data/lib/justimmo_client/api/v1/requests/justimmo_request.rb +64 -0
  61. data/lib/justimmo_client/api/v1/requests/realty_request.rb +209 -0
  62. data/lib/justimmo_client/api/v1.rb +16 -0
  63. data/lib/justimmo_client/autoload.rb +17 -0
  64. data/lib/justimmo_client/core/caching.rb +55 -0
  65. data/lib/justimmo_client/core/config.rb +46 -0
  66. data/lib/justimmo_client/core/logging.rb +46 -0
  67. data/lib/justimmo_client/core/utils.rb +38 -0
  68. data/lib/justimmo_client/employee.rb +39 -0
  69. data/lib/justimmo_client/errors.rb +58 -0
  70. data/lib/justimmo_client/misc.rb +10 -0
  71. data/lib/justimmo_client/option_parser.rb +107 -0
  72. data/lib/justimmo_client/realty.rb +150 -0
  73. data/lib/justimmo_client/version.rb +5 -0
  74. data/lib/justimmo_client.rb +22 -0
  75. data/notes.md +19 -0
  76. metadata +271 -0
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JustimmoClient::V1
4
+ # @api private
5
+ module RealtyRequest
6
+ extend JustimmoRequest
7
+
8
+ # Mappings for the option parser
9
+ TRANSLATION_MAPPING = {
10
+ limit: :Limit,
11
+ offset: :Offset,
12
+ lang: :culture,
13
+ with_projects: :alleProjektObjekte,
14
+ number: :objektnummer,
15
+ price: :preis,
16
+ zip_code: :plz,
17
+ price_per_sqm: :preis_per_m2,
18
+ type: :objektart,
19
+ subtype: :subobjektart,
20
+ tag: :tagname,
21
+ room_count: :zimmer,
22
+ area: :flaeche,
23
+ living_area: :wohnflaeche,
24
+ floor_area: :nutzflaeche,
25
+ surface_area: :grundflaeche,
26
+ keyword: :stichwort,
27
+ country: :land,
28
+ federal_state: :bundesland,
29
+ small_undbanded: :small,
30
+ small2_unbranded: :s220x155,
31
+ small3_unbranded: :s312x208,
32
+ id: :objekt_id,
33
+ salutation: :anrede,
34
+ title: :titel,
35
+ first_name: :vorname,
36
+ last_name: :nachname,
37
+ phone: :tel,
38
+ location: :ort,
39
+ all: :alle
40
+ }.freeze
41
+
42
+ module_function
43
+
44
+ # @!group Realty Requests
45
+
46
+ # @param [Hash] params
47
+ # @return [String] The requested XML.
48
+ def list(**params)
49
+ get("objekt/list", list_option_parser.parse(params))
50
+ end
51
+
52
+ # @param [Integer] id
53
+ # @param [String, Symbol] lang
54
+ # @return [String] The requested XML.
55
+ def detail(id, lang: nil)
56
+ get("objekt/detail", detail_option_parser.parse(id: id, lang: lang))
57
+ end
58
+
59
+ # @param [Integer] id
60
+ # @param [Hash] params
61
+ # @return [String] The requested XML.
62
+ def inquiry(id, **params)
63
+ get("objekt/anfrage", inquiry_option_parser.parse(params.update(id: id)))
64
+ end
65
+
66
+ # @param [Hash] params
67
+ # @return [String] A JSON string containing an array of ids.
68
+ def ids(**params)
69
+ get("objekt/ids", ids_option_parser.parse(params))
70
+ end
71
+
72
+ # @todo implement this
73
+ # @param [Hash] params
74
+ # @return [File] The PDF file.
75
+ def expose(**params)
76
+ get("objekt/expose", params)
77
+ end
78
+
79
+ # @!group Basic Data Requests
80
+
81
+ # @param [Boolean] all (false)
82
+ # @return [String] The requested XML.
83
+ def categories(all: false)
84
+ get("objekt/kategorien", alle: all ? 1 : 0)
85
+ end
86
+
87
+ # @param [Boolean] all (false)
88
+ # @return [String] The requested XML.
89
+ def types(all: false)
90
+ get("objekt/objektarten", alle: all ? 1 : 0)
91
+ end
92
+
93
+ # @param [Boolean] all (false)
94
+ # @return [String] The requested XML.
95
+ def countries(all: false)
96
+ get("objekt/laender", alle: all ? 1 : 0)
97
+ end
98
+
99
+ # @param [Boolean] all (false)
100
+ # @param [Integer, String] country
101
+ # @return [String] The requested XML.
102
+ def federal_states(country:, all: false)
103
+ get("objekt/bundeslaender", land: country, alle: all ? 1 : 0)
104
+ end
105
+
106
+ # @param [Boolean] all (false)
107
+ # @param [Integer, String] country
108
+ # @param [Integer] federal_state
109
+ # @return [String] The requested XML.
110
+ def regions(country: nil, federal_state: nil, all: false)
111
+ get("objekt/regionen", land: country, bundesland: federal_state, alle: all ? 1 : 0)
112
+ end
113
+
114
+ # @param [Boolean] all (false)
115
+ # @param [Integer, String] country
116
+ # @param [Integer] federal_state
117
+ # @return [String] The requested XML.
118
+ def zip_codes_and_cities(country: nil, federal_state: nil, all: false)
119
+ get("objekt/plzsUndOrte", land: country, bundesland: federal_state, alle: all ? 1 : 0)
120
+ end
121
+
122
+ # @!group Option Parsers
123
+
124
+ # @return [Hash]
125
+ def list_option_parser
126
+ @option_parsers ||= {}
127
+ @option_parsers[:list] ||= JustimmoClient::OptionParser.new do |options|
128
+ options.mappings = TRANSLATION_MAPPING
129
+ options.range_suffix = %i[_von _bis]
130
+
131
+ options.add :limit
132
+ options.add :offset
133
+ options.add :lang
134
+ options.add :orderby, values: %w[price zip_code number created_at updated_at published_at]
135
+ options.add :ordertype, values: %w[asc desc]
136
+ options.add :picturesize, values: %w[small_unbranded small2_unbranded small3_unbranded medium_unbranded big_unbranded big2_unbranded medium big bin2]
137
+ options.add :with_projects, type: :bool
138
+ options.group :filter do |f|
139
+ f.add :price_min
140
+ f.add :price_max
141
+ f.add :price_per_sqm_min
142
+ f.add :price_per_sqm_max
143
+ f.add :type_id
144
+ f.add :subtype_id
145
+ f.add :tag
146
+ f.add :zip_code
147
+ f.add :zip_code_min
148
+ f.add :zip_code_max
149
+ f.add :room_count_min
150
+ f.add :room_count_max
151
+ f.add :number
152
+ f.add :number_min
153
+ f.add :number_max
154
+ f.add :area_min
155
+ f.add :area_max
156
+ f.add :living_area_min
157
+ f.add :living_area_max
158
+ f.add :floor_area_min
159
+ f.add :floor_area_max
160
+ f.add :surface_area_min
161
+ f.add :surface_area_max
162
+ f.add :keyword
163
+ f.add :country_id
164
+ f.add :federal_state_id
165
+ f.add :status_id
166
+ f.add :project_id
167
+ f.add :type
168
+ f.add :parent_id
169
+ f.add :updated_at_min, as: :aktualisiert_am_von
170
+ f.add :updated_at_max, as: :aktualisiert_am_bis
171
+ end
172
+ end
173
+ end
174
+
175
+ alias ids_option_parser list_option_parser
176
+
177
+ # @return [Hash]
178
+ def inquiry_option_parser
179
+ @option_parsers ||= {}
180
+ @option_parsers[:inquiry] = JustimmoClient::OptionParser.new do |options|
181
+ options.mappings = TRANSLATION_MAPPING
182
+ options.range_suffix = %i[_von _bis]
183
+
184
+ options.add :salutation_id
185
+ options.add :title
186
+ options.add :first_name
187
+ options.add :last_name
188
+ options.add :email
189
+ options.add :phone
190
+ options.add :message
191
+ options.add :street
192
+ options.add :zip_code
193
+ options.add :location
194
+ options.add :country
195
+ end
196
+ end
197
+
198
+ # @return [Hash]
199
+ def detail_option_parser
200
+ @option_parsers ||= {}
201
+ @option_parsers[:detail] = JustimmoClient::OptionParser.new do |options|
202
+ options.mappings = TRANSLATION_MAPPING
203
+
204
+ options.add :all
205
+ options.add :lang
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "justimmo_client/autoload"
4
+
5
+ module JustimmoClient
6
+ # Version 1 of the API
7
+ module V1
8
+ extend JustimmoClient::Utils
9
+
10
+ API_PATH = "#{__dir__}/v1"
11
+
12
+ autoload_dir "#{API_PATH}/models/*.rb"
13
+ autoload_dir "#{API_PATH}/representers/*.rb"
14
+ autoload_dir "#{API_PATH}/requests/*_request.rb"
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "justimmo_client/version"
4
+ require "justimmo_client/errors"
5
+ require "justimmo_client/misc"
6
+
7
+ module JustimmoClient
8
+ include JustimmoClient::Errors
9
+
10
+ autoload :Config, "justimmo_client/core/config"
11
+ autoload :Logging, "justimmo_client/core/logging"
12
+ autoload :Utils, "justimmo_client/core/utils"
13
+ autoload :Caching, "justimmo_client/core/caching"
14
+ autoload :Realty, "justimmo_client/realty"
15
+ autoload :Employee, "justimmo_client/employee"
16
+ autoload :OptionParser, "justimmo_client/option_parser"
17
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "justimmo_client/core/config"
4
+ require "justimmo_client/core/logging"
5
+
6
+ module JustimmoClient
7
+ # Caching support
8
+ # @api private
9
+ module Caching
10
+ extend JustimmoClient::Logging
11
+
12
+ class NullCache
13
+ def write(_key, _data, **options); end
14
+ def read(_key); end
15
+ end
16
+
17
+ class << self
18
+ # Returns the current cache
19
+ # @!attribute [rw] cache
20
+ def cache
21
+ @cache ||= default_cache
22
+ end
23
+
24
+ def cache=(c)
25
+ @cache = c
26
+ end
27
+
28
+ def default_cache
29
+ cache = JustimmoClient::Config.cache || NullCache.new
30
+ log.info("Using default cache #{cache.class}")
31
+ cache
32
+ end
33
+ end
34
+
35
+ def cache
36
+ JustimmoClient::Caching.cache
37
+ end
38
+
39
+ # TODO: JSON serialize/deserialize the cached value
40
+ def with_cache(key, **options)
41
+ log.debug("Looking up cache key #{key}")
42
+ data = cache.read(key)
43
+
44
+ if data.nil?
45
+ log.debug("Cache miss for #{key}")
46
+ data = yield
47
+ cache.write(key, data, options)
48
+ else
49
+ log.debug("Cache hit for #{key}")
50
+ end
51
+
52
+ data
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "active_support/configurable"
5
+ require "justimmo_client/errors"
6
+
7
+ module JustimmoClient
8
+ # Configuration options storage
9
+ # @api private
10
+ class Config
11
+ include ActiveSupport::Configurable
12
+
13
+ SUPPORTED_API_VERSIONS = [1].freeze
14
+ REQUIRED = %i[username password].freeze
15
+
16
+ config_accessor(:base_url) { "api.justimmo.at/rest" }
17
+ config_accessor(:secure) { true }
18
+ config_accessor(:api_ver) { 1 }
19
+ config_accessor(:username)
20
+ config_accessor(:password)
21
+ config_accessor(:credentials)
22
+ config_accessor(:debug) { false }
23
+ config_accessor(:cache) { nil }
24
+ config_accessor(:request_retries) { 3 }
25
+
26
+ class << self
27
+ def configure
28
+ super
29
+ self.credentials = Base64.urlsafe_encode64("#{username}:#{password}")
30
+ validate
31
+ end
32
+
33
+ def validate
34
+ missing = REQUIRED.select { |r| @_config[r].nil? }
35
+ raise MissingConfiguration, missing unless missing.empty?
36
+
37
+ supported_ver = SUPPORTED_API_VERSIONS.include?(api_ver)
38
+ raise UnsupportedAPIVersion, api_ver unless supported_ver
39
+ end
40
+
41
+ def url
42
+ "#{secure ? 'https' : 'http'}://#{base_url}/v#{api_ver}"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "justimmo_client/core/config"
5
+
6
+ module JustimmoClient
7
+ # Logging support
8
+ # @api private
9
+ module Logging
10
+ class << self
11
+ # Use the Rails or default logger if none is set.
12
+ # @!attribute [rw] logger
13
+ # @return [Logger]
14
+ def logger
15
+ @logger ||= rails_logger || default_logger
16
+ end
17
+
18
+ attr_writer :logger
19
+
20
+ def default_logger
21
+ logger = Logger.new($stdout)
22
+ logger.level = JustimmoClient::Config.debug ? Logger::DEBUG : Logger::INFO
23
+ logger.datetime_format = "%Y-%m-%d %H:%M:%S"
24
+ logger.progname = "JustimmoClient"
25
+ logger.formatter = proc do |severity, datetime, progname, message|
26
+ "[#{format("%-5s", severity)}] #{datetime} #{progname} #{message}\n"
27
+ end
28
+ logger
29
+ end
30
+
31
+ # The Ruby on Rails logger
32
+ # @return [Logger, nil] The logger object
33
+ def rails_logger
34
+ if ("true" == ENV.fetch("JUSTIMMO_USE_RAILS_LOGGER", "true")) && defined?(::Rails)
35
+ ::Rails&.logger
36
+ end
37
+ end
38
+ end
39
+
40
+ def logger
41
+ Logging.logger
42
+ end
43
+
44
+ alias log logger
45
+ end
46
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/inflections"
4
+
5
+ module JustimmoClient
6
+ # Useful utility methods
7
+ # @api private
8
+ module Utils
9
+ def autoload_dir(path)
10
+ dirname = File.dirname(path)
11
+
12
+ Dir[path].each do |f|
13
+ basename = File.basename(f, ".rb")
14
+ send :autoload, basename.classify, File.join(dirname, basename)
15
+ end
16
+ end
17
+
18
+ def versioned_api(*name)
19
+ (["JustimmoClient::V#{JustimmoClient::Config.api_ver}"] + name).join("::").constantize
20
+ end
21
+
22
+ def api(name)
23
+ "JustimmoClient::#{name.to_s.classify}".constantize
24
+ end
25
+
26
+ def representer(name, type = :xml)
27
+ versioned_api(type.to_s.classify, "#{name.to_s.classify}Representer")
28
+ end
29
+
30
+ def model(name)
31
+ versioned_api(name.to_s.classify)
32
+ end
33
+
34
+ def request(name)
35
+ versioned_api("#{name.to_s.classify}Request")
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JustimmoClient
4
+ # Public employee query interface
5
+ module Employee
6
+ extend JustimmoClient::Utils
7
+
8
+ module_function
9
+
10
+ # Retrieve a list of employee data.
11
+ # @return [Array<Object>]
12
+ def list
13
+ xml_response = request(:employee).list
14
+ model = Struct.new(:employees).new
15
+ representer(:employee_list).new(model).from_xml(xml_response).employees
16
+ rescue JustimmoClient::RetrievalFailed
17
+ []
18
+ end
19
+
20
+ # Retrieve detailed information about a single employee.
21
+ # @param id [Integer] The ID of the employee
22
+ # @return [Object]
23
+ def detail(id)
24
+ xml_response = request(:employee).detail(id)
25
+ model = model(:employee).new
26
+ representer(:employee).new(model).from_xml(xml_response)
27
+ rescue JustimmoClient::RetrievalFailed
28
+ nil
29
+ end
30
+
31
+ # @return [Array<Integer>] An array of employee IDs
32
+ def ids
33
+ json_response = request(:employee).ids
34
+ ::JSON.parse(json_response).map(&:to_i)
35
+ rescue JustimmoClient::RetrievalFailed
36
+ []
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Exceptions for internal use
4
+ module JustimmoClient::Errors
5
+ JustimmoError = Class.new(StandardError)
6
+ InitializationError = Class.new(JustimmoError)
7
+
8
+ # Raised when configuration validation fails.
9
+ ConfigurationError = Class.new(JustimmoError)
10
+
11
+ # Raised when authentication with the API fails.
12
+ class AuthenticationFailed < JustimmoError
13
+ def initialize
14
+ super("Authentication failed.")
15
+ end
16
+ end
17
+
18
+ # Raised when retrieval from the API fails.
19
+ class RetrievalFailed < JustimmoError
20
+ def initialize(message)
21
+ super("Failed to get data from the API: #{message}")
22
+ end
23
+ end
24
+
25
+ # A mapping could not be found in the mappings hash.
26
+ class MappingNotFound < JustimmoError
27
+ def initialize(map)
28
+ super("Could not find #{map} mapping.")
29
+ end
30
+ end
31
+
32
+ # A key could not be found in the specified mapping.
33
+ class KeyNotFound < JustimmoError
34
+ def initialize(key, map)
35
+ super("Key #{key} not found in #{map} mapping.")
36
+ end
37
+ end
38
+
39
+ class NotImplemented < JustimmoError
40
+ def initialize(meth)
41
+ super("Method #{meth} not implemented!")
42
+ end
43
+ end
44
+
45
+ # Raised when an unsupported API version is set.
46
+ class UnsupportedAPIVersion < ConfigurationError
47
+ def initialize(version)
48
+ super("API Version #{version} not supported.")
49
+ end
50
+ end
51
+
52
+ # Raised on missing required configuration options.
53
+ class MissingConfiguration < ConfigurationError
54
+ def initialize(missing)
55
+ super("Required configuration missing: #{missing}.")
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/inflector/inflections"
4
+
5
+ module JustimmoClient
6
+ ActiveSupport::Inflector.inflections do |inflect|
7
+ inflect.acronym "XML"
8
+ inflect.acronym "JSON"
9
+ end
10
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JustimmoClient
4
+ # @api private
5
+ class OptionParser
6
+ include JustimmoClient::Logging
7
+
8
+ attr_accessor :range_suffix
9
+ attr_accessor :mappings
10
+
11
+ def initialize(options = {})
12
+ @options = options
13
+ @mappings = {}
14
+ @range_suffix = %i[_min _max]
15
+ @context = nil
16
+
17
+ yield self if block_given?
18
+
19
+ parse unless options.empty?
20
+ end
21
+
22
+ def add(key, **options)
23
+ add_option(key, options)
24
+ end
25
+
26
+ def group(groupname)
27
+ @context = groupname.to_sym
28
+ yield self if block_given?
29
+ end
30
+
31
+ def parse(options = {})
32
+ out = {}
33
+
34
+ options.each do |key, value|
35
+ raise ArgumentError, "Invalid option: #{key}" unless @options.key?(key.to_sym)
36
+ group = group_of(key)
37
+
38
+ if group
39
+ out[group] ||= {}
40
+ out[group].update(parse_option(key.to_sym, value))
41
+ else
42
+ out.update(parse_option(key.to_sym, value))
43
+ end
44
+ end
45
+
46
+ out
47
+ end
48
+
49
+ private
50
+
51
+ def add_option(key, **options)
52
+ @options[key.to_sym] = {
53
+ group: @context,
54
+ type: options[:type],
55
+ as: options[:as],
56
+ values: options[:values]
57
+ }
58
+ end
59
+
60
+ def group_of(key)
61
+ @options.dig(key, :group)
62
+ end
63
+
64
+ def mapping(key)
65
+ @mappings.fetch(key, key)
66
+ end
67
+
68
+ def translate(key)
69
+ return @options[key][:as] if @options[key][:as]
70
+
71
+ suffix =
72
+ case key
73
+ when /(.*)_min/ then @range_suffix.first
74
+ when /(.*)_max/ then @range_suffix.last
75
+ when /(.*)_id/ then "_id"
76
+ else nil
77
+ end
78
+
79
+ key = ($1 || key).to_sym
80
+
81
+ "#{mapping(key)}#{suffix}".to_sym
82
+ end
83
+
84
+ def parse_option(key, value)
85
+ values = @options.dig(key, :values)
86
+ raise ArgumentError, "Value #{value} not supported" unless values.nil? || values.include?(value)
87
+
88
+ coerced =
89
+ case @options.dig(key, :type)
90
+ when :bool then i_to_bool(value)
91
+ else mapping(value)
92
+ end
93
+
94
+ { translate(key) => coerced }
95
+ end
96
+
97
+ def parse_range(key, range)
98
+ min, max = @options[key][:range_suffix]
99
+ api_param = @options[key][:mapped]
100
+ { "#{api_param}#{min}": range.first, "#{api_param}#{max}": range.last }
101
+ end
102
+
103
+ def i_to_bool(value)
104
+ value ? 1 : 0
105
+ end
106
+ end
107
+ end