reqcord 0.1.4 → 0.3.0

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.
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/mime"
4
+
5
+ module Reqcord
6
+ # Serves the generated documentation from inside the application, the way
7
+ # Sidekiq::Web does:
8
+ #
9
+ # # config/routes.rb
10
+ # mount Reqcord::Web => "/api-docs" if Rails.env.development?
11
+ #
12
+ # `/api-docs` renders the OpenAPI document with Scalar; every other path is
13
+ # a file from the output directory (`dataset.json`, `postman/collection.json`,
14
+ # the Markdown pages, the cURL scripts). Nothing is generated on request —
15
+ # run `bin/rails reqcord:generate` first.
16
+ class Web
17
+ SCALAR_SCRIPT = "https://cdn.jsdelivr.net/npm/@scalar/api-reference"
18
+ OPENAPI_PATH = "openapi/openapi.json"
19
+
20
+ class << self
21
+ # `mount Reqcord::Web => "/api-docs"` calls the class itself.
22
+ def call(env)
23
+ app.call(env)
24
+ end
25
+
26
+ def app
27
+ @app ||= new
28
+ end
29
+ end
30
+
31
+ def initialize(root: nil)
32
+ @root = root
33
+ end
34
+
35
+ def call(env)
36
+ path = Rack::Utils.unescape_path(env["PATH_INFO"].to_s)
37
+
38
+ return index(env) if path.empty? || path == "/"
39
+
40
+ file = resolve(path)
41
+
42
+ return not_found unless file
43
+
44
+ [200, { "content-type" => content_type(file) }, [File.binread(file)]]
45
+ end
46
+
47
+ private
48
+
49
+ def root
50
+ Pathname(@root || Reqcord.configuration.output_directory).expand_path
51
+ end
52
+
53
+ def index(env)
54
+ html = File.exist?(root.join(OPENAPI_PATH)) ? scalar_page(env) : empty_page(env)
55
+
56
+ [200, { "content-type" => "text/html; charset=utf-8" }, [html]]
57
+ end
58
+
59
+ # The path traversal guard: whatever the request says, the file must be
60
+ # inside the output directory.
61
+ def resolve(path)
62
+ candidate = root.join(path.delete_prefix("/")).expand_path
63
+
64
+ return nil unless candidate.to_s.start_with?("#{root}/")
65
+ return nil unless candidate.file?
66
+
67
+ candidate.to_s
68
+ end
69
+
70
+ def content_type(file)
71
+ case File.extname(file)
72
+ when ".md" then "text/markdown; charset=utf-8"
73
+ when ".sh" then "text/plain; charset=utf-8"
74
+ else Rack::Mime.mime_type(File.extname(file), "application/octet-stream")
75
+ end
76
+ end
77
+
78
+ def not_found
79
+ [404, { "content-type" => "text/plain" }, ["Not Found"]]
80
+ end
81
+
82
+ # Absolute links: the page is served at the mount point itself, so a
83
+ # relative `openapi/…` would resolve one level too high.
84
+ def base(env)
85
+ env["SCRIPT_NAME"].to_s.chomp("/")
86
+ end
87
+
88
+ def scalar_page(env)
89
+ <<~HTML
90
+ <!doctype html>
91
+ <html>
92
+ <head>
93
+ <meta charset="utf-8">
94
+ <meta name="viewport" content="width=device-width, initial-scale=1">
95
+ <title>API Reference</title>
96
+ </head>
97
+ <body>
98
+ <script id="api-reference" data-url="#{base(env)}/#{OPENAPI_PATH}"></script>
99
+ <script src="#{SCALAR_SCRIPT}"></script>
100
+ </body>
101
+ </html>
102
+ HTML
103
+ end
104
+
105
+ def empty_page(env)
106
+ <<~HTML
107
+ <!doctype html>
108
+ <html>
109
+ <head><meta charset="utf-8"><title>Reqcord</title></head>
110
+ <body style="font-family: system-ui, sans-serif; max-width: 40rem; margin: 4rem auto; line-height: 1.5">
111
+ <h1>No documentation generated yet</h1>
112
+ <p>Reqcord serves the files under <code>#{root}</code>. Generate them from your test suite:</p>
113
+ <pre>bin/rails reqcord:generate</pre>
114
+ <p>Then reload this page: the OpenAPI document is rendered here with Scalar, and
115
+ <a href="#{base(env)}/dataset.json">dataset.json</a>,
116
+ <a href="#{base(env)}/postman/collection.json">postman/collection.json</a> and the Markdown pages are served alongside it.</p>
117
+ </body>
118
+ </html>
119
+ HTML
120
+ end
121
+ end
122
+ end
data/lib/reqcord.rb CHANGED
@@ -11,6 +11,7 @@ require "pathname"
11
11
  require_relative "reqcord/version"
12
12
  require_relative "reqcord/errors"
13
13
  require_relative "reqcord/support"
14
+ require_relative "reqcord/file_value"
14
15
  require_relative "reqcord/configuration"
15
16
 
16
17
  require_relative "reqcord/request_example"
@@ -32,7 +33,10 @@ require_relative "reqcord/exporters"
32
33
  require_relative "reqcord/exporters/curl"
33
34
  require_relative "reqcord/exporters/markdown"
34
35
  require_relative "reqcord/exporters/postman"
36
+ require_relative "reqcord/exporters/openapi"
35
37
  require_relative "reqcord/generator"
