flexmls_api 0.6.5 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/History.txt +12 -1
  2. data/VERSION +1 -1
  3. data/lib/flexmls_api.rb +13 -11
  4. data/lib/flexmls_api/authentication/api_auth.rb +5 -3
  5. data/lib/flexmls_api/authentication/oauth2.rb +23 -17
  6. data/lib/flexmls_api/authentication/oauth2_impl/password_provider.rb +25 -0
  7. data/lib/flexmls_api/cli.rb +51 -25
  8. data/lib/flexmls_api/cli/oauth2.rb +1 -30
  9. data/lib/flexmls_api/cli/setup.rb +4 -1
  10. data/lib/flexmls_api/client.rb +1 -1
  11. data/lib/flexmls_api/configuration.rb +10 -0
  12. data/lib/flexmls_api/configuration/yaml.rb +81 -0
  13. data/lib/flexmls_api/faraday.rb +12 -2
  14. data/lib/flexmls_api/models.rb +25 -23
  15. data/lib/flexmls_api/models/constraint.rb +16 -0
  16. data/lib/flexmls_api/models/listing.rb +45 -2
  17. data/lib/flexmls_api/models/photo.rb +57 -2
  18. data/lib/flexmls_api/models/subresource.rb +5 -2
  19. data/lib/flexmls_api/multi_client.rb +21 -6
  20. data/lib/flexmls_api/paginate.rb +18 -1
  21. data/lib/flexmls_api/request.rb +1 -71
  22. data/lib/flexmls_api/response.rb +69 -0
  23. data/spec/fixtures/{contacts.json → contacts/contacts.json} +0 -0
  24. data/spec/fixtures/{contact_my.json → contacts/my.json} +0 -0
  25. data/spec/fixtures/{contact_new.json → contacts/new.json} +0 -0
  26. data/spec/fixtures/{contact_new_empty.json → contacts/new_empty.json} +0 -0
  27. data/spec/fixtures/{contact_new_notify.json → contacts/new_notify.json} +0 -0
  28. data/spec/fixtures/{contacts_post.json → contacts/post.json} +0 -0
  29. data/spec/fixtures/{contact_tags.json → contacts/tags.json} +0 -0
  30. data/spec/fixtures/{listing_cart_add_listing.json → listing_carts/add_listing.json} +0 -0
  31. data/spec/fixtures/{listing_cart_add_listing_post.json → listing_carts/add_listing_post.json} +0 -0
  32. data/spec/fixtures/{listing_cart_empty.json → listing_carts/empty.json} +0 -0
  33. data/spec/fixtures/{listing_cart.json → listing_carts/listing_cart.json} +0 -0
  34. data/spec/fixtures/{listing_cart_new.json → listing_carts/new.json} +0 -0
  35. data/spec/fixtures/{listing_cart_post.json → listing_carts/post.json} +0 -0
  36. data/spec/fixtures/{listing_cart_remove_listing.json → listing_carts/remove_listing.json} +0 -0
  37. data/spec/fixtures/listings/constraints.json +18 -0
  38. data/spec/fixtures/listings/constraints_with_pagination.json +24 -0
  39. data/spec/fixtures/{listing_document_index.json → listings/document_index.json} +0 -0
  40. data/spec/fixtures/{listing_no_subresources.json → listings/no_subresources.json} +0 -0
  41. data/spec/fixtures/{open_houses.json → listings/open_houses.json} +0 -0
  42. data/spec/fixtures/{listing_photos_index.json → listings/photos/index.json} +0 -0
  43. data/spec/fixtures/listings/photos/new.json +12 -0
  44. data/spec/fixtures/listings/photos/post.json +20 -0
  45. data/spec/fixtures/listings/put.json +5 -0
  46. data/spec/fixtures/{saved_search.json → listings/saved_search.json} +0 -0
  47. data/spec/fixtures/{shared_listing_new.json → listings/shared_listing_new.json} +0 -0
  48. data/spec/fixtures/{shared_listing_post.json → listings/shared_listing_post.json} +0 -0
  49. data/spec/fixtures/{tour_of_homes.json → listings/tour_of_homes.json} +0 -0
  50. data/spec/fixtures/{listing_videos_index.json → listings/videos_index.json} +0 -0
  51. data/spec/fixtures/{listing_virtual_tours_index.json → listings/virtual_tours_index.json} +0 -0
  52. data/spec/fixtures/{listing_with_documents.json → listings/with_documents.json} +0 -0
  53. data/spec/fixtures/listings/with_permissions.json +44 -0
  54. data/spec/fixtures/{listing_with_photos.json → listings/with_photos.json} +0 -0
  55. data/spec/fixtures/{listing_with_supplement.json → listings/with_supplement.json} +0 -0
  56. data/spec/fixtures/{listing_with_videos.json → listings/with_videos.json} +0 -0
  57. data/spec/fixtures/{listing_with_vtour.json → listings/with_vtour.json} +0 -0
  58. data/spec/fixtures/logo_fbs.png +0 -0
  59. data/spec/fixtures/{add_note.json → notes/add.json} +0 -0
  60. data/spec/fixtures/{agent_shared_note.json → notes/agent_shared.json} +0 -0
  61. data/spec/fixtures/{agent_shared_note_empty.json → notes/agent_shared_empty.json} +0 -0
  62. data/spec/fixtures/{note_new.json → notes/new.json} +0 -0
  63. data/spec/fixtures/{standardfields_city.json → standardfields/city.json} +0 -0
  64. data/spec/fixtures/{standardfields_nearby.json → standardfields/nearby.json} +0 -0
  65. data/spec/fixtures/{standardfields.json → standardfields/standardfields.json} +0 -0
  66. data/spec/fixtures/{standardfields_stateorprovince.json → standardfields/stateorprovince.json} +0 -0
  67. data/spec/mock_helper.rb +5 -3
  68. data/spec/unit/flexmls_api/authentication/api_auth_spec.rb +11 -2
  69. data/spec/unit/flexmls_api/authentication/base_auth_spec.rb +10 -0
  70. data/spec/unit/flexmls_api/authentication/oauth2_spec.rb +3 -3
  71. data/spec/unit/flexmls_api/configuration/yaml_spec.rb +70 -0
  72. data/spec/unit/flexmls_api/models/constraint_spec.rb +19 -0
  73. data/spec/unit/flexmls_api/models/contact_spec.rb +8 -8
  74. data/spec/unit/flexmls_api/models/document_spec.rb +1 -1
  75. data/spec/unit/flexmls_api/models/listing_cart_spec.rb +15 -15
  76. data/spec/unit/flexmls_api/models/listing_spec.rb +78 -13
  77. data/spec/unit/flexmls_api/models/note_spec.rb +4 -4
  78. data/spec/unit/flexmls_api/models/open_house_spec.rb +1 -1
  79. data/spec/unit/flexmls_api/models/photo_spec.rb +73 -40
  80. data/spec/unit/flexmls_api/models/saved_search_spec.rb +3 -3
  81. data/spec/unit/flexmls_api/models/shared_listing_spec.rb +1 -1
  82. data/spec/unit/flexmls_api/models/standard_fields_spec.rb +4 -7
  83. data/spec/unit/flexmls_api/models/tour_of_home_spec.rb +1 -1
  84. data/spec/unit/flexmls_api/models/video_spec.rb +1 -1
  85. data/spec/unit/flexmls_api/models/virtual_tour_spec.rb +1 -1
  86. data/spec/unit/flexmls_api/multi_client_spec.rb +9 -1
  87. data/spec/unit/flexmls_api/paginate_spec.rb +1 -1
  88. data/spec/unit/flexmls_api/request_spec.rb +2 -2
  89. metadata +182 -152
@@ -3,7 +3,7 @@ module FlexmlsApi
3
3
  #=Flexmls API Faraday middleware
4
4
  # HTTP Response after filter to package api responses and bubble up basic api errors.
5
5
  class FlexmlsMiddleware < Faraday::Response::ParseJson
6
-
6
+ include FlexmlsApi::PaginateHelper
7
7
  # Handles pretty much all the api response parsing and error handling. All responses that
8
8
  # indicate a failure will raise a FlexmlsApi::ClientError exception
9
9
  def on_complete(finished_env)
@@ -13,6 +13,16 @@ module FlexmlsApi
13
13
  raise InvalidResponse, "The server response could not be understood"
14
14
  end
15
15
  response = ApiResponse.new body
16
+ paging = response.pagination
17
+ if paging.nil?
18
+ results = response
19
+ else
20
+ if finished_env[:url].query_values["_pagination"] == "count"
21
+ results = paging['TotalRows']
22
+ else
23
+ results = paginate_response(response, paging)
24
+ end
25
+ end
16
26
  case finished_env[:status]
17
27
  when 400, 409
18
28
  raise BadResourceRequest, {:message => response.message, :code => response.code, :status => finished_env[:status]}
@@ -33,7 +43,7 @@ module FlexmlsApi
33
43
  else
