frodo 0.10.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 (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