Repository navigation
Add :md and :markdown formatters that render an object's to_markdown - #3025
Merged
Merged
Conversation
ericproulx
force-pushed
the
feat/markdown-formatter
branch
from
October 10, 2026 22:33
3c7100d to
860f03e
Compare
Danger ReportNo issues found. |
ericproulx
marked this pull request as ready for review
October 10, 2026 22:38
dblock
approved these changes
Oct 12, 2026
ericproulx
force-pushed
the
feat/markdown-formatter
branch
from
October 16, 2026 19:00
860f03e to
93401e3
Compare
ericproulx
force-pushed
the
feat/markdown-formatter
branch
6 times, most recently
from
October 17, 2026 20:42
044b039 to
014f61c
Compare
Member
|
Could we drop the class Markdown < Base
def self.call(object, _env)
return object.to_markdown if object.respond_to?(:to_markdown)
return object if object.is_a?(String)
raise Grape::Exceptions::InvalidFormatter.new(
object.class, name.demodulize.underscore
)
end
end
class Md < Markdown
endBoth classes register automatically and report their own format name in errors. The name derivation only happens on the error path. — AI posting on behalf of @dblock |
Rails 8.1 registers text/markdown as Mime[:md] and adds `render markdown: obj`, which calls obj.to_markdown. Grape::Formatter::Markdown is the same contract for Grape: an object answering to_markdown is rendered with it and a String is returned as is. Anything else raises Grape::Exceptions::InvalidFormatter, so a client that negotiated Markdown for a Hash body gets the default format instead of its #to_s. .md and .markdown are both common Markdown extensions, so :md and :markdown name the same format. Declaring either content type answers both extensions and both ?format= spellings: the Formatter middleware resolves the other name through ContentTypes::FORMAT_SYNONYMS to the one the API declared, so a formatter, parser or error formatter declared for that name applies too. The lookup runs only when the requested format is not declared, so the common path is unchanged. Grape::Formatter::Md, an empty subclass of Markdown, registers the same rendering under :md. Each class names its own format in the InvalidFormatter message, derived from the class name the way the registry derives its key. Neither is a default content type; an API opts in with `content_type :md, 'text/markdown; charset=utf-8'`. Errors keep the text error formatter, and a text/markdown request body passes through unparsed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ericproulx
force-pushed
the
feat/markdown-formatter
branch
from
October 19, 2026 11:52
014f61c to
00f9af8
Compare
Contributor
Author
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.
Rails 8.1 registers
text/markdownasMime[:md]and addsrender markdown: obj, which callsobj.to_markdown(renderers.rb). Action Text addsto_markdowntoContent,Fragment,RichTextandAttachmenton Rails main (#56858, #56894). This gives Grape the same contract, so an object written for a Rails controller renders the same way in a Grape API.GET /pages/1.md,GET /pages/1.markdown,?format=md,?format=markdownandAccept: text/markdownall answertext/markdown.Grape::Formatter::MdandGrape::Formatter::Markdownto_markdownobject.to_markdownGrape::Exceptions::InvalidFormatter: the default format when the client negotiated Markdown,500when the API pinsformat :mdNotes
:mdnor:markdownis added toGrape::ContentTypes::DEFAULTS. Doing so would switch.mdrequests andAccept: text/markdown, which answer:txttoday, on every API that declares no content types. That can wait for a major release..mdand.markdown. Both are common extensions for Markdown, so:mdand:markdownname the same format (Grape::ContentTypes::FORMAT_SYNONYMS). When a request asks for a format the API did not declare (by extension,?format=ordefault_format), the Formatter middleware tries its synonym and negotiates the name the API declared. A.markdownrequest to an API that declared:mdnegotiates:md, so aformatter :mdof the API's own renders it too. Requests for a declared format never reach the lookup.Grape::Formatter::Mdis an empty subclass ofMarkdown, so the registry, which derives each key from the class name, registers the same rendering under both. TheInvalidFormattermessage names the format the same way, so each class reports its own.:xmlrather than falling back toto_slike:txt. A client asking for Markdown from an endpoint that returns a Hash gets the default format through the existing negotiated-format fallback, not the Hash's#to_s. Rails passes such an object through unchanged.Grape::Mdbackend module, parser or error formatter.Grape::JsonandGrape::Xmlexist to choose a library Grape encodes and decodes with. Grape never produces or parses Markdown itself. Atext/markdownrequest body stays inenv['api.request.input'], and errors go through the text error formatter, since plain text is valid Markdown.charset. RFC 7763 makes it a required parameter oftext/markdown, so the README declarestext/markdown; charset=utf-8. Negotiation still matches, because Grape compares media types without their parameters.to_markdowndefaults toattachment_links: false, so blob attachments render as plain-text placeholders and need no rendering context. Real links needto_markdown(attachment_links: true)called in the endpoint, inside a Rails rendering context.content_type :mdwithout a formatter used to send bodies unchanged, an API that declared one name used to answer the other extension in its default format (or406for?format=), and a user formatter class namedMdorMarkdownnow replaces Grape's with a warning.Verified with full
bundle exec rubocopandbundle exec rspec(3153 examples, 0 failures).🤖 Generated with Claude Code