34
44
  raise ClientError, {:message => response.message, :code => response.code, :status => finished_env[:status]}
35
45
  end
36
- finished_env[:body] = response
46
+ finished_env[:body] = results
37
47
  end
38
48
 
39
49
  def initialize(app)
@@ -1,26 +1,28 @@
1
- require File.expand_path('../models/base', __FILE__)
2
- require File.expand_path('../models/finders', __FILE__)
3
- require File.expand_path('../models/account', __FILE__)
4
- require File.expand_path('../models/listing', __FILE__)
5
- require File.expand_path('../models/subresource', __FILE__)
6
- require File.expand_path('../models/photo', __FILE__)
7
- require File.expand_path('../models/system_info', __FILE__)
8
- require File.expand_path('../models/standard_fields', __FILE__)
9
- require File.expand_path('../models/custom_fields', __FILE__)
10
- require File.expand_path('../models/property_types', __FILE__)
11
- require File.expand_path('../models/connect_prefs', __FILE__)
12
- require File.expand_path('../models/contact', __FILE__)
13
- require File.expand_path('../models/idx_link', __FILE__)
14
- require File.expand_path('../models/market_statistics', __FILE__)
15
- require File.expand_path('../models/video', __FILE__)
16
- require File.expand_path('../models/open_house', __FILE__)
17
- require File.expand_path('../models/tour_of_home', __FILE__)
18
- require File.expand_path('../models/virtual_tour', __FILE__)
19
- require File.expand_path('../models/document', __FILE__)
20
- require File.expand_path('../models/note', __FILE__)
21
- require File.expand_path('../models/listing_cart.rb', __FILE__)
22
- require File.expand_path('../models/shared_listing.rb', __FILE__)
23
- require File.expand_path('../models/saved_search.rb', __FILE__)
1
+ require 'flexmls_api/models/base'
2
+ require 'flexmls_api/models/finders'
3
+ require 'flexmls_api/models/constraint'
4
+
5
+ require 'flexmls_api/models/account'
6
+ require 'flexmls_api/models/listing'
7
+ require 'flexmls_api/models/subresource'
8
+ require 'flexmls_api/models/photo'
9
+ require 'flexmls_api/models/system_info'
10
+ require 'flexmls_api/models/standard_fields'
11
+ require 'flexmls_api/models/custom_fields'
12
+ require 'flexmls_api/models/property_types'
13
+ require 'flexmls_api/models/connect_prefs'
14
+ require 'flexmls_api/models/contact'
15
+ require 'flexmls_api/models/idx_link'
16
+ require 'flexmls_api/models/market_statistics'
17
+ require 'flexmls_api/models/video'
18
+ require 'flexmls_api/models/open_house'
19
+ require 'flexmls_api/models/tour_of_home'
20
+ require 'flexmls_api/models/virtual_tour'
21
+ require 'flexmls_api/models/document'
22
+ require 'flexmls_api/models/note'
23
+ require 'flexmls_api/models/listing_cart'
24
+ require 'flexmls_api/models/shared_listing'
25
+ require 'flexmls_api/models/saved_search'
24
26
 
25
27
  module FlexmlsApi
