<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Doctave</title>
    <description>The latest articles on DEV Community by Doctave (@doctave).</description>
    <link>https://dev.to/doctave</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2978%2F8347fabb-d50a-4b96-af50-57562561ac9a.png</url>
      <title>DEV Community: Doctave</title>
      <link>https://dev.to/doctave</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/doctave"/>
    <language>en</language>
    <item>
      <title>Continuous documentation: publishing docs early and often</title>
      <dc:creator>Nik Begley</dc:creator>
      <pubDate>Mon, 05 Feb 2024 11:18:01 +0000</pubDate>
      <link>https://dev.to/doctave/continuous-documentation-publishing-docs-early-and-often-8gj</link>
      <guid>https://dev.to/doctave/continuous-documentation-publishing-docs-early-and-often-8gj</guid>
      <description>&lt;p&gt;One of the key pillars of &lt;a href="https://www.doctave.com/docs-as-code"&gt;docs-as-code&lt;/a&gt; is releasing changes via &lt;a href="https://about.gitlab.com/topics/ci-cd/"&gt;CI/CD&lt;/a&gt;. Just as how software is often released continuously, we can keep our documentation up to date as the product changes.&lt;/p&gt;

&lt;p&gt;This lets us achieve &lt;strong&gt;continuous documentation&lt;/strong&gt;: instead of large bulk releases, we can incrementally improve our documentation, as we make changes to our product.&lt;/p&gt;

&lt;p&gt;Let's dive into how continuous documentation is implemented in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it easy to make changes
&lt;/h2&gt;

&lt;p&gt;The key to continuous documentation is making it easy to contribute to documentation.&lt;/p&gt;

&lt;p&gt;We want to lower the barrier to making changes, and empower contributors to make the docs better. Let's see how!&lt;/p&gt;

&lt;h3&gt;
  
  
  Smaller, iterative changes
&lt;/h3&gt;

&lt;p&gt;The bigger the change, the harder it is to make. Everyone who has worked in the technology industry has seen large project spiral out of control, missing deadlines, and seemingly never ending.&lt;/p&gt;

&lt;p&gt;Fast-moving teams get around this problem by splitting work into smaller chunks. Instead of a large monolithic release, preferring smaller, iterative changes that are easier to manage. Small changes are easier to review, have less risk, and can be shipped with fewer checks in place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Releasing docs with code
&lt;/h3&gt;

&lt;p&gt;It makes sense to update your product and documentation at the same time. Documentation should be in your &lt;a href="https://www.scrum.org/resources/what-definition-done"&gt;"definition of done"&lt;/a&gt;: a hard requirement for a feature to be released.&lt;/p&gt;

&lt;p&gt;A Git &lt;a href="https://semaphoreci.com/blog/what-is-monorepo"&gt;monorepo&lt;/a&gt;, it's natural to even have code and documentation changes in the same branch or pull-request. Engineers and technical writers can work in the same branch, and eventually merge and deploy the code and documentation in tandem.&lt;/p&gt;

&lt;p&gt;This is what we do at Doctave. We use a monorepo with a dedicated &lt;code&gt;/docs&lt;/code&gt; folder. Every customer-facing feature will have documentation changes included, and get reviewed as part of the same pull-request on GitHub.&lt;/p&gt;

&lt;p&gt;A multi-repository setup requires some more coordination. but the same principles apply. Every feature change should have a corresponding change in the documentation repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Empowering contributors
&lt;/h3&gt;

&lt;p&gt;Documentation is the responsibility of the whole product team. We want to foster a culture where everyone is welcome to contribute by either writing or reviewing documentation.&lt;/p&gt;

&lt;p&gt;Companies like &lt;a href="https://www.doctave.com/blog/2021/09/07/how-google-twitter-and-spotify-build-culture-of-documentation"&gt;Google, Twitter, and Spotify put a lot of effort into building a culture of documentation&lt;/a&gt;. We also see this at many of our customers: reviews are done not just by technical writers, but engineers and product managers responsible for the implementation of a feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automate, automate, automate
&lt;/h3&gt;

&lt;p&gt;Automation means your process is repeatable and, most importantly, transparent. You don't want to be bottlenecked by that one person who knows how to copy your final artifacts to the production server.&lt;/p&gt;

&lt;p&gt;Instead, when changes are merged, they should automatically be deployed without human intervention.&lt;/p&gt;

&lt;p&gt;If you are worried about quality issues, you can catch lots of common issues with tools like linters like &lt;a href="https://vale.sh/"&gt;Vale&lt;/a&gt;. They will check your content for spelling issues or unwanted phrases. This lessens some of the burden on the manual reviewer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing continuous documentation
&lt;/h2&gt;

&lt;p&gt;Let's talk tactically: how do we implement continuous documentation in practice?&lt;/p&gt;

&lt;h3&gt;
  
  
  CI/CD integration
&lt;/h3&gt;

&lt;p&gt;CI/CD plays an important part in continuous documentation since it's the engine automating everything.&lt;/p&gt;

&lt;p&gt;The exact way you should implement CI/CD depends on your repository setup and choice of tools. At minimum, you need to be triggering automatic deployments whenever new changes are merged in your main branch.&lt;/p&gt;

&lt;p&gt;To give a more complex example, we have an &lt;a href="https://docs.doctave.com/docs/publishing/ci-cd"&gt;example in our documentation&lt;/a&gt; about how to integrate Doctave to your CI/CD pipeline in &lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The way it works is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any time there is a pull request, or we've merged changes into the &lt;code&gt;main&lt;/code&gt; branch,&lt;/li&gt;
&lt;li&gt;Check if there are changes to the &lt;code&gt;docs/&lt;/code&gt; subfolder,&lt;/li&gt;
&lt;li&gt;If so, send the docs to Doctave&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We then have versioning rules set up in Doctave that update the public-facing documentation when the &lt;code&gt;main&lt;/code&gt; branch is updated. Else, we just get an isolated preview environment that we can use in our review process.&lt;/p&gt;

&lt;p&gt;It's simple, and works great!&lt;/p&gt;

&lt;h3&gt;
  
  
  Building a culture of documentation
&lt;/h3&gt;

&lt;p&gt;We want to empower contributors by building a culture of documentation. Instead of technical writers being solely responsible for docs, developers should feel welcome to write documentation for the features they are working on, and product managers should be considering how documentation fits into the overall product.&lt;/p&gt;

&lt;p&gt;We have found there are three key steps to doing this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standardize your process and tools:&lt;/strong&gt; Teach everyone in your team &lt;em&gt;how&lt;/em&gt; your documentation is updated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make documentation an expectation&lt;/strong&gt;: No feature is done without great documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove as much friction as possible&lt;/strong&gt;: Have a development environment that Just Works™ and use the same workflows you're already using for reviewing code&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Versioning and branching
&lt;/h3&gt;

&lt;p&gt;It's important your team has an agreed upon strategy for versioning and branching documentation. We've written a whole article on &lt;a href="https://www.doctave.com/blog/documentation-versioning-best-practices"&gt;versioning documentation&lt;/a&gt;, so we'll focus on branching strategies briefly in this section.&lt;/p&gt;

&lt;p&gt;Choosing the right branching structure depends on your repository layout and team preferences. The goal is to ensure it's clear which documentation changes are associated with a specific product change.&lt;/p&gt;

&lt;p&gt;As mentioned above, in the multi-repo case the docs changes may be in the same pull-request. In the multi-repo case, there may be branches in different repositories with matching names.&lt;/p&gt;

&lt;p&gt;Having your team is aligned on your branching strategy makes releasing changes much more fluid and removes confusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Continuous documentation is not just a practice, but a mindset shift in how we approach documentation.&lt;/p&gt;

&lt;p&gt;Here are the main takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Embrace small, iterative changes&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Continuous documentation thrives on the principle of making small, manageable updates to documentation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Foster a culture of documentation&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By empowering all team members, from engineers to product managers, to contribute to documentation, we create a culture where documentation is everyone's responsibility.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Leverage automation and CI/CD integration&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Integrating documentation processes into CI/CD pipelines streamlines updates, making documentation an integral part of the development workflow.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>cicd</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>Generating an OpenAPI/Swagger spec from a Ruby on Rails API</title>
      <dc:creator>Nik Begley</dc:creator>
      <pubDate>Tue, 13 Jun 2023 13:09:10 +0000</pubDate>
      <link>https://dev.to/doctave/generating-an-openapiswagger-spec-from-a-ruby-on-rails-api-1ojd</link>
      <guid>https://dev.to/doctave/generating-an-openapiswagger-spec-from-a-ruby-on-rails-api-1ojd</guid>
      <description>&lt;p&gt;In this post we will go through how to generate an OpenAPI (previously Swagger) API reference from a Ruby on Rails application serving an JSON REST API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.doctave.com/blog/2023/02/21/what-is-openapi.html"&gt;OpenAPI&lt;/a&gt; has become an industry standard for describing APIs, and companies commonly publish OpenAPI specs for documentation and code generation purposes.&lt;/p&gt;

&lt;p&gt;We will be creating a "Coffee Ordering API" using Ruby on Rails, and using a tool called &lt;a href="https://github.com/rswag/rswag/"&gt;rswag&lt;/a&gt; to create tests that verify the behaviour of our API and generate an OpenAPI reference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You can view the source code for this tutorial &lt;a href="https://github.com/Doctave/ruby-on-rails-openapi-example"&gt;on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the app
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;(We are going to assume for the purposes of this demo that you have both Ruby and Rails installed on your machine. We recommend the &lt;a href="https://guides.rubyonrails.org/v5.1/getting_started.html"&gt;Rails Guides&lt;/a&gt; if you need help getting started.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We are using Rails 7.0.2 and Ruby 3.1.2 for this example.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;First, let's create a new app! We are going to skip some best practices here in order to get something we can play with quickly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails new coffee_shop &lt;span class="nt"&gt;--api&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;coffee_shop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are passing the &lt;code&gt;--api&lt;/code&gt; flag to skip some code generation, as we are not going to be serving any HTML from this app, only JSON.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data model
&lt;/h3&gt;

&lt;p&gt;Next, we will create our data model, the &lt;code&gt;Order&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create a migration for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./bin/rails g model Order kind price:integer customer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also update the migration to make sure our fields are required on the database level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# db/migrate/&amp;lt;timestamp&amp;gt;_create_orders.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrders&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decimal&lt;/span&gt; &lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember to migrate your database!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./bin/rails db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's also add some validations into our model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/order.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;

  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;numericality: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;greater_than: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Controller
&lt;/h3&gt;

&lt;p&gt;Next, let's create a controller. We will put it under an &lt;code&gt;Api&lt;/code&gt; namespace, since we want to prefix the API routes with &lt;code&gt;/api&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./bin/rails g controller Api::Orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, make sure we specify a route for this controller.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

  &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:api&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# We will only care about a couple actions in this example&lt;/span&gt;
    &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On to the controller! We will define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;code&gt;index&lt;/code&gt; method for listing all orders&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;show&lt;/code&gt; method for getting the details of a single order&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;create&lt;/code&gt; method for creating a new order
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::OrdersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;

    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="vi"&gt;@orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;except: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:updated_at&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="vi"&gt;@order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="vi"&gt;@order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;except: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:updated_at&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RecordNotFound&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;error: &lt;/span&gt;&lt;span class="s2"&gt;"Order not found"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;status: :not_found&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="vi"&gt;@order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="vi"&gt;@order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;except: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:updated_at&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="vi"&gt;@order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;order_params&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:order&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Taking it for a spin
&lt;/h3&gt;

&lt;p&gt;Great! We're ready to try out the API. Let's start the Rails local server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./bin/rails s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now test our controller and see it in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl
  &lt;span class="nt"&gt;-XPOST&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"order": {"price": "2.3", "customer": "Nik", "kind": "Espresso"}}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  localhost:3000/api/orders
&lt;span class="c"&gt;#=&amp;gt; {"id":1,"kind":"Espresso","price":"2.3","customer":"Nik"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works! Lets list all the orders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  localhost:3000/api/orders
&lt;span class="c"&gt;#=&amp;gt; [{"id":1,"kind":"Espresso","price":"2.3","customer":"Nik"}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And what about fetching a single order?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  localhost:3000/api/orders/1
&lt;span class="c"&gt;#=&amp;gt; {"id":1,"kind":"Espresso","price":"2.3","customer":"Nik"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking good. Let's move on to describing this API with OpenAPI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Describing REST APIs with rswag
&lt;/h2&gt;

&lt;p&gt;This is where &lt;a href="https://github.com/rswag/rswag/"&gt;rswag&lt;/a&gt; comes in. It is an extension to &lt;a href="https://github.com/rspec/rspec-rails"&gt;rspec-rails&lt;/a&gt; for "describing and testing API operations".&lt;/p&gt;

&lt;p&gt;The workflow is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create tests under &lt;code&gt;spec/requests&lt;/code&gt; that describe your API&lt;/li&gt;
&lt;li&gt;Add OpenAPI annotations to your tests&lt;/li&gt;
&lt;li&gt;Run your tests to ensure the arguments and results conform to the expected shape&lt;/li&gt;
&lt;li&gt;Generate an OpenAPI spec once tests pass&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;rswag also includes a bundled version of &lt;a href="https://github.com/swagger-api/swagger-ui"&gt;Swagger UI&lt;/a&gt; if you want to inspect your OpenAPI spec visually during testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing rswag
&lt;/h3&gt;

&lt;p&gt;First, we install rswag. Let's add them to our Gemfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rswag-api'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rswag-ui'&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rspec-rails'&lt;/span&gt;   &lt;span class="c1"&gt;# Note that we also need rspec-rails&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rswag-specs'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install your new dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./bin/bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, run the installers for rswag and rspec-rails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# rspec-rails
rails generate rspec:install

# rswag
./bin/rails g rswag:api:install &amp;amp;&amp;amp; ./bin/rails g rswag:ui:install &amp;amp;&amp;amp; RAILS_ENV=test ./bin/rails g rswag:specs:install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will set up the boilerplate needed to execute our rswag tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the spec
&lt;/h3&gt;

&lt;p&gt;Next, let's create a spec to describe our API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./bin/rails generate rspec:swagger Api::Orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate a spec file for you under &lt;code&gt;spec/requests/api/orders_spec.rb&lt;/code&gt;. Open it up and inspect its contents. Let's take a look at the very first test, which describes the &lt;code&gt;index&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/requests/api/orders_spec.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'swagger_helper'&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'api/orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :request&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

  &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="s1"&gt;'/api/orders'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

    &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'list orders'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'successful'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

        &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:response&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:content&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;'application/json'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="ss"&gt;example: &lt;/span&gt;&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;symbolize_names: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="n"&gt;run_test!&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rswag's DSL tries to mirror OpenAPI's structure. We have here a &lt;code&gt;path&lt;/code&gt; method, which takes a block for describing the operations under that path. In this case, we see the &lt;code&gt;get&lt;/code&gt; HTTP method for this path, which is handled by the &lt;code&gt;OrdersController&lt;/code&gt;'s &lt;code&gt;index&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;This spec says that we just expect it to return a 200 response, but does not say anything about &lt;em&gt;what&lt;/em&gt; it should return. We will be changing this shortly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining common objects
&lt;/h3&gt;

