reqcord 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -0
  3. data/Gemfile +10 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +252 -95
  6. data/Rakefile +13 -0
  7. data/docs/configuration.md +319 -0
  8. data/examples/reqcord.yml +58 -0
  9. data/gemfiles/rails_7.1.gemfile +13 -0
  10. data/gemfiles/rails_7.2.gemfile +13 -0
  11. data/gemfiles/rails_8.0.gemfile +13 -0
  12. data/gemfiles/rails_8.1.gemfile +13 -0
  13. data/lib/reqcord/capture/collector.rb +31 -0
  14. data/lib/reqcord/capture/integration_patch.rb +209 -0
  15. data/lib/reqcord/capture/minitest_context.rb +34 -0
  16. data/lib/reqcord/capture/rspec_context.rb +42 -0
  17. data/lib/reqcord/capture/test_context.rb +25 -0
  18. data/lib/reqcord/capture.rb +19 -0
  19. data/lib/reqcord/configuration.rb +198 -0
  20. data/lib/reqcord/dataset.rb +176 -0
  21. data/lib/reqcord/endpoint.rb +263 -0
  22. data/lib/reqcord/errors.rb +9 -0
  23. data/lib/reqcord/exporters/curl.rb +68 -0
  24. data/lib/reqcord/exporters/markdown.rb +295 -0
  25. data/lib/reqcord/exporters/postman.rb +206 -0
  26. data/lib/reqcord/exporters.rb +32 -0
  27. data/lib/reqcord/generator.rb +364 -0
  28. data/lib/reqcord/railtie.rb +51 -0
  29. data/lib/reqcord/renderers/curl.rb +56 -0
  30. data/lib/reqcord/renderers/payload.rb +69 -0
  31. data/lib/reqcord/request_example.rb +104 -0
  32. data/lib/reqcord/response_example.rb +72 -0
  33. data/lib/reqcord/route_collector.rb +242 -0
  34. data/lib/reqcord/sanitizers/sanitizer.rb +140 -0
  35. data/lib/reqcord/schema.rb +187 -0
  36. data/lib/reqcord/support.rb +58 -0
  37. data/lib/reqcord/version.rb +5 -0
  38. data/lib/reqcord.rb +78 -0
  39. data/lib/tasks/reqcord.rake +99 -0
  40. data/reqcord.gemspec +60 -0
  41. metadata +165 -3
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reqcord
4
+ # Describes what an endpoint accepts, inferred from the requests that
5
+ # actually worked. One test sends status "active", another "passive", a third
6
+ # sends "inactive" and gets a 422: the documentation should say the field
7
+ # takes "active" | "passive", not list three requests.
8
+ class Schema
9
+ # Beyond this many distinct values a field is an open set, not a choice.
10
+ ENUM_LIMIT = 6
11
+
12
+ Field = Struct.new(:path, :types, :values, :present_count, :total_count, :repetition, keyword_init: true) do
13
+ def type
14
+ types.to_a.sort.join(" | ")
15
+ end
16
+
17
+ # A field missing from a working request cannot be required.
18
+ def required?
19
+ present_count == total_count
20
+ end
21
+
22
+ # Telling a closed set from free text: either a value came back in more
23
+ # than one request, or every value reads like a token ("active"), not
24
+ # like content ("Ada Lovelace", "ada@example.com").
25
+ def enum?
26
+ return false unless values.size.between?(2, ENUM_LIMIT)
27
+ return false unless values.all? { |value| scalar?(value) }
28
+ return false if identifier?
29
+
30
+ # Repetition only counts among the requests that carried the field:
31
+ # two requests, two different values, is not a set.
32
+ return true if repetition && values.size < present_count
33
+
34
+ values.all? { |value| token?(value) }
35
+ end
36
+
37
+ def example
38
+ values.first
39
+ end
40
+
41
+ # Test order is not stable, so a listed set must be ordered by something
42
+ # that is.
43
+ def listed_values
44
+ values.sort_by { |value| [value.to_s, value.class.name] }
45
+ end
46
+
47
+ def to_h
48
+ {
49
+ path: path,
50
+ type: type,
51
+ required: required?,
52
+ values: listed_values
53
+ }
54
+ end
55
+
56
+ private
57
+
58
+ def scalar?(value)
59
+ value.is_a?(String) || value.is_a?(Numeric) || value == true || value == false
60
+ end
61
+
62
+ # Every record has a different one, so listing the ones a test happened
63
+ # to use would read as if those were the only allowed values.
64
+ def identifier?
65
+ path.to_s.split(/[.\[\]]/).last.to_s.match?(/\A(id|uuid|.*_id|.*_uuid|.*_key|token|slug)\z/i)
66
+ end
67
+
68
+ # A word, not content: "active", "pending_review", "USD". Digits and
69
+ # dashes usually mean a generated value ("e-00056197", "safari-859f04").
70
+ def token?(value)
71
+ case value
72
+ when true, false then true
73
+ when String then value.match?(/\A[a-z][a-z_]*\z/) || value.match?(/\A[A-Z_]{2,}\z/)
74
+ else false
75
+ end
76
+ end
77
+ end
78
+
79
+ # `payloads` are request bodies, query hashes or path parameter hashes from
80
+ # successful captures. With `repetition: true` a value seen in more than
81
+ # one payload marks a closed set — right for what tests *send*, wrong for
82
+ # what an application *returns*, where fixtures repeat by nature; response
83
+ # bodies are inferred with `repetition: false`, so only token-like values
84
+ # ("open", "done") are listed as a set.
85
+ def self.infer(payloads, repetition: true)
86
+ new(payloads, repetition: repetition).call
87
+ end
88
+
89
+ # Empty payloads are kept: a request that was accepted without any
90
+ # parameters is the proof that every parameter is optional.
91
+ def initialize(payloads, repetition: true)
92
+ @payloads = Array(payloads)
93
+ @repetition = repetition
94
+ end
95
+
96
+ def call
97
+ return self.class.empty if @payloads.empty?
98
+
99
+ fields = {}
100
+
101
+ @payloads.each do |payload|
102
+ flatten(payload).each do |path, value|
103
+ field = fields[path] ||= Field.new(
104
+ path: path,
105
+ types: [],
106
+ values: [],
107
+ present_count: 0,
108
+ total_count: @payloads.size,
109
+ repetition: @repetition
110
+ )
111
+
112
+ field.types |= [type_of(value)]
113
+ field.values |= [value] unless value.nil? || value.is_a?(Hash) || value.is_a?(Array)
114
+ field.present_count += 1
115
+ end
116
+ end
117
+
118
+ Result.new(fields.values)
119
+ end
120
+
121
+ def self.empty
122
+ Result.new([])
123
+ end
124
+
125
+ # The inferred description of one payload shape.
126
+ class Result
127
+ include Enumerable
128
+
129
+ attr_reader :fields
130
+
131
+ def initialize(fields)
132
+ @fields = fields
133
+ end
134
+
135
+ def each(&block)
136
+ fields.each(&block)
137
+ end
138
+
139
+ def empty?
140
+ fields.empty?
141
+ end
142
+
143
+ def size
144
+ fields.size
145
+ end
146
+
147
+ def [](path)
148
+ fields.find { |field| field.path == path }
149
+ end
150
+
151
+ def to_a
152
+ fields.map(&:to_h)
153
+ end
154
+ end
155
+
156
+ private
157
+
158
+ # { "customer" => { "tags" => ["a"] } } -> { "customer.tags[]" => "a" }
159
+ def flatten(value, prefix = nil, result = {})
160
+ case value
161
+ when Hash
162
+ value.each { |key, nested| flatten(nested, prefix ? "#{prefix}.#{key}" : key.to_s, result) }
163
+ when Array
164
+ # An empty array still tells the reader the field is a list.
165
+ result[prefix] = [] if value.empty? && prefix
166
+ value.each { |item| flatten(item, "#{prefix}[]", result) }
167
+ else
168
+ result[prefix] = value if prefix
169
+ end
170
+
171
+ result
172
+ end
173
+
174
+ def type_of(value)
175
+ case value
176
+ when String then "string"
177
+ when Integer then "integer"
178
+ when Float then "number"
179
+ when true, false then "boolean"
180
+ when nil then "null"
181
+ when Array then "array"
182
+ when Hash then "object"
183
+ else value.class.name.downcase
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reqcord
4
+ # Minimal inflection helpers. ActiveSupport is used when it is available so
5
+ # that generated titles match the host application's inflections.
6
+ module Support
7
+ module_function
8
+
9
+ def underscore(value)
10
+ string = value.to_s.dup
11
+ return ActiveSupport::Inflector.underscore(string) if defined?(ActiveSupport::Inflector)
12
+
13
+ string.gsub("::", "/")
14
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
15
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
16
+ .tr("-", "_")
17
+ .downcase
18
+ end
19
+
20
+ def humanize(value)
21
+ underscore(value).tr("_", " ").strip
22
+ end
23
+
24
+ def titleize(value)
25
+ humanize(value).split(/\s+/).map { |word| word.empty? ? word : word[0].upcase + word[1..] }.join(" ")
26
+ end
27
+
28
+ def singularize(value)
29
+ string = value.to_s
30
+ return ActiveSupport::Inflector.singularize(string) if defined?(ActiveSupport::Inflector)
31
+
32
+ case string
33
+ when /ies\z/i then string.sub(/ies\z/i, "y")
34
+ when /(ss|sh|ch|x|z)es\z/i then string.sub(/es\z/i, "")
35
+ when /ss\z/i then string
36
+ when /s\z/i then string.sub(/s\z/i, "")
37
+ else string
38
+ end
39
+ end
40
+
41
+ def pluralize(value)
42
+ string = value.to_s
43
+ return ActiveSupport::Inflector.pluralize(string) if defined?(ActiveSupport::Inflector)
44
+
45
+ case string
46
+ when /(ss|sh|ch|x|z)\z/i then "#{string}es"
47
+ when /[^aeiou]y\z/i then string.sub(/y\z/i, "ies")
48
+ # Already plural, as resource names usually are.
49
+ when /s\z/i then string
50
+ else "#{string}s"
51
+ end
52
+ end
53
+
54
+ def parameterize(value)
55
+ underscore(value).gsub(/[^a-z0-9]+/, "-").gsub(/\A-+|-+\z/, "")
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reqcord
4
+ VERSION = "0.1.2"
5
+ end
data/lib/reqcord.rb ADDED
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "yaml"
5
+ require "time"
6
+ require "uri"
7
+ require "rack/utils"
8
+ require "fileutils"
9
+ require "pathname"
10
+
11
+ require_relative "reqcord/version"
12
+ require_relative "reqcord/errors"
13
+ require_relative "reqcord/support"
14
+ require_relative "reqcord/configuration"
15
+
16
+ require_relative "reqcord/request_example"
17
+ require_relative "reqcord/response_example"
18
+ require_relative "reqcord/schema"
19
+ require_relative "reqcord/endpoint"
20
+ require_relative "reqcord/dataset"
21
+
22
+ require_relative "reqcord/route_collector"
23
+
24
+ require_relative "reqcord/capture"
25
+ require_relative "reqcord/capture/test_context"
26
+ require_relative "reqcord/capture/collector"
27
+
28
+ require_relative "reqcord/sanitizers/sanitizer"
29
+ require_relative "reqcord/renderers/payload"
30
+ require_relative "reqcord/renderers/curl"
31
+ require_relative "reqcord/exporters"
32
+ require_relative "reqcord/exporters/curl"
33
+ require_relative "reqcord/exporters/markdown"
34
+ require_relative "reqcord/exporters/postman"
35
+ require_relative "reqcord/generator"
36
+
37
+ require_relative "reqcord/railtie" if defined?(Rails::Railtie)
38
+
39
+ module Reqcord
40
+ class << self
41
+ attr_writer :root
42
+
43
+ def root
44
+ return @root if @root
45
+
46
+ if defined?(Rails) &&
47
+ Rails.respond_to?(:root) &&
48
+ Rails.root
49
+
50
+ Rails.root
51
+ else
52
+ Pathname(Dir.pwd)
53
+ end
54
+ end
55
+
56
+ def configuration
57
+ @configuration ||=
58
+ Configuration.load(
59
+ root: root
60
+ )
61
+ end
62
+
63
+ def reload_configuration!
64
+ @configuration =
65
+ Configuration.load(
66
+ root: root
67
+ )
68
+ end
69
+
70
+ def log(message)
71
+ $stdout.puts("[reqcord] #{message}")
72
+ end
73
+
74
+ def warn(message)
75
+ $stderr.puts("[reqcord] #{message}")
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :reqcord do
4
+ desc "Create reqcord.yml"
5
+ task init: :environment do
6
+ path = Rails.root.join("reqcord.yml")
7
+
8
+ if path.exist?
9
+ puts "reqcord.yml already exists."
10
+ next
11
+ end
12
+
13
+ content = <<~YAML
14
+ version: 1
15
+
16
+ test:
17
+ framework: minitest
18
+ command: bin/rails test
19
+
20
+ routes:
21
+ prefix: /api
22
+
23
+ output:
24
+ directory: docs/api
25
+ include_uncovered: false
26
+
27
+ exporters:
28
+ - curl
29
+ - markdown
30
+ - postman
31
+
32
+ variables:
33
+ base_url: http://localhost:3000
34
+
35
+ sanitize:
36
+ headers:
37
+ Authorization: "Bearer {{token}}"
38
+ X-Api-Key: "{{api_key}}"
39
+ YAML
40
+
41
+ File.write(path, content)
42
+
43
+ FileUtils.mkdir_p(
44
+ Rails.root.join("docs", "api")
45
+ )
46
+
47
+ puts "Created reqcord.yml"
48
+ puts "Created docs/api/"
49
+ end
50
+
51
+ desc "Generate API documentation from Rails integration tests"
52
+ task generate: :environment do
53
+ resources =
54
+ ENV.fetch("RESOURCE", "")
55
+ .split(",")
56
+ .map(&:strip)
57
+ .reject(&:empty?)
58
+
59
+ version = ENV["VERSION"]&.strip
60
+
61
+ version = nil if version&.empty?
62
+
63
+ Reqcord.reload_configuration!
64
+
65
+ dataset =
66
+ Reqcord::Generator.call(
67
+ resources: resources,
68
+ version: version
69
+ )
70
+
71
+ documented = dataset.curl_ready_endpoints.size
72
+
73
+ puts
74
+ puts "Reqcord generated API documentation."
75
+ puts "Endpoints: #{dataset.endpoints.size} (#{documented} with successful 2xx request)"
76
+ puts "Output: #{Reqcord.configuration.output_directory}"
77
+ end
78
+
79
+ desc "List the routes Reqcord would document"
80
+ task routes: :environment do
81
+ routes =
82
+ Reqcord::RouteCollector.call(
83
+ resources: ENV.fetch("RESOURCE", "").split(",").map(&:strip).reject(&:empty?),
84
+ version: ENV["VERSION"],
85
+ prefix: Reqcord.configuration.route_prefix
86
+ )
87
+
88
+ if routes.empty?
89
+ puts "No routes matched the configured prefix."
90
+ next
91
+ end
92
+
93
+ width = routes.map { |route| route.method.length }.max
94
+
95
+ routes.each do |route|
96
+ puts format("%-#{width}s %s (%s#%s)", route.method, route.path, route.controller, route.action)
97
+ end
98
+ end
99
+ end
data/reqcord.gemspec ADDED
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/reqcord/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "reqcord"
7
+ spec.version = Reqcord::VERSION
8
+
9
+ spec.authors = ["Ahmet Saridogan"]
10
+
11
+ spec.summary = "Generate living API documentation from Rails integration tests"
12
+ spec.description = <<~DESC
13
+ Reqcord captures HTTP requests and responses from Rails integration tests
14
+ and generates static API documentation with executable cURL examples.
15
+ DESC
16
+
17
+ spec.homepage = "https://github.com/ahmetsaridogan/reqcord"
18
+ spec.license = "MIT"
19
+
20
+ spec.required_ruby_version = ">= 3.2"
21
+
22
+ spec.metadata["homepage_uri"] = spec.homepage
23
+ spec.metadata["changelog_uri"] = spec.homepage
24
+ spec.metadata["source_code_uri"] = spec.homepage
25
+ spec.metadata["rubygems_mfa_required"] = "true"
26
+
27
+ # What ships: lib/, the rake task, the docs and the annotated config. Tests,
28
+ # CI files and the example apps (with their generated output) stay on
29
+ # GitHub — `git ls-files` runs against the checkout, so `gem build` must be
30
+ # run from the repository root.
31
+ spec.files = Dir.chdir(__dir__) do
32
+ `git ls-files -z`.split("\x0").reject do |file|
33
+ file.start_with?(
34
+ "test/",
35
+ "spec/",
36
+ "features/",
37
+ "examples/",
38
+ ".git",
39
+ ".github/"
40
+ ) || file.end_with?(".gem")
41
+ end + ["examples/reqcord.yml"]
42
+ end
43
+
44
+ spec.require_paths = ["lib"]
45
+
46
+ # Rails 7.1 through 8.x; every version is exercised in CI (see gemfiles/).
47
+ spec.add_dependency "railties", ">= 7.1", "< 9.0"
48
+ spec.add_dependency "actionpack", ">= 7.1", "< 9.0"
49
+
50
+ spec.add_development_dependency "minitest"
51
+ spec.add_development_dependency "rake"
52
+
53
+ # The test suite serves the dummy app over HTTP to replay the generated
54
+ # cURL and Postman collection, validates the collection against the
55
+ # Postman schema, and runs the RSpec example apps.
56
+ spec.add_development_dependency "puma"
57
+ spec.add_development_dependency "json-schema"
58
+ spec.add_development_dependency "rspec-core"
59
+ spec.add_development_dependency "rspec-expectations"
60
+ end
metadata CHANGED
@@ -1,14 +1,138 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reqcord
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ahmet Saridogan
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: railties
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.1'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '7.1'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9.0'
32
+ - !ruby/object:Gem::Dependency
33
+ name: actionpack
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '7.1'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '9.0'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '7.1'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '9.0'
52
+ - !ruby/object:Gem::Dependency
53
+ name: minitest
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ type: :development
60
+ prerelease: false
61
+ version_requirements: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ - !ruby/object:Gem::Dependency
67
+ name: rake
68
+ requirement: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ type: :development
74
+ prerelease: false
75
+ version_requirements: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ - !ruby/object:Gem::Dependency
81
+ name: puma
82
+ requirement: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :development
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: json-schema
96
+ requirement: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ type: :development
102
+ prerelease: false
103
+ version_requirements: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ - !ruby/object:Gem::Dependency
109
+ name: rspec-core
110
+ requirement: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ type: :development
116
+ prerelease: false
117
+ version_requirements: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ - !ruby/object:Gem::Dependency
123
+ name: rspec-expectations
124
+ requirement: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ type: :development
130
+ prerelease: false
131
+ version_requirements: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
12
136
  description: |
