Kentico/delivery-sdk-ruby

View on GitHub
lib/delivery/client/delivery_query.rb

Summary

Maintainability
A
2 hrs
Test Coverage
require 'rubygems'
require 'delivery/builders/url_builder'
require 'delivery/query_parameters/query_string'

module Kontent
  module Ai
    module Delivery
      # Responsible for executing REST requests to Kontent.ai.
      class DeliveryQuery
        ERROR_PREVIEW = 'Preview is enabled for the query, but the key is null. '\
                        'You can set the preview_key attribute of the query, or '\
                        'when you initialize the client. See '\
                        'https://github.com/kontent-ai/delivery-sdk-ruby#previewing-unpublished-content'.freeze
        ERROR_PARAMS = 'Only filters may be passed in the .item or .items methods'\
                        '. See https://github.com/kontent-ai/delivery-sdk-ruby#filtering'.freeze
        HEADER_WAIT_FOR_CONTENT = 'X-KC-Wait-For-Loading-New-Content'.freeze
        HEADER_SDK_ID = 'X-KC-SDKID'.freeze
        HEADER_SDK_VALUE = 'rubygems.org;kontent-ai-delivery;3.0.1'.freeze
        HEADER_CONTINUATION = 'X-Continuation'.freeze
        attr_accessor :use_preview,
                      :preview_key,
                      :project_id,
                      :code_name,
                      :secure_key,
                      :content_link_url_resolver,
                      :inline_content_item_resolver,
                      :query_type,
                      :query_string,
                      :content_type,
                      :with_retry_policy,
                      :default_rendition_preset

        # Setter for a custom URL.
        #
        # * *Args*:
        #   - *url* (+string+) _optional_ Custom URL to use for the query
        #
        # * *Returns*:
        #   - +self+
        def url(url = nil)
          @url = url unless url.nil?
          self
        end

        # Constructor. Queries should not be instantiated using the constructor, but
        # using one of the Kontent::Ai::Delivery::DeliveryClient methods instead.
        #
        # * *Args*:
        #   - *config* (+Hash+) A hash in which each key automatically has its value paired with the corresponding attribute
        def initialize(config)
          @headers = {}

          # Map each hash value to attr with corresponding key
          # from https://stackoverflow.com/a/2681014/5656214
          config.each do |k, v|
            instance_variable_set("@#{k}", v) unless v.nil?
          end
          self.query_string = Kontent::Ai::Delivery::QueryParameters::QueryString.new
          return if config.fetch(:qp, nil).nil?

          # Query parameters were passed, parse and validate
          validate_params config.fetch(:qp)
        end

        # Executes the REST request.
        #
        # * *Returns*:
        #   - Kontent::Ai::Delivery::Responses::ResponseBase or a class extending it
        def execute
          resp = Kontent::Ai::Delivery::RequestManager.start self, headers
          yield resp if block_given?
          resp
        end

        # Determines whether the query should use preview mode.
        #
        # * *Returns*:
        #   - +boolean+ Whether preview mode should be used for the query
        #
        # * *Raises*:
        #   - +StandardError+ if +use_preview+ is true, but +preview_key+ is +nil+
        def should_preview
          raise ERROR_PREVIEW if use_preview && preview_key.nil?

          use_preview && !preview_key.nil?
        end

        # Enables the total_count attribute of the pagination object, which specifies
        # the total number of items returned by the query regardless of paging. See
        # https://docs.kontent.ai/reference/delivery-api#operation/list-content-items
        #
        # * *Returns*:
        #   - +self+
        def include_total_count
          query_string.set_param('includeTotalCount', 1)
          self
        end

        # Sets a content link resolver to render links contained in rich text. See
        # https://github.com/kontent-ai/delivery-sdk-ruby#resolving-links
        #
        # * *Args*:
        #   - *resolver* ( Kontent::Ai::Delivery::Resolvers::ContentLinkResolver ) The resolver. Replaces a resolver registered during +DeliveryClient+ instantiation, for this query only.
        #
        # * *Returns*:
        #   - +self+
        def with_link_resolver(resolver)
          self.content_link_url_resolver = resolver
          self
        end

        # Sets an inline content itme to render content items and components in rich text.
        # See https://github.com/kontent-ai/delivery-sdk-ruby#resolving-inline-content
        #
        # * *Args*:
        #   - *resolver* ( Kontent::Ai::Delivery::Resolvers::InlineContentItemResolver ) The resolver. Replaces a resolver registered during +DeliveryClient+ instantiation, for this query only.
        #
        # * *Returns*:
        #   - +self+
        def with_inline_content_item_resolver(resolver)
          self.inline_content_item_resolver = resolver
          self
        end

        def with_default_rendition_preset(rendition)
          self.default_rendition_preset = rendition
          self
        end

        # Sets the 'order' query string parameter
        #
        # * *Args*:
        #   - *value* (+string+) The value to order by
        #   - *sort* (+string+) _optional_ The direction of the order, surrounded by brackets. The default value is '[asc]'
        #
        # * *Returns*:
        #   - +self+
        def order_by(value, sort = '[asc]')
          query_string.set_param('order', value + sort)
          self
        end

        # Sets the 'skip' query string parameter for paging results.
        # See https://kontent.ai/learn/reference/delivery-api/#operation/list-content-items
        #
        # * *Args*:
        #   - *value* (+integer+) The number to skip by
        #
        # * *Returns*:
        #   - +self+
        def skip(value)
          query_string.set_param('skip', value) unless query_type.eql? Kontent::Ai::Delivery::QUERY_TYPE_ITEMS_FEED
          self
        end

        # Sets the 'language' query string parameter. Language fallbacks will be used
        # if untranslated content items are found.
        # See https://kontent.ai/learn/tutorials/develop-apps/get-content/localized-content-items/
        #
        # * *Args*:
        #   - *value* (+string+) The code name of the desired language
        #
        # * *Returns*:
        #   - +self+
        def language(value)
          query_string.set_param('language', value)
          self
        end

        # Sets the 'limit' query string parameter for paging results, or just to
        # return a specific number of content items.
        # See https://kontent.ai/learn/reference/delivery-api/#operation/list-content-items
        #
        # * *Args*:
        #   - *value* (+integer+) The number of content items to return
        #
        # * *Returns*:
        #   - +self+
        def limit(value)
          query_string.set_param('limit', value) unless query_type.eql? Kontent::Ai::Delivery::QUERY_TYPE_ITEMS_FEED
          self
        end

        # Sets the 'elements' query string parameter to limit the elements returned
        # by the query.
        # See https://kontent.ai/learn/reference/delivery-api/#tag/Projection
        #
        # * *Args*:
        #   - *value* (+Array+) A single string or array of strings specifying the desired elements, e.g. %w[price product_name image]
        #
        # * *Returns*:
        #   - +self+
        def elements(value)
          query_string.set_param('elements', value)
          self
        end

        # Sets the 'depth' query string parameter to determine how many levels of
        # linked content items should be returned. By default, only 1 level of depth
        # is used.
        # See https://kontent.ai/learn/reference/delivery-api/#tag/Linked-content-and-components/linked-content-depth
        #
        # * *Args*:
        #   - *value* (+integer+) Level of linked items to be returned
        #
        # * *Returns*:
        #   - +self+
        def depth(value)
          query_string.set_param('depth', value) unless query_type.eql? Kontent::Ai::Delivery::QUERY_TYPE_ITEMS_FEED
          self
        end

        # Allows the request to bypass caching and return the latest content
        # directly from Kontent.ai.
        # See https://github.com/kontent-ai/delivery-sdk-ruby#requesting-the-latest-content
        #
        # * *Returns*:
        #   - +self+
        def request_latest_content
          @headers[HEADER_WAIT_FOR_CONTENT] = true
          self
        end

        # Uses Kontent::Ai::Delivery::Builders::UrlBuilder.provide_url to set
        # the URL for the query. The +UrlBuilder+ also validates the URL.
        #
        # * *Raises*:
        #   - +UriFormatException+ if the URL is 65,519 characters or more
        #
        # * *Returns*:
        #   - +string+ The full URL for this query
        def provide_url
          @url = Kontent::Ai::Delivery::Builders::UrlBuilder.provide_url self if @url.nil?
          Kontent::Ai::Delivery::Builders::UrlBuilder.validate_url @url
          @url
        end

        # Allows providing custom headers for client requests.
        # See https://github.com/kontent-ai/delivery-sdk-ruby#providing-custom-headers
        #
        # * *Args*:
        #   - *headers* (+Hash+) A hash that corresponds to provided headers
        #
        # * *Returns*:
        #   - +self+
        def custom_headers(headers)
          @custom_headers = headers
          self
        end

        def update_continuation(token)
          @headers[HEADER_CONTINUATION] = token
          self
        end

        def continuation_exists?
          !continuation_token.nil?
        end

        def continuation_token
          @headers[HEADER_CONTINUATION]
        end

        private

        # Returns request headers that are extended with custom headers.
        # Custom headers do not override existing headers.
        #
        # * *Returns*
        #   - +Hash+
        def headers
          headers = @headers.clone
          headers[HEADER_SDK_ID] = HEADER_SDK_VALUE
          headers['Authorization'] = "Bearer #{preview_key}" if should_preview
          headers['Authorization'] = "Bearer #{secure_key}" if !should_preview && secure_key

          if @custom_headers
            headers.merge!(@custom_headers) { |key, v1, v2| v1 }
          end

          headers
        end

        # Initializes the +query_string+ attribute with the passed array of
        # Kontent::Ai::Delivery::QueryParameters::Filter objects.
        #
        # * *Raises*:
        #   - +ArgumentError+ if one the passed objects is not a +Filter+
        def validate_params(query_parameters)
          params = if query_parameters.is_a? Array
                    query_parameters
                  else
                    [query_parameters]
                  end
          params.each do |p|
            query_string.set_param p
            unless p.is_a? Kontent::Ai::Delivery::QueryParameters::Filter
              raise ArgumentError, ERROR_PARAMS
            end
          end
        end
      end
    end
  end
end