&lt;p&gt;A common thing to do in OpenAPI is to create &lt;a href="https://swagger.io/docs/specification/components/"&gt;schema components&lt;/a&gt; that are reusable in your spec. This is commonly done for objects that show up in multiple places, or common errors.&lt;/p&gt;

&lt;p&gt;In our case we are dealing with a single type of object: the &lt;code&gt;Order&lt;/code&gt; model. Let's create a reusable component for it.&lt;/p&gt;

&lt;p&gt;rswag has created a helper file for you under &lt;code&gt;spec/swagger_helper.rb&lt;/code&gt;. It contains is the root of your OpenAPI specification into which the description from your specs will be injected. This is where we can describe out &lt;code&gt;order&lt;/code&gt; model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/swagger_helper.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swagger_root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'swagger'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swagger_docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;'v1/swagger.yaml'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;openapi: &lt;/span&gt;&lt;span class="s1"&gt;'3.0.1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;info: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s1"&gt;'Coffee Shop API V1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;version: &lt;/span&gt;&lt;span class="s1"&gt;'v1'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="ss"&gt;paths: &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
      &lt;span class="ss"&gt;components: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;schemas: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;not_found: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'object'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;properties: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;type: :string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="ss"&gt;order: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;    &lt;span class="c1"&gt;# &amp;lt;&amp;lt; This is where we describe our order&lt;/span&gt;
            &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'object'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;required: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:customer&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;properties: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="ss"&gt;kind: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="ss"&gt;type: :string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="ss"&gt;example: &lt;/span&gt;&lt;span class="s2"&gt;"Espresso"&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="ss"&gt;price: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="ss"&gt;type: :string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="ss"&gt;pattern: &lt;/span&gt;&lt;span class="s2"&gt;"^&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;d*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.?&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;d*$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="ss"&gt;example: &lt;/span&gt;&lt;span class="s2"&gt;"1.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s2"&gt;"Price, formatted as a string"&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="ss"&gt;customer: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="ss"&gt;type: :string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="ss"&gt;example: &lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="ss"&gt;servers: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="s1"&gt;'https://{defaultHost}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;variables: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;defaultHost: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="s1"&gt;'www.example.com'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swagger_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:yaml&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See line 23 of the example - this is where we describe our reusable component. You can also set other things in this config file, such as your API version, your endpoint URL, and other metadata.&lt;/p&gt;

&lt;h3&gt;
  
  
  Describing operations
&lt;/h3&gt;

&lt;p&gt;Let's take our &lt;code&gt;index&lt;/code&gt; example and expand the spec that was autogenerated for us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/requests/api/orders_spec.rb&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'List orders'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="s1"&gt;'Orders'&lt;/span&gt;
  &lt;span class="n"&gt;consumes&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;
  &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="s2"&gt;"List all orders in the system"&lt;/span&gt;

  &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'successful'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="ss"&gt;type: :array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;items: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"$ref"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"#/components/schemas/order"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:order1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;kind: &lt;/span&gt;&lt;span class="s2"&gt;"Latte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;price: &lt;/span&gt;&lt;span class="mf"&gt;2.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;customer: &lt;/span&gt;&lt;span class="s2"&gt;"Bob"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:order2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;kind: &lt;/span&gt;&lt;span class="s2"&gt;"Espresso"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;price: &lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;customer: &lt;/span&gt;&lt;span class="s2"&gt;"Eve"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:response&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:content&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
      &lt;span class="n"&gt;example_spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;examples: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;test_example: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;symbolize_names: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:response&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:content&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deep_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example_spec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;run_test!&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we've done a number of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added a tag to the operation. Tags are used to group common operations together, and tools that consume OpenAPI will use this information.&lt;/li&gt;
&lt;li&gt;Set the &lt;code&gt;Accepts&lt;/code&gt; and &lt;code&gt;Content-Type&lt;/code&gt; header requirements&lt;/li&gt;
&lt;li&gt;Set a description for the operation. Note that according to the OpenAPI spec you can use Markdown in any &lt;code&gt;description&lt;/code&gt; field!&lt;/li&gt;
&lt;li&gt;We refer to the &lt;code&gt;order&lt;/code&gt; shared component with a &lt;code&gt;$ref&lt;/code&gt;, telling OpenAPI this is the shape of the returned object for the operation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;(&lt;strong&gt;NOTE&lt;/strong&gt;: rswag has a "bug" that causes response schemas to be empty in the final OpenAPI spec, unless the &lt;code&gt;produces&lt;/code&gt; method is called specifying the return content type. Thank you to &lt;a href="https://github.com/rswag/rswag/issues/559#issuecomment-1298004275"&gt;this person&lt;/a&gt; for pointing me in the right direction).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Finally, we've taken advantage of a really cool rswag feature: automatic example generation.&lt;/p&gt;

&lt;p&gt;When the test is run, we can take the output the operation returned and use it in our OpenAPI spec examples. In this example, we create 2 orders, which will be returned by the list operation, and serialised into our OpenAPI example.&lt;/p&gt;

&lt;p&gt;We'll do the same for the other two operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/requests/api/orders_spec.rb&lt;/span&gt;
&lt;span class="c1"&gt;# continued from above...&lt;/span&gt;

    &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'create order'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="s1"&gt;'Orders'&lt;/span&gt;
      &lt;span class="n"&gt;consumes&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;
      &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;
      &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="s2"&gt;"Create a new order. **NOTE**: Price is set by customer! Do not go to production."&lt;/span&gt;

      &lt;span class="n"&gt;parameter&lt;/span&gt; &lt;span class="ss"&gt;name: :order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;in: :body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;schema: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"$ref"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"#/components/schemas/order"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'successful'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="s2"&gt;"$ref"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"#/components/schemas/order"&lt;/span&gt;

        &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;kind: &lt;/span&gt;&lt;span class="s2"&gt;"Espresso"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;price: &lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;customer: &lt;/span&gt;&lt;span class="s2"&gt;"Eve"&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:response&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:content&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
          &lt;span class="n"&gt;example_spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="ss"&gt;examples: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="ss"&gt;test_example: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;symbolize_names: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:response&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:content&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deep_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example_spec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="n"&gt;run_test!&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="s1"&gt;'/api/orders/{id}'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;parameter&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;in: :path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s1"&gt;'The ID for the order'&lt;/span&gt;

    &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'show order'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="s2"&gt;"Get the details for a particular order"&lt;/span&gt;

      &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;

      &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'successful'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="s2"&gt;"$ref"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"#/components/schemas/order"&lt;/span&gt;

        &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;kind: &lt;/span&gt;&lt;span class="s2"&gt;"Latte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;price: &lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;customer: &lt;/span&gt;&lt;span class="s2"&gt;"Bob"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:response&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:content&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
          &lt;span class="n"&gt;example_spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="ss"&gt;examples: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="ss"&gt;test_example: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;symbolize_names: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:response&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:content&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deep_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example_spec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="n"&gt;run_test!&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'not found'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="s2"&gt;"$ref"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"#/components/schemas/not_found"&lt;/span&gt;

        &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mi"&gt;999999999&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:response&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:content&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
          &lt;span class="n"&gt;example_spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="ss"&gt;examples: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="ss"&gt;test_example: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;symbolize_names: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:response&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:content&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deep_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example_spec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="n"&gt;run_test!&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that for the &lt;code&gt;show&lt;/code&gt; action we supply two response examples: a 200 and 404 response. We could do the same for the errors that could be returned from the &lt;code&gt;create&lt;/code&gt; action, but we'll leave that as an exercise for the reader.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the tests
&lt;/h3&gt;

&lt;p&gt;Let's run the tests! If we did everything correctly, we should see all green.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec ./spec/requests/api/orders_spec.rb
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="c"&gt;# Finished in 0.03847 seconds (files took 0.51792 seconds to load)&lt;/span&gt;
&lt;span class="c"&gt;# 3 examples, 0 failures&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success!&lt;/p&gt;

&lt;p&gt;Let's try changing something in our spec, and see if the test catches it. We will make a change to the shared &lt;code&gt;order&lt;/code&gt; model under &lt;code&gt;spec/swagger_helper.rb&lt;/code&gt; so that the customer should be an integer instead of a string and run the tests again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec ./spec/requests/api/orders_spec.rb

&lt;span class="c"&gt;#   ...a gigantic stacktrace...&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="c"&gt;#   2) api/orders /api/orders post successful returns a 200 response&lt;/span&gt;
&lt;span class="c"&gt;#      Failure/Error:&lt;/span&gt;
&lt;span class="c"&gt;#        raise UnexpectedResponse,&lt;/span&gt;
&lt;span class="c"&gt;#              "Expected response body to match schema: #{errors.join("\n")}\n" \&lt;/span&gt;
&lt;span class="c"&gt;#              "Response body: #{JSON.pretty_generate(JSON.parse(body))}"&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="c"&gt;#      Rswag::Specs::UnexpectedResponse:&lt;/span&gt;
&lt;span class="c"&gt;#        Expected response body to match schema: The property '#/customer' of type string did not match the following type: integer in schema ba752ce3-171a-5719-945f-62b5f5cd1cf5#&lt;/span&gt;
&lt;span class="c"&gt;#        Response body: {&lt;/span&gt;
&lt;span class="c"&gt;#          "id": 1,&lt;/span&gt;
&lt;span class="c"&gt;#          "kind": "Espresso",&lt;/span&gt;
&lt;span class="c"&gt;#          "price": "0.2",&lt;/span&gt;
&lt;span class="c"&gt;#          "customer": "Eve"&lt;/span&gt;
&lt;span class="c"&gt;#        }&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="c"&gt;#   ...&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="c"&gt;# Finished in 0.03698 seconds (files took 0.49892 seconds to load)&lt;/span&gt;
&lt;span class="c"&gt;# 3 examples, 2 failures&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="c"&gt;# Failed examples:&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="c"&gt;# rspec ./spec/requests/api/orders_spec.rb:12 # api/orders /api/orders get successful returns a 200 response&lt;/span&gt;
&lt;span class="c"&gt;# rspec ./spec/requests/api/orders_spec.rb:43 # api/orders /api/orders post successful returns a 200 response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've shortened the output of rspec significantly to focus on the important parts. Rswag correctly realised that we returned a string for our &lt;code&gt;customer&lt;/code&gt; field instead of an integer, like the OpenAPI spec described.&lt;/p&gt;

&lt;p&gt;This means we can verify that our OpenAPI spec matches the actual implementation of our server automatically. If we run these checks in CI/CD, our spec should never be out of date.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating the OpenAPI spec
&lt;/h3&gt;

&lt;p&gt;As a final step, let's actually generate the final OpenAPI spec for this API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SWAGGER_DRY_RUN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; ./bin/rails rswag

&lt;span class="c"&gt;# Generating Swagger docs ...&lt;/span&gt;
&lt;span class="c"&gt;# Swagger doc generated at ./coffee_shop/swagger/v1/swagger.yaml&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="c"&gt;# Finished in 0.06065 seconds (files took 0.504 seconds to load)&lt;/span&gt;
&lt;span class="c"&gt;# 3 examples, 0 failures&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rswag just generated our OpenAPI file under &lt;code&gt;swagger/v1/swagger.yml&lt;/code&gt;. Let's take&lt;br&gt;
a look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.1&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Coffee Shop API V1&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/orders"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List orders&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Orders&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List all orders in the system&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;200'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;successful&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;examples&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;test_example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
                    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Latte&lt;/span&gt;
                    &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2.8'&lt;/span&gt;
                    &lt;span class="na"&gt;customer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bob&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
                    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Espresso&lt;/span&gt;
                    &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.1'&lt;/span&gt;
                    &lt;span class="na"&gt;customer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Eve&lt;/span&gt;
    &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;create order&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Orders&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;new&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;order.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;**NOTE**:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Price&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;set&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;by&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;customer!&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Do&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;
        &lt;span class="s"&gt;go&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;production.'&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;200'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;successful&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;examples&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;test_example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
                    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Espresso&lt;/span&gt;
                    &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.2'&lt;/span&gt;
                    &lt;span class="na"&gt;customer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Eve&lt;/span&gt;
      &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$ref"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/order"&lt;/span&gt;
&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/orders/{id}"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;id&lt;/span&gt;
      &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The ID for the order&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;integer&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;show order&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get the details for a particular order&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;200'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;successful&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;examples&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;test_example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
                    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Latte&lt;/span&gt;
                    &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.8'&lt;/span&gt;
                    &lt;span class="na"&gt;customer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bob&lt;/span&gt;
        &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;404'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;not found&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;examples&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;test_example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Order not found&lt;/span&gt;
&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;not_found&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;order&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kind&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;price&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;customer&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
          &lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Espresso&lt;/span&gt;
        &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
          &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;d*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;.?&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;d*$"&lt;/span&gt;
          &lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.2'&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Price, formatted as a string&lt;/span&gt;
        &lt;span class="na"&gt;customer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
          &lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Alice&lt;/span&gt;
&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://{defaultHost}&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;defaultHost&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;www.example.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There we have it! An OpenAPI spec, generated automatically from our code, verified by tests to match our actual implementation. This spec can now be used to generate client SDKs or API reference documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;So, we now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A JSON API with 3 actions&lt;/li&gt;
&lt;li&gt;Tests that verify the implementation&lt;/li&gt;
&lt;li&gt;Automatically generated OpenAPI specifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are using Ruby on Rails, this can be a very effective way to produce accurate OpenAPI specifications.&lt;/p&gt;

&lt;p&gt;That being said, there is no free lunch. Especially when producing API documentation from OpenAPI, you have to put effort into adding helpful descriptions and lots of examples for your operations. Users won't find a bare bones OpenAPI reference useful. But a well maintained and annotated one can make an API shine.&lt;/p&gt;

&lt;p&gt;But with a setup like this, you are well on your way to using OpenAPI effectively!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Export an OpenAPI specification from your FastAPI app</title>
      <dc:creator>Nik Begley</dc:creator>
      <pubDate>Fri, 19 May 2023 08:14:50 +0000</pubDate>
      <link>https://dev.to/doctave/export-an-openapi-specification-from-your-fastapi-app-5363</link>
      <guid>https://dev.to/doctave/export-an-openapi-specification-from-your-fastapi-app-5363</guid>
      <description>&lt;p&gt;&lt;a href="https://fastapi.tiangolo.com/"&gt;FastAPI&lt;/a&gt; is a modern Python web framework for building APIs. FastAPI is a great choice for building simple APIs, and it comes with built-in support for generating OpenAPI documentation.&lt;/p&gt;

&lt;p&gt;In this post we will look at how to generate and extract the &lt;a href="https://www.doctave.com/blog/2023/02/21/what-is-openapi.html"&gt;OpenAPI specification&lt;/a&gt; from a FastAPI project.&lt;/p&gt;

&lt;p&gt;A simple single-file FastAPI example taken from &lt;a href="https://fastapi.tiangolo.com/tutorial/response-model/"&gt;the official docs&lt;/a&gt; will suffice as our testbed. The same workflow will also work for larger projects with routers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Add Tags and Metadata
&lt;/h2&gt;

&lt;p&gt;Chances are that you'll use the OpenAPI spec to generate documentation or code. It is important to add metadata to your FastAPI app so that the generated OpenAPI spec is complete.&lt;/p&gt;

