Class: Apipie::Extractor::Recorder

Inherits:
Object
  • Object
show all
Defined in:
lib/apipie/extractor/recorder.rb

Defined Under Namespace

Modules: FunctionalTestRecording Classes: Middleware

Constant Summary collapse

MULTIPART_BOUNDARY =
'APIPIE_RECORDER_EXAMPLE_BOUNDARY'.freeze

Instance Method Summary collapse

Constructor Details

#initializeRecorder

Returns a new instance of Recorder.



6
7
8
# File 'lib/apipie/extractor/recorder.rb', line 6

def initialize
  @ignored_params = [:controller, :action]
end

Instance Method Details

#analyse_controller(controller) ⇒ Object



25
26
27
28
# File 'lib/apipie/extractor/recorder.rb', line 25

def analyse_controller(controller)
  @controller = controller.class
  @action = Apipie.configuration.api_action_matcher.call(controller)
end

#analyse_env(env) ⇒ Object



10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/apipie/extractor/recorder.rb', line 10

def analyse_env(env)
  @verb = env["REQUEST_METHOD"].to_sym
  @path = env["PATH_INFO"].sub(%r{^/*},"/")
  @query = env["QUERY_STRING"] unless env["QUERY_STRING"].blank?
  @params = Rack::Utils.parse_nested_query(@query)
  @params.merge!(env["action_dispatch.request.request_parameters"] || {})
  rack_input = env["rack.input"]
  if data = parse_data(rack_input&.read)
    @request_data = data
  elsif form_hash = env["rack.request.form_hash"]
    @request_data = reformat_multipart_data(form_hash)
  end
  rack_input&.rewind
end

#analyse_response(response) ⇒ Object



30
31
32
33
34
35
36
37
38
39
# File 'lib/apipie/extractor/recorder.rb', line 30

def analyse_response(response)
  if response.last.respond_to?(:body) && data = parse_data(response.last.body)
    @response_data = if response[1]['Content-Disposition'].to_s.start_with?('attachment')
                       '<STREAMED ATTACHMENT FILE>'
                     else
                       data
                     end
  end
  @code = response.first
end

#analyze_functional_test(test_context) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/apipie/extractor/recorder.rb', line 41

def analyze_functional_test(test_context)
  request, response = test_context.request, test_context.response
  @verb = request.request_method.to_sym
  @path = request.path
  @params = request.request_parameters
  if [:POST, :PUT, :PATCH, :DELETE].include?(@verb)
    @request_data = request.content_type == "multipart/form-data" ? reformat_multipart_data(@params) : @params
  else
    @query = request.query_string
  end
  if response.media_type != 'application/pdf'
    @response_data = parse_data(response.body)
  end
  @code = response.code
end

#content_disposition(name) ⇒ Object



115
116
117
# File 'lib/apipie/extractor/recorder.rb', line 115

def content_disposition(name)
  %{Content-Disposition: form-data; name="#{name}"}
end

#parse_data(data) ⇒ Object



57
58
59
60
61
62
# File 'lib/apipie/extractor/recorder.rb', line 57

def parse_data(data)
  return nil if data.strip.blank?
  JSON.parse(data)
rescue StandardError
  data
end

#recordObject



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/apipie/extractor/recorder.rb', line 131

def record
  if @controller
    {:controller => @controller,
     :action => @action,
     :verb => @verb,
     :path => @path,
     :params => @params,
     :query => @query,
     :request_data => @request_data,
     :response_data => @response_data,
     :code => @code}
  else
    nil
  end
end

#reformat_array(boundary, attrs, key, lines) ⇒ Object



102
103
104
105
106
107
# File 'lib/apipie/extractor/recorder.rb', line 102

def reformat_array(boundary, attrs, key, lines)
  attrs.each do |item|
    lines << boundary << content_disposition("#{key}[]")
    lines << '' << item
  end
end

#reformat_boolean(boundary, attrs, key, lines) ⇒ Object



97
98
99
100
# File 'lib/apipie/extractor/recorder.rb', line 97

def reformat_boolean(boundary, attrs, key, lines)
  lines << boundary << content_disposition(key)
  lines << '' << attrs.to_s
end

#reformat_data(data) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
# File 'lib/apipie/extractor/recorder.rb', line 119

def reformat_data(data)
  parsed = parse_data(data)
  case parsed
  when nil
    nil
  when String
    parsed
  else
    JSON.pretty_generate().gsub(/: \[\s*\]/,": []").gsub(/\{\s*\}/,"{}")
  end
end

#reformat_hash(boundary, attrs, lines) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/apipie/extractor/recorder.rb', line 85

def reformat_hash(boundary, attrs, lines)
  if head = attrs[:head]
    lines << boundary
    lines.concat(head.split("\r\n"))
    # To avoid large and/or binary file bodies, simply indicate the contents in the output.
    lines << '' << %{... contents of "#{attrs[:name]}" ...}
  else
    # Look for subelements that contain a part.
    attrs.each_value { |v| v.is_a?(Hash) and reformat_hash(boundary, v, lines) }
  end
end

#reformat_multipart_data(form) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/apipie/extractor/recorder.rb', line 64

def reformat_multipart_data(form)
  form.empty? and return ''
  lines = ["Content-Type: multipart/form-data; boundary=#{MULTIPART_BOUNDARY}",'']
  boundary = "--#{MULTIPART_BOUNDARY}"
  form.each do |key, attrs|
    if attrs.is_a?(String) # rubocop:disable Style/CaseLikeIf
      lines << boundary << content_disposition(key) << "Content-Length: #{attrs.size}" << '' << attrs
    elsif attrs.is_a?(Rack::Test::UploadedFile) || attrs.is_a?(ActionDispatch::Http::UploadedFile)
      reformat_uploaded_file(boundary, attrs, key, lines)
    elsif attrs.is_a?(Array)
      reformat_array(boundary, attrs, key, lines)
    elsif attrs.is_a?(TrueClass) || attrs.is_a?(FalseClass)
      reformat_boolean(boundary, attrs, key, lines)
    else
      reformat_hash(boundary, attrs, lines)
    end
  end
  lines << "#{boundary}--"
  lines.join("\n")
end

#reformat_uploaded_file(boundary, file, key, lines) ⇒ Object



109
110
111
112
113
# File 'lib/apipie/extractor/recorder.rb', line 109

def reformat_uploaded_file(boundary, file, key, lines)
  lines << boundary << %{#{content_disposition(key)}; filename="#{file.original_filename}"}
  lines << "Content-Length: #{file.size}" << "Content-Type: #{file.content_type}" << "Content-Transfer-Encoding: binary"
  lines << '' << %{... contents of "#{key}" ...}
end