26
28
  module Models
@@ -0,0 +1,16 @@
1
+ module FlexmlsApi
2
+ module Models
3
+ class Constraint
4
+ ATTRIBUTES = ["RuleValue","Value","RuleFieldValue","RuleField","RuleName"]
5
+ attr_accessor *ATTRIBUTES
6
+ def initialize(args)
7
+ ATTRIBUTES.each { |f| send("#{f}=", args[f]) if args.include?(f) || args.include?(f.to_sym) }
8
+ end
9
+
10
+ def to_s
11
+ "#{self.RuleName}: Field(#{self.RuleField},#{self.RuleFieldValue}) Value(#{self.RuleValue},#{self.Value})"
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -3,14 +3,17 @@ module FlexmlsApi
3
3
  class Listing < Base
4
4
  extend Finders
5
5
  attr_accessor :photos, :videos, :virtual_tours, :documents
6
+ attr_accessor :constraints
6
7
  self.element_name="listings"
8
+ DATA_MASK = "********"
7
9
 
8
10
  def initialize(attributes={})
9
11
  @photos = []
10
12
  @videos = []
11
13
  @virtual_tours = []
12
14
  @documents = []
13
-
15
+ @constraints = []
16
+
14
17
  if attributes.has_key?('StandardFields')
15
18
  pics, vids, tours, docs = attributes['StandardFields'].values_at('Photos','Videos', 'VirtualTours', 'Documents')
16
19
  end
@@ -19,7 +22,7 @@ module FlexmlsApi
19
22
  pics.collect { |pic| @photos.push(Photo.new(pic)) }
20
23
  attributes['StandardFields'].delete('Photos')
21
24
  end
22
-
25
+
23
26
  if vids != nil
24
27
  vids.collect { |vid| @videos.push(Video.new(vid)) }
25
28
  attributes['StandardFields'].delete('Videos')
@@ -88,6 +91,46 @@ module FlexmlsApi
88
91
  end
89
92
  end
90
93
 
94
+ def street_address
95
+ "#{self.StreetNumber} #{self.StreetDirPrefix} #{self.StreetName} #{self.StreetSuffix} #{self.StreetDirSuffix} #{self.StreetAdditionalInfo}".delete(DATA_MASK).strip().gsub(/\s{2,}/, ' ')
96
+ end
97
+
98
+ def region_address
99
+ "#{self.City}, #{self.StateOrProvince} #{self.PostalCode}".delete(DATA_MASK).strip().gsub(/^,\s/, '').gsub(/,$/, '')
100
+ end
101
+
102
+ def full_address
103
+ "#{self.street_address}, #{self.region_address}".strip().gsub(/^,\s/, '').gsub(/,$/, '')
104
+ end
105
+
106
+ def save(arguments={})
107
+ begin
108
+ return save!(arguments)
109
+ rescue NotFound, BadResourceRequest => e
110
+ FlexmlsApi.logger.error("Failed to save resource #{self}: #{e.message}")
111
+ end
112
+ false
113
+ end
114
+ def save!(arguments={})
115
+ results = connection.put "#{self.class.path}/#{self.Id}", {"ListPrice"=> self.ListPrice}, arguments
116
+ @contstraints = []
117
+ results.details.each do |detail|
118
+ detail.each_pair do |k,v|
119
+ v.each { |constraint| @constraints << Constraint.new(constraint)}
120
+ end
121
+ end
122
+ true
123
+ end
124
+
125
+ def editable?(editable_settings = [])
126
+ settings = Array(editable_settings)
127
+ editable = attributes.include?("Permissions") && self.Permissions["Editable"] == true
128
+ if editable
129
+ settings.each{ |setting| editable = false unless self.Permissions["EditableSettings"][setting.to_s] == true }
130
+ end
131
+ editable
132
+ end
133
+
91
134
 
92
135
  private
93
136
 
@@ -2,13 +2,68 @@ module FlexmlsApi
2
2
  module Models
3
3
  class Photo < Base
4
4
  extend Subresource
5
-
6
5
  self.element_name = "photos"
