kontent-delivery-sdk-ruby 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +21 -0
  3. data/README.md +511 -0
  4. data/bin/console +14 -0
  5. data/bin/setup +8 -0
  6. data/lib/delivery/builders/image_transformation_builder.rb +271 -0
  7. data/lib/delivery/builders/url_builder.rb +117 -0
  8. data/lib/delivery/client/delivery_client.rb +155 -0
  9. data/lib/delivery/client/delivery_query.rb +249 -0
  10. data/lib/delivery/client/request_manager.rb +108 -0
  11. data/lib/delivery/models/content_item.rb +153 -0
  12. data/lib/delivery/models/content_type.rb +41 -0
  13. data/lib/delivery/models/pagination.rb +21 -0
  14. data/lib/delivery/models/taxonomy_group.rb +39 -0
  15. data/lib/delivery/query_parameters/filters.rb +158 -0
  16. data/lib/delivery/query_parameters/parameter_base.rb +44 -0
  17. data/lib/delivery/query_parameters/query_string.rb +78 -0
  18. data/lib/delivery/resolvers/content_link_resolver.rb +102 -0
  19. data/lib/delivery/resolvers/inline_content_item_resolver.rb +75 -0
  20. data/lib/delivery/resolvers/linked_item_resolver.rb +37 -0
  21. data/lib/delivery/responses/delivery_element_response.rb +33 -0
  22. data/lib/delivery/responses/delivery_item_listing_response.rb +53 -0
  23. data/lib/delivery/responses/delivery_item_response.rb +39 -0
  24. data/lib/delivery/responses/delivery_taxonomy_listing_response.rb +46 -0
  25. data/lib/delivery/responses/delivery_taxonomy_response.rb +32 -0
  26. data/lib/delivery/responses/delivery_type_listing_response.rb +45 -0
  27. data/lib/delivery/responses/delivery_type_response.rb +31 -0
  28. data/lib/delivery/responses/response_base.rb +36 -0
  29. data/lib/delivery/tests/401.json +6 -0
  30. data/lib/delivery/tests/fake_responder.rb +67 -0
  31. data/lib/delivery/tests/filtering/items_gt.json +566 -0
  32. data/lib/delivery/tests/filtering/multiple.json +283 -0
  33. data/lib/delivery/tests/filtering/pagination_about_us.json +647 -0
  34. data/lib/delivery/tests/generic/items.json +4985 -0
  35. data/lib/delivery/tests/generic/items/about_us.json +228 -0
  36. data/lib/delivery/tests/generic/items/aeropress_filters.json +139 -0
  37. data/lib/delivery/tests/generic/items/coffee_processing_techniques.json +169 -0
  38. data/lib/delivery/tests/generic/items/where_does_coffee_come_from_.json +621 -0
  39. data/lib/delivery/tests/generic/taxonomies.json +127 -0
  40. data/lib/delivery/tests/generic/types.json +781 -0
  41. data/lib/delivery/tests/generic/types/brewer/elements/product_status.json +6 -0
  42. data/lib/delivery/version.rb +7 -0
  43. data/lib/kontent-delivery-sdk-ruby.rb +19 -0
  44. metadata +200 -0
