v3: initial stab at request creation api

This commit is contained in:
Konstantin Haase 2015-02-11 10:05:08 +01:00
parent d3157ce0b8
commit 3567a759c2
29 changed files with 208 additions and 61 deletions

5
app.rb Normal file
View File

@ -0,0 +1,5 @@
require 'sinatra'
get '/' do
"<form method=post><input type=submit /></form>"
end

View File

@ -17,12 +17,13 @@ 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')
end
end
end

View File

@ -1,5 +1,9 @@
module Travis::API::V3
class AccessControl::Generic
DEFAULT_LIMIT = 25
MAX_LIMT = 100
NO_LIMIT = 2 ** 62 - 1 # larges Fixnum on MRI
def self.for_request(type, payload, env)
end
@ -18,6 +22,23 @@ module Travis::API::V3
false
end
# def limit(resource_type, value = nil)
# case value
# when ''.freeze, 'true'.freeze, true, nil then DEFAULT_LIMIT
# when 'false'.freeze, false then NO_LIMIT
# when /^\d+$/ then limit(resource_type, Integer(value))
# when 0..MAX_LIMIT then value
# end
# # # TODO move to config
# # value = Time.now.to_i if value == false or value == 'false'.freeze
# # value = 25 if value.nil? or value == ''.freezee or value ==
# # value = Integer(value)
# # value = 100 if value > 100 and not full_access?
# # value = 0 if value < 0
# rescue TypeError
# raise WrongParams, 'limit must be a positive integer'.freeze, resource_type: resource_type
# end
protected
def repository_visible?(repository)

View File

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

View 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

View File

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

View File

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

View 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

View File

@ -1,4 +1,3 @@
module Travis::API::V3
module Renderer::Collection
extend self

View File

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

View File

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

View File

@ -25,7 +25,7 @@ module Travis::API::V3
end
def render(result, env_params)
V3.response(result.render)
V3.response(result.render, status: result.status)
end
def service_index(env)

View File

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

View File

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

View File

@ -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
@ -29,5 +34,9 @@ module Travis::API::V3
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
end
end

View File

@ -0,0 +1,5 @@
module Travis::API::V3
module ServiceHelpers
extend ConstantResolver
end
end

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,9 @@
module Travis::API::V3
class Services::Repository::Find < Service
helpers :repository
def run!
repository
end
end
end

View File

@ -0,0 +1,10 @@
module Travis::API::V3
class Services::Requests::Create < Service
helpers :repository
def run
query.schedule_for(repository)
accepted
end
end
end

View File

@ -0,0 +1,5 @@
module Travis::API::V3
class Services::Requests::Find < Service
helpers :repository
end
end

View File

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

View File

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

View File

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

View File

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