feat(page): DocsUI::RequestExample + JsonResponse — one request definition, every client tab#31
Merged
Conversation
…y client tab
## Summary
Declare an API request once and render one code tab per configured client;
render a Ruby hash as a pretty JSON response. Kills the per-client heredoc
duplication (curl/CLI/JS/Ruby/Python authored 5× per endpoint) and the
copy-pasted deep_stringify.
- `DocsKit::ApiRequest` (Data) — method/path/url/query/headers/body + derived
#http_method, #body?, #pretty_body_json (deep-stringified), #url_with_query.
- `DocsKit::ApiClient` (Data) — label/lexer/filename/template; ships four
generic-HTTP defaults (curl, javascript, ruby, python) via ApiTemplates.
- Config: `c.api_clients` (ordered, merged OVER defaults — reuse a token to
replace, new token to append), `c.api_base_url` ("https://api.example.com"),
`c.api_auth_header` (nil ⇒ no auth line). All defaulted → backwards compatible.
- `DocsUI::JsonResponse.new(body, filename:)` — Hash → pretty JSON with string
keys; String → passthrough; renders through DocsUI::Code(lexer: :json).
- `DocsUI::RequestExample.new(method:, path:, body:, query:, headers:, clients:)`
— composes DocsUI::Example so the sticky global language preference keeps
working; `clients:` filters/orders the tabs.
- `DocsUI::Example#code` gains an optional `label:` (additive, backwards
compatible) so a client carries its own tab name.
- Dogfooded: an "API-docs kit" section on the components page (Endpoint +
FieldTable + RequestExample + JsonResponse). README "API docs" section.
## Test Coverage
- api_request_spec: derived helpers, deep-stringify (Hash/Array/Symbol), query.
- api_client_spec: value object + filename_for (String/proc) + the four default
templates (body-present vs body-less GET, auth header line).
- configuration_spec: api_clients defaults/order/merge/override, base_url/auth.
- json_response_spec: Hash pretty-print, String passthrough, HTML-escape safety.
- request_example_spec: 4 tabs, curl base_url+method+body, auth injection,
clients: filter/order, custom client tab, body-less GET omits payload lines,
progressive-enhancement markup (no server-side hidden panels).
## Verification
- [x] bundle exec rake (rspec + rubocop) — 232 examples, 0 failures; no offenses
- [x] Configuration still 100% covered; suite 93% line coverage (floor 80%)
- [x] DocsUI::Example behavior preserved; all new config knobs defaulted
Closes #15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #15.
Problem
An endpoint example was authored 5–7 times — curl, CLI, JavaScript, Ruby, Python heredocs (one builder method per client per endpoint), plus a hand-pretty-printed JSON response with
deep_stringifycopy-pasted 3×. A field rename meant editing up to 7 places. The gem'sExamplewas just a tab container; there was no way to define a request once and derive the language tabs.What this ships
One structured request declaration renders every configured client tab; one Ruby hash renders a pretty JSON response block.
Value objects (
lib/docs_kit/)DocsKit::ApiRequest(Data.define) —method/path/url/query/headers/body, plus derived#http_method,#body?,#pretty_body_json(deep-stringified),#url_with_query. This is what templates receive, so each stays one short heredoc.DocsKit::ApiClient(Data.define) —label/lexer/filename(String or proc)/template((request) -> String). Ships four generic-HTTP defaults (curl,javascript,ruby,python) inApiClient::DEFAULTS, backed byDocsKit::ApiTemplates.Config (all defaulted — backwards compatible)
c.api_clients{ token => ApiClient }merged over the defaults — reuse a token to swap in an SDK-flavored snippet, new token to append a tab (e.g.cli). Order stable.c.api_base_url"https://api.example.com"path.c.api_auth_headernilAuthorization:line merged into every snippet;nil⇒ no auth line.Components (
app/components/docs_ui/)DocsUI::JsonResponse.new(body, filename: "response.json")— Hash → deep-stringified +JSON.pretty_generate; String → passthrough; rendersDocsUI::Code(lexer: :json).DocsUI::RequestExample.new(method:, path:, body:, query:, headers:, clients:)— composesDocsUI::Example, one tab per selected client, so the global sticky language preference keeps working.clients:filters/orders the tabs.DocsUI::Example#codegains an optionallabel:(additive — existing behavior unchanged) so a client carries its own tab name.The gem ships generic HTTP templates only — it can't know a site's SDK. app-4's SDK flavors and its
clitab becomec.api_clientsoverrides.Test coverage
api_request_spec— derived helpers; deep-stringify across Hash/Array/Symbol; query encoding; String passthrough.api_client_spec— value object +filename_for(String/proc); the four default templates asserting body-present vs body-less GET, and the auth-header line.configuration_spec—api_clientsdefaults/order/merge/override;api_base_url/api_auth_headerdefaults + overrides.json_response_spec— Hash pretty-print with string keys, String passthrough, HTML-escape safety via the Rouge path.request_example_spec— a POST renders 4 tabs; curl carriesapi_base_url+method+-dbody; auth header injected;clients: [:ruby, :curl]filters and orders; a custom client from config renders its own label; a body-less GET omits payload lines in every default template; Example's progressive-enhancement markup (no server-sidehiddenpanels).Also smoke-tested the exact dogfood composition (RequestExample + JsonResponse in an endpoint Section) renders clean end-to-end.
Verification
bundle exec rake(rspec + rubocop) — 232 examples, 0 failures; no offensesDocsKit::Configurationstill 100% covered; suite 93% line coverage (floor 80%)DocsUI::Examplebehavior preserved (additive optionallabel:), all new config knobs defaultedOut of scope (per the issue)
OpenAPI import (the request struct is the stepping stone a bridge would target later); response generation from live serializers; WebSocket/streaming shapes.