flexmls_api 0.6.5 → 0.7.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 (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