13
137
  Reqcord captures HTTP requests and responses from Rails integration tests
14
138
  and generates static API documentation with executable cURL examples.
@@ -16,14 +140,52 @@ executables: []
16
140
  extensions: []
17
141
  extra_rdoc_files: []
18
142
  files:
143
+ - CHANGELOG.md
144
+ - Gemfile
145
+ - LICENSE.txt
19
146
  - README.md
147
+ - Rakefile
148
+ - docs/configuration.md
149
+ - examples/reqcord.yml
150
+ - gemfiles/rails_7.1.gemfile
151
+ - gemfiles/rails_7.2.gemfile
152
+ - gemfiles/rails_8.0.gemfile
153
+ - gemfiles/rails_8.1.gemfile
154
+ - lib/reqcord.rb
155
+ - lib/reqcord/capture.rb
156
+ - lib/reqcord/capture/collector.rb
157
+ - lib/reqcord/capture/integration_patch.rb
158
+ - lib/reqcord/capture/minitest_context.rb
159
+ - lib/reqcord/capture/rspec_context.rb
160
+ - lib/reqcord/capture/test_context.rb
161
+ - lib/reqcord/configuration.rb
162
+ - lib/reqcord/dataset.rb
163
+ - lib/reqcord/endpoint.rb
164
+ - lib/reqcord/errors.rb
165
+ - lib/reqcord/exporters.rb
166
+ - lib/reqcord/exporters/curl.rb
167
+ - lib/reqcord/exporters/markdown.rb
168
+ - lib/reqcord/exporters/postman.rb
169
+ - lib/reqcord/generator.rb
170
+ - lib/reqcord/railtie.rb
171
+ - lib/reqcord/renderers/curl.rb
172
+ - lib/reqcord/renderers/payload.rb
173
+ - lib/reqcord/request_example.rb
174
+ - lib/reqcord/response_example.rb
175
+ - lib/reqcord/route_collector.rb
176
+ - lib/reqcord/sanitizers/sanitizer.rb
177
+ - lib/reqcord/schema.rb
178
+ - lib/reqcord/support.rb
179
+ - lib/reqcord/version.rb
180
+ - lib/tasks/reqcord.rake
181
+ - reqcord.gemspec
20
182
  homepage: https://github.com/ahmetsaridogan/reqcord
21
183
  licenses:
22
184
  - MIT
23
185
  metadata:
24
186
  homepage_uri: https://github.com/ahmetsaridogan/reqcord
187
+ changelog_uri: https://github.com/ahmetsaridogan/reqcord
25
188
  source_code_uri: https://github.com/ahmetsaridogan/reqcord
26
- changelog_uri: https://github.com/ahmetsaridogan/reqcord/blob/main/CHANGELOG.md
27
189
  rubygems_mfa_required: 'true'
28
190
  rdoc_options: []
29
191
  require_paths: