petail

A RFC 9457 Problem Details for HTTP APIs implementation.

https://github.com/bkuhlmann/petail

Science Score: 44.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
    Found CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Academic email domains
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (11.3%) to scientific vocabulary

Keywords

details errors gem http problem rfc9457
Last synced: 4 months ago · JSON representation ·

Repository

A RFC 9457 Problem Details for HTTP APIs implementation.

Basic Info
Statistics
  • Stars: 1
  • Watchers: 1
  • Forks: 0
  • Open Issues: 0
  • Releases: 0
Topics
details errors gem http problem rfc9457
Created 9 months ago · Last pushed 5 months ago
Metadata Files
Readme Funding License Citation

README.adoc

:toc: macro
:toclevels: 5
:figure-caption!:

= Petail

Petail is a portmanteau (i.e. `[p]roblem + d[etail] = petail`) that implements link:https://www.rfc-editor.org/rfc/rfc9457[RFC 9457: Problem Details for HTTP APIs]. This allows you to produce HTTP error responses that are structured, machine readable, and consistent.

toc::[]

== Features

* Provides JSON and XML serialization and deserialization.
* Provides HTTP header and media type support.

== Requirements

. link:https://www.ruby-lang.org[Ruby].

== Setup

To install _with_ security, run:

[source,bash]
----
# 💡 Skip this line if you already have the public certificate installed.
gem cert --add <(curl --compressed --location https://alchemists.io/gems.pem)
gem install petail --trust-policy HighSecurity
----

To install _without_ security, run:

[source,bash]
----
gem install petail
----

You can also add the gem directly to your project:

[source,bash]
----
bundle add petail
----

Once the gem is installed, you only need to require it:

[source,ruby]
----
require "petail"
----

== Usage

The quickest way to get started is to create a new instance and then cast as JSON or XML:

[source,ruby]
----
payload = Petail[
  type: "https://demo.io/problem_details/timeout",
  status: 413,
  detail: "You've exceeded the 5MB upload limit.",
  instance: "/profile/3a1bfd54-ae6c-4a61-8d0d-90c132428dc3"
]

payload.to_json

# {
#   "type": "https://demo.io/problem_details/timeout",
#   "title": "Content Too Large",
#   "status": 413,
#   "detail": "You've exceeded the 5MB upload limit.",
#   "instance": "/profile/3a1bfd54-ae6c-4a61-8d0d-90c132428dc3"
# }

payload.to_xml

# 
# 
#   https://demo.io/problem_details/timeout
#   Content Too Large
#   413
#   You've exceeded the 5MB upload limit.
#   /profile/3a1bfd54-ae6c-4a61-8d0d-90c132428dc3
# 
----

💡 You can also use `Petail.new` to create instances if you don't like `Petail.[]`, as shown above, but `.[]` is preferred.

=== Members

As briefly shown above, the minimum members (attributes) that make up problem details are:

* `type` (optional): The full (or relative) URI that links to additional documentation. Default: `"about:blank"`.
* `status` (optional): The link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status[HTTP status code] (or symbol) that must match your HTTP status code. Default: `nil`.
* `title` (optional): The HTTP status label that must match your HTTP status code label. Default: HTTP status label (dynamically computed based on code unless overwritten).
* `detail` (optional): The human readable reason for the error (should not include debugging information). Default: `nil`.
* `instance` (optional): The full (or relative) URI that represents the cause of the error. Default: `nil`.
* `extensions` (optional): A free form hash of additional details. Default: `{}`.

=== Media Types

For convenience, you can obtain the necessary media types for your HTTP headers as follows:

[source,ruby]
----
Petail::MEDIA_TYPE_JSON  # "application/problem+json"
Petail::MEDIA_TYPE_XML   # "application/problem+xml"

Petail.media_type_for :json  # "application/problem+json"
Petail.media_type_for :xml   # "application/problem+xml"
----

=== Payload

You'll always get a `Petail::Payload` object answered back when using `Petail.[]` or `Petail.new` for which you can cast to JSON, XML, and other types. There are few conveniences provided for you when constructing a new payload. For instance, you can also use status to set default title:

[source,ruby]
----
Petail[status: 413]
# #
----

Notice that standard HTTP 413 title of "Content Too Large" is provided for you but only if you don't supply a title. This works for symbols too. Example:

[source,ruby]
----
Petail[status: :bad_request]
# #
----

This is similar to the above, but notice the status is cast to an integer while the title is also populated for you. Using either an integer or symbol for the HTTP status is handy for situations where you don't need a custom title and prefer the default HTTP title.

Due to the payload being a `Struct`, you have all of the standard methods available to you. One thing to note is that the payload is frozen by default so you can't mutate attributes. That said, you can still add or check for extensions after the fact. Example:

[source,]
----
payload = Petail[status: :forbidden]

payload.add_extension(:account, "/accounts/1")
       .add_extension(:balance, 50)

# # "/accounts/1",
#     :balance => 50
#   },
#   instance = nil,
#   status = 403,
#   title = "Forbidden",
#   type = "about:blank"
# >
----

Given the above, you can also check if an extension exists:

[source,ruby]
----
payload.extension? :account  # true
payload.extension? :bogus    # false
----

=== JSON

Both serialization and deserialization of JSON is supported. For example, given the following payload:

[source,ruby]
----
payload = Petail[
  type: "https://demo.io/problem_details/out_of_credit",
  title: "You do not have enough credit.",
  status: 403,
  detail: "Your current balance is 30, but that costs 50.",
  instance: "/accounts/1",
  extensions: {
    balance: 30,
    accounts: %w[/accounts/1 /accounts/10]
  }
]
----

This means you can serialize as follows:

[source,ruby]
----
payload.to_json
# "{\"type\":\"https://demo.io/problem_details/out_of_credit\",\"title\":\"You do not have enough credit.\",\"status\":403,\"detail\":\"Your current balance is 30, but that costs 50.\",\"instance\":\"/accounts/1\",\"balance\":30,\"accounts\":[\"/accounts/1\",\"/accounts/10\"]}"

payload.to_json indent: "  ", space: " ", object_nl: "\n", array_nl: "\n"
# {
#   "type": "https://demo.io/problem_details/out_of_credit",
#   "title": "You do not have enough credit.",
#   "status": 403,
#   "detail": "Your current balance is 30, but that costs 50.",
#   "instance": "/accounts/1",
#   "balance": 30,
#   "accounts": [
#     "/accounts/1",
#     "/accounts/10"
#   ]
# }
----

💡 All of the link:https://docs.ruby-lang.org/en/master/JSON.html#module-JSON-label-Output+Options[JSON] output options are available to you when casting to JSON.

You can also deserialize by taking the result of the above and turning the raw JSON back into a `Petail::Payload`:

[source,ruby]
----
Petail.from_json "{\"type\":\"https://demo.io/problem_details/out_of_credit\",\"title\":\"You do not have enough credit.\",\"status\":403,\"detail\":\"Your current balance is 30, but that costs 50.\",\"instance\":\"/accounts/1\",\"balance\":30,\"accounts\":[\"/accounts/1\",\"/accounts/10\"]}"

# # 30,
#     :accounts => [
#       "/accounts/1",
#       "/accounts/10"
#     ]
#   },
#   instance = "/accounts/1",
#   status = 403,
#   title = "You do not have enough credit.",
#   type = "https://demo.io/problem_details/out_of_credit"
# >
----

=== XML

XML is supported too but isn't as robust as JSON support, at the moment. This is mostly due to the fact that extensions can be deeply nested so your mileage may vary. For example, given the following payload:

[source,ruby]
----
payload = Petail[
  type: "https://demo.io/problem_details/out_of_credit",
  title: "You do not have enough credit.",
  status: 403,
  detail: "Your current balance is 30, but that costs 50.",
  instance: "/accounts/1",
  extensions: {
    balance: 30,
    accounts: %w[/accounts/1 /accounts/10]
  }
]
----

This means you can serialize as follows:

[source,ruby]
----
payload.to_xml
# "https://demo.io/problem_details/out_of_creditYou do not have enough credit.403Your current balance is 30, but that costs 50./accounts/130/accounts/1/accounts/10"

payload.to_xml indent: 2
# 
# 
#   
#     https://demo.io/problem_details/out_of_credit
#   
#   
#     You do not have enough credit.
#   
#   
#     403
#   
#   
#     Your current balance is 30, but that costs 50.
#   
#   
#     /accounts/1
#   
#   
#     30
#   
#   
#     
#       /accounts/1
#     
#     
#       /accounts/10
#     
#   
# 
----

💡 All of the link:https://ruby.github.io/rexml/REXML/Document.html#method-i-write[REXML::Document.write] output options are available to you when casting to XML.

You can also deserialize by taking the result of the above and turning the raw JSON back into a `Petail::Payload`:

[source,ruby]
----
payload = Petail.from_xml <<~XML
  
  
    https://demo.io/problem_details/out_of_credit
    You do not have enough credit.
    403
    Your current balance is 30, but that costs 50.
    /accounts/1
    30
    
      /accounts/1
      /accounts/10
    
  
XML

# # "30",
#     :accounts => [
#       "/accounts/1",
#       "/accounts/10"
#     ]
#   },
#   instance = "/accounts/1",
#   status = 403,
#   title = "You do not have enough credit.",
#   type = "https://demo.io/problem_details/out_of_credit"
# >
----

=== Examples

There is a lot of useful information you can provide in your problem details depending on the context you are working in. Some have been shown above but here's a few more that might be of interest.

==== HATEOAS

With link:https://nordicapis.com/tools-to-make-hateoas-compliance-easier[HATEOAS], you can provide additional information and links for which the client can understand what next actions are available. The below example shows how you can provide additional resources for clients to adjust accordingly:

[source,ruby]
----
Petail[
  type: "https://demo.io/problem_details/rate_limit",
  title: "Rate limit exceeded",
  status: 429,
  detail: "You have exceeded your rate limit of 150 requests per minute",
  instance: "/articles",
  extensions: {
    retry_after: 5,
    links: [
      {
        ref: "self",
        href: "/articles"
      },
      {
        rel: "retry",
        href: "/articles",
        title: "Retry after five minutes"
      },
      {
        rel: "status",
        href: "/statuses/rate_limit",
        title: "Check current rate limit usage"
      }
    ]
  }
]
----

==== Semantic Structure

In other situations, you might need a different structure in order to aid clients that might be AI driven which needs a semantically structured response in order to course correct. Example:

[source,ruby]
----
Petail[
  type: "https://demo.io/problem_details/invalid_field",
  title: "Invalid field value",
  status: 400,
  detail: "The category requested doesn't exist",
  instance: "/categories",
  extensions: {
    parameters: {
      category_id: 123
    },
    suggestions: [
      "ruby",
      "git",
      "htmx"
    ]
  }
]
----

With the above, the client now knows what parameters where invalid along with relevant suggestions for proceeding. Even better, the suggestions implicitly show the types of IDs that are required.

== Development

To contribute, run:

[source,bash]
----
git clone https://github.com/bkuhlmann/petail
cd petail
bin/setup
----

You can also use the IRB console for direct access to all objects:

[source,bash]
----
bin/console
----

== Tests

To test, run:

[source,bash]
----
bin/rake
----

== Resources

You can find additional resources here:

* link:https://www.iana.org/assignments/http-problem-types/http-problem-types.xhtml[IANA Hypertext Transfer Protocol (HTTP) Problem Types]: A registered list of problem types you can use.
* link:https://github.com/protocol-registries/http-problem-types[HTTP Problem Type Registration Requests]: Where you can register new problem types.

== link:https://alchemists.io/policies/license[License]

== link:https://alchemists.io/policies/security[Security]

== link:https://alchemists.io/policies/code_of_conduct[Code of Conduct]

== link:https://alchemists.io/policies/contributions[Contributions]

== link:https://alchemists.io/policies/developer_certificate_of_origin[Developer Certificate of Origin]

== link:https://alchemists.io/projects/petail/versions[Versions]

== link:https://alchemists.io/community[Community]

== Credits

* Built with link:https://alchemists.io/projects/gemsmith[Gemsmith].
* Engineered by link:https://alchemists.io/team/brooke_kuhlmann[Brooke Kuhlmann].

Owner

  • Name: Brooke Kuhlmann
  • Login: bkuhlmann
  • Kind: user
  • Location: Boulder, CO USA
  • Company: Alchemists

Quality over quantity.

Citation (CITATION.cff)

cff-version: 1.2.0
message: Please use the following metadata when citing this project in your work.
title: Petail
abstract: A RFC 9457 Problem Details for HTTP APIs implementation.
version: 0.5.0
license: Hippocratic-2.1
date-released: 2025-09-01
authors:
  - family-names: Kuhlmann
    given-names: Brooke
    affiliation: Alchemists
    orcid: https://orcid.org/0000-0002-5810-6268
keywords:
 - ruby
 - rfc9457
 - problem_details
 - http
 - errors
repository-code: https://github.com/bkuhlmann/petail
repository-artifact: https://rubygems.org/gems/petail
url: https://alchemists.io/projects/petail

GitHub Events

Total
  • Issues event: 2
  • Watch event: 1
  • Delete event: 4
  • Issue comment event: 2
  • Push event: 20
  • Pull request event: 2
  • Create event: 9
Last Year
  • Issues event: 2
  • Watch event: 1
  • Delete event: 4
  • Issue comment event: 2
  • Push event: 20
  • Pull request event: 2
  • Create event: 9

Issues and Pull Requests

Last synced: 4 months ago

All Time
  • Total issues: 1
  • Total pull requests: 2
  • Average time to close issues: about 12 hours
  • Average time to close pull requests: 1 minute
  • Total issue authors: 1
  • Total pull request authors: 1
  • Average comments per issue: 2.0
  • Average comments per pull request: 0.0
  • Merged pull requests: 2
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 1
  • Pull requests: 2
  • Average time to close issues: about 12 hours
  • Average time to close pull requests: 1 minute
  • Issue authors: 1
  • Pull request authors: 1
  • Average comments per issue: 2.0
  • Average comments per pull request: 0.0
  • Merged pull requests: 2
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
  • ahx (1)
Pull Request Authors
  • bkuhlmann (2)
Top Labels
Issue Labels
Pull Request Labels

Packages

  • Total packages: 1
  • Total downloads:
    • rubygems 2,121 total
  • Total dependent packages: 0
  • Total dependent repositories: 0
  • Total versions: 6
  • Total maintainers: 1
rubygems.org: petail

A RFC 9457 Problem Details for HTTP APIs implementation.

  • Versions: 6
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Downloads: 2,121 Total
Rankings
Dependent packages count: 14.4%
Dependent repos count: 44.2%
Average: 50.5%
Downloads: 92.7%
Maintainers (1)
Funding
  • https://github.com/sponsors/bkuhlmann
Last synced: 4 months ago

Dependencies

Gemfile rubygems
  • amazing_print ~> 1.7 development
  • caliber ~> 0.74 development
  • debug ~> 1.10 development
  • git-lint ~> 9.0 development
  • irb-kit ~> 1.1 development
  • rake ~> 13.2 development
  • reek ~> 6.5 development
  • repl_type_completor ~> 0.1 development
  • rspec ~> 3.13 development
  • simplecov ~> 0.22 development
petail.gemspec rubygems
  • rack ~> 3.1