frodo 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +2 -0
  3. data/.circleci/config.yml +54 -0
  4. data/.gitignore +24 -0
  5. data/.gitlab-ci.yml +9 -0
  6. data/.rspec +2 -0
  7. data/.ruby-gemset +1 -0
  8. data/.ruby-version +1 -0
  9. data/.travis.yml +75 -0
  10. data/CHANGELOG.md +163 -0
  11. data/Gemfile +4 -0
  12. data/LICENSE.txt +23 -0
  13. data/README.md +479 -0
  14. data/Rakefile +7 -0
  15. data/TODO.md +55 -0
  16. data/frodo.gemspec +39 -0
  17. data/images/frodo.jpg +0 -0
  18. data/lib/frodo/abstract_client.rb +11 -0
  19. data/lib/frodo/client.rb +6 -0
  20. data/lib/frodo/concerns/api.rb +292 -0
  21. data/lib/frodo/concerns/authentication.rb +32 -0
  22. data/lib/frodo/concerns/base.rb +84 -0
  23. data/lib/frodo/concerns/caching.rb +26 -0
  24. data/lib/frodo/concerns/connection.rb +79 -0
  25. data/lib/frodo/concerns/verbs.rb +68 -0
  26. data/lib/frodo/config.rb +143 -0
  27. data/lib/frodo/entity.rb +335 -0
  28. data/lib/frodo/entity_container.rb +75 -0
  29. data/lib/frodo/entity_set.rb +131 -0
  30. data/lib/frodo/errors.rb +70 -0
  31. data/lib/frodo/middleware/authentication/token.rb +13 -0
  32. data/lib/frodo/middleware/authentication.rb +87 -0
  33. data/lib/frodo/middleware/authorization.rb +18 -0
  34. data/lib/frodo/middleware/caching.rb +30 -0
  35. data/lib/frodo/middleware/custom_headers.rb +14 -0
  36. data/lib/frodo/middleware/gzip.rb +33 -0
  37. data/lib/frodo/middleware/instance_url.rb +20 -0
  38. data/lib/frodo/middleware/logger.rb +42 -0
  39. data/lib/frodo/middleware/multipart.rb +64 -0
  40. data/lib/frodo/middleware/odata_headers.rb +13 -0
  41. data/lib/frodo/middleware/raise_error.rb +47 -0
  42. data/lib/frodo/middleware.rb +33 -0
  43. data/lib/frodo/navigation_property/proxy.rb +80 -0
  44. data/lib/frodo/navigation_property.rb +29 -0
  45. data/lib/frodo/properties/binary.rb +50 -0
  46. data/lib/frodo/properties/boolean.rb +37 -0
  47. data/lib/frodo/properties/collection.rb +50 -0
  48. data/lib/frodo/properties/complex.rb +114 -0
  49. data/lib/frodo/properties/date.rb +27 -0
  50. data/lib/frodo/properties/date_time.rb +83 -0
  51. data/lib/frodo/properties/date_time_offset.rb +17 -0
  52. data/lib/frodo/properties/decimal.rb +54 -0
  53. data/lib/frodo/properties/enum.rb +62 -0
  54. data/lib/frodo/properties/float.rb +67 -0
  55. data/lib/frodo/properties/geography/base.rb +162 -0
  56. data/lib/frodo/properties/geography/line_string.rb +33 -0
  57. data/lib/frodo/properties/geography/point.rb +31 -0
  58. data/lib/frodo/properties/geography/polygon.rb +38 -0
  59. data/lib/frodo/properties/geography.rb +13 -0
  60. data/lib/frodo/properties/guid.rb +17 -0
  61. data/lib/frodo/properties/integer.rb +107 -0
  62. data/lib/frodo/properties/number.rb +14 -0
  63. data/lib/frodo/properties/string.rb +72 -0
  64. data/lib/frodo/properties/time.rb +40 -0
  65. data/lib/frodo/properties/time_of_day.rb +27 -0
  66. data/lib/frodo/properties.rb +32 -0
  67. data/lib/frodo/property.rb +139 -0
  68. data/lib/frodo/property_registry.rb +41 -0
  69. data/lib/frodo/query/criteria/comparison_operators.rb +49 -0
  70. data/lib/frodo/query/criteria/date_functions.rb +61 -0
  71. data/lib/frodo/query/criteria/geography_functions.rb +21 -0
  72. data/lib/frodo/query/criteria/lambda_operators.rb +27 -0
  73. data/lib/frodo/query/criteria/string_functions.rb +40 -0
  74. data/lib/frodo/query/criteria.rb +92 -0
  75. data/lib/frodo/query/in_batches.rb +58 -0
  76. data/lib/frodo/query.rb +221 -0
  77. data/lib/frodo/railtie.rb +19 -0
  78. data/lib/frodo/schema/complex_type.rb +79 -0
  79. data/lib/frodo/schema/enum_type.rb +95 -0
  80. data/lib/frodo/schema.rb +164 -0
  81. data/lib/frodo/service.rb +199 -0
  82. data/lib/frodo/service_registry.rb +52 -0
  83. data/lib/frodo/version.rb +3 -0
  84. data/lib/frodo.rb +67 -0
  85. data/spec/fixtures/auth_success_response.json +11 -0
  86. data/spec/fixtures/error.json +11 -0
  87. data/spec/fixtures/files/entity_to_xml.xml +18 -0
  88. data/spec/fixtures/files/error.xml +5 -0
  89. data/spec/fixtures/files/metadata.xml +150 -0
  90. data/spec/fixtures/files/metadata_with_error.xml +157 -0
  91. data/spec/fixtures/files/product_0.json +10 -0
  92. data/spec/fixtures/files/product_0.xml +28 -0
  93. data/spec/fixtures/files/products.json +106 -0
  94. data/spec/fixtures/files/products.xml +308 -0
  95. data/spec/fixtures/files/supplier_0.json +26 -0
  96. data/spec/fixtures/files/supplier_0.xml +32 -0
  97. data/spec/fixtures/leads.json +923 -0
  98. data/spec/fixtures/refresh_error_response.json +8 -0
  99. data/spec/frodo/abstract_client_spec.rb +13 -0
  100. data/spec/frodo/client_spec.rb +57 -0
  101. data/spec/frodo/concerns/authentication_spec.rb +79 -0
  102. data/spec/frodo/concerns/base_spec.rb +68 -0
  103. data/spec/frodo/concerns/caching_spec.rb +40 -0
  104. data/spec/frodo/concerns/connection_spec.rb +65 -0
  105. data/spec/frodo/config_spec.rb +127 -0
  106. data/spec/frodo/entity/shared_examples.rb +83 -0
  107. data/spec/frodo/entity_container_spec.rb +38 -0
  108. data/spec/frodo/entity_set_spec.rb +169 -0
  109. data/spec/frodo/entity_spec.rb +153 -0
  110. data/spec/frodo/errors_spec.rb +48 -0
  111. data/spec/frodo/middleware/authentication/token_spec.rb +87 -0
  112. data/spec/frodo/middleware/authentication_spec.rb +83 -0
  113. data/spec/frodo/middleware/authorization_spec.rb +17 -0
  114. data/spec/frodo/middleware/custom_headers_spec.rb +21 -0
  115. data/spec/frodo/middleware/gzip_spec.rb +68 -0
  116. data/spec/frodo/middleware/instance_url_spec.rb +27 -0
  117. data/spec/frodo/middleware/logger_spec.rb +21 -0
  118. data/spec/frodo/middleware/odata_headers_spec.rb +15 -0
  119. data/spec/frodo/middleware/raise_error_spec.rb +66 -0
  120. data/spec/frodo/navigation_property/proxy_spec.rb +46 -0
  121. data/spec/frodo/navigation_property_spec.rb +55 -0
  122. data/spec/frodo/properties/binary_spec.rb +50 -0
  123. data/spec/frodo/properties/boolean_spec.rb +72 -0
  124. data/spec/frodo/properties/collection_spec.rb +44 -0
  125. data/spec/frodo/properties/date_spec.rb +23 -0
  126. data/spec/frodo/properties/date_time_offset_spec.rb +30 -0
  127. data/spec/frodo/properties/date_time_spec.rb +23 -0
  128. data/spec/frodo/properties/decimal_spec.rb +50 -0
  129. data/spec/frodo/properties/float_spec.rb +45 -0
  130. data/spec/frodo/properties/geography/line_string_spec.rb +33 -0
  131. data/spec/frodo/properties/geography/point_spec.rb +29 -0
  132. data/spec/frodo/properties/geography/polygon_spec.rb +55 -0
  133. data/spec/frodo/properties/geography/shared_examples.rb +72 -0
  134. data/spec/frodo/properties/guid_spec.rb +17 -0
  135. data/spec/frodo/properties/integer_spec.rb +58 -0
  136. data/spec/frodo/properties/string_spec.rb +46 -0
  137. data/spec/frodo/properties/time_of_day_spec.rb +23 -0
  138. data/spec/frodo/properties/time_spec.rb +15 -0
  139. data/spec/frodo/property_registry_spec.rb +16 -0
  140. data/spec/frodo/property_spec.rb +71 -0
  141. data/spec/frodo/query/criteria_spec.rb +229 -0
  142. data/spec/frodo/query_spec.rb +156 -0
  143. data/spec/frodo/schema/complex_type_spec.rb +97 -0
  144. data/spec/frodo/schema/enum_type_spec.rb +112 -0
  145. data/spec/frodo/schema_spec.rb +113 -0
  146. data/spec/frodo/service_registry_spec.rb +19 -0
  147. data/spec/frodo/service_spec.rb +153 -0
  148. data/spec/frodo/usage_example_spec.rb +161 -0
  149. data/spec/spec_helper.rb +35 -0
  150. data/spec/support/coverage.rb +2 -0
  151. data/spec/support/fixture_helpers.rb +14 -0
  152. data/spec/support/middleware.rb +19 -0
  153. metadata +479 -0
@@ -0,0 +1,292 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+ require 'uri'
5
+ require 'frodo/concerns/verbs'
6
+
7
+ module Frodo
8
+ module Concerns
9
+ module API
10
+ extend Frodo::Concerns::Verbs
11
+
12
+ # Public: Helper methods for performing arbitrary actions against the API using
13
+ # various HTTP verbs.
14
+ #
15
+ # Examples
16
+ #
17
+ # # Perform a get request
18
+ # client.get '/api/data/v9.1/leads'
19
+ # client.api_get 'leads'
20
+ #
21
+ # # Perform a post request
22
+ # client.post '/api/data/v9.1/leads', { ... }
23
+ # client.api_post 'leads', { ... }
24
+ #
25
+ # # Perform a put request
26
+ # client.put '/api/data/v9.1/leads(073ca9c8-2a41-e911-a81d-000d3a1d5a0b)', { ... }
27
+ # client.api_put 'leads(073ca9c8-2a41-e911-a81d-000d3a1d5a0b)', { ... }
28
+ #
29
+ # # Perform a delete request
30
+ # client.delete '/api/data/v9.1/leads(073ca9c8-2a41-e911-a81d-000d3a1d5a0b)'
31
+ # client.api_delete 'leads(073ca9c8-2a41-e911-a81d-000d3a1d5a0b)'
32
+ #
33
+ # Returns the Faraday::Response.
34
+ define_verbs :get, :post, :put, :delete, :patch, :head
35
+
36
+ # Public: Return the metadata XML schema for the service
37
+ #
38
+ # Returns [String]
39
+ def metadata
40
+ api_get("$metadata").body
41
+ end
42
+
43
+ # Public: Execute a query and returns the result.
44
+ #
45
+ # Query can be the url_chunk per the OData V4 spec or
46
+ # a Frodo::Query. The latter being preferred
47
+ #
48
+ # Examples
49
+ #
50
+ # # Find the names of all Accounts
51
+ # client.query("leads?$filter=firstname eq 'yo'")
52
+ #
53
+ # or
54
+ #
55
+ # query = client.service['leads'].query
56
+ # query.where("firstname eq 'yo'")
57
+ # client.query(query)
58
+ #
59
+ # Returns a list of Frodo::Entity
60
+ def query(query)
61
+ url_chunk, entity_set = if query.is_a?(Frodo::Query)
62
+ [query.to_s, query.entity_set.name]
63
+ else
64
+ [query]
65
+ end
66
+
67
+ body = api_get(url_chunk).body
68
+
69
+ # if manual query as a string we detect the set on the response
70
+ entity_set = body['@odata.context'].split('#')[-1] if entity_set.nil?
71
+ build_entity(entity_set, body)
72
+ end
73
+
74
+ # Public: Insert a new record.
75
+ #
76
+ # entity_set - The set the entity belongs to
77
+ # attrs - Hash of attributes to set on the new record.
78
+ #
79
+ # Examples
80
+ #
81
+ # # Add a new lead
82
+ # client.create('leads', {"firstname" =>'Bob'})
83
+ # # => '073ca9c8-2a41-e911-a81d-000d3a1d5a0b'
84
+ #
85
+ # Returns the primary key value of the newly created entity.
86
+ # Returns false if something bad happens.
87
+ def create(*args)
88
+ create!(*args)
89
+ rescue *exceptions
90
+ false
91
+ end
92
+ alias insert create
93
+
94
+ # Public: Insert a new record.
95
+ #
96
+ # entity_set - The set the entity belongs to
97
+ # attrs - Hash of attributes to set on the new record.
98
+ #
99
+ # Examples
100
+ #
101
+ # # Add a new lead
102
+ # client.create!('leads', {"firstname" =>'Bob'})
103
+ # # => '073ca9c8-2a41-e911-a81d-000d3a1d5a0b'
104
+ #
105
+ # Returns the primary key value of the newly created entity.
106
+ # Raises exceptions if an error is returned from Dynamics.
107
+ def create!(entity_set, attrs)
108
+ entity = service[entity_set].new_entity(attrs)
109
+ url_chunk = to_url_chunk(entity)
110
+ url = api_post(url_chunk, attrs).headers['odata-entityid']
111
+ id = url.match(/\(.+\)/)[0]
112
+ end
113
+ alias insert! create!
114
+
115
+ # Public: Update a record.
116
+ #
117
+ # entity_set - The set the entity belongs to
118
+ # attrs - Hash of attributes to set on the record.
119
+ #
120
+ # Examples
121
+ #
122
+ # # Update the lead with id '073ca9c8-2a41-e911-a81d-000d3a1d5a0b'
123
+ # client.update('leads', "leadid": '073ca9c8-2a41-e911-a81d-000d3a1d5a0b', Name: 'Whizbang Corp')
124
+ #
125
+ # Returns true if the entity was successfully updated.
126
+ # Returns false if there was an error.
127
+ def update(*args)
128
+ update!(*args)
129
+ rescue *exceptions
130
+ false
131
+ end
132
+
133
+ # Public: Update a record.
134
+ #
135
+ # entity_set - The set the entity belongs to
136
+ # attrs - Hash of attributes to set on the record.
137
+ #
138
+ # Examples
139
+ #
140
+ # # Update the leads with id '073ca9c8-2a41-e911-a81d-000d3a1d5a0b'
141
+ # client.update!('leads', 'leadid' => '073ca9c8-2a41-e911-a81d-000d3a1d5a0b', "firstname" => 'Whizbang Corp')
142
+ #
143
+ # Returns true if the entity was successfully updated.
144
+ # Raises an exception if an error is returned from Dynamics.
145
+ def update!(entity_set, attrs)
146
+ entity = service[entity_set].new_entity(attrs)
147
+ url_chunk = to_url_chunk(entity)
148
+
149
+ raise ArgumentError, 'ID field missing from provided attributes' if entity.is_new?
150
+
151
+ api_patch url_chunk, attrs
152
+ true
153
+ end
154
+
155
+ # Public: Delete a record.
156
+ #
157
+ # entity_set - The set the entity belongs to
158
+ # id - The Dynamics primary key ID of the record.
159
+ #
160
+ # Examples
161
+ #
162
+ # # Delete the lead with id "073ca9c8-2a41-e911-a81d-000d3a1d5a0b"
163
+ # client.destroy('leads', "073ca9c8-2a41-e911-a81d-000d3a1d5a0b")
164
+ #
165
+ # Returns true if the entity was successfully deleted.
166
+ # Returns false if an error is returned from Dynamics.
167
+ def destroy(*args)
168
+ destroy!(*args)
169
+ rescue *exceptions
170
+ false
171
+ end
172
+
173
+ # Public: Delete a record.
174
+ #
175
+ # entity_set - The set the entity belongs to
176
+ # id - The Dynamics primary key ID of the record.
177
+ #
178
+ # Examples
179
+ #
180
+ # # Delete the lead with id "073ca9c8-2a41-e911-a81d-000d3a1d5a0b"
181
+ # client.destroy!('leads', "073ca9c8-2a41-e911-a81d-000d3a1d5a0b")
182
+ #
183
+ # Returns true of the entity was successfully deleted.
184
+ # Raises an exception if an error is returned from Dynamics.
185
+ def destroy!(entity_set, id)
186
+ query = service[entity_set].query
187
+ url_chunk = query.find(id).to_s
188
+ api_delete url_chunk
189
+ true
190
+ end
191
+
192
+ # Public: Finds a single record and returns all fields.
193
+ #
194
+ # entity_set - The set the entity belongs to
195
+ # id - The id of the record. If field is specified, id should be the id
196
+ # of the external field.
197
+ #
198
+ # Returns the Entity record.
199
+ def find(entity_set, id)
200
+ query = service[entity_set].query
201
+ url_chunk = query.find(id)
202
+
203
+ body = api_get(url_chunk).body
204
+ build_entity(entity_set, body)
205
+ end
206
+
207
+ # Public: Finds a single record and returns select fields.
208
+ #
209
+ # entity_set - The set the entity belongs to
210
+ # id - The id of the record. If field is specified, id should be the id
211
+ # of the external field.
212
+ # fields - A String array denoting the fields to select. If nil or empty array
213
+ # is passed, all fields are selected.
214
+ def select(entity_set, id, fields)
215
+ query = service[entity_set].query
216
+
217
+ fields.each{|field| query.select(field)}
218
+ url_chunk = query.find(id)
219
+
220
+ body = api_get(url_chunk).body
221
+ build_entity(entity_set, body)
222
+ end
223
+
224
+ # Public: Count the entity set or for the query passed
225
+ #
226
+ # entity_set or query - A String or a Frodo::Query. If String is passed,
227
+ # all entities for the set are counted.
228
+ def count(query)
229
+ url_chunk = if query.is_a?(Frodo::Query)
230
+ query.include_count
231
+ query.to_s
232
+ else
233
+ service[query].query.count
234
+ end
235
+
236
+ body = api_get(url_chunk).body
237
+
238
+ if query.is_a?(Frodo::Query)
239
+ body['@odata.count']
240
+ else
241
+ # Some servers (*cough* Microsoft *cough*) seem to return
242
+ # extraneous characters in the response.
243
+ # I found out that the _\xef\xbb\xbf contains probably invisible junk characters
244
+ # called the Unicode BOM (short name for: byte order mark).
245
+ body.scan(/\d+/).first.to_i
246
+ end
247
+ end
248
+
249
+ private
250
+
251
+ # Internal: Returns a path to an api endpoint based on configured client
252
+ #
253
+ # Examples
254
+ #
255
+ # api_path('leads')
256
+ # # => '/leads'
257
+ def api_path(path)
258
+ "#{options[:base_path]}/#{path}" || "/#{path}"
259
+ end
260
+
261
+ def build_entity(entity_set, data)
262
+ entity_options = service[entity_set].entity_options
263
+ single_entity?(data) ? parse_entity(data, entity_options) : parse_entities(data, entity_options)
264
+ end
265
+
266
+ def single_entity?(body)
267
+ body['@odata.context'] =~ /\$entity$/
268
+ end
269
+
270
+ def parse_entity(entity_json, entity_options)
271
+ Frodo::Entity.from_json(entity_json, entity_options)
272
+ end
273
+
274
+ def parse_entities(body, entity_options)
275
+ body['value'].map do |entity_data|
276
+ Frodo::Entity.from_json(entity_data, entity_options)
277
+ end
278
+ end
279
+
280
+ def to_url_chunk(entity)
281
+ primary_key = entity.get_property(entity.primary_key).url_value
282
+ set = entity.entity_set.name
283
+ entity.is_new? ? set : "#{set}(#{primary_key})"
284
+ end
285
+
286
+ # Internal: Errors that should be rescued from in non-bang methods
287
+ def exceptions
288
+ [Faraday::Error::ClientError]
289
+ end
290
+ end
291
+ end
292
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Frodo
4
+ module Concerns
5
+ module Authentication
6
+ # Public: Force an authentication
7
+ def authenticate!
8
+ unless authentication_middleware
9
+ raise AuthenticationError, 'No authentication middleware present'
10
+ end
11
+
12
+ middleware = authentication_middleware.new nil, self, options
13
+ middleware.authenticate!
14
+ end
15
+
16
+ # Internal: Determines what middleware will be used based on the options provided
17
+ def authentication_middleware
18
+ if oauth_refresh?
19
+ Frodo::Middleware::Authentication::Token
20
+ end
21
+ end
22
+
23
+ # Internal: Returns true if oauth token refresh flow should be used for
24
+ # authentication.
25
+ def oauth_refresh?
26
+ options[:refresh_token] &&
27
+ options[:client_id] &&
28
+ options[:client_secret]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Frodo
4
+ module Concerns
5
+ module Base
6
+ attr_reader :options
7
+
8
+ MIME_TYPES = {
9
+ json: 'application/json'
10
+ }
11
+
12
+ # Public: Creates a new client instance
13
+ #
14
+ # opts - A hash of options to be passed in (default: {}).
15
+ #
16
+ # :oauth_token - The String oauth access token to authenticate
17
+ # API calls (required unless password
18
+ # authentication is used).
19
+ # :refresh_token - The String refresh token to obtain fresh
20
+ # OAuth access tokens (required if oauth
21
+ # authentication is used).
22
+ # :instance_url - The String base url for all api requests
23
+ # (required if oauth authentication is used).
24
+ #
25
+ # :client_id - The oauth client id to use. Needed for both
26
+ # password and oauth authentication
27
+ # :client_secret - The oauth client secret to use.
28
+ #
29
+ # :host - The String hostname to use during
30
+ # authentication requests
31
+ # (default: 'login.microsoftonline.com').
32
+ #
33
+ # :base_path - The base path for the REST api. (default: '/')
34
+ #
35
+ # :authentication_retries - The number of times that client
36
+ # should attempt to reauthenticate
37
+ # before raising an exception (default: 3).
38
+ #
39
+ # :compress - Set to true to have Dynamics compress the
40
+ # response (default: false).
41
+ # :raw_json - Set to true to skip the conversion of
42
+ # Entities responses (default: false).
43
+ # :timeout - Faraday connection request read/open timeout.
44
+ # (default: nil).
45
+ #
46
+ # :proxy_uri - Proxy URI: 'http://proxy.example.com:port' or
47
+ # 'http://user@pass:proxy.example.com:port'
48
+ #
49
+ # :authentication_callback - A Proc that is called with the response body
50
+ # after a successful authentication.
51
+ #
52
+ # :request_headers - A hash containing custom headers that will be
53
+ # appended to each request
54
+
55
+ def initialize(opts = {})
56
+ raise ArgumentError, 'Please specify a hash of options' unless opts.is_a?(Hash)
57
+
58
+ # allow injecting the service for performance purpose such as
59
+ # when you have already a local schema
60
+ @service = opts.delete(:service)
61
+
62
+ @options = Hash[Frodo.configuration.options.map do |option|
63
+ [option, Frodo.configuration.send(option)]
64
+ end]
65
+
66
+ @options.merge! opts
67
+ yield builder if block_given?
68
+ end
69
+
70
+ def instance_url
71
+ authenticate! unless options[:instance_url]
72
+ options[:instance_url]
73
+ end
74
+
75
+ def service
76
+ @service ||= Frodo::Service.new(instance_url, strict: false, metadata_document: metadata)
77
+ end
78
+
79
+ def inspect
80
+ "#<#{self.class} @options=#{@options.inspect}>"
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Frodo
4
+ module Concerns
5
+ module Caching
6
+ # Public: Runs the block with caching disabled.
7
+ #
8
+ # block - A query/describe/etc.
9
+ #
10
+ # Returns the result of the block
11
+ def without_caching
12
+ options[:use_cache] = false
13
+ yield
14
+ ensure
15
+ options.delete(:use_cache)
16
+ end
17
+
18
+ private
19
+
20
+ # Internal: Cache to use for the caching middleware
21
+ def cache
22
+ options[:cache]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Frodo
4
+ module Concerns
5
+ module Connection
6
+ # Public: The Faraday::Builder instance used for the middleware stack. This
7
+ # can be used to insert an custom middleware.
8
+ #
9
+ # Examples
10
+ #
11
+ # # Add the instrumentation middleware for Rails.
12
+ # client.middleware.use FaradayMiddleware::Instrumentation
13
+ #
14
+ # Returns the Faraday::Builder for the Faraday connection.
15
+ def middleware
16
+ connection.builder
17
+ end
18
+ alias builder middleware
19
+
20
+ private
21
+
22
+ # Internal: Internal faraday connection where all requests go through
23
+ def connection
24
+ @connection ||= Faraday.new(options[:instance_url],
25
+ connection_options) do |builder|
26
+
27
+ # Converts the request into JSON.
28
+ builder.request :json
29
+ # Handles reauthentication for 403 responses.
30
+ if authentication_middleware
31
+ builder.use authentication_middleware, self, options
32
+ end
33
+ # Sets the oauth token in the headers.
34
+ builder.use Frodo::Middleware::Authorization, self, options
35
+ # Ensures the instance url is set.
36
+ builder.use Frodo::Middleware::InstanceURL, self, options
37
+ # Caches GET requests.
38
+ builder.use Frodo::Middleware::Caching, cache, options if cache
39
+ # Follows 30x redirects.
40
+ builder.use FaradayMiddleware::FollowRedirects
41
+ # Raises errors for 40x responses.
42
+ builder.use Frodo::Middleware::RaiseError
43
+ # Parses returned JSON response into a hash.
44
+ builder.response :json, content_type: /\bjson$/
45
+ # Compress/Decompress the request/response
46
+ unless adapter == :httpclient
47
+ builder.use Frodo::Middleware::Gzip, self, options
48
+ end
49
+ # Inject OData headers into requests
50
+ builder.use Frodo::Middleware::OdataHeaders, self, options
51
+ # Inject custom headers into requests
52
+ builder.use Frodo::Middleware::CustomHeaders, self, options
53
+ # Log request/responses
54
+ if Frodo.log?
55
+ builder.use Frodo::Middleware::Logger,
56
+ Frodo.configuration.logger,
57
+ options
58
+ end
59
+
60
+ builder.adapter adapter
61
+ end
62
+ end
63
+
64
+ def adapter
65
+ options[:adapter]
66
+ end
67
+
68
+ # Internal: Faraday Connection options
69
+ def connection_options
70
+ { request: {
71
+ timeout: options[:timeout],
72
+ open_timeout: options[:timeout]
73
+ },
74
+ proxy: options[:proxy_uri],
75
+ ssl: options[:ssl] }
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Frodo
4
+ module Concerns
5
+ module Verbs
6
+ # Internal: Define methods to handle a verb.
7
+ #
8
+ # verbs - A list of verbs to define methods for.
9
+ #
10
+ # Examples
11
+ #
12
+ # define_verbs :get, :post
13
+ #
14
+ # Returns nil.
15
+ def define_verbs(*verbs)
16
+ verbs.each do |verb|
17
+ define_verb(verb)
18
+ define_api_verb(verb)
19
+ end
20
+ end
21
+
22
+ # Internal: Defines a method to handle HTTP requests with the passed in
23
+ # verb.
24
+ #
25
+ # verb - Symbol name of the verb (e.g. :get).
26
+ #
27
+ # Examples
28
+ #
29
+ # define_verb :get
30
+ # # => get '/path/to/entity'
31
+ #
32
+ # Returns nil.
33
+ def define_verb(verb)
34
+ define_method verb do |*args, &block|
35
+ retries = options[:authentication_retries]
36
+ begin
37
+ connection.send(verb, *args, &block)
38
+ rescue Frodo::UnauthorizedError
39
+ if retries.positive?
40
+ retries -= 1
41
+ connection.url_prefix = options[:instance_url]
42
+ retry
43
+ end
44
+ raise
45
+ end
46
+ end
47
+ end
48
+
49
+ # Internal: Defines a method to handle HTTP requests with the passed in
50
+ # verb to a Dynamics api endpoint.
51
+ #
52
+ # verb - Symbol name of the verb (e.g. :get).
53
+ #
54
+ # Examples
55
+ #
56
+ # define_api_verb :get
57
+ # # => api_get 'entity_set'
58
+ #
59
+ # Returns nil.
60
+ def define_api_verb(verb)
61
+ define_method :"api_#{verb}" do |*args, &block|
62
+ args[0] = api_path(args[0])
63
+ send(verb, *args, &block)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end