Merge branch 'master' into cd-v3-parity

This commit is contained in:
carlad 2015-08-24 10:01:59 +02:00
commit 517b3b60ef
13 changed files with 291 additions and 44 deletions

View File

@ -37,4 +37,3 @@ module Travis
end
end
end

View File

@ -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)

View File

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

View File

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

View File

@ -1,7 +1,21 @@
module Travis::API::V3
class Queries::Builds < Query
params :state, :event_type, :previous_state, prefix: :build
params :name, prefix: :branch, method_name: :branch_name
def find(repository)
repository.builds
filter(repository.builds)
end
def filter(list)
list = list.where(state: list(state)) if state
list = list.where(previous_state: list(previous_state)) if previous_state
list = list.where(event_type: list(event_type)) if event_type
list = list.where(branch: list(branch_name)) if branch_name
list = list.includes(:commit).includes(branch: :last_build).includes(:repository)
list = list.includes(branch: { last_build: :commit }) if includes? 'build.commit'.freeze
list
end
end
end

View File

@ -4,24 +4,35 @@ module Travis::API::V3
# generate from eval to avoid additional string allocations on every params access
@@params_accessor = <<-RUBY
attr_writer :%<name>s
attr_writer :%<method_name>s
def %<name>s
return @%<name>s if defined? @%<name>s
return @%<name>s = @params['%<prefix>s.%<name>s'.freeze] if @params.include? '%<prefix>s.%<name>s'.freeze
return @%<name>s = @params['%<prefix>s'.freeze]['%<name>s'.freeze] if @params.include? '%<prefix>s'.freeze and @params['%<prefix>s'.freeze].is_a? Hash
return @%<name>s = @params['%<name>s'.freeze] if (@params['@type'.freeze] || @main_type) == '%<prefix>s'.freeze
@%<name>s = nil
def %<method_name>s
return @%<method_name>s if defined? @%<method_name>s
return @%<method_name>s = @params['%<prefix>s.%<name>s'.freeze] if @params.include? '%<prefix>s.%<name>s'.freeze
return @%<method_name>s = @params['%<prefix>s'.freeze]['%<name>s'.freeze] if @params.include? '%<prefix>s'.freeze and @params['%<prefix>s'.freeze].is_a? Hash
return @%<method_name>s = @params['%<name>s'.freeze] if (@params['@type'.freeze] || @main_type) == '%<prefix>s'.freeze
return @%<method_name>s = @params['%<name>s'.freeze] if %<check_type>p and (@params['@type'.freeze] || @main_type) == '%<type>s'.freeze
@%<method_name>s = nil
end
def %<name>s!
%<name>s or raise WrongParams, 'missing %<prefix>s.%<name>s'.freeze
def %<method_name>s!
%<method_name>s or raise WrongParams, 'missing %<prefix>s.%<name>s'.freeze
end
RUBY
def self.params(*list, prefix: nil)
prefix ||= name[/[^:]+$/].underscore
list.each { |e| class_eval(@@params_accessor % { name: e, prefix: prefix }) }
def self.params(*list, prefix: nil, method_name: nil)
type = name[/[^:]+$/].underscore
prefix ||= type.to_s
check_type = method_name.nil? and type != prefix
list.each do |entry|
class_eval(@@params_accessor % {
name: entry,
prefix: prefix,
type: type,
method_name: method_name || entry,
check_type: check_type
})
end
end
attr_reader :params, :main_type
@ -51,6 +62,10 @@ module Travis::API::V3
!!value
end
def list(value)
value.split(?,.freeze)
end
def user_condition(value)
case value
when String then { login: value }

View File

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

View File

@ -1,15 +1,25 @@
module Travis::API::V3
class Result
attr_accessor :access_control, :type, :resource, :status, :href
attr_accessor :access_control, :type, :resource, :status, :href, :meta_data, :warnings
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)
@warnings = []
@access_control, @type, @resource, @status, @meta_data = access_control, type, resource, status, meta_data
end
def respond_to_missing?(method, *)
super or method.to_sym == type.to_sym
end
def warn(message, **info)
warnings << { :@type => 'warning'.freeze, :message => message, **info }
end
def ignored_param(param, reason: nil, **info)
message = reason ? "query parameter #{param} #{reason}, ignored" : "query parameter #{param} ignored"
warn(message, warning_type: :ignored_parameter, parameter: param, **info)
end
def <<(value)
resource << value
self
@ -19,7 +29,20 @@ 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)
add_info Renderer[type].render(resource,
href: href,
script_name: env['SCRIPT_NAME'.freeze],
include: include,
access_control: access_control,
meta_data: meta_data)
end
def add_info(payload)
if warnings.any?
payload = { :@warnings => [] }.merge!(payload) unless payload.include? :@warnings
payload[:@warnings].concat(warnings)
end
payload
end
def method_missing(method, *args)

View File

@ -16,8 +16,11 @@ module Travis::API::V3
raise NotFound unless factory
service = factory.new(access_control, factory.filter_params(env_params).merge(params))
filtered = factory.filter_params(env_params)
service = factory.new(access_control, filtered.merge(params))
result = service.run
env_params.each_key { |key| result.ignored_param(key, reason: "not whitelisted".freeze) unless filtered.include?(key) }
render(result, env_params, env)
rescue Error => error
result = Result.new(access_control, :error, error)

View File

@ -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)

View File

@ -1,5 +1,9 @@
module Travis::API::V3
class Services::Builds::Find < Service
params :state, :event_type, :previous_state, prefix: :build
params "branch.name"
paginate
def run!
query.find(find(:repository))
end

View File

@ -1,7 +1,9 @@
require 'simplecov'
unless ENV['SKIP_COVERAGE']
require 'simplecov'
SimpleCov.start do
coverage_dir '.coverage'
add_filter "/spec/"
add_group "v3", "lib/travis/api/v3"
SimpleCov.start do
coverage_dir '.coverage'
add_filter "/spec/"
add_group "v3", "lib/travis/api/v3"
end
end

View File

@ -130,14 +130,19 @@ describe Travis::API::V3::Services::Owner::Find do
before { get("/v3/owner/example-org?organization.id=#{other.id}") }
example { expect(last_response).to be_ok }
example { expect(JSON.load(body)).to be == {
"@type" => "organization",
"@href" => "/v3/org/#{org.id}",
"@permissions" => { "read"=>true, "sync"=>false },
"id" => org.id,
"login" => "example-org",
"name" => nil,
"github_id" => nil,
"avatar_url" => nil
"@type" => "organization",
"@href" => "/v3/org/#{org.id}",
"@permissions" => { "read"=>true, "sync"=>false },
"id" => org.id,
"login" => "example-org",
"name" => nil,
"github_id" => nil,
"avatar_url" => nil,
"@warnings" => [{
"@type" => "warning",
"message" => "query parameter organization.id not whitelisted, ignored",
"warning_type" => "ignored_parameter",
"parameter" => "organization.id"}]
}}
end
end
@ -198,7 +203,12 @@ describe Travis::API::V3::Services::Owner::Find do
"github_id" => nil,
"avatar_url" => nil,
"is_syncing" => nil,
"synced_at" => nil
"synced_at" => nil,
"@warnings" => [{
"@type" => "warning",
"message" => "query parameter user.id not whitelisted, ignored",
"warning_type" => "ignored_parameter",
"parameter" => "user.id"}]
}}
end
end