From b3f47d385695210e2bff276a300d3f55246f158c Mon Sep 17 00:00:00 2001 From: Konstantin Haase Date: Fri, 21 Aug 2015 17:04:12 +0200 Subject: [PATCH] v3: add pagination --- lib/travis/api/v3/access_control/anonymous.rb | 4 + lib/travis/api/v3/paginator.rb | 42 ++++++++ lib/travis/api/v3/paginator/url_generator.rb | 95 +++++++++++++++++++ .../api/v3/renderer/collection_renderer.rb | 35 +++++-- lib/travis/api/v3/result.rb | 13 ++- lib/travis/api/v3/service.rb | 23 ++++- lib/travis/api/v3/services/builds/find.rb | 2 + 7 files changed, 199 insertions(+), 15 deletions(-) create mode 100644 lib/travis/api/v3/paginator.rb create mode 100644 lib/travis/api/v3/paginator/url_generator.rb diff --git a/lib/travis/api/v3/access_control/anonymous.rb b/lib/travis/api/v3/access_control/anonymous.rb index c41df37f..08300b33 100644 --- a/lib/travis/api/v3/access_control/anonymous.rb +++ b/lib/travis/api/v3/access_control/anonymous.rb @@ -2,6 +2,10 @@ require 'travis/api/v3/access_control/generic' module Travis::API::V3 class AccessControl::Anonymous < AccessControl::Generic + def self.new + @instace ||= super + end + # use when Authorization header is not set auth_type(nil) diff --git a/lib/travis/api/v3/paginator.rb b/lib/travis/api/v3/paginator.rb new file mode 100644 index 00000000..26503c2d --- /dev/null +++ b/lib/travis/api/v3/paginator.rb @@ -0,0 +1,42 @@ +module Travis::API::V3 + class Paginator + attr_accessor :default_limit, :max_limit + + def initialize(default_limit: 25, max_limit: 100) + @default_limit = default_limit + @max_limit = max_limit + end + + def paginate(result, limit: nil, offset: nil, access_control: AccessControl::Anonymous.new) + limit &&= Integer(limit, :limit) + limit ||= default_limit + limit = default_limit if limit < 0 + + unless access_control.full_access? + limit = max_limit if limit > max_limit or limit < 1 + end + + offset &&= Integer(offset, :offset) + offset = 0 if offset.nil? or offset < 0 + + count = result.resource ? result.resource.count : 0 + result.resource &&= result.resource.limit(limit) unless limit == 0 + result.resource &&= result.resource.offset(offset) unless offset == 0 + + pagination_info = { + limit: limit, + offset: offset, + count: count, + } + + result.meta_data[:pagination] = pagination_info + result + end + + def Integer(value, key) + super(value) + rescue ArgumentError + raise WrongParams, "#{key} must be an integer" + end + end +end diff --git a/lib/travis/api/v3/paginator/url_generator.rb b/lib/travis/api/v3/paginator/url_generator.rb new file mode 100644 index 00000000..da29c34e --- /dev/null +++ b/lib/travis/api/v3/paginator/url_generator.rb @@ -0,0 +1,95 @@ +require "addressable/uri" + +module Travis::API::V3 + class Paginator + class URLGenerator + class FancyParser + def initialize(href) + @uri = Addressable::URI.parse(href) + end + + def generate(offset, limit) + uri = @uri.dup + uri.query_values = uri.query_values.merge("offset".freeze => offset, "limit".freeze => limit) + uri.to_s + end + end + + class FastParser + PATTERN = /\?(?:&?(?:limit|offset)=[^=]*)*\Z/ + + def self.can_handle?(href) + return true unless href.include? ??.freeze + href =~ PATTERN + end + + def initialize(href) + @path_info = href.split(??.freeze, 2).first + end + + def generate(offset, limit) + "#{@path_info}?limit=#{limit}&offset=#{offset}" + end + end + + def initialize(href, limit: 0, offset: 0, count: 0, **) + @parser = FastParser.can_handle?(href) ? FastParser.new(href) : FancyParser.new(href) + @href = href + @limit = limit + @offset = offset + @count = count + end + + def last? + @count <= @offset + @limit + end + + def first? + @offset == 0 + end + + def next_info + info(offset: @offset + @limit) unless last? + end + + def previous_info + return if @offset == 0 + @offset <= @limit ? info(offset: 0, limit: @offset) : info(offset: @offset - @limit, limit: @limit) + end + + def first_info + info(offset: 0) + end + + def last_info + offset = @count / @limit * @limit + offset -= @limit if offset == @count + info(offset: offset) + end + + def info(offset: @offset, limit: @limit) + { + :@href => uri_with(offset, limit), + :offset => offset, + :limit => limit + } + end + + def to_h + { + is_first: first?, + is_last: last?, + next: next_info, + prev: previous_info, + first: first_info, + last: last_info + } + end + + def uri_with(offset, limit) + return @href if offset == @offset and limit == @limit + @parser.generate(offset, limit) + end + end + end +end diff --git a/lib/travis/api/v3/renderer/collection_renderer.rb b/lib/travis/api/v3/renderer/collection_renderer.rb index 1be17af6..461c7353 100644 --- a/lib/travis/api/v3/renderer/collection_renderer.rb +++ b/lib/travis/api/v3/renderer/collection_renderer.rb @@ -17,19 +17,34 @@ module Travis::API::V3 available_attributes << value end - def initialize(list, href: nil, included: [], **options) - @href = href - @options = options - @list = list - @included = included + attr_reader :href, :options, :list, :included, :meta_data + + def initialize(list, href: nil, included: [], meta_data: {}, **options) + @href = href + @options = options + @list = list + @included = included + @meta_data = meta_data + end + + def fields + fields = { :"@type" => type } + fields[:@href] = href if href + fields[:pagination] = pagination_info if meta_data.include? :pagination + fields + end + + def pagination_info + return meta_data[:@pagination] unless href + generator = V3::Paginator::URLGenerator.new(href, **meta_data[:pagination]) + meta_data[:pagination].merge generator.to_h end def render - result = { :"@type" => type } - result[:@href] = @href if @href - included = @included.dup - result[collection_key] = @list.map do |entry| - rendered = render_entry(entry, included: included, mode: :standard, **@options) + result = fields + included = self.included.dup + result[collection_key] = list.map do |entry| + rendered = render_entry(entry, included: included, mode: :standard, **options) included << entry rendered end diff --git a/lib/travis/api/v3/result.rb b/lib/travis/api/v3/result.rb index 1cf5733b..7f8d2b0d 100644 --- a/lib/travis/api/v3/result.rb +++ b/lib/travis/api/v3/result.rb @@ -1,9 +1,9 @@ module Travis::API::V3 class Result - attr_accessor :access_control, :type, :resource, :status, :href + attr_accessor :access_control, :type, :resource, :status, :href, :meta_data - def initialize(access_control, type, resource = [], status: 200) - @access_control, @type, @resource, @status = access_control, type, resource, status + def initialize(access_control, type, resource = [], status: 200, **meta_data) + @access_control, @type, @resource, @status, @meta_data = access_control, type, resource, status, meta_data end def respond_to_missing?(method, *) @@ -19,7 +19,12 @@ module Travis::API::V3 href = self.href href = V3.location(env) if href.nil? and env['REQUEST_METHOD'.freeze] == 'GET'.freeze include = params['include'.freeze].to_s.split(?,.freeze) - Renderer[type].render(resource, href: href, script_name: env['SCRIPT_NAME'.freeze], include: include, access_control: access_control) + Renderer[type].render(resource, + href: href, + script_name: env['SCRIPT_NAME'.freeze], + include: include, + access_control: access_control, + meta_data: meta_data) end def method_missing(method, *args) diff --git a/lib/travis/api/v3/service.rb b/lib/travis/api/v3/service.rb index c8c90249..2a0011e0 100644 --- a/lib/travis/api/v3/service.rb +++ b/lib/travis/api/v3/service.rb @@ -24,6 +24,19 @@ module Travis::API::V3 @params end + def self.paginate(**options) + params("limit".freeze, "offset".freeze) + @paginator = Paginator.new(**options) + end + + def self.paginator + @paginator ||= nil + end + + def self.paginate? + !!@paginator if defined? @paginator + end + attr_accessor :access_control, :params def initialize(access_control, params) @@ -68,7 +81,15 @@ module Travis::API::V3 def run not_found unless result = run! result = result(result_type, result) unless result.is_a? Result - result + self.class.paginate? ? paginate(result) : result + end + + def paginate(result) + p params + self.class.paginator.paginate(result, + limit: params['limit'.freeze], + offset: params['offset'.freeze], + access_control: access_control) end def params_for?(prefix) diff --git a/lib/travis/api/v3/services/builds/find.rb b/lib/travis/api/v3/services/builds/find.rb index 48354927..9c2a69f9 100644 --- a/lib/travis/api/v3/services/builds/find.rb +++ b/lib/travis/api/v3/services/builds/find.rb @@ -1,5 +1,7 @@ module Travis::API::V3 class Services::Builds::Find < Service + paginate + def run! query.find(find(:repository)) end