@@ -0,0 +1,249 @@
1
+ require 'delivery/builders/url_builder'
2
+ require 'delivery/query_parameters/query_string'
3
+ require 'delivery/version'
4
+
5
+ module Kentico
6
+ module Kontent
7
+ module Delivery
8
+ # Responsible for executing REST requests to Kentico Kontent.
9
+ class DeliveryQuery
10
+ ERROR_PREVIEW = 'Preview is enabled for the query, but the key is null. '\
11
+ 'You can set the preview_key attribute of the query, or '\
12
+ 'when you initialize the client. See '\
13
+ 'https://github.com/Kentico/kontent-delivery-sdk-ruby#previewing-unpublished-content'.freeze
14
+ ERROR_PARAMS = 'Only filters may be passed in the .item or .items methods'\
15
+ '. See https://github.com/Kentico/kontent-delivery-sdk-ruby#filtering'.freeze
16
+ attr_accessor :use_preview,
17
+ :preview_key,
18
+ :project_id,
19
+ :code_name,
20
+ :secure_key,
21
+ :content_link_url_resolver,
22
+ :inline_content_item_resolver,
23
+ :query_type,
24
+ :query_string,
25
+ :content_type,
26
+ :with_retry_policy
27
+
28
+ # Setter for a custom URL.
29
+ #
30
+ # * *Args*:
31
+ # - *url* (+string+) _optional_ Custom URL to use for the query
32
+ #
33
+ # * *Returns*:
34
+ # - +self+
35
+ def url(url = nil)
36
+ @url = url unless url.nil?
37
+ self
38
+ end
39
+
40
+ # Constructor. Queries should not be instantiated using the constructor, but
41
+ # using one of the Kentico::Kontent::Delivery::DeliveryClient methods instead.
42
+ #
43
+ # * *Args*:
44
+ # - *config* (+Hash+) A hash in which each key automatically has its value paired with the corresponding attribute
45
+ def initialize(config)
46
+ @headers = {}
47
+
48
+ # Map each hash value to attr with corresponding key
49
+ # from https://stackoverflow.com/a/2681014/5656214
50
+ config.each do |k, v|
51
+ instance_variable_set("@#{k}", v) unless v.nil?
52
+ end
53
+ self.query_string = Kentico::Kontent::Delivery::QueryParameters::QueryString.new
54
+ return if config.fetch(:qp, nil).nil?
55
+
56
+ # Query parameters were passed, parse and validate
57
+ validate_params config.fetch(:qp)
58
+ end
59
+
60
+ # Executes the REST request.
61
+ #
62
+ # * *Returns*:
63
+ # - Kentico::Kontent::Delivery::Responses::ResponseBase or a class extending it
64
+ def execute
65
+ headers = @headers.clone
66
+ headers['X-KC-SDKID'] = provide_sdk_header
67
+ headers['Authorization'] = "Bearer #{preview_key}" if should_preview
68
+ headers['Authorization'] = "Bearer #{secure_key}" if !should_preview && secure_key
69
+
70
+ resp = Kentico::Kontent::Delivery::RequestManager.start self, headers
71
+ yield resp if block_given?
72
+ resp
73
+ end
74
+
75
+ # Determines whether the query should use preview mode.
76
+ #
77
+ # * *Returns*:
78
+ # - +boolean+ Whether preview mode should be used for the query
79
+ #
80
+ # * *Raises*:
81
+ # - +StandardError+ if +use_preview+ is true, but +preview_key+ is +nil+
82
+ def should_preview
83
+ raise ERROR_PREVIEW if use_preview && preview_key.nil?
84
+
85
+ use_preview && !preview_key.nil?
86
+ end
87
+
88
+ # Sets a content link resolver to render links contained in rich text. See
89
+ # https://github.com/Kentico/kontent-delivery-sdk-ruby#resolving-links
90
+ #
91
+ # * *Args*:
92
+ # - *resolver* ( Kentico::Kontent::Delivery::Resolvers::ContentLinkResolver ) The resolver. Replaces a resolver registered during +DeliveryClient+ instantiation, for this query only.
93
+ #
94
+ # * *Returns*:
95
+ # - +self+
96
+ def with_link_resolver(resolver)
97
+ self.content_link_url_resolver = resolver
98
+ self
99
+ end
100
+
101
+ # Sets an inline content itme to render content items and components in rich text.
102
+ # See https://github.com/Kentico/kontent-delivery-sdk-ruby#resolving-inline-content
103
+ #
104
+ # * *Args*:
105
+ # - *resolver* ( Kentico::Kontent::Delivery::Resolvers::InlineContentItemResolver ) The resolver. Replaces a resolver registered during +DeliveryClient+ instantiation, for this query only.
106
+ #
107
+ # * *Returns*:
108
+ # - +self+
109
+ def with_inline_content_item_resolver(resolver)
110
+ self.inline_content_item_resolver = resolver
111
+ self
112
+ end
113
+
114
+ # Sets the 'order' query string parameter
115
+ #
116
+ # * *Args*:
117
+ # - *value* (+string+) The value to order by
118
+ # - *sort* (+string+) _optional_ The direction of the order, surrounded by brackets. The default value is '[asc]'
119
+ #
120
+ # * *Returns*:
121
+ # - +self+
122
+ def order_by(value, sort = '[asc]')
123
+ query_string.set_param('order', value + sort)
124
+ self
125
+ end
126
+
127
+ # Sets the 'skip' query string parameter for paging results.
128
+ # See https://developer.kenticocloud.com/v1/reference#listing-response-paging
129
+ #
130
+ # * *Args*:
131
+ # - *value* (+integer+) The number to skip by
132
+ #
133
+ # * *Returns*:
134
+ # - +self+
135
+ def skip(value)
136
+ query_string.set_param('skip', value)
137
+ self
138
+ end
139
+
140
+ # Sets the 'language' query string parameter. Language fallbacks will be used
141
+ # if untranslated content items are found.
142
+ # See https://developer.kenticocloud.com/docs/localization#section-getting-localized-content-items
143
+ #
144
+ # * *Args*:
145
+ # - *value* (+string+) The code name of the desired language
146
+ #
147
+ # * *Returns*:
148
+ # - +self+
149
+ def language(value)
150
+ query_string.set_param('language', value)
151
+ self
152
+ end
153
+
154
+ # Sets the 'limit' query string parameter for paging results, or just to
155
+ # return a specific number of content items.
156
+ # See https://developer.kenticocloud.com/v1/reference#listing-response-paging
157
+ #
158
+ # * *Args*:
159
+ # - *value* (+integer+) The number of content items to return
160
+ #
161
+ # * *Returns*:
162
+ # - +self+
163
+ def limit(value)
164
+ query_string.set_param('limit', value)
165
+ self
166
+ end
167
+
168
+ # Sets the 'elements' query string parameter to limit the elements returned
169
+ # by the query.
170
+ # See https://developer.kenticocloud.com/v1/reference#projection
171
+ #
172
+ # * *Args*:
173
+ # - *value* (+Array+) A single string or array of strings specifying the desired elements, e.g. %w[price product_name image]
174
+ #
175
+ # * *Returns*:
176
+ # - +self+
177
+ def elements(value)
178
+ query_string.set_param('elements', value)
179
+ self
180
+ end
181
+
182
+ # Sets the 'depth' query string parameter to determine how many levels of
183
+ # linked content items should be returned. By default, only 1 level of depth
184
+ # is used.
185
+ # See https://developer.kenticocloud.com/v1/reference#linked-content
186
+ #
187
+ # * *Args*:
188
+ # - *value* (+integer+) Level of linked items to be returned
189
+ #
190
+ # * *Returns*:
191
+ # - +self+
192
+ def depth(value)
193
+ query_string.set_param('depth', value)
194
+ self
195
+ end
196
+
197
+ # Allows the request to bypass caching and return the latest content
198
+ # directly from Kentico Kontent.
199
+ # See https://github.com/Kentico/kontent-delivery-sdk-ruby#requesting-the-latest-content
200
+ #
201
+ # * *Returns*:
202
+ # - +self+
203
+ def request_latest_content
204
+ @headers['X-KC-Wait-For-Loading-New-Content'] = true
205
+ self
206
+ end
207
+
208
+ # Uses Kentico::Kontent::Delivery::Builders::UrlBuilder.provide_url to set
209
+ # the URL for the query. The +UrlBuilder+ also validates the URL.
210
+ #
211
+ # * *Raises*:
212
+ # - +UriFormatException+ if the URL is 65,519 characters or more
213
+ #
214
+ # * *Returns*:
215
+ # - +string+ The full URL for this query
216
+ def provide_url
217
+ @url = Kentico::Kontent::Delivery::Builders::UrlBuilder.provide_url self if @url.nil?
218
+ Kentico::Kontent::Delivery::Builders::UrlBuilder.validate_url @url
219
+ @url
220
+ end
221
+
222
+ private
223
+
224
+ # Initializes the +query_string+ attribute with the passed array of
225
+ # Kentico::Kontent::Delivery::QueryParameters::Filter objects.
226
+ #
227
+ # * *Raises*:
228
+ # - +ArgumentError+ if one the passed objects is not a +Filter+
229
+ def validate_params(query_parameters)
230
+ params = if query_parameters.is_a? Array
231
+ query_parameters
232
+ else
233
+ [query_parameters]
234
+ end
235
+ params.each do |p|
236
+ query_string.set_param p
237
+ unless p.is_a? Kentico::Kontent::Delivery::QueryParameters::Filter
238
+ raise ArgumentError, ERROR_PARAMS
239
+ end
240
+ end
241
+ end
242
+
243
+ def provide_sdk_header
244
+ "rubygems.org;kontent-delivery-sdk-ruby;#{Kentico::Kontent::Delivery::VERSION}"
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,108 @@
1
+ require 'rest-client'
2
+ require 'dotenv/load'
3
+
4
+ module Kentico
5
+ module Kontent
6
+ module Delivery
7
+ class RequestManager
8
+ class << self
9
+ MAX_ATTEMPTS = 6
10
+ INITIAL_DELAY = 0.2
11
+ RETRY_WHEN_CODE = [408, 500, 502, 503, 504].freeze
12
+
13
+ def start(query, headers)
14
+ @query = query
15
+ @headers = headers
16
+ @times_run = 1
17
+ @delay = INITIAL_DELAY
18
+ @url = @query.provide_url
19
+ continue
20
+ end
21
+
22
+ private
23
+
24
+ def should_retry(potential_response)
25
+ return potential_response if @times_run == MAX_ATTEMPTS ||
26
+ !RETRY_WHEN_CODE.include?(potential_response.http_code) ||
27
+ !@query.with_retry_policy
28
+
29
+ @times_run += 1
30
+ @delay *= 2
31
+ sleep(@delay)
32
+ continue
33
+ end
34
+
35
+ def continue
36
+ if ENV['TEST'] == '1'
37
+ resp = Kentico::Kontent::Delivery::Tests::FakeResponder.get_response @query, @url, @headers
38
+ return resp if resp.is_a? Kentico::Kontent::Delivery::Responses::ResponseBase
39
+
40
+ make_response resp # resp is pure JSON
41
+ else
42
+ begin
43
+ resp = RestClient.get @url, @headers
44
+ rescue RestClient::ExceptionWithResponse => err
45
+ should_retry Kentico::Kontent::Delivery::Responses::ResponseBase.new err.http_code, err.response
46
+ rescue RestClient::SSLCertificateNotVerified => err
47
+ should_retry Kentico::Kontent::Delivery::Responses::ResponseBase.new 500, err
48
+ rescue SocketError => err
49
+ should_retry Kentico::Kontent::Delivery::Responses::ResponseBase.new 500, err.message
50
+ else
51
+ make_response resp
52
+ end
53
+ end
54
+ end
55
+
56
+ # Converts a standard REST response based on the type of query.
57
+ #
58
+ # * *Returns*:
59
+ # - An object derived from the Kentico::Kontent::Delivery::Responses::ResponseBase class
60
+ def make_response(response)
61
+ case @query.query_type
62
+ when Kentico::Kontent::Delivery::QUERY_TYPE_ITEMS
63
+ respond_item response
64
+ when Kentico::Kontent::Delivery::QUERY_TYPE_TYPES
65
+ respond_type response
66
+ when Kentico::Kontent::Delivery::QUERY_TYPE_TAXONOMIES
67
+ respond_taxonomy response
68
+ when Kentico::Kontent::Delivery::QUERY_TYPE_ELEMENT
69
+ Kentico::Kontent::Delivery::Responses::DeliveryElementResponse.new JSON.parse(response)
70
+ end
71
+ end
72
+
73
+ def respond_type(response)
74
+ if @query.code_name.nil?
75
+ Kentico::Kontent::Delivery::Responses::DeliveryTypeListingResponse.new JSON.parse(response)
76
+ else
77
+ Kentico::Kontent::Delivery::Responses::DeliveryTypeResponse.new JSON.parse(response)
78
+ end
79
+ end
80
+
81
+ def respond_taxonomy(response)
82
+ if @query.code_name.nil?
83
+ Kentico::Kontent::Delivery::Responses::DeliveryTaxonomyListingResponse.new JSON.parse(response)
84
+ else
85
+ Kentico::Kontent::Delivery::Responses::DeliveryTaxonomyResponse.new JSON.parse(response)
86
+ end
87
+ end
88
+
89
+ def respond_item(response)
90
+ if @query.code_name.nil?
91
+ Kentico::Kontent::Delivery::Responses::DeliveryItemListingResponse.new(
92
+ JSON.parse(response),
93
+ @query.content_link_url_resolver,
94
+ @query.inline_content_item_resolver
95
+ )
96
+ else
97
+ Kentico::Kontent::Delivery::Responses::DeliveryItemResponse.new(
98
+ JSON.parse(response),
99
+ @query.content_link_url_resolver,
100
+ @query.inline_content_item_resolver
101
+ )
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,153 @@
1
+ require 'ostruct'
2
+ require 'nokogiri'
3
+
4
+ module Kentico
5
+ module Kontent
6
+ module Delivery
7
+ class ContentItem
8
+ attr_accessor :content_link_url_resolver,
9
+ :inline_content_item_resolver
10
+
11
+ # Parses the 'elements' JSON object as a dynamic OpenStruct object.
12
+ #
13
+ # * *Returns*:
14
+ # - +OpenStruct+ The elements of the content item
15
+ def elements
16
+ @elements unless @elements.nil?
17
+ @elements = JSON.parse(
18
+ JSON.generate(@source['elements']),
19
+ object_class: OpenStruct
20
+ )
21
+ end
22
+
23
+ # Parses the 'system' JSON object as a dynamic OpenStruct object.
24
+ #
25
+ # * *Returns*:
26
+ # - +OpenStruct+ The system properties of the content item
27
+ def system
28
+ @system unless @system.nil?
29
+ @system = JSON.parse(
30
+ JSON.generate(@source['system']),
31
+ object_class: OpenStruct
32
+ )
33
+ end
34
+
35
+ # Constructor.
36
+ #
37
+ # * *Args*:
38
+ # - *source* (+JSON+) The response from a REST request for content items. The item may be on the root or under the 'item' node
39
+ # - *content_link_url_resolver* ( Kentico::Kontent::Delivery::Resolvers::ContentLinkResolver )
40
+ # - *inline_content_item_resolver* ( Kentico::Kontent::Delivery::Resolvers::InlineContentItemResolver )
41
+ # - *linked_items_resolver* ( Kentico::Kontent::Delivery::Resolvers::LinkedItemResolver )
42
+ def initialize(source, content_link_url_resolver, inline_content_item_resolver, linked_items_resolver)
43
+ @source =
44
+ if source['item'].nil?
45
+ source
46
+ else
47
+ source['item']
48
+ end
49
+ @linked_items_resolver = linked_items_resolver
50
+ self.content_link_url_resolver = content_link_url_resolver
51
+ self.inline_content_item_resolver = inline_content_item_resolver
52
+ end
53
+
54
+ # Gets a string representation of the data stored in the element. Using this
55
+ # method instead of directly accessing the +elements+ collection causes
56
+ # the content to be resolved using the resolvers passed during instantiation.
57
+ # See https://github.com/Kentico/kontent-delivery-sdk-ruby#resolving-links
58
+ #
59
+ # * *Args*:
60
+ # - *code_name* (+string+) The code name of the desired element
61
+ #
62
+ # * *Returns*:
63
+ # - +string+ The data converted to a string, resolved if the element is a 'rich_text' element
64
+ def get_string(code_name)
65
+ element = get_element code_name
66
+ content = element['value']
67
+
68
+ if element['type'] == 'rich_text'
69
+ content = content_link_url_resolver.resolve content, element['links'] if should_resolve_links element
70
+ inline_items = get_inline_items code_name
71
+ content = inline_content_item_resolver.resolve content, inline_items if should_resolve_inline_content element
72
+ end
73
+ content.to_s
74
+ end
75
+
76
+ # Returns an array of assets inserted into the specified element of the
77
+ # 'asset' type.
78
+ #
79
+ # * *Args*:
80
+ # - *code_name* (+string+) The code name of the desired element
81
+ #
82
+ # * *Returns*:
83
+ # - +Array+ The element's assets parsed as +OpenStruct+ objects
84
+ def get_assets(code_name)
85
+ element = get_element code_name
86
+ element['value'].map { |n| OpenStruct.new(n) }
87
+ end
88
+
89
+ # Returns an array of ContentItems that are linked in a 'modular_content'
90
+ # element.
91
+ #
92
+ # * *Args*:
93
+ # - *code_name* (+string+) The code name of the desired element
94
+ #
95
+ # * *Returns*:
96
+ # - +Array+ The element's linked items parsed as +ContentItem+ objects
97
+ def get_links(code_name)
98
+ element = get_element code_name
99
+ get_linked_items element['value']
100
+ end
101
+
102
+ # Returns an array of ContentItems that are inserted as inline content
103
+ # items or componenets of a 'rich_text' element.
104
+ #
105
+ # * *Args*:
106
+ # - *code_name* (+string+) The code name of the desired element
107
+ #
108
+ # * *Returns*:
109
+ # - +Array+ The element's inline content items parsed as +ContentItem+ objects
110
+ def get_inline_items(code_name)
111
+ element = get_element code_name
112
+ get_linked_items element['modular_content']
113
+ end
114
+
115
+ private
116
+
117
+ def should_resolve_links(element)
118
+ !element['links'].nil? && !content_link_url_resolver.nil?
119
+ end
120
+
121
+ def should_resolve_inline_content(element)
122
+ !element['modular_content'].nil? && !inline_content_item_resolver.nil?
123
+ end
124
+
125
+ # Gets the JSON object from the 'elements' collection with the specified key
126
+ #
127
+ # * *Args*:
128
+ # - *code_name* (+string+, +symbol+) The code name or symbol of the desired element
129
+ #
130
+ # * *Returns*:
131
+ # - +JSON+ The element as a JSON object
132
+ #
133
+ # * *Raises*:
134
+ # - +ArgumentError+ if +code_name+ is +nil+
135
+ def get_element(code_name)
136
+ raise ArgumentError, "Argument 'code_name' cannot be null" if code_name.nil?
137
+
138
+ code_name = code_name.to_s if code_name.is_a? Symbol
139
+ @source['elements'][code_name]
140
+ end
141
+
142
+ def get_linked_items(codenames)
143
+ return [] unless codenames.class == Array
144
+
145
+ codenames.each_with_object([]) do |codename, items|
146
+ item = @linked_items_resolver.resolve codename
147
+ items << item if item
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end