kontent-delivery-sdk-ruby 2.0.6

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 (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