justimmo_client 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.
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