38
+ require_relative "reqcord/check"
39
+ require_relative "reqcord/web"
36
40
 
37
41
  require_relative "reqcord/railtie" if defined?(Rails::Railtie)
38
42
 
@@ -45,11 +45,13 @@ namespace :reqcord do
45
45
  include_uncovered: false
46
46
 
47
47
  # markdown: pages under docs/api, curl: one runnable .sh per endpoint,
48
- # postman: postman/collection.json (import into Postman or Hoppscotch).
48
+ # postman: postman/collection.json (import into Postman or Hoppscotch),
49
+ # openapi: openapi/openapi.json (rendered with Scalar by Reqcord::Web).
49
50
  exporters:
50
51
  - curl
51
52
  - markdown
52
53
  - postman
54
+ - openapi
53
55
 
54
56
  variables:
55
57
  # Host of every generated cURL and the Postman `base_url` variable.
@@ -107,6 +109,29 @@ namespace :reqcord do
107
109
  puts "Output: #{Reqcord.configuration.output_directory}"
108
110
  end
109
111
 
112
+ desc "Fail when the committed documentation is behind the tests"
113
+ task check: :environment do
114
+ Reqcord.reload_configuration!
115
+
116
+ result =
117
+ Reqcord::Check.call(
118
+ resources: ENV.fetch("RESOURCE", "").split(",").map(&:strip).reject(&:empty?),
119
+ version: ENV["VERSION"].to_s.strip.then { |value| value.empty? ? nil : value }
120
+ )
121
+
122
+ output = Reqcord.configuration.output_directory
123
+
124
+ if result.clean?
125
+ puts "Reqcord: #{output} is up to date."
126
+ else
127
+ puts "Reqcord: #{output} is out of date."
128
+ puts
129
+ result.lines.each { |line| puts " #{line}" }
130
+ puts
131
+ abort "Run `bin/rails reqcord:generate` and commit the result."
132
+ end
133
+ end
134
+
110
135
  desc "List the routes Reqcord would document"
111
136
  task routes: :environment do
112
137
  routes =
data/reqcord.gemspec CHANGED
@@ -20,7 +20,8 @@ Gem::Specification.new do |spec|
20
20
  spec.required_ruby_version = ">= 3.2"
21
21
 
22
22
  spec.metadata["homepage_uri"] = spec.homepage
23
- spec.metadata["changelog_uri"] = spec.homepage
23
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
24
+ spec.metadata["documentation_uri"] = "#{spec.homepage}/tree/master/docs"
24
25
  spec.metadata["source_code_uri"] = spec.homepage
25
26
  spec.metadata["rubygems_mfa_required"] = "true"
26
27
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reqcord
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ahmet Saridogan
@@ -145,7 +145,14 @@ files:
145
145
  - LICENSE.txt
146
146
  - README.md
147
147
  - Rakefile
148
+ - docs/architecture.md
149
+ - docs/capture.md
148
150
  - docs/configuration.md
151
+ - docs/exporters.md
152
+ - docs/getting-started.md
153
+ - docs/route-coverage.md
154
+ - docs/troubleshooting.md
155
+ - docs/web.md
149
156
  - examples/reqcord.yml
150
157
  - gemfiles/rails_7.1.gemfile
151
158
  - gemfiles/rails_7.2.gemfile
@@ -158,6 +165,7 @@ files:
158
165
  - lib/reqcord/capture/minitest_context.rb
159
166
  - lib/reqcord/capture/rspec_context.rb
160
167
  - lib/reqcord/capture/test_context.rb
168
+ - lib/reqcord/check.rb
161
169
  - lib/reqcord/configuration.rb
162
170
  - lib/reqcord/dataset.rb
163
171
  - lib/reqcord/endpoint.rb
@@ -165,7 +173,9 @@ files:
165
173
  - lib/reqcord/exporters.rb
166
174
  - lib/reqcord/exporters/curl.rb
167
175
  - lib/reqcord/exporters/markdown.rb
176
+ - lib/reqcord/exporters/openapi.rb
168
177
  - lib/reqcord/exporters/postman.rb
178
+ - lib/reqcord/file_value.rb
169
179
  - lib/reqcord/generator.rb
170
180
  - lib/reqcord/railtie.rb
171
181
  - lib/reqcord/renderers/curl.rb
@@ -177,6 +187,7 @@ files:
177
187
  - lib/reqcord/schema.rb
178
188
  - lib/reqcord/support.rb
179
189
  - lib/reqcord/version.rb
190
+ - lib/reqcord/web.rb
180
191
  - lib/tasks/reqcord.rake
181
192
  - reqcord.gemspec
182
193
  homepage: https://github.com/ahmetsaridogan/reqcord
@@ -184,7 +195,8 @@ licenses:
184
195
  - MIT
185
196
  metadata:
186
197
  homepage_uri: https://github.com/ahmetsaridogan/reqcord
187
- changelog_uri: https://github.com/ahmetsaridogan/reqcord
198
+ changelog_uri: https://github.com/ahmetsaridogan/reqcord/blob/master/CHANGELOG.md
199
+ documentation_uri: https://github.com/ahmetsaridogan/reqcord/tree/master/docs
188
200
  source_code_uri: https://github.com/ahmetsaridogan/reqcord
189
201
  rubygems_mfa_required: 'true'
190
202
  rdoc_options: []