6
+
7
+ attr_accessor :update_path
8
+
9
+ EDITABLE_FIELDS = [:Picture, :FileName, :Name, :Caption, :Primary]
10
+
11
+ def initialize(opts={})
12
+ defaulted_opts = {}
13
+ EDITABLE_FIELDS.each do |k|
14
+ key = k.to_s()
15
+ defaulted_opts[key] = opts[key] || nil
16
+ end
17
+ super(opts.merge(defaulted_opts))
18
+ end
7
19
 
8
20
  def primary?
9
21
  @attributes["Primary"] == true
10
22
  end
11
-
23
+
24
+ def save(arguments={})
25
+ begin
26
+ return save!(arguments)
27
+ rescue NotFound, BadResourceRequest => e
28
+ FlexmlsApi.logger.error("Failed to save resource #{self}: #{e.message}")
29
+ end
30
+ false
31
+ end
32
+ def save!(arguments={})
33
+ payload = {"Photos" => [ build_photo_hash]}
34
+ if exists?
35
+ results = connection.put "#{update_path}/#{self.Id}", payload, arguments
36
+ else
37
+ results = connection.post update_path, payload, arguments
38
+ end
39
+ result = results.first
40
+ load(result)
41
+ true
42
+ end
43
+
44
+ def load_picture(file_name)
45
+ self.Picture = Base64.encode64(File.open(file_name, 'rb').read).gsub(/\n/, '')
46
+ self.FileName = File.basename(file_name)
47
+ end
48
+
49
+ def delete(args={})
50
+ connection.delete("#{update_path}/#{self.Id}", args)
51
+ end
52
+
53
+ def exists?
54
+ @attributes.include?("Id")
55
+ end
56
+
57
+ private
58
+
59
+ def build_photo_hash
60
+ results_hash = {}
61
+ EDITABLE_FIELDS.each do |k|
62
+ key = k.to_s
63
+ results_hash[key] = @attributes[key] unless @attributes[key].nil?
64
+ end
65
+ results_hash
66
+ end
12
67
 
13
68
  end
14
69
  end
@@ -6,11 +6,14 @@ module FlexmlsApi
6
6
  Class.new(self)
7
7
  end
8
8
 
9
-
10
9
  def find_by_listing_key(key, arguments={})
11
10
  collect(connection.get("/listings/#{key}#{self.path}", arguments))
12
11
  end
13
-
12
+
13
+ def find_by_id(id, parent_id, arguments={})
14
+ collect(connection.get("/listings/#{parent_id}#{self.path}/#{id}", arguments)).first
15
+ end
16
+
14
17
  end
15
18
  end
16
19
  end
@@ -1,6 +1,7 @@
1
1
  module FlexmlsApi
2
2
  #===Active support for multiple clients
3
3
  module MultiClient
4
+ include FlexmlsApi::Configuration
4
5
 
5
6
  # Activate a specific instance of the client (with appropriate config settings). Each client
6
7
  # is lazily instanciated by calling the matching FlexmlsApi.symbol_name method on the
@@ -13,8 +14,8 @@ module FlexmlsApi
13
14
  def activate(sym)
14
15
  if block_given?
15
16
  original_client = Thread.current[:flexmls_api_client]
16
- activate_client(sym)
17
17
  begin
18
+ activate_client(sym)
18
19
  yield
19
20
  ensure
20
21
  Thread.current[:flexmls_api_client] = original_client
@@ -27,11 +28,25 @@ module FlexmlsApi
27
28
  private
28
29
 
29
30
  # set the active client for the symbol
30
- def activate_client(sym)
31
- active_client = Thread.current[sym] || FlexmlsApi.send(sym)
32
- Thread.current[:flexmls_api_client] = active_client unless active_client.nil?
33
- rescue NoMethodError => e
34
- raise ArgumentError, "The symbol #{sym} is missing a corresponding FlexmlsApi.#{sym} method.", e.backtrace
31
+ def activate_client(symbol)
32
+ if !Thread.current[symbol].nil?
33
+ active_client = Thread.current[symbol]
34
+ elsif FlexmlsApi.respond_to? symbol
35
+ active_client = FlexmlsApi.send(symbol)
36
+ elsif YamlConfig.exists? symbol.to_s
37
+ active_client = activate_client_from_config(symbol)
38
+ else
39
+ raise ArgumentError, "The symbol #{symbol} is not setup correctly as a multi client key."
40
+ end
41
+ Thread.current[symbol] = active_client
42
+ Thread.current[:flexmls_api_client] = active_client
35
43
  end