&lt;p&gt;Firstly, you should tag our endpoints with &lt;code&gt;tags&lt;/code&gt; to make sure they are grouped in logical operations. This example does not use &lt;a href="https://fastapi.tiangolo.com/tutorial/bigger-applications/#apirouter"&gt;routers&lt;/a&gt;, but if you do, you need to tag the router instead of the endpoint.&lt;/p&gt;

&lt;p&gt;Tags are used by documentation and code generators to group endpoints together. Tags may include spaces and special characters, but we recommend to keep the tags simple. It is common to use either lowercase or Capital Case for tags, like &lt;code&gt;Items&lt;/code&gt; in our example.&lt;/p&gt;

&lt;p&gt;In addition to tags, we'll add a &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;version&lt;/code&gt; metadata to our FastAPI app instance. The &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;version&lt;/code&gt; will be used in the generated OpenAPI docs on the overview page. You can find the full list of &lt;a href="https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api"&gt;metadata parameters&lt;/a&gt; in the FastAPI docs if you need to include additional details in your specification.&lt;/p&gt;

&lt;p&gt;The full example &lt;code&gt;main.py&lt;/code&gt; now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Example app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0.1.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;tax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;


&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/items/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Items"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;


&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/items/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Items"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Portal Gun"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;42.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Plumbus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;32.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example requires &lt;code&gt;pip install fastapi[all]&lt;/code&gt; and &lt;code&gt;pip install pydantic&lt;/code&gt; to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Create the Export Script
&lt;/h2&gt;

&lt;p&gt;By default FastAPI will generate OpenAPI docs under &lt;code&gt;/docs&lt;/code&gt;. You can try this out by running the app and navigating to &lt;code&gt;http://localhost:8000/docs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is possible to get the OpenAPI JSON directly by navigating to &lt;code&gt;/openapi.json&lt;/code&gt;, but we'll want to extract the document programmatically in order to be able to automate the process. FastAPI does not support exporting the OpenAPI specification directly, but we'll use a small script to extract it.&lt;/p&gt;

&lt;p&gt;Create a file &lt;code&gt;extract-openapi.py&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# extract-openapi.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;yaml&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;uvicorn.importer&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;import_from_string&lt;/span&gt;

&lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"extract-openapi.py"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'App import string. Eg. "main:app"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"main:app"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"--app-dir"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Directory containing the app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"--out"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Output file ending in .json or .yaml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"openapi.yaml"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app_dir&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"adding &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app_dir&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; to sys.path"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"importing app from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;import_from_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;openapi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"openapi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"unknown version"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"writing openapi spec v&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".json"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"spec written to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this script does is import the app from the given import string, and then call &lt;code&gt;app.openapi()&lt;/code&gt; to get the OpenAPI spec. The spec is then written to the given output file.&lt;/p&gt;

&lt;p&gt;You can invoke help to see the available options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python3 export-openapi.py &lt;span class="nt"&gt;--help&lt;/span&gt;
usage: extract-openapi.py &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-h&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--app-dir&lt;/span&gt; APP_DIR] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--out&lt;/span&gt; OUT] app

positional arguments:
app App import string. Eg. &lt;span class="s2"&gt;"main:app"&lt;/span&gt;

options:
&lt;span class="nt"&gt;-h&lt;/span&gt;, &lt;span class="nt"&gt;--help&lt;/span&gt; show this &lt;span class="nb"&gt;help &lt;/span&gt;message and &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="nt"&gt;--app-dir&lt;/span&gt; APP_DIR Directory containing the app
&lt;span class="nt"&gt;--out&lt;/span&gt; OUT Output file ending &lt;span class="k"&gt;in&lt;/span&gt; .json or .yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have trouble with the &lt;code&gt;uvicorn&lt;/code&gt; import, make sure you have installed the &lt;code&gt;fastapi[all]&lt;/code&gt; package, which includes uvicorn, or that you have &lt;code&gt;uvicorn&lt;/code&gt; installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Export OpenAPI spec from FastAPI
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Running the script
&lt;/h3&gt;

&lt;p&gt;To run the export script, you need to know the import string of your FastAPI app. &lt;em&gt;The import string is is what you pass to uvicorn when running your app, like &lt;code&gt;uvicorn main:app&lt;/code&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This depends on where your code is located, and what the FastAPI instance is called. See below for tips on how to determine the import string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run the script by passing the import string of your FastAPI app&lt;/span&gt;
&lt;span class="c"&gt;# If you don't know the import string, see below for examples&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;python extract-openapi.py main:app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should create an &lt;code&gt;openapi.json&lt;/code&gt; or &lt;code&gt;openapi.yaml&lt;/code&gt; file in your current directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example case: simple project structure
&lt;/h3&gt;

&lt;p&gt;In our example, we have a project structure like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/my_project
├── extract-openapi.py
└── main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our FastAPI instance is &lt;code&gt;app&lt;/code&gt; since we wrote &lt;code&gt;app = FastAPI()&lt;/code&gt; in &lt;code&gt;main.py&lt;/code&gt;. We also have a single file &lt;code&gt;main.py&lt;/code&gt; in the current directory, so the import string is &lt;code&gt;main:app&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To extract the OpenAPI spec we do&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python extract-openapi.py main:app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example case: nested project structure
&lt;/h3&gt;

&lt;p&gt;For larger applications, the project structure is often more nested, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/my_project
├── extract-openapi.py
└── myapp
    └── main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case we'll have to add the module name, &lt;code&gt;myapp&lt;/code&gt;, to the import string. The import string is now &lt;code&gt;myapp.main:app&lt;/code&gt;. Alternatively, if you are having trouble with this, you can use the &lt;code&gt;--app-dir&lt;/code&gt; argument to specify the directory containing the entrypoint of your application.&lt;/p&gt;

&lt;p&gt;In this case, to extract the OpenAPI spec we do&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python extract-openapi.py myapp.main:app

&lt;span class="c"&gt;# or alternatively&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;python extract-openapi.py &lt;span class="nt"&gt;--app-dir&lt;/span&gt; myapp main:app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now have the &lt;code&gt;openapi.json&lt;/code&gt; or &lt;code&gt;openapi.yaml&lt;/code&gt; file in your current directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Automate in your CI/CD pipeline (optional)
&lt;/h2&gt;

&lt;p&gt;How you'll integrate the extraction to your CI/CD depends on what you are trying to accomplish. The three most common ways to approach this are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extract the spec locally and commit it to your repository. Let CI/CD verify the committed spec is up-to-date.&lt;/li&gt;
&lt;li&gt;Extract the spec as part of your CI/CD pipeline, and use the spec as a temporary file to accomplish something (eg. generate a client).&lt;/li&gt;
&lt;li&gt;Extract the spec as part of your CI/CD pipeline, and commit the generated spec to your repository, when merging to main.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benefit with using a script is that it can also be run locally. Locally committing is often a safe and straight-forward approach, but may occasionally make merging more difficult. If, however, you only need to generate the OpenAPI spec as part of your CI/CD pipeline, you should also consider dedicated GitHub Actions.&lt;/p&gt;

&lt;p&gt;As an example, we'll demonstrate a GitHub Actions job, which verifies that the committed spec matches a generated one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/main.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;extract-openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
      &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Python&lt;/span&gt;
         &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v2&lt;/span&gt;
         &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3.11&lt;/span&gt;

         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
         &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;python -m pip install --upgrade pip&lt;/span&gt;
            &lt;span class="s"&gt;pip install -r requirements.txt&lt;/span&gt;

         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Extract OpenAPI spec&lt;/span&gt;
         &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python extract-openapi.py main:app --out openapi_generated.yaml&lt;/span&gt;

         &lt;span class="c1"&gt;# Do something with the generated spec here.&lt;/span&gt;
         &lt;span class="c1"&gt;# For example, validate that the committed spec matches the generated one.&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Verify OpenAPI spec has been updated&lt;/span&gt;
         &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git diff --exit-code openapi.yaml openapi_generated.yaml&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In summary, we have&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added &lt;code&gt;tags&lt;/code&gt; to each endpoint or router&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt; and other metadata to our FastAPI app instance&lt;/li&gt;
&lt;li&gt;Created a script &lt;code&gt;extract-openapi.py&lt;/code&gt; to extract the OpenAPI spec from FastAPI&lt;/li&gt;
&lt;li&gt;Automated the extraction in CI/CD pipeline&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>How AI is changing documentation</title>
      <dc:creator>Nik Begley</dc:creator>
      <pubDate>Wed, 17 May 2023 13:25:18 +0000</pubDate>
      <link>https://dev.to/doctave/how-ai-is-changing-documentation-bd7</link>
      <guid>https://dev.to/doctave/how-ai-is-changing-documentation-bd7</guid>
      <description>&lt;p&gt;Documentation has been one of the earliest fields to see large changes due to &lt;a href="https://openai.com/blog/chatgpt"&gt;ChatGPT&lt;/a&gt;, and more recently &lt;a href="https://openai.com/research/gpt-4"&gt;GPT-4&lt;/a&gt;. Especially technical documentation, being in the intersection of AI-tinkerers (programmers) and written content, has seen a lot of movement. We've seen experiments in generating documentation, new ways of interacting with documentation, and lots of people wondering what the future will look like.&lt;/p&gt;

&lt;p&gt;We at Doctave are, for obvious reasons, looking at this space carefully. In this post will take a look at the AI-powered documentation landscape: what we have seen work and not work so far. And finally, we will make some guesses about what the future holds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclaimer: Things are moving fast
&lt;/h2&gt;

&lt;p&gt;I am writing this in mid-May of 2023. The pace of change in AI means that the opinions and statements of fact in this piece may be out of date very quickly.&lt;/p&gt;

&lt;p&gt;As an example, less than a week ago, as of time of writing, &lt;a href="https://www.anthropic.com/"&gt;Anthropic&lt;/a&gt; &lt;a href="https://www.anthropic.com/index/100k-context-windows"&gt;announced 100K context windows&lt;/a&gt; for their Claude model: equivalent to around 75,000 words. This may already change the dynamics discussed in this post when released to the public.&lt;/p&gt;

&lt;p&gt;This post will also only focus on AI in the context of (technical) documentation, instead of AI capabilities broadly, which should not be underestimated.&lt;/p&gt;

&lt;p&gt;It's important to stay humble when things are moving this fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we've seen so far
&lt;/h2&gt;

&lt;p&gt;AI applied to documentation has touched how we both read and write documentation.&lt;/p&gt;

&lt;p&gt;On the writing side, OpenAI's &lt;a href="https://platform.openai.com/docs/guides/code"&gt;Codex&lt;/a&gt; model, powering GitHub Copilot, has turned out to be great at understanding what a piece of source code is doing and producing documentation at least at the function level. ChatGPT has also proved to be a valuable tool for expanding, rewording, or editing any type of prose: including documentation.&lt;/p&gt;

&lt;p&gt;On the reading side, companies have started adding AI-powered chat interfaces to their docs. And for popular tools you can even get ChatGPT to tell you the incantation you need without having to even look at the docs.&lt;/p&gt;

&lt;p&gt;These are just the earliest experiments, but let's dive into them a bit.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-powered documentation chat
&lt;/h2&gt;

&lt;p&gt;Chat interfaces powered by OpenAI's API was one of the first things people started playing with. It was an obvious leap from the ChatGPT interface: what if you could have a discussion with an AI that knows everything about your documentation?&lt;/p&gt;

&lt;p&gt;As far as I can tell, &lt;a href="https://supabase.com/blog/chatgpt-supabase-docs"&gt;Supabase&lt;/a&gt; was the first large company to add such an interface (aptly named "Clippy") to their developer documentation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D666C-4W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yagiyzl3kroh5uk3yed7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D666C-4W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yagiyzl3kroh5uk3yed7.png" alt="Supabase Clippy chat interface screenshot" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since then, a long list of companies have built such interfaces for their documentation. Similarly, companies providing documentation platforms have added such features to their offering. Not only that, there are companies whose whole business is building chat interfaces over whatever content you can throw at them: be it technical documentation, CRM content, or sales meeting transcripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do they work?
&lt;/h3&gt;

