Merge pull request #160 from travis-ci/rkh-v3-request-create
[DO NOT MERGE] API v3: Request API and some refactoring
This commit is contained in:
commit
89f96c98ec
|
@ -50,7 +50,7 @@ GIT
|
|||
|
||||
GIT
|
||||
remote: git://github.com/travis-ci/travis-core.git
|
||||
revision: a0aa1e2f79d45a4c59c1046a5435fe598eda2d2a
|
||||
revision: a82f25fb00a39c3c64b8c09c716c206e6f4c6fad
|
||||
specs:
|
||||
travis-core (0.0.1)
|
||||
actionmailer (~> 3.2.19)
|
||||
|
|
|
@ -17,12 +17,15 @@ module Travis
|
|||
extend self
|
||||
load_dir("#{__dir__}/v3")
|
||||
|
||||
ClientError = Error .create(status: 400)
|
||||
NotFound = ClientError .create(:resource, status: 404, template: '%s not found (or insufficient access)')
|
||||
EnitityMissing = NotFound .create(type: 'not_found')
|
||||
WrongCredentials = ClientError .create('access denied', status: 403)
|
||||
LoginRequired = ClientError .create('login required', status: 403)
|
||||
WrongParams = ClientError .create('wrong parameters')
|
||||
ClientError = Error .create(status: 400)
|
||||
NotFound = ClientError .create(:resource, status: 404, template: '%s not found (or insufficient access)')
|
||||
EntityMissing = NotFound .create(type: 'not_found')
|
||||
WrongCredentials = ClientError .create('access denied', status: 403)
|
||||
LoginRequired = ClientError .create('login required', status: 403)
|
||||
InsufficientAccess = ClientError .create(status: 403)
|
||||
WrongParams = ClientError .create('wrong parameters')
|
||||
ServerError = Error .create(status: 500)
|
||||
NotImplemented = ServerError .create('request not (yet) implemented', status: 501)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,5 +13,10 @@ module Travis::API::V3
|
|||
return key unless key.is_a? Symbol
|
||||
resolver_cache[key] ||= const_get(key.to_s.camelize)
|
||||
end
|
||||
|
||||
def extended(base)
|
||||
base.extend(ConstantResolver)
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
14
lib/travis/api/v3/queries/requests.rb
Normal file
14
lib/travis/api/v3/queries/requests.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
module Travis::API::V3
|
||||
class Queries::Requests < Query
|
||||
def schedule_for(repository)
|
||||
perform_async(:build_request, type: 'api'.freeze, payload: payload, credentials: {})
|
||||
end
|
||||
|
||||
def find(repository)
|
||||
end
|
||||
|
||||
def payload
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +1,27 @@
|
|||
module Travis::API::V3
|
||||
class Query
|
||||
@@sidekiq_cache = Tool::ThreadLocal.new
|
||||
|
||||
# generate from eval to avoid additional string allocations on every params access
|
||||
@@params_accessor = <<-RUBY
|
||||
attr_writer :%<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].nil? or @params['@type'.freeze] == '%<prefix>s'.freeze
|
||||
@%<name>s = nil
|
||||
end
|
||||
|
||||
def %<name>s!
|
||||
%<name>s or raise WrongParams, 'missing %<prefix>s.%<name>s'.freeze, missing_field: '%<prefix>s.%<name>s'.freeze
|
||||
end
|
||||
RUBY
|
||||
|
||||
def self.params(*list, prefix: nil)
|
||||
prefix ||= name[/[^:]+$/].underscore
|
||||
list.each { |e| class_eval("def #{e}; @params[\"#{prefix}.#{e}\".freeze]; end") }
|
||||
list.each { |e| class_eval(@@params_accessor % { name: e, prefix: prefix }) }
|
||||
end
|
||||
|
||||
attr_reader :params
|
||||
|
@ -11,6 +30,15 @@ module Travis::API::V3
|
|||
@params = params
|
||||
end
|
||||
|
||||
def perform_async(worker, *args)
|
||||
class_name, queue, client = @@sidekiq_cache[identifier] ||= [
|
||||
"Travis::Sidekiq::#{identifier.to_s.camelcase}".freeze,
|
||||
identifier.to_s.pluralize.freeze
|
||||
]
|
||||
|
||||
::Sidekiq::Client.push('queue'.freeze => queue, 'class'.freeze => class_name, 'args'.freeze => args)
|
||||
end
|
||||
|
||||
def bool(value)
|
||||
return false if value == 'false'.freeze
|
||||
!!value
|
||||
|
|
|
@ -6,5 +6,14 @@ module Travis::API::V3
|
|||
def format_date(date)
|
||||
date && date.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
end
|
||||
|
||||
def get_attributes(object, *attributes, **defaults)
|
||||
attributes.map { |a| [a, get_attribute(object, a, **defaults)] }.to_h
|
||||
end
|
||||
|
||||
def get_attribute(object, attribute, **defaults)
|
||||
value = object.public_send(attribute)
|
||||
value.nil? ? defaults[attribute] : value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
12
lib/travis/api/v3/renderer/accepted.rb
Normal file
12
lib/travis/api/v3/renderer/accepted.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
module Travis::API::V3
|
||||
module Renderer::Error
|
||||
extend self
|
||||
|
||||
def render(type)
|
||||
{
|
||||
:@type => 'pending'.freeze,
|
||||
:resource_type => type
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
module Travis::API::V3
|
||||
module Renderer::Collection
|
||||
extend self
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
module Travis::API::V3
|
||||
module Renderer::Repository
|
||||
DIRECT_ATTRIBUTES = %i[id name slug description github_language private]
|
||||
DIRECT_ATTRIBUTES = %i[id name slug description github_language private active default_branch]
|
||||
DEFAULTS = { active: false, default_branch: 'master' }
|
||||
extend self
|
||||
|
||||
def render(repository)
|
||||
{ :@type => 'repository'.freeze, active: !!repository.active, **direct_attributes(repository), **nested_resources(repository) }
|
||||
end
|
||||
|
||||
def direct_attributes(repository)
|
||||
DIRECT_ATTRIBUTES.map { |a| [a, repository.public_send(a)] }.to_h
|
||||
{ :@type => 'repository'.freeze, **Renderer.get_attributes(repository, *DIRECT_ATTRIBUTES, **DEFAULTS), **nested_resources(repository) }
|
||||
end
|
||||
|
||||
def nested_resources(repository)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
module Travis::API::V3
|
||||
class Result
|
||||
attr_accessor :type, :resource
|
||||
attr_accessor :type, :resource, :status
|
||||
|
||||
def initialize(type, resource = [])
|
||||
@type, @resource = type, resource
|
||||
def initialize(type, resource = [], status: 200)
|
||||
@type, @resource, @status = type, resource, status
|
||||
end
|
||||
|
||||
def respond_to_missing?(method, *)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module Travis::API::V3
|
||||
class Router
|
||||
CASCADE = { 'X-Cascade'.freeze => 'pass'.freeze }
|
||||
include Travis::API::V3
|
||||
attr_accessor :routes
|
||||
|
||||
|
@ -20,12 +21,13 @@ module Travis::API::V3
|
|||
result = service.run
|
||||
render(result, env_params)
|
||||
rescue Error => error
|
||||
result = Result.new(:error, error)
|
||||
V3.response(result.render, 'X-Cascade'.freeze => 'pass'.freeze, status: error.status)
|
||||
result = Result.new(:error, error)
|
||||
headers = error.status == 404 ? CASCADE : {}
|
||||
V3.response(result.render, headers, status: error.status)
|
||||
end
|
||||
|
||||
def render(result, env_params)
|
||||
V3.response(result.render)
|
||||
V3.response(result.render, status: result.status)
|
||||
end
|
||||
|
||||
def service_index(env)
|
||||
|
|
|
@ -5,17 +5,23 @@ module Travis::API::V3
|
|||
|
||||
resource :repository do
|
||||
route '/repo/{repository.id}'
|
||||
get :find_repository
|
||||
get :find
|
||||
|
||||
resource :requests do
|
||||
route '/requests'
|
||||
get :find
|
||||
post :create
|
||||
end
|
||||
end
|
||||
|
||||
resource :repositories do
|
||||
route '/repos'
|
||||
get :repositories_for_current_user
|
||||
get :for_current_user
|
||||
end
|
||||
|
||||
resource :organizations do
|
||||
route '/orgs'
|
||||
get :organizations_for_current_user
|
||||
get :for_current_user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,10 @@ module Travis::API::V3
|
|||
@current_resource ||= nil
|
||||
end
|
||||
|
||||
def prefix
|
||||
@prefix ||= ""
|
||||
end
|
||||
|
||||
def resource(type, &block)
|
||||
resource = Routes::Resource.new(type)
|
||||
with_resource(resource, &block)
|
||||
|
@ -22,13 +26,15 @@ module Travis::API::V3
|
|||
|
||||
def with_resource(resource)
|
||||
resource_was, @current_resource = current_resource, resource
|
||||
prefix_was, @prefix = @prefix, resource_was.route if resource_was
|
||||
yield
|
||||
ensure
|
||||
@prefix = prefix_was if resource_was
|
||||
@current_resource = resource_was
|
||||
end
|
||||
|
||||
def route(value)
|
||||
current_resource.route = value
|
||||
current_resource.route = prefix + value
|
||||
end
|
||||
|
||||
def get(*args)
|
||||
|
@ -45,7 +51,7 @@ module Travis::API::V3
|
|||
resource.services.each do |(request_method, sub_route), service|
|
||||
route = sub_route ? prefix + sub_route : prefix
|
||||
routes[route] ||= {}
|
||||
routes[route][request_method] = Services[service]
|
||||
routes[route][request_method] = Services[resource.identifier][service]
|
||||
end
|
||||
end
|
||||
self.routes.replace(routes)
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
module Travis::API::V3
|
||||
class Service
|
||||
def self.helpers(*list)
|
||||
include(*list.map { |e| ServiceHelpers[e] })
|
||||
end
|
||||
|
||||
def self.result_type(type = nil)
|
||||
@result_type = type if type
|
||||
@result_type = type if type
|
||||
@result_type ||= parent.result_type if parent and parent.respond_to? :result_type
|
||||
raise 'result type not set' unless defined? @result_type
|
||||
@result_type
|
||||
end
|
||||
|
@ -24,10 +29,22 @@ module Travis::API::V3
|
|||
raise(error, type || self.class.result_type)
|
||||
end
|
||||
|
||||
def run!
|
||||
not_implemented
|
||||
end
|
||||
|
||||
def run
|
||||
not_found unless result = run!
|
||||
result = Result.new(self.class.result_type, result) unless result.is_a? Result
|
||||
result
|
||||
end
|
||||
|
||||
def accepted(type = self.class.result_type)
|
||||
Result.new(:accepted, type, status: 202)
|
||||
end
|
||||
|
||||
def not_implemented
|
||||
raise NotImplemented
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
5
lib/travis/api/v3/service_helpers.rb
Normal file
5
lib/travis/api/v3/service_helpers.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
module Travis::API::V3
|
||||
module ServiceHelpers
|
||||
extend ConstantResolver
|
||||
end
|
||||
end
|
13
lib/travis/api/v3/service_helpers/repository.rb
Normal file
13
lib/travis/api/v3/service_helpers/repository.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
module Travis::API::V3
|
||||
module ServiceHelpers::Repository
|
||||
def repository
|
||||
@repository ||= find_repository
|
||||
end
|
||||
|
||||
def find_repository
|
||||
not_found(true, :repository) unless repo = query(:repository).find
|
||||
not_found(false, :repository) unless access_control.visible? repo
|
||||
repo
|
||||
end
|
||||
end
|
||||
end
|
|
@ -27,15 +27,14 @@ module Travis::API::V3
|
|||
routes.resources.each do |resource|
|
||||
resources[resource.identifier] ||= {}
|
||||
resource.services.each do |(request_method, sub_route), service|
|
||||
service &&= service.to_s.sub(/^#{resource.identifier}_|_#{resource.identifier}$/, ''.freeze)
|
||||
list = resources[resource.identifier][service] ||= []
|
||||
pattern = sub_route ? resource.route + sub_route : resource.route
|
||||
list = resources[resource.identifier][service] ||= []
|
||||
pattern = sub_route ? resource.route + sub_route : resource.route
|
||||
pattern.to_templates.each do |template|
|
||||
list << { 'request-method'.freeze => request_method, 'uri-template'.freeze => prefix + template }
|
||||
end
|
||||
end
|
||||
end
|
||||
{ resources: resources }
|
||||
{ :@type => 'home'.freeze, :resources => resources }
|
||||
end
|
||||
|
||||
def render_json_home
|
||||
|
@ -43,9 +42,8 @@ module Travis::API::V3
|
|||
|
||||
routes.resources.each do |resource|
|
||||
resource.services.each do |(request_method, sub_route), service|
|
||||
service &&= service.to_s.sub(/_#{resource.identifier}$/, ''.freeze)
|
||||
pattern = sub_route ? resource.route + sub_route : resource.route
|
||||
relation = "http://schema.travis-ci.com/rel/#{resource.identifier}/#{service}"
|
||||
pattern = sub_route ? resource.route + sub_route : resource.route
|
||||
relation = "http://schema.travis-ci.com/rel/#{resource.identifier}/#{service}"
|
||||
pattern.to_templates.each do |template|
|
||||
relations[relation] ||= {}
|
||||
relations[relation][template] ||= { allow: [], vars: template.scan(/{\+?([^}]+)}/).flatten }
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
module Travis::API::V3
|
||||
module Services
|
||||
extend ConstantResolver
|
||||
|
||||
Organizations = Module.new { extend Services }
|
||||
Repositories = Module.new { extend Services }
|
||||
Repository = Module.new { extend Services }
|
||||
Requests = Module.new { extend Services }
|
||||
|
||||
def result_type
|
||||
@resul_type ||= name[/[^:]+$/].underscore.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
module Travis::API::V3
|
||||
class Services::FindRepository < Service
|
||||
result_type :repository
|
||||
|
||||
def run!
|
||||
repository if repository and access_control.visible? repository
|
||||
end
|
||||
|
||||
def repository
|
||||
not_found(true) if defined?(@repository) and @repository.nil?
|
||||
@repository ||= find_repository
|
||||
end
|
||||
|
||||
def find_repository
|
||||
query.find
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,5 @@
|
|||
module Travis::API::V3
|
||||
class Services::RepositoriesForCurrentUser < Service
|
||||
result_type :repositories
|
||||
|
||||
class Services::Repositories::ForCurrentUser < Service
|
||||
def run!
|
||||
raise LoginRequired unless access_control.logged_in?
|
||||
query.for_member(access_control.user)
|
|
@ -1,7 +1,5 @@
|
|||
module Travis::API::V3
|
||||
class Services::OrganizationsForCurrentUser < Service
|
||||
result_type :organizations
|
||||
|
||||
class Services::Organizations::ForCurrentUser < Service
|
||||
def run!
|
||||
raise LoginRequired unless access_control.logged_in?
|
||||
query.for_member(access_control.user)
|
9
lib/travis/api/v3/services/repository/find.rb
Normal file
9
lib/travis/api/v3/services/repository/find.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
module Travis::API::V3
|
||||
class Services::Repository::Find < Service
|
||||
helpers :repository
|
||||
|
||||
def run!
|
||||
repository
|
||||
end
|
||||
end
|
||||
end
|
11
lib/travis/api/v3/services/requests/create.rb
Normal file
11
lib/travis/api/v3/services/requests/create.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
module Travis::API::V3
|
||||
class Services::Requests::Create < Service
|
||||
helpers :repository
|
||||
|
||||
def run
|
||||
not_implemented
|
||||
query.schedule_for(repository)
|
||||
accepted
|
||||
end
|
||||
end
|
||||
end
|
5
lib/travis/api/v3/services/requests/find.rb
Normal file
5
lib/travis/api/v3/services/requests/find.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
module Travis::API::V3
|
||||
class Services::Requests::Find < Service
|
||||
helpers :repository
|
||||
end
|
||||
end
|
|
@ -9,11 +9,14 @@ describe Travis::API::V3::ServiceIndex do
|
|||
describe "custom json entry point" do
|
||||
let(:expected_resources) {{
|
||||
"repository" => {
|
||||
"find" => [{"request-method"=>"GET", "uri-template"=>"#{path}repo/{repository.id}"}] },
|
||||
"find" => [{"request-method"=>"GET", "uri-template"=>"#{path}repo/{repository.id}"}] },
|
||||
"repositories" => {
|
||||
"for_current_user" => [{"request-method"=>"GET", "uri-template"=>"#{path}repos"}] },
|
||||
"for_current_user" => [{"request-method"=>"GET", "uri-template"=>"#{path}repos"}] },
|
||||
"organizations" => {
|
||||
"for_current_user" => [{"request-method"=>"GET", "uri-template"=>"#{path}orgs"}] }
|
||||
"for_current_user" => [{"request-method"=>"GET", "uri-template"=>"#{path}orgs"}] },
|
||||
"requests" => {
|
||||
"find" => [{"request-method"=>"GET", "uri-template"=>"#{path}repo/{repository.id}/requests"}],
|
||||
"create" => [{"request-method"=>"POST", "uri-template"=>"#{path}repo/{repository.id}/requests"}]}
|
||||
}}
|
||||
|
||||
describe 'with /v3 prefix' do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Travis::API::V3::Services::FindRepository do
|
||||
describe Travis::API::V3::Services::Organizations::ForCurrentUser do
|
||||
let(:repo) { Repository.by_slug('svenfuchs/minimal').first }
|
||||
|
||||
let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) }
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Travis::API::V3::Services::FindRepository do
|
||||
describe Travis::API::V3::Services::Repositories::ForCurrentUser do
|
||||
let(:repo) { Repository.by_slug('svenfuchs/minimal').first }
|
||||
|
||||
let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) }
|
||||
|
@ -23,6 +23,7 @@ describe Travis::API::V3::Services::FindRepository do
|
|||
"github_language" => nil,
|
||||
"active" => true,
|
||||
"private" => true,
|
||||
"default_branch" => "master",
|
||||
"owner" => {
|
||||
"@type" => "user",
|
||||
"id" => repo.owner_id,
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Travis::API::V3::Services::FindRepository do
|
||||
describe Travis::API::V3::Services::Repository::Find do
|
||||
let(:repo) { Repository.by_slug('svenfuchs/minimal').first }
|
||||
|
||||
describe "public repository" do
|
||||
|
@ -15,6 +15,7 @@ describe Travis::API::V3::Services::FindRepository do
|
|||
"github_language" => nil,
|
||||
"active" => true,
|
||||
"private" => false,
|
||||
"default_branch" => "master",
|
||||
"owner" => {
|
||||
"@type" => "user",
|
||||
"id" => repo.owner_id,
|
||||
|
@ -84,6 +85,7 @@ describe Travis::API::V3::Services::FindRepository do
|
|||
"github_language" => nil,
|
||||
"active" => true,
|
||||
"private" => true,
|
||||
"default_branch" => "master",
|
||||
"owner" => {
|
||||
"@type" => "user",
|
||||
"id" => repo.owner_id,
|
||||
|
@ -138,6 +140,7 @@ describe Travis::API::V3::Services::FindRepository do
|
|||
"github_language" => nil,
|
||||
"active" => true,
|
||||
"private" => true,
|
||||
"default_branch" => "master",
|
||||
"owner" => {
|
||||
"@type" => "user",
|
||||
"id" => repo.owner_id,
|
||||
|
@ -198,6 +201,7 @@ describe Travis::API::V3::Services::FindRepository do
|
|||
"github_language" => nil,
|
||||
"active" => true,
|
||||
"private" => true,
|
||||
"default_branch" => "master",
|
||||
"owner" => {
|
||||
"@type" => "user",
|
||||
"id" => repo.owner_id,
|
Loading…
Reference in New Issue
Block a user