Class: RailsApiDocs::Doc::Renderer
- Inherits:
-
Object
- Object
- RailsApiDocs::Doc::Renderer
- Defined in:
- lib/rails-api-docs/doc/renderer.rb
Overview
Turns the parsed YAML config into the final self-contained HTML document. Used by both the rake task (writes to public/api-docs.html) and the dev controller mounted at /rails/api-docs.
The output is a single HTML string with CSS and JS inlined — no external assets, no asset pipeline required.
Constant Summary collapse
- TEMPLATE_PATH =
File.("../templates/api_docs.html.erb", __dir__)
- DEFAULT_GENERAL =
{ "title" => "API Documentation", "base_url" => "https://api.example.com", "primary_color" => "#CC0000", "secondary_color" => "#2E2E2E", "accent_color" => "#D30001", "font_family" => "system-ui, -apple-system, sans-serif", "show_curl" => true, "show_examples" => true }.freeze
Instance Method Summary collapse
- #auth_label(auth) ⇒ Object
- #body_present?(endpoint) ⇒ Boolean
- #call ⇒ Object
- #curl_for(endpoint) ⇒ Object
- #empty? ⇒ Boolean
- #endpoint_id(section_key, endpoint) ⇒ Object
- #first_endpoint_id ⇒ Object
-
#general ⇒ Object
template helpers — public so ERB binding can reach them =====.
- #h(value) ⇒ Object
- #headers_present?(endpoint) ⇒ Boolean
-
#initialize(config) ⇒ Renderer
constructor
A new instance of Renderer.
- #params_present?(endpoint) ⇒ Boolean
-
#render_field(field) ⇒ Object
Renders a single field row with all supported attributes.
-
#render_fields(fields) ⇒ Object
Helper: render an array of fields as joined HTML.
-
#responses_for(endpoint) ⇒ Object
Returns an array of { status:, description:, example:, headers:, schema: } always in YAML insertion order.
- #responses_have_details?(endpoint) ⇒ Boolean
- #section_slug(key) ⇒ Object
-
#sidebar_tags_attr(endpoint) ⇒ Object
Returns the ‘data-endpoint-tags=’‘` attribute (with leading space) for a sidebar <li>, or an empty string when the endpoint has no tags.
- #status_class(code) ⇒ Object
- #verb_class(method) ⇒ Object
- #verb_label(method) ⇒ Object
- #visible_sections ⇒ Object
Constructor Details
#initialize(config) ⇒ Renderer
Returns a new instance of Renderer.
28 29 30 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 28 def initialize(config) @config = config || {} end |
Instance Method Details
#auth_label(auth) ⇒ Object
145 146 147 148 149 150 151 152 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 145 def auth_label(auth) case auth.to_s.downcase when "bearer" then "Bearer auth" when "basic" then "Basic auth" when "none" then "No auth" else auth.to_s end end |
#body_present?(endpoint) ⇒ Boolean
117 118 119 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 117 def body_present?(endpoint) endpoint["body"].is_a?(Array) && !endpoint["body"].empty? end |
#call ⇒ Object
32 33 34 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 32 def call ERB.new(File.read(TEMPLATE_PATH), trim_mode: "-").result(binding) end |
#curl_for(endpoint) ⇒ Object
86 87 88 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 86 def curl_for(endpoint) CurlRenderer.new(endpoint, base_url: general["base_url"]).call end |
#empty? ⇒ Boolean
53 54 55 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 53 def empty? visible_sections.empty? end |
#endpoint_id(section_key, endpoint) ⇒ Object
61 62 63 64 65 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 61 def endpoint_id(section_key, endpoint) method = endpoint["method"].to_s.downcase path = endpoint["path"].to_s.gsub(/[^a-z0-9]+/i, "-").gsub(/^-+|-+$/, "") "#{section_slug(section_key)}--#{method}--#{path}" end |
#first_endpoint_id ⇒ Object
79 80 81 82 83 84 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 79 def first_endpoint_id visible_sections.each do |key, section| return endpoint_id(key, section["endpoints"].first) end nil end |
#general ⇒ Object
template helpers — public so ERB binding can reach them =====
38 39 40 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 38 def general @general ||= DEFAULT_GENERAL.merge(@config["general_configurations"] || {}) end |
#h(value) ⇒ Object
57 58 59 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 57 def h(value) ERB::Util.html_escape(value.to_s) end |
#headers_present?(endpoint) ⇒ Boolean
121 122 123 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 121 def headers_present?(endpoint) endpoint["headers"].is_a?(Array) && !endpoint["headers"].empty? end |
#params_present?(endpoint) ⇒ Boolean
125 126 127 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 125 def params_present?(endpoint) endpoint["params"].is_a?(Array) && !endpoint["params"].empty? end |
#render_field(field) ⇒ Object
Renders a single field row with all supported attributes. Used uniformly by body, params, headers, and response schema — one source of truth for badge rendering.
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 157 def render_field(field) parts = [] parts << %(<div class="field">) parts << %(<div class="field-row">) parts << %(<span class="field-name">#{h(field["name"])}</span>) parts << %(<span class="field-type">#{h(field["type"])}</span>) if field["type"] parts << %(<span class="field-badge format">#{h(field["format"])}</span>) if field["format"] parts << %(<span class="field-badge in-path">#{h(field["in"])}</span>) if field["in"] parts << %(<span class="field-badge readonly">read-only</span>) if field["read_only"] parts << %(<span class="field-badge writeonly">write-only</span>) if field["write_only"] parts << %(<span class="field-badge nullable">nullable</span>) if field["nullable"] parts << %(<span class="field-badge required">Required</span>) if field["required"] min = field["min"] || field["min_length"] max = field["max"] || field["max_length"] parts << %(<span class="field-meta">min: #{h(min)}</span>) if min parts << %(<span class="field-meta">max: #{h(max)}</span>) if max parts << %(<span class="field-meta">default: #{h(field["default"])}</span>) unless field["default"].nil? parts << %(<span class="field-meta mono">pattern: #{h(field["pattern"])}</span>) if field["pattern"] parts << %(</div>) if field["enum"].is_a?(Array) && !field["enum"].empty? values = field["enum"].map { |v| %(<code>#{h(v)}</code>) }.join(" · ") parts << %(<div class="field-enum">one of: #{values}</div>) end parts << %(<div class="field-desc">#{h(field["description"])}</div>) if field["description"] && !field["description"].to_s.empty? parts << %(<div class="field-example">Example: <code>#{h(field["example"])}</code></div>) unless field["example"].nil? parts << %(</div>) parts.join("\n") end |
#render_fields(fields) ⇒ Object
Helper: render an array of fields as joined HTML. Use from the template like ‘<%= render_fields(endpoint) -%>`.
192 193 194 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 192 def render_fields(fields) Array(fields).map { |f| render_field(f) }.join("\n") end |
#responses_for(endpoint) ⇒ Object
Returns an array of { status:, description:, example:, headers:, schema: } always in YAML insertion order. If the user defined ‘responses:` in the YAML we honor it verbatim; otherwise we synthesize a single “200” entry from `response_example` or the inferred body sample.
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 94 def responses_for(endpoint) if endpoint["responses"].is_a?(Hash) && !endpoint["responses"].empty? endpoint["responses"].map do |status, resp| resp ||= {} { "status" => status.to_s, "description" => resp["description"].to_s, "example" => resp["example"].to_s, "headers" => Array(resp["headers"]), "schema" => Array(resp["schema"]) } end else [{ "status" => "200", "description" => "", "example" => default_response_example(endpoint), "headers" => [], "schema" => [] }] end end |
#responses_have_details?(endpoint) ⇒ Boolean
129 130 131 132 133 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 129 def responses_have_details?(endpoint) responses_for(endpoint).any? do |r| !r["headers"].empty? || !r["schema"].empty? || !r["description"].empty? end end |
#section_slug(key) ⇒ Object
67 68 69 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 67 def section_slug(key) key.to_s.gsub(/[^a-z0-9]+/i, "-") end |
#sidebar_tags_attr(endpoint) ⇒ Object
Returns the ‘data-endpoint-tags=’‘` attribute (with leading space) for a sidebar <li>, or an empty string when the endpoint has no tags. JSON encoding is intentional — robust against tag values that contain spaces or special characters.
139 140 141 142 143 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 139 def (endpoint) = Array(endpoint["tags"]) return "" if .empty? %( data-endpoint-tags='#{h(.to_json)}') end |
#status_class(code) ⇒ Object
196 197 198 199 200 201 202 203 204 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 196 def status_class(code) case code.to_s[0] when "2" then "status-2xx" when "3" then "status-3xx" when "4" then "status-4xx" when "5" then "status-5xx" else "" end end |
#verb_class(method) ⇒ Object
71 72 73 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 71 def verb_class(method) "verb-#{method.to_s.downcase}" end |
#verb_label(method) ⇒ Object
75 76 77 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 75 def verb_label(method) method.to_s.upcase == "DELETE" ? "DEL" : method.to_s.upcase end |
#visible_sections ⇒ Object
42 43 44 45 46 47 48 49 50 51 |
# File 'lib/rails-api-docs/doc/renderer.rb', line 42 def visible_sections @visible_sections ||= (@config["sections"] || {}).each_with_object({}) do |(key, section), acc| next if section["show"] == false endpoints = (section["endpoints"] || []).reject { |e| e["show"] == false } next if endpoints.empty? acc[key] = section.merge("endpoints" => endpoints) end end |