44
+
45
+ def activate_client_from_config(symbol)
46
+ FlexmlsApi.logger.debug("Loading multiclient [#{symbol.to_s}] from config")
47
+ yaml = YamlConfig.build(symbol.to_s)
48
+ Client.new(yaml.client_keys)
49
+ end
50
+
36
51
  end
37
52
  end
@@ -55,13 +55,28 @@ module FlexmlsApi
55
55
  def per_page
56
56
  DEFAULT_PAGE_SIZE
57
57
  end
58
+
59
+
58
60
  end
59
61
 
60
62
  # ==Paginate Api Responses
61
- # Module use by the request layer to decorate the response's results array with paging support.
63
+ # Module used by the request layer to decorate the response's results array with paging support.
62
64
  # Pagination only happens if the response includes the pagination information as specified by the
63
65
  # API.
64
66
  module PaginateResponse
67
+ attr_accessor :results
68
+ def method_missing(method_symbol, *arguments)
69
+ if results.respond_to?(method_symbol)
70
+ arguments.empty? ? self.results.send(method_symbol) : self.results.send(method_symbol, arguments)
71
+ else
72
+ super
73
+ end
74
+ end
75
+ end
76
+
77
+ # ==Pagination Helpers
78
+ # Helpers to create the pagination collection
79
+ module PaginateHelper
65
80
  # ==Enable pagination
66
81
  # * results -- array of hashes representing api resources
67
82
  # * paging_hash -- the pagination response information from the api representing paging state.
@@ -73,6 +88,8 @@ module FlexmlsApi
73
88
  paged_results = WillPaginate::Collection.create(pager.current_page, pager.page_size, pager.total_rows) do |p|
74
89
  p.replace(results)
75
90
  end
91
+ paged_results.extend PaginateResponse
92
+ paged_results.results = results
76
93
  paged_results
77
94
  end
78
95
  end
@@ -3,7 +3,6 @@ require 'cgi'
3
3
  module FlexmlsApi
4
4
  # HTTP request wrapper. Performs all the api session mumbo jumbo so that the models don't have to.
5
5
  module Request
6
- include PaginateResponse
7
6
  # Perform an HTTP GET request
8
7
  #
9
8
  # * path - Path of an api resource, excluding version and endpoint (domain) information
@@ -89,78 +88,9 @@ module FlexmlsApi
89
88
  FlexmlsApi.logger.error("Authentication failed or server is sending us expired tokens, nothing we can do here.")
90
89
  raise
91
90
  end
92
- results = response.body.results
93
- paging = response.body.pagination
94
- unless paging.nil?
95
- if request_opts[:_pagination] == "count"
96
- results = paging['TotalRows']
97
- else
98
- results = paginate_response(results, paging)
99
- end
100
- end
101
- results
91
+ response.body
102
92
  end
103
93
 
104
94
  end
105
95
 
106
- # All known response codes listed in the API
107
- module ResponseCodes
108
- NOT_FOUND = 404
109
- METHOD_NOT_ALLOWED = 405
110
- INVALID_KEY = 1000
111
- DISABLED_KEY = 1010
112
- API_USER_REQUIRED = 1015
113
- SESSION_TOKEN_EXPIRED = 1020
114
- SSL_REQUIRED = 1030
115
- INVALID_JSON = 1035
116
- INVALID_FIELD = 1040
117
- MISSING_PARAMETER = 1050
118
- INVALID_PARAMETER = 1053
119
- CONFLICTING_DATA = 1055
120
- NOT_AVAILABLE= 1500
121
- RATE_LIMIT_EXCEEDED = 1550
122
- end
123
-
124
- # Errors built from API responses
125
- class InvalidResponse < StandardError; end
126
- class ClientError < StandardError
127
- attr_reader :code, :status
128
- def initialize (options = {})
129
- # Support the standard initializer for errors
130
- opts = options.is_a?(Hash) ? options : {:message => options.to_s}
131
- @code = opts[:code]
132
- @status = opts[:status]
133
- super(opts[:message])
134
- end
135
-
136
- end
137
- class NotFound < ClientError; end
138
- class PermissionDenied < ClientError; end
139
- class NotAllowed < ClientError; end
140
- class BadResourceRequest < ClientError; end
141
-
142
- # Nice and handy class wrapper for the api response hash
143
- class ApiResponse
144
- attr_accessor :code, :message, :results, :success, :pagination
145
- def initialize(d)
146
- begin
147
- hash = d["D"]
148
- if hash.nil? || hash.empty?
149
- raise InvalidResponse, "The server response could not be understood"
150
- end
151
- self.message = hash["Message"]
152
- self.code = hash["Code"]
153
- self.results = hash["Results"]
154
- self.success = hash["Success"]
155
- self.pagination = hash["Pagination"]
156
- rescue Exception => e
157
- FlexmlsApi.logger.error "Unable to understand the response! #{d}"
158
- raise
159
- end
160
- end
161
- def success?
162
- @success
163
- end
164
- end
165
-
166
96
  end
@@ -0,0 +1,69 @@
1
+ module FlexmlsApi
2
+ # API Response interface
3
+ module Response
4
+ ATTRIBUTES = [:code, :message, :results, :success, :pagination, :details]
5
+ attr_accessor *ATTRIBUTES
6
+ def success?
7
+ @success
8
+ end
9
+ end
10
+
11
+ # All known response codes listed in the API
12
+ module ResponseCodes
13
+ NOT_FOUND = 404
14
+ METHOD_NOT_ALLOWED = 405
15
+ INVALID_KEY = 1000
16
+ DISABLED_KEY = 1010
17
+ API_USER_REQUIRED = 1015
18
+ SESSION_TOKEN_EXPIRED = 1020
19
+ SSL_REQUIRED = 1030
20
+ INVALID_JSON = 1035
21
+ INVALID_FIELD = 1040
22
+ MISSING_PARAMETER = 1050
23
+ INVALID_PARAMETER = 1053
24
+ CONFLICTING_DATA = 1055
25
+ NOT_AVAILABLE= 1500
26
+ RATE_LIMIT_EXCEEDED = 1550
27
+ end
28
+
29
+ # Errors built from API responses
30
+ class InvalidResponse < StandardError; end
31
+ class ClientError < StandardError
32
+ attr_reader :code, :status
33
+ def initialize (options = {})
34
+ # Support the standard initializer for errors
35
+ opts = options.is_a?(Hash) ? options : {:message => options.to_s}
36
+ @code = opts[:code]
37
+ @status = opts[:status]
38
+ super(opts[:message])
39
+ end
40
+
41
+ end
42
+ class NotFound < ClientError; end
43
+ class PermissionDenied < ClientError; end
44
+ class NotAllowed < ClientError; end
45
+ class BadResourceRequest < ClientError; end
46
+
47
+ # Nice and handy class wrapper for the api response hash
48
+ class ApiResponse < ::Array
49
+ include FlexmlsApi::Response
50
+ def initialize(d)
51
+ begin
52
+ hash = d["D"]
53
+ if hash.nil? || hash.empty?
54
+ raise InvalidResponse, "The server response could not be understood"
55
+ end
56
+ self.message = hash["Message"]
57
+ self.code = hash["Code"]
58
+ self.results = Array(hash["Results"])
59
+ self.success = hash["Success"]
60
+ self.pagination = hash["Pagination"]
61
+ self.details = hash["Details"] || []
62
+ super(results)
63
+ rescue Exception => e
64
+ FlexmlsApi.logger.error "Unable to understand the response! #{d}"
65
+ raise
66
+ end
67
+ end
68
+ end
69
+ end