class Xenon::API
Constants
- DEFAULT_MARSHALLERS
Attributes
context[R]
Public Class Methods
marshallers(*marshallers)
click to toggle source
# File lib/xenon/api.rb, line 12 def marshallers(*marshallers) @marshallers = marshallers unless marshallers.nil? || marshallers.empty? (@marshallers.nil? || @marshallers.empty?) ? DEFAULT_MARSHALLERS : @marshallers end
method_missing(name, *args, &block)
click to toggle source
Calls superclass method
# File lib/xenon/api.rb, line 37 def method_missing(name, *args, &block) if instance_methods.include?(name) routes << [name, args, block] else super end end
request_marshaller(content_type)
click to toggle source
# File lib/xenon/api.rb, line 17 def request_marshaller(content_type) marshallers.find { |m| m.unmarshal?(content_type.media_type) } end
response_marshaller(media_ranges)
click to toggle source
# File lib/xenon/api.rb, line 21 def response_marshaller(media_ranges) weighted = marshallers.map do |marshaller| media_range = media_ranges.find { |media_range| marshaller.marshal?(media_range) } [marshaller, media_range ? media_range.q : 0] end weighted.select { |_, q| q > 0 }.sort_by { |_, q| q }.map { |m, _| m }.last end
routes()
click to toggle source
# File lib/xenon/api.rb, line 33 def routes @routes ||= [] end
Public Instance Methods
call(env)
click to toggle source
# File lib/xenon/api.rb, line 46 def call(env) dup.call!(env) end
call!(env)
click to toggle source
# File lib/xenon/api.rb, line 50 def call!(env) @context = Routing::Context.new(Request.new(Rack::Request.new(env)), Response.new) accept = @context.request.header('Accept') response_marshaller = accept ? self.class.response_marshaller(accept.media_ranges) : self.class.marshallers.first catch :complete do begin if response_marshaller self.class.routes.each do |route| name, args, block = route route_block = proc { instance_eval(&block) } send(name, *args, &route_block) end else reject :accept, supported: self.class.marshallers.map(&:media_type) end handle_rejections(@context.rejections) rescue => e handle_error(e) end end response_marshaller ||= self.class.marshallers.first headers = @context.response.headers.set(Headers::ContentType.new(response_marshaller.content_type)) if @context.request.allow_response_body? body = response_marshaller.marshal(@context.response.body) else # TODO: Suppress the Content-Lenth heder body = [] end resp = @context.response.copy(headers: headers, body: body) [resp.status, resp.headers.map { |h| [h.name, h.to_s] }.to_h, resp.body] end
handle_error(e)
click to toggle source
# File lib/xenon/api.rb, line 85 def handle_error(e) puts "handle_error: #{e.class}: #{e}\n #{e.backtrace.join("\n ")}" case e when ParseError fail 400, e.message else fail 500, e.message # TODO: Only if verbose errors configured end end
handle_rejections(rejections)
click to toggle source
# File lib/xenon/api.rb, line 95 def handle_rejections(rejections) puts "handle_rejections: #{rejections}" if rejections.empty? fail 404 else rejection = rejections.first case rejection.reason when :accept fail 406, "Supported media types: #{rejection[:supported].join(", ")}" when :forbidden fail 403 when :header fail 400, "Missing required header: #{rejection[:required]}" when :method supported = rejections.take_while { |r| r.reason == :method }.flat_map { |r| r[:supported] } fail 405, "Supported methods: #{supported.map(&:upcase).join(", ")}" when :unauthorized if rejection[:scheme] challenge = Headers::Challenge.new(rejection[:scheme], rejection.info.except(:scheme)) respond_with_header Headers::WWWAuthenticate.new(challenge) do fail 401 end else fail 401 end else fail 500 end end end