&lt;p&gt;These chat interfaces are quite straightforward to build these days. In fact, OpenAI has a guide on how to build them &lt;a href="https://platform.openai.com/docs/tutorials/web-qa-embeddings"&gt;in their documentation&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Split your content into topical sections&lt;/li&gt;
&lt;li&gt;Create &lt;a href="https://platform.openai.com/docs/guides/embeddings"&gt;embeddings&lt;/a&gt; with OpenAI for your sections&lt;/li&gt;
&lt;li&gt;Index your embeddings in a database (Postgres works great, but this use case among others has caused an explosion in &lt;a href="https://www.pinecone.io/"&gt;vector&lt;/a&gt; &lt;a href="https://qdrant.tech/"&gt;database&lt;/a&gt; &lt;a href="https://weaviate.io/"&gt;startups&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Create an embedding for your user's question through OpenAI&lt;/li&gt;
&lt;li&gt;Run a query against your embeddings to find the ones that are the closest matches with the question's embedding. (Embeddings are arrays of numbers, so it is possible to compute a "distance" between different embeddings).&lt;/li&gt;
&lt;li&gt;Include the closest matches as "context" for your final OpenAI completion call, followed by your user's query&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What is the experience like?
&lt;/h3&gt;

&lt;p&gt;Having tried out a number of the customised documentation chat interfaces so far, the experience has been slightly underwhelming. Especially considering the incredible success of ChatGPT, it was surprising to find that these chat interfaces for documentation seemed to be &lt;em&gt;less&lt;/em&gt; helpful than traditional search.&lt;/p&gt;

&lt;p&gt;We think there are two main issues with these documentation chat interfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Speed of answering&lt;/li&gt;
&lt;li&gt;Accuracy and helpfulness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both of these issues seem to be preventing chat interfaces from being the 10x improvement we can imagine them being at the moment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Call me when you're done
&lt;/h3&gt;

&lt;p&gt;If you have not done so yet, I encourage you to try using a chat interface for a documentation site. What you will find is that it is painfully slow.&lt;/p&gt;

&lt;p&gt;While the GPT-3.5 model can be incredibly fast today, for documentation you likely want to use the most powerful GPT-4 based model, which is just plain slow. You can end up waiting for even up to a minute for the AI to complete its response.&lt;/p&gt;

&lt;p&gt;We have decades of experience building search engines that understand spelling mistakes, synonyms, and work in milliseconds. Going to a system that takes 10s of seconds to surface more or less the same information seems like a step backwards.&lt;/p&gt;

&lt;p&gt;But, &lt;em&gt;this is absolutely a short-term problem&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We have no doubt that OpenAI (and competitors?) will improve their algorithms, computational power will increase, and these models will become faster over time. But for now, we only have painfully slow assistants that make you think "forget it, I'll do it myself".&lt;/p&gt;

&lt;h3&gt;
  
  
  Trust issues
&lt;/h3&gt;

&lt;p&gt;Speed would not be a problem if the answers these systems produced were stellar. But the responses have not reached the helpfulness expected of ChatGPT.&lt;/p&gt;

&lt;p&gt;At the heart of this, we believe, are &lt;a href="https://en.wikipedia.org/wiki/Hallucination_(artificial_intelligence)"&gt;hallucinations&lt;/a&gt;. Hallucinations have been a well-known problem from the day ChatGPT was launched. In short: AIs will make things up.&lt;/p&gt;

&lt;p&gt;Luckily, hallucinations have &lt;u&gt;not&lt;/u&gt; been a huge issue with documentation chat AIs. Likely because they tend to set their &lt;a href="https://platform.openai.com/docs/api-reference/completions/create#completions/create-temperature"&gt;&lt;em&gt;temperature&lt;/em&gt;&lt;/a&gt; to, or close to 0. Temperature, in OpenAI's terminology, means how "random" the answer will be. From their documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With a low temperature the answers &lt;em&gt;seem&lt;/em&gt; to stick to the truth and be quick to say "I don't know" if it cannot find an answer in its provided context. Documentation is an area where incorrect answers can cause a lot of frustration. Since there isn't a human in the loop verifying the answers of these chat interfaces, companies tend to be conservative in the responses they want their AI to give.&lt;/p&gt;

&lt;p&gt;Still, in our testing we have seen documentation AIs produce bogus information. Supabase for this reason has the following disclaimer:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O-XqqMij--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kkyr2qggs6gxsrhzdhjo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O-XqqMij--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kkyr2qggs6gxsrhzdhjo.png" alt="Supabase chat disclaimer that the answers may not be correct" width="800" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The unfortunate net effect is that the answers are...bland. Low temperatures mean the model is unable to fill gaps in your documentation and reason about how your product works.&lt;/p&gt;

&lt;p&gt;To give an example, asking one of these documentation assistants if the tool they describe has a Ruby client, when it doesn't, will most of the time cause it to respond with "I don't know", instead of the more helpful "We don't have a Ruby client, but we do have official API wrappers in X, Y and Z languages".&lt;/p&gt;

&lt;p&gt;If the context the AI is provided does not verbatim have the information it needs to respond, it will not be able to give an answer. At which point, is this really better than search yet?&lt;/p&gt;

&lt;p&gt;Unlike with the speed issue, hallucinations and accuracy are problems that we have zero solutions for. We have no idea how to stop Large Language Models (LLMs) from lying, which does not bode well for &lt;a href="https://en.wikipedia.org/wiki/AI_alignment"&gt;AI alignment&lt;/a&gt; overall. It remains to be seen if we can thread the line between a system that can only repeat almost verbatim what is found in your content, and a more helpful assistant that is able to correctly infer more information from your documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Obvious potential
&lt;/h3&gt;

&lt;p&gt;It is easy to imagine how having a GPT-4 level chat assistant trained on your documentation could be incredibly useful. It could personalise examples on demand, write customised tutorials, and infer how to use your product from e.g. your &lt;a href="https://www.doctave.com/blog/documenting-rest-apis-with-openapi"&gt;OpenAPI specifications and documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Imagine being new to a library or product, telling an LLM you are familiar with XYZ technologies, and creating a customised learning path for you to get started based on techniques and technologies you already understand.&lt;/p&gt;

&lt;p&gt;Currently we have not seen this become a reality.&lt;/p&gt;

&lt;p&gt;In our experience, it is faster and more convenient to use traditional search and skim through documentation yourself to find the answers you are looking for.&lt;/p&gt;

&lt;p&gt;Interestingly, it is not possible for anyone but OpenAI (or its competitors) to fix these issues. All those startups built around making chat interfaces for your content will have to wait until the state of the art moves forward.&lt;/p&gt;

&lt;p&gt;That being said, we look forward to revisiting this opinion in 3 &lt;del&gt;days&lt;/del&gt; months!&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating documentation
&lt;/h2&gt;

&lt;p&gt;Ok, we started with the reading part. What about writing documentation? Here we see AI being much more helpful today and already making the lives of developers and technical writers easier.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(We will focus mostly on long-form technical writing here, instead of e.g. generating docstrings for your functions, which also works great.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We have seen companies like &lt;a href="https://www.jasper.ai/"&gt;Jasper&lt;/a&gt; pop up to help with AI-powered content generation (built on OpenAI, naturally), but Notion was one of the first companies to &lt;a href="https://www.notion.so/blog/notion-ai-is-here-for-everyone"&gt;integrate OpenAI's models into their own product&lt;/a&gt;. In Notion, the AI is an assistant: summarising, rewording, and improving your content on demand.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI-assisted technical writing
&lt;/h3&gt;

&lt;p&gt;All writing jobs were changed the moment ChatGPT was released. Documentation is no different. Assuming AI models don't take over technical writing completely (we'd likely have bigger disruptions at that point), how can we use AIs effectively in technical writing?&lt;/p&gt;

&lt;p&gt;There are some obvious cases that you can achieve even with just using the Chat GPT interface today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Summarising or expanding text&lt;/li&gt;
&lt;li&gt;Simplify or clarify your writing&lt;/li&gt;
&lt;li&gt;Maintaining a consistent tone of voice&lt;/li&gt;
&lt;li&gt;Generating guides from API references&lt;/li&gt;
&lt;li&gt;Format Markdown tables for us...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But can we do better? What could the future have in store?&lt;/p&gt;

&lt;h3&gt;
  
  
  Customised learning paths
&lt;/h3&gt;

&lt;p&gt;We often come back to this diagram (&lt;a href="https://documentation.divio.com/"&gt;source&lt;/a&gt;) when thinking about structuring documentation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kvqMa4PR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eeo1n5g2pj1wfktb5bqk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kvqMa4PR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eeo1n5g2pj1wfktb5bqk.png" alt="4 quadrants of documentation: tutorials, how-to guides, explanation and reference." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The idea is that your documentation should try to cover all 4 aspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tutorials&lt;/strong&gt;: for new users to learn the basics of your product&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How-to guides&lt;/strong&gt;: for users trying to solve specific problems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explanations&lt;/strong&gt;: to help users better understand the concepts behind your product&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;References&lt;/strong&gt;: for describing low level details of the system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where we see a lot of opportunities for LLMs to help are with the first 3 sections.&lt;/p&gt;

&lt;p&gt;LLMs could help produce more specialised how-to guides and tutorials than human writers can. Not only that, you could create &lt;em&gt;multiple versions of the same guide that match the background of the reader&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You have a Ruby background? Here is a Rust tutorial tailored for a Ruby programmer. Familiar with MySQL? Here is a MongoDB tutorial that explains its usage in terms you already understand.&lt;/p&gt;

&lt;p&gt;The reference is also an interesting case. You may have &lt;a href="///blog/2023/02/21/what-is-openapi.html"&gt;OpenAPI specifications&lt;/a&gt; or type signatures that describe the low level workings of your system. Well annotated references can be a great starting point for an LLM to start understanding how your product works in order for it to produce accurate and helpful content.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does the AI know?
&lt;/h3&gt;

&lt;p&gt;All of the above implies that the LLM understands how your product/tool/library works. How does it gain that knowledge?&lt;/p&gt;

&lt;p&gt;Currently, the only way is through, wait for it... documentation!&lt;/p&gt;

&lt;p&gt;Ironically, it is the quality of your documentation that will determine if an LLM can be a useful assistant for your product or not. Current techniques, as described above, require you to ship your documentation to the LLM in order for it to generate its response. If your prompt is poor quality, the LLM is going to do a poor job.&lt;/p&gt;

&lt;p&gt;This feels like a chicken-or-egg problem, but ultimately means that you cannot escape writing documentation. What is more likely is writers providing LLMs the context they need to assist in the generation of documentation: through annotated API specifications like OpenAPI or GraphQL, or longer written guides.&lt;/p&gt;

&lt;p&gt;This is of course another area of great uncertainty. Perhaps someone will create a system that lets LLMs interact with your tool to learn how it works? Or, could the model eventually understand your product by simply reading its source code? There are movements in both these directions, but it is too early to say if these experiments pan out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making your documentation "AI Ready"
&lt;/h2&gt;

&lt;p&gt;Anyone who claims they can see how the world will pan out is likely incredibly naive. But that never stopped anyone from speculating!&lt;/p&gt;

&lt;p&gt;It remains to be seen if chat interfaces will be useful, or simply a fad in the beginning of the AI revolution. Or if they do remain, &lt;strong&gt;&lt;em&gt;where&lt;/em&gt;&lt;/strong&gt; will the chatting happen?&lt;/p&gt;

&lt;p&gt;OpenAI announced &lt;a href="https://openai.com/blog/chatgpt-plugins"&gt;ChatGPT Plugins&lt;/a&gt; in March 2023, which let ChatGPT fetch information from external live data sources. Now you have one powerful LLM that is able to integrate with other tools and services.&lt;/p&gt;

&lt;p&gt;Will people be using multiple AI chat assistants, e.g. one per service, or just a single main assistant that knows how to talk to various data sources?&lt;/p&gt;

&lt;p&gt;Perhaps it will make more sense for your documentation to be "AI-ready", instead of you building your own chat interface. The task of the technical writer would be to ensure both humans &lt;em&gt;and&lt;/em&gt; AIs are able to interact with the documentation in an effective way.&lt;/p&gt;

&lt;p&gt;Regardless, what is quite probable is best practices emerging around how to make your documentation easily digestible by LLMs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to look out for in the future
&lt;/h2&gt;

&lt;p&gt;This is all incredibly speculative, and we live in a strange time. Depending on who you ask, we should be worried, or incredibly excited for a beautiful future where AIs make our world a better place. Documentation is a tiny part of this broader discussion, but it is interesting because it sits in the nexus of people playing with AI and written content. Things are moving fast.&lt;/p&gt;

&lt;p&gt;There are a few things to keep an eye on going forward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When will GPT-4 or equivalent models become significantly faster?&lt;/li&gt;
&lt;li&gt;Will we get better at limiting hallucinations allowing for more creative but accurate AI assistants?&lt;/li&gt;
&lt;li&gt;Will we end up using one or more AI assistants?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing seems clear for now: you still need to write the docs.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>writing</category>
      <category>softwareengineering</category>
      <category>devrel</category>
    </item>
    <item>
      <title>Should You Use Docs-as-Code?</title>
      <dc:creator>Anton Rautio</dc:creator>
      <pubDate>Thu, 11 May 2023 08:27:13 +0000</pubDate>
      <link>https://dev.to/doctave/should-you-use-docs-as-code-15l2</link>
      <guid>https://dev.to/doctave/should-you-use-docs-as-code-15l2</guid>
      <description>&lt;p&gt;So you've heard about docs-as-code and are wondering if it's right for your project. When deciding whether to implement docs-as-code for your documentation projects, it's essential to weigh the unique aspects of your project, such as quality standards, project requirements, and the nature of contributions. Understanding the benefits and requirements of docs-as-code will help you make an informed decision on whether it's the right approach for your specific needs. In this article we'll discuss some of the key considerations when deciding whether to use docs-as-code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Balancing Quality Standards and Project Requirements
&lt;/h2&gt;

&lt;p&gt;Every documentation project has unique quality standards and expectations. For internal documentation, the focus is often on accessibility and ease of use for company employees, making minor imperfections more acceptable.&lt;/p&gt;

&lt;p&gt;On the other hand, external documentation demands a higher level of precision, especially for technical software products like APIs that other developers rely on. In these cases, the documentation directly reflects the quality of the product itself, as there may be no other user interface. For Software-as-a-Service products, while documentation quality remains important, surgical precision and constant updates may not be as critical.&lt;/p&gt;

&lt;p&gt;Docs-as-code facilitates the creation of clear, accurate, and high-quality documentation by integrating objective quality standards and the review process.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement automated tests and validation tools to identify issues such as broken links, formatting inconsistencies, and content errors during the review process.&lt;/li&gt;
&lt;li&gt;Enhance the review and feedback process with version control and change tracking systems, similar to code repositories.&lt;/li&gt;
&lt;li&gt;Foster collaboration among developers, technical writers, and other stakeholders to ensure documentation accurately captures the product's features and functionality.&lt;/li&gt;
&lt;li&gt;Pair documentation with code changes, ensuring that documentation is updated as the code changes and their history is linked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Projects with any type of quality standards can benefit from docs-as-code as it's up to the project team to determine the level of quality they want to maintain, whether it's strict or a bit more relaxed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contribution Dynamics
&lt;/h2&gt;

&lt;p&gt;The advantages of docs-as-code become more apparent based on how people contribute to your project. Adopting docs-as-code is particularly beneficial when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have an extensive team of developers, technical writers, and other stakeholders contributing to the documentation.&lt;/li&gt;
&lt;li&gt;or, your project is open source, welcoming external contributions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The degree of docs-as-code implementation one should aim for depends on the contributors' profiles. When developers and other stakeholders without professional writing experience contribute to the project, it's crucial to employ automated quality checks and other safeguards to uphold documentation standards. When doing so, it is equally important that the rationale behind such checks is communicated clearly to avoid confusion.&lt;/p&gt;

&lt;p&gt;However, if your team consists of a smaller group of professional writers, these measures may be less of a priority.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluating Automation Potential
&lt;/h2&gt;

&lt;p&gt;Assessing the potential to automate aspects of your documentation process plays a crucial role in deciding whether to embrace docs-as-code.&lt;/p&gt;

&lt;p&gt;If your product incorporates &lt;a href="https://www.doctave.com/blog/2023/02/21/what-is-openapi.html"&gt;OpenAPI&lt;/a&gt; specifications, code comments, code examples, or other design artifacts, docs-as-code can save you time and minimize manual effort.&lt;/p&gt;

&lt;p&gt;Integrating the documentation process into your development workflow enables you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate API documentation automatically from OpenAPI specifications, guaranteeing accuracy and consistency throughout your documentation.&lt;/li&gt;
&lt;li&gt;Extract documentation from code comments or annotations, decreasing duplication and keeping the documentation current as the code changes.&lt;/li&gt;
&lt;li&gt;Derive code examples directly from your source code, offering users precise and relevant examples that mirror your product's current state.&lt;/li&gt;
&lt;li&gt;Automatically track documentation tasks in version-control linked trackers such as Linear so that they can be handled in a way similar to other product-related tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By evaluating the automation potential in your documentation project, you can better determine if docs-as-code is the right approach for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Is Docs-as-Code Right for Your Project?
&lt;/h2&gt;

&lt;p&gt;Docs-as-code offers a powerful solution for maintaining and enhancing your documentation quality. To determine if it's the right fit, consider your project's unique requirements, contribution dynamics, and automation potential. While not an exhaustive list, the following scenarios can help guide your decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your project depends on design artifacts like OpenAPI specifications, code comments, or code examples, and automation could significantly reduce manual effort when keeping your docs up to date.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You're working on a project with specific quality standards and a diverse team of contributors. Automated quality checks, validation tools, and a robust review process are essential for preserving documentation quality.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You have a sizable engineering team that requires accurate internal documentation for various interfaces. Developers are the primary contributors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You're managing an open-source project with a diverse range of contributors, where efficient collaboration, version control, and review processes are crucial.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your project includes maintaining and updating documentation for multiple versions of a product separately. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider adopting docs-as-code if any of these scenarios apply to your project. If multiple points resonate, you should definitely implement docs-as-code.&lt;/p&gt;

</description>
      <category>writing</category>
      <category>discuss</category>
      <category>api</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Documenting REST APIs with OpenAPI</title>
      <dc:creator>Nik Begley</dc:creator>
      <pubDate>Tue, 09 May 2023 13:13:55 +0000</pubDate>
      <link>https://dev.to/doctave/documenting-rest-apis-with-openapi-473d</link>
      <guid>https://dev.to/doctave/documenting-rest-apis-with-openapi-473d</guid>
      <description>&lt;p&gt;Most APIs today are designed as RESTful APIs. &lt;a href="https://en.wikipedia.org/wiki/Representational_state_transfer"&gt;REST&lt;/a&gt; (Representational State Transfer) is an architectural style for designing networked applications. RESTful APIs use HTTP to expose &lt;em&gt;resources&lt;/em&gt; and perform operations on them using standard HTTP verbs, such as &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;GET&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this post, we'll explore how to document RESTful APIs using &lt;a href="https://www.doctave.com/blog/2023/02/21/what-is-openapi.html"&gt;OpenAPI&lt;/a&gt;, an industry-standard specification for describing, producing, consuming, and visualizing RESTful web services. We'll focus on documenting a single operation, its components, and the tools OpenAPI provides to create comprehensive documentation.&lt;/p&gt;

&lt;p&gt;In this post we'll expect that you have a tool that is able to produce a documentation site for you given an OpenAPI spec. &lt;a href="https://www.doctave.com/features"&gt;Doctave supports OpenAPI&lt;/a&gt; but these concepts are transferable to any tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is in an operation?
&lt;/h2&gt;

&lt;p&gt;In the context of a RESTful API, an operation refers to a single action you can perform. Each operation consists of the following components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP Verb (such as &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A URI path (such as &lt;code&gt;/users&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Parameters&lt;/li&gt;
&lt;li&gt;A request body&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these components, you can invoke the operation and receive a response. The response itself contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A response body&lt;/li&gt;
&lt;li&gt;A status code&lt;/li&gt;
&lt;li&gt;A content type&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  OpenAPI operation model
&lt;/h2&gt;

&lt;p&gt;OpenAPI provides a standardized model for defining operations in RESTful APIs. In an OpenAPI document, operations are organized under the paths field. The paths field defines all possible URI paths for the API, and each path can have operations associated with different HTTP verbs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- Top level `paths` field&lt;/span&gt;
  &lt;span class="na"&gt;/pets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- The URI path for the operation&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- The HTTP verb&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Returns all pets from the system&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- The response for a 200 OK status&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A list of pets.&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;array&lt;/span&gt;
                &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Pet"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;em&gt;(You can find all the examples used here in the official &lt;a href="https://github.com/OAI/OpenAPI-Specification/tree/main/examples/v3.0"&gt;OpenAPI GitHub Repo&lt;/a&gt;)&lt;/em&gt;&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Building on the this example, the &lt;code&gt;/pets&lt;/code&gt; path is defined to handle a &lt;code&gt;GET&lt;/code&gt; request. When a &lt;code&gt;GET&lt;/code&gt; request is issued, it returns a list of objects describing the available pets. We can see here the main components of an operation: the URI path, the HTTP verb, and the response. Notice that in this case, the operation does not require any parameters or a request body.&lt;/p&gt;

&lt;p&gt;For a more in-depth understanding of the different parts of OpenAPI, you can refer to the &lt;a href="https://swagger.io/specification/v3/"&gt;official OpenAPI specification&lt;/a&gt;. While it may be verbose, it serves as a valuable reference for comprehending how OpenAPI components work together.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where does the documentation go?
&lt;/h2&gt;

&lt;p&gt;OpenAPI was luckily built with documentation in mind. After all, it's designed to be used for generating human readable documentation, among other use cases. Most OpenAPI objects include a &lt;code&gt;description&lt;/code&gt; field. You can see some in the example above! This field is your friend and a valuable tool for writing detailed explanations of the corresponding object&lt;/p&gt;

&lt;p&gt;Additionally, you can use &lt;a href="https://www.doctave.com/blog/markdown-cheat-sheet"&gt;Markdown&lt;/a&gt;! The OpenAPI spec specifically states that &lt;code&gt;description&lt;/code&gt; should support the CommonMark Markdown flavor. This means you can include clickable links, lists, and anything else you can describe in Markdown.&lt;/p&gt;

&lt;p&gt;Some objects also feature a &lt;code&gt;summary&lt;/code&gt; field, intended for concise descriptions of the operation's purpose.&lt;/p&gt;

&lt;p&gt;Beyond these fields, it's important to consider clear and consistent naming for the objects and operations themselves in your API. It will help readers search and skim your docs quickly to find the sections they are looking for.&lt;/p&gt;
&lt;h2&gt;
  
  
  Parameters
&lt;/h2&gt;

&lt;p&gt;If an operation is like a function in a programming language, parameters are like function arguments.&lt;/p&gt;

&lt;p&gt;There are four of ways to pass parameters in an operation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query parameters&lt;/li&gt;
&lt;li&gt;Path parameters&lt;/li&gt;
&lt;li&gt;Headers&lt;/li&gt;
&lt;li&gt;Cookies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Parameters are defined in the operation's &lt;code&gt;parameters&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;/pets/{petId}&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;            &lt;span class="c1"&gt;# &amp;lt;- Note the URI path with a placeholder&lt;/span&gt;
  &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Info for a specific pet&lt;/span&gt;
    &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;showPetById&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pets&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;            &lt;span class="c1"&gt;# &amp;lt;- List of parameters for this operation&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;petId&lt;/span&gt;
        &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The id of the pet to retrieve&lt;/span&gt;
        &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above operation says you can request details of a specific pet by specifying the pet ID as a &lt;em&gt;path parameter&lt;/em&gt;. The &lt;code&gt;in&lt;/code&gt; field for the parameter defines how to pass the parameter to the operation. It can be one of &lt;code&gt;query&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt;, &lt;code&gt;header&lt;/code&gt;, or &lt;code&gt;cookie&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Additionally, each parameter can have an associated schema. Generally, these schemas are simple, as parameters are often IDs or short strings. More on these shortly!&lt;/p&gt;

&lt;h3&gt;
  
  
  Query and Path Parameters
&lt;/h3&gt;

&lt;p&gt;Let's look at query and path parameters together. Since they are very similar.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Query parameters&lt;/em&gt;

&lt;ul&gt;
&lt;li&gt;parameters at the end of the URI, after a &lt;code&gt;?&lt;/code&gt; as a series of key value pairs&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Path parameters&lt;/em&gt;

&lt;ul&gt;
&lt;li&gt;parameters in the &lt;code&gt;path&lt;/code&gt; section of the URI&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a (slighty contrived) example operation for querying the history of a pet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/pets/{petId}/history?page=2
      ───┬───         ───┬──
  path ──┘               │
 query ──────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;petId&lt;/code&gt; &lt;em&gt;path parameter&lt;/em&gt; is part of the URI path. Commonly these parameters are used when referencing a specific resource such as a user. Usually the parameter is in the form of a unique ID, such as an integer, or a unique username.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;query parameter&lt;/em&gt; in this case is the &lt;code&gt;page=2&lt;/code&gt; section. Query parameters are often used for pagination and filtering results. Although query parameters can be used to identify resources, it's not a common practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Headers
&lt;/h3&gt;

&lt;p&gt;Headers can be thought of as &lt;em&gt;metadata&lt;/em&gt; about the request. They are additional information sent along with the request, such as the User Agent, which determines what kind of client is making the request.&lt;/p&gt;

&lt;p&gt;You will most likely encounter headers in reference to API authentication (see below), but there are other uses as well, such as specifying in what format the payload you are sending is, or what format you would like to receive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cookies
&lt;/h3&gt;

&lt;p&gt;Cookies are less commonly used in HTTP APIs, but they may still appear in some cases. Similar to headers, cookies are often used by browsers for tasks like user authentication. However, in the context of APIs, their usage is relatively rare, as they go against the RESTful principle of statelessness.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAPI schemas
&lt;/h2&gt;

&lt;p&gt;Before moving on, let's address a key question:&lt;/p&gt;

&lt;p&gt;How do you actually describe what the values of these various parameters should be? Can we specify that the &lt;code&gt;page&lt;/code&gt; query parameter has to be an integer greater than zero? This is where OpenAPI &lt;em&gt;schemas&lt;/em&gt; come in.&lt;/p&gt;

&lt;p&gt;If we look at one of the previous examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;/pets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Returns all pets from the system&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A list of pets.&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- NOTE the schema here&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;array&lt;/span&gt;
                &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Pet"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On line 10, we state that the response should have a particular schema‚Äîan array of pets. The schema's &lt;code&gt;item&lt;/code&gt; points to another part of the spec to a shared pet schema component. Let's see what that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Pet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;id&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;integer&lt;/span&gt;
          &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;int64&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The name of the pet&lt;/span&gt;
        &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're talking! This is what a pet looks like.&lt;/p&gt;

&lt;p&gt;The pet has a &lt;code&gt;type&lt;/code&gt; of &lt;code&gt;object&lt;/code&gt;. This means it has a number of properties, or fields. In this case we have &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, and &lt;code&gt;tag&lt;/code&gt; properties for this pet object.&lt;/p&gt;

&lt;p&gt;This is where OpenAPI schemas gets really cool: they can be &lt;em&gt;nested&lt;/em&gt;. &lt;code&gt;Pet&lt;/code&gt; is a schema defining an &lt;code&gt;object&lt;/code&gt;, and the properties each define child schemas. The &lt;code&gt;id&lt;/code&gt; property is a schema with a &lt;code&gt;type&lt;/code&gt; of &lt;code&gt;integer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This allows for the definition of complex, deeply nested objects.&lt;/p&gt;

&lt;p&gt;Also, each schema can include the previously described &lt;code&gt;description&lt;/code&gt; field. Which means you can document every property as required.&lt;/p&gt;

&lt;p&gt;You can also specify all kinds of other requirements and constraints in schemas. A non-exhaustive list includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maximum and minimum values (for numeric types)&lt;/li&gt;
&lt;li&gt;Maximum and minimum length (for string types)&lt;/li&gt;
&lt;li&gt;A list of possible values (enums)&lt;/li&gt;
&lt;li&gt;A regular expression pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I highly recommend reading the official &lt;a href="https://swagger.io/docs/specification/data-models/"&gt;OpenAPI guides on schemas&lt;/a&gt; for more examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request Body
&lt;/h2&gt;

&lt;p&gt;Ok, with the schema detour out of the way, lets move onto request bodies, where schemas play a big part!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, and &lt;code&gt;UPDATE&lt;/code&gt; HTTP requests usually contain a &lt;em&gt;request body&lt;/em&gt;. This is the payload you want to send to the server along with the request. It will most likely be JSON encoded string, though other formats are possible.&lt;/p&gt;

&lt;p&gt;As with parameters, the request body is in fact one large schema definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;/pets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Creates a new pet in the store. Duplicates are allowed&lt;/span&gt;
    &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pet to add to the store&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/NewPet"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we again refer to a shared component schema to define the shape of the request body, this time called &lt;code&gt;NewPet&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;NewPet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks very similar to the parameter examples. Turns out OpenAPI is just schemas all the way down.&lt;/p&gt;

&lt;p&gt;One thing to note about request bodies is that they are placed under a &lt;em&gt;content type&lt;/em&gt;. In this case, it's &lt;code&gt;application/json&lt;/code&gt;, meaning that the request body should be encoded as JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responses
&lt;/h2&gt;

&lt;p&gt;Responses are what the operation returns to you under various conditions. Just like the request body, the response shape is defined using schemas.&lt;/p&gt;

&lt;p&gt;Since the server can return multiple different statuses, you can define schemas for each of the HTTP status codes. For example, if you are creating a resource by calling the operation, you should return a 201 status. This in itself documents that something was created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;/pets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Creates a new pet in the store. Duplicates are allowed&lt;/span&gt;
    &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;addPet&lt;/span&gt;
    &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pet to add to the store&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/NewPet"&lt;/span&gt;
    &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- NOTE responses here&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;201"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pet response&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Pet"&lt;/span&gt;
    &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unexpected error&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Error"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we see that creating a pet has two possible responses: a pet, or an error. A 201 status will return the pet, but any other status code (using the &lt;code&gt;default&lt;/code&gt; catch-all) will return the error.&lt;/p&gt;

&lt;p&gt;Again, just as with request bodies, the schemas are located under the content type of the response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Errors
&lt;/h3&gt;

&lt;p&gt;It's worth talking for a moment about errors in general.&lt;/p&gt;

&lt;p&gt;Proper error handling is important in order to have a developer-friendly API. If your API spews out a generic &lt;code&gt;an error occurred, try again&lt;/code&gt; message when the inputs are not just right, you will have a lot of frustrated users.&lt;/p&gt;

&lt;p&gt;OpenAPI allows you to document error responses alongside your successful responses too. Errors work just like regular responses. Consider thinking carefully what your various &lt;code&gt;4xx&lt;/code&gt; status responses return, and documenting them along with your happy paths.&lt;/p&gt;

&lt;p&gt;It's also important to use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status"&gt;proper status codes&lt;/a&gt; for the type of error you are returning. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;403 when accessing a resource the user is not authorized for&lt;/li&gt;
&lt;li&gt;404 when the resource does not exist&lt;/li&gt;
&lt;li&gt;418 when the server is a
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418"&gt;teapot&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider also returning a human-readable description of the error in the body of the error response. It can actually limit your support load and let people debug their issues themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;p&gt;Something we have not yet mentioned about request bodies and responses are examples. You can give concrete examples of what the body of a request should look like for each case.&lt;/p&gt;

&lt;p&gt;Here's a (verbose) example (pun intended):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;/&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;listVersionsv2&lt;/span&gt;
    &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List API versions&lt;/span&gt;
    &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
          &lt;span class="s"&gt;200 response&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;examples&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- Example responses for this status&lt;/span&gt;
              &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="pi"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;versions"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                      &lt;span class="pi"&gt;[&lt;/span&gt;
                        &lt;span class="pi"&gt;{&lt;/span&gt;
                          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CURRENT"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;updated"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2011-01-21T11:33:21Z"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v2.0"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;links"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                            &lt;span class="pi"&gt;[&lt;/span&gt;
                              &lt;span class="pi"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;href"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:8774/v2/"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rel"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;self"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                              &lt;span class="pi"&gt;},&lt;/span&gt;
                            &lt;span class="pi"&gt;],&lt;/span&gt;
                        &lt;span class="pi"&gt;},&lt;/span&gt;
                        &lt;span class="pi"&gt;{&lt;/span&gt;
                          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EXPERIMENTAL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;updated"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2013-07-23T11:33:21Z"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v3.0"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;links"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                            &lt;span class="pi"&gt;[&lt;/span&gt;
                              &lt;span class="pi"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;href"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:8774/v3/"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rel"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;self"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                              &lt;span class="pi"&gt;},&lt;/span&gt;
                            &lt;span class="pi"&gt;],&lt;/span&gt;
                        &lt;span class="pi"&gt;},&lt;/span&gt;
                      &lt;span class="pi"&gt;],&lt;/span&gt;
                  &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here under the &lt;code&gt;examples&lt;/code&gt; field we can list one or more examples. As you can see, this works by including the literal response JSON in the spec.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;em&gt;(Fun fact: JSON is actually valid YAML.)&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;You can define multiple examples for a single status to illustrate different use cases or scenarios. In the above example we only have one example: the terribly named &lt;code&gt;foo&lt;/code&gt; example. But you could instead of have properly named examples that describe the different cases in your domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;Finally, let's talk about authentication. We previously mentioned that API authentication usually happens with header parameters. But because there are more complex authentication mechanisms, and since authentication is core to almost every API available, OpenAPI has &lt;a href="https://swagger.io/docs/specification/authentication/"&gt;specific methods&lt;/a&gt; for describing how to authenticate against your API.&lt;/p&gt;

&lt;p&gt;In OpenAPI's terminology, these methods are called &lt;em&gt;security schemes&lt;/em&gt;. They define the authentication process for your API. There are 4 types of security schemes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic Authentication&lt;/li&gt;
&lt;li&gt;API Keys / JWT Bearer Token&lt;/li&gt;
&lt;li&gt;OAuth2&lt;/li&gt;
&lt;li&gt;OpenID Connect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We won't go into the specifics of these schemes in this post. I will just mention that security schemes also support the &lt;code&gt;description&lt;/code&gt; field. Some of these authentication mechanism can be complicated and require multiple steps to configure. It is usually a good idea to refer to a longer tutorial in your description that you have written outside of the OpenAPI spec itself.&lt;/p&gt;

&lt;p&gt;Authentication is often the first thing a new user has to deal with in order to get started with an API, so having solid documentation in this area is worth it.&lt;/p&gt;

&lt;h3&gt;
  
  
  OAuth2 Scopes
&lt;/h3&gt;

&lt;p&gt;One thing to mention around operations is &lt;em&gt;OAuth2 scopes&lt;/em&gt;. OAuth2 supports the concept of "scopes", which are similar to permissions around what the user can do once authenticated.&lt;/p&gt;

&lt;p&gt;OpenAPI operations can specify &lt;em&gt;which scopes they require&lt;/em&gt; in order to be called. If the authenticated caller does not have the required scopes, the operation should return an error and not give the called access.&lt;/p&gt;

&lt;p&gt;Here's a brief example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# First we define an OAuth2 security scheme&lt;/span&gt;
&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;securitySchemes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;OAuth2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oauth2&lt;/span&gt;
      &lt;span class="na"&gt;flows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;authorizationCode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;authorizationUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://example.com/oauth/authorize&lt;/span&gt;
          &lt;span class="na"&gt;tokenUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://example.com/oauth/token&lt;/span&gt;
          &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Grants read access&lt;/span&gt;
            &lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Grants write access&lt;/span&gt;
            &lt;span class="na"&gt;admin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Grants access to admin operations&lt;/span&gt;

&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;/billing_info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Only admins can view billing info&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Gets the account billing info&lt;/span&gt;
      &lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;OAuth2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;admin&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- Note the required admin scope&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OK&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;401"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Not authenticated&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;403"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Access token does not have the required scope&lt;/span&gt;
  &lt;span class="na"&gt;/ping&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checks if the server is running&lt;/span&gt;
      &lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- No scopes required. Everyone can access&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Server is up and running&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Something is wrong&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scopes are defined as part of the OAuth2 security scheme. Each scope can have an associated description, which you should use to describe the purpose of the scope.&lt;/p&gt;

&lt;p&gt;Note the descriptive error statuses for the &lt;code&gt;/billing_info&lt;/code&gt; operation. If your authentication token is missing, or you don't have the required scopes, you get an appropriate error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;OpenAPI is a fairly complicated specification, but it can be incredibly powerful for documenting your REST APIs.&lt;/p&gt;

&lt;p&gt;Key points to remember when working with OpenAPI include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Understanding what goes into calling an operation&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's important to know what is required in to invoke the operations in your API: how to authenticate against it, and what parameters to provide.&lt;/p&gt;

&lt;p&gt;These need to be made clear in your documentation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Using &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;summary&lt;/code&gt; fields effectively&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This will let your documentation generator produce effective and readable documentation sites. Also consider linking to longer explanations from your &lt;code&gt;description&lt;/code&gt; fields when required.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Providing real examples for request bodies and responses&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing beats a real example. Even better if you can provide your user an example that they can copy-paste into their terminal to start playing around.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Using HTTP correctly and returning approriate errors&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HTTP itself can be quite descriptive. Work with the engineers designing the API to make sure you are using status codes and error messages effectively.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>Selling to Developers: Your Documentation Is a Competitive Advantage</title>
      <dc:creator>Nik Begley</dc:creator>
      <pubDate>Mon, 27 Feb 2023 12:33:23 +0000</pubDate>
      <link>https://dev.to/doctave/selling-to-developers-your-documentation-is-a-competitive-advantage-54cb</link>
      <guid>https://dev.to/doctave/selling-to-developers-your-documentation-is-a-competitive-advantage-54cb</guid>
      <description>&lt;p&gt;Usually people think of technical documentation as helping existing users. Documentation lets customers find answers to questions themselves. It reduces load on your support team. But when you are selling to developers, your technical documentation is a &lt;strong&gt;&lt;em&gt;sales tool&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your ability to win customers will depend on how developers perceive your documentation, and thus your product. Bad documentation leads to lost sales. But great documentation can make you stand out in the market and help you win over developers and close deals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't make me talk to a human
&lt;/h2&gt;

&lt;p&gt;Developers have become an important buyer in modern tech organisations. Unlike many other buyers, the customer journey software engineers take is &lt;strong&gt;self-serve&lt;/strong&gt;. This means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;They do not want to talk to a sales person&lt;/li&gt;
&lt;li&gt;They want to try it before they buy it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In one survey, &lt;strong&gt;57% of developers said they wouldn't reach out for a sales demo&lt;/strong&gt; &lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Looking at the best companies that sell to developers, they make it easy for customers to buy how they want. &lt;strong&gt;89% of developer-first Cloud 100 companies offer a freemium product or a free trial&lt;/strong&gt; &lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;They give their prospects the ability to try out their product and form an opinion themselves. This is where your documentation comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building trust
&lt;/h2&gt;

&lt;p&gt;Documentation builds trust with developers. If they buy your product, they will be spending hours or even months interacting with your service/API/SDK. Good documentation reassures developers. It lets them know they will not have to contact a support agent when they have an issue or something is unclear in your product. It is also an indirect sign of your product quality. If your documentation is sub-par, is your product that good either? &lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stripe.com/docs"&gt;Stripe&lt;/a&gt; is the canonical example of a company with world-class technical documentation. So much so, that it has almost become a meme at this point. Every developer-tools company wants to have docs like Stripe. Their whole brand of being developer-friendly is built upon excellent documentation (and a product to match, of course). Building a payments integration can be hard. But they know that Stripe is a solid service that will guide them through the process.&lt;/p&gt;

&lt;p&gt;If your documentation is unclear, hard to read, full of obvious mistakes or outdated screenshots, developers will turn away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guiding towards the "Aha-moment"
&lt;/h2&gt;

&lt;p&gt;Another key function of your documentation is guiding developers on how to get started. It is important to help your users get to the point in your product where they have the &lt;em&gt;"aha-moment"&lt;/em&gt; where they understand the value you can provide.&lt;/p&gt;

&lt;p&gt;For any even slightly complex product, it is hard to get your users to this point through only an onboarding flow. You need tutorials and guides that help new users navigate your product.&lt;/p&gt;

&lt;p&gt;In other words, you need good docs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Docs are like air. It's impossible for a developer to be successful without them&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;- &lt;em&gt;Andrew Baker, Twilio Director of Developer Education &lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.twilio.com/docs"&gt;Twilio&lt;/a&gt; is another company that prides itself on having high quality documentation. They provide &lt;a href="https://www.twilio.com/docs/sms/quickstart"&gt;"Quickstart"&lt;/a&gt; guides to introduce developers to various parts of their product. They have &lt;a href="https://www.twilio.com/docs/tutorials"&gt;goal-based tutorials&lt;/a&gt; that walk through real examples of building a full Twilio-integration for different use cases.&lt;/p&gt;

&lt;p&gt;Everything is pushing a developer new to the platform towards success.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docs in sales
&lt;/h2&gt;

&lt;p&gt;When I said developers never want to talk to salespeople, this is only partly true. Large enterprises with a complex list of requirements will still expect to talk to a human. But only after their developers have first developed an understanding of your offering on their own.&lt;/p&gt;

&lt;p&gt;Even Atlassian, which at times claimed to have no sales staff, does sales:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're a large enterprise customer that has more complexity, or&lt;br&gt;
potentially more value to us, we have a team that can help steer you in the&lt;br&gt;
right direction and answer a more complicated set of questions that you have.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;- &lt;em&gt;Jay Simmons, Atlassian President &lt;sup id="fnref4"&gt;4&lt;/sup&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Our customers tell us documentation is something their sales representatives reference constantly on calls. We have even heard of companies giving early access to feature documentation privately to some customers, before releasing the feature publicly.&lt;/p&gt;

&lt;p&gt;Documentation helps close deals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;If you are selling to developers, your documentation has to be a high priority. Developers want to try out products themselves, and you have to be able to support them with proper guides, tutorials, and references. Having world-class documentation will make you stand out in the market.&lt;/p&gt;

&lt;p&gt;Here are a few takeaways about how to improve your docs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make sure someone is responsible for the quality of your documentation&lt;/li&gt;
&lt;li&gt;Have proper analytics to measure how people are consuming your documentation&lt;/li&gt;
&lt;li&gt;Consider &lt;a href="https://www.doctave.com/blog/2021/08/30/why-you-should-consider-docs-as-code.html"&gt;docs-as-code&lt;/a&gt; if you want your developers to be able to contribute to docs easily&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://offers.openviewpartners.com/developerfocusedgtmplaybook"&gt;&lt;em&gt;OpenView: The developer-focused go-to-market playbook&lt;/em&gt;&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Broken_windows_theory"&gt;&lt;em&gt;Broken windows theory&lt;/em&gt;&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://www.decibel.vc/content/how-to-build-developer-loyalty-with-docs-and-dev-ed"&gt;&lt;em&gt;Lessons from Twilio: How To Build Developer Loyalty with Docs and Dev Ed&lt;/em&gt;&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;&lt;a href="https://www.intercom.com/blog/podcasts/scale-how-atlassian-built-a-20-billion-dollar-company-with-no-sales-team"&gt;&lt;em&gt;How Atlassian built a $20 billion company with a unique sales model&lt;/em&gt;&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>devrel</category>
      <category>writing</category>
      <category>leadership</category>
    </item>
    <item>
      <title>What is OpenAPI? Examples, Purpose &amp; Advantages</title>
      <dc:creator>Nik Begley</dc:creator>
      <pubDate>Wed, 22 Feb 2023 13:37:40 +0000</pubDate>
      <link>https://dev.to/doctave/what-is-openapi-examples-purpose-advantages-35e7</link>
      <guid>https://dev.to/doctave/what-is-openapi-examples-purpose-advantages-35e7</guid>
      <description>&lt;p&gt;&lt;a href="https://swagger.io/specification/" rel="noopener noreferrer"&gt;OpenAPI&lt;/a&gt;, previously known as "Swagger", is a specification for describing HTTP APIs. Since its initial release in 2011, it has become a commonly adopted tool for companies to generate documentation and client implementations for their endpoints.&lt;/p&gt;

&lt;p&gt;In this post we will go over how it works, some examples, and the advantages (and disadvantages) of using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The very basics
&lt;/h2&gt;

&lt;p&gt;When you encounter an OpenAPI spec, it is either in JSON or Yaml format. I will look something like this condensed example from OpenAPI's own docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"openapi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Swagger Petstore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MIT"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://petstore.swagger.io/v1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"/pets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"List all pets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c"&gt;/* Removed for brevity */&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Create a pet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c"&gt;/* Removed for brevity */&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"/pets/{petId}"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Info for a specific pet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c"&gt;/* Removed for brevity */&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this example describes is a "PetStore" API. It is accessible at the domain &lt;code&gt;http://petstore.swagger.io/v1&lt;/code&gt;, it is currently at version &lt;code&gt;1.0.0&lt;/code&gt;, and it exposes 2 URL paths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;/pets&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/pets/{petId}&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first path, &lt;code&gt;pets&lt;/code&gt;, handles 2 different HTTP methods: &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt;. The first lists all the available pets, and the second one creates a new pet (a very strange API, we will admit).&lt;/p&gt;

&lt;p&gt;The second path only responds to the &lt;code&gt;GET&lt;/code&gt; HTTP method, in which case it returns information about a &lt;em&gt;specific&lt;/em&gt; pet, and it expects the URL to include the &lt;em&gt;ID&lt;/em&gt; of the pet you are asking for.&lt;/p&gt;

&lt;p&gt;For brevity, I have removed some details from this example. But you can see the the whole spec &lt;a href="https://github.com/OAI/OpenAPI-Specification/blob/36a3a67264cc1c4f1eff110cea3ebfe679435108/examples/v3.0/petstore.json" rel="noopener noreferrer"&gt;here&lt;/a&gt;. In it, you can see that the spec goes into detail about what the shape of the data it returns is, what errors can be occur, and much more.&lt;/p&gt;

&lt;p&gt;This, in short is what OpenAPI is: a format for describing your API down to the smallest detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is it used for?
&lt;/h2&gt;

&lt;p&gt;There are 2 main uses for using OpenAPI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Documenting and communicating to your users how your API works&lt;/li&gt;
&lt;li&gt;Automatically generating code that can interact with your API&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Documentation
&lt;/h3&gt;

&lt;p&gt;Since an OpenAPI spec tells you exactly how your API works, we can use it to generate nice looking and easy to read documentation.&lt;/p&gt;

&lt;p&gt;This is in fact what Doctave does - you can include your OpenAPI description and Doctave generates documentation automatically from your spec. Here is an example where &lt;a href="https://github.com/github/rest-api-description" rel="noopener noreferrer"&gt;GitHub's OpenAPI spec&lt;/a&gt; is rendered in Doctave.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7twsgrza9qrqr6uqp0a2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7twsgrza9qrqr6uqp0a2.png" alt="Screenshot of GitHub's OpenAPI spec rendered in Doctave showing a the docs of a GET operation listing repositories starred by the authenticated user." width="800" height="652"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This can be incredibly useful for your colleagues that may be writing code against an API you have designed, or customers using your product.&lt;/p&gt;

&lt;p&gt;There are a whole host of &lt;a href="https://openapi.tools/" rel="noopener noreferrer"&gt;OpenAPI tools&lt;/a&gt; ranging from documentation to code generation to linting and validating your specs. It is worth browsing around to see what is out there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating API clients
&lt;/h3&gt;

&lt;p&gt;In theory, if we have a well documented API, and know all the types in the responses as well as the available operations, we should be able to write a program that generates another program that can be used to interact with the API in question.&lt;/p&gt;

&lt;p&gt;This is where the &lt;a href="https://github.com/OpenAPITools/openapi-generator" rel="noopener noreferrer"&gt;OpenAPI Generator&lt;/a&gt; tool comes in. It is able to generate client code in over 50 languages just by providing it an OpenAPI specification.&lt;/p&gt;

&lt;p&gt;It goes even further and is able to generate &lt;em&gt;server stubs&lt;/em&gt; that you can use as a starting point for generating a compliant service implementing an API contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating OpenAPI specifications
&lt;/h2&gt;

&lt;p&gt;How does one go about creating an OpenAPI spec in the first place? How do you get that JSON or Yaml file that describes your API? You have two options:&lt;/p&gt;

&lt;p&gt;1. &lt;strong&gt;Generate it automatically from your server code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are tools that will look at the code running on your server and create an OpenAPI file based on it. This can be a great way to get started.&lt;/p&gt;

&lt;p&gt;2. &lt;strong&gt;Craft it by hand&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you need more control, you can write the spec by hand. You don't have to do it all in your text editor though - there are visual and even collaborative tools for managing your OpenAPI specs.&lt;/p&gt;

&lt;p&gt;There is no real right or wrong answer about which way is correct, but here are some arguments for and against both:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto generation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Your spec will always be up to date&lt;/li&gt;
&lt;li&gt;✅ Can sometimes require very little work from developers to set up&lt;/li&gt;
&lt;li&gt;❌ It can be easy to accidentally introduce breaking changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Crafting by hand&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Forces you to think carefully about API changes&lt;/li&gt;
&lt;li&gt;❌ Can be more work (can be assisted by proper tooling)&lt;/li&gt;
&lt;li&gt;❌ You have to ensure your server is compatible with your spec yourself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a general rule, if you are descrbing an API that is consumed internally by others in your company, auto generating your specification can be a quick way to describe your API.&lt;/p&gt;

&lt;p&gt;However if your customers are using your API, you need to be much more deliberate with breaking changes and API design over all. In this case, hand crafting your spec and running tests that ensure your service is compliant can be a better option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages and disadvantages of OpenAPI
&lt;/h2&gt;

&lt;p&gt;Communicating how your API works is a hard requirement for anyone to actually use it. This is the problem OpenAPI tries to solve.&lt;/p&gt;

&lt;p&gt;The big benefit of OpenAPI is that &lt;em&gt;it is a standard&lt;/em&gt;. There is a large set of tools that know the format and having an OpenAPI spec lets you and your users take advantage of those tools. Documentation, client generation, and much more become easier once you have a well specified API.&lt;/p&gt;

&lt;p&gt;But as the saying goes, there is no free lunch. So what are some issues with OpenAPI that we should be aware or?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Auto generation is not enough&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You would be wrong to think that generating your OpenAPI spec from your server code is enough documentation for your users to get started.&lt;/p&gt;

&lt;p&gt;This is why many sections in the OpenAPI spec include a &lt;code&gt;description&lt;/code&gt; field which according to the spec can include Markdown. The designers of OpenAPI understood that examples must have written guides and examples to make the spec useful and gave authors the ability to add written prose to guide their users.&lt;/p&gt;

&lt;p&gt;This means &lt;em&gt;you will have have to spend time adding explanations in your spec, even if you auto generate a lot of it.&lt;/em&gt; This is less of an issue with OpenAPI itself, but something you should keep in mind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. You need to provide &lt;em&gt;a lot&lt;/em&gt; of detail&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is easy to underestimate the amount of work that goes into describing your API. If you look at the screenshot above, the operation has a lot of detail: default values for parameters, example responses, a hand-written description.&lt;/p&gt;

&lt;p&gt;Doing this for &lt;em&gt;all&lt;/em&gt; of your operations is not a small task and you likely will not get the level of detail you need from just auto generating things from code. You will have to spend time annotating your code to give OpenAPI high quality hints about how your API works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Some languages are not well supported&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are using a slightly more esoteric language, do not expect client code generation or producing OpenAPI specs from your servers to work flawlessly.&lt;/p&gt;

&lt;p&gt;Java, Go, Python, Ruby, JavaScript, and other "big" languages have well tested integrations, but your mileage may vary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Not a great fit for event-driven architectures&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your API uses asynchronous messages heavily, OpenAPI does not provide great primitives for describing them.&lt;/p&gt;

&lt;p&gt;Instead, you should consider looking into &lt;a href="https://www.asyncapi.com/" rel="noopener noreferrer"&gt;AsyncAPI&lt;/a&gt;, which grew out of OpenAPI to address this shortcoming.&lt;/p&gt;

&lt;p&gt;Ultimately OpenAPI has proved itself to be a valuable tool by becoming the industry standard for describing HTTP APIs. It is a great tool that does its job very well when used correctly.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>softwaredevelopment</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Why we built a Rust-powered desktop app for previewing documentation</title>
      <dc:creator>Nik Begley</dc:creator>
      <pubDate>Thu, 02 Feb 2023 14:25:36 +0000</pubDate>
      <link>https://dev.to/doctave/why-we-built-a-rust-powered-desktop-app-for-previewing-documentation-29nd</link>
      <guid>https://dev.to/doctave/why-we-built-a-rust-powered-desktop-app-for-previewing-documentation-29nd</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published on the &lt;a href="https://www.doctave.com/blog/2023/02/02/why-we-built-a-rust-powered-desktop-app-for-previewing-documentation.html" rel="noopener noreferrer"&gt;Doctave blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;We took the arguably unusual choice to build a desktop app for documentation. If you look around, documentation tools generally fall in 2 categories: open source CLI tools, and cloud-based WYSIWYG editors. In this post we'll talk about the reasoning that lead us to build a desktop app instead, and how it actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is it for?
&lt;/h2&gt;

&lt;p&gt;Our desktop app allows users to preview what their documentation will look like once published. It also catches errors such as broken links or syntactical problems.&lt;/p&gt;

&lt;p&gt;Users can open our app next to their editor of choice, edit their Markdown documents, and the app will refresh on every save to reflect the latest version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7ubzx3yi0a9nn50lyub.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7ubzx3yi0a9nn50lyub.gif" alt="an animation of the Doctave app updating the content shown when an edit is made in a text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When an error is detected, the user is immediately notified, and clicking on the link in the error takes you to the page with the issue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsv2g8hxzrx0nz57qq007.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsv2g8hxzrx0nz57qq007.png" alt="a cropped screenshot of the desktop app showing an error notifying the user about a broken link"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While my opinion is obviously biased, this fast feedback loop is amazing to work in. It takes the app milliseconds to do a complete re-render of the project, so you're able to move essentially as fast as you type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a desktop app?
&lt;/h2&gt;

&lt;p&gt;Doctave is built with&lt;br&gt;
&lt;a href="///blog/2021/08/30/why-you-should-consider-docs-as-code.html"&gt;docs-as-code&lt;/a&gt;&lt;br&gt;
in mind. As said above, there are two popular approaches for documentation tools: CLIs and cloud-based WYSIWYG editors. The point of docs-as-code is working on local files in source control so the cloud was out immediately.&lt;/p&gt;

&lt;p&gt;So this leaves the CLI option. There's nothing &lt;em&gt;"technically"&lt;/em&gt; that would have stopped Doctave from having this interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;doctave serve &lt;span class="nb"&gt;.&lt;/span&gt;
Serving your docs at http://localhost:9090
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But here are the negatives for requiring a CLI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better UX
&lt;/h3&gt;

&lt;p&gt;There has been a resurgence of desktop applications in the past few years. This is likely partly due to technologies like Electron making it easier to ship applications built with web technologies as cross-platform desktop apps, but it's also &lt;a href="https://www.youtube.com/watch?v=1vBesOFURek" rel="noopener noreferrer"&gt;arguably&lt;/a&gt;&lt;br&gt;
because the user experience can be better.&lt;/p&gt;

&lt;p&gt;Our users love that the preview and error checking happen in one place. You don't have to spin up terminal sessions and swap between an editor, a terminal, and a browser.&lt;/p&gt;

&lt;p&gt;Also, our app is fast. We are using &lt;a href="https://tauri.app/" rel="noopener noreferrer"&gt;Tauri&lt;/a&gt; for the shell of our app and the business logic that compiles your documentation is written in Rust. It runs natively inside Tauri, and not in the browser environment (more on this below). This is how we can get such fast response times: we run native code instead of a bundle of JavaScript or even WebAssembly. Would it be possible to do this in a CLI? Yes, but the desktop app gives us a great environment to take advantage both native code and web technologies.&lt;/p&gt;

&lt;p&gt;Finally, updates become trivial. Tauri's built-in update function has worked flawlessly, across 3 operating systems, ensuring our users have the latest and greatest version with minimal headache. You get a pop-up every time there's a new version, and the update happens automatically. There isn't really a universal way to update CLIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Non-developer users
&lt;/h3&gt;

&lt;p&gt;A lot of people working with technical documentation are not developers themselves. They are still technical, but in roles like tech writers, product managers, or support agents. They are comfortable on the command line, can use Git, but do not want to spend time understanding why their version of OpenSSL does not match what is expected.&lt;/p&gt;

&lt;p&gt;If you are expecting your users to setup a complex development environment to run your tool, you are alienating this important group of users. Even developers get frustrated when small environment differences cause &lt;code&gt;npm install&lt;/code&gt; to fail!&lt;/p&gt;

&lt;p&gt;There are ways to mitigate this, such as building static binaries. But you still have the problem of getting your users to update your SDK when you release features, you have to support different package managers, you have to make sure your software is not flagged by the OS as malicious, and much more.&lt;/p&gt;

&lt;p&gt;A common comment we hear from our users: it's just easier to use Doctave.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;As mentioned above, Doctave uses &lt;a href="https://tauri.app/" rel="noopener noreferrer"&gt;Tauri&lt;/a&gt; as the shell of our application. Think of it as a lightweight and security-focused Electron. It's a very interesting project that I highly recommend checking out. For our purposes, here are the 2 key features that are interesting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It uses each operating system's native web view, making app bundles
significantly smaller&lt;/li&gt;
&lt;li&gt;You can code your local "backend" in Rust which you can invoke from inside
the web view&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;(NOTE: this is not a "web server backend". This is code running natively&lt;br&gt;
  inside the local Tauri process.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Doctave's core documentation project parsing code is written in Rust and we call it &lt;code&gt;libdoctave&lt;/code&gt;. It takes as input a bunch of files and creates an in-memory structure that you can use to render a Doctave documentation site. It is used both in the desktop app and on our web platform, written in Elixir, via &lt;a href="https://github.com/rusterlium/rustler" rel="noopener noreferrer"&gt;Rustler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is how we are able to share code across the desktop and the web platform. We can render the same content identically regardless of where we are.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rendering a page
&lt;/h3&gt;

&lt;p&gt;As an example, let's walk through what happens when Doctave renders a page you are editing.&lt;/p&gt;

&lt;p&gt;First, Doctave is running a thread inside our local "backend" that is monitoring filesystem events. If it detects a change, it triggers a refresh of the project. Here's how that works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The backend re-reads &lt;em&gt;all&lt;/em&gt; the project files from disk&lt;/li&gt;
&lt;li&gt;It feeds those files to &lt;code&gt;libdoctave&lt;/code&gt; to create an in-memory representation of the project&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;libdoctave&lt;/code&gt; first runs a &lt;code&gt;build&lt;/code&gt; stage, to check the project is syntactically correct&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;libdoctave&lt;/code&gt; next runs a &lt;code&gt;verify&lt;/code&gt; stage, where all pages are checked for issues&lt;/li&gt;
&lt;li&gt;Finally, the requested page's HTML is serialized and sent to the frontend to display, along with any errors&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This all happens in single-digit milliseconds every time you edit a file.&lt;/p&gt;

&lt;p&gt;As you can see, there is still a lot of room for optimization if ever required. We could be smarter about incrementally updating the &lt;code&gt;libdoctave&lt;/code&gt; in-memory representation. We could reduce IO by only reading the files that have changed.&lt;/p&gt;

&lt;p&gt;So far, however, that has not been necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Desktop apps rock 🤘
&lt;/h2&gt;

&lt;p&gt;Betting on Tauri and having a desktop app in general has been a great decision. Especially with our choices of technologies, namely Rust, this combination has been a delightful if somewhat unconventional tech stack.&lt;/p&gt;

&lt;p&gt;Every startup has a certain number of "innovation tokens" that you can use on risky and/or new technologies. In our case, this was a great investment.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>writing</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Wikis don't work for software documentation</title>
      <dc:creator>Nik Begley</dc:creator>
      <pubDate>Tue, 21 Sep 2021 13:57:03 +0000</pubDate>
      <link>https://dev.to/doctave/wikis-don-t-work-for-software-documentation-1ejk</link>
      <guid>https://dev.to/doctave/wikis-don-t-work-for-software-documentation-1ejk</guid>
      <description>&lt;p&gt;If your software documentation lives in a Wiki, it's very likely out of date and possibly actively harming your engineering team's productivity. This is a story that repeats in almost every organization - a tool like Confluence or Notion is selected as the "corporate knowledge base" of choice and  developers start documenting code in them. But time and again that documentation goes stale and eventually serves no other purpose than to &lt;a href="http://geek-and-poke.com/geekandpoke/2012/4/25/the-new-developer.html"&gt;confuse eager new hires&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are wikis good for?
&lt;/h2&gt;

&lt;p&gt;First, let's take a look at what wikis &lt;em&gt;do&lt;/em&gt; work for. They are used actively by millions of people every day and absolutely have good use cases.&lt;/p&gt;

&lt;p&gt;We'd list the following reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trivially easy to add new content&lt;/li&gt;
&lt;li&gt;WYSIWYG editing allows non-technical users to collaborate&lt;/li&gt;
&lt;li&gt;Searchability and linking across pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wikis are an accessible and easy to use way to write down information. At a click of a button you can create a new page and the powerful in-built editors make it easy to add graphs and style to your thoughts. Finally, once it's all written down, you can reference other pages and search across the whole knowledge base. Wikis are the lowest common denominator that can work in &lt;em&gt;most&lt;/em&gt; cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can't you do with wikis?
&lt;/h2&gt;

&lt;p&gt;But let's look at what you &lt;em&gt;cannot&lt;/em&gt; do with wikis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integrate documentation changes into code review&lt;/li&gt;
&lt;li&gt;Atomic changes to your docs and code&lt;/li&gt;
&lt;li&gt;Programmatically generate docs at build time&lt;/li&gt;
&lt;li&gt;Edit the documentation using your IDE/editor of choice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wouldn't you want to be able to see during code review that the relevant documentation has been updated along with the code? Maybe the committer included a link to the updated wiki page, but are the docs changes already live before the PR is merged? Or will they remember to go submit the changes in the wiki once the code goes live? What if your update only applies to v2.5 of your product but not the newest v3.1 release and the documentation needs an important update?&lt;/p&gt;

&lt;p&gt;None of these questions have clear answers when using a wiki and most likely will lead to the documentation simply never being written or maintained.&lt;/p&gt;

&lt;p&gt;As &lt;a href="https://twitter.com/rionam"&gt;Riona MacNamara&lt;/a&gt;, who worked on Google's internal documentation tooling, put it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Documentation will never be part of engineering culture until it is integrated into the codebase&lt;br&gt;
and workflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A better tool for the job
&lt;/h2&gt;

&lt;p&gt;We previously wrote about how &lt;a href="https://blog.doctave.com/2021/09/07/how-google-twitter-and-spotify-build-culture-of-documentation.html"&gt;Google, Twitter, and Spotify all built their own docs-as-code systems&lt;/a&gt; for managing internal technical documentation. They did not build wikis.&lt;/p&gt;

&lt;p&gt;Instead, they built systems that allowed documentation to live next to source code and get rendered into searchable pages in CI/CD. This practice is generally called "docs-as-code" and is not a new invention.&lt;/p&gt;

&lt;p&gt;What they all found was that allowing developers to update documentation as part of their regular workflow lowered the barrier to update docs and ultimately lead to better documented projects. Instead of having to step into a different system to make changes to the project docs, all they had to do was open a Markdown file and start writing. There was one way to document and it was optimized for developers.&lt;/p&gt;

&lt;p&gt;Removing friction leads to better documentation. We should be using better tools to remove all the friction we can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs
&lt;/h2&gt;

&lt;p&gt;No solution is perfect, so let's take a moment to talk about the trade-offs of taking a docs-as-code approach.&lt;/p&gt;

&lt;p&gt;Firstly, using docs-as-code will make it more difficult for non-technical collaborators to edit documentation. That being said, for internal technical documentation the maintainers should be the engineers and technical writers. Also, GitHub allows editing directly in the browser, so everyone does not need to learn the correct &lt;code&gt;git&lt;/code&gt; incantations to contribute.&lt;/p&gt;

&lt;p&gt;Secondly, you have to think about search. If you use a documentation site generator to turn your Markdown docs into HTML sites, even if the toolchain builds a search index for the site, you will end up with N disconnected sites for N projects. &lt;a href="https://www.doctave.com"&gt;Doctave.com&lt;/a&gt; can help with this if you are using our open source &lt;a href="https://github.com/Doctave/doctave"&gt;documentation site generator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, your documentation will have to be limited to Markdown (or an equivalent format). Many would argue that this is a feature, not a limitation - forcing documentation to be in a simple format can make it easier to write and consume. Some tools will support useful "extensions" to Markdown. Our generator for example supports &lt;a href="https://mermaid-js.github.io/mermaid/#/"&gt;Mermaid JS&lt;/a&gt; diagrams, so that you can embed graphs and charts into your docs easily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technology options
&lt;/h2&gt;

&lt;p&gt;Here's a quick list of some technology options if you want to look into docs-as-code.&lt;/p&gt;

&lt;p&gt;We are building &lt;a href="https://www.doctave.com"&gt;Doctave.com&lt;/a&gt; to be the best home for your technical documentation. If you want to do docs-as-code with a light-weight &lt;a href="https://https://github.com/Doctave/doctave"&gt;open source docs site generator&lt;/a&gt;, but host the docs in a centralized place that allows searching across all your projects, &lt;a href="https://www.doctave.com"&gt;check us out&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are also many open source generators out there that are worth mentioning: &lt;a href="https://www.mkdocs.org/"&gt;MkDocs&lt;/a&gt; and &lt;a href="https://docusaurus.io/"&gt;Docusaurus&lt;/a&gt; to name a couple popular ones. If you need something more customizable, &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt; and &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; are good options. Generic static site generators however tend to add a lot of complexity so we'd recommend going for something more light-weight.&lt;/p&gt;

&lt;p&gt;In the end, the exact technology choice is less important. As long as it's not a wiki.&lt;/p&gt;

</description>
      <category>writing</category>
      <category>documentation</category>
      <category>agile</category>
    </item>
    <item>
      <title>Doctave CLI 0.2.0: A Benchmarking Story</title>
      <dc:creator>Nik Begley</dc:creator>
      <pubDate>Wed, 15 Sep 2021 12:34:15 +0000</pubDate>
      <link>https://dev.to/doctave/doctave-cli-0-2-0-a-benchmarking-story-m7j</link>
      <guid>https://dev.to/doctave/doctave-cli-0-2-0-a-benchmarking-story-m7j</guid>
      <description>&lt;p&gt;The &lt;a href="https://www.github.com/Doctave/doctave"&gt;Doctave CLI&lt;/a&gt; is a free to use open source documentation generator. It takes your Markdown files and converts them into a beautiful &lt;a href="https://cli.doctave.com"&gt;documentation site&lt;/a&gt;. Today we have released version 0.2.0, which brings some cosmetic improvements and a fully &lt;em&gt;in-memory development server&lt;/em&gt; for local development. We'll also talk about how this surprisingly &lt;em&gt;did not&lt;/em&gt; make the CLI faster like we expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serving your docs from memory
&lt;/h2&gt;

&lt;p&gt;Before Doctave would rewrite the whole documentation site to disk every time you made a change to your Markdown files. It would pretty much &lt;code&gt;rm -r&lt;/code&gt; the &lt;code&gt;site&lt;/code&gt; directory, used to house the generated HTML, and regenerate the docs site from scratch. Because Doctave (which is written in Rust) is able to generate the whole site in ~tens of milliseconds, we hadn't looked into optimizations here yet.&lt;/p&gt;

&lt;p&gt;But there were two non-performance related issues that bothered us about this approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You would end up with a &lt;code&gt;site&lt;/code&gt; directory that didn't really serve any purpose while developing
your docs&lt;/li&gt;
&lt;li&gt;While very minor in scale in our case, constantly writing and deleting small files from disk
isn't something that modern SSDs like to do&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is why Doctave 0.2.0 never writes the HTML documentation site to disk until you run&lt;br&gt;
&lt;code&gt;doctave build --release&lt;/code&gt;. In &lt;code&gt;serve&lt;/code&gt; mode when you write your documentation, the embedded web server serves them directly from memory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ doctave serve Doctave | Serve Starting development server...

Server running on http://0.0.0.0:4001/

    File docs/README.md updated.
    Site rebuilt in 31.698781ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait a minute - shouldn't this be faster now that we are not writing files to disk?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is this not faster?
&lt;/h2&gt;

&lt;p&gt;After completing this feature, I was surprised to see that Doctave wasn't generating sites noticeably faster than before. I was expecting a speedup due to us not spending as much time on IO. This was not happening.&lt;/p&gt;

&lt;p&gt;After double checking to make sure the code was doing what I expected, it was time to benchmark things to see where the time was being spent. After some digging I found the answer: &lt;em&gt;generating the search index&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Doctave comes with offline search built in. We use the &lt;a href="https://github.com/mattico/elasticlunr-rs"&gt;elasticlunr-rs&lt;/a&gt; crate to generate a search index that is compatible with the &lt;a href="https://github.com/weixsong/elasticlunr.js"&gt;elasticlunr.js&lt;/a&gt; library. You can see it in action by going to &lt;a href="https://cli.doctave.com"&gt;our docs&lt;/a&gt; (built with the CLI, naturally) and hitting the letter &lt;code&gt;s&lt;/code&gt; on your keyboard to focus on the search bar. The searching happens entirely client-side.&lt;/p&gt;

&lt;p&gt;It turned out that we are spending &lt;em&gt;~70% of the site generation time creating the search index&lt;/em&gt;. This is completely reasonable, as this is a somewhat CPU intensive task that parses the input files and generates the index. The time spent doing IO writing files to disk is completely negligible in comparison, and thus did not move the needle much at all. On top of that, the CLI was already writing files to disk in parallel. Moving things in-memory did not save us much time.&lt;/p&gt;

&lt;p&gt;So while Doctave 0.2.0 is technically faster, this was not a big performance win. There is some more work we can do to parallelize the build process further - maybe in the next release. In the meantime, Doctave will be more friendly on your SSD and not pollute your workspace as much.&lt;/p&gt;

&lt;p&gt;Still, this was a change for the better. It just wasn't the big performance win I suspected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;I think this was an instructive little story about performance and benchmarking that was worth sharing. It's often the case that when we make assumptions about the performance of a system that turn out to be wrong once you measure things. This is just another reminder: &lt;strong&gt;always measure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Finally, do try out &lt;a href="https://github.com/Doctave/doctave"&gt;Doctave 0.2.0&lt;/a&gt;! If you're looking for a batteries-included documentation generator that doesn't require plugins, doesn't pollute your repository with loads of files, and doesn't need a specialized environment to run, Doctave may be a good choice for you. It also comes with &lt;a href="https://mermaid-js.github.io/mermaid/#/"&gt;Mermaid JS diagram&lt;/a&gt; support and dark mode!&lt;/p&gt;

&lt;p&gt;You can host sites generated by Doctave on GitHub pages, or your favorite static site hosting provider. We are also building a &lt;a href="https://www.doctave.com"&gt;specialized host&lt;/a&gt; for teams using Doctave on multiple projects - check it out if you're using Doctave at your organization.&lt;/p&gt;

&lt;p&gt;Let me know if you're using it to document your project. I can be reached at &lt;code&gt;nik@doctave.com&lt;/code&gt;. I'd be excited to hear what you think.&lt;/p&gt;

&lt;h3&gt;
  
  
  Want to try out Doctave?
&lt;/h3&gt;

&lt;p&gt;If you're on Mac using Homebrew, you can install Doctave with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install doctave/doctave/doctave
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're a Rust fan and want to use Cargo, you can do so too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cargo install --git https://github.com/Doctave/doctave --tag 0.2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also have &lt;a href="https://github.com/Doctave/doctave/releases/tag/0.2.0"&gt;prebuilt static binaries&lt;/a&gt; for Mac, Linux, and Windows.&lt;/p&gt;

</description>
      <category>markdown</category>
      <category>showdev</category>
      <category>writing</category>
      <category>rust</category>
    </item>
    <item>
      <title>Why documentation is important</title>
      <dc:creator>Nik Begley</dc:creator>
      <pubDate>Thu, 09 Sep 2021 11:23:37 +0000</pubDate>
      <link>https://dev.to/doctave/why-documentation-is-important-55bp</link>
      <guid>https://dev.to/doctave/why-documentation-is-important-55bp</guid>
      <description>&lt;p&gt;Most software engineers know that they &lt;em&gt;should&lt;/em&gt; write documentation. But how many can articulate exactly &lt;em&gt;why&lt;/em&gt; documentation is needed? Let's look into some of the reasons.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Productive engineers
&lt;/h2&gt;

&lt;p&gt;The lack of good documentation is often cited by engineers as one of the main reasons holding them back doing good work. In 2014, &lt;strong&gt;48% of Google engineers&lt;/strong&gt; cited bad docs as #1 productivity issue,  and the &lt;a href="https://insights.stackoverflow.com/survey/2016"&gt;2016 StackOverflow survey&lt;/a&gt; found that poor documentation was the &lt;strong&gt;#2 challenge at work&lt;/strong&gt; for engineers&lt;/p&gt;

&lt;p&gt;Engineers are not cheap. If you have 100 engineers, and you save them them 1 hour each month because you have decent documentation covering your internal systems, that translates into huge savings and a sizable productivity boost. If your engineers are wasting time searching for answers to questions that have already been answered, your velocity is going to suffer. &lt;/p&gt;

&lt;p&gt;If however engineers document their work as they go, a lot of these interruptions can be minimized. Instead of asking coworkers for assistance, &lt;em&gt;just check the docs&lt;/em&gt;. Need to setup a new development environment? &lt;em&gt;Just check the docs&lt;/em&gt;. How do I deploy this project again? &lt;em&gt;Just check the docs&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Remote employees will thank you
&lt;/h2&gt;

&lt;p&gt;Over the past 2 years thanks to the Covid-19 pandemic, evern more of us have had to become familiar with working remotely. The importance of documentation is emphasized when you don't have coworkers around to answer questions when you get stuck. Now, you have to rely on &lt;em&gt;asynchronous communication channels&lt;/em&gt; to get your answer.&lt;/p&gt;

&lt;p&gt;You may send an email to that senior colleague, but when will they respond? Maybe they'll be online in Slack, or are they on a different continent and fast asleep? You may not get your answer for many hours and meanwhile you're stuck, unable to make progres. What's worse, soon a significant portion of engineering time is spent in these communication channels coordinating work instead of in the editor.&lt;/p&gt;

&lt;p&gt;Having high quality documentation allows your remote employees to help themselves to get the information they need without interrupting a colleague's workflow, and stay productive.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Onboarding becomes a lot easier
&lt;/h2&gt;

&lt;p&gt;Bringing on a new engineer is a big investment. It often takes months for an engineer to ramp up and become productive in a new company - every codebase is different and requires some getting used to. During those months they will be asking a lot of questions and taking time from other engineers.&lt;/p&gt;

&lt;p&gt;Having good documentation can help here. Instead of having to sit down with other engineers all the time, the new hires can read through the documentation and become familiar with the codebase and customs on their own. Of course they won't be able to become 100% productive on their own. But you will be able to cut down their training time. And your new hire will thank you because they can be self-sufficient and don't have to feel like they are constantly interrupting their new colleagues to ask questions.&lt;/p&gt;

&lt;p&gt;This is actually a great opportunity to test your documentation. If a new hire can get started on a project on their own by reading the docs, that's a great sign. If not, figure out what is missing, and make the necessary changes. Just don't make the new hire change the docs - that's your job!&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Stop the brain drain
&lt;/h2&gt;

&lt;p&gt;Have you ever been in a company with that one engineer who has been there forever? The one that knows every single system inside out and people constantly come to them to ask about different parts of the codebase. These types of engineers can be a liability.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Bus_factor"&gt;"lottery factor"&lt;/a&gt; (also known as the "bus factor") refers to the number of team members you could lose before your business stops functioning. Having critical information inside the heads of a small number of key people means that you are at risk of losing important institutional knowledge if they were to leave after winning the lottery, getting a better job offer, or simply deciding to retire.&lt;/p&gt;

&lt;p&gt;By building a culture where information is written down and documented you maximize your lottery factor and stop the brain drain.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Encourage transparency
&lt;/h2&gt;

&lt;p&gt;Writing in public encourages transparency. It's empowering to be able to see what other teams are working on and break down silos in organizations. Especially with remote teams where you can't have watercooler conversations, writing things down is not only important but absolutely necessary.&lt;/p&gt;

&lt;p&gt;Engineers can go and look at how other teams are solving problems and even suggest improvements. It's a great way to share knowledge and make sure information doesn't get stuck.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's cheaper than you'd expect
&lt;/h2&gt;

&lt;p&gt;One common complaint about documentation is that &lt;em&gt;"we don't have time to write things down"&lt;/em&gt; or &lt;em&gt;"documentation isn't necessary"&lt;/em&gt;. These are similar objections to what automated testing encountered a decade or two ago. Today, testing is part of any project's definition of done.&lt;/p&gt;

&lt;p&gt;Documentation does not take a lot of time if done incrementally throughout the software engineering lifecycle. Similar to how taking a project with 0% test coverage to &amp;gt;50% is a huge effort, if you start documenting a project with no existing documentation, it can seem daunting. But small incremental updates and refactors are a great return on time invested.&lt;/p&gt;

&lt;p&gt;We've written a post about how &lt;a href="https://blog.doctave.com/2021/09/07/how-google-twitter-and-spotify-build-culture-of-documentation.html"&gt;Google, Twitter, and Spotify built their documentation cultures&lt;/a&gt; if you're looking for tips on how to improve your organization's documentation. Here are some quick ideas to get you started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a docs-as-code approach to make it easier for developers to contribute documentation&lt;/li&gt;
&lt;li&gt;Start asking for documentation updates as part of code review&lt;/li&gt;
&lt;li&gt;Create "documentation days" where the whole engineering teams focuses on docs to bootstrap your
projects with bad docs&lt;/li&gt;
&lt;li&gt;Provide templates your teams can use as starting points to start documenting projects&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>writing</category>
      <category>programming</category>
      <category>agile</category>
    </item>
  </channel>
</rss>
