<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://vladimirgorej.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://vladimirgorej.com/" rel="alternate" type="text/html" /><updated>2026-07-09T09:27:17+00:00</updated><id>https://vladimirgorej.com/feed.xml</id><title type="html">Vladimír Gorej’s Site</title><subtitle>Vladimír Gorej is a Prague-based software engineer. Passionate with software/code quality, Functional and Reactive programming.</subtitle><author><name>Vladimír Gorej</name></author><entry><title type="html">The one Arazzo condition no tool can evaluate safely</title><link href="https://vladimirgorej.com/blog/the-one-arazzo-condition-no-tool-can-evaluate-safely/" rel="alternate" type="text/html" title="The one Arazzo condition no tool can evaluate safely" /><published>2026-07-08T08:00:00+00:00</published><updated>2026-07-08T08:00:00+00:00</updated><id>https://vladimirgorej.com/blog/the-one-arazzo-condition-no-tool-can-evaluate-safely</id><content type="html" xml:base="https://vladimirgorej.com/blog/the-one-arazzo-condition-no-tool-can-evaluate-safely/"><![CDATA[<p class="lead">
  I'm building an Arazzo runner: a program that executes API workflows step by step. That means every step
  needs an answer to a deceptively simple question: did this step succeed?
</p>

<p>In the case I was implementing, that answer came from a <a href="https://spec.openapis.org/arazzo/v1.1.0.html#criterion-object">Criterion Object</a>
with a <code class="language-plaintext highlighter-rouge">simple</code> condition, such as <code class="language-plaintext highlighter-rouge">$statusCode == 200</code>. It looks obvious. It reads like plain English. But
if Arazzo workflows are meant to be executable and portable, then every runner has to read that condition
the same way.</p>

<p>So I wrote a quick evaluator. It handled the easy cases, then quietly fell apart on corner case after
corner case I hadn’t anticipated. That was enough to make me stop, put the runner down, and go into
research mode.</p>

<p>This is the story of what that turned up, and of how the detour became a standalone library,
<a href="https://github.com/swaggerexpert/arazzo-criterion">@swaggerexpert/arazzo-criterion</a>: a small evaluator
that reads each condition as faithfully as the spec permits, and is honest about the one case where no
tool can know for sure.</p>

<h2 id="what-a-condition-looks-like">What a condition looks like</h2>

<p><a href="https://spec.openapis.org/arazzo/v1.1.0.html">Arazzo</a> is the OpenAPI Initiative’s specification for
describing sequences of API calls: workflows. After each step, you check that it worked by evaluating its
success criteria. Each criterion is a <a href="https://spec.openapis.org/arazzo/v1.1.0.html#criterion-object">Criterion Object</a>,
and one common form is a <code class="language-plaintext highlighter-rouge">simple</code> condition:</p>

<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">condition</span><span class="pi">:</span> <span class="s">$statusCode == 200</span></code></pre></figure>

<p>Read it out loud and it’s obvious: <em>did the server return a 200 status code?</em> That readability is the
whole appeal. You can write conditions that sound like what you mean:</p>

<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">condition</span><span class="pi">:</span> <span class="s">$response.body.status == 'available' &amp;&amp; $response.body.pets[0].id &gt; 0</span></code></pre></figure>

<p><em>Is the status “available”, and does the first pet have a real id?</em> A person can read that. My job was to
write software that reads it the same way a person does, and, just as importantly, the same way every
<em>other</em> runner does.</p>

<p>That portability requirement is the part that matters. Arazzo isn’t prose documentation for humans; it’s a
machine-readable workflow description. If one runner treats a condition as true and another treats the same
condition as false, the workflow has stopped being portable. At that point the problem is no longer just
parsing. It’s interoperability.</p>

<h2 id="my-first-instinct-just-run-it">My first instinct: just run it</h2>

<p>My first idea was the lazy one: a condition looks like code. <code class="language-plaintext highlighter-rouge">$statusCode == 200</code> is almost a line you
could paste into a program and let the language run. Fill in the <code class="language-plaintext highlighter-rouge">$...</code> parts (like <code class="language-plaintext highlighter-rouge">$statusCode</code>) with
their real values, hand the rest to the language’s <code class="language-plaintext highlighter-rouge">eval</code>, and read back true or false. An afternoon’s work.</p>

<p>I’m glad I didn’t ship that. The obvious objection is safety: feeding arbitrary text into <code class="language-plaintext highlighter-rouge">eval</code> is the
classic way to run code you never meant to run.</p>

<p>So I reached for the safer version: parse the text with a proper JavaScript parser first (a well-tested
component that turns code into a structured tree without executing anything), then walk that tree myself
and decide what each piece means. No blind execution, no injection hole. This felt right for about a day.</p>

<p>Then the deeper problem surfaced, and it’s the reason this whole project exists. A <em>JavaScript</em> parser
reads <em>JavaScript’s</em> rules. A Python runner would reach for a Python parser, a Go one for Go’s, each with
its own subtly different semantics. I’d have traded “whatever <code class="language-plaintext highlighter-rouge">eval</code> does” for “whatever this one
language’s parser does”, still shackled to a single language, with no guarantee that two conformant runners
reach the same answer. Arazzo even layers on comparison rules that no language reproduces out of the box,
but I’ll come back to those.</p>

<p>That was the click: I didn’t need JavaScript semantics. I needed <em>Arazzo</em> semantics. A definition that
sits above any single language, that I could write down once and that every implementation, in any
language, could follow to the exact same result. That’s what pushed me toward describing the condition
language with its own precise, portable rulebook. And that’s where the real problem revealed itself.</p>

<h2 id="two-languages-one-line">Two languages, one line</h2>

<p>Writing that rulebook down sounds simple. It isn’t, and here is why: that one innocent line is secretly
<strong>two different mini-languages mixed together</strong>.</p>

<p>The first is for <em>pointing at data</em>: <code class="language-plaintext highlighter-rouge">$statusCode</code>, <code class="language-plaintext highlighter-rouge">$response.body</code>, <code class="language-plaintext highlighter-rouge">$request.query.limit</code>. Arazzo calls
these <a href="https://spec.openapis.org/arazzo/v1.1.0.html#runtime-expressions">runtime expressions</a>, and each
one just points at a value somewhere in the request or the response, saying <em>where to look</em>. They already
have a precise definition of their own. I’d formalized that definition earlier in a separate library,
<a href="https://github.com/swaggerexpert/arazzo-runtime-expression">@swaggerexpert/arazzo-runtime-expression</a>,
and building it out carefully even fed back into the standard: the work helped tighten the
runtime-expression grammar in Arazzo 1.1.0 (<a href="https://github.com/OAI/Arazzo-Specification/pull/454">PR #454</a>).
So my criterion library doesn’t reinvent any of that. It just reuses that parser.</p>

<p>The second is for <em>comparing</em> things: <code class="language-plaintext highlighter-rouge">==</code>, <code class="language-plaintext highlighter-rouge">!=</code>, <code class="language-plaintext highlighter-rouge">&lt;</code>, <code class="language-plaintext highlighter-rouge">&gt;</code>, the word-joiners <code class="language-plaintext highlighter-rouge">&amp;&amp;</code> and <code class="language-plaintext highlighter-rouge">||</code>, and so on.
This is the part that says <em>what to check</em> once you’ve looked.</p>

<p>In the common case I cared about, a condition is a runtime expression plus a comparison: <strong><code class="language-plaintext highlighter-rouge">⟨where to look⟩ ⟨what to check⟩</code></strong>.
Simple enough, until you notice that the two languages <strong>share some of the same symbols</strong>.</p>

<p>That’s the whole problem in one sentence. Let me show you why it bites.</p>

<h2 id="why-the-obvious-reading-breaks">Why the obvious reading breaks</h2>

<p>Runtime expressions in Arazzo are allowed to be surprisingly loose. A query-parameter name, for instance,
can legally contain almost any character, including <code class="language-plaintext highlighter-rouge">=</code>, <code class="language-plaintext highlighter-rouge">&lt;</code>, <code class="language-plaintext highlighter-rouge">&gt;</code>, even spaces. On its own that’s fine; a
runtime expression is usually the <em>only</em> thing on the line, so there’s nothing after it to get confused with.</p>

<p>Now put a runtime expression next to a comparison and watch it go wrong:</p>

<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">condition</span><span class="pi">:</span> <span class="s">$request.query.limit == 10</span></code></pre></figure>

<p>You and I read that instantly: <em>the query parameter <code class="language-plaintext highlighter-rouge">limit</code> equals 10</em>. But the runtime-expression reader,
following its own loose rules, sees no reason to stop at <code class="language-plaintext highlighter-rouge">limit</code>. Spaces are allowed in a name. So is
<code class="language-plaintext highlighter-rouge">=</code>. So it happily swallows the <strong>entire line</strong>, <code class="language-plaintext highlighter-rouge">== 10</code> included, treating the whole thing as if the
parameter were literally named <code class="language-plaintext highlighter-rouge">"limit == 10"</code>. The comparison never gets its turn, and the reading
collapses.</p>

<figure class="figure d-block text-center my-4">
  <svg viewBox="0 0 760 300" role="img" aria-labelledby="splitTitle splitDesc" style="max-width:100%;height:auto;font-family:'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace;">
    <title id="splitTitle">Two ways to read one condition</title>
    <desc id="splitDesc">The condition dollar request dot query dot limit equals equals 10. The intended reading splits it into the runtime expression (dollar request dot query dot limit) and the comparison (equals equals 10). A literal reader instead swallows the whole line as a single runtime-expression name, losing the comparison.</desc>
    <text x="380" y="30" text-anchor="middle" font-size="15" fill="#6c757d" style="font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif">What you mean</text>
    <rect x="150" y="48" width="290" height="46" rx="8" fill="#e7f1ff" stroke="#0d6efd" stroke-width="1.5" />
    <text x="295" y="77" text-anchor="middle" font-size="18" fill="#084298">$request.query.limit</text>
    <line x1="452" y1="42" x2="452" y2="100" stroke="#198754" stroke-width="2.5" stroke-dasharray="4 3" />
    <rect x="464" y="48" width="146" height="46" rx="8" fill="#fff3e6" stroke="#fd7e14" stroke-width="1.5" />
    <text x="537" y="77" text-anchor="middle" font-size="18" fill="#a5510a">== 10</text>
    <text x="380" y="164" text-anchor="middle" font-size="15" fill="#6c757d" style="font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif">What a literal reader does</text>
    <rect x="150" y="182" width="460" height="46" rx="8" fill="#e7f1ff" stroke="#0d6efd" stroke-width="1.5" />
    <text x="380" y="211" text-anchor="middle" font-size="18" fill="#084298">$request.query.limit == 10</text>
    <text x="380" y="258" text-anchor="middle" font-size="14" fill="#dc3545" style="font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif">✗ swallowed as a single name, the comparison is lost</text>
    <rect x="238" y="278" width="13" height="13" rx="3" fill="#e7f1ff" stroke="#0d6efd" />
    <text x="257" y="289" font-size="13" fill="#495057" style="font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif">where to look</text>
    <rect x="408" y="278" width="13" height="13" rx="3" fill="#fff3e6" stroke="#fd7e14" />
    <text x="427" y="289" font-size="13" fill="#495057" style="font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif">what to check</text>
  </svg>
  <figcaption class="figure-caption">The same line, read two ways. The whole problem is deciding where the runtime expression stops.</figcaption>
</figure>

<p>It’s a bit like reading the sentence <em>“Call Mr. Green house painters today”</em> and not knowing whether
someone is named “Mr. Green” or “Mr. Green house.” Without a clear signal for where the name ends, a
literal-minded reader keeps going and gets it wrong.</p>

<h2 id="the-part-i-could-fix-and-the-part-i-couldnt">The part I could fix, and the part I couldn’t</h2>

<p>Some of these collisions have a clean answer. Take the dot, <code class="language-plaintext highlighter-rouge">.</code>, which shows up both inside runtime
expressions and as a way to reach into data. It sounds ambiguous, but it usually isn’t, because the
runtime-expression rulebook itself knows where a runtime expression like <code class="language-plaintext highlighter-rouge">$response.body</code> naturally ends. So I can simply <em>ask it</em>, and let the
rulebook draw the line rather than guessing. That collision, I could solve.</p>

<p>The unsolvable one is the shared <code class="language-plaintext highlighter-rouge">=</code>. Consider a query parameter that genuinely has an <code class="language-plaintext highlighter-rouge">=</code> in its name:</p>

<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">condition</span><span class="pi">:</span> <span class="s">$request.query.a=b == 1</span></code></pre></figure>

<p>Is that <code class="language-plaintext highlighter-rouge">=</code> <strong>part of the parameter’s name</strong> (<code class="language-plaintext highlighter-rouge">a=b</code>), or the <strong>start of the comparison</strong> <code class="language-plaintext highlighter-rouge">==</code>? Both
readings are legal. There is no rule, none exists in the Arazzo spec, that says “this <code class="language-plaintext highlighter-rouge">=</code> is part of the
name” versus “this <code class="language-plaintext highlighter-rouge">=</code> is an operator.” A human can’t reliably tell either. Nobody can. The string is
simply ambiguous, and no amount of clever programming invents an answer that isn’t there.</p>

<p>This is the honest heart of it: <strong>the ambiguity isn’t a flaw in my code. It’s a property of what was
written.</strong> The best any implementation can do is be predictable about it and say so out loud.</p>

<h2 id="reading-a-condition-the-way-it-was-meant">Reading a condition the way it was meant</h2>

<p>So how do you read a condition the way its author meant it, while staying upfront about the cases you
can’t? Here’s the approach I shipped.</p>

<p>Instead of teaching my comparison-reader every rule of the runtime-expression language (copying someone
else’s rulebook and letting it drift out of date), I do something simpler. When I hit a runtime expression,
I grab the whole suspicious chunk and hand it to the one component that already knows every rule:
<a href="https://github.com/swaggerexpert/arazzo-runtime-expression">@swaggerexpert/arazzo-runtime-expression</a>,
the parser from earlier, used unmodified. I ask it a single question: <em>how much of this is a valid runtime
expression?</em></p>

<p>The parser reads as far as it legitimately can and hands back the leftover. Whatever’s left is the
“what to check” part. So for <code class="language-plaintext highlighter-rouge">$response.body.data[0].id</code>, it reports that <code class="language-plaintext highlighter-rouge">$response.body</code> is the
runtime expression, and I treat the rest, <code class="language-plaintext highlighter-rouge">.data[0].id</code>, as the criterion language’s navigation into the
resolved value: <em>reach into the first data item’s id</em>. The boundary is drawn by the component that actually
knows the rules, not by me guessing.</p>

<p>Two things fall out of this:</p>

<ul>
  <li><strong>The upside:</strong> I never fork or modify the runtime-expression rulebook. It stays the single source of
truth, and my library just <em>asks</em> it. When those rules improve upstream, I get the improvement for free.</li>
  <li><strong>The catch:</strong> my library first does a rough pass (“this looks like a runtime expression, roughly”) and
only then does the parser confirm it for real. Passing the rough pass isn’t enough; something can
<em>look</em> like a runtime expression and still be nonsense. Think of a bouncer glancing at you versus someone
actually checking your ID. That two-look approach, it turns out, is exactly how big specifications embed
one language inside another: match the rough shape first, verify it for real second.</li>
</ul>

<h2 id="the-one-case-that-stays-broken-on-purpose">The one case that stays broken (on purpose)</h2>

<p>In this <code class="language-plaintext highlighter-rouge">simple</code> grammar, because I stop the runtime-expression operand at the comparison symbols, that
unfenced operand can never <em>contain</em> those symbols. That’s the flip side of making the common case work:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="p">{</span> <span class="nx">test</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@swaggerexpert/arazzo-criterion</span><span class="dl">'</span><span class="p">;</span>

<span class="nx">test</span><span class="p">(</span><span class="dl">'</span><span class="s1">$statusCode == 200</span><span class="dl">'</span><span class="p">);</span>        <span class="c1">// true:  reads exactly as intended</span>
<span class="nx">test</span><span class="p">(</span><span class="dl">'</span><span class="s1">$request.query.a=b == 1</span><span class="dl">'</span><span class="p">);</span>   <span class="c1">// false: the '=' in the name can't be told apart</span>
<span class="nx">test</span><span class="p">(</span><span class="dl">'</span><span class="s1">$response.body#/a=b == 1</span><span class="dl">'</span><span class="p">);</span>  <span class="c1">// true:  this style has its own clear fences</span></code></pre></figure>

<p>That middle line is the unavoidable casualty from earlier, and it fails <em>cleanly</em>. It doesn’t silently
guess wrong, which is the important part. The last line works because the <code class="language-plaintext highlighter-rouge">#/…</code> pointer form gives the
operand a clear internal structure of its own: the <code class="language-plaintext highlighter-rouge">=</code> sits <em>inside</em> the pointer, so it never touches the
comparison layer.</p>

<div class="table-responsive">
  <table class="table">
    <thead>
      <tr><th scope="col">Condition</th><th scope="col">How it reads</th><th scope="col">Result</th></tr>
    </thead>
    <tbody>
      <tr><td><code>$statusCode == 200</code></td><td>compare the status code to 200</td><td>works</td></tr>
      <tr><td><code>$request.query.limit == 10</code></td><td>compare query param <code>limit</code> to 10</td><td>works</td></tr>
      <tr><td><code>$request.query.a=b == 1</code></td><td>ambiguous: is <code>=</code> in the name, or the operator?</td><td>refused: ambiguous</td></tr>
      <tr><td><code>$response.body#/a=b == 1</code></td><td>fenced pointer, the <code>=</code> lives inside it</td><td>works</td></tr>
    </tbody>
  </table>
</div>

<p>I didn’t bury this. It lives as a documented limitation,
<a href="https://github.com/swaggerexpert/arazzo-criterion/issues/3">issue #3</a>, with the reasoning spelled out.
If a future version of the Arazzo spec adds a way to “escape” these symbols, the limitation lifts, so I’ve
<a href="https://github.com/OAI/Arazzo-Specification/issues/518">asked it to</a>. Until then, being honest about it
<em>is</em> the feature.</p>

<h2 id="comparisons-play-by-arazzos-rules">Comparisons play by Arazzo’s rules</h2>

<p>So far I’ve shown <code class="language-plaintext highlighter-rouge">test</code>, which only answers “is this a valid condition?”. The point, though, is to
<code class="language-plaintext highlighter-rouge">evaluate</code> one and get an actual true or false. The library never fetches live data itself, so you hand it
a small <code class="language-plaintext highlighter-rouge">resolve</code> function that turns a runtime expression into its current value:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="p">{</span> <span class="nx">evaluate</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@swaggerexpert/arazzo-criterion</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">context</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">$statusCode</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span>
  <span class="dl">'</span><span class="s1">$response.body</span><span class="dl">'</span><span class="p">:</span> <span class="p">{</span> <span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Available</span><span class="dl">'</span><span class="p">,</span> <span class="na">data</span><span class="p">:</span> <span class="p">[{</span> <span class="na">id</span><span class="p">:</span> <span class="mi">42</span> <span class="p">}]</span> <span class="p">},</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">resolve</span> <span class="o">=</span> <span class="p">(</span><span class="nx">expression</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">context</span><span class="p">[</span><span class="nx">expression</span><span class="p">];</span>

<span class="nx">evaluate</span><span class="p">(</span><span class="dl">'</span><span class="s1">$statusCode == 200</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="nx">resolve</span> <span class="p">});</span>                    <span class="c1">// true</span>
<span class="nx">evaluate</span><span class="p">(</span><span class="dl">"</span><span class="s2">$response.body.status == 'available'</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">resolve</span> <span class="p">});</span>  <span class="c1">// true, even though the data says "Available"</span>
<span class="nx">evaluate</span><span class="p">(</span><span class="dl">'</span><span class="s1">$response.body.data[0].id &gt; 10</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="nx">resolve</span> <span class="p">});</span>        <span class="c1">// true</span></code></pre></figure>

<p>That second line hides the last decision worth mentioning, and it’s the loose end from the <code class="language-plaintext highlighter-rouge">eval</code> idea I
promised to come back to. When you compare things, the spec has its own gentle rules:
text comparisons ignore capitalization (<code class="language-plaintext highlighter-rouge">'Available'</code> equals <code class="language-plaintext highlighter-rouge">'available'</code>), the number <code class="language-plaintext highlighter-rouge">200</code> and the text
<code class="language-plaintext highlighter-rouge">'200'</code> are treated as equal, and a check passes on anything truthy and fails on <code class="language-plaintext highlighter-rouge">false</code>, nothing, or a
missing value. Those aren’t any programming language’s native rules, which is the <em>second</em> reason <code class="language-plaintext highlighter-rouge">eval</code>
would have quietly given wrong answers, even in a world with only one language. I implemented them
faithfully instead.</p>

<p>The tempting mistake is to import a rule from a <em>similar</em> feature next door. People often assume an empty
list should count as “false”, but that instinct comes from <em>other</em> condition types (the ones built on
JSONPath or XPath, where an empty match means failure). <code class="language-plaintext highlighter-rouge">simple</code> has no such rule. The library follows the
plain reading of the spec: a condition passes on any truthy value and fails only on <code class="language-plaintext highlighter-rouge">false</code>, <code class="language-plaintext highlighter-rouge">null</code>, or a
missing value. An empty list is none of those, so this evaluator treats it as truthy and passes it.</p>

<p>And that word “truthy” hides a real gap. The spec never defines whether an empty array is truthy, so the
answer falls to the host language, and languages disagree: <code class="language-plaintext highlighter-rouge">[]</code> is truthy in JavaScript but falsy in
Python (<code class="language-plaintext highlighter-rouge">bool([])</code> is <code class="language-plaintext highlighter-rouge">False</code>). This library runs on JavaScript, so <code class="language-plaintext highlighter-rouge">[]</code> passes; a Python runner could
fail the very same condition. It’s the ambiguous <code class="language-plaintext highlighter-rouge">=</code> all over again, just quieter: where the spec stays
silent, two conformant runners can still diverge. I’ve since opened
<a href="https://github.com/OAI/Arazzo-Specification/issues/517">an Arazzo spec issue</a> to pin these truthiness
rules down.</p>

<h2 id="why-this-matters-beyond-arazzo">Why this matters beyond Arazzo</h2>

<p>Strip away the specifics and you’re left with something you’ll recognize once you’ve seen it:</p>

<ul>
  <li>A web address sitting inside a sentence: where does the link end and the prose resume?</li>
  <li>A file name that’s allowed to contain spaces, listed next to other file names.</li>
  <li>Any place where you paste one kind of expression into the middle of another.</li>
</ul>

<p>The general truth is this: <strong>whenever you embed one little language inside another and they share the
same symbols, someone has to decide where one ends and the other begins, and if those symbols overlap
with no way to escape it, some inputs are simply undecidable.</strong> That’s not a bug you failed to
fix. It’s a fact about the input. Good engineering isn’t pretending the fact away; it’s choosing <em>where</em>
to make the tradeoff, making the common case feel effortless, and naming the rare case that
can’t work.</p>

<p>Arazzo’s <a href="https://spec.openapis.org/arazzo/v1.1.0.html#criterion-object"><code class="language-plaintext highlighter-rouge">simple</code> condition</a> is just a crisp, real example: a very permissive runtime-expression language, a
comparison language on top, a shared set of symbols, and a spec that offers no escape. So I made the
everyday conditions read exactly as intended, and I wrote down the one they can’t.</p>

<h2 id="for-the-curious-the-grammar">For the curious: the grammar</h2>

<p>Feel free to skip this part. But I promised a definition that lives above any single language, so here it
is. The Arazzo spec describes <code class="language-plaintext highlighter-rouge">simple</code> only in prose and never gives it a formal grammar, so this is an
original formalization: an <a href="https://www.rfc-editor.org/rfc/rfc5234">ABNF</a> grammar, the same portable
notation the internet’s specs are written in, that any implementation in any language can follow to the
same result.</p>

<style>
  details.abnf-grammar {
    border: 1px solid #dee2e6;
    border-radius: .5rem;
    background: #f8f9fa;
    padding: .6rem 1rem;
    margin: 1.5rem 0 2rem;
  }
  details.abnf-grammar > summary {
    cursor: pointer;
    font-weight: 600;
    color: #343a40;
    list-style: none;
  }
  details.abnf-grammar > summary::-webkit-details-marker { display: none; }
  details.abnf-grammar > summary::before {
    content: "\25B8";
    display: inline-block;
    margin-right: .5rem;
    color: #fd7e14;
    transition: transform .15s ease;
  }
  details.abnf-grammar[open] > summary::before { transform: rotate(90deg); }
  details.abnf-grammar[open] > summary { margin-bottom: 1rem; }
  details.abnf-grammar .highlight { margin-bottom: 0; }
</style>

<details class="abnf-grammar">
  <summary>Show the full ABNF grammar</summary>

  <figure class="highlight"><pre><code class="language-text" data-lang="text">condition        = S logical-expr S

logical-expr     = logical-or-expr
logical-or-expr  = logical-and-expr *( S "||" S logical-and-expr )
logical-and-expr = basic-expr *( S "&amp;&amp;" S basic-expr )

basic-expr       = paren-expr / comparison-expr / test-expr
paren-expr       = [ logical-not-op S ] "(" S logical-expr S ")"
test-expr        = [ logical-not-op S ] comparable
comparison-expr  = comparable S comparison-op S comparable

logical-not-op   = "!"
comparison-op    = "==" / "!=" / "&lt;=" / "&gt;=" / "&lt;" / "&gt;"

comparable                 = literal / runtime-expression-operand
runtime-expression-operand = "$" 1*operand-char
operand-char               = %x22-25 / %x27 / %x2A-3B / %x3F-5A / %x5B-5D / %x5E-7A / %x7E / %x80-10FFFF

runtime-expression-navigation = 1*( member-access / index-access )
member-access                 = "." member-name
index-access                  = "[" index "]"
member-name                   = 1*( ALPHA / DIGIT / "_" / "-" )
index                         = 1*DIGIT

literal          = number / string / boolean / null
boolean          = "true" / "false"
null             = "null"

number           = ( int / "-0" ) [ frac ] [ exp ]
int              = "0" / ( [ "-" ] DIGIT1 *DIGIT )
frac             = "." 1*DIGIT
exp              = ( "e" / "E" ) [ "-" / "+" ] 1*DIGIT
DIGIT1           = %x31-39

string           = squote *( escaped-quote / string-char ) squote
escaped-quote    = squote squote
string-char      = %x20-26 / %x28-10FFFF
squote           = %x27

S                = *B
B                = %x20 / %x09 / %x0A / %x0D

ALPHA          = %x41-5A / %x61-7A
DIGIT          = %x30-39</code></pre></figure>

</details>

<p>Three things in there tie straight back to the story:</p>

<ul>
  <li><strong>The bounded token.</strong> <code class="language-plaintext highlighter-rouge">runtime-expression-operand = "$" 1*operand-char</code> is the “grab the whole <code class="language-plaintext highlighter-rouge">$…</code>
chunk” step. Look closely at <code class="language-plaintext highlighter-rouge">operand-char</code>: it is deliberately every character <em>except</em> whitespace and
the operator symbols <code class="language-plaintext highlighter-rouge">! &amp; ( ) &lt; = &gt; { | }</code>. That one exclusion is what makes a runtime expression stop
at the comparison, and it is the very same reason this unfenced runtime-expression operand can’t
<em>contain</em> those symbols. Both faces of the problem, one line of grammar.</li>
  <li><strong>Runtime expressions aren’t defined here.</strong> Notice the grammar never says what a valid runtime
expression actually <em>is</em>. It only captures the <code class="language-plaintext highlighter-rouge">$…</code> token, then delegates the real check to
arazzo-runtime-expression afterwards. The grammar matches the rough shape; the second pass confirms it.</li>
  <li><strong>The two-layer spine.</strong> The <code class="language-plaintext highlighter-rouge">logical-*</code> and <code class="language-plaintext highlighter-rouge">comparison-expr</code> rules are adapted from
<a href="https://www.rfc-editor.org/rfc/rfc9535">RFC 9535</a> (the JSONPath standard). A logical layer composes
booleans over a <em>flat</em>, non-recursive comparison layer, which is why a chained comparison like
<code class="language-plaintext highlighter-rouge">$a &lt; 200 &lt; 300</code> isn’t merely rejected, it’s ungrammatical: the rules give you no way to even write it.</li>
</ul>

<h2 id="try-it">Try it</h2>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">$ </span>npm <span class="nb">install</span> @swaggerexpert/arazzo-criterion</code></pre></figure>

<div class="list-group mb-3">
  <a href="https://github.com/swaggerexpert/arazzo-criterion" class="list-group-item list-group-item-action" target="_blank" rel="noopener noreferrer">
    <div class="d-flex w-100 justify-content-between">
      <h2 class="h5 mb-1"><i class="fa-brands fa-github"></i> @swaggerexpert/arazzo-criterion</h2>
    </div>
    <blockquote class="blockquote fs-6 mb-1">
      Reads, validates, and evaluates the <em>simple</em> conditions of the Arazzo Criterion Object.
    </blockquote>
    <script type="application/ld+json">
      {
        "@context": "https://schema.org",
        "@type": "SoftwareSourceCode",
        "author": { "@id": "https://vladimirgorej.com" },
        "name": "@swaggerexpert/arazzo-criterion",
        "abstract": "Reads, validates, and evaluates the simple conditions of the Arazzo Criterion Object.",
        "codeRepository": "https://github.com/swaggerexpert/arazzo-criterion"
      }
    </script>
  </a>
</div>

<p>It’s part of <a href="https://github.com/swaggerexpert">SwaggerExpert</a>, a family of libraries that formalize the
expression languages around OpenAPI and Arazzo:</p>

<ul>
  <li><a href="https://github.com/swaggerexpert/arazzo-runtime-expression" target="_blank" rel="noopener noreferrer">arazzo-runtime-expression</a> (the runtime-expression parser this library asks)</li>
  <li><a href="https://github.com/swaggerexpert/openapi-runtime-expression" target="_blank" rel="noopener noreferrer">openapi-runtime-expression</a> (its OpenAPI sibling)</li>
  <li><a href="https://github.com/swaggerexpert/jsonpath" target="_blank" rel="noopener noreferrer">jsonpath</a> and <a href="https://github.com/swaggerexpert/json-pointer" target="_blank" rel="noopener noreferrer">json-pointer</a> (the pointer syntaxes that give an escape hatch)</li>
</ul>

<h2 id="closing-words">Closing words</h2>

<p>I wanted one thing: software that reads a condition the way its author meant it. For everyday conditions,
it does. The trick is that it never guesses. It asks the runtime-expression parser where each expression
ends, and trusts the answer. One case stays out of reach: a name that contains a symbol like <code class="language-plaintext highlighter-rouge">=</code>, which
the spec gives no way to tell apart from the operator. No tool can evaluate that one safely, so rather than
guess, mine refuses it and says why. That is the condition in the title.</p>

<p>And it doesn’t end here. <code class="language-plaintext highlighter-rouge">simple</code> still has no formal grammar in the spec, and its evaluation rules don’t
say how a bare value like an empty array should be treated. So I’ve taken both questions back to the Arazzo
specification: one issue to <a href="https://github.com/OAI/Arazzo-Specification/issues/517">define the evaluation semantics</a>,
and one to <a href="https://github.com/OAI/Arazzo-Specification/issues/518">define a normative grammar and settle the operand boundary</a>.
Some of it may land in a future version of the spec. That is already how the earlier runtime-expression
fixes reached Arazzo 1.1.0, and it is where a definition meant to be shared belongs.</p>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[Arazzo simple conditions look like plain comparisons, but they combine runtime expressions with their own comparison language. Most cases can be parsed faithfully. One case cannot. This is how @swaggerexpert/arazzo-criterion handles the boundary without pretending ambiguity away.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vladimirgorej.com/assets/img/blog/arazzo-criterion.webp" /><media:content medium="image" url="https://vladimirgorej.com/assets/img/blog/arazzo-criterion.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">‘Off Spec’ № 01: On OpenAPI, agents, and what specs are actually for</title><link href="https://vladimirgorej.com/blog/off-spec-01-openapi-agents-and-what-specs-are-actually-for/" rel="alternate" type="text/html" title="‘Off Spec’ № 01: On OpenAPI, agents, and what specs are actually for" /><published>2026-07-03T08:00:00+00:00</published><updated>2026-07-03T08:00:00+00:00</updated><id>https://vladimirgorej.com/blog/off-spec-01-openapi-agents-and-what-specs-are-actually-for</id><content type="html" xml:base="https://vladimirgorej.com/blog/off-spec-01-openapi-agents-and-what-specs-are-actually-for/"><![CDATA[<style>
  /* page-specific styles for this interview */
  .interview-toc {
    background: #f8f9fa;
    border: 1px solid #dee2e6;
    border-radius: .5rem;
    padding: 1.25rem 1.5rem;
    margin: 1.5rem 0 2.5rem;
  }
  .interview-toc__title {
    font-size: .85rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: #6c757d;
    margin-bottom: .75rem;
  }
  .interview-toc ol {
    margin: 0;
    padding-left: 1.25rem;
    columns: 2;
    column-gap: 2rem;
  }
  @media (max-width: 575px) {
    .interview-toc ol { columns: 1; }
  }
  .interview-toc li { margin-bottom: .35rem; break-inside: avoid; }
  .interview-toc a { text-decoration: none; }
  .interview-toc a:hover { text-decoration: underline; }

  .interview-q {
    margin-top: 2.75rem;
    margin-bottom: 1rem;
    scroll-margin-top: 1rem;
  }
  .interview-q__num {
    color: #fd7e14;
    font-weight: 700;
    margin-right: .15rem;
  }

  .interview-pullquote {
    margin: 1.75rem 0;
    padding: .25rem 0 .25rem 1.25rem;
    border-left: 4px solid #fd7e14;
    font-size: 1.2rem;
    font-style: italic;
    line-height: 1.5;
    color: #495057;
  }

  .interview-lightning { margin: 1rem 0 0; }
  .interview-lightning dt { font-weight: 600; color: #212529; }
  .interview-lightning dd { margin-bottom: 1rem; }
</style>

<p class="lead">
  <em>'Off Spec'</em>, brought to you by APIwiz, is a conversation series about how APIs actually get built,
  maintained, governed, and consumed at scale — what the specs don't capture. For № 01,
  <a href="https://www.linkedin.com/in/shane-o-connor-apiwiz/" target="_blank" rel="noopener noreferrer">Shane O'Connor</a>
  put fifteen questions to me. This is a re-post of the
  <a href="https://www.linkedin.com/pulse/off-spec-01-vladim%C3%ADr-gorej-char0n-openapi-agents-what-shane-o-connor-yw5ge/" target="_blank" rel="noopener noreferrer">original interview</a>.
</p>

<p>I spent five years as a core maintainer of Swagger tools. Before that I was principal engineer at Apiary, home of
API Blueprint. I’m a maintainer of the AsyncAPI specification. And today I co-found SpecLynx and work as an AI
engineer at Jentic, where the two ends of my career — specifications and agents — are finally colliding.</p>

<nav class="interview-toc">
  <div class="interview-toc__title">Jump to a question</div>
  <ol>
    <li><a href="#q1">What no spec will ever capture</a></li>
    <li><a href="#q2">What actually killed API Blueprint</a></li>
    <li><a href="#q3">The decision almost nobody noticed</a></li>
    <li><a href="#q4">Was OpenAPI 3.1 worth the pain?</a></li>
    <li><a href="#q5">API wisdom that's just wrong</a></li>
    <li><a href="#q6">What teams miss about specs</a></li>
    <li><a href="#q7">One feature I'd delete</a></li>
    <li><a href="#q8">Why event-driven feels second-class</a></li>
    <li><a href="#q9">Was OpenAPI written for agents?</a></li>
    <li><a href="#q10">Will hand-crafted specs get bypassed?</a></li>
    <li><a href="#q11">MCP: reinventing OpenAPI?</a></li>
    <li><a href="#q12">What I built that the world didn't want</a></li>
    <li><a href="#q13">The unglamorous plumbing that matters</a></li>
    <li><a href="#q14">Who actually pays for open source</a></li>
    <li><a href="#q15">Lightning round</a></li>
  </ol>
</nav>

<h2 id="q1" class="interview-q h4"><span class="interview-q__num">1.</span> The series is called Off Spec. So let's start there: what's something about how APIs actually get built and consumed that no specification will ever capture?</h2>

<p>No specification fully captures the original author’s intent.</p>

<p>A spec can describe paths, schemas, status codes, and examples, but it rarely explains why the API was designed
that way: the trade-offs, constraints, assumptions, and consumer needs behind it.</p>

<p>That intent matters. Without it, people can follow the contract and still miss the design.</p>

<p class="interview-pullquote">The real API is not just the spec. It is the spec plus the documentation, SDKs, examples, error messages, and production behavior people come to rely on.</p>

<h2 id="q2" class="interview-q h4"><span class="interview-q__num">2.</span> You were principal engineer at Apiary, home of API Blueprint, a spec that genuinely competed with Swagger. What actually killed Blueprint, and what did OpenAPI get right that it didn't?</h2>

<p>Blueprint was not killed by one thing. It was a mix of tooling economics, industry convergence, and probably
corporate gravity after Apiary.</p>

<p>API Blueprint had a beautiful authoring experience. It was readable, elegant, and very human-friendly. But
OpenAPI was more explicit and easier for a broad tooling ecosystem to build around: generators, validators,
gateways, documentation tools, and enterprise platforms.</p>

<p>Once the industry started converging on OpenAPI, even Apiary had to support it. At that point Blueprint was no
longer just competing on specification quality; it was competing against a network effect.</p>

<p>So I would not say Blueprint lost because it was bad. It lost because OpenAPI became the safer ecosystem bet, and
after the Apiary/Oracle chapter there was not enough independent momentum left to reverse that.</p>

<h2 id="q3" class="interview-q h4"><span class="interview-q__num">3.</span> Five years maintaining Swagger tools that millions of developers depend on. What's the most consequential decision you made that almost nobody noticed?</h2>

<p>Probably Swagger ApiDOM.</p>

<p>We built Swagger ApiDOM as a semantic parser for API specifications, a unified way to parse, process, transform,
and reason about specs across the Swagger ecosystem. The important part is that we integrated it in a completely
backward-compatible way.</p>

<p>For most users, the only visible effect was that OpenAPI 3.1 support arrived. But underneath, it changed the
foundation: Swagger tooling no longer had to treat every spec version and format as a separate world. It also
opened the door to supporting AsyncAPI, Arazzo, and Overlays through the same underlying model.</p>

<p class="interview-pullquote">Good infrastructure becomes invisible when it works well.</p>

<h2 id="q4" class="interview-q h4"><span class="interview-q__num">4.</span> OpenAPI 3.1 finally aligned with JSON Schema 2020-12 after years of divergence, and you built the renderer for it. Was that alignment worth the pain, or did it fix a problem most users never actually had?</h2>

<p>Yes, it was worth the pain.</p>

<p>Most real-world specifications were still OpenAPI 3.0.x, but there was clear interest in OpenAPI 3.1 support. I do
not think most users cared about JSON Schema 2020-12 alignment as a standards debate. They cared that newer
specifications worked, validation was more consistent, and tooling could support OpenAPI 3.1 properly.</p>

<p>For tooling authors, the old divergence between OpenAPI Schema Objects and JSON Schema created years of special
cases across renderers, validators, parsers, and generators. OpenAPI 3.1 did not remove all complexity, but it did
move the ecosystem closer to a shared foundation.</p>

<p>So yes, it fixed a real problem, just not always at the layer where most users described it.</p>

<h2 id="q5" class="interview-q h4"><span class="interview-q__num">5.</span> What's a piece of received wisdom in the API world that you think is just wrong? "Design-first always," "REST is dead," "spec-driven everything." Get as spicy as you want.</h2>

<p>The received wisdom I think is wrong is that specs are mainly for documentation.</p>

<p>That view is too small. In today’s world, an API specification should be treated more like infrastructure-as-code:
a living description of the system that is versioned, tested, checked against reality, and kept up to date.</p>

<p>A spec should participate in the lifecycle of the API: design, review, validation, testing, governance, SDK
generation, agent consumption, and runtime checks.</p>

<p class="interview-pullquote">The future is not "write a spec to produce nice documentation." The future is "use the spec to prove the API, the tooling, and the consumers still agree."</p>

<h2 id="q6" class="interview-q h4"><span class="interview-q__num">6.</span> Most teams treat the OpenAPI spec as a documentation output, something you generate at the end. What are they fundamentally missing about what a spec is for?</h2>

<p>They are missing the feedback loop.</p>

<p>If the spec is generated only at the end, it can tell consumers what the API does, but it cannot help the team
catch problems while the API is still being shaped.</p>

<p>A good spec should be part of the pull request, the CI pipeline, and the release process. It should catch breaking
changes, validate examples, drive contract tests, generate SDKs, and give consumers something stable to review and
build against as the API evolves.</p>

<p>Documentation is one output of a spec. The bigger job is keeping design, implementation, tooling, and consumers
aligned.</p>

<h2 id="q7" class="interview-q h4"><span class="interview-q__num">7.</span> If you could delete one feature from the OpenAPI spec, and one thing from your own past code, what goes, and who'd be mad about it?</h2>

<p>From OpenAPI 3.1, I would probably delete the jsonSchemaDialect fixed field.</p>

<p>Most people think of OpenAPI 3.1 as “the version that aligned with JSON Schema 2020-12.” But technically, through
jsonSchemaDialect, an OpenAPI document can declare a different JSON Schema dialect, including previous drafts or
even a custom dialect.</p>

<p>In theory, that flexibility makes sense. In practice, I have not seen many OpenAPI documents that actually use it.
The complexity it brings to tooling implementation feels disproportionate. Parsers, validators, renderers, and
generators suddenly have to care not just about OpenAPI 3.1, but about which JSON Schema dialect is active and what
behavior that implies.</p>

<p>From my own past code, I would happily delete the code that tried to provide full support for jsonSchemaDialect.
It was the kind of implementation work where the theoretical correctness was real, but the practical value for most
users was hard to justify.</p>

<p>Who would be mad? Probably the very small number of people who really need custom dialect support. But I suspect
most users would happily trade that flexibility for simpler, more predictable tooling.</p>

<h2 id="q8" class="interview-q h4"><span class="interview-q__num">8.</span> You also maintain AsyncAPI. Why does event-driven still feel like a second-class citizen in the API tooling world, and is that finally changing?</h2>

<p>Event-driven still feels second-class because it is harder to see.</p>

<p>With HTTP APIs, the interaction model is familiar: request, response, status code, payload. It maps naturally to
documentation, testing, gateways, SDKs, and mental models. Event-driven systems are different. The important
behavior is spread across producers, consumers, brokers, topics, retries, ordering, delivery guarantees, and time.</p>

<p>That makes the tooling problem harder. You are not just describing an endpoint; you are describing a system of
conversations that may happen asynchronously and indirectly.</p>

<p>I do think it is changing. AsyncAPI gives the industry a shared language for describing those systems, and as
workflows, agents, and event-driven architectures become more common, that language becomes more important.</p>

<h2 id="q9" class="interview-q h4"><span class="interview-q__num">9.</span> You're now an AI engineer at Jentic. LLMs and agents have quietly become the biggest consumers of API specs. Was OpenAPI ever written for machines, or for humans pretending to be machines?</h2>

<p>OpenAPI was written for machines, but not for agents.</p>

<p>It was designed so tooling could parse an API contract and generate documentation, clients, servers, validators,
tests, and governance checks. In that sense, it was absolutely machine-readable by design.</p>

<p>But agents are a different kind of consumer. They do not just need a valid contract or a generated client. They
need to decide whether an operation is relevant, what the side effects might be, how to combine calls, how to
recover from errors, and whether the API is safe to use in a given context.</p>

<p>That is where work like Jentic API Scorecard comes in: measuring whether an OpenAPI document is actually aligned
with how agents use specs.</p>

<p class="interview-pullquote">The question is no longer just "is this valid OpenAPI?" It is "does this spec make the API understandable, usable, and safe for an autonomous consumer?"</p>

<h2 id="q10" class="interview-q h4"><span class="interview-q__num">10.</span> Honest take: are we about to watch a generation of lovingly hand-crafted API specs get bypassed, because an agent would rather read the code or just hit the endpoint and see what happens?</h2>

<p>I think agents will bypass bad specs, not good ones.</p>

<p>If a specification is outdated, incomplete, or just a thin documentation artifact, then yes, an agent may get more
value from reading code, examples, traffic, or trying the endpoint directly. In that world, the lovingly
hand-crafted spec risks becoming decorative.</p>

<p>But a good OpenAPI document is still one of the best ways to give an agent a structured, intentional view of an
API. Code tells you what is implemented. Runtime behavior tells you what happens. A spec should tell you what is
intended, supported, stable, and safe to rely on.</p>

<p>So I do not think agents make specs irrelevant. I think they raise the bar.</p>

<p class="interview-pullquote">Specs that are stale will be ignored faster. Specs that are accurate, rich, and aligned with reality become even more important.</p>

<h2 id="q11" class="interview-q h4"><span class="interview-q__num">11.</span> MCP and tool schemas are suddenly everywhere. Is that the API world reinventing OpenAPI badly, or solving something OpenAPI genuinely couldn't?</h2>

<p>I do not think MCP and tool schemas are just OpenAPI reinvented badly. They are solving a different problem.</p>

<p>OpenAPI describes an API surface in detail: operations, parameters, schemas, responses, auth, errors, and
documentation. MCP is more focused on exposing capabilities to agents as callable tools.</p>

<p>That narrower focus is useful. An agent does not always need the full API description; it needs to know what action
it can take, what inputs are required, what result to expect, and whether the action is safe in context.</p>

<p>But I do think there is a risk of relearning old API lessons. Versioning, authentication, error handling,
pagination, compatibility, and governance all come back eventually.</p>

<p>So my take is: MCP is not a replacement for OpenAPI. It is an agent-facing runtime layer. The interesting future is
connecting them: using OpenAPI for rich API context, and MCP/tool schemas for the operational interface agents
actually call.</p>

<h2 id="q12" class="interview-q h4"><span class="interview-q__num">12.</span> You founded SpecLynx and re-built ApiDOM. Serious bets. What's something you poured yourself into that the world just didn't want, and were they right not to want it?</h2>

<p>SpecLynx is probably the clearest example.</p>

<p>We built a VS Code extension and editor around API specifications, and I still believe the problem was real. At the
time, a better human editing experience for specs made sense.</p>

<p>But the world has evolved. The more important surfaces now are MCP, LSP, CLIs, CI pipelines, and agent workflows,
places where specifications are not just written and viewed, but actively used, checked, transformed, and connected
to other systems.</p>

<p>Were people right not to want it? In that form, probably yes. Not because the idea was wrong, but because the
interface the world needs has changed. The future is less about people lovingly editing specs by hand, and more
about tools and agents continuously working with them.</p>

<h2 id="q13" class="interview-q h4"><span class="interview-q__num">13.</span> What's the least glamorous part of API tooling, the unsexy plumbing, that actually matters more than all the design-philosophy debates people love to have on Twitter?</h2>

<p>Proper parsing and reference resolution.</p>

<p>They are not glamorous, but they are where API tooling either becomes trustworthy or quietly falls apart. People
often think parsing means “read some JSON or YAML,” and reference resolution means “follow a <code class="language-plaintext highlighter-rouge">$ref</code>.” In reality,
both are much harder.</p>

<p>Good tooling has to understand the specification version, preserve semantic meaning, track source locations, handle
malformed input, and resolve references across files, URLs, anchors, bundles, dialects, and all the edge cases real
users create.</p>

<p>Everything else depends on that foundation: validation, rendering, editing, linting, code generation, governance,
testing, and now agent consumption.</p>

<p class="interview-pullquote">If parsing or resolution is wrong, every layer above it becomes unreliable.</p>

<h2 id="q14" class="interview-q h4"><span class="interview-q__num">14.</span> Open source at Swagger's scale: what's the honest story about who actually pays for the tooling the entire API industry quietly runs on?</h2>

<p>The honest story is that open source at this scale is often paid for indirectly, but it is paid for.</p>

<p>In the Swagger ecosystem, SmartBear played a central role. It owned the Swagger brand, built commercial products
around it, and funded engineering work on open source tooling that mattered both to those products and to the wider
API community.</p>

<p>That model has real benefits. The ecosystem gets dedicated engineering time, and the company has a strategic reason
to invest. But it also creates tension: the tooling is used far beyond one company, while the responsibility for
maintaining it is not shared equally.</p>

<p>So the honest answer is that some of the work is funded by companies with a reason to invest, and some of it is
still carried by maintainers because the ecosystem depends on it.</p>

<h2 id="q15" class="interview-q h4"><span class="interview-q__num">15.</span> Lightning round. One word or one line each.</h2>

<dl class="interview-lightning row">
  <dt class="col-sm-4">Design-first or code-first?</dt>
  <dd class="col-sm-8">Contract-first.</dd>

  <dt class="col-sm-4">OpenAPI or GraphQL schema?</dt>
  <dd class="col-sm-8">OpenAPI for action-oriented APIs; GraphQL schema when clients need to query related data flexibly.</dd>

  <dt class="col-sm-4">Most overrated API tool?</dt>
  <dd class="col-sm-8">Static API portals.</dd>

  <dt class="col-sm-4">Most underrated?</dt>
  <dd class="col-sm-8">A good parser.</dd>

  <dt class="col-sm-4">Will REST still dominate in 2030?</dt>
  <dd class="col-sm-8">Yes, but agents will care more about clear operations, good descriptions, predictable errors, and safety than whether the API is perfectly RESTful.</dd>
</dl>

<hr />

<p class="text-muted">
  This interview originally appeared on LinkedIn as part of APIwiz's <em>'Off Spec'</em> series. Read the
  <a href="https://www.linkedin.com/pulse/off-spec-01-vladim%C3%ADr-gorej-char0n-openapi-agents-what-shane-o-connor-yw5ge/" target="_blank" rel="noopener noreferrer">original article</a>.
</p>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[In the first edition of APIwiz's 'Off Spec' series, Shane O'Connor put fifteen questions to Vladimír Gorej about how APIs actually get built, maintained, and consumed at scale — from what killed API Blueprint, to why OpenAPI was written for machines but not for agents, to what specs are really for once machines start reading them instead of humans.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vladimirgorej.com/assets/img/blog/off-spec-01.webp" /><media:content medium="image" url="https://vladimirgorej.com/assets/img/blog/off-spec-01.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Advanced memoization for JavaScript functions with lodash.memoize</title><link href="https://vladimirgorej.com/blog/advanced-memoization-for-javascript-functions-with-lodash-memoize/" rel="alternate" type="text/html" title="Advanced memoization for JavaScript functions with lodash.memoize" /><published>2022-02-05T18:00:00+00:00</published><updated>2022-02-05T18:00:00+00:00</updated><id>https://vladimirgorej.com/blog/advanced-memoization-for-javascript-functions-with-lodash-memoize</id><content type="html" xml:base="https://vladimirgorej.com/blog/advanced-memoization-for-javascript-functions-with-lodash-memoize/"><![CDATA[<blockquote class="blockquote lead">
  <p>
    "In computing, memoization or memoisation is an optimization technique used primarily to speed up 
    computer programs by storing the results of expensive function calls and returning the cached result
    when the same inputs occur again."
  </p>
  <footer class="blockquote-footer"><cite title="Wikipedia on Memoization">Wikipedia on Memoization</cite></footer>
</blockquote>

<p><a href="https://lodash.com/" target="_blank" rel="noopener noreferrer">Lodash</a> is a modern JavaScript utility library delivering modularity, performance &amp; extras.
It’s a ubiquitous library, and your project probably uses it, even if you’re not aware of it.
Lodash contains an implementation of memoization in the form of <a href="https://lodash.com/docs/4.17.15#memoize" target="_blank" rel="noopener noreferrer">memoize</a> function.</p>

<p>By default, memoize function only uses the first argument provided to the memoized function as the cache key. 
Here is the simplest possible example of memoization:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="code"><pre><span class="kd">const</span> <span class="p">{</span> <span class="nx">memoize</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">lodash</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">func</span> <span class="o">=</span> <span class="p">(</span><span class="nx">arg1</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">`My name is </span><span class="p">${</span><span class="nx">arg1</span><span class="p">}</span><span class="s2">`</span>
<span class="kd">const</span> <span class="nx">funcM</span> <span class="o">=</span> <span class="nx">memoize</span><span class="p">(</span><span class="nx">func</span><span class="p">);</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">(</span><span class="dl">'</span><span class="s1">John</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// =&gt; 'My name is John'</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">(</span><span class="dl">'</span><span class="s1">John</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// =&gt; 'My name is John' (cache hit)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">.</span><span class="nx">cache</span><span class="p">.</span><span class="nx">size</span><span class="p">);</span> <span class="c1">// =&gt; 1</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>It’s important to realize that the cache key of the memoized function is determined using <a href="https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluezero" target="_blank" rel="noopener noreferrer">SameValueZero</a> comparison:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="code"><pre><span class="kd">const</span> <span class="p">{</span> <span class="nx">memoize</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">lodash</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">func</span> <span class="o">=</span> <span class="p">(</span><span class="nx">arg1</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">arg1</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">funcM</span> <span class="o">=</span> <span class="nx">memoize</span><span class="p">(</span><span class="nx">func</span><span class="p">);</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">({</span><span class="na">a</span><span class="p">:</span> <span class="mi">1</span><span class="p">}));</span> <span class="c1">// =&gt; '{"a":1}'</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">({</span><span class="na">a</span><span class="p">:</span> <span class="mi">1</span><span class="p">}));</span> <span class="c1">// =&gt; '{"a":1}'</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">.</span><span class="nx">cache</span><span class="p">.</span><span class="nx">size</span><span class="p">);</span> <span class="c1">// =&gt; 2</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>Now, what if we introduce more arguments to our func?</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="code"><pre><span class="kd">const</span> <span class="p">{</span> <span class="nx">memoize</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">lodash</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">func</span> <span class="o">=</span> <span class="p">(</span><span class="nx">arg1</span><span class="p">,</span> <span class="nx">arg2</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">`</span><span class="p">${</span><span class="nx">arg1</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="nx">arg2</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">funcM</span> <span class="o">=</span> <span class="nx">memoize</span><span class="p">(</span><span class="nx">func</span><span class="p">);</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">(</span><span class="dl">'</span><span class="s1">John</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Doe</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// =&gt; "John Doe"</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">(</span><span class="dl">'</span><span class="s1">John</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">FooBar</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// =&gt; "John Doe" (cache hit)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">.</span><span class="nx">cache</span><span class="p">.</span><span class="nx">size</span><span class="p">);</span> <span class="c1">// =&gt; 2</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>We can see that there is an obvious problem. As stated above, lodash only uses the first argument
as the cache key. To fix this, we have to use resolver function:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="code"><pre><span class="kd">const</span> <span class="p">{</span> <span class="nx">memoize</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">lodash</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">func</span> <span class="o">=</span> <span class="p">(</span><span class="nx">arg1</span><span class="p">,</span> <span class="nx">arg2</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">`</span><span class="p">${</span><span class="nx">arg1</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="nx">arg2</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">resolver</span> <span class="o">=</span> <span class="p">(</span><span class="nx">arg1</span><span class="p">,</span> <span class="nx">arg2</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([</span><span class="nx">arg1</span><span class="p">,</span> <span class="nx">arg2</span><span class="p">]);</span>
<span class="kd">const</span> <span class="nx">funcM</span> <span class="o">=</span> <span class="nx">memoize</span><span class="p">(</span><span class="nx">func</span><span class="p">,</span> <span class="nx">resolver</span><span class="p">);</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">(</span><span class="dl">'</span><span class="s1">John</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Doe</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// =&gt; "John Doe"</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">(</span><span class="dl">'</span><span class="s1">John</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">FooBar</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// =&gt; "John FooBar"</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">.</span><span class="nx">cache</span><span class="p">.</span><span class="nx">size</span><span class="p">);</span> <span class="c1">// =&gt; 2</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>This resolver solution obviously wouldn’t scale. What if our func needs to consume complex objects as arguments?
That’s fine unless the object arguments are huge or contain a cyclic reference.</p>

<p>When arguments are huge objects, we’ll spend too much CPU time serializing those objects into JSON strings 
to determine the cache key. And instead of the SameValueZero comparison for objects, we’ll be comparing huge strings.
When object arguments contain a cyclic reference, we’ll not be able to serialize them into JSON string and determine the cache key.</p>

<p>Lodash has one remaining trick up its sleeve to overcome these two problems - being able to influence how 
cache keys are maintained. To achieve our goal, we’ll have to create a specialization of <code class="language-plaintext highlighter-rouge">lodash.memoize</code>;
the function called <code class="language-plaintext highlighter-rouge">memoizeN</code>:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
</pre></td><td class="code"><pre><span class="kd">const</span> <span class="p">{</span> <span class="nx">memoize</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">lodash</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">sameValueZero</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">===</span> <span class="nx">y</span> <span class="o">||</span> <span class="p">(</span><span class="nb">Number</span><span class="p">.</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nb">Number</span><span class="p">.</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">y</span><span class="p">));</span>
<span class="kd">const</span> <span class="nx">shallowArrayEquals</span> <span class="o">=</span> <span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nx">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span>
    <span class="o">&amp;&amp;</span> <span class="nx">a</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="nx">b</span><span class="p">.</span><span class="nx">length</span>
    <span class="o">&amp;&amp;</span> <span class="nx">a</span><span class="p">.</span><span class="nx">every</span><span class="p">((</span><span class="nx">val</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">sameValueZero</span><span class="p">(</span><span class="nx">val</span><span class="p">,</span> <span class="nx">b</span><span class="p">[</span><span class="nx">index</span><span class="p">]));</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">list</span> <span class="o">=</span> <span class="p">(...</span><span class="nx">args</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">args</span><span class="p">;</span>

<span class="kd">class</span> <span class="nx">Cache</span> <span class="kd">extends</span> <span class="nb">Map</span> <span class="p">{</span>
  <span class="k">delete</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">keys</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">keys</span><span class="p">());</span>
    <span class="kd">const</span> <span class="nx">foundKey</span> <span class="o">=</span> <span class="nx">keys</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="nx">shallowArrayEquals</span><span class="p">(</span><span class="nx">key</span><span class="p">));</span>
    <span class="k">return</span> <span class="k">super</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">foundKey</span><span class="p">);</span>
  <span class="p">}</span>
  <span class="kd">get</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">keys</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">keys</span><span class="p">());</span>
    <span class="kd">const</span> <span class="nx">foundKey</span> <span class="o">=</span> <span class="nx">keys</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="nx">shallowArrayEquals</span><span class="p">(</span><span class="nx">key</span><span class="p">));</span>
    <span class="k">return</span> <span class="k">super</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">foundKey</span><span class="p">);</span>
  <span class="p">}</span>
  <span class="nx">has</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">keys</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">keys</span><span class="p">());</span>
    <span class="k">return</span> <span class="nx">keys</span><span class="p">.</span><span class="nx">findIndex</span><span class="p">(</span><span class="nx">shallowArrayEquals</span><span class="p">(</span><span class="nx">key</span><span class="p">))</span> <span class="o">!==</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">memoizeN</span> <span class="o">=</span> <span class="p">(</span><span class="nx">fn</span><span class="p">,</span> <span class="p">{</span> <span class="nx">resolver</span> <span class="o">=</span> <span class="nx">list</span> <span class="p">}</span> <span class="o">=</span> <span class="p">{})</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="na">Cache</span><span class="p">:</span> <span class="nx">OriginalCache</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">memoize</span><span class="p">;</span>
  <span class="nx">memoize</span><span class="p">.</span><span class="nx">Cache</span> <span class="o">=</span> <span class="nx">Cache</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">memoized</span> <span class="o">=</span> <span class="nx">memoize</span><span class="p">(</span><span class="nx">fn</span><span class="p">,</span> <span class="nx">resolver</span><span class="p">);</span>
  <span class="nx">memoize</span><span class="p">.</span><span class="nx">Cache</span> <span class="o">=</span> <span class="nx">OriginalCache</span><span class="p">;</span>
  <span class="k">return</span> <span class="nx">memoized</span><span class="p">;</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>The real trick here is that the memoized function parameters are transformed into a list, and this list forms a cache key.
We override the <code class="language-plaintext highlighter-rouge">lodash.memoize.Cache</code> class on an ad-hoc basis with our implementation capable of doing cache key
lookups for our list cache keys.</p>

<p>Observe the following example:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="code"><pre><span class="kd">const</span> <span class="nx">complexObj</span> <span class="o">=</span> <span class="p">{</span><span class="na">a</span><span class="p">:</span> <span class="mi">1</span><span class="p">};</span>
<span class="kd">const</span> <span class="nx">simpleObj</span> <span class="o">=</span> <span class="p">{</span><span class="na">b</span><span class="p">:</span> <span class="mi">2</span><span class="p">};</span>

<span class="kd">const</span> <span class="nx">func</span> <span class="o">=</span> <span class="p">(</span><span class="nx">arg1</span><span class="p">,</span> <span class="nx">arg2</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">arg2</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">resolver</span> <span class="o">=</span> <span class="p">(</span><span class="nx">arg1</span><span class="p">,</span> <span class="nx">arg2</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">[</span><span class="nx">arg1</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">arg2</span><span class="p">)];</span>
<span class="kd">const</span> <span class="nx">funcM</span> <span class="o">=</span> <span class="nx">memoizeN</span><span class="p">(</span><span class="nx">func</span><span class="p">,</span> <span class="p">{</span> <span class="nx">resolver</span> <span class="p">});</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">(</span><span class="nx">complexObj</span><span class="p">,</span> <span class="nx">simpleObj</span><span class="p">));</span> <span class="c1">// =&gt; {b: 2}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">(</span><span class="nx">complexObj</span><span class="p">,</span> <span class="p">{</span><span class="na">b</span><span class="p">:</span> <span class="mi">2</span><span class="p">}));</span> <span class="c1">// =&gt; {b: 2} (cache hit)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">({</span><span class="na">a</span><span class="p">:</span> <span class="mi">1</span><span class="p">},</span> <span class="p">{</span><span class="na">b</span><span class="p">:</span> <span class="mi">2</span><span class="p">}));</span> <span class="c1">// =&gt; {b: 2}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">.</span><span class="nx">cache</span><span class="p">.</span><span class="nx">size</span><span class="p">);</span> <span class="c1">// =&gt; 2</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>We intend to memoize func using both arguments to form the cache key. For the first argument,
use the SameValueZero comparison on the original value. For the second one, use SameValueZero comparison
on JSON stringified value.</p>

<p>We can go even further with the specialization to make <code class="language-plaintext highlighter-rouge">memoizeN</code> consume a list of comparator functions
applied to appropriate arguments instead of abusing a resolver to serialize some arguments.</p>

<p>The implementation will look as follows:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
</pre></td><td class="code"><pre><span class="kd">const</span> <span class="nx">memoize</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">lodash/memoize</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">isEqual</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">lodash/isEqual</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">sameValueZero</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">===</span> <span class="nx">y</span> <span class="o">||</span> <span class="p">(</span><span class="nb">Number</span><span class="p">.</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nb">Number</span><span class="p">.</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">y</span><span class="p">));</span>
<span class="kd">const</span> <span class="nx">makeShallowArrayEquals</span> <span class="o">=</span> <span class="p">(</span><span class="nx">comparators</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nx">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span>
    <span class="o">&amp;&amp;</span> <span class="nx">a</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="nx">b</span><span class="p">.</span><span class="nx">length</span>
    <span class="o">&amp;&amp;</span> <span class="nx">a</span><span class="p">.</span><span class="nx">every</span><span class="p">((</span><span class="nx">val</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">comparator</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nx">comparators</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span> <span class="p">?</span> <span class="nx">comparators</span> <span class="p">:</span> <span class="nx">comparators</span><span class="p">[</span><span class="nx">index</span><span class="p">]</span> <span class="o">||</span> <span class="nx">sameValueZero</span><span class="p">;</span>
      <span class="k">return</span> <span class="nx">comparator</span><span class="p">(</span><span class="nx">val</span><span class="p">,</span> <span class="nx">b</span><span class="p">[</span><span class="nx">index</span><span class="p">]);</span>
    <span class="p">});</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">list</span> <span class="o">=</span> <span class="p">(...</span><span class="nx">args</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">args</span><span class="p">;</span>

<span class="kd">class</span> <span class="nx">Cache</span> <span class="kd">extends</span> <span class="nb">Map</span> <span class="p">{</span>
  <span class="nx">shallowArrayEquals</span> <span class="o">=</span> <span class="nx">makeShallowArrayEquals</span><span class="p">(</span><span class="nx">sameValueZero</span><span class="p">);</span>

  <span class="k">delete</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">keys</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">keys</span><span class="p">());</span>
    <span class="kd">const</span> <span class="nx">foundKey</span> <span class="o">=</span> <span class="nx">keys</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">shallowArrayEquals</span><span class="p">(</span><span class="nx">key</span><span class="p">));</span>
    <span class="k">return</span> <span class="k">super</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">foundKey</span><span class="p">);</span>
  <span class="p">}</span>
  <span class="kd">get</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">keys</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">keys</span><span class="p">());</span>
    <span class="kd">const</span> <span class="nx">foundKey</span> <span class="o">=</span> <span class="nx">keys</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">shallowArrayEquals</span><span class="p">(</span><span class="nx">key</span><span class="p">));</span>
    <span class="k">return</span> <span class="k">super</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">foundKey</span><span class="p">);</span>
  <span class="p">}</span>
  <span class="nx">has</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">keys</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">keys</span><span class="p">());</span>
    <span class="k">return</span> <span class="nx">keys</span><span class="p">.</span><span class="nx">findIndex</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">shallowArrayEquals</span><span class="p">(</span><span class="nx">key</span><span class="p">))</span> <span class="o">!==</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">memoizeN</span> <span class="o">=</span> <span class="p">(</span><span class="nx">fn</span><span class="p">,</span> <span class="p">{</span> <span class="nx">resolver</span> <span class="o">=</span> <span class="nx">list</span><span class="p">,</span> <span class="nx">comparators</span> <span class="o">=</span> <span class="nx">sameValueZero</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="na">Cache</span><span class="p">:</span> <span class="nx">OriginalCache</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">memoize</span><span class="p">;</span>
  <span class="nx">memoize</span><span class="p">.</span><span class="nx">Cache</span> <span class="o">=</span> <span class="nx">Cache</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">memoized</span> <span class="o">=</span> <span class="nx">memoize</span><span class="p">(</span><span class="nx">fn</span><span class="p">,</span> <span class="nx">resolver</span> <span class="o">||</span> <span class="nx">list</span><span class="p">);</span>
  <span class="nx">memoized</span><span class="p">.</span><span class="nx">cache</span><span class="p">.</span><span class="nx">shallowArrayEquals</span> <span class="o">=</span> <span class="nx">makeShallowArrayEquals</span><span class="p">(</span><span class="nx">comparators</span><span class="p">);</span>
  <span class="nx">memoize</span><span class="p">.</span><span class="nx">Cache</span> <span class="o">=</span> <span class="nx">OriginalCache</span><span class="p">;</span>
  <span class="k">return</span> <span class="nx">memoized</span><span class="p">;</span>
<span class="p">};</span>

<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">memoizeN</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>This version of memoizeN applies a specific comparator algorithm for each argument:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="code"><pre><span class="kd">const</span> <span class="nx">obj1</span> <span class="o">=</span> <span class="p">{</span><span class="na">a</span><span class="p">:</span> <span class="mi">1</span><span class="p">};</span>
<span class="kd">const</span> <span class="nx">obj2</span> <span class="o">=</span> <span class="p">{</span><span class="na">b</span><span class="p">:</span> <span class="mi">2</span><span class="p">};</span>
<span class="kd">const</span> <span class="nx">obj3</span> <span class="o">=</span> <span class="p">{</span><span class="na">c</span><span class="p">:</span> <span class="mi">3</span><span class="p">};</span>

<span class="kd">const</span> <span class="nx">func</span> <span class="o">=</span> <span class="p">(</span><span class="nx">arg1</span><span class="p">,</span> <span class="nx">arg2</span><span class="p">,</span> <span class="nx">arg3</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">arg3</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">strictEquality</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">===</span> <span class="nx">y</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">sameValueZero</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">===</span> <span class="nx">y</span> <span class="o">||</span> <span class="p">(</span><span class="nb">Number</span><span class="p">.</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nb">Number</span><span class="p">.</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">y</span><span class="p">));</span>
<span class="kd">const</span> <span class="nx">deepEquality</span> <span class="o">=</span> <span class="nx">lodash</span><span class="p">.</span><span class="nx">isEqual</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">comparators</span> <span class="o">=</span> <span class="p">[</span><span class="nx">strictEquality</span><span class="p">,</span> <span class="nx">sameValueZero</span><span class="p">,</span> <span class="nx">deepEquality</span><span class="p">];</span>
<span class="kd">const</span> <span class="nx">funcM</span> <span class="o">=</span> <span class="nx">memoizeN</span><span class="p">(</span><span class="nx">func</span><span class="p">,</span> <span class="p">{</span> <span class="nx">comparators</span> <span class="p">});</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">(</span><span class="nx">obj1</span><span class="p">,</span> <span class="nx">obj2</span><span class="p">,</span> <span class="nx">obj3</span><span class="p">));</span> <span class="c1">// =&gt; {c: 3}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">(</span><span class="nx">obj1</span><span class="p">,</span> <span class="nx">obj2</span><span class="p">,</span> <span class="p">{</span><span class="na">c</span><span class="p">:</span> <span class="mi">3</span><span class="p">}));</span> <span class="c1">// =&gt; {c: 3} (cache hit)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">(</span><span class="nx">obj1</span><span class="p">,</span> <span class="p">{</span><span class="na">b</span><span class="p">:</span> <span class="mi">2</span><span class="p">},</span> <span class="nx">obj3</span><span class="p">);</span> <span class="c1">// =&gt; {c: 3}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">.</span><span class="nx">cache</span><span class="p">.</span><span class="nx">size</span><span class="p">);</span> <span class="c1">// =&gt; 2</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>If we define <code class="language-plaintext highlighter-rouge">comparators</code> option as a function, the memoized function will use it to compare every argument. 
When comparators are not provided, SameValueZero comparison is used for all arguments.</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="code"><pre><span class="kd">const</span> <span class="nx">obj1</span> <span class="o">=</span> <span class="p">{</span><span class="na">a</span><span class="p">:</span> <span class="mi">1</span><span class="p">};</span>
<span class="kd">const</span> <span class="nx">obj2</span> <span class="o">=</span> <span class="p">{</span><span class="na">b</span><span class="p">:</span> <span class="mi">2</span><span class="p">};</span>
<span class="kd">const</span> <span class="nx">obj3</span> <span class="o">=</span> <span class="p">{</span><span class="na">c</span><span class="p">:</span> <span class="mi">3</span><span class="p">};</span>

<span class="kd">const</span> <span class="nx">func</span> <span class="o">=</span> <span class="p">(</span><span class="nx">arg1</span><span class="p">,</span> <span class="nx">arg2</span><span class="p">,</span> <span class="nx">arg3</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">arg3</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">sameValueZero</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">===</span> <span class="nx">y</span> <span class="o">||</span> <span class="p">(</span><span class="nb">Number</span><span class="p">.</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nb">Number</span><span class="p">.</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">y</span><span class="p">));</span>
<span class="kd">const</span> <span class="nx">funcM</span> <span class="o">=</span> <span class="nx">memoizeN</span><span class="p">(</span><span class="nx">func</span><span class="p">,</span> <span class="p">{</span> <span class="na">comparators</span><span class="p">:</span> <span class="nx">sameValueZero</span> <span class="p">});</span> <span class="c1">// equivalen with memoizeN(func)</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">(</span><span class="nx">obj1</span><span class="p">,</span> <span class="nx">obj2</span><span class="p">,</span> <span class="nx">obj3</span><span class="p">));</span> <span class="c1">// =&gt; {c: 3}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">(</span><span class="nx">obj1</span><span class="p">,</span> <span class="nx">obj2</span><span class="p">,</span> <span class="nx">obj3</span><span class="p">));</span> <span class="c1">// =&gt; {c: 3} // cache hit</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">funcM</span><span class="p">.</span><span class="nx">cache</span><span class="p">.</span><span class="nx">size</span><span class="p">);</span> <span class="c1">// =&gt; 1</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>I’ve extracted <code class="language-plaintext highlighter-rouge">memoizeN</code> into <a href="https://gist.github.com/char0n/2e6c77d38cf00faacceaf37e46b76a32" target="_blank" rel="noopener noreferrer">GitHub Gist</a> installable via npm. 
If you prefer to use <code class="language-plaintext highlighter-rouge">memoizeN</code> as npm package,
here is how to achieve that:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"> <span class="nv">$ </span>npm <span class="nb">install </span>gist:2e6c77d38cf00faacceaf37e46b76a32 <span class="nt">--save</span></code></pre></figure>

<p>This command will create the following entry inside your dependencies:</p>

<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"@char0n/memoizeN"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gist:2e6c77d38cf00faacceaf37e46b76a32"</span><span class="w">
</span><span class="p">}</span></code></pre></figure>

<p>Then use it via CommonJS or ESM:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">memoizeN</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">@char0n/memoizeN</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// or</span>
<span class="k">import</span> <span class="nx">memoizeN</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@char0n/memoizeN</span><span class="dl">'</span><span class="p">;</span></code></pre></figure>

<div class="list-group mb-3">
  <a href="https://gist.github.com/char0n/2e6c77d38cf00faacceaf37e46b76a32" class="list-group-item list-group-item-action" target="_blank" rel="noopener noreferrer">
    <div class="d-flex w-100 justify-content-between">
      <h2 class="h5 mb-1"><i class="fa-brands fa-github"></i> memoizeN</h2>
    </div>
    <blockquote class="blockquote fs-6 mb-1">
      Advanced memoization for JavaScript functions with lodash.memoize.
    </blockquote>
    <script type="application/ld+json">
      {
        "@context": "https://schema.org",
        "@type": "SoftwareSourceCode",
        "author": { "@id": "https://vladimirgorej.com" },
        "name": "memoizeN",
        "abstract": "Advanced memoization for JavaScript functions with lodash.memoize.",
        "codeRepository": "https://gist.github.com/char0n/2e6c77d38cf00faacceaf37e46b76a32"
      }
    </script>
  </a>
</div>

<p>If you need even more advanced functionality like LRU cache, TTL, reference counting, and others,
I suggest you look at <a href="https://github.com/medikoo/memoizee" target="_blank" rel="noopener noreferrer">memoizee</a>. memizee is probably the most
feature-rich implementation of memoization for JavaScript.</p>

<hr />

<div class="alert alert-info" role="alert">
  All code snippets in this article are licensed under <a href="https://www.apache.org/licenses/LICENSE-2.0" target="_blank" rel="noopener noreferrer">Apache 2.0 license</a> using the following copyright notice: <strong>Copyright 2022 Vladimír Gorej</strong>.
</div>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vladimirgorej.com/assets/img/blog/lodash.webp" /><media:content medium="image" url="https://vladimirgorej.com/assets/img/blog/lodash.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">How to handle errors in SwaggerUI</title><link href="https://vladimirgorej.com/blog/swagger-ui-error-handling/" rel="alternate" type="text/html" title="How to handle errors in SwaggerUI" /><published>2022-01-24T08:00:00+00:00</published><updated>2022-01-24T08:00:00+00:00</updated><id>https://vladimirgorej.com/blog/swagger-ui-error-handling</id><content type="html" xml:base="https://vladimirgorej.com/blog/swagger-ui-error-handling/"><![CDATA[<p class="lead">
  Error handling is an essential aspect of today's modern Single Page Applications.
  Error handling refers to the anticipation, detection, and resolution of different kinds of errors.
  As of version <a href="https://www.npmjs.com/package/swagger-ui/v/4.3.0" target="_blank" rel="noopener noreferrer">v4.3.0</a>, <a href="https://github.com/swagger-api/swagger-ui" target="_blank" rel="noopener noreferrer">SwaggerUIs</a> error handling capabilities have considerably improved
  and allowed SwaggerUI integrators to easily integrate their custom errors handlers. 
</p>

<p>Original SwaggerUI error handling consisted of wrapping every render method of every class component
with an imperative <code class="language-plaintext highlighter-rouge">try/catch</code> statement and displaying the <code class="language-plaintext highlighter-rouge">Fallback</code> component if the error was thrown.
Along with displaying the Fallback component, <code class="language-plaintext highlighter-rouge">console.error</code> was used to display the caught error in the browser console.
This solution required wrapping every function component into class component to make sure that
the component has the <code class="language-plaintext highlighter-rouge">render</code> method.</p>

<p>React 16 introduces a new concept of an <a href="https://reactjs.org/docs/error-boundaries.html" target="_blank" rel="noopener noreferrer">error boundary</a>.
It’s no longer necessary to create a custom solution for catching errors in React apps, as we have a standardized way of doing it.</p>

<p>In order to fully utilize new error boundaries, I had to rewrite SwaggerUIs <a href="https://github.com/swagger-api/swagger-ui/tree/master/src/core/plugins/view" target="_blank" rel="noopener noreferrer">view plugin</a>, which all other 
plugins build on. An additional new plugin called <code class="language-plaintext highlighter-rouge">safe-render</code> was introduced to allow configurable
application of error boundaries to SwaggerUI components.</p>

<h2 id="view-plugin">View plugin</h2>

<p>View plugins <strong>public API didn’t change</strong>, I’ve just added one additional util function called <code class="language-plaintext highlighter-rouge">getDisplayName</code>
which is used for accessing React component names.</p>

<figure class="highlight"><pre><code class="language-js" data-lang="js"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="code"><pre><span class="p">{</span>
  <span class="nl">rootInjects</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">getComponent</span><span class="p">:</span> <span class="nx">memGetComponent</span><span class="p">,</span>
    <span class="na">makeMappedContainer</span><span class="p">:</span> <span class="nx">memMakeMappedContainer</span><span class="p">,</span>
    <span class="na">render</span><span class="p">:</span> <span class="nx">render</span><span class="p">(</span><span class="nx">getSystem</span><span class="p">,</span> <span class="nx">getStore</span><span class="p">,</span> <span class="nx">getComponent</span><span class="p">,</span> <span class="nx">getComponents</span><span class="p">),</span>
  <span class="p">},</span>
  <span class="nx">fn</span><span class="p">:</span> <span class="p">{</span>
    <span class="nx">getDisplayName</span><span class="p">,</span>
  <span class="p">},</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">view</code> plugin is no longer responsible for error handling logic. Rewriting the plugin had several
positive side effects like making the SwaggerUI rendering faster and simplifying the React component
tree in React developer tools:</p>

<figure class="figure d-block border">
  <img src="/assets/img/blog/swagger-ui-error-handling-rdtd-before.webp" width="539" height="466" class="figure-img rounded mx-auto d-block img-fluid" alt="todoList plugin" />
  <figcaption class="figure-caption text-center">Before refactor</figcaption>
</figure>

<figure class="figure d-block border">
  <img src="/assets/img/blog/swagger-ui-error-handling-rdtd-after.webp" width="551" height="305" class="figure-img rounded mx-auto d-block img-fluid" alt="todoList plugin" />
  <figcaption class="figure-caption text-center">After refactor</figcaption>
</figure>

<h2 id="safe-render-plugin">Safe-render plugin</h2>

<p>This plugin is solely responsible for error handling logic in SwaggerUI. Accepts a list of component
names that should be protected by error boundaries.</p>

<p>Its public API looks like this:</p>

<figure class="highlight"><pre><code class="language-js" data-lang="js"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="code"><pre><span class="p">{</span>
  <span class="nl">fn</span><span class="p">:</span> <span class="p">{</span>
    <span class="nx">componentDidCatch</span><span class="p">,</span>
    <span class="na">withErrorBoundary</span><span class="p">:</span> <span class="nx">withErrorBoundary</span><span class="p">(</span><span class="nx">getSystem</span><span class="p">),</span>
  <span class="p">},</span>
  <span class="nx">components</span><span class="p">:</span> <span class="p">{</span>
    <span class="nx">ErrorBoundary</span><span class="p">,</span>
    <span class="nx">Fallback</span><span class="p">,</span>
  <span class="p">},</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>safe-render plugin is automatically utilized by <a href="https://github.com/swagger-api/swagger-ui/blob/78f62c300a6d137e65fd027d850981b010009970/src/core/presets/base.js" target="_blank" rel="noopener noreferrer">base</a> and <a href="https://github.com/swagger-api/swagger-ui/tree/78f62c300a6d137e65fd027d850981b010009970/src/standalone" target="_blank" rel="noopener noreferrer">standalone</a> SwaggerUI presets and
should always be used as the last plugin, after all the components are already known to the SwaggerUI.
The plugin defines a default list of components that should be protected by error boundaries:</p>

<figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="p">[</span>
  <span class="dl">"</span><span class="s2">App</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">BaseLayout</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">VersionPragmaFilter</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">InfoContainer</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">ServersContainer</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">SchemesContainer</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">AuthorizeBtnContainer</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">FilterContainer</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">Operations</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">OperationContainer</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">parameters</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">responses</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">OperationServers</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">Models</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">ModelWrapper</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">Topbar</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">StandaloneLayout</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">onlineValidatorBadge</span><span class="dl">"</span>
<span class="p">]</span></code></pre></figure>

<p>As demonstrated below, additional components can be protected by utilizing the safe-render plugin 
with configuration options. This gets really handy if you are a SwaggerUI integrator and you maintain a number of
plugins with additional custom components.</p>

<figure class="highlight"><pre><code class="language-js" data-lang="js"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="code"><pre><span class="kd">const</span> <span class="nx">swaggerUI</span> <span class="o">=</span> <span class="nx">SwaggerUI</span><span class="p">({</span>
  <span class="na">url</span><span class="p">:</span> <span class="dl">"</span><span class="s2">https://petstore.swagger.io/v2/swagger.json</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">dom_id</span><span class="p">:</span> <span class="dl">'</span><span class="s1">#swagger-ui</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">plugins</span><span class="p">:</span> <span class="p">[</span>
    <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">({</span>
      <span class="na">components</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">MyCustomComponent1</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">my custom component</span><span class="dl">'</span><span class="p">,</span>
      <span class="p">},</span>
    <span class="p">}),</span>
    <span class="nx">SwaggerUI</span><span class="p">.</span><span class="nx">plugins</span><span class="p">.</span><span class="nx">SafeRender</span><span class="p">({</span>
      <span class="na">fullOverride</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// only the component list defined here will apply (not the default list)</span>
      <span class="na">componentList</span><span class="p">:</span> <span class="p">[</span>
        <span class="dl">"</span><span class="s2">MyCustomComponent1</span><span class="dl">"</span><span class="p">,</span>
      <span class="p">],</span>
    <span class="p">}),</span>
  <span class="p">],</span>
<span class="p">});</span>
</pre></td></tr></tbody></table></code></pre></figure>

<h3 id="componentdidcatch">componentDidCatch</h3>

<p>This static function is invoked after a component has thrown an error.<br />
It receives two parameters:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">error</code> - The error that was thrown.</li>
  <li><code class="language-plaintext highlighter-rouge">info</code> - An object with a componentStack key containing <a href="https://reactjs.org/docs/error-boundaries.html#component-stack-traces" target="_blank" rel="noopener noreferrer">information about which component threw the error</a>.</li>
</ol>

<p>It has precisely the same signature as error boundaries <a href="https://reactjs.org/docs/react-component.html#componentdidcatch" target="_blank" rel="noopener noreferrer">componentDidCatch lifecycle method</a>,
except it’s a static function and not a class method.</p>

<p>Default implement of componentDidCatch uses <code class="language-plaintext highlighter-rouge">console.error</code> to display the received error:</p>

<figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="k">export</span> <span class="kd">const</span> <span class="nx">componentDidCatch</span> <span class="o">=</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">;</span></code></pre></figure>

<p>To utilize your own error handling logic (e.g. <a href="https://www.bugsnag.com/" target="_blank" rel="noopener noreferrer">bugsnag</a>), create new SwaggerUI plugin that overrides componentDidCatch:</p>

<figure class="highlight"><pre><code class="language-js" data-lang="js"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="code"><pre><span class="kd">const</span> <span class="nx">BugsnagErrorHandlerPlugin</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="c1">// init bugsnag</span>

  <span class="k">return</span> <span class="p">{</span>
    <span class="na">fn</span><span class="p">:</span> <span class="p">{</span>
      <span class="nx">componentDidCatch</span> <span class="o">=</span> <span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="nx">info</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">Bugsnag</span><span class="p">.</span><span class="nx">notify</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
        <span class="nx">Bugsnag</span><span class="p">.</span><span class="nx">notify</span><span class="p">(</span><span class="nx">info</span><span class="p">);</span>
      <span class="p">},</span>
    <span class="p">},</span>
  <span class="p">};</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></figure>

<h3 id="witherrorboundary">withErrorBoundary</h3>

<p>This function is HOC (Higher Order Component). It wraps a particular component into the <code class="language-plaintext highlighter-rouge">ErrorBoundary</code> component.
It can be overridden via a plugin system to control how components are wrapped by the ErrorBoundary component.
In 99.9% of situations, you won’t need to override this function, but if you do, please read the source code of this function first.</p>

<h3 id="fallback">Fallback</h3>

<p>The component is displayed when the error boundary catches an error. It can be overridden via a plugin system.
Its default implementation is trivial:</p>

<figure class="highlight"><pre><code class="language-js" data-lang="js"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="code"><pre><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">PropTypes</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">prop-types</span><span class="dl">"</span>

<span class="kd">const</span> <span class="nx">Fallback</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">name</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">(</span>
  <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">fallback</span><span class="dl">"</span><span class="o">&gt;</span>
    <span class="err">😱</span> <span class="o">&lt;</span><span class="nx">i</span><span class="o">&gt;</span><span class="nx">Could</span> <span class="nx">not</span> <span class="nx">render</span> <span class="p">{</span> <span class="nx">name</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span> <span class="p">?</span> <span class="dl">"</span><span class="s2">this component</span><span class="dl">"</span> <span class="p">:</span> <span class="nx">name</span> <span class="p">},</span> <span class="nx">see</span> <span class="nx">the</span> <span class="nx">console</span><span class="p">.</span><span class="o">&lt;</span><span class="sr">/i</span><span class="err">&gt;
</span>  <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span><span class="p">)</span>
<span class="nx">Fallback</span><span class="p">.</span><span class="nx">propTypes</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">name</span><span class="p">:</span> <span class="nx">PropTypes</span><span class="p">.</span><span class="nx">string</span><span class="p">.</span><span class="nx">isRequired</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Fallback</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>Feel free to override it to match your look &amp; feel:</p>

<figure class="highlight"><pre><code class="language-js" data-lang="js"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="code"><pre><span class="kd">const</span> <span class="nx">CustomFallbackPlugin</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">components</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">Fallback</span><span class="p">:</span> <span class="p">({</span> <span class="nx">name</span> <span class="p">}</span> <span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">`This is my custom fallback. </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2"> failed to render`</span><span class="p">,</span>
  <span class="p">},</span>
<span class="p">});</span>

<span class="kd">const</span> <span class="nx">swaggerUI</span> <span class="o">=</span> <span class="nx">SwaggerUI</span><span class="p">({</span>
  <span class="na">url</span><span class="p">:</span> <span class="dl">"</span><span class="s2">https://petstore.swagger.io/v2/swagger.json</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">dom_id</span><span class="p">:</span> <span class="dl">'</span><span class="s1">#swagger-ui</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">plugins</span><span class="p">:</span> <span class="p">[</span>
    <span class="nx">CustomFallbackPlugin</span><span class="p">,</span>
  <span class="p">]</span>  
<span class="p">});</span>
</pre></td></tr></tbody></table></code></pre></figure>

<h3 id="errorboundary">ErrorBoundary</h3>

<p>This is the component that implements React error boundaries. Uses <code class="language-plaintext highlighter-rouge">componentDidCatch</code> and <code class="language-plaintext highlighter-rouge">Fallback</code>
under the hood. In 99.9% of situations, you won’t need to override this component, but if you do, 
please read the source code of this component first.</p>

<h2 id="change-in-behavior">Change in behavior</h2>

<p>In prior releases of SwaggerUI, almost all components have been protected, and when thrown error,
<code class="language-plaintext highlighter-rouge">Fallback</code> component was displayed. This changes with SwaggerUI v4.3.0. Only components defined
by the <code class="language-plaintext highlighter-rouge">safe-render</code> plugin are now protected and display fallback. If a small component somewhere within
SwaggerUI React component tree fails to render and throws an error, the error bubbles up to the closest
error boundary, and that error boundary displays the <code class="language-plaintext highlighter-rouge">Fallback</code> component and invokes <code class="language-plaintext highlighter-rouge">componentDidCatch</code>.</p>

<p>If you’re interested in more technical details, here is the <a href="https://github.com/swagger-api/swagger-ui/pull/7761/files" target="_blank" rel="noopener noreferrer">PR</a>
that introduced the new error handling into SwaggerUI.</p>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[Error handling is an essential aspect of today's modern Single Page Applications. Error handling refers to the anticipation, detection, and resolution of different kinds of errors. As of version v4.3.0, SwaggerUIs error handling capabilities have considerably improved and allowed SwaggerUI integrators to easily integrate their custom errors handlers.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vladimirgorej.com/assets/img/blog/swagger-ui-error-handling.webp" /><media:content medium="image" url="https://vladimirgorej.com/assets/img/blog/swagger-ui-error-handling.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">How to migrate from querystring to URLSearchParams in Node.js</title><link href="https://vladimirgorej.com/blog/how-to-migrate-from-querystring-to-url-search-params-in-nodejs/" rel="alternate" type="text/html" title="How to migrate from querystring to URLSearchParams in Node.js" /><published>2021-10-14T07:00:00+00:00</published><updated>2021-10-14T07:00:00+00:00</updated><id>https://vladimirgorej.com/blog/how-to-migrate-from-querystring-to-url-search-params-in-nodejs</id><content type="html" xml:base="https://vladimirgorej.com/blog/how-to-migrate-from-querystring-to-url-search-params-in-nodejs/"><![CDATA[<p class="lead">
  Node.js marked the <a href="https://nodejs.org/docs/latest-v16.x/api/querystring.html" target="_blank" rel="noopener noreferrer">querystring</a> as legacy API in version 14.x,
  and recommends using <a href="https://nodejs.org/docs/latest-v16.x/api/url.html#url_class_urlsearchparams" target="_blank" rel="noopener noreferrer">URLSearchParams</a>. 
  But doesn't give us any clue how to actually migrate. This article fills this void and provides a migration guide.
</p>

<h2 id="querystring">querystring</h2>

<p><em>querystring</em> API is quite simple and straightforward:</p>

<dl class="row">
  <dt class="col-sm-3">querystring.parse()</dt>
  <dd class="col-sm-9">Parses a URL query string into a collection of key and value pairs. <span class="text-muted">querystring.decode()</span> is an alias.</dd>

  <dt class="col-sm-3">querystring.stringify()</dt>
  <dd class="col-sm-9">Produces a URL query string from a given obj by iterating through the object's "own properties". <span class="text-muted">querystring.encode()</span> is an alias.</dd>

  <dt class="col-sm-3">querystring.escape()</dt>
  <dd class="col-sm-9">Performs URL percent-encoding on the given string in a manner that is optimized for the specific requirements of URL query strings. Is used by <span class="text-muted">querystring.stringify()</span>.</dd>

  <dt class="col-sm-3">querystring.unescape()</dt>
  <dd class="col-sm-9">Performs decoding of URL percent-encoded characters on the given string. Is used by <span class="text-muted">querystring.parse().</span></dd>
</dl>

<h3 id="parse-a-url-query-string">Parse a URL query string</h3>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="p">{</span> <span class="nx">parse</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">querystring</span><span class="dl">'</span><span class="p">);</span>

<span class="nx">parse</span><span class="p">(</span><span class="dl">'</span><span class="s1">foo=bar&amp;abc=xyz&amp;abc=123</span><span class="dl">'</span><span class="p">);</span>

<span class="c1">// returns</span>
<span class="c1">// { </span>
<span class="c1">//   foo: 'bar',</span>
<span class="c1">//   abc: ['xyz', '123']</span>
<span class="c1">// }</span></code></pre></figure>

<h3 id="produce-a-url-query-string">Produce a URL query string</h3>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="p">{</span> <span class="nx">stringify</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">querystring</span><span class="dl">'</span><span class="p">);</span>

<span class="nx">stringify</span><span class="p">({</span> <span class="na">foo</span><span class="p">:</span> <span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">,</span> <span class="na">baz</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">qux</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">quux</span><span class="dl">'</span><span class="p">],</span> <span class="na">corge</span><span class="p">:</span> <span class="dl">''</span> <span class="p">});</span>

<span class="c1">// returns 'foo=bar&amp;baz=qux&amp;baz=quux&amp;corge='</span></code></pre></figure>

<h3 id="perform-url-percent-encoding-on-the-given-string">Perform URL percent-encoding on the given string</h3>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="p">{</span> <span class="nx">escape</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">querystring</span><span class="dl">'</span><span class="p">);</span>

<span class="nx">escape</span><span class="p">(</span><span class="dl">'</span><span class="s1">str1 str2</span><span class="dl">'</span><span class="p">);</span>

<span class="c1">// returns 'str1%20str2'</span></code></pre></figure>

<h3 id="perform-decoding-of-url-percent-encoded-characters-on-the-given-string">Perform decoding of URL percent-encoded characters on the given string</h3>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="p">{</span> <span class="nx">unescape</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">querystring</span><span class="dl">'</span><span class="p">);</span>

<span class="nx">escape</span><span class="p">(</span><span class="dl">'</span><span class="s1">str1%20str2</span><span class="dl">'</span><span class="p">);</span>

<span class="c1">// returns 'str1 str2'</span></code></pre></figure>

<h2 id="urlsearchparams">URLSearchParams</h2>

<p>What’s the actual difference between <code class="language-plaintext highlighter-rouge">querystring</code> vs. <code class="language-plaintext highlighter-rouge">URLSearchParams</code>?</p>

<blockquote class="blockquote">
  <p>"The WHATWG <em>URLSearchParams</em> interface and the <em>querystring</em> module have similar purpose, but the purpose of the <em>querystring</em> module is more general, as it allows the customization of delimiter characters."</p>
  <footer class="blockquote-footer"><cite title="Node.js 14.x documentation">Node.js 14.x documentation</cite></footer>
</blockquote>

<p>Working with URIs/URLs in JavaScript was always confusing. <a href="https://url.spec.whatwg.org/" target="_blank" rel="noopener noreferrer">WHATWG URL specification</a> finally
standardizes the term URL and provides a standardized way how we work with URLs. <em>URLSearchParams</em> is part of this specification.
Now let’s migrate our previous <em>querystring</em> examples to <em>URLSearchParams</em> API.</p>

<h3 id="parse-a-url-query-string-1">Parse a URL query string</h3>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">params</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">(</span><span class="dl">'</span><span class="s1">foo=bar&amp;abc=xyz&amp;abc=123</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// URLSearchParams { 'foo' =&gt; 'bar', 'abc' =&gt; 'xyz', 'abc' =&gt; '123' }</span>

<span class="nx">params</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">foo</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// 'bar'</span>
<span class="nx">params</span><span class="p">.</span><span class="nx">getAll</span><span class="p">(</span><span class="dl">'</span><span class="s1">abc</span><span class="dl">'</span><span class="p">)</span> <span class="c1">// ['xyz', '123']</span></code></pre></figure>

<h3 id="produce-a-url-query-string-1">Produce a URL query string</h3>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">({</span> <span class="na">foo</span><span class="p">:</span> <span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">,</span> <span class="na">baz</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">qux</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">quux</span><span class="dl">'</span><span class="p">],</span> <span class="na">corge</span><span class="p">:</span> <span class="dl">''</span> <span class="p">}).</span><span class="nx">toString</span><span class="p">();</span>
<span class="c1">// returns 'foo=bar&amp;baz=qux%2Cquux&amp;corge='</span></code></pre></figure>

<p>As we can see, what we got is a different result compared to <em>querystring</em>.</p>

<div class="table-responsive">
  <table class="table">
    <thead>
      <tr>
        <th scope="col">API</th>
        <th scope="col">Result</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>querystring</td>
        <td>foo=bar&amp;baz=qux&amp;baz=quux&amp;corge=</td>
      </tr>
      <tr>
        <td>URLSearchParams</td>
        <td>foo=bar&amp;baz=qux%2Cquux&amp;corge=</td>
      </tr>
    </tbody>
  </table>
</div>

<blockquote class="blockquote">
  <p>"Unlike <em>querystring</em> module, duplicate keys in the form of array values are not allowed. Arrays are stringified using array.toString(), which simply joins all array elements with commas."</p>
  <footer class="blockquote-footer"><cite title="Node.js 14.x documentation">Node.js 14.x documentation</cite></footer>
</blockquote>

<p>To get the result compatible with <em>querystring</em>, we have to initialize <em>URLSearchParams</em> with a list of tuples 
instead of object:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">([</span>
  <span class="p">[</span><span class="dl">'</span><span class="s1">foo</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">'</span><span class="s1">baz</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">qux</span><span class="dl">'</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">'</span><span class="s1">baz</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">quux</span><span class="dl">'</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">'</span><span class="s1">corge</span><span class="dl">'</span><span class="p">,</span> <span class="dl">''</span><span class="p">],</span>
<span class="p">]).</span><span class="nx">toString</span><span class="p">();</span>
<span class="c1">// returns 'foo=bar&amp;baz=qux&amp;baz=quux&amp;corge='</span></code></pre></figure>

<h3 id="perform-url-percent-encoding-on-the-given-string-1">Perform URL percent-encoding on the given string</h3>

<p>There is no low-level API for encoding simple strings in <em>URLSearchParams</em>.
We have to be a little creative to achieve URL encoding. Below
are 2 different yet functionally equivalent ways how to achieve the same result.</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">([[</span><span class="dl">''</span><span class="p">,</span> <span class="dl">'</span><span class="s1">str1 str2</span><span class="dl">'</span><span class="p">]]).</span><span class="nx">toString</span><span class="p">().</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">({</span><span class="dl">''</span><span class="p">:</span> <span class="dl">'</span><span class="s1">str1 str2</span><span class="dl">'</span><span class="p">}).</span><span class="nx">toString</span><span class="p">().</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="c1">// returns 'str1+str2'</span></code></pre></figure>

<p>Again, we can see what we got is a different result compared to <em>querystring</em>. WHATWG URL specification
is now precise about how various segments of URL gets encoded. Spaces in query parameters are now plus-sign (<code class="language-plaintext highlighter-rouge">+</code>) encoded, 
compared to percent-encoded spaces in the path segment of the URL. This currently accepted behavior is <strong>backward incompatible</strong> with <em>querystring</em>,
and there is nothing you can do about it except to take it.</p>

<table class="table">
  <thead>
    <tr>
      <th scope="col">API</th>
      <th scope="col">Result</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>querystring</td>
      <td>str1%20str2</td>
    </tr>
    <tr>
      <td>URLSearchParams</td>
      <td>str1+str2</td>
    </tr>
  </tbody>
</table>

<h3 id="perform-decoding-of-url-percent-encoded-characters-on-the-given-string-1">Perform decoding of URL percent-encoded characters on the given string</h3>

<p>There is no low-level API for decoding a simple string in <em>URLSearchParams</em>.
Again we have to be creative to achieve URL decoding on the given string.</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">urlPlusEncodedString</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">str1+str2</span><span class="dl">'</span><span class="p">;</span>

<span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">(</span><span class="s2">`=</span><span class="p">${</span><span class="nx">urlPlusEncodedString</span><span class="p">}</span><span class="s2">`</span><span class="p">).</span><span class="kd">get</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
<span class="c1">// returns 'str1 str2'</span></code></pre></figure>

<p>URLSearchParams will handle percent-encoded spaces as well:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">urlPercentEncodedString</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">str1%20str2</span><span class="dl">'</span><span class="p">;</span>

<span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">(</span><span class="s2">`=</span><span class="p">${</span><span class="nx">urlPercentEncodedString</span><span class="p">}</span><span class="s2">`</span><span class="p">).</span><span class="kd">get</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
<span class="c1">// returns 'str1 str2'</span></code></pre></figure>

<h2 id="other-differences">Other differences</h2>

<p><strong>Handling of question mark character</strong></p>

<p><em>URLSearchParams</em> removes question mark character from the start of the URL query string. <em>querystring</em>
keeps it and makes it part of the key name.</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="p">{</span> <span class="nx">parse</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">querystring</span><span class="dl">'</span><span class="p">);</span>

<span class="nx">parse</span><span class="p">(</span><span class="dl">'</span><span class="s1">?foo=bar</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// returns { '?foo': 'bar' }</span>
<span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">(</span><span class="dl">'</span><span class="s1">?foo=bar</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// returns URLSearchParams { 'foo' =&gt; 'bar' }</span></code></pre></figure>

<p><strong>Converting URLSearchParams object to Plain JavaScript Object</strong></p>

<p><em>URLSearchParams</em> object is a class instance. It is not a classical Plain JavaScript Object (POJO).
To transform URLSearchParam instance into a POJO, use the following recipe.</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">params</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">(</span><span class="dl">'</span><span class="s1">foo=bar</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">paramsPOJO</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">fromEntries</span><span class="p">(</span><span class="nx">params</span><span class="p">.</span><span class="nx">entries</span><span class="p">());</span> <span class="c1">// { 'foo': 'bar' }</span></code></pre></figure>

<p><em>Have you found other differences? I’d be happy to add them to this list.</em></p>

<h2 id="closing-words">Closing words</h2>

<p><em>querystring</em> is a legacy Node.js API that shouldn’t be used anymore. New code should only
use <em>URLSearchParams</em>, which is part of <strong>WHATWG URL specification</strong>. Old code using <em>querystring</em>
can easily be migrated to <em>URLSearchParams</em> using this guide. The only <strong>backward incompatibility</strong>
between the two APIs is that <em>URLSearchParams</em> use plus-encoded spaces instead of percent-encoded
spaces in <em>querystring</em>.</p>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[Node.js marked the querystring as legacy API and recommends using URLSearchParams. But doesn't give us any clue how to actually migrate. This article fills this void and provides a migration guide.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vladimirgorej.com/assets/img/blog/querystring-migration.webp" /><media:content medium="image" url="https://vladimirgorej.com/assets/img/blog/querystring-migration.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">How to apply the Apache 2.0 License to your Open Source software project</title><link href="https://vladimirgorej.com/blog/how-to-apply-apache2-license-to-your-open-source-software-project/" rel="alternate" type="text/html" title="How to apply the Apache 2.0 License to your Open Source software project" /><published>2021-10-02T12:15:00+00:00</published><updated>2021-10-02T12:15:00+00:00</updated><id>https://vladimirgorej.com/blog/how-to-apply-apache2-license-to-your-open-source-software-project</id><content type="html" xml:base="https://vladimirgorej.com/blog/how-to-apply-apache2-license-to-your-open-source-software-project/"><![CDATA[<p class="lead">
Apache 2.0 License is one of the best OSS licenses to use in your Open Source software project, but do you actually know how to properly apply it? We'll go through all the common mistakes and show how to use the license correctly.
</p>

<p>I’ve always been a <span class="fst-italic">3-Clause BSD License</span> (AKA <i>New BSD License</i> or <span class="fst-italic">Modified BSD License</span>) guy.
As I’ve matured, my understanding of Open Source licensing has grown as well, and so have my requirements. When I start a new Open Source
project now, I just use <strong>Apache 2.0 license</strong>. I want to point out that I am not a lawyer, and I am certainly not your lawyer.
My legal opinions are based on research and lengthy conversations with legal departments of companies that I worked for.</p>

<h2 id="why-apache-20-license">Why Apache 2.0 license?</h2>

<p>There are various reasons why I prefer Apache 2.0 license over the others (specifically the 3-Clause BSD License).
This <a href="https://www.whitesourcesoftware.com/resources/blog/top-10-apache-license-questions-answered/#7_What_is_the_difference_between_Apache_License_20_and_BSD" target="_blank" rel="noopener noreferrer">WhiteSource article</a>
covers the differences nicely, but for completeness, let me mentioned them here explicitly as well:</p>

<dl class="row">
  <dt class="col-sm-3">Explicit grant of patent rights</dt>
  <dd class="col-sm-9">Apache License 2.0 explicitly lays down the grant of patent rights while using, modifying, or distributing Apache licensed software; it also lists the circumstances when such grant gets withdrawn.</dd>

  <dt class="col-sm-3">Precise definitions of the used concepts</dt>
  <dd class="col-sm-9">Apache License 2.0 explicitly defines all the terms and concepts that it uses. This leaves little scope for ambiguity.</dd> 

  <dt class="col-sm-3">Reusable without rewording</dt>
  <dd class="col-sm-9">
    Apache License 2.0 can be easily used by other projects without any rewording in the license document itself.
    It also means that it should be sufficient to provide the canonical URL <code>https://www.apache.org/licenses/LICENSE-2.0.txt</code>, where
    the copy of the license can be obtained, instead of the license text.
  </dd>
</dl>

<p>These differences are most important to me, but other differences like <em>“prominent notices stating that You changed the files”</em>
might be more critical for you. Here is the full text of the Apache 2.0 license:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
</pre></td><td class="code"><pre>                                Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

   "License" shall mean the terms and conditions for use, reproduction,
   and distribution as defined by Sections 1 through 9 of this document.

   "Licensor" shall mean the copyright owner or entity authorized by
   the copyright owner that is granting the License.

   "Legal Entity" shall mean the union of the acting entity and all
   other entities that control, are controlled by, or are under common
   control with that entity. For the purposes of this definition,
   "control" means (i) the power, direct or indirect, to cause the
   direction or management of such entity, whether by contract or
   otherwise, or (ii) ownership of fifty percent (50%) or more of the
   outstanding shares, or (iii) beneficial ownership of such entity.

   "You" (or "Your") shall mean an individual or Legal Entity
   exercising permissions granted by this License.

   "Source" form shall mean the preferred form for making modifications,
   including but not limited to software source code, documentation
   source, and configuration files.

   "Object" form shall mean any form resulting from mechanical
   transformation or translation of a Source form, including but
   not limited to compiled object code, generated documentation,
   and conversions to other media types.

   "Work" shall mean the work of authorship, whether in Source or
   Object form, made available under the License, as indicated by a
   copyright notice that is included in or attached to the work
   (an example is provided in the Appendix below).

   "Derivative Works" shall mean any work, whether in Source or Object
   form, that is based on (or derived from) the Work and for which the
   editorial revisions, annotations, elaborations, or other modifications
   represent, as a whole, an original work of authorship. For the purposes
   of this License, Derivative Works shall not include works that remain
   separable from, or merely link (or bind by name) to the interfaces of,
   the Work and Derivative Works thereof.

   "Contribution" shall mean any work of authorship, including
   the original version of the Work and any modifications or additions
   to that Work or Derivative Works thereof, that is intentionally
   submitted to Licensor for inclusion in the Work by the copyright owner
   or by an individual or Legal Entity authorized to submit on behalf of
   the copyright owner. For the purposes of this definition, "submitted"
   means any form of electronic, verbal, or written communication sent
   to the Licensor or its representatives, including but not limited to
   communication on electronic mailing lists, source code control systems,
   and issue tracking systems that are managed by, or on behalf of, the
   Licensor for the purpose of discussing and improving the Work, but
   excluding communication that is conspicuously marked or otherwise
   designated in writing by the copyright owner as "Not a Contribution."

   "Contributor" shall mean Licensor and any individual or Legal Entity
   on behalf of whom a Contribution has been received by Licensor and
   subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of
   this License, each Contributor hereby grants to You a perpetual,
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
   copyright license to reproduce, prepare Derivative Works of,
   publicly display, publicly perform, sublicense, and distribute the
   Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of
   this License, each Contributor hereby grants to You a perpetual,
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
   (except as stated in this section) patent license to make, have made,
   use, offer to sell, sell, import, and otherwise transfer the Work,
   where such license applies only to those patent claims licensable
   by such Contributor that are necessarily infringed by their
   Contribution(s) alone or by combination of their Contribution(s)
   with the Work to which such Contribution(s) was submitted. If You
   institute patent litigation against any entity (including a
   cross-claim or counterclaim in a lawsuit) alleging that the Work
   or a Contribution incorporated within the Work constitutes direct
   or contributory patent infringement, then any patent licenses
   granted to You under this License for that Work shall terminate
   as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the
   Work or Derivative Works thereof in any medium, with or without
   modifications, and in Source or Object form, provided that You
   meet the following conditions:

   (a) You must give any other recipients of the Work or
   Derivative Works a copy of this License; and

   (b) You must cause any modified files to carry prominent notices
   stating that You changed the files; and

   (c) You must retain, in the Source form of any Derivative Works
   that You distribute, all copyright, patent, trademark, and
   attribution notices from the Source form of the Work,
   excluding those notices that do not pertain to any part of
   the Derivative Works; and

   (d) If the Work includes a "NOTICE" text file as part of its
   distribution, then any Derivative Works that You distribute must
   include a readable copy of the attribution notices contained
   within such NOTICE file, excluding those notices that do not
   pertain to any part of the Derivative Works, in at least one
   of the following places: within a NOTICE text file distributed
   as part of the Derivative Works; within the Source form or
   documentation, if provided along with the Derivative Works; or,
   within a display generated by the Derivative Works, if and
   wherever such third-party notices normally appear. The contents
   of the NOTICE file are for informational purposes only and
   do not modify the License. You may add Your own attribution
   notices within Derivative Works that You distribute, alongside
   or as an addendum to the NOTICE text from the Work, provided
   that such additional attribution notices cannot be construed
   as modifying the License.

   You may add Your own copyright statement to Your modifications and
   may provide additional or different license terms and conditions
   for use, reproduction, or distribution of Your modifications, or
   for any such Derivative Works as a whole, provided Your use,
   reproduction, and distribution of the Work otherwise complies with
   the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise,
   any Contribution intentionally submitted for inclusion in the Work
   by You to the Licensor shall be under the terms and conditions of
   this License, without any additional terms or conditions.
   Notwithstanding the above, nothing herein shall supersede or modify
   the terms of any separate license agreement you may have executed
   with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade
   names, trademarks, service marks, or product names of the Licensor,
   except as required for reasonable and customary use in describing the
   origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or
   agreed to in writing, Licensor provides the Work (and each
   Contributor provides its Contributions) on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
   implied, including, without limitation, any warranties or conditions
   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
   PARTICULAR PURPOSE. You are solely responsible for determining the
   appropriateness of using or redistributing the Work and assume any
   risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory,
   whether in tort (including negligence), contract, or otherwise,
   unless required by applicable law (such as deliberate and grossly
   negligent acts) or agreed to in writing, shall any Contributor be
   liable to You for damages, including any direct, indirect, special,
   incidental, or consequential damages of any character arising as a
   result of this License or out of the use or inability to use the
   Work (including but not limited to damages for loss of goodwill,
   work stoppage, computer failure or malfunction, or any and all
   other commercial damages or losses), even if such Contributor
   has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing
   the Work or Derivative Works thereof, You may choose to offer,
   and charge a fee for, acceptance of support, warranty, indemnity,
   or other liability obligations and/or rights consistent with this
   License. However, in accepting such obligations, You may act only
   on Your own behalf and on Your sole responsibility, not on behalf
   of any other Contributor, and only if You agree to indemnify,
   defend, and hold each Contributor harmless for any liability
   incurred by, or claims asserted against, such Contributor by reason
   of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

Copyright [yyyy] [name of copyright owner]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
</pre></td></tr></tbody></table></code></pre></figure>

<p>There are two variants of the Apache 2.0 license: justified version and version with text aligned to the left.
The justified version is the one you get by fetching <code class="language-plaintext highlighter-rouge">https://www.apache.org/licenses/LICENSE-2.0.txt</code> - I call it the <strong>canonical form</strong>.
The license has 201 lines of text and is composed of the header (lines 1-3),
the terms and conditions (lines 5-176) and the appendix (lines 178-201).</p>

<h2 id="license">License</h2>

<p>To apply the license to your software project, create a <code class="language-plaintext highlighter-rouge">LICENSE</code> file in the source tree’s top level.
Now copy the full Apache 2.0 License text from <a href="https://www.apache.org/licenses/LICENSE-2.0.txt" target="_blank" rel="noopener noreferrer">https://www.apache.org/licenses/LICENSE-2.0.txt</a> into a <code class="language-plaintext highlighter-rouge">LICENSE</code> file.
Notice that the <code class="language-plaintext highlighter-rouge">LICENSE</code> file doesn’t have any extension, but you can optionally name the file <code class="language-plaintext highlighter-rouge">LICENSE.txt</code> as well.</p>

<p><strong>Do not modify</strong> the license text. If you used a 3-Clause BSD License in the past,
you usually downloaded the license template, changed it, and saved it as a <code class="language-plaintext highlighter-rouge">LICENSE</code> file.</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">Copyright &lt;YEAR&gt; &lt;COPYRIGHT HOLDER&gt;

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.</code></pre></figure>

<p>You may be tempted to do the same with Apache 2.0 License. The license text contains what looks like
a copyright notice on line 189:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">Copyright [yyyy] [name of copyright owner]</code></pre></figure>

<p>Actually, this is a copyright notice template and an additional boilerplate that are part of the Apache 2.0 License appendix, which starts
at line 178 and explains how to apply the Apache License to individual source files instead
of having one single <code class="language-plaintext highlighter-rouge">LICENSE</code> file in the top level of the source tree. The text in the appendix is
very explicit about what the copyright notice template and additional boilerplate are for (use in source files). Changing
this copyright notice template directly in license text is IMHO an improper application of the license.
The whole appendix (lines 178-201) can technically be omitted from the license, as it’s outside the scope of “Section
1 through 9”, which forms the terms and conditions of the license.</p>

<p>My opinion is backed by the “<strong>Reusable without rewording</strong>” attribute of the Apache 2.0 License
I mentioned before. It’s even backed by my experience in a big corporation - two years ago, I was working for one of the biggest tech companies in the world.
The company had established an Open Source office with a Legal Department dealing with use of Open Source within the company.
This Open Source office stipulated that for an Open Source project to be licensed under Apache 2.0 License, it is not required
to have an explicit Apache 2.0 License text as part of the project. 
It is absolutely sufficient if the Open Source project README file
contains the name of the license, either “Apache 2.0 License” or its <a href="https://spdx.org/licenses/Apache-2.0.html" target="_blank" rel="noopener noreferrer">SPDX identifier</a>.
Ideally, (but not required) canonical URL where the copy of the license can be obtained should be mentioned as well: <code class="language-plaintext highlighter-rouge">https://www.apache.org/licenses/LICENSE-2.0.txt</code>.
This is just a legal opinion of one big tech company. Don’t take this legal opinion as implied truth.</p>

<h2 id="copyright-notice">Copyright notice</h2>

<p>In the previous section, we’ve stipulated that the Apache 2.0 License text doesn’t actually have a place where to
put an explicit copyright notice. Since 1989, <a href="https://www.wipo.int/treaties/en/ip/berne/" target="_blank" rel="noopener noreferrer">Berne Convention</a> established that the copyright is automatic,
and the use of copyright notices to claim the copyright has become optional.</p>

<p>This is how an <strong>explicit copyright notice</strong> looks like:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">Copyright 2021 Vladimír Gorej &lt;vladimir.gorej@gmail.com&gt;</code></pre></figure>

<p>When an explicit copyright notice is missing in Open Source software that you want to use in your project,
you’re theoretically still OK, as it is possible to compile one using the following assumptions:</p>

<ol>
  <li>The name of the copyright holder is the name of the first committer to the project</li>
  <li>The copyright holder contact is established by using the email address of the first committer</li>
  <li>The year of the copyright notice is established as the year of the first commit to the project</li>
</ol>

<p>If you cannot somehow obtain the above information, you’re kind of screwed, as there is no way to 
establish the copyright notice for compliance purposes in big tech companies.</p>

<p>It’s always a good idea to provide an explicit copyright notice when applying the Apache 2.0 License. But, where do we put one?
Well, if we would read the Apache 2.0 Licence terms and conditions carefully, <strong>section 4.d)</strong> (line 106) has an answer for that: <strong>a “NOTICE” text file</strong>.
Just create a new file called <code class="language-plaintext highlighter-rouge">NOTICE</code> and put your copyright notice there.</p>

<p>Today there are three competing formats of copyright notices:</p>

<dl class="row">
  <dt class="col-sm-3">Historical format</dt>
  <dd class="col-sm-9">
    Copyright 2017-2019, Vladimír Gorej<br />
    Copyright 1998, Linus Torvalds
  </dd>

  <dt class="col-sm-3">Newer format</dt>
  <dd class="col-sm-9">
    Copyright The Ramda Adjunct contributors<br />
    Copyright The Kubernetes Authors
  </dd>

  <dt class="col-sm-3">Hybrid format</dt>
  <dd class="col-sm-9">
    Copyright 2017-2019 Vladimír Gorej and the Ramda Adjunct contributors
  </dd>
</dl>

<p>Given above, your <code class="language-plaintext highlighter-rouge">NOTICE</code> file could look like this:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">Ramda Adjunct
Copyright 2017, Vladimír Gorej</code></pre></figure>

<h2 id="closing-words">Closing words</h2>

<p>Choose the appropriate Open Source license for the needs of your project.
If you’re deciding between BSD, MIT, and Apache 2.0, go for Apache 2.0 License. The license
automatically grants patent rights. It’s clear, explicit, and reusable without rewording. 
To apply the license to your Open Source software project, create two files: <code class="language-plaintext highlighter-rouge">LICENSE</code> and <code class="language-plaintext highlighter-rouge">NOTICE</code>
in the top level of the source tree. The copy of the license text that you obtained from <code class="language-plaintext highlighter-rouge">https://www.apache.org/licenses/LICENSE-2.0.txt</code>
goes into the <code class="language-plaintext highlighter-rouge">LICENSE</code> file. Your copyright notice goes into the <code class="language-plaintext highlighter-rouge">NOTICE</code> file. Doing these
two things makes the application of the Apache 2.0 License as explicit and clear as it can be.
Sounds pretty easy, right?</p>

<p>Another good source of information about applying Apache 2.0 License to a software project
can be official <a href="https://infra.apache.org/licensing-howto.html" target="_blank" rel="noopener noreferrer">“How to”</a> from Apache Software Foundation
directed to Apache Committers.</p>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[Apache 2.0 License is one of the best OSS licenses to use in your Open Source software project, but do you actually know how to properly apply it? We'll go through all the common mistakes and show how to use the license correctly.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vladimirgorej.com/assets/img/blog/apache-logo.webp" /><media:content medium="image" url="https://vladimirgorej.com/assets/img/blog/apache-logo.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Things I have learned while maintaining JavaScript monorepo with Lerna</title><link href="https://vladimirgorej.com/blog/things-i-have-learned-maintaining-javascript-monorepo-with-lerna/" rel="alternate" type="text/html" title="Things I have learned while maintaining JavaScript monorepo with Lerna" /><published>2021-09-07T12:15:00+00:00</published><updated>2021-09-07T12:15:00+00:00</updated><id>https://vladimirgorej.com/blog/things-i-have-learned-maintaining-javascript-monorepo-with-lerna</id><content type="html" xml:base="https://vladimirgorej.com/blog/things-i-have-learned-maintaining-javascript-monorepo-with-lerna/"><![CDATA[<p class="lead">
  This is the second part of the article series dealing with <i>monorepo</i> management using <a href="https://github.com/lerna/lerna" target="_blank" rel="noopener noreferrer">Lerna</a>.
  <a href="https://www.linkedin.com/pulse/things-i-wish-had-known-when-started-javascript-monorepo-gorej/" target="_blank" rel="noopener noreferrer">The first part</a> dealt
  with <i>monorepo</i> management in depth using Lerna and <a href="https://docs.npmjs.com/cli/v7/configuring-npm/pacage-json#local-paths" target="_blank" rel="noopener noreferrer">npm local paths</a>
  long before npm@7 workspaces were introduced. This article will try to describe <i>monorepo</i> management practices
  in the world of <a href="https://docs.npmjs.com/cli/v7/using-npm/workspaces" target="_blank" rel="noopener noreferrer">npm@7</a> workspaces. Nevertheless, I highly recommend you read the first part
  of this series to be familiar with how things used to be.
</p>

<h2 id="what-is-monorepo">What is monorepo?</h2>

<p>Let’s first define what <em>monorepo</em> is:</p>

<blockquote class="blockquote">
  <p>Monorepo is a software development strategy where code for many projects is stored in the same repository.</p>
</blockquote>

<p><em>Monorepo</em> code is usually divided into separate contexts, called packages. Every package contains code specific to its context and can depend on zero or more other packages within monorepo. Monorepo must have a mechanism to interconnect packages and allow us to use one package code within another.</p>

<p>In the following texts, I’m assuming you have your <em>monorepo</em> setup as described in the first part of this series.</p>

<h2 id="dependency-management">Dependency management</h2>

<p>Dependency managed has been simplified significantly with npm workspaces. npm workspaces require npm@7.
This version of npm uses the <a href="https://github.blog/2021-02-02-npm-7-is-now-generally-available/#changes-to-the-lockfile" target="_blank" rel="noopener noreferrer">new format</a> of a <code class="language-plaintext highlighter-rouge">package-lock.json</code> file.
You’ll need to convert your <code class="language-plaintext highlighter-rouge">package-lock.json</code> file to the new format for workspaces to work correctly.</p>

<h3 id="a-word-on-expressing-dependencies-between-packages">A word on expressing dependencies between packages</h3>

<p>We now use <strong>asterisk</strong> symbol to express dependencies between different <em>monorepo</em> packages.
Given that following are dependencies of <code class="language-plaintext highlighter-rouge">package-c</code> which, requires <code class="language-plaintext highlighter-rouge">package-a</code> and <code class="language-plaintext highlighter-rouge">package-b</code>,
this is how we express it in <code class="language-plaintext highlighter-rouge">package.json</code>:</p>

<figure class="highlight"><pre><code class="language-json" data-lang="json"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="code"><pre><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"ramda"</span><span class="p">:</span><span class="w"> </span><span class="s2">"=0.27.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ramda-adjunct"</span><span class="p">:</span><span class="w"> </span><span class="s2">"=2.27.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"package-a"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"package-b"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*"</span><span class="w">
</span><span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>Notice that we no longer use explicit npm local paths but rather an <strong>asterisk</strong> symbol.
<strong>Asterisk</strong> symbol is translated to npm local paths under the hood in a <code class="language-plaintext highlighter-rouge">package-lock.json</code> file.</p>

<p>Before npm workspaces, we had to list all packages in the <code class="language-plaintext highlighter-rouge">dependencies</code> field of the main <em>monorepo</em> <code class="language-plaintext highlighter-rouge">package.json</code> file.</p>

<figure class="highlight"><pre><code class="language-json" data-lang="json"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="code"><pre><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"package-a"</span><span class="p">:</span><span class="w"> </span><span class="s2">"file:packages/package-a"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"package-b"</span><span class="p">:</span><span class="w"> </span><span class="s2">"file:packages/package-b"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"package-c"</span><span class="p">:</span><span class="w"> </span><span class="s2">"file:packages/package-c"</span><span class="w">
</span><span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>This is no longer required. npm workspaces support recognizing <em>monorepo</em> packages by defining
a <code class="language-plaintext highlighter-rouge">workspaces</code> field in the main <em>monorepo</em> <code class="language-plaintext highlighter-rouge">package.json</code> file:</p>

<figure class="highlight"><pre><code class="language-json" data-lang="json"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="code"><pre><span class="nl">"workspaces"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
  </span><span class="s2">"./packages/*"</span><span class="w">
</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>We don’t need to add a new package explicitly in the <code class="language-plaintext highlighter-rouge">dependency</code> field whenever we want to add one. This saves
time and prevents cryptic errors when we forget to add the package explicitly as a dependency.</p>

<h3 id="a-word-on-development-dependencies">A word on development dependencies</h3>

<p>It is no longer required to keep all development dependencies in main the <em>monorepo</em> <code class="language-plaintext highlighter-rouge">package.json</code> file.
Every <em>monorepo</em> package can now have its own development dependencies in a particular version, given
the specific needs.</p>

<p>Before npm workspaces, the <code class="language-plaintext highlighter-rouge">devDependencies</code> field of every package <code class="language-plaintext highlighter-rouge">package.json</code> file was ignored.
Now, npm@7 recognizes it and installs the npm packages listed in <code class="language-plaintext highlighter-rouge">devDependencies</code>.</p>

<p>IMHO it still makes sense to keep certain <code class="language-plaintext highlighter-rouge">devDepedendies</code> (like testing frameworks) in the main
<em>monorepo</em> <code class="language-plaintext highlighter-rouge">package.json</code> file.</p>

<h3 id="a-word-on-topology">A word on topology</h3>

<p>npm workspaces are aware of <em>monorepo</em> package topology. Workspaces know that
<code class="language-plaintext highlighter-rouge">package-c</code> uses <code class="language-plaintext highlighter-rouge">package-a</code> and <code class="language-plaintext highlighter-rouge">package-b</code> as its dependencies. Let’s run the following command, which runs build npm script
in all the <em>monorepo</em> packages:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"> <span class="nv">$ </span>npm run build <span class="nt">--workspaces</span></code></pre></figure>

<p>This command will run <code class="language-plaintext highlighter-rouge">npm run build</code> for all the <em>monorepo</em> packages.
But it seems that npm workspaces don’t run the monorepo packages command in order as expected.
Let’s say that <code class="language-plaintext highlighter-rouge">package-a</code> depends on <code class="language-plaintext highlighter-rouge">package-b</code> and <code class="language-plaintext highlighter-rouge">package-c</code> depends both on <code class="language-plaintext highlighter-rouge">package-a</code> and <code class="language-plaintext highlighter-rouge">package-b</code>.
The order of execution you get from running the command is:</p>

<ul>
  <li>package-a</li>
  <li>package-b</li>
  <li>package-c</li>
</ul>

<p>But the correct order of build should be:</p>

<ul>
  <li>package-b</li>
  <li>package-a</li>
  <li>package-c</li>
</ul>

<p>Proper order of execution can be achieved by defining each package explicitly and in the correct order 
in the <code class="language-plaintext highlighter-rouge">workspaces</code> array of <code class="language-plaintext highlighter-rouge">package.json</code>. This array then needs to be maintained manually to reflect the monorepo topology.</p>

<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
  </span><span class="nl">"workspaces"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"./packages/package-b"</span><span class="p">,</span><span class="w"> </span><span class="s2">"./package/package-a"</span><span class="p">,</span><span class="w"> </span><span class="s2">"./packages/package-c"</span><span class="p">]</span><span class="w">
</span><span class="p">}</span></code></pre></figure>

<p>Because of this, <strong>Lerna</strong> still plays a significant role here.
Lerna will determine which packages have <strong>build</strong> npm script defined, and then it determines the proper execution order.</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"> <span class="nv">$ </span>lerna run build</code></pre></figure>

<p>Order of execution:</p>

<ul>
  <li>package-b</li>
  <li>package-a</li>
  <li>package-c</li>
</ul>

<h3 id="a-word-on-installing-a-new-dependency">A word on installing a new dependency</h3>

<p>npm workspaces completely simplified the process of adding a new production dependency. Let’s say
we have a <code class="language-plaintext highlighter-rouge">package-a</code>, and we want to add a dependency of <code class="language-plaintext highlighter-rouge">ramda-adjunct@2.27.0</code>. The only thing 
we need to do is run the following command:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">  <span class="nv">$ </span>npm <span class="nb">install </span>ramda-adjunct@2.27.0 <span class="nt">--workspace</span><span class="o">=</span>package-a <span class="nt">--save</span></code></pre></figure>

<h3 id="a-word-on-installing-a-new-development-dependency">A word on installing a new development dependency</h3>

<p>As already stipulated, development dependencies can be installed either in top-level <em>monorepo</em> (suitable for testing frameworks)</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"> <span class="nv">$ </span>npm <span class="nb">install </span>mocha <span class="nt">--save-dev</span></code></pre></figure>

<p>Or installed as a specific development dependency of a particular <em>monorepo</em> package</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"> <span class="nv">$ </span>npm <span class="nb">install </span>rimraf <span class="nt">--workspace</span><span class="o">=</span>package-a <span class="nt">--save-dev</span></code></pre></figure>

<h3 id="a-word-on-npm-binaries">A word on npm binaries</h3>

<p>Before npm workspaces, to run a binary from one of the <em>monorepo</em> packages, we had to use the <a href="https://www.npmjs.com/package/link-parent-bin" target="_blank" rel="noopener noreferrer">link-parent-bin</a>
npm package to link top-level npm binaries to all <em>monorepo</em> packages. This is no longer required as npm supports this natively.</p>

<h2 id="closing-words">Closing words</h2>

<p>Given all that has been written here, I think npm workspaces still doesn’t replace Lerna. Instead,
they just significantly simplified how we work with Lerna. Lerna still plays a significant part in
change detection, releases, and running npm scripts in the proper order (given the package topology).</p>

<p>All the examples mentioned in the article can be found in the following Lerna monorepo repository I’ve setup-ed for you. Clone it, install it, play with it and have a lot of fun with it!</p>

<div class="list-group mb-3">
  <a href="https://github.com/char0n/lerna-monorepo-taming" class="list-group-item list-group-item-action" target="_blank" rel="noopener noreferrer">
    <div class="d-flex w-100 justify-content-between">
      <h2 class="h5 mb-1"><i class="fa-brands fa-github"></i> lerna-monorepo-taming</h2>
    </div>
    <blockquote class="blockquote fs-6 mb-1">
      This monorepo implements current practices and conventions used while building monorepos using Lerna.
    </blockquote>
    <script type="application/ld+json">
      {
        "@context": "https://schema.org",
        "@type": "SoftwareSourceCode",
        "author": { "@id": "https://vladimirgorej.com" },
        "name": "lerna-monorepo-taming",
        "abstract": "This monorepo implements current practices and conventions used while building monorepos using Lerna.",
        "codeRepository": "https://github.com/char0n/lerna-monorepo-taming"
      }
    </script>
  </a>
</div>

<hr />

<p><strong>Maintaining Lerna monorepo series:</strong></p>

<ul>
  <li>Part 1: <a href="https://www.linkedin.com/pulse/things-i-wish-had-known-when-started-javascript-monorepo-gorej/" target="_blank" rel="noopener noreferrer">Things I wish I had known when I started JavaScript monorepo with Lerna</a></li>
  <li>Part 2: Things I have learned while maintaining JavaScript monorepo with Lerna</li>
</ul>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[Monorepo management processes using Lerna and npm@7 workspaces]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vladimirgorej.com/assets/img/blog/taming-lerna.webp" /><media:content medium="image" url="https://vladimirgorej.com/assets/img/blog/taming-lerna.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">How to validate OpenAPI definitions in Swagger Editor using GitHub Actions</title><link href="https://vladimirgorej.com/blog/how-to-validate-openapi-definitions-in-swagger-editor-using-github-actions/" rel="alternate" type="text/html" title="How to validate OpenAPI definitions in Swagger Editor using GitHub Actions" /><published>2021-03-07T08:39:04+00:00</published><updated>2021-03-07T08:39:04+00:00</updated><id>https://vladimirgorej.com/blog/how-to-validate-openapi-definitions-in-swagger-editor-using-github-actions</id><content type="html" xml:base="https://vladimirgorej.com/blog/how-to-validate-openapi-definitions-in-swagger-editor-using-github-actions/"><![CDATA[<p class="lead">
  Building a GitHub Action workflow that uses Swagger Editor to validate an OpenAPI definition.
</p>

<p>The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to HTTP APIs,
allowing both humans and computers to discover and understand the service’s capabilities without
access to source code, documentation, or network traffic inspection.
When properly defined, a consumer can understand and interact with the remote service with minimal implementation logic.</p>

<p>An OpenAPI definition can then be used by documentation generation tools to display the API, code generation
tools to generate servers and clients in various programming languages, testing tools, and many other use cases.</p>

<p>I took the above description of what OpenAPI is directly from <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md" target="_blank" rel="noopener noreferrer">its specification</a>.
If you’re one of the people who write OpenAPI definition by hand in <a href="https://editor.swagger.io/" target="_blank" rel="noopener noreferrer">Swagger Editor</a>, this article was written just for you.</p>

<p>A couple of years ago, I worked with <a href="https://www.ui.com/" target="_blank" rel="noopener noreferrer">Ubiquiti Inc</a>. We were producing great wireless hardware solutions
with embedded UIs. I was fortunate to have joined the company when a cross hardware configuration system
called <a href="https://unms.com/" target="_blank" rel="noopener noreferrer">UNMS</a> was being architected and developed. The backend part of the system consisted of a vast <a href="https://en.wikipedia.org/wiki/Representational_state_transfer" target="_blank" rel="noopener noreferrer">REST API</a> layer that 
the Frontend layer was consuming. We used a design-first approach to producing the API. The workflow of this 
<a href="https://www.visual-paradigm.com/guide/development/code-first-vs-design-first" target="_blank" rel="noopener noreferrer">design-first approach</a> consisted of the following steps:</p>

<ul>
  <li>create changes in <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md" target="_blank" rel="noopener noreferrer">OpenApi 2.0</a> definition using Swagger Editor</li>
  <li>issuer: issue a <a href="https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests" target="_blank" rel="noopener noreferrer">GitHub Pull Request</a> for the team to review how new API endpoints could look like</li>
  <li>reviewer: paste the OpenAPI definition from Pull Request to Swagger Editor to see if no errors were introduced</li>
  <li>merge Pull Request</li>
  <li>implement the actual REST API defined in OpenAPI definition</li>
</ul>

<p>Today I’d immediately think about how to automate this workflow and make <a href="https://en.wikipedia.org/wiki/Continuous_integration" target="_blank" rel="noopener noreferrer">Continuous Integration</a> do the work for us.
Unfortunately, at that time, my knowledge about CI/CD pipelines was limited, and I wasn’t aware of the full benefits 
and values CI/CD provides.</p>

<p>Some time has passed since then. I did my homework and studied CI/CD topic thoroughly. When <a href="https://github.com/features/actions" target="_blank" rel="noopener noreferrer">GitHub Actions</a> were born
I immediately became a massive fan. Let’s try to automate the workflow described above using GitHub Actions.</p>

<h2 id="technical-design">Technical design</h2>

<p>We need to produce a GitHub Action that uses Swagger Editor to validate the OpenAPI definition provided
as a parameter to that action. Using a Swagger Editor in GitHub Action can be achieved in two ways:
running it in a docker container using <a href="https://hub.docker.com/r/swaggerapi/swagger-editor/" target="_blank" rel="noopener noreferrer">swagger-api/swagger-editor image</a> or using <a href="https://editor.swagger.io/" target="_blank" rel="noopener noreferrer">https://editor.swagger.io/</a> directly.
Now that we have Swagger Editor running, we use <a href="https://pptr.dev/" target="_blank" rel="noopener noreferrer">puppeteer</a> to open a headless version of Chromium Browser.
Then we paste the OpenAPI definition into the Swagger Editor and wait for it to render errors
(if the definition is valid, no errors will be generated). Read errors from the Swagger Editor using puppeteer 
and represent them via GitHub Actions API. GitHub Action will report failure on any error or success if
the document is valid and contains no errors.</p>

<p>Why use Swagger Editor and not just JSON Schema validation? Swagger Editor adds its own layer of error recognition on top of JSON Schema validation.
Unfortunately, this additional layer is tightly coupled to Swagger Editor code, and the easiest way to use it is to use it via running
the Swagger Editor in a browser.</p>

<h2 id="implementation">Implementation</h2>

<p>The implementation wasn’t that straightforward as I expected, but I still managed to implement this GitHub Action
against the technical design mentioned earlier with all its requirements. I called it <a href="https://github.com/char0n/swagger-editor-validate" target="_blank" rel="noopener noreferrer">swagger-editor-validate</a>.</p>

<div class="list-group mb-3">
  <a href="https://github.com/char0n/swagger-editor-validate" class="list-group-item list-group-item-action" target="_blank" rel="noopener noreferrer">
    <div class="d-flex w-100 justify-content-between">
      <h2 class="h5 mb-1"><i class="fa-brands fa-github"></i> swagger-editor-validate</h2>
    </div>
    <blockquote class="blockquote fs-6 mb-1">
      This GitHub Actions validates OpenAPI (OAS) definition file using Swagger Editor.
    </blockquote>
    <script type="application/ld+json">
      {
        "@context": "https://schema.org",
        "@type": "SoftwareSourceCode",
        "author": { "@id": "https://vladimirgorej.com" },
        "name": "swagger-editor-validate",
        "abstract": "This GitHub Actions validates OpenAPI (OAS) definition file using Swagger Editor.",
        "codeRepository": "https://github.com/char0n/swagger-editor-validate"
      }
    </script>
  </a>
</div>

<h2 id="usage">Usage</h2>

<p>There are two significant use-cases of using this GitHub Action, but both of them use the fact that your definition
file lives in your GitHub repository. You just need to provide a file system path to it.</p>

<h3 id="public-use-case">Public use-case</h3>

<p>If you have access to the internet and don’t mind that this GitHub Action sends your OpenAPI definition to
<a href="https://editor.swagger.io/" target="_blank" rel="noopener noreferrer">https://editor.swagger.io/</a> for validation, then use this workflow.</p>

<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="code"><pre><span class="na">on</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">push</span><span class="pi">]</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">test_swagger_editor_validator_remote</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Swagger Editor Validator Remote</span>

    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v2</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Validate OpenAPI definition</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">char0n/swagger-editor-validate@v1</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">definition-file</span><span class="pi">:</span> <span class="s">examples/openapi-2-0.yaml</span>
</pre></td></tr></tbody></table></code></pre></figure>

<h3 id="private-use-case">Private use-case</h3>

<p>If you want to maintain complete privacy and your OpenAPI definition may contain sensitive information 
use the following workflow. The workflow uses a swagger-editor docker image that runs as a service of the workflow.</p>

<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
</pre></td><td class="code"><pre><span class="na">on</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">push</span><span class="pi">]</span>


<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">test_swagger_editor_validator_service</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Swagger Editor Validator Service</span>

    <span class="c1"># Service containers to run with `runner-job`</span>
    <span class="na">services</span><span class="pi">:</span>
      <span class="c1"># Label used to access the service container</span>
      <span class="na">swagger-editor</span><span class="pi">:</span>
        <span class="c1"># Docker Hub image</span>
        <span class="na">image</span><span class="pi">:</span> <span class="s">swaggerapi/swagger-editor</span>
        <span class="na">ports</span><span class="pi">:</span>
          <span class="c1"># Maps port 8080 on service container to the host 80</span>
          <span class="pi">-</span> <span class="s">80:8080</span>


    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v2</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Validate OpenAPI definition</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">char0n/swagger-editor-validate@v1</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">swagger-editor-url</span><span class="pi">:</span> <span class="s">http://localhost/</span>
          <span class="na">definition-file</span><span class="pi">:</span> <span class="s">examples/openapi-2-0.yaml</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>This is what you’ll see if your OpenAPI definition validates <strong>successfully</strong>.</p>

<figure class="figure">
  <img src="/assets/img/blog/swagger-editor-validate-success.webp" width="1149" height="365" class="figure-img rounded mx-auto d-block img-fluid" alt="Successful validation" />
  <figcaption class="figure-caption text-center">Successful validation</figcaption>
</figure>

<p>This is what you’ll see if your OpenAPI definition contains <strong>errors</strong>.</p>

<figure class="figure">
  <img src="/assets/img/blog/swagger-editor-validate-errors.webp" width="1218" height="430" class="figure-img rounded mx-auto d-block img-fluid" alt="Failed validation" />
  <figcaption class="figure-caption text-center">Failed validation</figcaption>
</figure>

<h2 id="future">Future</h2>

<p>During implementations, I identified other features that this action would benefit from.
To not extend the implementation scope of the first version, I decided not to implement these features but instead document them and implement them later.
These features include:</p>

<ul>
  <li><a href="https://github.com/char0n/swagger-editor-validate/issues/1" target="_blank" rel="noopener noreferrer">Providing OpenAPI definition as a string</a></li>
  <li><a href="https://github.com/char0n/swagger-editor-validate/issues/2" target="_blank" rel="noopener noreferrer">Providing OpenAPI definition as URL</a></li>
  <li><a href="https://github.com/char0n/swagger-editor-validate/issues/4" target="_blank" rel="noopener noreferrer">Expose a screenshot on failure</a></li>
  <li><a href="https://github.com/char0n/swagger-editor-validate/issues/3" target="_blank" rel="noopener noreferrer">The mechanism for ignoring errors</a></li>
</ul>

<p>I think the most interesting one is - <strong>Mechanism for ignoring errors</strong>. Commonly, OpenAPI definitions 
contain errors when validated in <a href="https://editor.swagger.io/" target="_blank" rel="noopener noreferrer">https://editor.swagger.io/</a>. There are cases when there is nothing authors can do 
with these errors, which are known and ignored. This will open a door for such a use-case,
which I’m sure is quite common.</p>

<p>Hope that <a href="https://github.com/char0n/swagger-editor-validate" target="_blank" rel="noopener noreferrer">swagger-editor-validate</a> will become a handy tool for anybody using Swagger Editor to edit OpenAPI definitions.
I do know it will become convenient for my old team in Ubiquiti Inc ;] Please leave any feedback in the comments. I’d highly appreciate it!</p>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[Building a GitHub Action workflow that uses Swagger Editor to validate an OpenAPI definition.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vladimirgorej.com/assets/img/blog/swagger-editor-validate.webp" /><media:content medium="image" url="https://vladimirgorej.com/assets/img/blog/swagger-editor-validate.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">How Swagger Adjust can help you build extensible React+Redux apps</title><link href="https://vladimirgorej.com/blog/how-swagger-adjust-can-help-you-build-extensible-react-redux-apps/" rel="alternate" type="text/html" title="How Swagger Adjust can help you build extensible React+Redux apps" /><published>2021-02-25T08:39:04+00:00</published><updated>2021-02-25T08:39:04+00:00</updated><id>https://vladimirgorej.com/blog/how-swagger-adjust-can-help-you-build-extensible-react-redux-apps</id><content type="html" xml:base="https://vladimirgorej.com/blog/how-swagger-adjust-can-help-you-build-extensible-react-redux-apps/"><![CDATA[<p class="lead">
  Step by step guide to building React-Redux apps using pluggable architecture. Features are produced as plugins and are later composed to create the app. Plugins can be removed, added, or amended by other plugins.
</p>

<p>More than four years ago, I worked on a backend project where I needed to render HTML
documentation from <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md" target="_blank" rel="noopener noreferrer">OpenApi 2.0</a> JSON document defining our REST API written in hapi.js.
The obvious choice was, of course, SwaggerUI. <a href="https://hapi.dev/" target="_blank" rel="noopener noreferrer">hapi.js</a> ecosystem came with already implemented
integrations for OpenApi 2.0 and <a href="https://swagger.io/tools/swagger-ui/" target="_blank" rel="noopener noreferrer">SwaggerUI</a>. We just needed to use them, and we did. 
It all worked like a charm. At that time, I successfully got out of the complex
<a href="https://devdocs.io/angularjs~1.2/" target="_blank" rel="noopener noreferrer">Angular 1.2/2</a> world and got into the simple and more predictable world of <a href="https://reactjs.org/" target="_blank" rel="noopener noreferrer">React</a>+<a href="https://redux.js.org/" target="_blank" rel="noopener noreferrer">Redux</a>. 
I figured out that SwaggerUI was built in React, so I navigated to its <a href="https://github.com/swagger-api/swagger-ui" target="_blank" rel="noopener noreferrer">GitHub repo</a> and 
read the code to learn how large-scale React apps are written. The code was
different from the code I was used to writing. I built React applications using classical
patterns like <a href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0" target="_blank" rel="noopener noreferrer">Presentational and Container Components</a> (smart &amp; dump components) and <a href="https://github.com/erikras/ducks-modular-redux" target="_blank" rel="noopener noreferrer">ducks pattern</a>.
It took me some time to understand that SwaggerUI was primarily built for extensibility in mind. 
Everything inside the code was revolves around this primary idea. The codebase was composed of several
plugins. These plugins were either adding new behaviors to SwaggerUI or enhancing
other behaviors defined in different plugins. There were almost no static imports involved 
because it had its own flavor of DI-container called <strong>System</strong>. Although the architecture looked 
interesting, it seemed overkill to use something like that in my current project.</p>

<p>In 2017, I was employed by a company called <a href="https://apiary.io/" target="_blank" rel="noopener noreferrer">Apiary</a>. I joined it a couple of months after
the acquisition by <a href="https://www.oracle.com/" target="_blank" rel="noopener noreferrer">Oracle</a>, and my primary task was to architect and develop a new version
of Apiary Documentation Renderer named: <strong>ApiaryUI</strong>. When I started to work on it, I remembered
my study on SwaggerUI and its unique architecture. I built ApiaryUI around the same ideas
of external extensibility. Of course, the code was completely different, but the architecture
was very similar to SwaggerUI. The core of ApiaryUI was based on a DI-container called <strong>Kernel</strong>.
I managed to finish the new <a href="https://blogs.oracle.com/developers/apiaryui" target="_blank" rel="noopener noreferrer">ApiaryUI</a> on time. Unfortunately, it has never been released as OpenSource
due to different political and organizational reasons. Still, it’s been utilized by Apiary
in its <a href="https://apiary.io/" target="_blank" rel="noopener noreferrer">web app</a> and used throughout various Oracle organizations.</p>

<p>Today I work for <a href="https://smartbear.com/" target="_blank" rel="noopener noreferrer">SmartBear</a>, the company that’s behind SwaggerUI and the entire Swagger ecosystem.
My primary focus in SmartBear is not on SwaggerUI, but from the moment I joined the company,
I started to think about improving and modernizing its DI-container (System). I’ve been actually
thinking about this for a long time. I started writing this article almost 3 years ago,
when I decoupled its DI-container for the first time as I needed it for a small project
with external extensibility requirements. Unfortunately, I never finished the article and 
never published the decoupled SwaggerUI DI-container as an OpenSource.</p>

<h2 id="swagger-adjust">Swagger Adjust</h2>

<p>But it’s all changing now. Let me introduce <a href="https://github.com/char0n/swagger-adjust" target="_blank" rel="noopener noreferrer">Swagger Adjust</a>  - a plugin-able framework for
building extensible React+Redux applications. This framework was born by decoupling ideas of 
the plugin-able core of SwaggerUI into a separate reusable component. It allows you to produce React+Redux applications where every component,
being a React Component, Redux state slice composed of actions, selector, reducers, or general business logic, is override-able and extensible.</p>

<p>Swagger Adjust is a wholly rewritten SwaggerUI’s DI-container (System) built on
the <a href="https://redux-toolkit.js.org/" target="_blank" rel="noopener noreferrer">Redux Toolkit</a>. The name for the DI-container is the same as SwaggerUI’s – <strong>System</strong>. 
Generally, the API of Swagger Adjust  is backward compatible with SwaggerUI.
When I say “generally”, I mean it in a way that the default configuration of Swagger Adjust 
is incompatible with SwaggerUI but can be adapted by configuration to be fully compatible.
Swagger Adjust is a free and Open Source software. It comes with a dual license – Apache License Version 2.0 is used for original code fragments, 
documentation fragments, and original ideas from SwaggerUI, and BSD 3-Clause License is used for everything else.</p>

<p>To demonstrate Swagger Adjust power, I’ve built a <a href="https://char0n.github.io/swagger-adjust/" target="_blank" rel="noopener noreferrer">simple TodoList app</a> composed of two plugins:
<a href="https://github.com/char0n/swagger-adjust/tree/main/demo/src/todo-list" target="_blank" rel="noopener noreferrer">todoList</a> and <a href="https://github.com/char0n/swagger-adjust/tree/main/demo/src/todo-list-enhancer" target="_blank" rel="noopener noreferrer">todoListEnhancer</a>. todoList plugin has the basic functionality of displaying a list of TodoItems 
and adding a new one. todoListEnhancer adds additional functionality like completion, deletion, and also 
batch operations on the entire TodoList.</p>

<figure class="figure d-block">
  <img src="/assets/img/blog/swagger-adjust-todo-list-plugin.webp" width="533" height="256" class="figure-img rounded mx-auto d-block img-fluid" alt="todoList plugin" />
  <figcaption class="figure-caption text-center">todoList plugin</figcaption>
</figure>

<p>todoList plugin looks like this when it renders. It contains AppBar, input for creating new todo
items, and a list of already added todo items.</p>

<figure class="figure d-block clearfix">
  <img src="/assets/img/blog/swagger-adjust-todo-list-plugin-dir-structure.webp" width="230" height="242" class="float-md-end" alt="todoList plugin" />
  <figcaption class="figure-caption">
    The directory structure of todoList plugin looks pretty standard. There is a components directory containing all the React Components.
    Redux-related code is concentrated in: actions.js, selectors.js, and reducers.js.
    Source code of these Redux files is unaware that it's part of a plugging-able DI-container and are built in the same way as they'd be built using Redux Toolkit.
    Please take a moment here and <a href="https://github.com/char0n/swagger-adjust/tree/main/demo/src/todo-list" target="_blank" rel="noopener noreferrer">browse these files</a> yourself to see the actual code. 
  </figcaption>
</figure>

<p>The only non-standard file is <a href="https://github.com/char0n/swagger-adjust/blob/main/demo/src/todo-list/plugin.js" target="_blank" rel="noopener noreferrer">plugin.js</a>. This file imports everything that todoPlugin consists of and
exports a function returning an <a href="https://github.com/char0n/swagger-adjust/blob/main/docs/customization/plugin-api.md#format" target="_blank" rel="noopener noreferrer">object with a specialized shape</a> that our DI-container recognizes.</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
</pre></td><td class="code"><pre><span class="k">import</span> <span class="nx">TodoListLayout</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/TodoListLayout</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">TodoListAppBar</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/TodoListAppBar</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">TodoListInput</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/TodoListInput</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">TodoList</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/TodoList</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">TodoListItem</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/TodoListItem</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">addItem</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./actions</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">selectTodoList</span><span class="p">,</span> <span class="nx">selectTodoListItems</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./selectors</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">reducers</span><span class="p">,</span> <span class="p">{</span> <span class="nx">initialState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./reducers</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">TodoListPlugin</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">{</span>
    <span class="na">components</span><span class="p">:</span> <span class="p">{</span>
      <span class="nx">TodoListLayout</span><span class="p">,</span>
      <span class="nx">TodoListAppBar</span><span class="p">,</span>
      <span class="nx">TodoListInput</span><span class="p">,</span>
      <span class="nx">TodoList</span><span class="p">,</span>
      <span class="nx">TodoListItem</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="na">fn</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">todoList</span><span class="p">:</span> <span class="p">{},</span>
    <span class="p">},</span>
    <span class="na">statePlugins</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">todoList</span><span class="p">:</span> <span class="p">{</span>
        <span class="nx">initialState</span><span class="p">,</span>
        <span class="na">actions</span><span class="p">:</span> <span class="p">{</span> <span class="nx">addItem</span> <span class="p">},</span>
        <span class="na">selectors</span><span class="p">:</span> <span class="p">{</span> <span class="nx">selectTodoList</span><span class="p">,</span> <span class="nx">selectTodoListItems</span> <span class="p">},</span>
        <span class="nx">reducers</span><span class="p">,</span>
      <span class="p">},</span>
    <span class="p">},</span>
  <span class="p">};</span>
<span class="p">};</span>

<span class="k">export</span> <span class="k">default</span> <span class="nx">TodoListPlugin</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>Now, this is where the real magic happens.
Instead of using static imports in React components, we use specialized <a href="https://github.com/char0n/swagger-adjust/blob/main/docs/usage/hooks-api.md" target="_blank" rel="noopener noreferrer">hooks</a> to get everything we need from DI-container.
For the TodoList component to correctly render, we need to select all todo items from the state and then use the TodoListItem
component to render them.</p>

<figure class="highlight"><pre><code class="language-jsx" data-lang="jsx"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="code"><pre><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">List</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@material-ui/core/List</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Divider</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@material-ui/core/Divider</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">makeStyles</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@material-ui/core/styles</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useSystemComponent</span><span class="p">,</span> <span class="nx">useSystemSelector</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">swagger-adjust</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">useStyles</span> <span class="o">=</span> <span class="nx">makeStyles</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">root</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">paddingTop</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="na">paddingBottom</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
  <span class="p">},</span>
<span class="p">}));</span>

<span class="kd">const</span> <span class="nx">TodoList</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">classes</span> <span class="o">=</span> <span class="nx">useStyles</span><span class="p">();</span>
  <span class="kd">const</span> <span class="nx">items</span> <span class="o">=</span> <span class="nx">useSystemSelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">todoList</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">selectTodoListItems</span><span class="dl">'</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">TodoListItem</span> <span class="o">=</span> <span class="nx">useSystemComponent</span><span class="p">(</span><span class="dl">'</span><span class="s1">TodoListItem</span><span class="dl">'</span><span class="p">);</span>

  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nc">List</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">classes</span><span class="p">.</span><span class="nx">root</span><span class="si">}</span><span class="p">&gt;</span>
      <span class="si">{</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">item</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span>
        <span class="p">&lt;</span><span class="nc">React</span><span class="p">.</span><span class="nc">Fragment</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">item</span><span class="p">.</span><span class="nx">id</span><span class="si">}</span><span class="p">&gt;</span>
          <span class="p">&lt;</span><span class="nc">TodoListItem</span> <span class="na">item</span><span class="p">=</span><span class="si">{</span><span class="nx">item</span><span class="si">}</span> <span class="p">/&gt;</span>
          <span class="p">&lt;</span><span class="nc">Divider</span> <span class="p">/&gt;</span>
        <span class="p">&lt;/</span><span class="nc">React</span><span class="p">.</span><span class="nc">Fragment</span><span class="p">&gt;</span>
      <span class="p">))</span><span class="si">}</span>
    <span class="p">&lt;/</span><span class="nc">List</span><span class="p">&gt;</span>
  <span class="p">);</span>
<span class="p">};</span>

<span class="k">export</span> <span class="k">default</span> <span class="nx">TodoList</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>DI-container has its own <a href="https://reactjs.org/docs/context.html" target="_blank" rel="noopener noreferrer">React Context</a> called <a href="https://github.com/char0n/swagger-adjust/blob/main/docs/usage/api.md#systemcontext" target="_blank" rel="noopener noreferrer">SystemContext</a>, which accepts its instance. 
Tne first thing we do is – compose a list of plugins and pass it to the System class constructor
(represents DI-container). DI-container automatically compiles the plugins under the hood and 
creates a store instance for us. Then we wrap our main App component into <a href="https://react-redux.js.org/api/provider" target="_blank" rel="noopener noreferrer">react-redux</a> and
SystemContext providers. This allows hooks mentioned above to access anything from the DI-container 
within React components.</p>

<figure class="highlight"><pre><code class="language-jsx" data-lang="jsx"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre></td><td class="code"><pre><span class="cm">/**
 * This component is just responsible to render the main TodoList component.
 * We need to this to allow other plugins to override TodoListLayout.
 */</span>

<span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">TodoListLayout</span> <span class="o">=</span> <span class="nx">useSystemComponent</span><span class="p">(</span><span class="dl">'</span><span class="s1">TodoListLayout</span><span class="dl">'</span><span class="p">);</span>

  <span class="k">return</span> <span class="p">&lt;</span><span class="nc">TodoListLayout</span> <span class="p">/&gt;;</span>
<span class="p">};</span>

<span class="nx">ReactDOM</span><span class="p">.</span><span class="nx">render</span><span class="p">(</span>
  <span class="p">&lt;</span><span class="nc">React</span><span class="p">.</span><span class="nc">StrictMode</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nc">Provider</span> <span class="na">store</span><span class="p">=</span><span class="si">{</span><span class="nx">store</span><span class="si">}</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">SystemContext</span><span class="p">.</span><span class="nc">Provider</span> <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">system</span><span class="p">.</span><span class="nx">getSystem</span><span class="si">}</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nc">App</span> <span class="p">/&gt;</span>
      <span class="p">&lt;/</span><span class="nc">SystemContext</span><span class="p">.</span><span class="nc">Provider</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nc">Provider</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nc">React</span><span class="p">.</span><span class="nc">StrictMode</span><span class="p">&gt;,</span>
  <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">root</span><span class="dl">'</span><span class="p">)</span>
<span class="p">);</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>Let’s look into our second plugin – todoListEnhancer. As mentioned before, this plugin enhances the 
original todoList plugin and adds additional features. Enhancement doesn’t happen via todoList 
plugin code modification but rather by its configuration. And this is where Swagger Adjust really shines.</p>

<figure class="figure d-block">
  <img src="/assets/img/blog/swagger-adjust-todo-list-enhancer-plugin.webp" width="533" height="256" class="figure-img rounded mx-auto d-block img-fluid" alt="todoListEnhancer plugin" />
  <figcaption class="figure-caption text-center">todoListEnhancer plugin</figcaption>
</figure>

<p>Every todo item in todoListEnhancer is now associated with creation time, the checkbox for completion, 
the bin icon for deletion, and at the bottom of TodoList, there are two buttons for batch operations.</p>

<p>Here, we’re presented with todoListEnhancer directory structure and the source code of plugin.js.
Again, please take a moment here and <a href="https://github.com/char0n/swagger-adjust/tree/main/demo/src/todo-list-enhancer" target="_blank" rel="noopener noreferrer">browse these files</a> yourself to see the actual code.
Inside <a href="https://github.com/char0n/swagger-adjust/blob/main/demo/src/todo-list-enhancer/plugin.js" target="_blank" rel="noopener noreferrer">plugin.js</a> we’re replacing the TodoListItem component with the new one. We’re adding
the new TodoListBatchOperations component and wrapping the TodoList component with a new one
that composes the original TodoList component with  TodoListBatchOperations.
Additionally, we’re exposing the time formatting function to the plugin so that additional plugins
could easily override how time is formatted.</p>

<figure class="figure d-block">
  <img src="/assets/img/blog/swagger-adjust-todo-list-enhancer-plugin-dir-structure.webp" width="252" height="202" class="figure-img rounded d-block img-fluid" alt="todoListEnhancer plugin directory structure" />
</figure>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
</pre></td><td class="code"><pre><span class="k">import</span> <span class="p">{</span> <span class="nx">assocPath</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ramda</span><span class="dl">'</span><span class="p">;</span>

<span class="k">import</span> <span class="p">{</span> <span class="nx">completeItem</span><span class="p">,</span> <span class="nx">uncompleteItem</span><span class="p">,</span> <span class="nx">deleteItem</span><span class="p">,</span> <span class="nx">completeAll</span><span class="p">,</span> <span class="nx">deleteAll</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./actions</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">formatTimestamp</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./fn</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">reducers</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./reducers</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">TodoList</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/TodoList</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">TodoListItem</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/TodoListItem</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">TodoListBatchOperations</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/TodoListBatchOperations</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">TodoListEnhancerPlugin</span> <span class="o">=</span> <span class="p">(</span><span class="nx">system</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">todoListFn</span> <span class="o">=</span> <span class="nx">assocPath</span><span class="p">([</span><span class="dl">'</span><span class="s1">formatTimestamp</span><span class="dl">'</span><span class="p">],</span> <span class="nx">formatTimestamp</span><span class="p">,</span> <span class="nx">system</span><span class="p">.</span><span class="nx">fn</span><span class="p">.</span><span class="nx">todoList</span><span class="p">);</span>

  <span class="k">return</span> <span class="p">{</span>
    <span class="na">components</span><span class="p">:</span> <span class="p">{</span>
      <span class="nx">TodoListItem</span><span class="p">,</span>
      <span class="nx">TodoListBatchOperations</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="na">wrapComponents</span><span class="p">:</span> <span class="p">{</span>
      <span class="nx">TodoList</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="na">fn</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">todoList</span><span class="p">:</span> <span class="nx">todoListFn</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="na">statePlugins</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">todoList</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">actions</span><span class="p">:</span> <span class="p">{</span>
          <span class="nx">completeItem</span><span class="p">,</span>
          <span class="nx">uncompleteItem</span><span class="p">,</span>
          <span class="nx">deleteItem</span><span class="p">,</span>
          <span class="nx">completeAll</span><span class="p">,</span>
          <span class="nx">deleteAll</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="na">wrapActions</span><span class="p">:</span> <span class="p">{</span>
          <span class="na">addItem</span><span class="p">:</span> <span class="p">(</span><span class="nx">oriAction</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nx">payload</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">oriAction</span><span class="p">({</span> <span class="p">...</span><span class="nx">payload</span><span class="p">,</span> <span class="na">completed</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">createdAt</span><span class="p">:</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span> <span class="p">});</span>
          <span class="p">},</span>
        <span class="p">},</span>
        <span class="nx">reducers</span><span class="p">,</span>
      <span class="p">},</span>
    <span class="p">},</span>
  <span class="p">};</span>
<span class="p">};</span>

<span class="k">export</span> <span class="k">default</span> <span class="nx">TodoListEnhancerPlugin</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>The last thing we need to do is to register the todoListEnhancer plugin with the DI-Container.</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="code"><pre><span class="kd">const</span> <span class="nx">plugins</span> <span class="o">=</span> <span class="p">[</span><span class="nx">TodoListPlugin</span><span class="p">,</span> <span class="nx">TodoListEnhancerPlugin</span><span class="p">];</span>
<span class="kd">const</span> <span class="nx">system</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">System</span><span class="p">({</span> <span class="nx">plugins</span> <span class="p">});</span>
<span class="kd">const</span> <span class="nx">store</span> <span class="o">=</span> <span class="nx">system</span><span class="p">.</span><span class="nx">getStore</span><span class="p">();</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>When building React+Redux features in this architecture, one always has to think hard about exposing
React components, Redux code, and other business or presentational logic to the plugin so that
other plugins can easily enhance it. This also works in reverse – when building features that need 
to be “protected” from enhancements, instead use classical static ES6 imports, which prohibits anybody
from enhancing your plugins.</p>

<h2 id="advanced-usage-patterns">Advanced usage patterns</h2>

<p>During writing Swagger Adjust and using it on different projects, I’ve identified
multiple advanced usage patterns that need to be mentioned.  These advanced usage patterns include:</p>

<ul>
  <li><a href="https://github.com/char0n/swagger-adjust/blob/main/docs/customization/plugin-api.md#routing-patterns" target="_blank" rel="noopener noreferrer">various messaging patterns (Routing and Transformation) when wrapping actions</a></li>
  <li><a href="https://github.com/char0n/swagger-adjust/blob/main/docs/customization/plugin-api.md#initial-state" target="_blank" rel="noopener noreferrer">providing initial state for a state plugin</a></li>
  <li><a href="https://github.com/char0n/swagger-adjust/blob/main/docs/customization/plugin-api.md#reducers" target="_blank" rel="noopener noreferrer">override reducers from another state plugin</a></li>
  <li><a href="https://github.com/char0n/swagger-adjust/blob/main/docs/customization/plugin-api.md#composing-selectors-from-single-plugin" target="_blank" rel="noopener noreferrer">composing selectors in single state plugin</a></li>
  <li><a href="https://github.com/char0n/swagger-adjust/blob/main/docs/customization/plugin-api.md#composing-selectors-from-different-plugins" target="_blank" rel="noopener noreferrer">composing selectors from different plugins</a></li>
  <li><a href="https://github.com/char0n/swagger-adjust/blob/main/docs/customization/plugin-api.md#hooks" target="_blank" rel="noopener noreferrer">registering and using hooks</a></li>
</ul>

<p>Swagger Adjust consumers need to understand these usage patterns properly as they set conventions on how Swagger Adjust was designed to be used.</p>

<h2 id="word-on-conventions">Word on conventions</h2>

<p>Conventions are essential when producing any code. When utilized properly, it makes code 
produced by different people look like it was produced by a single person.
Redux Toolkit has done a great job setting up conventions and standards for React+Redux applications.
Swagger Adjust adopts all these conventions and adds a couple of its own:</p>

<ul>
  <li>use <a href="https://github.com/char0n/swagger-adjust/blob/main/docs/usage/api.md#createaction" target="_blank" rel="noopener noreferrer">createAction</a> helper to create action creators</li>
  <li>use following convention for action type: “namespace/action”</li>
  <li>use <a href="https://github.com/char0n/swagger-adjust/blob/main/docs/usage/api.md#createasyncthunk" target="_blank" rel="noopener noreferrer">createAsyncThunk</a> to create async actions or handle side effects</li>
  <li>structure Files as Feature Folders as shown in this article</li>
  <li>name your selectors in the following naming scheme: <code class="language-plaintext highlighter-rouge">select&lt;Name&gt;</code></li>
  <li>create memoized selectors using <a href="https://github.com/char0n/swagger-adjust/blob/main/docs/usage/api.md#createselector" target="_blank" rel="noopener noreferrer">createSelector</a> utility</li>
</ul>

<p>Consult <a href="https://redux.js.org/style-guide/style-guide" target="_blank" rel="noopener noreferrer">Redux Style Guide</a> for additional conventions.</p>

<h2 id="closing-words">Closing words</h2>

<p>Swagger Adjust it’s an ideal framework for creating User Interfaces (UI) with external extensibility
requirement. It should work pretty well for creating renderers for different specifications like OpenApi,
AsyncApi, or ApiBlueprint (as SwaggerUI demonstrated). It allows external extensibility via configuration 
and plugins, bringing much more features than demonstrated in this article. 
I welcome you to consult our documentation to learn more.</p>

<p>You can find all code demonstrated in this article in the <a href="https://github.com/char0n/swagger-adjust" target="_blank" rel="noopener noreferrer">Swagger Adjust GitHub repository</a>.</p>

<div class="list-group mb-3">
  <a href="https://github.com/char0n/swagger-adjust" class="list-group-item list-group-item-action" target="_blank" rel="noopener noreferrer">
    <div class="d-flex w-100 justify-content-between">
      <h2 class="h5 mb-1"><i class="fa-brands fa-github"></i> swagger-adjust</h2>
    </div>
    <blockquote class="blockquote fs-6 mb-1">
      Pluggable framework for creating extendable React+Redux applications.
    </blockquote>
    <script type="application/ld+json">
      {
        "@context": "https://schema.org",
        "@type": "SoftwareSourceCode",
        "author": { "@id": "https://vladimirgorej.com" },
        "name": "swagger-adjust",
        "abstract": "Pluggable framework for creating extendable React+Redux applications.",
        "codeRepository": "https://github.com/char0n/swagger-adjust"
      }
    </script>
  </a>
</div>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[Step by step guide to building React-Redux apps using pluggable architecture. Features are produced as plugins and are later composed to create the app. Plugins can be removed, added, or amended by other plugins.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vladimirgorej.com/assets/img/blog/swagger-adjust.webp" /><media:content medium="image" url="https://vladimirgorej.com/assets/img/blog/swagger-adjust.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Ramda Adjunct</title><link href="https://vladimirgorej.com/blog/ramda-adjunct/" rel="alternate" type="text/html" title="Ramda Adjunct" /><published>2017-02-23T08:00:00+00:00</published><updated>2017-02-23T08:00:00+00:00</updated><id>https://vladimirgorej.com/blog/ramda-adjunct</id><content type="html" xml:base="https://vladimirgorej.com/blog/ramda-adjunct/"><![CDATA[<p class="lead">
  Ramda Adjunct is the most popular and most comprehensive set of functional utilities for use with Ramda, providing a variety of useful, well-tested functions with excellent documentation.
  This article describes the history of Ramda Adjunct's creation. 
</p>

<p>Functional point-free programming is all about composition. When I’m building an application nowadays,
I use composition from top to bottom. I compose functions to create modules, modules to compose features, 
and features to compose applications. For composing functions themselves, I use functional libraries.
Namely, my two most favorite <a href="https://lodash.com/" target="_blank" rel="noopener noreferrer">lodash</a> and <a href="https://ramdajs.com/" target="_blank" rel="noopener noreferrer">Ramda</a>.</p>

<p>While using these functional libraries, I found out that in every new project I was working on, 
I tend to create the same set of aggregate functions that I missed either from lodash or Ramda.
Let me demonstrate this in a code snippet:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="code"><pre><span class="kd">const</span> <span class="p">{</span> <span class="nx">isString</span><span class="p">,</span> <span class="nx">negate</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">lodash/fp</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">isNil</span><span class="p">,</span> <span class="nx">complement</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">ramda</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">isNotString</span> <span class="o">=</span> <span class="nx">negate</span><span class="p">(</span><span class="nx">isString</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">isNotNil</span> <span class="o">=</span> <span class="nx">complement</span><span class="p">(</span><span class="nx">isNil</span><span class="p">);</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>After realizing that I had to redefine these functions in every project, I opened my browser and
searched for extensions to lodash and Ramda. I decided to narrow my search to Ramda only because 
it is missing more functions related to my use-cases.</p>

<p><strong>My search criteria were:</strong></p>

<ul>
  <li>timestamp of the last commit</li>
  <li>100% test coverage</li>
  <li>quality documentation similar to Ramda</li>
</ul>

<p><strong>The results</strong>:</p>

<p>I could not find anything solid I could use. I found multiple fragmented libraries; some didn’t have tests at all, 
some did not have any documentation, some were buggy, and some were focused on Promises, etc…
Of course, I could use a combination of multiple libraries, but again, I will not build software on 
libraries without any test coverage.</p>

<p>Hence <a href="https://github.com/char0n/ramda-adjunct" target="_blank" rel="noopener noreferrer">Ramda Adjunct</a> was born.</p>

<div class="list-group mb-3">
  <a href="https://github.com/char0n/ramda-adjunct" class="list-group-item list-group-item-action" target="_blank" rel="noopener noreferrer">
    <div class="d-flex w-100 justify-content-between">
      <h2 class="h5 mb-1"><i class="fa-brands fa-github"></i> ramda-adjunct</h2>
    </div>
    <blockquote class="blockquote fs-6 mb-1">
      Ramda Adjunct is the most popular and most comprehensive set of functional utilities for use with Ramda, providing a variety of useful, well tested functions with excellent documentation.
    </blockquote>
    <script type="application/ld+json">
      {
        "@context": "https://schema.org",
        "@type": "SoftwareSourceCode",
        "author": { "@id": "https://vladimirgorej.com" },
        "name": "ramda-adjunct",
        "abstract": "Ramda Adjunct is the most popular and most comprehensive set of functional utilities for use with Ramda, providing a variety of useful, well tested functions with excellent documentation.",
        "codeRepository": "https://github.com/char0n/ramda-adjunct"
      }
    </script>
  </a>
</div>

<p><strong>Ramda Adjunct</strong> is based on three main principles:</p>

<ul>
  <li>centralization</li>
  <li>100% test coverage</li>
  <li>impeccable documentation</li>
</ul>

<h2 id="centralization">Centralization</h2>

<p>All Ramda recipes and aggregate utils not present in Ramda are centralized here. 
There is no more need for everybody to create their own utils in their own codebases.</p>

<h2 id="100-test-coverage">100% test coverage</h2>

<p>Creating custom aggregate utils or implementing recipes from the Ramda wiki creates the defectiveness problem.
The problem is caused by the absence of any tests. Ramda Adjunct keeps <a href="https://app.codecov.io/gh/char0n/ramda-adjunct" target="_blank" rel="noopener noreferrer">100% code coverage</a> and mimics the Ramda 
test patterns.</p>

<h2 id="impeccable-documentation">Impeccable documentation</h2>

<p>You cannot call a library great without excellent documentation. Ramda Adjunct generates its <a href="https://char0n.github.io/ramda-adjunct/" target="_blank" rel="noopener noreferrer">documentation</a>
directly from its codebase and uses patterns found in Ramda and Lodash to document its API.</p>

<h2 id="what-are-the-future-plans">What are the future plans?</h2>

<p>My first priority for the near feature is to absorb obsolete and no longer maintained <a href="https://github.com/enten/rcb" target="_blank" rel="noopener noreferrer">Ramda Cookbook implementation</a>
and implement all <code class="language-plaintext highlighter-rouge">Type</code> related functions that I’m missing in Ramda. 
After that, I would like to continue absorbing other <code class="language-plaintext highlighter-rouge">dead</code> libraries.
Ramda Adjunct repo is ready to receive pull requests. If you have some recipes you would like to share with the community,
please issue a pull request or <a href="https://github.com/char0n/ramda-adjunct/issues/new/choose" target="_blank" rel="noopener noreferrer">create an issue on GitHub</a>.</p>]]></content><author><name>Vladimír Gorej</name></author><summary type="html"><![CDATA[Ramda Adjunct is the most popular and most comprehensive set of functional utilities for use with Ramda.js, providing a variety of useful, well-tested functions with excellent documentation. This article describes the history of Ramda Adjunct's creation.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vladimirgorej.com/assets/img/blog/ramda-adjunct.webp" /><media:content medium="image" url="https://vladimirgorej.com/assets/img/blog/ramda-adjunct.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>