lib/cloud_controller/rest_controller/paginated_collection_renderer.rb
require 'addressable/uri'
require 'cloud_controller/rest_controller/order_applicator'
module VCAP::CloudController::RestController
class PaginatedCollectionRenderer
attr_reader :collection_transformer
def initialize(eager_loader, serializer, opts)
@eager_loader = eager_loader
@serializer = serializer
@max_results_per_page = opts.fetch(:max_results_per_page)
@default_results_per_page = opts.fetch(:default_results_per_page)
@max_inline_relations_depth = opts.fetch(:max_inline_relations_depth)
@default_inline_relations_depth = 0
@max_total_results = opts.fetch(:max_total_results)
@collection_transformer = opts[:collection_transformer]
end
# @param [RestController] controller Controller for the
# dataset being paginated.
#
# @param [Sequel::Dataset] dataset Dataset to paginate.
#
# @param [String] path Path used to fetch the dataset.
#
# @option opts [Integer] :page Page number to start at. Defaults to 1.
#
# @option opts [Integer] :results_per_page Number of results to include
# per page. Defaults to 50.
#
# @option opts [Integer] :inline_relations_depth Depth to recursively
# expand relationships in addition to providing the URLs.
#
# @option opts [Integer] :max_inline Maximum number of objects to
# expand inline in a relationship.
def render_json(controller, dataset, path, opts, request_params)
page = opts[:page] || 1
order_applicator = OrderApplicator.new(opts)
order_direction = opts[:order_direction] || 'asc'
page_size = opts[:results_per_page] || @default_results_per_page
inline_relations_depth = opts[:inline_relations_depth] || @default_inline_relations_depth
validate(page, page_size, inline_relations_depth)
ordered_dataset = order_applicator.apply(dataset)
paginated_dataset = ordered_dataset.extension(:pagination).paginate(page, page_size)
prev_url = url(controller, path, paginated_dataset.prev_page, page_size, order_direction, opts, request_params) if paginated_dataset.prev_page
next_url = url(controller, path, paginated_dataset.next_page, page_size, order_direction, opts, request_params) if paginated_dataset.next_page
opts[:max_inline] ||= ::CloudController::Presenters::V2::RelationsPresenter::MAX_INLINE_DEFAULT
orphans = opts[:orphan_relations] == 1 ? {} : nil
resources = fetch_and_process_records(paginated_dataset, controller, inline_relations_depth, orphans, opts)
result = {
total_results: paginated_dataset.pagination_record_count,
total_pages: paginated_dataset.page_count,
prev_url: prev_url,
next_url: next_url,
resources: resources
}
result[:orphans] = orphans if orphans
Oj.dump(result, mode: :compat)
end
private
def validate(page, page_size, inline_relations_depth)
raise CloudController::Errors::ApiError.new_from_details('BadQueryParameter', "results_per_page must be <= #{@max_results_per_page}") if page_size > @max_results_per_page
if !@max_total_results.nil? && page * page_size > @max_total_results
raise CloudController::Errors::ApiError.new_from_details('BadQueryParameter',
"(page * per_page) must be less than #{@max_total_results}")
end
return unless inline_relations_depth > @max_inline_relations_depth
raise CloudController::Errors::ApiError.new_from_details('BadQueryParameter', "inline_relations_depth must be <= #{@max_inline_relations_depth}")
end
def fetch_and_process_records(paginated_dataset, controller, inline_relations_depth, orphans, opts)
dataset = @eager_loader.eager_load_dataset(
paginated_dataset,
controller,
default_visibility_filter,
opts[:additional_visibility_filters] || {},
inline_relations_depth
)
dataset_records = dataset.all
transform_opts = opts[:transform_opts] || {}
collection_transformer.transform(dataset_records, transform_opts) if collection_transformer
serialized_records = dataset_records.map { |obj| @serializer.serialize(controller, obj, opts, orphans) }
serialized_records.compact
end
def default_visibility_filter
access_context = VCAP::CloudController::Security::AccessContext.new
proc { |ds| ds.filter(ds.model.user_visibility(access_context.user, access_context.admin_override)) }
end
def url(controller, path, page, page_size, order_direction, opts, request_params)
params = {
'page' => page,
'results-per-page' => page_size,
'order-direction' => order_direction
}
depth = opts[:inline_relations_depth]
params['inline-relations-depth'] = depth if depth
params['q'] = opts[:q] if opts[:q]
params['orphan-relations'] = opts[:orphan_relations] if opts[:orphan_relations]
params['exclude-relations'] = opts[:exclude_relations] if opts[:exclude_relations]
params['include-relations'] = opts[:include_relations] if opts[:include_relations]
params['order-by'] = opts[:order_by] if opts[:order_by]
controller.preserve_query_parameters.each do |preserved_param|
params[preserved_param] = request_params[preserved_param] if request_params[preserved_param]
end
uri = Addressable::URI.parse(path)
uri.query_values = params
uri.normalize.request_uri
end
end
end