Return-Path: Delivered-To: apmail-incubator-deltacloud-dev-archive@minotaur.apache.org Received: (qmail 95280 invoked from network); 1 Nov 2010 14:07:28 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 1 Nov 2010 14:07:28 -0000 Received: (qmail 37499 invoked by uid 500); 1 Nov 2010 14:07:59 -0000 Delivered-To: apmail-incubator-deltacloud-dev-archive@incubator.apache.org Received: (qmail 37482 invoked by uid 500); 1 Nov 2010 14:07:59 -0000 Mailing-List: contact deltacloud-dev-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: deltacloud-dev@incubator.apache.org Delivered-To: mailing list deltacloud-dev@incubator.apache.org Received: (qmail 37474 invoked by uid 99); 1 Nov 2010 14:07:59 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 01 Nov 2010 14:07:59 +0000 X-ASF-Spam-Status: No, hits=-5.0 required=10.0 tests=RCVD_IN_DNSWL_HI,SPF_HELO_PASS,SPF_PASS X-Spam-Check-By: apache.org Received-SPF: pass (athena.apache.org: domain of mfojtik@redhat.com designates 209.132.183.28 as permitted sender) Received: from [209.132.183.28] (HELO mx1.redhat.com) (209.132.183.28) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 01 Nov 2010 14:07:54 +0000 Received: from int-mx02.intmail.prod.int.phx2.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id oA1E7Xmg027673 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Mon, 1 Nov 2010 10:07:33 -0400 Received: from patashnik.brq.redhat.com (dhcp-2-138.brq.redhat.com [10.34.2.138]) by int-mx02.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id oA1E7VDr023826 for ; Mon, 1 Nov 2010 10:07:32 -0400 From: mfojtik@redhat.com To: deltacloud-dev@incubator.apache.org Subject: [PATCH core 1/2] Moved Rabbit DSL to separate gem Date: Mon, 1 Nov 2010 15:07:28 +0100 Message-Id: <1288620449-8918-2-git-send-email-mfojtik@redhat.com> In-Reply-To: <1288620449-8918-1-git-send-email-mfojtik@redhat.com> References: <1288620449-8918-1-git-send-email-mfojtik@redhat.com> X-Scanned-By: MIMEDefang 2.67 on 10.5.11.12 From: Michal Fojtik --- server/deltacloud.rb | 2 - server/lib/deltacloud/base_driver/features.rb | 4 +- server/lib/deltacloud/hardware_profile.rb | 2 +- server/lib/deltacloud/validation.rb | 70 ------- server/lib/sinatra/rabbit.rb | 273 ------------------------- server/lib/sinatra/respond_to.rb | 248 ---------------------- server/lib/sinatra/static_assets.rb | 1 - server/lib/sinatra/url_for.rb | 53 ----- server/server.rb | 10 +- 9 files changed, 9 insertions(+), 654 deletions(-) delete mode 100644 server/lib/deltacloud/validation.rb delete mode 100644 server/lib/sinatra/rabbit.rb delete mode 100644 server/lib/sinatra/respond_to.rb delete mode 100644 server/lib/sinatra/url_for.rb diff --git a/server/deltacloud.rb b/server/deltacloud.rb index 82e5780..0ed3bd9 100644 --- a/server/deltacloud.rb +++ b/server/deltacloud.rb @@ -15,6 +15,4 @@ require 'deltacloud/models/storage_snapshot' require 'deltacloud/models/storage_volume' require 'deltacloud/models/bucket' require 'deltacloud/models/blob' - -require 'deltacloud/validation' require 'deltacloud/helpers' diff --git a/server/lib/deltacloud/base_driver/features.rb b/server/lib/deltacloud/base_driver/features.rb index 3b19ca2..ae358c0 100644 --- a/server/lib/deltacloud/base_driver/features.rb +++ b/server/lib/deltacloud/base_driver/features.rb @@ -1,4 +1,4 @@ -require 'deltacloud/validation' +require 'sinatra/rabbit/validation' # Add advertising of optional features to the base driver module Deltacloud @@ -14,7 +14,7 @@ module Deltacloud class Operation attr_reader :name - include Deltacloud::Validation + include ::Sinatra::Rabbit::Validation def initialize(name, &block) @name = name diff --git a/server/lib/deltacloud/hardware_profile.rb b/server/lib/deltacloud/hardware_profile.rb index 62aca33..53dcbca 100644 --- a/server/lib/deltacloud/hardware_profile.rb +++ b/server/lib/deltacloud/hardware_profile.rb @@ -61,7 +61,7 @@ module Deltacloud else args = [param, :string, :optional, values.collect { |v| v.to_s} ] end - Validation::Param.new(args) + ::Sinatra::Rabbit::Validation::Param.new(args) end def include?(v) diff --git a/server/lib/deltacloud/validation.rb b/server/lib/deltacloud/validation.rb deleted file mode 100644 index b4eb3ae..0000000 --- a/server/lib/deltacloud/validation.rb +++ /dev/null @@ -1,70 +0,0 @@ -module Deltacloud::Validation - - class Failure < StandardError - attr_reader :param - def initialize(param, msg='') - super(msg) - @param = param - end - - def name - param.name - end - end - - class Param - attr_reader :name, :klass, :type, :options, :description - - def initialize(args) - @name = args[0] - @klass = args[1] || :string - @type = args[2] || :optional - @options = args[3] || [] - @description = args[4] || '' - end - - def required? - type.eql?(:required) - end - - def optional? - type.eql?(:optional) - end - end - - def param(*args) - raise DuplicateParamException if params[args[0]] - p = Param.new(args) - params[p.name] = p - end - - def params - @params ||= {} - @params - end - - # Add the parameters in hash +new+ to already existing parameters. If - # +new+ contains a parameter with an already existing name, the old - # definition is clobbered. - def add_params(new) - # We do not check for duplication on purpose: multiple calls - # to add_params should be cumulative - new.each { |p| @params[p.name] = p } - end - - def each_param(&block) - params.each_value { |p| yield p } - end - - def validate(values) - each_param do |p| - if p.required? and not values[p.name] - raise Failure.new(p, "Required parameter #{p.name} not found") - end - if values[p.name] and not p.options.empty? and - not p.options.include?(values[p.name]) - raise Failure.new(p, "Parameter #{p.name} has value #{values[p.name]} which is not in #{p.options.join(", ")}") - end - end - end -end diff --git a/server/lib/sinatra/rabbit.rb b/server/lib/sinatra/rabbit.rb deleted file mode 100644 index f411268..0000000 --- a/server/lib/sinatra/rabbit.rb +++ /dev/null @@ -1,273 +0,0 @@ -require 'sinatra/base' -require 'sinatra/url_for' -require 'deltacloud/validation' - -module Sinatra - - module Rabbit - - class DuplicateParamException < Exception; end - class DuplicateOperationException < Exception; end - class DuplicateCollectionException < Exception; end - - class Operation - attr_reader :name, :method - - include ::Deltacloud::Validation - - STANDARD = { - :index => { :method => :get, :member => false }, - :show => { :method => :get, :member => true }, - :create => { :method => :post, :member => false }, - :update => { :method => :put, :member => true }, - :destroy => { :method => :delete, :member => true } - } - - def initialize(coll, name, opts, &block) - @name = name.to_sym - opts = STANDARD[@name].merge(opts) if standard? - @collection = coll - raise "No method for operation #{name}" unless opts[:method] - @method = opts[:method].to_sym - @member = opts[:member] - @description = "" - instance_eval(&block) if block_given? - generate_documentation - end - - def standard? - STANDARD.keys.include?(name) - end - - def description(text="") - return @description if text.blank? - @description = text - end - - def generate_documentation - coll, oper = @collection, self - ::Sinatra::Application.get("/api/docs/#{@collection.name}/#{@name}") do - @collection, @operation = coll, oper - respond_to do |format| - format.html { haml :'docs/operation' } - format.xml { haml :'docs/operation' } - end - end - end - - def control(&block) - op = self - @control = Proc.new do - op.validate(params) - instance_eval(&block) - end - end - - def prefix - # FIXME: Make the /api prefix configurable - "/api" - end - - def path(args = {}) - l_prefix = args[:prefix] ? args[:prefix] : prefix - if @member - if standard? - "#{l_prefix}/#{@collection.name}/:id" - else - "#{l_prefix}/#{@collection.name}/:id/#{name}" - end - else - "#{l_prefix}/#{@collection.name}" - end - end - - def generate - ::Sinatra::Application.send(@method, path, {}, &@control) - # Set up some Rails-like URL helpers - if name == :index - gen_route "#{@collection.name}_url" - elsif name == :show - gen_route "#{@collection.name.to_s.singularize}_url" - else - gen_route "#{name}_#{@collection.name.to_s.singularize}_url" - end - end - - private - def gen_route(name) - route_url = path - if @member - ::Sinatra::Application.send(:define_method, name) do |id, *args| - url = query_url(route_url, args[0]) - url_for url.gsub(/:id/, id.to_s), :full - end - else - ::Sinatra::Application.send(:define_method, name) do |*args| - url = query_url(route_url, args[0]) - url_for url, :full - end - end - end - end - - class Collection - attr_reader :name, :operations - - def initialize(name, &block) - @name = name - @description = "" - @operations = {} - instance_eval(&block) if block_given? - generate_documentation - end - - # Set/Return description for collection - # If first parameter is not present, full description will be - # returned. - def description(text='') - return @description if text.blank? - @description = text - end - - def generate_documentation - coll, oper, features = self, @operations, driver.features(name) - ::Sinatra::Application.get("/api/docs/#{@name}") do - @collection, @operations, @features = coll, oper, features - respond_to do |format| - format.html { haml :'docs/collection' } - format.xml { haml :'docs/collection' } - end - end - end - - # Add a new operation for this collection. For the standard REST - # operations :index, :show, :update, and :destroy, we already know - # what method to use and whether this is an operation on the URL for - # individual elements or for the whole collection. - # - # For non-standard operations, options must be passed: - # :method : one of the HTTP methods - # :member : whether this is an operation on the collection or an - # individual element (FIXME: custom operations on the - # collection will use a nonsensical URL) The URL for the - # operation is the element URL with the name of the operation - # appended - # - # This also defines a helper method like show_instance_url that returns - # the URL to this operation (in request context) - def operation(name, opts = {}, &block) - raise DuplicateOperationException if @operations[name] - @operations[name] = Operation.new(self, name, opts, &block) - end - - def generate - operations.values.each { |op| op.generate } - app = ::Sinatra::Application - collname = name # Work around Ruby's weird scoping/capture - app.send(:define_method, "#{name.to_s.singularize}_url") do |id| - url_for "/api/#{collname}/#{id}", :full - end - - if index_op = operations[:index] - app.send(:define_method, "#{name}_url") do - url_for index_op.path.gsub(/\/\?$/,''), :full - end - end - end - - def add_feature_params(features) - features.each do |f| - f.operations.each do |fop| - if cop = operations[fop.name] - fop.params.each_key do |k| - if cop.params.has_key?(k) - raise DuplicateParamException, "Parameter '#{k}' for operation #{fop.name} defined by collection #{@name} and by feature #{f.name}" - else - cop.params[k] = fop.params[k] - end - end - end - end - end - end - end - - def collections - @collections ||= {} - end - - # Create a new collection. NAME should be the pluralized name of the - # collection. - # - # Adds a helper method #{name}_url which returns the URL to the :index - # operation on this collection. - def collection(name, &block) - raise DuplicateCollectionException if collections[name] - return unless driver.has_collection?(name.to_sym) - collections[name] = Collection.new(name, &block) - collections[name].add_feature_params(driver.features(name)) - collections[name].generate - end - - # Generate a root route for API docs - get '/api/docs\/?' do - respond_to do |format| - format.html { haml :'docs/index' } - format.xml { haml :'docs/index' } - end - end - - end - - module RabbitHelper - def query_url(url, params) - return url if params.nil? || params.empty? - url + "?#{URI.escape(params.collect{|k,v| "#{k}=#{v}"}.join('&'))}" - end - - def entry_points - collections.values.inject([]) do |m, coll| - url = url_for coll.operations[:index].path, :full - m << [ coll.name, url ] - end - end - end - - register Rabbit - helpers RabbitHelper -end - -class String - # Rails defines this for a number of other classes, including Object - # see activesupport/lib/active_support/core_ext/object/blank.rb - def blank? - self !~ /\S/ - end - - # Title case. - # - # "this is a string".titlecase - # => "This Is A String" - # - # CREDIT: Eliazar Parra - # Copied from facets - def titlecase - gsub(/\b\w/){ $`[-1,1] == "'" ? $& : $&.upcase } - end - - def pluralize - self + "s" - end - - def singularize - self.gsub(/s$/, '') - end - - def underscore - gsub(/::/, '/'). - gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). - gsub(/([a-z\d])([A-Z])/,'\1_\2'). - tr("-", "_"). - downcase - end -end diff --git a/server/lib/sinatra/respond_to.rb b/server/lib/sinatra/respond_to.rb deleted file mode 100644 index 963df65..0000000 --- a/server/lib/sinatra/respond_to.rb +++ /dev/null @@ -1,248 +0,0 @@ -# respond_to (The MIT License) - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software -# and associated documentation files (the 'Software'), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, publish, distribute, -# sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE - -require 'sinatra/base' -require 'rack/accept' - -use Rack::Accept - -module Sinatra - module RespondTo - - class MissingTemplate < Sinatra::NotFound; end - - # Define all MIME types you want to support here. - # This conversion table will be used for auto-negotiation - # with browser in sinatra when no 'format' parameter is specified. - - SUPPORTED_ACCEPT_HEADERS = { - :xml => [ - 'text/xml', - 'application/xml' - ], - :html => [ - 'text/html', - 'application/xhtml+xml' - ], - :json => [ - 'application/json' - ] - } - - # We need to pass array of available response types to - # best_media_type method - def accept_to_array - SUPPORTED_ACCEPT_HEADERS.keys.collect do |key| - SUPPORTED_ACCEPT_HEADERS[key] - end.flatten - end - - # Then, when we get best media type for response, we need - # to know which format to choose - def lookup_format_from_mime(mime) - SUPPORTED_ACCEPT_HEADERS.keys.each do |format| - return format if SUPPORTED_ACCEPT_HEADERS[format].include?(mime) - end - end - - def self.registered(app) - - app.helpers RespondTo::Helpers - - app.before do - - # Skip development error image and static content - next if self.class.development? && request.path_info =~ %r{/__sinatra__/.*?.png} - next if options.static? && options.public? && (request.get? || request.head?) && static_file?(request.path_info) - - # Remove extension from URI - # Extension will be available as a 'extension' method (extension=='txt') - - extension request.path_info.match(/\.([^\.\/]+)$/).to_a.first - - # If ?format= is present, ignore all Accept negotiations because - # we are not dealing with browser - if request.params.has_key? 'format' - format params['format'].to_sym - end - - # Let's make a little exception here to handle - # /api/instance_states[.gv/.png] calls - if extension.eql?('gv') - format :gv - elsif extension.eql?('png') - format :png - end - - # Get Rack::Accept::Response object and find best possible - # mime type to output. - # This negotiation works fine with latest rest-client gem: - # - # RestClient.get 'http://localhost:3001/api', {:accept => :json } => - # 'application/json' - # RestClient.get 'http://localhost:3001/api', {:accept => :xml } => - # 'application/xml' - # - # Also browsers like Firefox (3.6.x) and Chromium reporting - # 'application/xml+xhtml' which is recognized as :html reponse - # In browser you can force output using ?format=[format] parameter. - - rack_accept = env['rack-accept.request'] - - if rack_accept.media_type.to_s.strip.eql?('Accept:') - format :xml - elsif is_chrome? - format :html - else - format lookup_format_from_mime(rack_accept.best_media_type(accept_to_array)) - end - - end - - app.class_eval do - - # Simple helper to detect Chrome based browsers - # which have screwed up they Accept headers. - # Set HTML as default output format here - def is_chrome? - true if env['HTTP_USER_AGENT'] =~ /Chrome/ - end - - # This code was copied from respond_to plugin - # http://github.com/cehoffman/sinatra-respond_to - # MIT License - alias :render_without_format :render - def render(*args, &block) - assumed_layout = args[1] == :layout - args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol) - render_without_format *args, &block - rescue Errno::ENOENT => e - raise MissingTemplate, "#{args[1]}.#{args[0]}" unless assumed_layout - raise e - end - private :render - end - - # This code was copied from respond_to plugin - # http://github.com/cehoffman/sinatra-respond_to - app.configure :development do |dev| - dev.error MissingTemplate do - content_type :html, :charset => 'utf-8' - response.status = request.env['sinatra.error'].code - - engine = request.env['sinatra.error'].message.split('.').last - engine = 'haml' unless ['haml', 'builder', 'erb'].include? engine - - path = File.basename(request.path_info) - path = "root" if path.nil? || path.empty? - - format = engine == 'builder' ? 'xml' : 'html' - - layout = case engine - when 'haml' then "!!!\n%html\n %body= yield" - when 'erb' then "\n \n <%= yield %>\n \n" - end - - layout = "app.#{format}.#{engine}\n
#{escape_html(layout)}
" - - (<<-HTML).gsub(/^ {10}/, '') - - - - - - -

Sinatra can't find #{request.env['sinatra.error'].message}

- -
#{request.env['sinatra.error'].backtrace.join("\n")}
-
- application.rb -
#{request.request_method.downcase} '#{request.path_info}' do\n  respond_to do |wants|\n    wants.#{format} { #{engine} :#{path} }\n  end\nend
-
- - - HTML - end - - end - end - - module Helpers - - # This code was copied from respond_to plugin - # http://github.com/cehoffman/sinatra-respond_to - def self.included(klass) - klass.class_eval do - alias :content_type_without_save :content_type - def content_type(*args) - content_type_without_save *args - @_format = args.first.to_sym - response['Content-Type'] - end - end - end - - def static_file?(path) - public_dir = File.expand_path(options.public) - path = File.expand_path(File.join(public_dir, unescape(path))) - - path[0, public_dir.length] == public_dir && File.file?(path) - end - - - # Extension holds trimmed extension. This is extra usefull - # when you want to build original URI (with extension) - # You can simply call "#{request.env['REQUEST_URI']}.#{extension}" - def extension(val=nil) - @_extension ||= val - @_extension - end - - # This helper will holds current format. Helper should be - # accesible from all places in Sinatra - def format(val=nil) - @_format ||= val - @_format - end - - def respond_to(&block) - wants = {} - - def wants.method_missing(type, *args, &handler) - self[type] = handler - end - - # Set proper content-type and encoding for - # text based formats - if [:xml, :gv, :html, :json].include?(format) - content_type format, :charset => 'utf-8' - end - yield wants - # Raise this error if requested format is not defined - # in respond_to { } block. - raise MissingTemplate if wants[format].nil? - - wants[format].call - end - - end - - end -end diff --git a/server/lib/sinatra/static_assets.rb b/server/lib/sinatra/static_assets.rb index 29e2d67..061deb2 100644 --- a/server/lib/sinatra/static_assets.rb +++ b/server/lib/sinatra/static_assets.rb @@ -1,5 +1,4 @@ require 'sinatra/base' -require 'sinatra/url_for' module Sinatra module StaticAssets diff --git a/server/lib/sinatra/url_for.rb b/server/lib/sinatra/url_for.rb deleted file mode 100644 index ce95567..0000000 --- a/server/lib/sinatra/url_for.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'uri' - -module Sinatra - module UrlForHelper - # Construct a link to +url_fragment+, which should be given relative to - # the base of this Sinatra app. The mode should be either - # :path_only, which will generate an absolute path within - # the current domain (the default), or :full, which will - # include the site name and port number. (The latter is typically - # necessary for links in RSS feeds.) Example usage: - # - # url_for "/" # Returns "/myapp/" - # url_for "/foo" # Returns "/myapp/foo" - # url_for "/foo", :full # Returns "http://example.com/myapp/foo" - #-- - # See README.rdoc for a list of some of the people who helped me clean - # up earlier versions of this code. - def url_for url_fragment, mode=:path_only - case mode - when :path_only - base = request.script_name - when :full - scheme = request.scheme - if (scheme == 'http' && request.port == 80 || - scheme == 'https' && request.port == 443) - port = "" - else - port = ":#{request.port}" - end - request_host = HOSTNAME ? HOSTNAME : request.host - base = "#{scheme}://#{request_host}#{port}#{request.script_name}" - else - raise TypeError, "Unknown url_for mode #{mode}" - end - url_escape = URI.escape(url_fragment) - # Don't add the base fragment if url_for gets called more than once - # per url or the url_fragment passed in is an absolute url - if url_escape.match(/^#{base}/) or url_escape.match(/^http/) - url_escape - else - "#{base}#{url_escape}" - end - end - - def root_url - url_for '/' - end - end - - - - helpers UrlForHelper -end diff --git a/server/server.rb b/server/server.rb index af8bc09..d5670fe 100644 --- a/server/server.rb +++ b/server/server.rb @@ -1,15 +1,15 @@ require 'sinatra' +require 'sinatra/rabbit' require 'deltacloud' require 'drivers' require 'json' -require 'sinatra/respond_to' require 'sinatra/static_assets' -require 'sinatra/rabbit' require 'sinatra/lazy_auth' require 'erb' require 'haml' require 'open3' require 'lib/deltacloud/helpers/blob_stream' +require 'lib/deltacloud/helpers/rabbit_helper' configure do set :raise_errors => false @@ -28,7 +28,10 @@ end # whatever you want (eg. if you running API behind NAT) HOSTNAME=ENV['API_HOST'] ? ENV['API_HOST'] : nil -error Deltacloud::Validation::Failure do +Sinatra::Application::register Sinatra::RespondTo +Sinatra::Application::register Sinatra::Rabbit + +error ::Sinatra::Rabbit::Validation::Failure do report_error(400, "validation_failure") end @@ -40,7 +43,6 @@ error Deltacloud::BackendError do report_error(500, "backend_error") end -Sinatra::Application.register Sinatra::RespondTo # Redirect to /api get '/' do redirect url_for('/api'); end -- 1.7.2.3