• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ruby-grape / grape / 14282460066

05 Apr 2025 01:38PM UTC coverage: 98.163% (+0.07%) from 98.098%
14282460066

Pull #2553

github

web-flow
Merge aa00f262f into 2b35fede4
Pull Request #2553: Less parse nested query

40 of 40 new or added lines in 8 files covered. (100.0%)

1 existing line in 1 file now uncovered.

3581 of 3648 relevant lines covered (98.16%)

75312.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.72
/lib/grape/middleware/formatter.rb
1
# frozen_string_literal: true
2

3
module Grape
60✔
4
  module Middleware
60✔
5
    class Formatter < Base
60✔
6

7
      def default_options
60✔
8
        {
13,761✔
9
          default_format: :txt,
61,212✔
10
          formatters: {},
11
          parsers: {}
12
        }
13
      end
14

15
      def before
60✔
16
        negotiate_content_type
89,485✔
17
        read_body_input
89,289✔
18
      end
19

20
      def after
60✔
21
        return unless @app_response
88,554✔
22

23
        status, headers, bodies = *@app_response
62,329✔
24

25
        if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
62,329✔
26
          [status, headers, []]
6,813✔
27
        else
28
          build_formatted_response(status, headers, bodies)
55,516✔
29
        end
30
      end
31

32
      private
60✔
33

34
      def build_formatted_response(status, headers, bodies)
60✔
35
        headers = ensure_content_type(headers)
55,516✔
36

37
        if bodies.is_a?(Grape::ServeStream::StreamResponse)
55,516✔
38
          Grape::ServeStream::SendfileResponse.new([], status, headers) do |resp|
294✔
39
            resp.body = bodies.stream
294✔
40
          end
41
        else
42
          # Allow content-type to be explicitly overwritten
43
          formatter = fetch_formatter(headers, options)
55,222✔
44
          bodymap = ActiveSupport::Notifications.instrument('format_response.grape', formatter: formatter, env: env) do
55,222✔
45
            bodies.collect { |body| formatter.call(body, env) }
110,444✔
46
          end
47
          Rack::Response.new(bodymap, status, headers)
54,977✔
48
        end
49
      rescue Grape::Exceptions::InvalidFormatter => e
50
        throw :error, status: 500, message: e.message, backtrace: e.backtrace, original_exception: e
147✔
51
      end
52

53
      def fetch_formatter(headers, options)
60✔
54
        api_format = env.fetch(Grape::Env::API_FORMAT) { mime_types[headers[Rack::CONTENT_TYPE]] }
55,222✔
55
        Grape::Formatter.formatter_for(api_format, options[:formatters])
55,222✔
56
      end
57

58
      # Set the content type header for the API format if it is not already present.
59
      #
60
      # @param headers [Hash]
61
      # @return [Hash]
62
      def ensure_content_type(headers)
60✔
63
        if headers[Rack::CONTENT_TYPE]
55,516✔
64
          headers
442✔
65
        else
66
          headers.merge(Rack::CONTENT_TYPE => content_type_for(env[Grape::Env::API_FORMAT]))
55,074✔
67
        end
68
      end
69

70
      def read_body_input
60✔
71
        input = rack_request.body # reads RACK_INPUT
89,289✔
72
        return if input.nil?
89,289✔
73
        return unless read_body_input?
51,258✔
74

75
        input.try(:rewind)
7,497✔
76
        body = env[Grape::Env::API_REQUEST_INPUT] = input.read
7,497✔
77
        begin
78
          read_rack_input(body)
7,497✔
79
        ensure
80
          input.try(:rewind)
7,497✔
81
        end
82
      end
83

84
      def read_rack_input(body)
60✔
85
        return if body.empty?
7,497✔
86

87
        media_type = rack_request.media_type
7,497✔
88
        fmt = media_type ? mime_types[media_type] : options[:default_format]
7,497✔
89

90
        throw :error, status: 415, message: "The provided content-type '#{media_type}' is not supported." unless content_type_for(fmt)
7,497✔
91
        parser = Grape::Parser.parser_for fmt, options[:parsers]
7,203✔
92
        if parser
7,203✔
93
          begin
94
            body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))
7,056✔
95
            if body.is_a?(Hash)
6,615✔
96
              env[Rack::RACK_REQUEST_FORM_HASH] = if env.key?(Rack::RACK_REQUEST_FORM_HASH)
4,263✔
UNCOV
97
                                                    env[Rack::RACK_REQUEST_FORM_HASH].merge(body)
×
98
                                                  else
99
                                                    body
4,263✔
100
                                                  end
101
              env[Rack::RACK_REQUEST_FORM_INPUT] = env[Rack::RACK_INPUT]
4,263✔
102
            end
103
          rescue Grape::Exceptions::Base => e
279✔
104
            raise e
392✔
105
          rescue StandardError => e
106
            throw :error, status: 400, message: e.message, backtrace: e.backtrace, original_exception: e
49✔
107
          end
108
        else
109
          env[Grape::Env::API_REQUEST_BODY] = body
147✔
110
        end
111
      end
112

113
      # this middleware will not try to format the following content-types since Rack already handles them
114
      # when calling Rack's `params` function
115
      # - application/x-www-form-urlencoded
116
      # - multipart/form-data
117
      # - multipart/related
118
      # - multipart/mixed
119
      def read_body_input?
60✔
120
        (rack_request.post? || rack_request.put? || rack_request.patch? || rack_request.delete?) &&
51,258✔
121
          !(rack_request.form_data? && rack_request.content_type) &&
18,533✔
122
          !rack_request.parseable_data? &&
123
          (rack_request.content_length.to_i.positive? || rack_request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == 'chunked')
7,938✔
124
      end
125

126
      def negotiate_content_type
60✔
127
        fmt = format_from_extension || query_params['format'] || options[:format] || format_from_header || options[:default_format]
89,485✔
128
        if content_type_for(fmt)
89,485✔
129
          env[Grape::Env::API_FORMAT] = fmt.to_sym
89,289✔
130
        else
131
          throw :error, status: 406, message: "The requested format '#{fmt}' is not supported."
196✔
132
        end
133
      end
134

135
      def format_from_extension
60✔
136
        request_path = rack_request.path.try(:scrub)
89,485✔
137
        dot_pos = request_path.rindex('.')
89,485✔
138
        return unless dot_pos
89,485✔
139

140
        extension = request_path[dot_pos + 1..]
3,332✔
141
        extension if content_type_for(extension)
3,332✔
142
      end
143

144
      def format_from_header
60✔
145
        accept_header = env[Grape::Http::Headers::HTTP_ACCEPT].try(:scrub)
73,012✔
146
        return if accept_header.blank?
73,012✔
147

148
        media_type = Rack::Utils.best_q_match(accept_header, mime_types.keys)
2,009✔
149
        mime_types[media_type] if media_type
2,009✔
150
      end
151
    end
152
  end
153
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc