<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>LornaJane's Website</title><link href="https://lornajane-net.pages.dev/" rel="alternate"/><link href="https://lornajane-net.pages.dev/feeds/all.atom.xml" rel="self"/><id>https://lornajane-net.pages.dev/</id><updated>2026-06-29T20:06:00+01:00</updated><entry><title>Using mitmproxy reverse mode for API testing</title><link href="https://lornajane-net.pages.dev/posts/2026/using-mitmproxy-reverse-mode" rel="alternate"/><published>2026-06-29T20:06:00+01:00</published><updated>2026-06-29T20:06:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2026-06-29:/posts/2026/using-mitmproxy-reverse-mode</id><summary type="html">&lt;p class="first last"&gt;&lt;a class="reference external" href="https://www.mitmproxy.org/"&gt;mitmproxy&lt;/a&gt; is my preferred proxy for API testing these days. I'm using it mostly in reverse mode though, so I thought I'd capture my setup in case it's useful for anyone else. My main use case is to inspect the traffic coming into the API server and being returned, mostly when running tests against it. Where mitmproxy is typically used as a proxy to monitor traffic passing into/out of a client application, for this I'm concerned with the server's handling. Reverse mode lets me inspect and adjust the traffic as I go along. I start up the proxy, configure the tests to point to it instead of the actual server, and I'm all set.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://www.mitmproxy.org/"&gt;mitmproxy&lt;/a&gt; is my preferred proxy for API testing these days. I'm using it mostly in reverse mode though, so I thought I'd capture my setup in case it's useful for anyone else. My main use case is to inspect the traffic coming into the API server and being returned, mostly when running tests against it. Where mitmproxy is typically used as a proxy to monitor traffic passing into/out of a client application, for this I'm concerned with the server's handling. Reverse mode lets me inspect and adjust the traffic as I go along. I start up the proxy, configure the tests to point to it instead of the actual server, and I'm all set.&lt;/p&gt;
&lt;div class="section" id="start-reverse-proxy-mode"&gt;
&lt;h3&gt;Start reverse proxy mode&lt;/h3&gt;
&lt;p&gt;Mitmproxy has a great web UI as well but as a keyboard-only user, I just use the terminal tools - pick what works best for you, but these instructions are CLI-based.&lt;/p&gt;
&lt;p&gt;Start the reverse proxy and tell it where to send traffic onward to; in this example the API I'm calling is on &lt;a class="reference external" href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
mitmproxy --mode reverse:http://localhost:3000
&lt;/pre&gt;
&lt;p&gt;By default the server listens on port 8080 and forwards everything.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configure-the-application"&gt;
&lt;h3&gt;Configure the application&lt;/h3&gt;
&lt;p&gt;In my use case, I'm adjusting the API tests to point to the proxy instead of the actual server so I can capture, replay, and/or adjust the traffic when I'm working with the tests.&lt;/p&gt;
&lt;p&gt;I configure my tests to test an API at &lt;a class="reference external" href="http://localhost:8080"&gt;http://localhost:8080&lt;/a&gt; (actually I'm running bits of this in docker so it ends up being &lt;a class="reference external" href="http://host.docker.internal:8080"&gt;http://host.docker.internal:8080&lt;/a&gt; but this is more of a note to future-me than general advice!)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That's all&lt;/strong&gt;. Start using the application and you should start to see the requests and responses showing up in mitmproxy.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="additional-notes-and-tricks"&gt;
&lt;h3&gt;Additional notes and tricks&lt;/h3&gt;
&lt;p&gt;From the logged traffic records, you can replay, duplicate, and edit requests, which is great for testing and inspecting anything that's not trivial to reproduce.&lt;/p&gt;
&lt;p&gt;You can also use &lt;a class="reference external" href="https://docs.mitmproxy.org/stable/addons/overview/"&gt;mitmproxy add-ons&lt;/a&gt; to &lt;strong&gt;rewrite responses&lt;/strong&gt; if you need to. I use this feature to throw unexpected responses (that the API should never actually produce) back at the tests to make sure they do fail when they should!&lt;/p&gt;
&lt;p&gt;To save a flow, either to share or to come back to later on, press &lt;tt class="docutils literal"&gt;w&lt;/tt&gt; and provide a filename. Then load it in a future mitmproxy session using a command like &lt;tt class="docutils literal"&gt;mitmproxy &lt;span class="pre"&gt;-r&lt;/span&gt; flows.mitm&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Add a comment if you have additional experiences to share, I'm always excited to learn some new tricks to add to my collection!&lt;/p&gt;
&lt;/div&gt;
</content><category term="APIs"/><category term="api"/><category term="cli"/><category term="http"/><category term="mitmproxy"/><category term="proxy"/><category term="tips"/><category term="tech"/></entry><entry><title>Selectively ignore lines in git diff</title><link href="https://lornajane-net.pages.dev/posts/2026/selectively-ignore-lines-in-git-diff" rel="alternate"/><published>2026-03-04T07:57:00+00:00</published><updated>2026-03-04T07:57:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2026-03-04:/posts/2026/selectively-ignore-lines-in-git-diff</id><summary type="html">&lt;p class="first last"&gt;I have a things-as-code project that outputs mostly text-based formats, but a lot of them. To keep an eye on consistency, I rebuild all the outputs and dump them into a local git repository so I can very easily diff to spot any changes - which was fine until we added a build timestamp, so every file looks changed on every run! This post is about ignoring the matching line with &lt;tt class="docutils literal"&gt;git diff &lt;span class="pre"&gt;-I&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I have a things-as-code project that outputs mostly text-based formats, but a lot of them. To keep an eye on consistency, I rebuild all the outputs and dump them into a local git repository so I can very easily diff to spot any changes - which was fine until we added a build timestamp, so every file looks changed on every run! This post is about ignoring the matching line with &lt;tt class="docutils literal"&gt;git diff &lt;span class="pre"&gt;-I&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;With a task like this where I want a quick check on a LOT of output (a few million lines I think) which &lt;em&gt;probably&lt;/em&gt; hasn't changed much, the text-based output and source control tools are a great fit. Also since I'm a confident git user, I can very easily re-run the script in the directory (already set up with the paths for my system) and when it finishes, check there's nothing in git diff.&lt;/p&gt;
&lt;p&gt;It worked great - until it didn't! We added a nice piece of metadata showing when the file was last regenerated, which seemed like a good feature and I approved the change myself. But of course it means all the output files look changed each time. I needed a way to ignore only that change and still see anything except the timestamp updates.&lt;/p&gt;
&lt;div class="section" id="ignore-patterns-in-git-diff"&gt;
&lt;h3&gt;Ignore patterns in git diff&lt;/h3&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-I&lt;/span&gt;&lt;/tt&gt; (it's a capital &amp;quot;i&amp;quot; and is short for &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--ignore-matching-lines&lt;/span&gt;&lt;/tt&gt;) accepts a regex and can be used with diff, log, show and probably other git commands. I'm using it with diff, like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
git diff -I 'x-generated-date'
&lt;/pre&gt;
&lt;p&gt;I only see diff entries that do not match this regex (okay, in this example it's a string, but you can do cleverer things if you need them! I could put the datetime pattern in here to be super certain what I'm matching), which is exactly what I need. I know that this line will always be changed so we can ignore it and only inspect any &lt;em&gt;other&lt;/em&gt; changes that are present.&lt;/p&gt;
&lt;p&gt;To see just the list of files so I can get an idea of the blast radius on what I just changed, I can add &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--stat&lt;/span&gt;&lt;/tt&gt; to my diff command.&lt;/p&gt;
&lt;p&gt;Working with larger filesets and particularly with generated output where it's useful to quickly sanity check any side effects, tricks like this are so useful - and I thought I would share. If you have additional strategies to offer me in return then I would love to hear about them in the comments, please!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="git"/><category term="tips"/></entry><entry><title>Git renames are not renames</title><link href="https://lornajane-net.pages.dev/posts/2026/git-renames-are-not-renames" rel="alternate"/><published>2026-01-28T09:29:00+00:00</published><updated>2026-01-28T09:29:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2026-01-28:/posts/2026/git-renames-are-not-renames</id><summary type="html">&lt;p class="first last"&gt;I consider myself pretty git-confident, I've worked with it a lot, taught it, been a git consultant, run engineering and various things-as-code teams. This week I had a spectactular git problem where merging one branch into another produced changes that didn't exist on either branch. Turns out, renaming directories in a monorepo with multiple almost-identical boilerplate documentation files comes with surprises...&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I consider myself pretty git-confident, I've worked with it a lot, taught it, been a git consultant, run engineering and various things-as-code teams. This week I had a spectactular git problem where merging one branch into another produced changes that didn't exist on either branch. Turns out, renaming directories in a monorepo with multiple almost-identical boilerplate documentation files comes with surprises...&lt;/p&gt;
&lt;p&gt;The short version is: Git actually doesn't track renames at all. It tracks adds and deletes, and presents them to the end user as renames. If the file is 50% the same, it's considered a rename. It's a bit of a blunt instrument and so it goes wrong sometimes. You can adjust whether your client shows you renames, or how similar the files can be - but you don't impact how other users see it.&lt;/p&gt;
&lt;div class="section" id="the-situation"&gt;
&lt;h3&gt;The situation&lt;/h3&gt;
&lt;p&gt;I'm in a situation where I need to alter the directory structure of a monorepo with lots of similar-structured but actually different files that appear in every folder. I'm adding an additional folder level, to allow siblings of each project. Did I mention that git doesn't support renames? It doesn't track folders either.&lt;/p&gt;
&lt;p&gt;The similar structures of the projects turned out to be even more interesting when I realised that there are two sets of mostly-boilerplate documentation in every folder. The two folders (they both have markup content that gets published as documents) have some files that are named the same, and every folder has both folders, with either no changes, or with very minor (but crucial!) changes.&lt;/p&gt;
&lt;p&gt;I renamed every folder, committed the changes, updated ALL the build scripts to handle the changes, and thought I'd done the difficult bit (famous last words). Then when I started applying these updates to the existing work-in-progress branches, I noticed that git was deleting or updating files in other projects within the monorepo - changes that were not in either of the branches I was combining.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="why-this-happens"&gt;
&lt;h3&gt;Why this happens&lt;/h3&gt;
&lt;p&gt;Git simply has no idea which adds go with which deletes - and on a big project, with a lot of almost-the-same files which were all renamed? It just fails in a big messy heap.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Things I tried that did not help at all:&lt;/div&gt;
&lt;div class="line"&gt;- splitting up the restructure into small chunks so I only moved one directory at a time, per commit&lt;/div&gt;
&lt;div class="line"&gt;- adjusting git's rename settings when merging the branches&lt;/div&gt;
&lt;div class="line"&gt;- swearing a lot&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;(I did have it on my list to make all the repeated content into templated files, because it's annoying to maintain as it is - but I had no idea that was on the critical path for the directory restructure!)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="this-story-has-no-ending"&gt;
&lt;h3&gt;This story has no ending&lt;/h3&gt;
&lt;p&gt;As things stand, I have no solution! I knew this was the way git worked and I knew there was content that could be problematic - but this problem manifested in a way I didn't expect when the changes which had been applied apparently without any issue were then merged into the ongoing feature branches (it's a slow moving project, a lot of contributors, all the reasons I normally advise against doing this sort of repository surgery in the first place). I started seeing phantom changes that simply didn't exist in any of the &amp;quot;before&amp;quot; branches and it took me a moment to understand what happened.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Things I am doing so we can make progress with the changes:&lt;/div&gt;
&lt;div class="line"&gt;- lots of checks in CI for pull requests to help humans catch any weirdness at review time and before merge&lt;/div&gt;
&lt;div class="line"&gt;- having a good attitude to if we need to fix minor documentation excitements as a result of these changes, because they are absolutely a positive change for the project and it's the least interesting files that are impacted&lt;/div&gt;
&lt;div class="line"&gt;- manually applying the same folder restructures to the feature branches (it was a script in the first place so it's repeatable) before allowing users to sync with the main branch seems to help git to know what's happening&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;My only advice is either not to do this sort of change, or to have a no-pull-requests interval. Neither of those were options for me though so I'm sharing in case you find yourself in a similar situation some day! Be aware that &amp;quot;renames&amp;quot; (and in fact &amp;quot;directories&amp;quot;) are not always what they seem ....&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="git"/><category term="tips"/></entry><entry><title>Manage Diagrams in AsciiDoc on GitHub</title><link href="https://lornajane-net.pages.dev/posts/2026/manage-diagrams-in-asciidoc-on-github" rel="alternate"/><published>2026-01-25T21:22:00+00:00</published><updated>2026-01-25T21:22:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2026-01-25:/posts/2026/manage-diagrams-in-asciidoc-on-github</id><summary type="html">&lt;p class="first last"&gt;I use a lot of &lt;a class="reference external" href="https://asciidoc.org/"&gt;asciidoc&lt;/a&gt; these days for work documentation (and I love it) and I've been so happy that GitHub renders it when you view a repository in the web browser, just like it does for Markdown and ReStructuredText. BUT what GitHub does not do is render the image types that asciidoc does so even though I'm working with asciidoc and PlantUML, and the asciidoc tools render those diagrams nicely in PDF and HTML output - GitHub's rendering doesn't. So here's a quick overview of how I handle those repositories.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I use a lot of &lt;a class="reference external" href="https://asciidoc.org/"&gt;asciidoc&lt;/a&gt; these days for work documentation (and I love it) and I've been so happy that GitHub renders it when you view a repository in the web browser, just like it does for Markdown and ReStructuredText. BUT what GitHub does not do is render the image types that asciidoc does so even though I'm working with asciidoc and PlantUML, and the asciidoc tools render those diagrams nicely in PDF and HTML output - GitHub's rendering doesn't. So here's a quick overview of how I handle those repositories.&lt;/p&gt;
&lt;p&gt;Before I start, I should mention that for our context, the outputs are usually A4-sized PDF documents, so GitHub in the browser is a similar sort of form factor, and none of this stuff needs to be print quality. If you have a use case that's none of the above, you might want to take a different approach.&lt;/p&gt;
&lt;div class="section" id="setup-for-github-and-asciidoctor"&gt;
&lt;h3&gt;Setup for GitHub AND Asciidoctor&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://asciidoctor.org/"&gt;Asciidoctor&lt;/a&gt; has good rendering of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/PlantUML"&gt;PlantUML&lt;/a&gt; (and all sorts of other format) diagrams, but GitHub does not, so the tradeoff is to have the diagram source in the repository, have build scripts (that run locally and in CI) to build the SVG version of the diagram and also include that in the repository, and to reference the SVG to include the diagram in the document.&lt;/p&gt;
&lt;p&gt;When adding a diagram, the author creates the PlantUML (&lt;tt class="docutils literal"&gt;.puml&lt;/tt&gt;) file and runs the build script which generates the SVG format in an &lt;tt class="docutils literal"&gt;images/&lt;/tt&gt; directory. The author can then put the link to the SVG file into the text.&lt;/p&gt;
&lt;p&gt;The build script regenerates images if the user has the PlantUML tooling installed so that any update are included. If the tool isn't there, the &lt;tt class="docutils literal"&gt;images/&lt;/tt&gt; directory is included in the repository anyway so that the content is viewable in GitHub, so the existing images can be used locally too.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="image-content-that-works-in-multiple-publish-destinations"&gt;
&lt;h3&gt;Image content that works in multiple publish destinations&lt;/h3&gt;
&lt;p&gt;This version of the diagrams works perfectly well in the generated PDFs too, and since there's a build step for PDFs there was an obvious place to hook the image generation step into.&lt;/p&gt;
&lt;p&gt;SVG works well enough in both contexts, and while it's annoying to have the extra build requirement for authors, I think the benefits of having the content readable with images in the repository is worth it. Having to get the published PDF to have all the information available is not great, and this is a document that has a lot more readers than writers!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="asciidoc"/><category term="ci"/><category term="git"/><category term="github"/><category term="markup"/><category term="tips"/></entry><entry><title>Tag Kinds in OpenAPI 3.2</title><link href="https://lornajane-net.pages.dev/posts/2025/tag-kinds-in-openapi-3-2" rel="alternate"/><published>2025-12-08T08:32:00+00:00</published><updated>2025-12-08T08:32:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-12-08:/posts/2025/tag-kinds-in-openapi-3-2</id><summary type="html">&lt;p class="first last"&gt;OpenAPI tags have always been annoying: user-supplied arbitrary data for endpoints &lt;em&gt;should&lt;/em&gt; be a fabulous feature - but the documentation tools seem to think that tags are only for them so it becomes more difficult to use tags for other purposes. In fact it is very useful to be able to tag endpoints with lots of different categories of data and so in OpenAPI 3.2, tags were enhanced to include an additional &amp;quot;kind&amp;quot; field so that different kinds of tag could be used.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;OpenAPI tags have always been annoying: user-supplied arbitrary data for endpoints &lt;em&gt;should&lt;/em&gt; be a fabulous feature - but the documentation tools seem to think that tags are only for them so it becomes more difficult to use tags for other purposes. In fact it is very useful to be able to tag endpoints with lots of different categories of data and so in OpenAPI 3.2, tags were enhanced to include an additional &amp;quot;kind&amp;quot; field so that different kinds of tag could be used.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Being grumpy about documentation tools using tags for themselves is silly (especially as I built some of those tools!), and I'm really pleased there's a better way now. Tags might be useful for:&lt;/div&gt;
&lt;div class="line"&gt;- tagging endpoints that are only for an exclusive group of users&lt;/div&gt;
&lt;div class="line"&gt;- tagging endpoints to show something is &amp;quot;new&amp;quot; in an API, or relates to a particular version&lt;/div&gt;
&lt;div class="line"&gt;- tagging endpoints to indicate their lifecycle status such as being in beta, or being deprecated&lt;/div&gt;
&lt;div class="line"&gt;- tagging endpoints to say whether they are included in the published documentation or SDKs&lt;/div&gt;
&lt;div class="line"&gt;- tagging if this endpoint follows a particular pattern or exception to a pattern&lt;/div&gt;
&lt;div class="line"&gt;- tagging for some other tool to pick up and use the tag for more information later&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;... you get the idea, tags can be used for anything now.&lt;/p&gt;
&lt;div class="section" id="declare-the-kind-of-tag-it-is"&gt;
&lt;h3&gt;Declare the kind of tag it is&lt;/h3&gt;
&lt;p&gt;Tags are declared in their own &lt;tt class="docutils literal"&gt;tags&lt;/tt&gt; section, and it's here that you can say what sort of tags they are. Here's an example:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;product&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Products&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Product operations&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;partner&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Partner&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Operations available only to partner organisations, or other privileged accounts.&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;audience&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;v2&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Version 2&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Available in API v2 or later&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;version&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Tags without a &amp;quot;kind&amp;quot; default to type &amp;quot;nav&amp;quot; - meaning the navigation tags which is the pre-3.2 standard behaviour. This default setting means that you can upgrade an OpenAPI description from 3.1 to 3.2 without needing to make changes, but when you want to introduce new tags you can do so by including the &amp;quot;kind&amp;quot; of tag that they are.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="apply-the-tag"&gt;
&lt;h3&gt;Apply the tag&lt;/h3&gt;
&lt;p&gt;Applying tags is done the same way as before - again, no changes needed on upgrade and the additional data is applied in the &lt;tt class="docutils literal"&gt;tags&lt;/tt&gt; entry rather than at the operation level. So it looks something like the example below if I apply a v2 tag:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/v2/products/&amp;quot;&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;getProductsV2&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Get all products&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;v2&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;products&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Applying multiple different kinds of tag is expected and doesn't need any special setup - the tags have their own properties, and you can apply as many as you like.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="tags-registry"&gt;
&lt;h3&gt;Tags Registry&lt;/h3&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;kind&lt;/tt&gt; field is an open string - you can define your own kinds of tag and that's intentional so that this feature can be used in different ways in different contexts. That said, there are some common use cases and for those we'll get much better experiences if we collaborate on the tags we use and how we use them. That way, we can all align and have the tools support our use cases and give us seamless interoperability as we send our OpenAPI descriptions through all sorts of different pipelines.&lt;/p&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://spec.openapis.org/registry/tag-kind/index.html"&gt;OpenAPI tag kinds registry&lt;/a&gt; has already been set up with some basic entries: nav (default), badge, and audience. Please check out the list for new entries and open pull requests to add tag kinds you're using yourself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="tags-are-not-just-for-docs-any-more"&gt;
&lt;h3&gt;Tags are not just for docs any more&lt;/h3&gt;
&lt;p&gt;Tell your friends, your colleagues, and your AI assistants: tags are not a docs feature. They're an arbitrary user-supplied data feature, and I can't wait to see what you build!&lt;/p&gt;
&lt;p&gt;For more information about tags, read the &lt;a class="reference external" href="https://learn.openapis.org/specification/tags.html#kind-field-for-tag-classification"&gt;tag kinds documentation on the OpenAPI Learn site&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="APIs"/><category term="kind"/><category term="openapi"/><category term="tags"/><category term="tips"/></entry><entry><title>Nested tags in OpenAPI 3.2</title><link href="https://lornajane-net.pages.dev/posts/2025/nested-tags-in-openapi-3-2" rel="alternate"/><published>2025-12-04T20:53:00+00:00</published><updated>2025-12-04T20:53:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-12-04:/posts/2025/nested-tags-in-openapi-3-2</id><summary type="html">&lt;p class="first last"&gt;OpenAPI has always had support for simple tags, but the OpenAPI 3.2 release brought in some serious tag upgrades including a summary field, a &amp;quot;kind&amp;quot; field with registry, - and the ability to nest tags which is the focus of today's post. If one level of organisation isn't enough for your API (and on bigger APIs I'd argue it shouldn't be) then the ability to indicate which tag is the parent of this tag will be a good feature to adopt when you upgrade your OpenAPI descriptions.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;OpenAPI has always had support for simple tags, but the OpenAPI 3.2 release brought in some serious tag upgrades including a summary field, a &amp;quot;kind&amp;quot; field with registry, - and the ability to nest tags which is the focus of today's post. If one level of organisation isn't enough for your API (and on bigger APIs I'd argue it shouldn't be) then the ability to indicate which tag is the parent of this tag will be a good feature to adopt when you upgrade your OpenAPI descriptions.&lt;/p&gt;
&lt;p&gt;The basic premise is: each tag has a 'parent' field which contains the name of a tag for this tag to belong to. Let's quickly look at an example:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;account&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Account Details&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Operations relating to the customer's account&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;accVerification&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Verification&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Operations for verify a new user or user detail change&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;account&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The parent tag does not have awareness of its children, but the child tag indicates the parent that it belongs to. This works well for APIs that are composed of smaller APIs such as in a miroservices context.&lt;/p&gt;
&lt;p&gt;There are no rules in the specification itself about the matching parent must exist - there are also no restrictions on creating structures that clearly won't work such as loops. The tools that implement the tag structures will probably point out the error of your ways though, if you try to use something like that!&lt;/p&gt;
&lt;p&gt;APIs that have previously used extensions to group tags, such as &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;x-tagGroups&lt;/span&gt;&lt;/tt&gt; (which I see fairly frequently), will find this approach gives an OpenAPI official way to do the same thing. The major benefit is that all tools now use the same syntax to describe the same thing and your API descriptions should be more portable across different contexts.&lt;/p&gt;
&lt;p&gt;For more details on nesting tags in OpenAPI, see the &lt;a class="reference external" href="https://learn.openapis.org/specification/tags.html#parent-field-for-hierarchical-organization"&gt;OpenAPI learn site&lt;/a&gt; - then come back here and leave me a comment on what your structure looks like!&lt;/p&gt;
</content><category term="APIs"/><category term="openapi"/><category term="tags"/><category term="tips"/><category term="tech"/></entry><entry><title>Quick local API docs with Scalar</title><link href="https://lornajane-net.pages.dev/posts/2025/quick-local-api-docs-with-scalar" rel="alternate"/><published>2025-09-17T21:05:00+01:00</published><updated>2025-09-17T21:05:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-09-17:/posts/2025/quick-local-api-docs-with-scalar</id><summary type="html">&lt;p class="first last"&gt;Today I'm sharing a quick-and-dirty script to take an OpenAPI description, spin up a docs server locally, and copy the URL into your clipboard. I also use a bit of glob expansion in my script to find the right folder, because I have a lot of APIs with long and formulaic directory names (&lt;a class="reference external" href="https://tmforum.org"&gt;TM Forum members&lt;/a&gt; know this story). I'm spinning up &lt;a class="reference external" href="https://scalar.com/"&gt;Scalar&lt;/a&gt; here as it's my current favourite just-works local API docs platform.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Today I'm sharing a quick-and-dirty script to take an OpenAPI description, spin up a docs server locally, and copy the URL into your clipboard. I also use a bit of glob expansion in my script to find the right folder, because I have a lot of APIs with long and formulaic directory names (&lt;a class="reference external" href="https://tmforum.org"&gt;TM Forum members&lt;/a&gt; know this story). I'm spinning up &lt;a class="reference external" href="https://scalar.com/"&gt;Scalar&lt;/a&gt; here as it's my current favourite just-works local API docs platform.&lt;/p&gt;
&lt;p&gt;The script:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="ch"&gt;#!/usr/bin/env -S uv run --script&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# /// script&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# requires-python = &amp;quot;&amp;gt;=3.11&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# dependencies = [&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;#   &amp;quot;argparse&amp;quot;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# ]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# ///&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;glob&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;subprocess&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;random&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Add your own fudge/expansion magic here if you have APIs to find&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;'../api-*/oas/*.oas.yaml'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;'http://localhost:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# works for one match&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# copy URL to clipboard for convenience&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pbcopy&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scalar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;document&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;serve&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;-p&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;API not found&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;I'm using Python and I run this script with &lt;a class="reference external" href="https://docs.astral.sh/uv/"&gt;uv&lt;/a&gt; because I run this script from lots of different folders and it takes care of dependencies so neatly (confession: my actual script uses argparse and some inputs to feed the &amp;quot;guess the location of the API file from these three digits&amp;quot; magic but that part probably isn't useful to other people!)&lt;/p&gt;
&lt;p&gt;First I generate a random port number and use it to construct a URL - there's no checking that the port is available but given that I rarely have more than 4 or 5 of these up at one time, it rarely collides and I just run the script again if it does!&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;subprocess&lt;/tt&gt; calls then run the commands I would actually run myself on the commandline if I didn't use a script to generate a random port number and do some path fudging logic for me:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;`pbcopy &lt;a class="reference external" href="http://localhost:3333"&gt;http://localhost:3333&lt;/a&gt;` (run this command first because the next one blocks)&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;scalar document serve &lt;span class="pre"&gt;-p&lt;/span&gt; 3333 openapi.yaml&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then I have the URL and can paste it into my browser. You could just open a browser tab instead of copying but I find it always picks the wrong tab/window/app/whatever so in my clipboard is more useful to me. I also use a clipboard history tool so it isn't a problem to have lots of things writing to my copy buffer.&lt;/p&gt;
&lt;p&gt;Feel free to adapt the script to fit your own paths and preferred tools; probably some of you do this often enough that it's worth having a wrapper rather than remembering the correct incantation or relying on a hosted platform to download and upload to when the file is already local. And as always, if you do something differently, I'd love to hear about it!&lt;/p&gt;
</content><category term="APIs"/><category term="api"/><category term="scalar"/><category term="tips"/><category term="tools"/></entry><entry><title>API Specificity with Overlays and Enums</title><link href="https://lornajane-net.pages.dev/posts/2025/api-specificity-with-overlays-and-enums" rel="alternate"/><published>2025-07-21T08:48:00+01:00</published><updated>2025-07-21T08:48:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-07-21:/posts/2025/api-specificity-with-overlays-and-enums</id><summary type="html">&lt;p class="first last"&gt;The more I work on API standards, the more I realise how few teams understand that they can adopt the standards and, without breaking any contract, adapt them to make a strong interface for their own application. One of my favourite examples is to add enums where a standard interface cannot dictate the exact values, but in your own implementation it is very helpful to do so.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;The more I work on API standards, the more I realise how few teams understand that they can adopt the standards and, without breaking any contract, adapt them to make a strong interface for their own application. One of my favourite examples is to add enums where a standard interface cannot dictate the exact values, but in your own implementation it is very helpful to do so.&lt;/p&gt;
&lt;p&gt;To start with, let's use this library book example snippet from an OpenAPI file - this is just the Components section:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;object&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;isbn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;ISBN publication identifier&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;isbn&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;9781250236210&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Book title&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;A&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Psalm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Wild-Built&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Author's name&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Becky&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Chambers&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;genre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Genre of the book&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Solarpunk&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;That &lt;tt class="docutils literal"&gt;genre&lt;/tt&gt; field at the end of the list is just a string - but in my library, I'm going to only offer a very specific set of genres, so I want to limit the values that can be used there. To that I'll use an OpenAPI Overlay.&lt;/p&gt;
&lt;p&gt;Here's the Overlay:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;overlay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1.0.0&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;x-speakeasy-jsonpath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;rfc9535&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Set&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;genres&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;list&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1.0.1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;$[&amp;quot;components&amp;quot;][&amp;quot;schemas&amp;quot;][&amp;quot;Book&amp;quot;][&amp;quot;properties&amp;quot;][&amp;quot;genre&amp;quot;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Science Fiction&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Fantasy&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Speculative&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Solarpunk&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Philosophy&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;There are a few different tools that you can use to apply an Overlay - I've been using &lt;a class="reference external" href="https://www.speakeasy.com/docs/speakeasy-reference/cli/getting-started"&gt;Speakeasy CLI&lt;/a&gt; a lot lately, mostly because I'm working with other OpenAPI tools and then using this tool's &lt;tt class="docutils literal"&gt;compare&lt;/tt&gt; command to get an OpenAPI diff in Overlay format that I can reapply elsewhere.&lt;/p&gt;
&lt;p&gt;Using Speakeasy CLI, the command to apply the overlay (let's say the file is &lt;tt class="docutils literal"&gt;overlay.yaml&lt;/tt&gt;) to the OpenAPI description (imaginatively, mine is called &lt;tt class="docutils literal"&gt;library.openapi.yaml&lt;/tt&gt;) is as follows:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
speakeasy&lt;span class="w"&gt; &lt;/span&gt;overlay&lt;span class="w"&gt; &lt;/span&gt;apply&lt;span class="w"&gt; &lt;/span&gt;--overlay&lt;span class="o"&gt;=&lt;/span&gt;overlay.yaml&lt;span class="w"&gt; &lt;/span&gt;--schema&lt;span class="o"&gt;=&lt;/span&gt;library.openapi.yaml&lt;span class="w"&gt; &lt;/span&gt;--out&lt;span class="w"&gt; &lt;/span&gt;updated.openapi.yaml
&lt;/pre&gt;
&lt;p&gt;The overlay updates the Book schema so that now the components section looks like this:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;omponents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;object&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;isbn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;9781250236210&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;ISBN publication identifier&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;isbn&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Book title&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;A&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Psalm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Wild-Built&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Author's name&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Becky&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Chambers&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;genre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Genre of the book&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Solarpunk&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Science Fiction&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Fantasy&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Speculative&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Solarpunk&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Philosophical&lt;/span&gt;
&lt;/pre&gt;
&lt;div class="section" id="make-your-api-implementations-your-own"&gt;
&lt;h3&gt;Make your API implementations your own&lt;/h3&gt;
&lt;p&gt;Adding repeatable OpenAPI description updates with Overlays is a good way to get from a generic or rather vague API description to something that you can implement efficiently in your own projects. Especially with published standards that might be used in many different settings, it can be particularly valuable to adapt to your own needs.&lt;/p&gt;
&lt;p&gt;In this example, we showed adding enums which is a great way to give your SDKs and validators clearer instructions on the values that should be expected. Don't neglect the other fields, such as updating descriptions or adding meaningful examples that will help those implementing the API (human or machine) to have the context they need to be successful.&lt;/p&gt;
&lt;/div&gt;
</content><category term="APIs"/><category term="openapi"/><category term="overlays"/><category term="speakeasy"/><category term="tools"/><category term="tech"/></entry><entry><title>Safe Screensharing Setup</title><link href="https://lornajane-net.pages.dev/posts/2025/safe-screensharing-setup" rel="alternate"/><published>2025-06-10T17:32:00+01:00</published><updated>2025-06-10T17:32:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-06-10:/posts/2025/safe-screensharing-setup</id><summary type="html">&lt;p class="first last"&gt;In my time I've spent a LOT of time on screenshare, and I'm confident sharing my screen. Part of the confidence comes from maintaining a usage pattern that means I CAN confidently share my screen and rarely embarrass myself. After sharing some tips with a former colleague recently, I have tried to package up the best bits of that conversation and turn it into some tips to share.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;In my time I've spent a LOT of time on screenshare, and I'm confident sharing my screen. Part of the confidence comes from maintaining a usage pattern that means I CAN confidently share my screen and rarely embarrass myself. After sharing some tips with a former colleague recently, I have tried to package up the best bits of that conversation and turn it into some tips to share.&lt;/p&gt;
&lt;p&gt;Everything about this really hinges on me having two screens, if that's not your setup, then these tips might not work brilliantly for you. Doing anything more than opening a shared document without a second screen to put the video call on is super hard work and probably needs a different approach than I'm using here.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Always share the same screen&lt;/strong&gt; My setup has my laptop on a stand on the left, my second screen on the right, and the webcam somewhere above the join between the two. I always share the laptop screen, and never share the other one, so my computer is always set up to respect that, even when I'm not screensharing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good window location habits&lt;/strong&gt; Since I know I will sometimes share my laptop screen, I never put unsafe content on that monitor. Ever. ALL chat windows are always on the not-shared screen for example, because you just don't know when a colleague is going to suddenly DM you something.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Popups are a risk&lt;/strong&gt; I keep my computer permanently in &amp;quot;do not disturb&amp;quot; mode and none of my applications are permitted to pop up toast notifications. I have mobile devices on a stand on my desk for that sort of thing!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Share the whole screen&lt;/strong&gt; because otherwise you have to keep restarting to share a different tab or application. I often open a new web browser window and put the one I'm actually using onto the &amp;quot;do not share&amp;quot; space, and just open the things I do want to share in the browser window on the laptop screen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Make the fonts bigger than you'd imagine&lt;/strong&gt; there is always someone watching on their phone, or who doesn't see as clearly as someone else. This is also another reason for sharing my laptop screen, always - it's smaller than my external monitor and sharing that just makes content unreadably small (or I have to magnify everything a LOT).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Assume that the screenshare is working&lt;/strong&gt;. To avoid the awkward &amp;quot;can everyone see my screen? [pregnant pause]&amp;quot; situation, take an audio description approach for what's on screen. &amp;quot;I'm sharing this week's meeting agenda so we can look at it together ....&amp;quot; someone will absolutely tell you if they don't see it! Speaking the visual like this can be more inclusive either to those who don't see well or to those who are looking at another window :) Sometimes it's a useful cue that just listening is missing some of the content.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consider your workspaces setup&lt;/strong&gt; I use workspaces on my linux machine, essentially four different desktops that I use for different purposes. Most of the time, the desktops span monitors, and I'll have web browser and notes apps on one, development work on another, and so on. If I know I'll be sharing applications from multiple spaces then I sometimes set the second monitor to not change with the workspace changing. The advantage of this is that I can put the video call with other people, the speaker notes, or something else in a location where I can see it throughout the session. I also don't need to learn/remember how to switch between things to demo - because my muscle memory will Just Work (TM) since this is my usual setup.&lt;/p&gt;
&lt;p&gt;Your mileage will absolutely vary - and I would love to hear your tips! I so often &amp;quot;lead&amp;quot; a meeting, teach or demo something, or at one time of my life stream on Twitch, that having this setup permanent works well for me and means I don't spend any mental energy on how to present a thing. I just turn on the share, and focus on what I'm actually trying to do. Hopefully there's something there that will be useful in your own setup - let me know?&lt;/p&gt;
</content><category term="tech"/></entry><entry><title>Publish to GitHub pages with Sphinx</title><link href="https://lornajane-net.pages.dev/posts/2025/publish-to-github-pages-with-sphinx" rel="alternate"/><published>2025-06-05T09:59:00+01:00</published><updated>2025-06-05T09:59:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-06-05:/posts/2025/publish-to-github-pages-with-sphinx</id><summary type="html">&lt;p class="first last"&gt;I'm a big fan of &lt;a class="reference external" href="https://opensource.com/article/22/10/docs-as-code"&gt;docs-as-code&lt;/a&gt; for more or less any type of content publishing, but I'm less of a fan of Jekyll, the default tool used in GitHub pages. I also prefer ReStructuredText over Markdown as a markup format, so Sphinx is definitely on my shortlist of SSGs (Static Site Generators) for my projects. I recently switched the &lt;a class="reference external" href="https://rst2pdf.org"&gt;rst2pdf docs&lt;/a&gt; to using Sphinx hosted on GitHub pages, so here are my setup notes.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I'm a big fan of &lt;a class="reference external" href="https://opensource.com/article/22/10/docs-as-code"&gt;docs-as-code&lt;/a&gt; for more or less any type of content publishing, but I'm less of a fan of Jekyll, the default tool used in GitHub pages. I also prefer ReStructuredText over Markdown as a markup format, so Sphinx is definitely on my shortlist of SSGs (Static Site Generators) for my projects. I recently switched the &lt;a class="reference external" href="https://rst2pdf.org"&gt;rst2pdf docs&lt;/a&gt; to using Sphinx hosted on GitHub pages, so here are my setup notes.&lt;/p&gt;
&lt;div class="section" id="basic-setup"&gt;
&lt;h3&gt;Basic setup&lt;/h3&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;This project uses Sphinx with a few additions:&lt;/div&gt;
&lt;div class="line"&gt;- &lt;a class="reference external" href="https://github.com/pradyunsg/furo"&gt;Furo theme&lt;/a&gt; - a more modern-looking theme than the default that you'll recognise from older websites!&lt;/div&gt;
&lt;div class="line"&gt;- &lt;a class="reference external" href="https://sphinx-external-toc.readthedocs.io/en/latest/intro.html"&gt;External TOC plugin&lt;/a&gt;, to make the navigation sane (The &lt;tt class="docutils literal"&gt;toctree&lt;/tt&gt; is confusing for me, never mind all the fly-by docs contributors this project wants to be ready for)&lt;/div&gt;
&lt;div class="line"&gt;- &lt;a class="reference external" href="https://sphinx-copybutton.readthedocs.io/en/latest/index.html"&gt;Copy button plugin&lt;/a&gt;, for easy use of code samples&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote&gt;
I also published a list of &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2022/sphinx-extensions-for-developer-portals"&gt;Sphinx extensions for developer portals&lt;/a&gt; which might be interesting if you're setting up your own Sphinx docs project.&lt;/blockquote&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Using the basic Furo skeleton as a setup, I moved our existing content (mostly converted with &lt;a class="reference external" href="https://pandoc.org/"&gt;pandoc&lt;/a&gt; into the new structure.&lt;/div&gt;
&lt;div class="line"&gt;Content migration is a whole other blog post so I'll try to avoid that tangent for now.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="build-process"&gt;
&lt;h3&gt;Build process&lt;/h3&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Sphinx uses &lt;tt class="docutils literal"&gt;make&lt;/tt&gt; to build, which is great as it's very widely known, used and supported.&lt;/div&gt;
&lt;div class="line"&gt;I got everything working locally so that I was sure the content worked and the Sphinx build was all correct and happy.&lt;/div&gt;
&lt;div class="line"&gt;One thing that I'd recommend is putting all the input content in one folder (not the root - in my case &lt;tt class="docutils literal"&gt;source/&lt;/tt&gt;) and all the output content in another (I used &lt;tt class="docutils literal"&gt;build/&lt;/tt&gt;).&lt;/div&gt;
&lt;div class="line"&gt;The site gets built by a GitHub action, and the new HTML content goes into the &lt;tt class="docutils literal"&gt;build/&lt;/tt&gt; folder, then GitHub pages serves the static content from that folder.&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;The command to build the site is &lt;tt class="docutils literal"&gt;make html&lt;/tt&gt; - running locally is a good way to work through any errors and fix them.&lt;/div&gt;
&lt;div class="line"&gt;Sometimes, Sphinx warns about things that aren't important (in this context), so look for the &amp;quot;Build succeeded&amp;quot; message at the end of the output to know if the site did actually build or not.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="github-workflow"&gt;
&lt;h3&gt;GitHub workflow&lt;/h3&gt;
&lt;p&gt;Next, I added the GitHub action to build the site (here's the &lt;a class="reference external" href="https://github.com/rst2pdf/rst2pdf.github.io/blob/main/.github/workflows/static.yml"&gt;link to the full workflow&lt;/a&gt;, I'm not pasting the whole thing).&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;The action needs to have write permission, because it's going to build the HTML and commit it to the branch.&lt;/div&gt;
&lt;div class="line"&gt;Note: this means that if you push a branch, your local branch will be out of date very soon and you should pull down again before you do more work on this branch.&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;After building the site, you'll also need to deploy it to GitHub pages.&lt;/div&gt;
&lt;div class="line"&gt;There are a lot of ready-made actions to tie together to make this work, and the &lt;a class="reference external" href="https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site"&gt;GitHub Pages docs&lt;/a&gt; are pretty good (just avoid their wizard, it's a quick start with Jekyll).&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Overview of how my project workflow works:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;a class="reference external" href="https://github.com/marketplace/actions/checkout"&gt;actions/checkout&amp;#64;v4&lt;/a&gt;&lt;/p&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;a class="reference external" href="https://github.com/marketplace/actions/setup-python"&gt;actions/setup-python&amp;#64;v5&lt;/a&gt;
Sets up the Python environment.&lt;/p&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;div class="first line-block"&gt;
&lt;div class="line"&gt;&lt;a class="reference external" href="https://github.com/marketplace/actions/setup-uv"&gt;astral-sh/setup-uv&amp;#64;v3&lt;/a&gt;&lt;/div&gt;
&lt;div class="line"&gt;Sets up the &lt;tt class="docutils literal"&gt;`uv&lt;/tt&gt; &amp;lt;&lt;a class="reference external" href="https://github.com/astral-sh/uv"&gt;https://github.com/astral-sh/uv&lt;/a&gt;&amp;gt;`__ package manager that we use in this project.&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;a step that installs the dependencies and actually builds the site.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;div class="first line-block"&gt;
&lt;div class="line"&gt;&lt;a class="reference external" href="https://github.com/marketplace/actions/configure-github-pages"&gt;actions/configure-pages&amp;#64;v5&lt;/a&gt;&lt;/div&gt;
&lt;div class="line"&gt;Gets GitHub Pages ready.&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;div class="first line-block"&gt;
&lt;div class="line"&gt;&lt;a class="reference external" href="https://github.com/marketplace/actions/upload-pages-artifact"&gt;actions/upload-pages-artifact&amp;#64;v3&lt;/a&gt;&lt;/div&gt;
&lt;div class="line"&gt;Uploads the content that was built in an earlier step as an artifact.&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;div class="first line-block"&gt;
&lt;div class="line"&gt;&lt;a class="reference external" href="https://github.com/marketplace/actions/deploy-github-pages"&gt;actions/deploy-pages&amp;#64;v4&lt;/a&gt;&lt;/div&gt;
&lt;div class="line"&gt;Actually deploys!&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="repository-settings"&gt;
&lt;h3&gt;Repository settings&lt;/h3&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;All the settings for GitHub pages are under &amp;quot;Pages&amp;quot; on the left hand navigation of the repository settings area.&lt;/div&gt;
&lt;div class="line"&gt;Use the &amp;quot;GitHub Actions&amp;quot; option to have everything picked up from the workflow you created (this setting is the default but if you're updating an install that used the old branches pattern, this is is how to change it).&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="static-sites-are-here-to-stay"&gt;
&lt;h3&gt;Static sites are here to stay&lt;/h3&gt;
&lt;p&gt;Well-structured and linked content with metadata serves both human and machine audiences, and static sites help us do that with maximum clarity and efficiency, and minimum hassle. GitHub has a whole set of &lt;a class="reference external" href="https://github.com/actions/starter-workflows/tree/main/pages"&gt;pages starter sets&lt;/a&gt; for you to check out if you prefer Hugo, Astro, or another SSG... what are you waiting for? I'd love to see the links to what you build :)&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="ci"/><category term="docs-as-code"/><category term="documentation"/><category term="github"/><category term="sphinx"/></entry><entry><title>Just Enough Docs</title><link href="https://lornajane-net.pages.dev/posts/2025/just-enough-docs" rel="alternate"/><published>2025-04-28T18:12:00+01:00</published><updated>2025-04-28T18:12:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-04-28:/posts/2025/just-enough-docs</id><summary type="html">&lt;p class="first last"&gt;Too many open source projects suffer from inadequate documentation, and that hurts their adoption, their communities, and puts more burden on maintainers. Many people who create open source software don't see see themselves as writers, but today I'm sharing tips for documenting open source projects without it needing to take up a huge amount of time to get something published and ready for (docs) contributions.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Too many open source projects suffer from inadequate documentation, and that hurts their adoption, their communities, and puts more burden on maintainers. Many people who create open source software don't see see themselves as writers, but today I'm sharing tips for documenting open source projects without it needing to take up a huge amount of time to get something published and ready for (docs) contributions.&lt;/p&gt;
&lt;p&gt;It seems obvious, but start with your &lt;tt class="docutils literal"&gt;README&lt;/tt&gt; file - and if you have an existing file, really look at it from the point of view of a new user. Does it still contain the content from the template repository or code generator that this projected was started from? Feel free to throw it away and start again!&lt;/p&gt;
&lt;div class="section" id="key-facts"&gt;
&lt;h3&gt;Key facts&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;What would I use this project for?&lt;/li&gt;
&lt;li&gt;What does it not do?&lt;/li&gt;
&lt;li&gt;Are there platform requirements?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you can answer these questions in the first paragraph, you're serving even the most casual visitors in the best possible and &amp;quot;hooking&amp;quot; the people who are in the right place. If you have ascii art, those counter/status badges, a video or anything else - put this paragraph first even if you want to keep those other things.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For bonus points:&lt;/strong&gt; set the topics and description for your project. These are shown at the top of the page and show up in search results when repositories are listed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="quick-start"&gt;
&lt;h3&gt;Quick start&lt;/h3&gt;
&lt;p&gt;For users who are already experienced with the tech stack and concepts covered by your tool, they might not need much help to onboard. Add a section for these impatient individuals with quick installation instructions, and a couple of realistic usage commands that will work when copied/pasted.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="installation"&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;p&gt;For normal users, take a more stepwise approach to explaining the platform requirements, any dependencies, and how to install the tool. Remember that not every user might use your tech stack, so mentioning which dependency management tool to use, and showing the command(s) for installation, can help. Far more people can use your tool than can write the code for it - but the majority of tools should be usable by any technical person. This section makes that actually true.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="usage"&gt;
&lt;h3&gt;Usage&lt;/h3&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;The &amp;quot;help&amp;quot; output of your CLI tool can be a useful thing to include, but by itself it is not documentation.&lt;/div&gt;
&lt;div class="line"&gt;You should also include one or two sentences for each of a handful of common tasks, and then code examples of how to achieve those tasks. You can probably harvest some of this content from your answered issues list. It serves two purposes: one is the usage documentation that is the expected use, but the examples you pick here also illustrate the main purposes of the tool and help users to understand if this thing meets the needs that brought them to your page.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If you have a lot of functionality or complicated usage, move the details to another page or directory and link to it. Similarly if you start a section like this and it just gets really long over time. It's an excellent problem, put everything in a one-section-per-markdown-file format in a folder called &lt;tt class="docutils literal"&gt;docs/&lt;/tt&gt; and link to it. Perhaps you'll publish a docs site one day, perhaps you won't, but the simple approaches serve your technical audience well without causing a lot of extra work.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="more-key-information"&gt;
&lt;h3&gt;More key information&lt;/h3&gt;
&lt;p&gt;If your tool has a lot of configuration, requires templates, or has a lot of integrations available - add a section for those main topics here. Again, the issues list (including closed issues) on an established project will give a good sense of what users get tripped up on and where to start. If the information is already available elsewhere then link to it. The same points about putting markdown files in a docs folder to separate topics will help you here.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="contributing-file"&gt;
&lt;h3&gt;Contributing file&lt;/h3&gt;
&lt;p&gt;If your project allows (or encourages!) contributions, say so. From the README file, link to the CONTRIBUTING file. In there, spell out whether contributions are welcome, if there are any requirements around issues, branch naming, license agreements, or anything else. You might already have this information in the README, in which case move it over.&lt;/p&gt;
&lt;p&gt;Tell users how to build/run the project from the source code in the respository, how to run the tests, what formatting rules are used - anything else that will help make a pull request closer to acceptable on the first attempt, because those small things save time and effort for both contributors and maintainers.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="call-to-action"&gt;
&lt;h3&gt;Call to action&lt;/h3&gt;
&lt;p&gt;Can users contact you? Open an issue? Join a group? Help in some way? Support the project? Tell them!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="summary-just-enough-docs"&gt;
&lt;h3&gt;Summary: Just enough docs&lt;/h3&gt;
&lt;p&gt;Start with a good README, clear getting started instructions, and some key usage points, and you won't go far wrong. Encourage users to open pull requests to update the pages in the project if you answer a question for them, and soon you'll have a documentation culture you can be proud of. Documentation is one of the most valuable and transferable skills for developers too, so it's very much worth your time and effort personally as well as for the project.&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="docs"/><category term="markdown"/><category term="open source"/><category term="template"/><category term="tips"/></entry><entry><title>Right-Size Your RESTful API: More Flexibility Without GraphQL</title><link href="https://lornajane-net.pages.dev/posts/2025/better-rest-before-graphql" rel="alternate"/><published>2025-03-26T10:31:00+00:00</published><updated>2025-03-26T10:31:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-03-26:/posts/2025/better-rest-before-graphql</id><summary type="html">&lt;p class="first"&gt;If you publish a RESTful API and there are situations when you need less (or more!) detail in the API responses from your API - then this post is for you. When I run into this with the teams I advise, the initial problem statement usually arrives more like &amp;quot;We need to move to GraphQL&amp;quot;, but this is a solution, not a problem to solve. The problem usually turns out to be either or both of:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p class="first"&gt;the API responses are simply too large for some of the clients or uses cases, users need to select just the fields they want&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;the API doesn't have enough information in the response payload for this client or use case, users need to choose to include nested data&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p class="last"&gt;GraphQL is one way to achieve these things, but this post is about the RESTful way to provide the right level of detail in an API response.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;If you publish a RESTful API and there are situations when you need less (or more!) detail in the API responses from your API - then this post is for you. When I run into this with the teams I advise, the initial problem statement usually arrives more like &amp;quot;We need to move to GraphQL&amp;quot;, but this is a solution, not a problem to solve. The problem usually turns out to be either or both of:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;the API responses are simply too large for some of the clients or uses cases, users need to select just the fields they want&lt;/li&gt;
&lt;li&gt;the API doesn't have enough information in the response payload for this client or use case, users need to choose to include nested data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GraphQL is one way to achieve these things, but this post is about the RESTful way to provide the right level of detail in an API response.&lt;/p&gt;
&lt;div class="section" id="a-little-rest-theory"&gt;
&lt;h3&gt;A little REST theory&lt;/h3&gt;
&lt;p&gt;A quick dive into the formal structure of REST will give the foundation we need to understand the options.&lt;/p&gt;
&lt;p&gt;REST stands for REpresentational State Transfer; the idea is that we're presenting a way to transfer &lt;em&gt;representations&lt;/em&gt; of resources. There are no rules anywhere that say you must or must not include particular data, or that you can only choose one representation (actually there are a whole load of rules that don't exist about REST, you don't have to expose your entire database structure ... I think this is another blog post so I'll save it for another day!).&lt;/p&gt;
&lt;p&gt;We can use this concept of representations to provide a choice of formats to an API consumer.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="representations-of-resources"&gt;
&lt;h3&gt;Representations of resources&lt;/h3&gt;
&lt;p&gt;Let's get away from the theory and look at an (imaginary) example. It's really an API design choice whether I build my potions inventory API &lt;a class="reference external" href="#potions-api"&gt;1&lt;/a&gt; to return something like this:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Luckyleaf Elixir&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Temporarily enhances good fortune&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Or if I define an interface that returns a lot more context:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Luckyleaf Elixir&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Temporarily enhances good fortune&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;ingredients&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Four-Leaf Clover&lt;/span&gt;&lt;span class="p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Whispering Willow Sap&lt;/span&gt;&lt;span class="p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Stardust&lt;/span&gt;&lt;span class="p-Indicator"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;3 hours&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;brewing_instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Steep the clover in whispering willow sap under starlight, sprinkle stardust at dawn, and recite a wish.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;side_effects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Mild Overconfidence&lt;/span&gt;&lt;span class="p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Unexpected Coincidences&lt;/span&gt;&lt;span class="p-Indicator"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;First crafted by the renowned herbalist Fortuna Greenfingers to escape a series of unfortunate events.&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Either of these representations might be useful, but in different contexts. RESTful services allow us to offer more than one representation of a resource at the same URL, and picking from a short list of options is simpler than figuring out what data structure you want and how to request that.&lt;/p&gt;
&lt;p&gt;To implement this option, either keep it simple with a &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;?format=verbose&lt;/span&gt;&lt;/tt&gt; parameter to switch between response types, or use an Accept header to allow the user to pick the media type they want (a good example of the Accept header approach is how it is used to offer patch/diff/sha/json responses in the &lt;a class="reference external" href="https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#get-a-commit"&gt;GitHub API commits endpoint&lt;/a&gt; ).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="graphql-is-not-always-the-answer"&gt;
&lt;h3&gt;GraphQL is not (always) the answer&lt;/h3&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;If you have a RESTful-ish or HTTP API that is returning too many fields, or too few, then consider if you can iterate on the solution that is already in place before replacing it with a different approach entirely. GraphQL is good for plenty of use cases, but I usually try to start by looking at using more representations in REST because:&lt;/div&gt;
&lt;div class="line"&gt;- plain HTTP APIs are more widely understood by both users and tools&lt;/div&gt;
&lt;div class="line"&gt;- offering a small list of formats is simpler to understand for users to just pick from the menu, and as a provider it is easier to evolve those formats moving forward&lt;/div&gt;
&lt;div class="line"&gt;- optimising a small number of known options, such as returning just the basics of an item, or returning it and a bunch of related data in the response, is easier to optimise and crucially for APIs running at scale it can be cached (one of the main benefits of REST!)&lt;/div&gt;
&lt;div class="line"&gt;- it's less disruptive to extend an existing API than to introduce a new technology and support two APIs&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;GraphQL is a good option where the users' use cases are truly unknown, or data exploration is a feature. But otherwise I would recommend weighing up the options before setting on a whole API replacement programme because one endpoint is too verbose, or not verbose enough. We have established design practices that can help, and I hope the example in this post gave you some ideas.&lt;/p&gt;
&lt;blockquote&gt;
[1] In the writers room for this post, meaning me and ChatGPT, I asked for some fun API example ideas, and then asked for an OpenAPI file to illustrate the concept. The result made me laugh, and if it would make you laugh too then you can find the &lt;a class="reference external" href="https://gist.github.com/lornajane/f44cce24d7d8cd936fee5076f21f5303"&gt;Potions Inventory API on GitHub&lt;/a&gt;.&lt;/blockquote&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="api"/><category term="design"/><category term="graphql"/><category term="openapi"/><category term="rest"/></entry><entry><title>Markdown/Mermaid output for OpenAPI Arazzo</title><link href="https://lornajane-net.pages.dev/posts/2025/markdown-mermaid-output-for-openapi-arazzo" rel="alternate"/><published>2025-03-12T16:01:00+00:00</published><updated>2025-03-12T16:01:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-03-12:/posts/2025/markdown-mermaid-output-for-openapi-arazzo</id><summary type="html">&lt;p&gt;API reference documentation changed the way we built integrations, and eventually became part of the driving force for OpenAPI adoption and all the good tooling that flowed from it. As a developer experience specialist, I spend a lot of time thinking about how human users can work with the technical …&lt;/p&gt;</summary><content type="html">&lt;p&gt;API reference documentation changed the way we built integrations, and eventually became part of the driving force for OpenAPI adoption and all the good tooling that flowed from it. As a developer experience specialist, I spend a lot of time thinking about how human users can work with the technical assets in a project. HTML-format API reference documentation does a great job of building that bridge when working on OpenAPI projects, but now I'm using &lt;a class="reference external" href="https://www.openapis.org/arazzo"&gt;Arazzo&lt;/a&gt; and it's a very new standard with not nearly as many tools available for that format yet - so I built one.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://codeberg.org/lornajane/apitapviz"&gt;ApiTapViz&lt;/a&gt; is a (very early stage!) Python CLI utility that helps humans to see what's in an Arazzo file without having to read it raw. It picks out the summary data of the workflows and steps in an Arazzo file, and presents them to the user in either Markdown or Mermaid (diagram) format.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2025/03/Screenshot-from-2025-03-12-15-51-02.png"&gt;&lt;img alt="Flow diagram with title &amp;quot;Blog workflows&amp;quot;. getCategories points to savePost which points to addImage" class="aligncenter size-medium wp-image-4970" src="https://lornajane-net.pages.dev/images/2025/03/Screenshot-from-2025-03-12-15-51-02-300x228.png" style="width: 300px; height: 228px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The idea is to make it simple to output a representation for users to quickly see how a flow works, or what's covered in a particular workflow. It doesn't have all the details that the Arazzo file has, but it could be useful to capture the output in internal (or external!) documentation, or as an artefact from a CI build.&lt;/p&gt;
&lt;p&gt;Please try the tool with your Arazzo files, let me know how you get on, and get involved with the project on its (CodeBerg, there's a GitHub mirror but only for discoverability) page: &lt;a class="reference external" href="https://codeberg.org/lornajane/apitapviz"&gt;https://codeberg.org/lornajane/apitapviz&lt;/a&gt;&lt;/p&gt;
</content><category term="tech"/><category term="arazzo"/><category term="cli"/><category term="open source"/><category term="openapi"/><category term="tools"/></entry><entry><title>From HTTP to OpenAPI with Optic</title><link href="https://lornajane-net.pages.dev/posts/2025/from-http-to-openapi-with-optic" rel="alternate"/><published>2025-02-17T10:07:00+00:00</published><updated>2025-02-17T10:07:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-02-17:/posts/2025/from-http-to-openapi-with-optic</id><summary type="html">&lt;p class="first last"&gt;I've been using &lt;a class="reference external" href="https://github.com/opticdev/optic"&gt;Optic's CLI&lt;/a&gt;, an OpenAPI tool that does a bunch of things including diffing OpenAPI descriptions and comparing HTTP traffic with OpenAPI. My use case was an established API that didn't have an OpenAPI file yet - using Optic we could create one as a starting point, and then move to a design-first workflow to make the changes that I was there to help with. For this blog post, I've used the example of &lt;a class="reference external" href="https://api.joind.in"&gt;https://api.joind.in&lt;/a&gt; as an excellent representation of an API still in use, but without an OpenAPI file and not built with code that a code generator would recognise.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've been using &lt;a class="reference external" href="https://github.com/opticdev/optic"&gt;Optic's CLI&lt;/a&gt;, an OpenAPI tool that does a bunch of things including diffing OpenAPI descriptions and comparing HTTP traffic with OpenAPI. My use case was an established API that didn't have an OpenAPI file yet - using Optic we could create one as a starting point, and then move to a design-first workflow to make the changes that I was there to help with. For this blog post, I've used the example of &lt;a class="reference external" href="https://api.joind.in"&gt;https://api.joind.in&lt;/a&gt; as an excellent representation of an API still in use, but without an OpenAPI file and not built with code that a code generator would recognise.&lt;/p&gt;
&lt;p&gt;The basic premise goes something like this:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Configure Optic to set where your API is, and how to run the API tests.&lt;/li&gt;
&lt;li&gt;Configure your API tests (or other API traffic source - I used a collection of curl requests which was enough to begin!) to test against the URL in an environment variable.&lt;/li&gt;
&lt;li&gt;Run Optic in capture-and-update mode, pointing to an empty/template OpenAPI file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At the end of the process, you'll have an OpenAPI file with the operations, parameters and data structures that Optic observed from the API traffic. What you won't have is anything fit for users to use, because there are no examples, descriptions, or any other user-helping content. What you will have is a structure to start adding those things into (and I have some suggestions when we get to that point).&lt;/p&gt;
&lt;div class="section" id="configure-optic"&gt;
&lt;h3&gt;Configure Optic&lt;/h3&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Give Optic the location of your live or sandbox API, and the command to run the test suite for your API.&lt;/div&gt;
&lt;div class="line"&gt;Here's how my configuration looks:&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;openapi.yaml&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Run with &amp;quot;optic capture openapi.yaml --update interactive&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;https://api.joind.in/v2.1/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;./test.sh&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;&amp;quot;API Tests&amp;quot; is a pretty generous term for what I used! Ideally you'd run your end-to-end or integration tests - but this API doesn't have any of that yet. So I hacked together a series of &lt;tt class="docutils literal"&gt;curl&lt;/tt&gt; commands to run from a shell script; the main aim is to put some API traffic through the Optic proxy so it can see what's going on. You can definitely do better for your own APIs, but I also secretly love that this was enough to get things moving on this project so I'm sharing it :)&lt;/p&gt;
&lt;pre class="code text literal-block"&gt;
#!/bin/bash

curl -s -o /dev/null $OPTIC_PROXY/users
curl -s -o /dev/null $OPTIC_PROXY/users/18888888881
curl -s -o /dev/null $OPTIC_PROXY/users/11

curl -s -o /dev/null $OPTIC_PROXY/events
curl -s -o /dev/null $OPTIC_PROXY/events?filter=hot
curl -s -o /dev/null $OPTIC_PROXY/events?start=10&amp;amp;resultsperpage=20
curl -s -o /dev/null $OPTIC_PROXY/events/8377
curl -s -o /dev/null $OPTIC_PROXY/events/8387/tracks
&lt;/pre&gt;
&lt;p&gt;The main thing to note is that rather than hitting the actual API endpoints, I'm using an environment variable &lt;tt class="docutils literal"&gt;$OPTIC_PROXY&lt;/tt&gt;. In fact, the API requests all go to Optic, and Optic passes them on to the actual API, checking the request and response data along the way, then returns the response to us.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="proxy-and-capture"&gt;
&lt;h3&gt;Proxy and Capture&lt;/h3&gt;
&lt;p&gt;Let's go! Run Optic, and give it the name of a file to update with its findings:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
optic capture openapi.yaml --update interactive
&lt;/pre&gt;
&lt;p&gt;Tada! You have an OpenAPI file! You can't really do anything with it at this point, but you have a great structure in place.&lt;/p&gt;
&lt;p&gt;Crucially, you can re-run and re-update the file at any time, which is a great way to catch API drift situations where code changes have been made and not propogated through to the OpenAPI file. In an ideal world, changes would be design-first, and then implemented promptly. I don't live in the ideal world though, so tools like Optic help a lot.&lt;/p&gt;
&lt;div class="section" id="now-draw-the-rest-of-the-owl-i-mean-openapi"&gt;
&lt;h4&gt;Now draw the rest of the Owl, I mean OpenAPI&lt;/h4&gt;
&lt;p&gt;The basic structure of an API description would be incredibly tedious to do by hand, so we've saved ourselves a lot of work. But OpenAPI files should be much more than what you can learn from sniffing traffic. They are the API experience you offer to producers and consumers who have to work with your API, so there's more work to do.&lt;/p&gt;
&lt;p&gt;It doesn't all have to be done in one go, but take the time to tick off as many of the following as you can:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Fill in the fields in the info section such as contact and license, and definitely title. This stuff is easy to gloss over but as we move more and more to API discovery by machines or through catalogs, the metadata is more important than ever.&lt;/li&gt;
&lt;li&gt;For each Operation, add a summary, a description, and as many response examples as you can think of. Include error responses, these are vitally important for tools such as SDK generators to know how to handle the various structures that might be returned by your API.&lt;/li&gt;
&lt;li&gt;For each schema and parameter, add a description, any additional format information you can, and an example. These really help users to know how to use the fields available to them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It might seem like a lot of work! You can consider hiring specialist API documentation writers to help gather together the information you need and work through building out the detail of the OpenAPI description if you don't have the expertise or bandwidth in house. Tech writers understand users and APIs, and can help keep everyone aligned.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="an-llm-helping-hand"&gt;
&lt;h3&gt;An LLM Helping Hand&lt;/h3&gt;
&lt;p&gt;One thing to note here if you're trying to construct an OpenAPI file is that since everyone publishes their OpenAPI files and they're very formulaic, an LLM can help to speed up the process. Please approach this part of the post with caution: these tools don't &amp;quot;know&amp;quot; anything and will easily mislead you if you don't know either the API or OpenAPI itself very well. The tools are also non-deterministic, so what works one day might do something else completely next time. However I do use LLMs with OpenAPI files when the OpenAPI file is under source control and changes are made one at a time, under my supervision, and reviewed thoroughly by experts.&lt;/p&gt;
&lt;p&gt;How did I use an LLM in my own project? Actually the Joind.in API had some hand-written markdown documentation with some good explanations of the various endpoints and parameters. Using the OpenAPI from Optic, and a generated PDF of the API documentation, and telling it not to use any other inputs at all, I did get a reasonable first attempt at an OpenAPI document out of the end of the process! As with all these projects, there's a lot of human work needed (tip: tell your LLM &amp;quot;now suggest and make improvements&amp;quot; a few times, why this works I am unclear but it does! See also my earlier remarks about having it in source control and using small steps, I find whole sections went missing sometimes) before you'd want to give the result to a user, but the machines have done the boring/repetitive parts for us, and now the humans bring their critical thinking skills as expert reviewers.&lt;/p&gt;
&lt;p&gt;As more and more organisations embark on API modernisation projects, keeping their tried-and-tested APIs online and updating the processes around them, I expect to see more tools like this in use. If you do a project like this yourself, I'd love to hear how it went and what you'd add to the advice I shared here.&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="ai"/><category term="api"/><category term="joindin"/><category term="llm"/><category term="openapi"/><category term="optic"/><category term="tools"/></entry><entry><title>Preview OpenAPI as HTML using Scalar</title><link href="https://lornajane-net.pages.dev/posts/2025/preview-openapi-as-html-using-scalar" rel="alternate"/><published>2025-02-14T14:49:00+00:00</published><updated>2025-02-14T14:49:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-02-14:/posts/2025/preview-openapi-as-html-using-scalar</id><summary type="html">&lt;p class="first last"&gt;The API DevTools space is alive and well with lots of new and exciting products popping up all the time. I've been especially impressed by the &lt;a class="reference external" href="https://apievangelist.com/2025/01/28/we-are-entering-the-3rd-api-documentation-renaissance/"&gt;new tools in the docs space&lt;/a&gt;, but some of the options are less practical for use as quick human-friendly OpenAPI renderings or previews to use during API development. My current favourite in this space is &lt;a class="reference external" href="https://scalar.com/"&gt;Scalar&lt;/a&gt; because I can get an HTML file to easily include in a build or share with others. It's a bit of a hack though, so I thought I'd share...&lt;/p&gt;
</summary><content type="html">&lt;p&gt;The API DevTools space is alive and well with lots of new and exciting products popping up all the time. I've been especially impressed by the &lt;a class="reference external" href="https://apievangelist.com/2025/01/28/we-are-entering-the-3rd-api-documentation-renaissance/"&gt;new tools in the docs space&lt;/a&gt;, but some of the options are less practical for use as quick human-friendly OpenAPI renderings or previews to use during API development. My current favourite in this space is &lt;a class="reference external" href="https://scalar.com/"&gt;Scalar&lt;/a&gt; because I can get an HTML file to easily include in a build or share with others. It's a bit of a hack though, so I thought I'd share...&lt;/p&gt;
&lt;p&gt;What I actually want here is a command that accepts an OpenAPI as an input and outputs a single HTML file as an output. Many of the modern docs tools think they need to start a local server and perhaps that's useful for some use cases but it's not a good developer experience for rendering a preview alongside some API design changes, for example. Other tools expect you to publish your OpenAPI file somewhere publicly, which is always a slow feedback loop and is sometimes not appropriate.&lt;/p&gt;
&lt;p&gt;I found that Scalar &lt;a class="reference external" href="https://github.com/scalar/scalar/blob/main/documentation/integrations/html.md#yaml"&gt;supports adding OpenAPI into the HTML source&lt;/a&gt; and so I made a shell script that does the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;paste everything before where the HTML goes&lt;/li&gt;
&lt;li&gt;paste the OpenAPI file&lt;/li&gt;
&lt;li&gt;paste the rest of the HTML content&lt;/li&gt;
&lt;li&gt;save the whole lot as a file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's the script:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/bash
&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;template_before.html&lt;span class="w"&gt; &lt;/span&gt;openapi.yaml&lt;span class="w"&gt; &lt;/span&gt;template_after.html&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;output.html
&lt;/pre&gt;
&lt;p&gt;Have fun with your instant-but-hacky HTML preview of OpenAPI files, and thanks Scalar for making it easy!&lt;/p&gt;
&lt;p&gt;You can add in configuration (the solarized theme looks SO GOOD in dark mode!) to suit your needs and preferences, and since we're just using &lt;tt class="docutils literal"&gt;cat&lt;/tt&gt; to put the files together, it's FAST.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2025/02/Screenshot-2025-01-14-at-20-55-29-Scalar-API-Reference.png"&gt;&lt;img alt="screenshot of scalar-rendered API documentation using solarized dark mode" class="aligncenter size-large wp-image-4922" src="https://lornajane-net.pages.dev/images/2025/02/Screenshot-2025-01-14-at-20-55-29-Scalar-API-Reference-1024x540.png" style="width: 584px; height: 308px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Enjoy your OpenAPI-to-HTML build process, and thank you to Scalar for making a great tool.&lt;/p&gt;
</content><category term="tech"/><category term="docs"/><category term="openapi"/></entry><entry><title>Presenting with pdfpc</title><link href="https://lornajane-net.pages.dev/posts/2025/presenting-with-pdfpc" rel="alternate"/><published>2025-02-11T12:08:00+00:00</published><updated>2025-02-11T12:08:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-02-11:/posts/2025/presenting-with-pdfpc</id><summary type="html">&lt;p class="first last"&gt;My slide deck tool ( &lt;a class="reference external" href="https://rst2pdf.org"&gt;rst2pdf&lt;/a&gt; ) produces PDFs, and I use &lt;a class="reference external" href="https://pdfpc.github.io/"&gt;pdfpc&lt;/a&gt; to present the PDF slides. It shows the current and next slides, my notes, a timer, and it probably does other things too that I don't use! I've used it for years but it was really designed for &amp;quot;in real world&amp;quot; presenting with one or two screens. Recently I discovered it also supports some great options for remote presentations, but I had to look up how to do that so here are my notes (mostly in case I need to look it up 1 minute before I go on stage again!!).&lt;/p&gt;
</summary><content type="html">&lt;p&gt;My slide deck tool ( &lt;a class="reference external" href="https://rst2pdf.org"&gt;rst2pdf&lt;/a&gt; ) produces PDFs, and I use &lt;a class="reference external" href="https://pdfpc.github.io/"&gt;pdfpc&lt;/a&gt; to present the PDF slides. It shows the current and next slides, my notes, a timer, and it probably does other things too that I don't use! I've used it for years but it was really designed for &amp;quot;in real world&amp;quot; presenting with one or two screens. Recently I discovered it also supports some great options for remote presentations, but I had to look up how to do that so here are my notes (mostly in case I need to look it up 1 minute before I go on stage again!!).&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;The default modes:&lt;/div&gt;
&lt;div class="line"&gt;f&lt;/div&gt;
&lt;div class="line"&gt;* with only your laptop screen, pdfpc gives a full-screen presentation mode.&lt;/div&gt;
&lt;div class="line"&gt;* with two screens, it assumes you and a projector, with the slides presented on the second screen and the speaker view on the laptop. This is ideal for being at a conference with a computer connected to the projector.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To present with a projector but with the screens mirrored (so you can demo):&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;use &lt;tt class="docutils literal"&gt;pdfpc &lt;span class="pre"&gt;-s&lt;/span&gt; &lt;span class="pre"&gt;-S&lt;/span&gt;&lt;/tt&gt; in combination - you can't get the speaker view (because the audience would see it too), but now we can correctly display the presentation only.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There's a new (or I didn't notice it before) windowed mode which is great for streaming, or those fun situations where you are in a room but you connect to a digital meeting room for broadcast instead of to a monitor!&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;by default, only the presenter mode is windowed&lt;/li&gt;
&lt;li&gt;use &lt;tt class="docutils literal"&gt;pdfpc &lt;span class="pre"&gt;-w&lt;/span&gt; both&lt;/tt&gt; to put both &lt;tt class="docutils literal"&gt;presentation&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;presenter&lt;/tt&gt; options into windowed modes - this way you can share the presentation window with your meeting and then move the presenter view around so you can see and present from that.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a final note, please consider installing a PDF presenter tool (either this one or something else). Every slide deck program can export slides with embedded fonts and speaker notes to PDF, and if someone has a laptop with PDF presentation tools on it then the speaker can still go ahead and do their thing.&lt;/p&gt;
</content><category term="tech"/><category term="pdfp"/><category term="pdfpc"/><category term="speaking"/></entry><entry><title>Querying the GitHub GraphQL API</title><link href="https://lornajane-net.pages.dev/posts/2025/querying-the-github-graphql-api" rel="alternate"/><published>2025-01-27T12:48:00+00:00</published><updated>2025-01-27T12:48:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-01-27:/posts/2025/querying-the-github-graphql-api</id><summary type="html">&lt;p class="first last"&gt;In a recent project around open source contributors, I wanted to take a look at which projects a particular user (actually a few of them, but I wrote a wrapper to repeat the process for each handle) maintains. GitHub doesn't show this maintainer relationship, so instead I used the v4 GraphQL API and looked at pull request comments on repositories that the user has access to. I'm sharing my query and the Python script I used to make the API calls to.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;In a recent project around open source contributors, I wanted to take a look at which projects a particular user (actually a few of them, but I wrote a wrapper to repeat the process for each handle) maintains. GitHub doesn't show this maintainer relationship, so instead I used the v4 GraphQL API and looked at pull request comments on repositories that the user has access to. I'm sharing my query and the Python script I used to make the API calls to.&lt;/p&gt;
&lt;blockquote&gt;
For the very impatient, &lt;a class="reference external" href="https://gist.github.com/lornajane/a1dfc26d0520646faa6506709992f129"&gt;here's a gist with the full script&lt;/a&gt;.&lt;/blockquote&gt;
&lt;div class="section" id="graphql-query"&gt;
&lt;h3&gt;GraphQL Query&lt;/h3&gt;
&lt;p&gt;If you're new to GraphQL, the basic idea is that you can fetch data objects, and then use relationships to fetch data related to it. The &lt;a class="reference external" href="https://docs.github.com/en/graphql/guides/introduction-to-graphql"&gt;Introduction to GraphQL&lt;/a&gt; from GitHub is a good place to look if you want to learn more.&lt;/p&gt;
&lt;p&gt;In my example, I started by fetching a &lt;a class="reference external" href="https://docs.github.com/en/graphql/reference/objects#user"&gt;user&lt;/a&gt; by their login (their username or handle). Then I used the handy &lt;a class="reference external" href="https://docs.github.com/en/graphql/reference/objects#contributionscollection"&gt;ContributionsCollection&lt;/a&gt; field to get information about the user's activity - this is one of my favourite data features of the GitHub v4 GraphQL API and I have used it to build a few different dashboards because it has some cool data in it that would otherwise be laborious to extract from GitHub (although I do really like their v3 REST API too!).&lt;/p&gt;
&lt;p&gt;From ContributionsCollection, I can pick the [PullRequestReviewContributions]( as that's all I'm interested in here: I'm looking for review comment activity on repositories where the user has push access. Note that there's no way to tell if the user has previously had push access - for example my own account doesn't show any of the Redocly repositories despite that being a big part of my open source activity in recent years, because I don't have maintainer rights now that I don't work there any more.&lt;/p&gt;
&lt;p&gt;Finally I also collect information about the repository that the pull request is in, because my end goal is to collect a list of projects.&lt;/p&gt;
&lt;p&gt;Here's the query:&lt;/p&gt;
&lt;pre class="code text literal-block"&gt;
query ($login: String!) {
user(login: $login) {
login
contributionsCollection {
pullRequestReviewContributions(last: 100) {
nodes {
pullRequestReview {
authorCanPushToRepository
}
pullRequest {
repository {
name
owner {
login
}
}
}
}
}
}
}
}
&lt;/pre&gt;
&lt;p&gt;This query uses a &lt;tt class="docutils literal"&gt;$login&lt;/tt&gt; variable, which is supplied separately when we run the query.&lt;/p&gt;
&lt;p&gt;There's a handy explorer tool, but I found it much easier to quickly write a Python wrapper script so I could edit the query and re-run the script using my local development tools. YMMV, but the graphical web-based interfaces don't play that well with my accessibility tools so it was quite literally a painful process for me personally!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="python-script"&gt;
&lt;h3&gt;Python Script&lt;/h3&gt;
&lt;p&gt;Why Python? Why not? I don't write Python every day but I wish I did and it's a decent choice for something like this to fetch some data and manipulate it.&lt;/p&gt;
&lt;p&gt;I'm using the script as a CLI entry point, including passing in a variable, so the first thing is to grab the input, or prompt the user if there isn't any. That section looks like this:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Usage: python3 github-api.py name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# variables for query&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;login&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;I don't need to pass in any other variables, so the &lt;tt class="docutils literal"&gt;variables&lt;/tt&gt; is ready to pass into the API call when we make it. I also define a &lt;tt class="docutils literal"&gt;query&lt;/tt&gt; variable that contains the query shown in the first section of the blog post (so I'm not pasting it again here!).&lt;/p&gt;
&lt;p&gt;I've got my GitHub access token in an environment variable named &lt;tt class="docutils literal"&gt;GH_KEY&lt;/tt&gt;, mostly so that I don't accidentally paste when I make the gist!! Using an environment variable is good practice, each user maintains their own and there's no risk of checking something into the repository that shouldn't be there. Remember if you use a tool like dotenv to add those files to your &lt;tt class="docutils literal"&gt;.gitignore&lt;/tt&gt; file to avoid mishaps.&lt;/p&gt;
&lt;p&gt;Calling the API itself is a bit of an anticlimax, here's the Python code:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://api.github.com/graphql'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'Authorization'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Bearer '&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'GH_KEY'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'query'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'variables'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Here I'm setting the URL in its own variable: this is super useful in case you ever want to send the request somewhere else, through a debugging or testing tool or something. I'm setting the header to authenticate myself - this step isn't really needed here as I'm only accessing publicly-available data, but if you would need to log in to see something then you need to authenticate. It also gives higher rate limits, which didn't matter for this project but might be useful for you to know.&lt;/p&gt;
&lt;p&gt;Finally, the rest of the script checks the data, picks out the projects the user has been working on, puts them in a list, and finally just prints some output.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="c1"&gt;# be careful, errors are response status 200&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;'errors'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Errors:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'errors'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="s1"&gt;'data'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s1"&gt;'user'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'login'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pr_review&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'contributionsCollection'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'pullRequestReviewContributions'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'nodes'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="n"&gt;pr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pr_review&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'pullRequest'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'repository'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'repository'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'owner'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'login'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;  &lt;span class="n"&gt;can_push&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pr_review&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'pullRequestReview'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'authorCanPushToRepository'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="n"&gt;project_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;can_push&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_string&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;  &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error: User data not found in the response.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error: Request failed with status code &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The main &amp;quot;gotcha&amp;quot; is that the request can be successful with status 200 but if you did something wrong in your query, there can still be errors in the response body. I'm sure there are other error cases that this script doesn't handle but the only one I'm aware of is that it got VERY confused when someone had deleted their account that I was trying to fetch data about! I also didn't implement pagination because I didn't need it, but that might be a good consideration if you're doing something with a GraphQL API.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="rest-vs-graphql"&gt;
&lt;h3&gt;REST vs GraphQL&lt;/h3&gt;
&lt;p&gt;The naming of the REST API as v3 and the GraphQL API as v4 is a bit misleading in my opinion since both have their own strengths and I can't imagine GitHub deprecating their API when it's so widely used! However it is always worth checking what's available in each one, as some things are only in one API or the other; discussions are GraphQL only (or they were last time I implemented something that needed them), so take care.&lt;/p&gt;
&lt;p&gt;Good luck with your GraphQL projects, drop me a comment and let me know what you build!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="api"/><category term="github"/><category term="graphql"/><category term="python"/><category term="rest"/></entry><entry><title>Save edits to OpenAPI as an Overlay</title><link href="https://lornajane-net.pages.dev/posts/2025/save-edits-to-openapi-as-an-overlay" rel="alternate"/><published>2025-01-09T12:18:00+00:00</published><updated>2025-01-09T12:18:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-01-09:/posts/2025/save-edits-to-openapi-as-an-overlay</id><summary type="html">&lt;p class="first last"&gt;For teams that generate OpenAPI from their codebase, there's a tough choice between maintaining rich and extensive content such as Markdown descriptions and examples in codebase annotations, or in making those changes to the generated file - and then losing them when the code changes and the file is regenerated. The new OpenAPI Overlay Specification defines a format for storing updates to an OpenAPI document, and there's a new generation of tools to make it easy to do, so let's take a look.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;For teams that generate OpenAPI from their codebase, there's a tough choice between maintaining rich and extensive content such as Markdown descriptions and examples in codebase annotations, or in making those changes to the generated file - and then losing them when the code changes and the file is regenerated. The new OpenAPI Overlay Specification defines a format for storing updates to an OpenAPI document, and there's a new generation of tools to make it easy to do, so let's take a look.&lt;/p&gt;
&lt;div class="section" id="about-overlays"&gt;
&lt;h3&gt;About Overlays&lt;/h3&gt;
&lt;p&gt;An Overlay is a set of amendments to be made to an OpenAPI description. It is an ordered series of steps, where each step can add, update or remove elements from the OpenAPI description.&lt;/p&gt;
&lt;p&gt;An Overlay is applied to an OpenAPI description - and the output is an (improved) OpenAPI description. OpenAPI is always the interchange format between the tools that you use in your API description pipeline, and so you can add an Overlay, or many of them, at any point in the process.&lt;/p&gt;
&lt;p&gt;Common examples might be to add descriptions to a generated OpenAPI file that doesn't have much user-friendly content in it but describes the API and data structures, to remove private endpoints before publishing documentation, or to add extra fields for your SDK generator or API gateway tools.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="edit-an-openapi-and-save-the-edits-to-re-use"&gt;
&lt;h3&gt;Edit an OpenAPI and save the edits to re-use&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; edit the OpenAPI file! Make it awesome, add links to descriptions and fill in multiple response examples without using either &amp;quot;foo&amp;quot; or &amp;quot;bar&amp;quot;. Generally, go wild :) If you would like something to test with, I'm using the &lt;a class="reference external" href="https://github.com/Redocly/museum-openapi-example/"&gt;Museum API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; use the &lt;a class="reference external" href="https://www.speakeasy.com/docs/speakeasy-reference/cli/getting-started"&gt;Speakeasy CLI&lt;/a&gt; to generate the overlay. The command looks something like this:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
speakeasy&lt;span class="w"&gt; &lt;/span&gt;overlay&lt;span class="w"&gt; &lt;/span&gt;compare&lt;span class="w"&gt; &lt;/span&gt;--before&lt;span class="w"&gt; &lt;/span&gt;museum.yaml&lt;span class="w"&gt; &lt;/span&gt;--after&lt;span class="w"&gt; &lt;/span&gt;museum-updated.yaml&lt;span class="w"&gt; &lt;/span&gt;--out&lt;span class="w"&gt; &lt;/span&gt;museum-overlay.yaml
&lt;/pre&gt;
&lt;p&gt;This automated way to create overlays is super convenient, because it means you can use any OpenAPI tool to make changes, save the new OpenAPI, and then get an overlay. Try editing with a live OpenAPI docs preview in your IDE or running locally if you are working on documentation and want to make quick changes and save them!&lt;/p&gt;
&lt;p&gt;In my example, I changed the server URL to be a localhost entry because I'm running some local tests against an API, here's the overlay that was generated:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;overlay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;1.0.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Overlay museum.yaml =&amp;gt; museum-updated.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;0.0.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;$[&amp;quot;servers&amp;quot;][0][&amp;quot;url&amp;quot;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;http://localhost:8081&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Once you make a few edits, you'll get a longer file, but the point I am trying to illustrate is that it pretty tightly mirrors your OpenAPI file. You can edit this overlay file as you wish, or apply the edits, make more edits, and generate a new Overlay - it's absolutely safe to work with and is no more mysterious than the OpenAPI format.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Add the new overlay file into your repository alongside the OpenAPI file, and add a step to your CI to apply it before the docs are generated (or otherwise as appropriate). I'd recommend running your API linting after applying the changes, to make sure everything is still as expected.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="tips-for-overlays"&gt;
&lt;h3&gt;Tips for Overlays&lt;/h3&gt;
&lt;p&gt;Overlays make sense in multiple places in the API workflow, and I usually characterise them into two rough groups:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;em&gt;input overlays&lt;/em&gt; make your OpenAPI input file ready to be used by the tools in your pipeline. These are steps that add missing error entries, add missing descriptions and licenses, and otherwise patch things up to the standard you want them.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;output overlays&lt;/em&gt; prepare an OpenAPI file for a specific destination or tool. For example adding some metadata for your SDK generator to know how to group operations and name the functionality. Or adding code samples or formatting information like &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;x-logo&lt;/span&gt;&lt;/tt&gt; before generating documentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At each stage you can have one Overlay or many, and apply them as you wish. The same Overlay might be used for multiple API descriptions if it's generic enough (such as adding a license or contact information, or setting some tags that are common for all your APIs).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; between writing this post and publishing it, Speakeasy made a very nice Overlay editor tool!! Go to &lt;a class="reference external" href="https://overlay.speakeasy.com"&gt;https://overlay.speakeasy.com&lt;/a&gt; and try pasting your OpenAPI in the left hand pane. Remove everything in the right hand pane (to reset the overlay) and then start editing in the middle to get the Overlay generated for your edits as you go along. It uses the same tools as the &lt;tt class="docutils literal"&gt;compare&lt;/tt&gt; command in this post, so feel free to mix and match these tools while you get familiar with Overlays!&lt;/p&gt;
&lt;p&gt;In this post, you saw how you can improve an OpenAPI and then keep those improvements when the API changes and the file gets updated. This approach can help you to get the best out of all the OpenAPI tools you use, and I hope it's helpful! Please leave a comment and let me know how you use Overlays in your own workflow.&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="api"/><category term="openapi"/><category term="overlays"/><category term="speakeasy"/><category term="tips"/><category term="tools"/></entry><entry><title>Run GitHub Actions on Subdirectories</title><link href="https://lornajane-net.pages.dev/posts/2025/run-github-actions-on-subdirectories" rel="alternate"/><published>2025-01-07T13:39:00+00:00</published><updated>2025-01-07T13:39:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2025-01-07:/posts/2025/run-github-actions-on-subdirectories</id><summary type="html">&lt;p class="first last"&gt;I come across a lot of &amp;quot;greedy&amp;quot; GitHub Actions, where automation is running across a whole project instead of only on the parts that are relevant. Examples might be code linters that report problems with documentation folders, or the inverse of that. It's especially problematic in monorepos where we probably want to use the same tool when we're doing the same task for different subfolders, but that tool might not make sense to run everywhere.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I come across a lot of &amp;quot;greedy&amp;quot; GitHub Actions, where automation is running across a whole project instead of only on the parts that are relevant. Examples might be code linters that report problems with documentation folders, or the inverse of that. It's especially problematic in monorepos where we probably want to use the same tool when we're doing the same task for different subfolders, but that tool might not make sense to run everywhere.&lt;/p&gt;
&lt;p&gt;For GitHub Actions (other platforms have equivalents), there are &lt;tt class="docutils literal"&gt;`paths&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;paths-ignore&lt;/span&gt;&lt;/tt&gt; parameters &amp;lt;&lt;a class="reference external" href="https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#example-including-paths"&gt;https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#example-including-paths&lt;/a&gt;&amp;gt;`__ you can add to the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;pull-request&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;push&lt;/tt&gt; configuration. For example, here's the first few lines of a GitHub action that only fires on a push event to one of a specific set of paths:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;check-links&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'project/docs/**'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'another-project/docs/**'&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;In terms of functionality, there's no harm in using the common pattern of having a job that runs, checks if any of the changes need it to do anything, and exiting if not.&lt;/p&gt;
&lt;p&gt;In terms of both usability and performance, however, I greatly prefer this approach of filtering with paths. If a change doesn't match a path, the job doesn't need to wait for resources to run and then realise it doesn't need to run. And the humans don't need to scroll past 10 inapplicable jobs that woke up, did nothing, and exited to find the one that related to the project they're actually working on.&lt;/p&gt;
&lt;p&gt;Take some time to check the automation on the repositories you work on and if there are exemption checks within the jobs, adjust them so that they don't run at all if the changes don't require it - I can't share the performance improvements I gained on a recent project, but I would love to hear your anecdotes in the comments if you have them :)&lt;/p&gt;
</content><category term="tech"/><category term="ci"/><category term="github"/><category term="tools"/></entry><entry><title>OpenAPI Overlays to avoid API oversharing</title><link href="https://lornajane-net.pages.dev/posts/2024/openapi-overlays-to-avoid-api-oversharing" rel="alternate"/><published>2024-12-12T11:37:00+00:00</published><updated>2024-12-12T11:37:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2024-12-12:/posts/2024/openapi-overlays-to-avoid-api-oversharing</id><summary type="html">&lt;p class="first last"&gt;Most APIs aren't perfect. By design they are long-lived and may serve many audiences. I see a lot of organisations maintaining multiple versions of APIs, or simply not documenting some of the endpoints to avoid them being included in the published documentation and confusing users. The problem of API redaction is a real one in most organisations, and it goes unsolved or is solved in a way that causes ongoing friction. I've helped a few API projects to solve this problem with the new &lt;a class="reference external" href="https://learn.openapis.org/overlay/"&gt;OpenAPI Overlay specification&lt;/a&gt; so I thought I'd share!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Most APIs aren't perfect. By design they are long-lived and may serve many audiences. I see a lot of organisations maintaining multiple versions of APIs, or simply not documenting some of the endpoints to avoid them being included in the published documentation and confusing users. The problem of API redaction is a real one in most organisations, and it goes unsolved or is solved in a way that causes ongoing friction. I've helped a few API projects to solve this problem with the new &lt;a class="reference external" href="https://learn.openapis.org/overlay/"&gt;OpenAPI Overlay specification&lt;/a&gt; so I thought I'd share!&lt;/p&gt;
&lt;p&gt;My starting point is always to prefer a &amp;quot;full fat&amp;quot; OpenAPI description, a single moment where all the API surface is available in one place, even if it means building out some &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2024/pipelines-api-descriptions"&gt;API description pipeline&lt;/a&gt; or &lt;a class="reference external" href="https://redocly.com/blog/combining-openapis"&gt;combining files&lt;/a&gt; to get there. The big advantage is being able to lint, transform and review changes within the wider context.&lt;/p&gt;
&lt;p&gt;From this point, start to filter out the parts that shouldn't be exposed externally. For example, perhaps you have a legacy endpoint that the API gateway needs to honour for historical reasons, but nobody should really know that it is there apart from that! Using the &lt;tt class="docutils literal"&gt;deprecated&lt;/tt&gt; field on those endpoints makes it easier to filter out.&lt;/p&gt;
&lt;p&gt;For example, here's a very short example API description with a single deprecated endpoint:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;3.1.0&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;1.0.0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Imaginary town&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;'/locations'&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;All locations&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;locationList&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;deprecated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s"&gt;'200'&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Returns all locations&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;content&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nt"&gt;application/json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;To remove the operations with &lt;tt class="docutils literal"&gt;deprecated: true&lt;/tt&gt;, I use an overlay like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
overlay: 1.0.0
info:
  title: Remove deprecated operations
  version: 1.0.0
actions:
- target: '$..paths..[?(&amp;#64;.deprecated==true)]'
  remove: true
&lt;/pre&gt;
&lt;p&gt;Run it with your &lt;a class="reference external" href="https://github.com/OAI/Overlay-Specification/?tab=readme-ov-file#tools-that-support-overlays"&gt;chosen overlay tool&lt;/a&gt; (I'm using Bump CLI so &lt;tt class="docutils literal"&gt;bump overlay openapi.yaml overlay.yaml &amp;amp;gt; output.yaml&lt;/tt&gt; works) and you are all set! The OpenAPI description is safe to share.&lt;/p&gt;
&lt;p&gt;It's not just the deprecated endpoints that you might not want to share. I also commonly see &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;x-experimental&lt;/span&gt;&lt;/tt&gt; for features that aren't ready for general adoption yet, or other teams use tags to indicate the intended audience such as only pro users, or only partners.&lt;/p&gt;
&lt;p&gt;What I like about gathering an &amp;quot;everything&amp;quot; OpenAPI version and then removing it is that it makes easier API governance and maintenance because you're seeing the whole picture. But that you can publish API descriptions to different audiences or different tooling with as much redacted as you like! Generate one set of API documentation to share with your internal teams or early adopters, then use an Overlay to remove the experimental endpoints before publishing to your public developer portal.&lt;/p&gt;
&lt;p&gt;The key points here are: API redaction is better than having informal documentation or multiple API descriptions, and Overlays make this easy. Your OpenAPI is not a one-time document, you'll use it as a pipeline, applying many Overlays or other transforms during the build process.&lt;/p&gt;
&lt;p&gt;What's your favourite thing to do with an Overlay? Show me an example if you have one!&lt;/p&gt;
</content><category term="tech"/><category term="api"/><category term="openapi"/><category term="overlays"/></entry><entry><title>How to structure and share code examples</title><link href="https://lornajane-net.pages.dev/posts/2024/how-to-structure-and-share-code-examples" rel="alternate"/><published>2024-11-18T09:28:00+00:00</published><updated>2024-11-18T09:28:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2024-11-18:/posts/2024/how-to-structure-and-share-code-examples</id><summary type="html">&lt;p class="first last"&gt;Writing for technical audiences often means sharing code in one form or another. If the code is only for illustration purposes, then it can be included in the article for observation. But if the code is useful or reusable by your readers, then you need a way to make it available to them. I've seen a few questions on this recently (shout out to the DevRel Collective slack for a brilliant discussion AND nagging me to write it up as a post!), so I'm sharing my experiences on how to publish and maintain code in sane ways!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Writing for technical audiences often means sharing code in one form or another. If the code is only for illustration purposes, then it can be included in the article for observation. But if the code is useful or reusable by your readers, then you need a way to make it available to them. I've seen a few questions on this recently (shout out to the DevRel Collective slack for a brilliant discussion AND nagging me to write it up as a post!), so I'm sharing my experiences on how to publish and maintain code in sane ways!&lt;/p&gt;
&lt;p&gt;To help you think more about what you're trying to do, there are some criteria that might help.&lt;/p&gt;
&lt;div class="section" id="single-file-or-simple-code"&gt;
&lt;h3&gt;Single file or simple code&lt;/h3&gt;
&lt;p&gt;If the example you need to share is in just one file (or a few files, for an advanced audience), consider sticking to the code-as-exhibit approach and embed it in the article with clear directions for where to copy and paste it to. Doing it this way makes it super simple for both publishers and users, and is very easy to understand and maintain. Since the code quickly ends up in the user's hands, it's also a good choice for code samples that require editing to work, such as &amp;quot;add your API key on line 3&amp;quot; rather than trying to walk through setting an environment variable so that a cloned code sample can run unchanged.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro-tip&lt;/strong&gt;: Make sure the destination publishing platform has an easy &amp;quot;copy&amp;quot; button on code samples.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="use-one-example-from-a-selection"&gt;
&lt;h3&gt;Use one example from a selection&lt;/h3&gt;
&lt;p&gt;If you create content for developers, you quickly amass a LOT of shareable code. How much of that code does each user need?&lt;/p&gt;
&lt;p&gt;If a user might only need one example, or there are a lot of files that are themselves a project, then one-repo-per-example is a good model. For example, creating template repositories for quickstarts that can be used to scaffold applications.&lt;/p&gt;
&lt;p&gt;The downside of having lots of example repos is that now you have lots of example repos to monitor, maintain, and look after! I've had good success using some of the GitHub features (adjust to fit your preferred source control platform) to help manage things:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Add a &lt;a class="reference external" href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners"&gt;codeowners file&lt;/a&gt; to every repository so that pull requests always have an owner and show up on someone's review requests list! Make this a team name, so you can add more people into the team or easily update who should get these notifications if you have churn.&lt;/li&gt;
&lt;li&gt;Automatically set new issues on these repositories to be added to a GitHub project so you can track the maintenance of the projects all in one place. Enable &lt;a class="reference external" href="https://github.com/dependabot"&gt;dependabot&lt;/a&gt; or an equivalent as well, and then you can tackle updates in batches as needed. You can automatically add the pull requests to a board as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="lots-of-examples-using-lots-of-files"&gt;
&lt;h3&gt;Lots of examples, using lots of files&lt;/h3&gt;
&lt;p&gt;When each code example spans multiple files, and users might want to use a few of them, or dip into them over time - that's a good prompt to build up a library of examples in one repository. Use a clear structure, such as one folder per product with subfolders for each example, so that it's easy to see what goes where, and to add new things in sensible places.&lt;/p&gt;
&lt;p&gt;How far you go with making the examples instantly executable in this context, vs just making them available, is really up to you. Two rather divergent examples I've worked on are the &lt;a class="reference external" href="https://github.com/Vonage/vonage-php-code-snippets"&gt;Vonage code snippets&lt;/a&gt; and the &lt;a class="reference external" href="https://github.com/redocly/redocly-cli-cookbook?tab=readme-ov-file#readme"&gt;Redocly CLI cookbook&lt;/a&gt;. The Vonage code snippets are executable, have automated testing for different versions of the technology they are for, and are included into the main developer documentation. This works really well for snippets that should be ready to use and to just drop into projects that already use the Vonage SDKs to perform simple tasks. The Redocly CLI cookbook is almost the opposite: the main &lt;tt class="docutils literal"&gt;README&lt;/tt&gt; lists a lot of different examples, and each subfolder has a detailed &lt;tt class="docutils literal"&gt;README&lt;/tt&gt; for the example and all the files needed to make it work. Users need to copy, paste and almost always edit parts of the provided example, and slot them in to their own project structure. Since each project is different, providing reusable source made sense there.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="code-example-upgrades"&gt;
&lt;h3&gt;Code example upgrades&lt;/h3&gt;
&lt;p&gt;Whichever approach you choose, there are a few things that make every example better:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Use meaningful values&lt;/strong&gt;: swap &lt;tt class="docutils literal"&gt;foo&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;bar&lt;/tt&gt; for &lt;tt class="docutils literal"&gt;product&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;basket&lt;/tt&gt;, or illustrate attributes with colour, weight and horsepower. Good examples really are worth a thousand words!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Words always help&lt;/strong&gt;: Code examples are not self-explanatory. Introduce every example with what it does, and why you'd like to do that. Follow every example with what just happened and how to observe that is, in fact, what happened.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continue to care&lt;/strong&gt;: if you publish to GitHub, subscribe to the issues and pull requests of where you've published it. If you publish to a developer portal, make sure you have access to the feedback for those pages. Let users tell you if something gets outdated or needs changing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;There is no such thing as too many examples&lt;/strong&gt;: Make as many examples as you can with the people/skills/time/energy you have available! People who write code will find the bits they need an mix-and-match to solve their own problems if you give them enough raw material to work with.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="code"/><category term="devex"/><category term="github"/></entry><entry><title>Pretty-print JSON with jq</title><link href="https://lornajane-net.pages.dev/posts/2024/pretty-print-json-with-jq" rel="alternate"/><published>2024-11-01T17:20:00+00:00</published><updated>2024-11-01T17:20:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2024-11-01:/posts/2024/pretty-print-json-with-jq</id><summary type="html">&lt;p class="first last"&gt;Wrangling some document conversion the other day, I ended up in a situation where I had the JSON I needed, but in a completely unreadable format. Luckily, this problem is very easily fixable .... when you know how. So today's post is a quick recap on how I did that using &lt;a class="reference external" href="https://jqlang.github.io/jq/"&gt;jq&lt;/a&gt;, a very handy command-line tool for working with JSON. For the impatient, here's the command:&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Wrangling some document conversion the other day, I ended up in a situation where I had the JSON I needed, but in a completely unreadable format. Luckily, this problem is very easily fixable .... when you know how. So today's post is a quick recap on how I did that using &lt;a class="reference external" href="https://jqlang.github.io/jq/"&gt;jq&lt;/a&gt;, a very handy command-line tool for working with JSON. For the impatient, here's the command:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
cat&lt;span class="w"&gt; &lt;/span&gt;posts.json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;better.json
&lt;/pre&gt;
&lt;p&gt;In this post we'll look at the data I started with and what the different bits of the command to do help.&lt;/p&gt;
&lt;p&gt;My data looked like this (actually I was working with an OpenAPI file, which was huge, so this is some other JSON data that will be easier to show the example with):&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Open Standards for APIs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;link&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://beta.lornajane.net/posts/2024/open-standards-for-apis&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;date&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2024-10-09&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Lint APIs with Redocly CLI&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;link&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://beta.lornajane.net/posts/2024/lint-apis-with-redocly-cli&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;date&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2024-08-05&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;API Description Pipelines&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;link&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://beta.lornajane.net/posts/2024/api-description-pipelines&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;date&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2024-06-18&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Checking Links in Docs-As-Code Projects&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;link&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://beta.lornajane.net/posts/2024/checking-links-in-docs-as-code-projects&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;date&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2024-04-27&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;That's ... not very human friendly! I put this mess into a file called &lt;tt class="docutils literal"&gt;posts.json&lt;/tt&gt; so I could more easily work with it.&lt;/p&gt;
&lt;p&gt;Then I used the command that you saw above. Here is a quick recap of what each part does:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;cat&lt;/tt&gt; is a command to print the contents of the file to stdout. It's a good way to get content into the start of a chain of commands.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;|&lt;/tt&gt; operator sends the output of the &lt;tt class="docutils literal"&gt;cat&lt;/tt&gt; command into the next thing in the command.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;jq&lt;/tt&gt; is a JSON tool, and the &lt;tt class="docutils literal"&gt;&amp;quot;.&amp;quot;&lt;/tt&gt; part is technically a filter - but it includes everything, so it's more like a not-filter. The output of &lt;tt class="docutils literal"&gt;jq&lt;/tt&gt; is the same JSON that we put in, but it's pretty-printed.&lt;/li&gt;
&lt;li&gt;If we stopped at this point, we'd get pretty, human-readable JSON content to look at with colour highlights in the terminal. I use this mode a lot when I'm debugging APIs. But in this case, I wanted the cute output in a file.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&amp;gt;&lt;/tt&gt; sends the output of stdout to a destination that isn't the terminal, and in this case I made it a file &lt;tt class="docutils literal"&gt;better.json&lt;/tt&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The data in the new file looks a lot better:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Open Standards for APIs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;link&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://beta.lornajane.net/posts/2024/open-standards-for-apis&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;date&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2024-10-09&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Lint APIs with Redocly CLI&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;link&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://beta.lornajane.net/posts/2024/lint-apis-with-redocly-cli&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;date&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2024-08-05&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;API Description Pipelines&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;link&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://beta.lornajane.net/posts/2024/api-description-pipelines&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;date&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2024-06-18&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Checking Links in Docs-As-Code Projects&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;link&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://beta.lornajane.net/posts/2024/checking-links-in-docs-as-code-projects&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;date&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2024-04-27&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Tools like &lt;tt class="docutils literal"&gt;jq&lt;/tt&gt; are great to have to hand. What are the tools you can't manage without? I'd love to hear some suggestions!&lt;/p&gt;
</content><category term="tech"/><category term="json"/><category term="tools"/></entry><entry><title>Open Standards for APIs</title><link href="https://lornajane-net.pages.dev/posts/2024/open-standards-for-apis" rel="alternate"/><published>2024-10-09T08:21:00+01:00</published><updated>2024-10-09T08:21:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2024-10-09:/posts/2024/open-standards-for-apis</id><summary type="html">&lt;p class="first last"&gt;I work or have worked with APIs of all sorts of standards, sometimes many standards at once - or none. Each standard exists for a purpose and might be a great fit, or a terrible fit for other purposes. Today's post is a recap of some of the common API standards around and what you need to know about each one.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I work or have worked with APIs of all sorts of standards, sometimes many standards at once - or none. Each standard exists for a purpose and might be a great fit, or a terrible fit for other purposes. Today's post is a recap of some of the common API standards around and what you need to know about each one.&lt;/p&gt;
&lt;p&gt;Why do APIs and open standards matter? APIs are the way that machines exchange information. They are the engines that drive connected software. Without APIs, we'd be waiting for a nightly CSV download or parsing data from PDFs. Using open standards gives us established patterns to follow, so that the data can be easily transferred and understood. The familiar formats make APIs easier for humans, and very very much easier for machines.&lt;/p&gt;
&lt;p&gt;Of course, there isn't a single standard (and there's an &lt;a class="reference external" href="https://xkcd.com/927/"&gt;XKCD for that&lt;/a&gt;), so let's take a quick look at some of the likely suspects.&lt;/p&gt;
&lt;div class="section" id="openapi"&gt;
&lt;h3&gt;OpenAPI&lt;/h3&gt;
&lt;p&gt;OpenAPI might be the most widely adopted and best known (even if people still think it is called &amp;quot;Swagger&amp;quot; - it isn't) API description formats around. It's an open format; different people from different organisations contribute to and maintain the standard, and all the changes are made through public GitHub activity and open meetings. Disclaimer: I'm one of the maintainers of the OpenAPI specification.&lt;/p&gt;
&lt;p&gt;OpenAPI descriptions are written in JSON or YAML format, and they seek to describe the endpoints, requests and responses of an API. This format aims to compromise between API producers, and the consumers of those APIs. In particular it includes user-facing information such as rich text description fields, and supports the inclusion of examples as well. Some organisations generate OpenAPI from their source code, in which case the text content and examples are usually fairly minimal.&lt;/p&gt;
&lt;p&gt;OpenAPI is brilliant for RESTful(ish) APIs, and may be useful for other HTTP formats.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="asyncapi"&gt;
&lt;h3&gt;AsyncAPI&lt;/h3&gt;
&lt;p&gt;AsyncAPI is very much from the same school of thought as OpenAPI, but it mostly describes event-driven APIs rather than HTTP ones. Event-driven APIs can use many, many differently protocols such as Apache Kafka, MQTT, websockets, AMQP/RabbitMQ, SQS and many more things including some I hadn't heard of before!&lt;/p&gt;
&lt;p&gt;AsyncAPI is therefore more complex than OpenAPI, and any applications that use it tend to have more AsyncAPI specifications in the mix since each producer, broker, and consumer may publish its own API description to describe how to interact with it. AsyncAPI is mostly used by machines and enables us to smoothly transfer many many messages around smoothly and without losing our minds.&lt;/p&gt;
&lt;p&gt;AsyncAPI is great for any event-driven applications that use protocols other than HTTP.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="grpc-service-definition"&gt;
&lt;h3&gt;gRPC Service Definition&lt;/h3&gt;
&lt;p&gt;RPC stands for &amp;quot;Remote Procedure Call&amp;quot; and gRPC is a standard service definition format. It's very code-driven, with established ways to declare the service itself and the data payloads that can be used with the various functions. The open standard means that it can be used in multiple technology stacks, and gives us a good option for using open standards with RPC-style APIs.&lt;/p&gt;
&lt;p&gt;gRPC is pretty lightweight and it is commonly found in microservices in organisations where REST didn't catch on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="soap-and-wsdl"&gt;
&lt;h3&gt;SOAP and WSDL&lt;/h3&gt;
&lt;p&gt;I always refer to WSDL as the original API description language, it and SOAP have been around for a long time and pre-date most of the other entries in this list. SOAP definitely isn't the new shiny thing, but it's still in active use in many organisations and it has earned its place.&lt;/p&gt;
&lt;p&gt;Generally seen as a fairly bulky API format, SOAP services are usually XML-based RPC services. Use of XML gives great support for strict and structured data (in contrast to JSON as used in some modern API formats) and this is one reason for SOAP's staying power. Inertia also counts for a lot, especially in the more established enterprises where these tech stacks are most likely to be spotted.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="graphql"&gt;
&lt;h3&gt;GraphQL&lt;/h3&gt;
&lt;p&gt;GraphQL was the new hotness a few years ago but it's not a replacement for all APIs; merely a great solution for some specific use cases. GraphQL is a web-enabled data fetching interface where the client/user defines the fields that should be returned, and can also add related data to make for unexpected and complex queries. For offering a flexible reporting interface, GraphQL is a tool of beauty and satisfaction for both producer and consumer. For replacing a CRUD (create/read/update/delete) API, GraphQL is ..... not that.&lt;/p&gt;
&lt;p&gt;The hype is fading, but GraphQL is not. The tools are maturing and awareness and understanding are improving.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="standards-matter"&gt;
&lt;h3&gt;Standards matter&lt;/h3&gt;
&lt;p&gt;Open standards enable us to build applications that talk to one another and achieve more than we could do alone. Choosing and adopting standards in your own work does matter, and being aware of what is out there is very important for tech professionals of all stripes. Hopefully this rather short and irreverent roundup gave you something new or useful (or both!).&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="api"/><category term="asyncapi"/><category term="openapi"/><category term="standards"/></entry><entry><title>Lint APIs with Redocly CLI</title><link href="https://lornajane-net.pages.dev/posts/2024/lint-apis-with-redocly-cli" rel="alternate"/><published>2024-08-05T07:58:00+01:00</published><updated>2024-08-05T07:58:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2024-08-05:/posts/2024/lint-apis-with-redocly-cli</id><summary type="html">&lt;p class="first"&gt;API governance needs good tooling, and rules that fit - so today's post covers both.&lt;/p&gt;
&lt;p class="last"&gt;Fun fact: I work at Redocly and had used the tool for years, but recently realised that I have a post about a different tool in the archives of my blog, but no Redocly post. So here's the Lorna-recommended version of OpenAPI linting with Redocly CLI.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;API governance needs good tooling, and rules that fit - so today's post covers both.&lt;/p&gt;
&lt;p&gt;Fun fact: I work at Redocly and had used the tool for years, but recently realised that I have a post about a different tool in the archives of my blog, but no Redocly post. So here's the Lorna-recommended version of OpenAPI linting with Redocly CLI.&lt;/p&gt;
&lt;p&gt;Linting won't make your API secure or necessarily delightful, but it can pick out some key aspects and check them programmatically. Typically you'd include linting in your CI (Continuous Integration) setup so that you get the checks run all the way through the API development lifecycle. It's also key to be able to run the tools locally for immediate checks when you're working on your API description.&lt;/p&gt;
&lt;p&gt;You can read the &lt;a class="reference external" href="https://redocly.com/docs/cli"&gt;docs for Redocly CLI&lt;/a&gt; for the details but in summary: an open source CLI tool that helps with every aspect of an OpenAPI description, including configurable linting. Install the tool and lint your API description with the following command:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
redocly&lt;span class="w"&gt; &lt;/span&gt;lint&lt;span class="w"&gt; &lt;/span&gt;openapi.yaml
&lt;/pre&gt;
&lt;p&gt;If you get a lot of output, DON'T PANIC.&lt;/p&gt;
&lt;p&gt;All the linting tools come with default or &amp;quot;recommended&amp;quot; rulesets, but they aren't a perfect fit for every situation so they're not always a great default. My biggest advice around API linting is that &lt;strong&gt;every team needs to use a custom ruleset to meet its own needs and context&lt;/strong&gt;. The recommended is a good starting point when you're totally new to these tools, but they're a one-size-fits-nobody solution, and you'll want to adapt.&lt;/p&gt;
&lt;p&gt;Redocly CLI is configured by &lt;tt class="docutils literal"&gt;redocly.yaml&lt;/tt&gt;, and again, the docs are excellent (yes, I am biased!), but to get you quickly started with a reduced but still relevant ruleset, try this configuration as a starting point:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;no-identical-paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;warn&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;no-server-trailing-slash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;warn&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;no-unresolved-refs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;operation-operationId-unique&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;operation-operationId-url-safe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;warn&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;spec-components-invalid-map-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;warn&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;no-invalid-media-type-examples&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;warn&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;no-example-value-and-externalValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;no-unused-components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;warn&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;operation-operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;warn&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;You still need to edit your own configuration and add/remove things that make sense for your use case, but especially if you're retro-fitting API governance to an existing API or APIs, the recommended ruleset can be a bit much!&lt;/p&gt;
&lt;div class="section" id="beyond-the-provided-rules"&gt;
&lt;h3&gt;Beyond the provided rules&lt;/h3&gt;
&lt;p&gt;In addition to a customised ruleset, I'd expect most teams to need some configurable rules and even some transformation steps of their own. I've written before &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2023/overlay-improvements-to-openapi"&gt;about OpenAPI Overlays&lt;/a&gt;, and Redocly CLI also has some nice custom plugin functionality that makes it easy to add resusable rules, sorting operations, or a variety of other features.&lt;/p&gt;
&lt;p&gt;Here's an example for checking that the API description has an &lt;tt class="docutils literal"&gt;info.version&lt;/tt&gt; field that uses &lt;a class="reference external" href="https://semver.org/"&gt;semantic versioning format&lt;/a&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
rule/version-is-semver:
subject:
type: Info
property: version
assertions:
pattern: /^[0-9]+\.[0-9]/
severity: error
message: info.version must use semantic versioning.
&lt;/pre&gt;
&lt;p&gt;Creating these custom rules is a great way to pick up on the naming rules and other conventions that you want to have done right for every change to every API.&lt;/p&gt;
&lt;p&gt;For more rule and plugin examples, &lt;a class="reference external" href="https://github.com/Redocly/redocly-cli-cookbook"&gt;Redocly CLI has a cookbook&lt;/a&gt; where you can find lots of examples, and share your secrets with others too - so if you have rules that work for you, I'd love to see them!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="api governance"/><category term="openapi"/><category term="redocly"/><category term="redocly-cli"/></entry><entry><title>API Description Pipelines</title><link href="https://lornajane-net.pages.dev/posts/2024/pipelines-api-descriptions" rel="alternate"/><published>2024-06-18T21:54:00+01:00</published><updated>2024-06-18T21:54:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2024-06-18:/posts/2024/pipelines-api-descriptions</id><summary type="html">&lt;p class="first last"&gt;Working on API tools, I get to see inside lots of different organisations' API projects and processes. Every scenario is different, but a common theme is that many companies use a more complicated API description workflow than you see in conference slide decks! This article shares my typical workflow, steps and chosen tools that might show up in an API description pipeline.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Working on API tools, I get to see inside lots of different organisations' API projects and processes. Every scenario is different, but a common theme is that many companies use a more complicated API description workflow than you see in conference slide decks! This article shares my typical workflow, steps and chosen tools that might show up in an API description pipeline.&lt;/p&gt;
&lt;p&gt;At a high level, the process looks something like this:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2024/06/api-description-pipelines-workflow.png"&gt;&lt;img alt="diagram of the process outlined in this post with the steps: normalise, lint, &amp;quot;now you have a complete openapi&amp;quot;, filter, and deliver." class="aligncenter size-full wp-image-4988" src="https://lornajane-net.pages.dev/images/2024/06/api-description-pipelines-workflow.png" style="width: 414px; height: 993px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(sorry, it's really tall, but really wide didn't work well!)) You can get the &lt;a class="reference external" href="https://gist.github.com/lornajane/d4bc693f8e2574c8202c42e74d1a8abe"&gt;mermaidjs source for this diagram&lt;/a&gt; and you are welcome to edit/reuse but please credit me.&lt;/p&gt;
&lt;p&gt;In this post, I'll dive into these steps and some tools that might be helpful.&lt;/p&gt;
&lt;div class="section" id="gather-the-inputs"&gt;
&lt;h3&gt;Gather the inputs&lt;/h3&gt;
&lt;p&gt;If you're publishing one API that draws on multiple sources, such as a set of microservices, it helps to &lt;a class="reference external" href="https://redocly.com/blog/combining-openapis/"&gt;combine those OpenAPI descriptions&lt;/a&gt; into one unified OpenAPI description. This is different to the bundling/dereferencing step that we'll come to later.&lt;/p&gt;
&lt;p&gt;If you're publishing multiple APIs, then you'll run all the steps here for each of the APIs. If you're publishing one API (in various forms) to multiple sources, we'll fan out to the different destinations part way through the pipeline.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="normalise-fill-gaps-and-fix-problems"&gt;
&lt;h3&gt;Normalise: fill gaps and fix problems&lt;/h3&gt;
&lt;p&gt;Wave the magic wand and improve the OpenAPI to add in everything that you'll need in later stages. Typically this could include:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;finishing the developer experience by adding summaries, descriptions (for operations, tags, parameters, schemas ... everything) and examples.&lt;/li&gt;
&lt;li&gt;adding metadata such as servers, info descriptions, licenses, docs platform links, and other items that either weren't included or which need adding to the newly-assembled API description.&lt;/li&gt;
&lt;li&gt;fixing things that the generator did wrong (generators make invalid output more often than I realised until I started working a lot with this stuff).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OpenAPI is much richer than any of the possible information sources, or any individual output. For design-first organisations, all the extra goodness is there by design, but for teams working with API descriptions generated from code, or otherwise find themselves working with wish-this-was-better API descriptions, using tools to programmatically patch improvements is a winning game plan. It's important to have a repeatable way of making these changes, since the OpenAPI inputs will update and it's likely that will happen often.&lt;/p&gt;
&lt;p&gt;Tools to try: &lt;a class="reference external" href="https://github.com/OAI/Overlay-Specification/"&gt;OpenAPI Overlays&lt;/a&gt; or &lt;a class="reference external" href="https://redocly.com/docs/cli/decorators/"&gt;Redocly decorators&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="traditional-ci-checks"&gt;
&lt;h3&gt;Traditional CI checks&lt;/h3&gt;
&lt;p&gt;Every API needs this step as a minimum! Check that the OpenAPI description is valid, and optionally apply some formatting rules to check that quotes, line wrapping, indentation and that sort of thing are all as you would want them. You could even &lt;a class="reference external" href="https://redocly.com/blog/lint-markdown/"&gt;lint the Markdown in your OpenAPI description&lt;/a&gt; or &lt;a class="reference external" href="https://github.com/Redocly/redocly-cli-cookbook/tree/main/custom-plugin-decorators/tag-sorting"&gt;sort entries into alphabetical order&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;API governance rules are applied at this point. Governance and linting are a whole separate post (and I write a lot about this in my day job for Redocly since they make excellent tools for these things) so I'm restraining myself from that particular tangent. TL;DR make sure the API is up to scratch before you proceed. If it is not, then stop and try again.&lt;/p&gt;
&lt;p&gt;Tools to try: &lt;a class="reference external" href="https://redocly.com/docs/cli/api-standards/"&gt;Redocly lint&lt;/a&gt; or &lt;a class="reference external" href="https://stoplight.io/open-source/spectral"&gt;Spectral&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="fan-out-from-here"&gt;
&lt;h3&gt;Fan out from here&lt;/h3&gt;
&lt;p&gt;This is &amp;quot;half time&amp;quot; in the game of API description pipelines and we have a complete OpenAPI. All the inputs are in, counted, tidied up and verified. Now we can use the API description to feed into all the outputs, adapting a copy of the description for each destination&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="filter-to-fit-the-use-case"&gt;
&lt;h3&gt;Filter to fit the use case&lt;/h3&gt;
&lt;p&gt;Your work-in-progress APIs may include more information than you necessarily want to publish to all users of an API, so the next stage is to weed out things that shouldn't be included. It's common to run this step and all following steps in parallel, once for each deliverable thing or destination of an API description.&lt;/p&gt;
&lt;p&gt;For example, you might remove all the endpoints with &lt;tt class="docutils literal"&gt;deprecated: true&lt;/tt&gt;, or create an API description that only includes the endpoints tagged &amp;quot;partner&amp;quot;. This step is important otherwise you can find yourself maintaining a lot of different API descriptions that sort of overlap, or not being able to have API descriptions for things you're actually working on because they can't be published yet.&lt;/p&gt;
&lt;p&gt;Tools to try: &lt;a class="reference external" href="https://github.com/OAI/Overlay-Specification/"&gt;OpenAPI Overlays&lt;/a&gt; or &lt;a class="reference external" href="https://redocly.com/docs/cli/decorators/"&gt;Redocly decorators&lt;/a&gt;. And if you end up with tags that have no endpoints, there's a &lt;a class="reference external" href="https://github.com/Redocly/redocly-cli-cookbook/tree/main/custom-plugin-decorators/remove-unused-tags"&gt;recipe for that&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="prepare-for-a-perfect-finish"&gt;
&lt;h3&gt;Prepare for a perfect finish&lt;/h3&gt;
&lt;p&gt;The API description may be going to a few different destinations, and each one might need some different additions. For example, maybe your documentation renderer doesn't understand multiple tags for one endpoint, so you strip the others at this point. Or the SDK generator you use gives better results if you add some extensions to the OpenAPI description before passing the API description to that tool.&lt;/p&gt;
&lt;p&gt;Tools to try: &lt;a class="reference external" href="https://github.com/OAI/Overlay-Specification/"&gt;OpenAPI Overlays&lt;/a&gt; or &lt;a class="reference external" href="https://redocly.com/docs/cli/decorators/"&gt;Redocly decorators&lt;/a&gt;. Yes, again.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="package-for-delivery"&gt;
&lt;h3&gt;Package for delivery&lt;/h3&gt;
&lt;p&gt;Some of the destination tools in the OpenAPI space (meaning there are no further uses of the OpenAPI description after that tool) such as API Gateways may feature limited capacity to resolve references. Some don't resolve external files and must be supplied a single-file OpenAPI description, in which case you can bundle yours and send that. Others can't follow the OpenAPI &lt;tt class="docutils literal"&gt;$ref&lt;/tt&gt; syntax at all, so look for a bundler than can &amp;quot;dereference&amp;quot; - meaning it puts everything inline, which can make for very large OpenAPI description files so do take care.&lt;/p&gt;
&lt;p&gt;At this stage you may also need to convert back to 3.0 or an even earlier version of OpenAPI, and there are tools available for that too. Just don't let that one tool hold back your whole process!&lt;/p&gt;
&lt;p&gt;Tools to try: &lt;a class="reference external" href="https://redocly.com/docs/cli/file-management/#bundle-openapi-to-a-single-file"&gt;Redocly bundle&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="designing-pipelines"&gt;
&lt;h3&gt;Designing pipelines&lt;/h3&gt;
&lt;p&gt;If every API project needed the same tools in the same order, you'd be able to add it with a single click! In fact, every API and organisation are different, and so are their pipelines. I've outlined some common patterns in this article, so start with the &amp;quot;Traditional CI checks&amp;quot; step, and then pick from the other items on the list as you need them. As always, OpenAPI tooling moves quickly and there's some good innovations in this space, so when you're ready to add a new tool to the box it's always worth a visit to &lt;a class="reference external" href="https://openapi.tools/"&gt;https://openapi.tools/&lt;/a&gt; and see what's new or what best fits your requirements.&lt;/p&gt;
&lt;p&gt;So, what's missing from my list? Add a comment and let me know!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="api"/><category term="api governance"/><category term="openapi"/><category term="tools"/></entry><entry><title>Reviewdog filter settings with GitHub Actions</title><link href="https://lornajane-net.pages.dev/posts/2024/reviewdog-filter-settings-with-github-actions" rel="alternate"/><published>2024-03-25T08:10:00+00:00</published><updated>2024-03-25T08:10:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2024-03-25:/posts/2024/reviewdog-filter-settings-with-github-actions</id><summary type="html">&lt;p class="first last"&gt;Reviewdog is a tool to use with GitHub actions for applying review tools in your CI. I use it with Vale, and it's really good. One thing that has tripped me up multiple times is that by default, it only applies the checks to the changes in the pull request, not to the whole project. So when you add something like Vale, which is a prose linter, it only checks ... the workflow file you just added! I always forget that it works this way, and how to change it, so I'm pasting some examples for future reference.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Reviewdog is a tool to use with GitHub actions for applying review tools in your CI. I use it with Vale, and it's really good. One thing that has tripped me up multiple times is that by default, it only applies the checks to the changes in the pull request, not to the whole project. So when you add something like Vale, which is a prose linter, it only checks ... the workflow file you just added! I always forget that it works this way, and how to change it, so I'm pasting some examples for future reference.&lt;/p&gt;
&lt;div class="section" id="apply-checks-to-all-files"&gt;
&lt;h3&gt;Apply checks to all files&lt;/h3&gt;
&lt;p&gt;In general, I don't find it helpful to have the review tools reporting errors across a whole project on every build. However when I do want to see GitHub actions running the checks for the whole project, I can never remember the setting. So, future self, note that it is &lt;tt class="docutils literal"&gt;nofilter&lt;/tt&gt; (the problem is not the option name, it's just that I don't remember things).&lt;/p&gt;
&lt;p&gt;At the moment I'm working on a pull request where I'm adding Vale to GitHub actions via Reviewdog. The pull request only changes the workflow file, the Vale config, and the styles/Vocab files that go with Vale, so by default the build goes green and no problems are reported with the existing contents of the &lt;tt class="docutils literal"&gt;docs/&lt;/tt&gt; folder. I am pretty sure we didn't write perfect content when we didn't have any tools in place!! In fact if you check the build output, it does find problems in the project, but since they weren't introduced in this pull request, they don't match the filter. You can see the problems listed, but then not reported.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;nofilter&lt;/tt&gt; setting removes the filter and those errors and warnings get reported to the build. The job configuration looks like this:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;vale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;vale action&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;actions/checkout&amp;#64;v4&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;errata-ai/vale-action&amp;#64;reviewdog&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'[&amp;quot;README.md&amp;quot;,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;docs&amp;quot;]'&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;filter_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;nofilter&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;fail_on_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Once I'm satisfied that we've introduced the necessary fixes as well as the tool that checks them, I'll switch this back to &lt;tt class="docutils literal"&gt;file&lt;/tt&gt; before the pull request merges.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="apply-checks-to-changed-files"&gt;
&lt;h3&gt;Apply checks to changed files&lt;/h3&gt;
&lt;p&gt;My preferred &lt;tt class="docutils literal"&gt;filter&lt;/tt&gt; setting for day-to-day use is &lt;tt class="docutils literal"&gt;file&lt;/tt&gt;. In this mode, the checks run across the whole file for any file that has changed. It works well for something like a prose linter because it means that if something needs updating, we update it everywhere in the file, all at once, while we're changing that file anyway.&lt;/p&gt;
&lt;p&gt;Here's the job configuration again:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;vale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;vale action&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;actions/checkout&amp;#64;v4&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;errata-ai/vale-action&amp;#64;reviewdog&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'[&amp;quot;README.md&amp;quot;,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;docs&amp;quot;]'&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;filter_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;file&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;fail_on_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;It can be frustrating if you're changing something tiny and you get a whole file full of annotations - but in general, this approach has worked pretty well for me every time I've remembered what the setting is called! I'd be interested to hear how others manage this sort of thing and if you have any tips to share.&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="docs-as-code"/><category term="github"/><category term="tips"/><category term="tools"/><category term="vale"/><category term="writing"/></entry><entry><title>Short tech writing style guide for developers</title><link href="https://lornajane-net.pages.dev/posts/2024/short-tech-writing-style-guide-for-developers" rel="alternate"/><published>2024-01-24T09:18:00+00:00</published><updated>2024-01-24T09:18:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2024-01-24:/posts/2024/short-tech-writing-style-guide-for-developers</id><summary type="html">&lt;p class="first last"&gt;Style guides are vital to successful publishing projects, but they are usually too extensive for casual contributors. After running a number of projects with developers rather than specialist documentarians as the main contributors, I've started using a short-form style guide, short enough to be read and used by people who don't spend as much time thinking about these things as a full time writer does. The main version is &lt;a class="reference external" href="https://github.com/lornajane/developer-style-guide"&gt;published on GitHub&lt;/a&gt;, but I'm also sharing the current version here.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Style guides are vital to successful publishing projects, but they are usually too extensive for casual contributors. After running a number of projects with developers rather than specialist documentarians as the main contributors, I've started using a short-form style guide, short enough to be read and used by people who don't spend as much time thinking about these things as a full time writer does. The main version is &lt;a class="reference external" href="https://github.com/lornajane/developer-style-guide"&gt;published on GitHub&lt;/a&gt;, but I'm also sharing the current version here.&lt;/p&gt;
&lt;div class="section" id="target-a-single-audience-and-outcome"&gt;
&lt;h3&gt;Target a single audience and outcome&lt;/h3&gt;
&lt;p&gt;Know who you are writing for. The new junior developer needs different context and explanation than the Ops person trying to follow disaster recovery documentation. If it helps to think of a specific individual, do that. The key here is to not write down what you know, but write down what the reader needs to reach the desired outcome. An example outcome could be completing a task, learning about a specific topic, or understanding a set of options.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="structure-your-writing"&gt;
&lt;h3&gt;Structure your writing&lt;/h3&gt;
&lt;p&gt;If your organisation offers templates, always use them; consistency helps readers a lot, even if the content is created by many authors. If not, try the &lt;a class="reference external" href="https://diataxis.fr/"&gt;Diátaxis&lt;/a&gt; framework as inspiration on how to frame different content types - and don't be afraid to create more than one article from a single topic or assignment. If no structure is provided as a template, create your own by writing the subheadings of the piece before you write the words.&lt;/p&gt;
&lt;p&gt;Use subheadings, images or diagrams, lists, and other content types to communicate to your readers. This &amp;quot;page furniture&amp;quot; helps them to navigate potentially long and complex technical articles.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="be-positive-and-inclusive"&gt;
&lt;h3&gt;Be positive and inclusive&lt;/h3&gt;
&lt;p&gt;Focus on things the user &lt;em&gt;can&lt;/em&gt; do. Don't explain what can't be done, isn't supported, or hasn't been implmented yet unless these are central to the topic.&lt;/p&gt;
&lt;blockquote&gt;
Example: Instead of &amp;quot;you cannot back up your data yourself&amp;quot; you could say &amp;quot;data backups can be arranged by contacting the support team&amp;quot;.&lt;/blockquote&gt;
&lt;p&gt;Inclusive language helps make your writing useful to the widest possible range of people. Avoid gendered language, using &amp;quot;they&amp;quot; and &amp;quot;theirs&amp;quot; rather than &amp;quot;he&amp;quot; or &amp;quot;she&amp;quot;. Even when this feels clunky to write, it makes a positive impact on your readers. Other language to find alternatives for includes terms that could seem ableist such as using &amp;quot;OCD&amp;quot; or &amp;quot;crazy&amp;quot; in your writing. It's already standard practice to avoid socially-loaded terminology such as master/slave, blacklist/whitelist, and so on. Keeping a wordlist that is automatically checked, or using a tool like &lt;a class="reference external" href="https://alexjs.com/"&gt;AlexJS&lt;/a&gt; can help with spotting words and phrases that can be improved.&lt;/p&gt;
&lt;p&gt;Beware of jargon and abbreviations, because they can be difficult for newcomers to understand. Expand your abbreviations the first time you use them, and link to definitions or supporting content for any special terms to give the user the option of reading on , or clicking the link to learn more.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="write-commanding-titles"&gt;
&lt;h3&gt;Write commanding titles&lt;/h3&gt;
&lt;p&gt;Titles should use &lt;em&gt;imperative&lt;/em&gt; language to make it clear what the reader is doing in each section. It can feel like an abrupt style, but imagine ordering a small robot to perform each action and you will be pretty close. The side benefits of this approach are that it's often easier to translate, and tends to match user search terms more closely.&lt;/p&gt;
&lt;blockquote&gt;
Example: Instead of &amp;quot;Configuring the widget&amp;quot; use &amp;quot;Configure the widget&amp;quot;&lt;/blockquote&gt;
&lt;p&gt;Use sentence case for titles, and check them automatically using a tool like &lt;a class="reference external" href="https://vale.sh/"&gt;Vale&lt;/a&gt;. Add product names and other proper nouns to Vale to avoid false reports.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="choose-examples-and-screenshots-wisely"&gt;
&lt;h3&gt;Choose examples and screenshots wisely&lt;/h3&gt;
&lt;p&gt;If a picture is worth a thousand words, then a good example is worth at least twice that amount. In both cases:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Choose content that the user will know or easily understand. Avoid &amp;quot;foo&amp;quot;, and instead use &amp;quot;Alice&amp;quot; if it's a name, &amp;quot;Bucket&amp;quot; if it's a product, &amp;quot;paleblue&amp;quot; if it's a colour, and so on. Your reader will internalise that their data should look like your data.&lt;/li&gt;
&lt;li&gt;Avoid pop culture references, using these can exclude groups from other demographics than your own.&lt;/li&gt;
&lt;li&gt;Every example and screenshot needs text with it, explaining what it shows and why that's useful to the user.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Code examples are always valuable, adding more examples is usually a good thing.&lt;/p&gt;
&lt;p&gt;Screenshots are both more difficult to get right and more difficult to maintain over time since small changes to a UI will mean repeating all the screenshots. Use them when you think the user would be likely to struggle with words alone. If you take a series of screenshots, use a small and consistent window size for them all. Always reduce the file size as much as you can, and include alt text on every image (tools such as &lt;a class="reference external" href="https://github.com/DavidAnson/markdownlint"&gt;Markdownlint&lt;/a&gt; can check for alt text automatically).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="add-hyperlinks"&gt;
&lt;h3&gt;Add hyperlinks&lt;/h3&gt;
&lt;p&gt;Add links. Link to the tools you mention, the concepts that the user might not know already, and to other articles that are likely to be useful to this reader. Adding links to supporting material can enrich all content types, and help the reader to find their way to success.&lt;/p&gt;
&lt;p&gt;Choose words that represent where the link goes to, such as &amp;quot;see the &lt;a class="reference external" href="#add-hyperlinks"&gt;section on hyperlinks&lt;/a&gt;&amp;quot;. Some tools will offer the links with their link text as a list, so meaningful words or phrases and avoiding duplicate link text within a page is good practice.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="avoid-technical-documentation-missteps"&gt;
&lt;h3&gt;Avoid technical documentation missteps&lt;/h3&gt;
&lt;p&gt;Technical writing has some unwritten norms that exist for mostly good reasons of readability and comprehension. In particular, here are some things you should NOT do:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Use exclamation points. It's not a gossip magazine, it's technical documentation.&lt;/li&gt;
&lt;li&gt;Use question marks, especially in titles. It very easily reads like a car advert.&lt;/li&gt;
&lt;li&gt;Add emoji, unless the platform you are writing for has it as an established acceptable practice. Emoji are difficult to keep consistent between authors, can be distracting, and can also make the content less accessible.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="tech"/></entry><entry><title>API Linting Levels</title><link href="https://lornajane-net.pages.dev/posts/2024/api-linting-levels" rel="alternate"/><published>2024-01-11T00:05:00+00:00</published><updated>2024-01-11T00:05:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2024-01-11:/posts/2024/api-linting-levels</id><summary type="html">&lt;p class="first last"&gt;I've been thinking about API linting lately, partly because I work on &lt;a class="reference external" href="https://github.com/redocly/redocly-cli"&gt;a tool for API linting&lt;/a&gt; in my day job, and partly because I get quite a lot of questions from teams wanting to improve their API quality. The &amp;quot;best&amp;quot; ruleset depends entirely on your context, where you are in your API practice journey, and how much you want to invest in the API in question. I typically use a 4-levels model of API linting readiness, and I'm sharing them so that you can find yourself on the map and see where you might go from here.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've been thinking about API linting lately, partly because I work on &lt;a class="reference external" href="https://github.com/redocly/redocly-cli"&gt;a tool for API linting&lt;/a&gt; in my day job, and partly because I get quite a lot of questions from teams wanting to improve their API quality. The &amp;quot;best&amp;quot; ruleset depends entirely on your context, where you are in your API practice journey, and how much you want to invest in the API in question. I typically use a 4-levels model of API linting readiness, and I'm sharing them so that you can find yourself on the map and see where you might go from here.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This post is part of&lt;/em&gt; &lt;a class="reference external" href="https://matthewreinbold.github.io/APIFutures/"&gt;API Futures 2024&lt;/a&gt;&lt;em&gt;, a series of posts by different contributors focused on the challenges for this year. My big wish is for everyone to find API tooling and configuration that fits their own context, which is why I'm publishing this linting levels post a&lt;/em&gt;s &lt;em&gt;part of the event.&lt;/em&gt;&lt;/p&gt;
&lt;div class="section" id="level-0-a-valid-api-description"&gt;
&lt;h3&gt;Level 0: A valid API description&lt;/h3&gt;
&lt;p&gt;This sounds basic but a surprising number of public APIs either have no API description, or their OpenAPI description is actually invalid in some way. It may well work with the tools used in the producer organisation, but unless you supply a file that works with all other tools, there are limits to who can use and interact with your API.&lt;/p&gt;
&lt;p&gt;Add some unopinionated validation to your CI workflow, and make sure that anyone who works on your API can also run those tools locally while they're making changes - and you can graduate to the next level immediately :) Redocly has a rule for &lt;a class="reference external" href="https://redocly.com/docs/cli/rules/spec/"&gt;linting against the specification&lt;/a&gt; which is a great starting point (other API linting tools are available!). For the next level, there's a great &lt;a class="reference external" href="https://github.com/Redocly/redocly-cli-cookbook/tree/main/rulesets/spec-compliant"&gt;ruleset in the Redocly CLI Cookbook&lt;/a&gt; that you can copy and paste into your own projects; it aims to catch the things that match the declared data structure of OpenAPI and yet make absolutely no sense in the real world!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="level-1-meets-basic-standards"&gt;
&lt;h3&gt;Level 1: Meets basic standards&lt;/h3&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Every API probably should have its own set of basic standards but I typically ask about the API linting rules that are already in place, such as:&lt;/div&gt;
&lt;div class="line"&gt;- are there checks that security has been declared on every endpoint?&lt;/div&gt;
&lt;div class="line"&gt;- is there useful metadata, such as a license and some contact information?&lt;/div&gt;
&lt;div class="line"&gt;- does the API follow a design, such as RESTful API design, and how clear is that from the OpenAPI description?&lt;/div&gt;
&lt;div class="line"&gt;- are consistent data formats used, such as cents (or equivalent) for money, ISO format dates, standard scientific units for measures?&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Having some sort of API design, with some level of data handling, some supporting metadata, and some security practices in place is a level 1 API. Some APIs only need to be at level 1, such as a service with one or two endpoints that handles something non-critical such as newsletter signups. Sure, this API could be brought to a higher API standard and that could be enforced by tools and linting. But not every API needs the same level of investment or must comply with the same standards. Within an organisation, have a few defined levels, and make clear which APIs meet which standards.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="level-2-has-a-consistent-interface"&gt;
&lt;h3&gt;Level 2: Has a consistent interface&lt;/h3&gt;
&lt;p&gt;There are few things more irritating than an inconsistent API, and yet a deeply consistent API interface is essentially an invisible feature. Users will very quickly feel comfortable and be productive using your API platform, but not being irritating is rarely given as customer feedback! So what are the ingredients for this developer happiness level of API standard? You can think of it as having two main elements: the philosophy, and the mechanics.&lt;/p&gt;
&lt;p&gt;The mechanics is the simplest thing to get right. It means using plurals (or not), absolutely everywhere. Always using exactly the same variable name to refer to the same data, wherever it is used, and validating that data exactly the same way in both request and response for every endpoint. Having endpoints that are consistent in their shape and depth, and keeping common parameter patterns for features such as filtering or pagination the same everywhere. Adding linting rules to an existing API for this level of API standard can be quite tough, but add the rules you want to follow, with exceptions for any existing/legacy endpoints that aren't compliant. Enabling the checks on the API, and acknowledging the parts that are known and shouldn't fail the build, means you can automate the checks and add no more violations! The ignores list can also serve as a to-do list for things to work on, either by making backwards-compatible changes or to revisit if a new API version is in the works.&lt;/p&gt;
&lt;p&gt;The philosophy part is a bit more difficult to think about but building on prior art in your APIs, for example by following established RESTful API design practices very tightly and adopting other standards that are already known and accepted. This approach means you don't have too many decisions to make since you're following existing and established standards, and the ideas may be familiar/supported by the users/tools that access your API. I particularly recommend looking into &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Hypertext_Application_Language"&gt;Hypertext Application Language (HAL)&lt;/a&gt; if you include links in the resources you return from your API endpoints, and &lt;a class="reference external" href="https://www.rfc-editor.org/rfc/rfc9457.html"&gt;RFC 9457: Problem details for APIs&lt;/a&gt; (replaces the better-known RFC 7807).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="level-3-is-a-joy-to-use"&gt;
&lt;h3&gt;Level 3: Is a joy to use&lt;/h3&gt;
&lt;p&gt;We don't describe developer experiences as &amp;quot;joy&amp;quot; often enough, but a really great API description definitely qualifies. If your API is your flagship product, or drives a lot of business value that is realised by strong integrations - this is the level you want to aim at. What's funny about these top-level APIs is that their actual API design isn't much different to the earlier levels, however the implementation and supporting materials are the special sauce.&lt;/p&gt;
&lt;p&gt;To produce an API at this level, the API governance expands into a more holistic view of the API and what really makes an API experience joyful. This can be achieved with rich OpenAPI descriptions, and supporting documentation as the main pillars. I think there's also an element here of discoverability, and that this aspect will only become more important in the future.&lt;/p&gt;
&lt;p&gt;Enriching OpenAPI descriptions can take many forms. Make the most of the description fields; these support Markdown so don't be afraid to add links, lists, or whatever else would help the user. From the very top level, where &lt;tt class="docutils literal"&gt;info.description&lt;/tt&gt; sets the scene and purpose for the whole API, through the individual endpoint descriptions and down to the individual parameter and schema fields, the descriptions are the magic sauce. Explain not just what the thing &lt;em&gt;is&lt;/em&gt;, but also what it &lt;em&gt;does&lt;/em&gt;, and why the user would want to do that. Enrich also with meaningful and illustrative examples, using (culturally varied) example data that represents something a user really would be doing, rather than &amp;quot;foo&amp;quot; or &amp;quot;bar&amp;quot;. A project name could be &amp;quot;holiday_planning&amp;quot; and an email address &amp;quot;&lt;a class="reference external" href="mailto:email&amp;#64;example.com"&gt;email&amp;#64;example.com&lt;/a&gt;&amp;quot;, and using values like this does make a more successful and happier end user.&lt;/p&gt;
&lt;p&gt;The documentation is not just the reference documentation either. I haven't seen linting rules for this, so perhaps it's a little out of scope, but adding meaningful onboarding documentation or getting started guides, and tutorials for the most common tasks in an API are both excellent additions that make a good API into a great API.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="make-your-api-next-level"&gt;
&lt;h3&gt;Make your API next-level&lt;/h3&gt;
&lt;p&gt;Don't be put off by API standards tooling that comes with big opinions of its own. Reflecting on what your API needs, and what your organisation can support, will help you find which level you are on and where you want to be and set your API projects up for success. Tune the API linting rules to get the support you need, but don't be afraid to turn things off that aren't important to you! All the tools come with default rules, but every situation is different so take the time to adjust it for your context rather than gritting your teeth and dealing with someone else's ideals.&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="api"/><category term="openapi"/></entry><entry><title>Add OpenAI flags to OpenAPI descriptions</title><link href="https://lornajane-net.pages.dev/posts/2023/add-openai-consequential-to-openapi" rel="alternate"/><published>2023-11-28T09:48:00+00:00</published><updated>2023-11-28T09:48:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2023-11-28:/posts/2023/add-openai-consequential-to-openapi</id><summary type="html">&lt;p class="first"&gt;With &lt;a class="reference external" href="https://openai.com/"&gt;OpenAI's&lt;/a&gt; new &lt;a class="reference external" href="https://platform.openai.com/docs/actions"&gt;Actions feature&lt;/a&gt; allowing you to get a GPT that uses your own API, many new doors are open. However giving an AI an access token the keys to your API and telling it to have fun may lead you to realise that one of the doors available leads to the empty lift shaft of overwritten data, or the bottomless well of cloud bill shock. To reduce the risks, OpenAI supports an extension &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;x-openai-isConsequential&lt;/span&gt;&lt;/tt&gt; that you can add to your OpenAPI description, to indicate which endpoints should not be called without a human confirmation step.&lt;/p&gt;
&lt;p class="last"&gt;I'm not doing much with OpenAI right now, but I do plenty with OpenAPI and the question &amp;quot;how do I add this field to my existing API description?&amp;quot; is one that I can answer! What's more, you can use the advice in this post to add other extensions or additions to your OpenAPI descriptions using &lt;a class="reference external" href="https://github.com/OAI/Overlay-Specification"&gt;Overlays&lt;/a&gt;, this advice isn't OpenAI-specific, but it's used in the examples.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;With &lt;a class="reference external" href="https://openai.com/"&gt;OpenAI's&lt;/a&gt; new &lt;a class="reference external" href="https://platform.openai.com/docs/actions"&gt;Actions feature&lt;/a&gt; allowing you to get a GPT that uses your own API, many new doors are open. However giving an AI an access token the keys to your API and telling it to have fun may lead you to realise that one of the doors available leads to the empty lift shaft of overwritten data, or the bottomless well of cloud bill shock. To reduce the risks, OpenAI supports an extension &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;x-openai-isConsequential&lt;/span&gt;&lt;/tt&gt; that you can add to your OpenAPI description, to indicate which endpoints should not be called without a human confirmation step.&lt;/p&gt;
&lt;p&gt;I'm not doing much with OpenAI right now, but I do plenty with OpenAPI and the question &amp;quot;how do I add this field to my existing API description?&amp;quot; is one that I can answer! What's more, you can use the advice in this post to add other extensions or additions to your OpenAPI descriptions using &lt;a class="reference external" href="https://github.com/OAI/Overlay-Specification"&gt;Overlays&lt;/a&gt;, this advice isn't OpenAI-specific, but it's used in the examples.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(is anyone else confused by the OpenAI vs OpenAPI similarity yet? I am ....)&lt;/em&gt;&lt;/p&gt;
&lt;div class="section" id="openapi-overlays"&gt;
&lt;h3&gt;OpenAPI Overlays&lt;/h3&gt;
&lt;p&gt;The OpenAPI Initiative has a group working on a feature called Overlays which solves exactly this problem, where the OpenAPI description exists, but needs some tweaks. Sadly, not very many tools support this yet - the ones I know of are &lt;a class="reference external" href="https://github.com/lornajane/openapi-overlays-js"&gt;my overlays-js library&lt;/a&gt; that I'll use for the examples in this post, and &lt;a class="reference external" href="https://github.com/speakeasy-api/speakeasy/blob/main/docs/overlay/README.md"&gt;Speakeasy CLI&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="overlay-example"&gt;
&lt;h3&gt;Overlay Example&lt;/h3&gt;
&lt;p&gt;This example shows an Overlay that adds the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;x-openai-isConsequential&lt;/span&gt;&lt;/tt&gt; field to all &lt;tt class="docutils literal"&gt;POST&lt;/tt&gt; endpoints:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;overlay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;1.0.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Add OpenAPI annotations&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;1.0.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$.paths.*.post&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;x-openai-isConsequential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;target&lt;/tt&gt; field accepts JSONPath, so edit this expression as required to capture the endpoints you want to flag. Note also that you can add as many &lt;tt class="docutils literal"&gt;action&lt;/tt&gt; steps as you need to, so if it's simpler to add specific targets rather than trying to JSONPath to the right subset, that is an option! (there's a bug with the overlays library that means structured overlays don't work well at this moment, so use targeted overlays).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="use-overlayjs-to-update-the-api-description"&gt;
&lt;h3&gt;Use &lt;tt class="docutils literal"&gt;overlayjs&lt;/tt&gt; to update the API description&lt;/h3&gt;
&lt;p&gt;Check the &lt;a class="reference external" href="https://github.com/lornajane/openapi-overlays-js#installation"&gt;installation instructions&lt;/a&gt; to get the tool installed if you don't have it already.&lt;/p&gt;
&lt;p&gt;Run the command and supply the API description as &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--openapi&lt;/span&gt;&lt;/tt&gt; and the overlay as &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--overlay&lt;/span&gt;&lt;/tt&gt;. For example, the following command applies &lt;tt class="docutils literal"&gt;overlay.js&lt;/tt&gt; to &lt;tt class="docutils literal"&gt;openapi.js&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code text literal-block"&gt;
overlayjs --openapi openapi.yaml --overlay overlay.js &amp;gt; new-openapi.yaml
&lt;/pre&gt;
&lt;p&gt;The command writes to &lt;tt class="docutils literal"&gt;stdout&lt;/tt&gt;, so you can direct the output wherever you would like it to go. In my case, I've directed it to a file called &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;new-openapi.yaml&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Take a look at the updated OpenAPI description, or diff it against the original, and you should see that the post endpoints now have the OpenAI &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;x-openai-isConsequential:&lt;/span&gt; true&lt;/tt&gt; added.&lt;/p&gt;
&lt;p&gt;By using OpenAPI Overlays, you get a standard format and a choice of tools to apply it with. You can prepare an OpenAPI file for use with OpenAI, even if you are an API consumer and have no input into the way that the OpenAPI is created - or if you just want to avoid cluttering other stages of your API lifecycle with the metadata specific to the OpenAI use case. Hopefully this helped you get your OpenAPI in shape for OpenAI (yes, still confusing to have such similar words!), I'd be happy to hear how you get on if you want to share in the comments.&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="api"/><category term="openai"/><category term="openapi"/><category term="overlayjs"/><category term="overlays"/><category term="tips"/></entry><entry><title>When to version bump your OpenAPI description</title><link href="https://lornajane-net.pages.dev/posts/2023/when-to-version-bump-your-openapi-description" rel="alternate"/><published>2023-11-20T13:10:00+00:00</published><updated>2023-11-20T13:10:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2023-11-20:/posts/2023/when-to-version-bump-your-openapi-description</id><summary type="html">&lt;p class="first last"&gt;I've heard a variety of bad advice (and some untruths!) regarding how to version your OpenAPI description lately. Rather than dig into those, instead I'll share my opinion on how I like to version API descriptions in my own projects. Every API project is different, but perhaps there's something here that can help your project too.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've heard a variety of bad advice (and some untruths!) regarding how to version your OpenAPI description lately. Rather than dig into those, instead I'll share my opinion on how I like to version API descriptions in my own projects. Every API project is different, but perhaps there's something here that can help your project too.&lt;/p&gt;
&lt;div class="section" id="version-your-api-descriptions"&gt;
&lt;h3&gt;Version your API Descriptions&lt;/h3&gt;
&lt;p&gt;Let's start with: please, please do use the version field, and update it from time to time. In OpenAPI, the version of the description is in the &lt;tt class="docutils literal"&gt;info.version&lt;/tt&gt; field and the &lt;a class="reference external" href="https://spec.openapis.org/oas/latest.html#info-object"&gt;official documentation&lt;/a&gt; describes it as:&lt;/p&gt;
&lt;blockquote&gt;
REQUIRED. The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API implementation version).&lt;/blockquote&gt;
&lt;p&gt;To break this down, the documentation describes what the version field is, and also two things that it is not:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;it &lt;strong&gt;is&lt;/strong&gt; the version of the OpenAPI document.&lt;/li&gt;
&lt;li&gt;it &lt;strong&gt;is not&lt;/strong&gt; the version of OpenAPI specification being used (this is in the top-level &lt;tt class="docutils literal"&gt;openapi&lt;/tt&gt; field).&lt;/li&gt;
&lt;li&gt;it &lt;strong&gt;is not&lt;/strong&gt; the version of the API that it relates to (many descriptions cover multiple API versions, and may also change without the API changing).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So now we've cleared this up for OpenAPI, and pretty much everything here applies to AsyncAPI as well, let's think about when the version should change.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="when-to-update-the-api-description-s-version-field"&gt;
&lt;h3&gt;When to update the API description's version field&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; when the API description changes, update the version field.&lt;/p&gt;
&lt;p&gt;The whole point of a version field is so that I can tell if I have the most recent version of your API description, or which version something used. So if the two things aren't the same, then probably their versions shouldn't be the same either.&lt;/p&gt;
&lt;p&gt;Some things that should probably cause a version change:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;a new endpoint was added to the API, or something was marked as deprecated&lt;/li&gt;
&lt;li&gt;the description was updated to include improved summaries, descriptions, tags or examples&lt;/li&gt;
&lt;li&gt;the description was restructured&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having a process that publishes a new OpenAPI description with an updated version is something you'll want to be able to do easily and regularly, so make it painless to do.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A note on version formats:&lt;/strong&gt; OpenAPI only recommends a string. Many organisations do use a &lt;a class="reference external" href="https://semver.org/"&gt;SemVer&lt;/a&gt; approach, and this is well understood by technical audiences, but choose something that fits you. Take a leaf out of Stripe's book and &lt;a class="reference external" href="https://stripe.com/docs/api/versioning"&gt;use dates for versions&lt;/a&gt;, for example. Whatever you choose, don't get bogged down in what does or does not require a new release. Make new releases easy and aim for releasing too often rather than too infrequently!&lt;/p&gt;
&lt;p&gt;This quick roundup on API versioning was the result of having this conversation several times over the last few weeks/months, with people all over the API ecosystem. We talk a lot about API versioning, but not nearly enough about API description versioning, so I wanted to share some thoughts and most of all encourage everyone to version their API descriptions (at all! With any sort of version!) and to update those version fields from time to time :)&lt;/p&gt;
&lt;p&gt;Are there any organisations or APIs you've seen that do this particularly well (or badly, if you can be constructive about why it's not working)? I'd be interested in seeing comments pointing to more examples of what you have spotted in the wild.&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="api"/><category term="apidesign"/><category term="asyncapi"/><category term="openapi"/><category term="tips"/></entry><entry><title>API docs previews with Redoc and Netlify</title><link href="https://lornajane-net.pages.dev/posts/2023/api-docs-previews-with-redoc-and-netlify" rel="alternate"/><published>2023-11-08T09:18:00+00:00</published><updated>2023-11-08T09:18:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2023-11-08:/posts/2023/api-docs-previews-with-redoc-and-netlify</id><summary type="html">&lt;p class="first last"&gt;I talk to a lot of people about their API lifecycles and processes, and one thing that has value in almost every case is immediate documentation preview for all updates to an &lt;a class="reference external" href="https://openapis.org"&gt;OpenAPI&lt;/a&gt; description. The time it takes to set it up (about ten minutes now I know what to do) is an investment that makes every future change easier to propose, review, and accept. Here's my recipe for open source OpenAPI documentation using &lt;a class="reference external" href="https://github.com/redocly/redoc"&gt;Redoc&lt;/a&gt; (disclaimer: I work for Redocly, who publish Redoc) with the files pushed to &lt;a class="reference external" href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt; for hosting (but it's HTML, so really any hosting will do and you should feel free to adapt the instructions for your preferred platform, and then share them in the comments).&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I talk to a lot of people about their API lifecycles and processes, and one thing that has value in almost every case is immediate documentation preview for all updates to an &lt;a class="reference external" href="https://openapis.org"&gt;OpenAPI&lt;/a&gt; description. The time it takes to set it up (about ten minutes now I know what to do) is an investment that makes every future change easier to propose, review, and accept. Here's my recipe for open source OpenAPI documentation using &lt;a class="reference external" href="https://github.com/redocly/redoc"&gt;Redoc&lt;/a&gt; (disclaimer: I work for Redocly, who publish Redoc) with the files pushed to &lt;a class="reference external" href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt; for hosting (but it's HTML, so really any hosting will do and you should feel free to adapt the instructions for your preferred platform, and then share them in the comments).&lt;/p&gt;
&lt;div class="section" id="openapi-in-a-github-repo"&gt;
&lt;h3&gt;OpenAPI in a GitHub repo&lt;/h3&gt;
&lt;p&gt;API documentation using OpenAPI assumes a docs-as-code workflow, and so does this post. You should have an OpenAPI description in a GitHub repository, so that when the description changes, the documentation can update. The big win here is that by adding preview builds for all pull requests, incoming changes can easily be shared and reviewed by all stakeholders, without any need to read YAML.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; there's no need to bundle the OpenAPI description to a single file, Redoc will follow references across files as needed so this approach is ideal for the during-development-and-review phase of the process.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configure-the-netlify-project"&gt;
&lt;h3&gt;Configure the Netlify project&lt;/h3&gt;
&lt;p&gt;The basic process, for Netlify or any equivalent, is to link the GitHub project to a deployable project so that updates run every time the repository updates. Netlify needs to know how to build the project, which in this case is generating the documentation as HTML, and what to publish. There's a setting for &amp;quot;do this for pull requests as well&amp;quot; which is what makes the whole thing seem like magic!&lt;/p&gt;
&lt;p&gt;Get started with the &amp;quot;Add new site&amp;quot; option and choose &amp;quot;Deploy with GitHub&amp;quot;. You may need to link your Netlify account to your GitHub account and/or the project you want to use if you haven't used these tools together before or if you grant access on a per-project basis. Once that's done, pick the project to generate API documentation for.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;The next screen sets the site configuration. My project has &lt;tt class="docutils literal"&gt;openapi.yaml&lt;/tt&gt; in the root folder, and my settings are like this:&lt;/div&gt;
&lt;div class="line"&gt;* The &lt;strong&gt;build command&lt;/strong&gt; is &lt;tt class="docutils literal"&gt;npx &amp;#64;redocly/cli &lt;span class="pre"&gt;build-docs&lt;/span&gt; openapi.yaml &lt;span class="pre"&gt;-o&lt;/span&gt; build/index.html&lt;/tt&gt;.&lt;/div&gt;
&lt;div class="line"&gt;* And &lt;strong&gt;publish directory&lt;/strong&gt; is set to &lt;tt class="docutils literal"&gt;build/&lt;/tt&gt; since that's where the previous command writes the files to.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2023/11/Screenshot-from-2023-11-04-21-34-04.png"&gt;&lt;img alt="Screenshot of the settings page, showing the build command and publish directory as described." class="aligncenter size-large wp-image-4666" src="https://lornajane-net.pages.dev/images/2023/11/Screenshot-from-2023-11-04-21-34-04-1024x320.png" style="width: 584px; height: 183px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Save the settings and the project will (hopefully) deploy successfully - don't look away, it's fast!&lt;/p&gt;
&lt;p&gt;I'm not sure when Netlify started enabling preview deploys by default but that's the behaviour in my most recent new site. Test your own setup by making a small but identifiable change to your API description, committing the change to a branch, and opening a pull request. After a few minutes the Netlify bot should comment with a link to the preview of the updated documentation.&lt;/p&gt;
&lt;p&gt;All in all, the time investment to get a basic documentation rendering is definitely worth it (and if you need something more then I think I am contractually obliged to suggest you check out &lt;a class="reference external" href="https://redocly.com"&gt;Redocly&lt;/a&gt; for the rest of the products). Investing in every stage of your API pipeline, from linting, to enrichment, and to docs - delivers rewards. And delivers them every time you make an API description change!&lt;/p&gt;
&lt;p&gt;Hopefully you found this useful, I'd love to hear if you set this up yourself, or if you can share equivalent setups for other platforms so that others can learn from your wisdom: add a comment!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="api"/><category term="docs"/><category term="docs-as-code"/><category term="netlify"/></entry><entry><title>Selective prose linting with Vale's glob switch</title><link href="https://lornajane-net.pages.dev/posts/2023/selective-prose-linting-with-vales-glob-switch" rel="alternate"/><published>2023-08-23T11:55:00+01:00</published><updated>2023-08-23T11:55:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2023-08-23:/posts/2023/selective-prose-linting-with-vales-glob-switch</id><summary type="html">&lt;p class="first"&gt;&lt;a class="reference external" href="https://vale.sh/"&gt;Vale&lt;/a&gt; is an open source CLI tool for linting prose (if you're not linting prose yet, why not? I never want to go back). I typically use it for everything, locally and in CI, and it's especially valuable now I'm working for a company that uses US English. You'd think that most of the challenges would be caused by the majority of non-native English language contributors? Nope, most of the chaos comes from the token Brit :)&lt;/p&gt;
&lt;p class="last"&gt;I ran into one challenge though where I didn't want to enable Vale for everything: a large internal documentation repo. This is the catch-all of things we should share with one another and like most internal company documentation sites, there is a lot going on. I assembled a very minimal set of Vale rules and still the first pass netted me over 10k errors. Luckily, Vale has a &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--glob&lt;/span&gt;&lt;/tt&gt; option, but it took me a while to find how to exclude multiple files and directories using it, so here's the example for future-me, and anyone else who needs to see it!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://vale.sh/"&gt;Vale&lt;/a&gt; is an open source CLI tool for linting prose (if you're not linting prose yet, why not? I never want to go back). I typically use it for everything, locally and in CI, and it's especially valuable now I'm working for a company that uses US English. You'd think that most of the challenges would be caused by the majority of non-native English language contributors? Nope, most of the chaos comes from the token Brit :)&lt;/p&gt;
&lt;p&gt;I ran into one challenge though where I didn't want to enable Vale for everything: a large internal documentation repo. This is the catch-all of things we should share with one another and like most internal company documentation sites, there is a lot going on. I assembled a very minimal set of Vale rules and still the first pass netted me over 10k errors. Luckily, Vale has a &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--glob&lt;/span&gt;&lt;/tt&gt; option, but it took me a while to find how to exclude multiple files and directories using it, so here's the example for future-me, and anyone else who needs to see it!&lt;/p&gt;
&lt;p&gt;Most of Vale's configuration uses a &lt;tt class="docutils literal"&gt;.vale.ini&lt;/tt&gt; file, but you specify the files and directories to check at the time you run the command, with something like:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
vale docs/
&lt;/pre&gt;
&lt;p&gt;In my case, I needed to exclude a mix of folders (there's little value in checking/fixing a meeting notes collection of a past project, and we can't make changes to our legal docs. Also there's a page of good restaurants near the office that is full of proper nouns!). To include just one section or another, I could run Vale multiple times to check each section, but to run everything &lt;em&gt;except&lt;/em&gt; some problem areas, I used &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--glob&lt;/span&gt;&lt;/tt&gt;. I ended up with a command like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
vale --glob='!{office/where-to-eat.md,legal/*,projects/codename/meetings/*}
&lt;/pre&gt;
&lt;p&gt;Some of the skipped directories we will probably work on improving and then dropping the exception for - but this approach is useful when retrofitting a tool like Vale to an existing repository which can't be made to align all at once. We're also using a pretty reduced ruleset and may incrementally add to this over time.&lt;/p&gt;
&lt;div class="section" id="glob-in-github-actions"&gt;
&lt;h3&gt;Glob in GitHub Actions&lt;/h3&gt;
&lt;p&gt;Probably all the CI integrations have something similar but &lt;a class="reference external" href="https://github.com/errata-ai/vale-action"&gt;Vale has a GitHub Action&lt;/a&gt; and I could add the whole &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--glob&lt;/span&gt;&lt;/tt&gt; expression to a &lt;tt class="docutils literal"&gt;vale_flags&lt;/tt&gt; entry in the &lt;tt class="docutils literal"&gt;with&lt;/tt&gt; step.&lt;/p&gt;
&lt;p&gt;Ideally we'd be running identical commands and configuration both locally and in CI, but since the configuration for this doesn't seem to be able to go in the Vale config file, I compromised on adding it as an npm script as well as a GitHub action. It'll be annoying to maintain but I didn't come up with any better ideas!&lt;/p&gt;
&lt;p&gt;Hopefully this post will help me next time I'm wondering how to combine glob expressions for Vale, since I didn't find many other examples. If it helps you do, then that's even better! Share your tips in the comments :)&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="docs-as-code"/><category term="tools"/></entry><entry><title>Visualise OpenAPI with Blockly</title><link href="https://lornajane-net.pages.dev/posts/2023/visualise-openapi-with-blockly" rel="alternate"/><published>2023-06-20T12:57:00+01:00</published><updated>2023-06-20T12:57:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2023-06-20:/posts/2023/visualise-openapi-with-blockly</id><summary type="html">&lt;p class="first"&gt;Giving talks about OpenAPI often means lots and lots of slides of YAML. In fact, one of my talks even contains a &amp;quot;warning, contains yaml&amp;quot; slide! If you aren't a professional yaml developer, it can be difficult to digest and learn from, and so I wanted to do better. The various elements are nested inside one another, and so I had this idea to build a representation of OpenAPI using &lt;a class="reference external" href="https://developers.google.com/blockly/"&gt;Blockly&lt;/a&gt; (the tooling also used for &lt;a class="reference external" href="https://scratch.mit.edu/"&gt;Scratch&lt;/a&gt;, if you've seen that).&lt;/p&gt;
&lt;p class="last"&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2023/06/openapi-tags-block.png"&gt;&lt;img alt="Showing tags with descriptions" class="aligncenter size-medium wp-image-4624" src="https://lornajane-net.pages.dev/images/2023/06/openapi-tags-block-300x128.png" style="width: 300px; height: 128px;" /&gt;&lt;/a&gt;&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Giving talks about OpenAPI often means lots and lots of slides of YAML. In fact, one of my talks even contains a &amp;quot;warning, contains yaml&amp;quot; slide! If you aren't a professional yaml developer, it can be difficult to digest and learn from, and so I wanted to do better. The various elements are nested inside one another, and so I had this idea to build a representation of OpenAPI using &lt;a class="reference external" href="https://developers.google.com/blockly/"&gt;Blockly&lt;/a&gt; (the tooling also used for &lt;a class="reference external" href="https://scratch.mit.edu/"&gt;Scratch&lt;/a&gt;, if you've seen that).&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2023/06/openapi-tags-block.png"&gt;&lt;img alt="Showing tags with descriptions" class="aligncenter size-medium wp-image-4624" src="https://lornajane-net.pages.dev/images/2023/06/openapi-tags-block-300x128.png" style="width: 300px; height: 128px;" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The hardest part was (as always!) getting started with an unfamiliar toolchain, knowing what to edit, how to serve the file, and if there was an extra step needed to see changes. The developer documentation for Blockly is very good, and I tried to follow their guidelines as much as I could. I ended up writing a messy web page that had a textarea that I could use to import/export what I'm working on, and writing it all to local browser storage - because every time I changed the toolbox, I lost the data structure I had just built!&lt;/p&gt;
&lt;p&gt;It's not a complete library, but it did allow me to create the visuals I wanted for my talk &amp;quot;OpenAPI for Documentarians&amp;quot; at APItheDocs in Amsterdam, and I think it's likely that I'll expand and use this again. I got positive feedback from trusted audience members, and a few people told me they found it &amp;quot;approachable&amp;quot; - which is exactly what I was aiming for.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2023/06/openapi-path-single-block.png"&gt;&lt;img alt="A single endpoint represented with blocks" class="aligncenter size-full wp-image-4626" src="https://lornajane-net.pages.dev/images/2023/06/openapi-path-single-block.png" style="width: 883px; height: 488px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I've published the whole project &lt;a class="reference external" href="https://github.com/lornajane/openapi-blockly-representation"&gt;on GitHub&lt;/a&gt;, and I'd be happy to have forks, contributions, suggestions or feedback on this. There are places where I think there's probably better ways to represent some elements, for example. Also the idea of Blockly is that it's a high level language that can then be compiled to something for the machines when the humans have finished with it. I'm not sure if it's even possible to write the converters for it to output OpenAPI but that would be a really fun project if anyone is up for it :)&lt;/p&gt;
&lt;p&gt;If you use it, I'd love to hear about it - drop me a comment!&lt;/p&gt;
</content><category term="tech"/><category term="api"/><category term="github"/><category term="openapi"/></entry><entry><title>Tips for better documentation with OpenAPI</title><link href="https://lornajane-net.pages.dev/posts/2023/tips-for-better-documentation-with-openapi" rel="alternate"/><published>2023-06-12T13:30:00+01:00</published><updated>2023-06-12T13:30:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2023-06-12:/posts/2023/tips-for-better-documentation-with-openapi</id><summary type="html">&lt;p class="first last"&gt;I love documentation, which is supposed to be rare for software engineers. I am pretty sure that loving documentation is actually very common, but not very cool and so few people admit to it! I love reading good docs, and so I love writing them too. Between hanging out in the &lt;a class="reference external" href="https://www.writethedocs.org/"&gt;Write the Docs&lt;/a&gt; community, and recently being at the &lt;a class="reference external" href="https://apithedocs.org/amsterdam-2023"&gt;APItheDocs conference&lt;/a&gt;, I've answered a bunch of questions about getting good documentation from an OpenAPI document, so I thought I'd round them up in case it's useful.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I love documentation, which is supposed to be rare for software engineers. I am pretty sure that loving documentation is actually very common, but not very cool and so few people admit to it! I love reading good docs, and so I love writing them too. Between hanging out in the &lt;a class="reference external" href="https://www.writethedocs.org/"&gt;Write the Docs&lt;/a&gt; community, and recently being at the &lt;a class="reference external" href="https://apithedocs.org/amsterdam-2023"&gt;APItheDocs conference&lt;/a&gt;, I've answered a bunch of questions about getting good documentation from an OpenAPI document, so I thought I'd round them up in case it's useful.&lt;/p&gt;
&lt;div class="section" id="summary-vs-description"&gt;
&lt;h3&gt;Summary vs description&lt;/h3&gt;
&lt;p&gt;OpenAPI supports both &lt;tt class="docutils literal"&gt;summary&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;description&lt;/tt&gt; fields and they're both strings that contain information about the thing they are attached to, such as a parameter, endpoint, schema, or response. So which do you use?&lt;/p&gt;
&lt;p&gt;Summary is a plain text field, and should typically be shorter: just one or two sentences. It's often used when listing elements, although different tools render different tools differently, so always check what your tools product and do whatever looks right there.&lt;/p&gt;
&lt;p&gt;Description fields support markdown, so this presents an opportunity to link to additional information or documentation. Particularly in the top-level &lt;tt class="docutils literal"&gt;info.description&lt;/tt&gt; field, this can be an opportunity to write multi-paragraph detailed overview documentation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="good-description-fields-give-context"&gt;
&lt;h3&gt;Good description fields give context&lt;/h3&gt;
&lt;p&gt;It's very common to see a description for the &lt;tt class="docutils literal"&gt;user_id&lt;/tt&gt; field which says something like &amp;quot;the ID of the user&amp;quot;. Um, thanks?&lt;/p&gt;
&lt;p&gt;Everyone can do better than this - and if you've ever had to read API documentation from someone else, you know how much difference it makes. I advise people to ask themselves WHAT this field/endpoint/whatever does, and WHY it's needed. Think about what the user is doing when they use this endpoint, and give a bit more context. Something like &amp;quot;the ID of the user placing the order. This value can be found in the order detail object or order status update object. Find the current user's ID by going to /user/me`. You get the idea. Context is everything!&lt;/p&gt;
&lt;p&gt;Taking pride in API documentation is a Developer Experience multiplier; just make sure that everyone working on your API documentation knows how much you value the extra effort in this area, and that it is something that collaborators should spend time on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="simplest-styleguide"&gt;
&lt;h3&gt;Simplest styleguide&lt;/h3&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Writing brilliantly is absolutely an art, but writing well enough is attainable for everyone. My advice is always to keep it simple and informative.&lt;/div&gt;
&lt;div class="line"&gt;* Use short sentences&lt;/div&gt;
&lt;div class="line"&gt;* Use present tense (the endpoint &amp;quot;does&amp;quot; something, rather than &amp;quot;will do&amp;quot; the thing)&lt;/div&gt;
&lt;div class="line"&gt;* If you can remove extra phrases, such as &amp;quot;in this case&amp;quot;, do so. It keeps things easier to read&lt;/div&gt;
&lt;div class="line"&gt;* Be kind. Keep things gender-neutral and inclusive, and avoid belittling words such as &amp;quot;simply&amp;quot; and &amp;quot;just&amp;quot;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;With these in mind, you won't go far wrong. Engineers can absolutely be brilliant at this without formal training.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="markdown-in-yaml"&gt;
&lt;h3&gt;Markdown in YAML&lt;/h3&gt;
&lt;p&gt;Description fields support markdown, which is brilliant, and allows expressive additions if you need them. If you use YAML for OpenAPI documents, there are a couple of additional operators that can make it much easier to work with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Folded block&lt;/strong&gt; starts with &lt;tt class="docutils literal"&gt;&amp;gt;&lt;/tt&gt;. This is great if you just want to wrap your content to make it manageable, or compatible with the linters/formatters you use on the file. I quite like to use a one sentence per line format for markdown, because it makes diffs more readable, and this works nicely with that approach.&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p-Indicator"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;This is a folded block style in YAML.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;It preserves line breaks but ignores leading&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;and trailing white spaces.&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Literal block&lt;/strong&gt; starts with &lt;tt class="docutils literal"&gt;|&lt;/tt&gt;. Use this approach if you have markdown that uses whitespace, such as titles or bullet points.&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p-Indicator"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;# Title&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="no"&gt;This is some Markdown content.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;It can include multiple paragraphs,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;lists, headers, and other Markdown syntax.&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Using the appropriate markdown syntax can make things much easier and maintainable over time.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="link-to-external-documentation-from-api-reference"&gt;
&lt;h3&gt;Link to external documentation from API reference&lt;/h3&gt;
&lt;p&gt;I don't often see the &lt;tt class="docutils literal"&gt;`externalDocs&lt;/tt&gt; &amp;lt;&lt;a class="reference external" href="https://spec.openapis.org/oas/v3.1.0#external-documentation-object"&gt;https://spec.openapis.org/oas/v3.1.0#external-documentation-object&lt;/a&gt;&amp;gt;`__ used but it's a top-level element in OpenAPI that is designed to make the main API documentation (landing pages, quickstarts, tutorials, etc) easily findable from the API reference documentation. Additionally, OpenAPI tags, schemas and operations all also support &lt;tt class="docutils literal"&gt;externalDocs&lt;/tt&gt; properties, so you can link directly to detailed documentation for any of those elements too. Especially useful when there's a lot of information to include, that can be unwieldy in the description fields.&lt;/p&gt;
&lt;p&gt;What are you tips for getting the best documentation from OpenAPI? I'm working full time in this space now (at &lt;a class="reference external" href="https://redocly.com"&gt;Redocly&lt;/a&gt;, and really enjoying having lots of conversations about docs, APIs, and everything in between!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="api"/><category term="apithedocs"/><category term="docs"/><category term="openapi"/></entry><entry><title>Raising the Next Generation of DevRel</title><link href="https://lornajane-net.pages.dev/posts/2023/raising-the-next-generation-of-devrel" rel="alternate"/><published>2023-03-08T20:42:00+00:00</published><updated>2023-03-08T20:42:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2023-03-08:/posts/2023/raising-the-next-generation-of-devrel</id><summary type="html">&lt;p class="first"&gt;Developer Relations isn't new, but it's also broad, ill-defined, and constantly evolving. It's difficult to keep your own skills up to date, never mind coping with a whole team full of people and constantly creeping industry scope. The required combination of specialist and generalist skills to be successful is any of the Developer Relations and allied roles is extensive, and it's easy to feel overwhelmed and end up coasting being just-good-enough at things but never feeling mastery.&lt;/p&gt;
&lt;p class="last"&gt;As an individual contributor, manager and general DevRel presence over the years, I've developed some tactics both for myself and my team, and I'd like to share them in case they're useful to others. Also since I'm moving to a less DevRel role, writing this down while I can still think about it!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Developer Relations isn't new, but it's also broad, ill-defined, and constantly evolving. It's difficult to keep your own skills up to date, never mind coping with a whole team full of people and constantly creeping industry scope. The required combination of specialist and generalist skills to be successful is any of the Developer Relations and allied roles is extensive, and it's easy to feel overwhelmed and end up coasting being just-good-enough at things but never feeling mastery.&lt;/p&gt;
&lt;p&gt;As an individual contributor, manager and general DevRel presence over the years, I've developed some tactics both for myself and my team, and I'd like to share them in case they're useful to others. Also since I'm moving to a less DevRel role, writing this down while I can still think about it!&lt;/p&gt;
&lt;div class="section" id="the-monumental-skillset"&gt;
&lt;h3&gt;The Monumental Skillset&lt;/h3&gt;
&lt;p&gt;When I was in my first ever round of interviews for a Developer Advocate role, I asked the interviewer &amp;quot;What is this role on the way to? I love Software Engineering, but if I become a Developer Advocate next, where does that lead me?&amp;quot;. He thought for a while and then said &amp;quot;CTO. I've seen a couple of very good DevRel people do that and the only skillset this weird belongs to a founder&amp;quot;.&lt;/p&gt;
&lt;p&gt;That phrase stayed with me &amp;quot;skillset this weird&amp;quot;; it's funny because it's true. Developer Relations professionals need to understand or maybe even perform the job of their target audience. We need to communicate in writing as well as the documentarians, operate social accounts like the PR specialists, deliver technical content like a lecturer or keynote speaker - and probably video stream as well. We need to be able to juggle multiple competing projects, and often complex travel arrangements, and be publicly available, polite and positive, online and IRL, at all times. That's three separate career categories right there, so how do you gain all those skills, and develop them in meaningful ways? And when you change jobs and need to switch out to a new tech stack and a different subset of communication and workplace skills, then what?&lt;/p&gt;
&lt;p&gt;The first thing to recognise is that many roles need some selection of these skills, but not all of them are equally important in each role. Your team may already do a skills matrix to help you understand what skills are most important in your role, or how the skills are spread across the team members, and this can be useful. If you're doing this with a team for the first time, keep it simple. Mine usually look something like this (I'm resisting the urge to go on a tangent but let me know if a post about this would be useful):&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2023/03/skills-matrix.png"&gt;&lt;img alt="Spreadsheet with skills in the left hand column: HTML, SQL, Media training, Product list, Speaking. And names along the top: Alice Bob Charlie. Each cell has a colourful number 1-5 in it" class="aligncenter size-large wp-image-4590" src="https://lornajane-net.pages.dev/images/2023/03/skills-matrix-1024x305.png" style="width: 584px; height: 174px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The desired skills and level of those skills will change between organisations, between roles, maybe even within one role over time - that's expected. What you need to do is identify something that you could get better at. Then it's time to make a plan.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="self-help-for-developer-relations-professionals"&gt;
&lt;h3&gt;Self Help for Developer Relations Professionals&lt;/h3&gt;
&lt;p&gt;This career isn't well-defined. There aren't many well-recognised certifications or exams to take, and in many ways as an industry we're still working out what would be considered good practice in any particular situation. By picking one skill to work on, you're essentially putting a better tool in the toolbox that you take with you and open when you need to tackle any challenge.&lt;/p&gt;
&lt;p&gt;In the absence of a single source of documentation or other truth, we become our own teachers, but luckily there are so many good resources out there that the real challenge might be choosing where to start! Some topics can be studied in an afternoon; if you need to prepare to host the regular webinar on StreamYard then their own docs, a well-presented example webinar and some time spent pressing all the buttons and checking the output is probably all you need.&lt;/p&gt;
&lt;p&gt;Other skills or topics may take a little longer, especially where the scope is large; for example public speaking is an apprenticeship that takes a lifetime! Along the way I have: read about ten books and many online articles about various aspects of the craft, given a few hundred talks with a few thousand hours of private rehearsal, watched those talks on video where they were recorded, received a lot of talk feedback, watched a thousand talks by other speakers, helped a few dozen other speakers to prepare and deliver their talks, become quite a connoiseur of talk abstracts for different types of event, spent two years as a regular Toastmaster attendee - and come away with the impression that there really is a lot still to learn.&lt;/p&gt;
&lt;p&gt;What's common to both of those learning journeys is the mix of study, activity, and advice that brings improvement in each area. Taking input from different people, in different formats, and crucially practising those skills and reflecting on your progress, is what drives you forward and also consolidates what you learn.&lt;/p&gt;
&lt;p&gt;Identifying resources that can give you what you need next can be difficult, the Internet is a big place. Set up a feed reader and follow the writings of people you admire in your area of interest (I started putting some suggestions here, but frankly it got out of hand and should be a separate post). You can also look at the archives for curated resources lists such as &lt;a class="reference external" href="https://devrelweekly.com/"&gt;DevRel Weekly&lt;/a&gt; to find things on specific topics or get to know some individuals to follow.&lt;/p&gt;
&lt;p&gt;When it comes to the hands-on element, there's an idea called &amp;quot;deliberate practice&amp;quot; that expands on research showing that the quality of someone's practice is what dictates their skill improvements. Be intentional with learning, with looking for opportunities to use those skills, preparing well for the opportunities, and in reflecting on how things went afterwards.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="multiply-the-growth"&gt;
&lt;h3&gt;Multiply the growth&lt;/h3&gt;
&lt;p&gt;So far, so personal and professional development. What about the next generation? Well, &lt;strong&gt;in DevRel, we are all the next generation&lt;/strong&gt;. There are people just joining, leaving, transitioning in sideways, and others doing the same role with a different title - and all of them are an important part of your story, just as you are of theirs. There is no &amp;quot;still doing it the old way&amp;quot; here, all of us keep on learning, evolving, adapting, improving.&lt;/p&gt;
&lt;p&gt;If you have experience in the field, then you can raise the next generation by sharing your skills and knowledge with the individuals behind you. This is super valuable and could have been a blog post by itself - but this is a different post.&lt;/p&gt;
&lt;p&gt;The real scaling of the DevRel skill standards comes from us being intentional in the way that we lean on one another to learn. You are probably familiar with all the techniques here, but consider how you might apply them to the next thing you want to learn, with the people you have around you. Here are some ideas and when to use them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;A study group or book club&lt;/strong&gt;: This is really effective when there's a well-defined knowledge need (rather than a skills one), and you're all at a similiar level to start with, so it's a group with a shared mission. An example of when this could be useful: working through the onboarding guide to a new reporting tool that you're going to be adopting at your organisation.&lt;/p&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Shadow an expert&lt;/strong&gt;: Borrowed from medical teaching practices, where famously they &amp;quot;See one, do one, teach one&amp;quot; to learn a procedure. This approach is ideal when you have an expert who is prepared to work with you to hand over the activity or skill. I've used this before when chairing meetings, and wanting to be able to rotate the chair. First, I explain my chairing process, what I'm looking for, how the mechanics work. Then, I chair a meeting, with my protege in attendance. At the next meeting, they chair, and get some feedback (realistically, we might repeat this step a few times - and I'm sure doctors do too!). Finally, and after some practice, they will take on the responsibility of teaching this to someone else one day.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Feedback loops&lt;/strong&gt;: Most developer relations teams practice this one already, using the rest of the team or company as talk rehearsal audiences or blog post reviewers is a great way to improve at speaking or writing. Note that the purpose of the feedback loop in this instance isn't to make a great talk/post, but to help the author to reflect themselves on what is already great, and what can be better - and pick that up themselves next time. &lt;em&gt;Getting better at delivering great feedback is also a skill worth working on, try&lt;/em&gt; &lt;a class="reference external" href="https://larahogan.me/blog/feedback-equation/"&gt;Lara Hogan's Feedback Equation&lt;/a&gt; &lt;em&gt;and take your responsibilities as a feedback giver seriously.&lt;/em&gt; To be really meta, take a leaf out of the Toastmasters' book and give feedback on how the feedback was for those looking to improve their feedback skills. This approach works really well for improving skills like speaking or giving feedback, and can also work in a team of peers - it doesn't necessarily need an expert to offer feedback that you wouldn't have thought of yourself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Formal study&lt;/strong&gt;: Formal study is still pretty effective, actually, especially for individuals with good study skills. Although if you prefer video, then do that too, of course. The hard part about this sort of thing is making time to study solo. An online course with deadlines can help ~force~ encourage you to keep up with your studies and invest the time in this approach. I've &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2013/study-days-keeping-on-top-of-the-new-shiny"&gt;written about this before&lt;/a&gt; (wow that's an old post!), but it's just as effective today as ever. This approach works best for fairly quantifiable skills, so learning a new tech stack or a particular technique makes this a good option.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Write the docs&lt;/strong&gt;: Docs-driven study is one of my favourite ways to get to know a tool or technology. Take responsibility for one piece of documentation or a tutorial on a topic that's a little outside of your zone. By working through the details yourself, to a level where another person can come along and use what you produce, you will unavoidably learn something! This is especially good for getting to know the tools that you haven't tried yet, or that cool third party integration you've been reading about.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these are difficult to adopt, but it's unlikely any of us will improve if we keep doing the same things, over and over. Go out of your way to pick one thing to improve, focus on, or intentionally practice. It's hard to feel like the other things can wait - but they can. Honestly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="be-curious-and-generous"&gt;
&lt;h3&gt;Be Curious, and Generous&lt;/h3&gt;
&lt;p&gt;The truth is, there's a lot to learn, and there always will be. Good learning habits, and intentional drive to improve are the best things you can teach yourself. There is a lot of research, plenty of good &amp;quot;this worked for me&amp;quot; content, and other resources around us. Go hunting for the best bits, and devour them. Share the links with others in your network, or curate collections so we all keep learning and building.&lt;/p&gt;
&lt;p&gt;Crucially, remember that someone is always you on the path, and you have something to teach them. Write what you've learned and how others can use it too. I hope this post helped you to reflect on the resources we have available to teach ourselves, our teams, and our industry - but I would never have written it until one day I looked at the team I had grown, and saw them grow one another, and thought we were on to something that we should share.&lt;/p&gt;
&lt;/div&gt;
</content><category term="work"/><category term="devrel"/><category term="opinion"/></entry><entry><title>Improve Existing OpenAPI Descriptions</title><link href="https://lornajane-net.pages.dev/posts/2023/overlay-improvements-to-openapi" rel="alternate"/><published>2023-02-28T18:04:00+00:00</published><updated>2023-02-28T18:04:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2023-02-28:/posts/2023/overlay-improvements-to-openapi</id><summary type="html">&lt;p class="first"&gt;I'm a firm fan of a design-first approach to building APIs, and advocate for it at every opportunity. Repeatedly. Design-first means that an API change starts in the OpenAPI description, gets reviewed and approved by the stakeholders, and then gets built afterwards.&lt;/p&gt;
&lt;p class="last"&gt;I also live in the real world, where OpenAPI descriptions get generated from existing codebases, and engineering teams lack either the knowledge or the enthusiasm to modernise their processes. With a generated OpenAPI description, everything else that OpenAPI enables is likely to be disappointing, because the description only contains the information that was in the serverside code, and if you're lucky some comment metadata. Enter &lt;em&gt;OpenAPI Overlays&lt;/em&gt;, a way to describe alterations to an existing OpenAPI description that can be reapplied every time that description changes.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I'm a firm fan of a design-first approach to building APIs, and advocate for it at every opportunity. Repeatedly. Design-first means that an API change starts in the OpenAPI description, gets reviewed and approved by the stakeholders, and then gets built afterwards.&lt;/p&gt;
&lt;p&gt;I also live in the real world, where OpenAPI descriptions get generated from existing codebases, and engineering teams lack either the knowledge or the enthusiasm to modernise their processes. With a generated OpenAPI description, everything else that OpenAPI enables is likely to be disappointing, because the description only contains the information that was in the serverside code, and if you're lucky some comment metadata. Enter &lt;em&gt;OpenAPI Overlays&lt;/em&gt;, a way to describe alterations to an existing OpenAPI description that can be reapplied every time that description changes.&lt;/p&gt;
&lt;div class="section" id="openapi-overlay-specification"&gt;
&lt;h3&gt;OpenAPI Overlay Specification&lt;/h3&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://github.com/OAI/Overlay-Specification"&gt;OpenAPI Overlay Specification&lt;/a&gt; has been around for a while but it didn't have implementations (or I just haven't seen them, which is possible, please correct me if appropriate!). However it solves problems that I see in almost every organisation I talk to - the OpenAPI description is lacking words, examples, links or other additions that would make the documentation output better; the client SDK generators need some metadata to give more detailed hints on how to handle specific fields; not all endpoints in the description file should be fed into another tool (some are private, or restricted).&lt;/p&gt;
&lt;p&gt;All these are familiar problems and I hope that the Overlay specification receives the attention and wide adoption that it merits. There are also some other similar solutions in this space, such as the &lt;a class="reference external" href="https://redocly.com/docs/cli/decorators/"&gt;Redocly decorators feature&lt;/a&gt;, but having an open standard really appeals to me since it enables switching between tools easily.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="a-library-for-overlays"&gt;
&lt;h3&gt;A library for Overlays&lt;/h3&gt;
&lt;p&gt;I've recently had a bit of time for a side project, and decided to start building a NodeJS implementation of this thing that I keep claiming will solve so many of the API problems I see!&lt;/p&gt;
&lt;p&gt;Please &lt;em&gt;try out the code&lt;/em&gt;, you can find &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;`lornajane/openapi-overlays-js&lt;/span&gt;&lt;/tt&gt; on GitHub &amp;lt;&lt;a class="reference external" href="https://github.com/lornajane/openapi-overlays-js"&gt;https://github.com/lornajane/openapi-overlays-js&lt;/a&gt;&amp;gt;`__. It's early days and I suspect I have a lot more tests to write, but if you're interested in taking it for a spin, please go ahead! I would love to hear what works (and what doesn't).&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="openapi"/><category term="overlays"/><category term="redocly"/></entry><entry><title>Manager's Secret Second Calendar</title><link href="https://lornajane-net.pages.dev/posts/2023/managers-secret-second-calendar" rel="alternate"/><published>2023-02-26T16:33:00+00:00</published><updated>2023-02-26T16:33:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2023-02-26:/posts/2023/managers-secret-second-calendar</id><summary type="html">&lt;p class="first last"&gt;Employees expect a lot from their managers, and as managers we do our best to meet those expectations. However most managers are only human (I've met a few that I had doubts about), and so it can be difficult to get absolutely everything right absolutely all the time. I'd like to share a tactic that I learned from another manager around the time that I took my first line manager role, that I think really helped me to at least give the impression that I knew what was going on: &lt;strong&gt;a second calendar, visible only to you, with key dates in&lt;/strong&gt;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Employees expect a lot from their managers, and as managers we do our best to meet those expectations. However most managers are only human (I've met a few that I had doubts about), and so it can be difficult to get absolutely everything right absolutely all the time. I'd like to share a tactic that I learned from another manager around the time that I took my first line manager role, that I think really helped me to at least give the impression that I knew what was going on: &lt;strong&gt;a second calendar, visible only to you, with key dates in&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Create a second calendar and set it to be private to you - but have it display in your default calendar view. To this calendar add:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;employee start dates, and set them to repeat annually&lt;/li&gt;
&lt;li&gt;team member birthdays, again with an annual repeat&lt;/li&gt;
&lt;li&gt;milestones such as probation end dates, review dates, or onboarding milestones&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this in hand, you can show that you care - because you do, but it can be hard to keep the details in line. You can also make sure that everyone is getting timely support, for example in my first manager role we did onboarding check-ins at 30, 60 and 90 days - which I really liked and I added those dates to my calendar. When I then onboarded 5 new members of staff in a 6 week period and more than doubled the size of my team in the process, the calendar helped me keep track of all those milestones and not miss anything for any of my valued (but numerous) humans, and ensure I did the best job I could of getting their journey off to a flying start.&lt;/p&gt;
&lt;p&gt;(Feel free to add personal reminders to your calendar too if that's a good place for them, I won't tell anyone!)&lt;/p&gt;
&lt;p&gt;Do you have a favourite calendar hack that helps you appear clued up at work? Please share, I'm always looking for these extra ideas that can make things run more smoothly for me and for my people.&lt;/p&gt;
</content><category term="work"/><category term="tips"/><category term="work"/></entry><entry><title>Generating a nice-looking PDF with Pandoc</title><link href="https://lornajane-net.pages.dev/posts/2023/generating-a-nice-looking-pdf-with-pandoc" rel="alternate"/><published>2023-01-03T10:33:00+00:00</published><updated>2023-01-03T10:33:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2023-01-03:/posts/2023/generating-a-nice-looking-pdf-with-pandoc</id><summary type="html">&lt;p class="first last"&gt;&lt;a class="reference external" href="https://pandoc.org/"&gt;Pandoc&lt;/a&gt; is one of my favourite tools in the world. If you haven't come across it before, it converts between more or less any two document formats. I use it a lot with MarkDown and ReStructuredText so that I can produce content efficiently and then share it in a way that looks nice, but also to get word documents into cleaner formats. However the PDF output has always looked a little bit ... dated? (this is rich from an &lt;a class="reference external" href="https://rst2pdf.org"&gt;rst2pdf&lt;/a&gt; maintainer I know!) and I was recently delighted to find some tricks that resulted in a better PDF. I'm sharing them here, so I can find them again in the future; you are welcome to use them too, of course!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://pandoc.org/"&gt;Pandoc&lt;/a&gt; is one of my favourite tools in the world. If you haven't come across it before, it converts between more or less any two document formats. I use it a lot with MarkDown and ReStructuredText so that I can produce content efficiently and then share it in a way that looks nice, but also to get word documents into cleaner formats. However the PDF output has always looked a little bit ... dated? (this is rich from an &lt;a class="reference external" href="https://rst2pdf.org"&gt;rst2pdf&lt;/a&gt; maintainer I know!) and I was recently delighted to find some tricks that resulted in a better PDF. I'm sharing them here, so I can find them again in the future; you are welcome to use them too, of course!&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;I'm starting from MarkDown, and in fact since I write blog posts in MarkDown format, I'm using the blog as source material for this (how meta).&lt;/div&gt;
&lt;div class="line"&gt;For the impatient, here's the punchline:&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="literal-block"&gt;
pandoc pandoc-nicer-pdf.md --template eisvogel -V linkcolor=blue -V header-includes:'\usepackage[export]{adjustbox} \let\includegraphicsbak\includegraphics \renewcommand*{\includegraphics}[2][]{\includegraphicsbak[frame,#1]{#2}}' -o pandoc.pdf
&lt;/pre&gt;
&lt;p&gt;For the curious, the next few sections outline how I got there. But first: here's a screenshot of the blog post you haven't read yet, since I'll need an image for demonstration purposes:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2023/01/Screenshot-Capture-2023-01-02-22-20-06.png"&gt;&lt;img alt="Screenshot of this post on this blog." class="aligncenter size-large wp-image-4575" src="https://lornajane-net.pages.dev/images/2023/01/Screenshot-Capture-2023-01-02-22-20-06-1024x596.png" style="width: 584px; height: 340px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="build-the-pdf-from-markdown"&gt;
&lt;h3&gt;Build the PDF from MarkDown&lt;/h3&gt;
&lt;p&gt;Start simple! Take a markdown file and make a PDF from it:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
pandoc pandoc-nicer-pdf.md -o pandoc.pdf
&lt;/pre&gt;
&lt;p&gt;I mean, it's a great outcome from almost no work at all on my part, but we can do better...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="adopt-a-template"&gt;
&lt;h3&gt;Adopt a template&lt;/h3&gt;
&lt;p&gt;I looked around for a better template and came across &lt;a class="reference external" href="https://github.com/Wandmalfarbe/pandoc-latex-template"&gt;Eisvogel&lt;/a&gt; on GitHub which looked really great. The install instructions in the project &lt;tt class="docutils literal"&gt;README&lt;/tt&gt; are good and will get you set up and the template in the right place for Pandoc to find it.&lt;/p&gt;
&lt;blockquote&gt;
TL;DR on Ubuntu I needed some additional packages (this is a new laptop so I have hardly anything installed): &lt;tt class="docutils literal"&gt;latex&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;texlive-fonts-extra&lt;/span&gt;&lt;/tt&gt; to get the ClearSans font that's used.&lt;/blockquote&gt;
&lt;p&gt;Apply the template when building the PDF:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
pandoc pandoc-nicer-pdf.md --template eisvogel -o pandoc.pdf
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="set-some-variables"&gt;
&lt;h3&gt;Set some variables&lt;/h3&gt;
&lt;p&gt;It's possible to &lt;a class="reference external" href="https://pandoc.org/MANUAL.html#extension-yaml_metadata_block"&gt;configure Pandoc with a yaml block&lt;/a&gt; but I didn't want to change my source document, and I was also doing a one-off process so I stuck to putting the extra variables on the command line.&lt;/p&gt;
&lt;p&gt;PDFs can have clickable links these days, and I expected the document to be mostly used digitally (if printing, you probably want to show link destinations as footnotes), so I set the link colour:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
pandoc pandoc-nicer-pdf.md --template eisvogel -V linkcolor=blue -o pandoc.pdf
&lt;/pre&gt;
&lt;p&gt;At this point, the document looks pretty decent, much more &amp;quot;me&amp;quot; than the standard output, but the images were sort of hanging around in the text, looking weird.&lt;/p&gt;
&lt;p&gt;Then I found this &lt;a class="reference external" href="https://tex.stackexchange.com/questions/349918/image-frame-borders-in-pdf-from-latex-template-with-pandoc-markdown"&gt;brilliant question and answer on the TeX StackExchange&lt;/a&gt; which pointed me at some additional variables that add a border around images.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
pandoc pandoc-nicer-pdf.md --template eisvogel -V linkcolor=blue -V header-includes:'\usepackage[export]{adjustbox} \let\includegraphicsbak\includegraphics \renewcommand*{\includegraphics}[2][]{\includegraphicsbak[frame,#1]{#2}}' -o pandoc.pdf
&lt;/pre&gt;
&lt;p&gt;There are lots of things I like about outputting content to a known layout. PDFs have fonts and images included, so you can always send a document to another person or device, and know that it will arrive intact and looking its best! Since I mostly write for digital outlets, the layout really doesn't matter, but being able to very easily transform it into something tangible is brilliant, and I really like how this turned out.&lt;/p&gt;
&lt;p&gt;Here is the &lt;a class="reference external" href="https://lornajane-net.pages.dev/images/2023/01/pandoc.pdf"&gt;PDF of this blog post&lt;/a&gt; if you want to see how it turned out.&lt;/p&gt;
&lt;p&gt;What are your Pandoc tricks? Please share them in the comments, I would love to add to my box of tricks!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="markdown"/><category term="pandoc"/><category term="pdf"/><category term="writing"/></entry><entry><title>Sphinx extensions for developer portals</title><link href="https://lornajane-net.pages.dev/posts/2022/sphinx-extensions-for-developer-portals" rel="alternate"/><published>2022-11-08T09:27:00+00:00</published><updated>2022-11-08T09:27:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2022-11-08:/posts/2022/sphinx-extensions-for-developer-portals</id><summary type="html">&lt;p&gt;I've been gushing about docs-as-code, but realised that I haven't shared many hands-on tips for actually building different types of sites in this sort of toolchain. I've been mostly using &lt;a class="reference external" href="https://www.sphinx-doc.org/en/master/"&gt;Sphinx&lt;/a&gt; which generates much better-looking documentation than its website suggests. It's a python-based static site generator, with a thriving community …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been gushing about docs-as-code, but realised that I haven't shared many hands-on tips for actually building different types of sites in this sort of toolchain. I've been mostly using &lt;a class="reference external" href="https://www.sphinx-doc.org/en/master/"&gt;Sphinx&lt;/a&gt; which generates much better-looking documentation than its website suggests. It's a python-based static site generator, with a thriving community and lots of great extensions. Today I thought I'd share the extensions I use the most for developer portals (like the &lt;a class="reference external" href="https://docs.aiven.io"&gt;Aiven docs&lt;/a&gt;, one of my work projects and the source of the screenshots in this post) and what I like about them. &lt;em&gt;Spoiler: may contain a theme as well as the extensions&lt;/em&gt;.&lt;/p&gt;
&lt;div class="section" id="copy-button-for-code"&gt;
&lt;h3&gt;Copy button for code&lt;/h3&gt;
&lt;p&gt;Short and sweet: The &lt;a class="reference external" href="https://sphinx-copybutton.readthedocs.io/en/latest/index.html"&gt;Sphinx Copybutton extension&lt;/a&gt; adds a cute and accessible copy button to all the code samples on your site. Especially for developer portals, it's these touches that make the difference.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="use-a-single-file-toc-table-of-contents"&gt;
&lt;h3&gt;Use a single-file TOC (Table Of Contents)&lt;/h3&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://sphinx-external-toc.readthedocs.io/en/latest/intro.html"&gt;Sphinx External TOC&lt;/a&gt; extension allows you to configure a single navigation structure for an entire site. For a developer portal or docs site, where the content isn't going to be reused in another context&lt;tt class="docutils literal"&gt;*&lt;/tt&gt; , I have found this very manageable. It makes the navigation structure independent of the folder/file structure, and it's all in one place so it makes it easy for contributors to know where to go to make the navigation bar change.&lt;/p&gt;
&lt;p&gt;I now always use Sphinx this way, and plan always to do so, that's why this one is top of the list!&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;*&lt;/tt&gt; we do reuse content but mostly by including snippets into more than one page, or into external locations; whole pieces of structure aren't reused which is when the &lt;tt class="docutils literal"&gt;toctree&lt;/tt&gt; stuff is really awesome.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configurable-open-graph-metadata"&gt;
&lt;h3&gt;Configurable open graph metadata&lt;/h3&gt;
&lt;p&gt;Get your metadata and social previews in shape with &lt;a class="reference external" href="https://sphinxext-opengraph.readthedocs.io/en/latest/index.html"&gt;Sphinx Open Graph extension&lt;/a&gt;. It is simple to set up and comes with well-documented configuration for how your site should be presented.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sprinkle-some-design-elements-to-support-your-best-content"&gt;
&lt;h3&gt;Sprinkle some design elements to support your best content&lt;/h3&gt;
&lt;p&gt;One thing I love about the docs-as-code approach is that there's no messing about. You want to publish something, you write it, you publish it. You use headings, paragraphs, diagrams, tables, bullet points. However it can all end up looking a bit like a textbook if you're not careful!&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2022/11/sphinx-design-cards.png"&gt;&lt;img alt="cards showing a selection of technology combinations that Aiven has Terraform recipes for" class="aligncenter size-large wp-image-4562" src="https://lornajane-net.pages.dev/images/2022/11/sphinx-design-cards-1024x389.png" style="width: 584px; height: 222px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;By adding a few pre-designed design elements and using them consistently across a site, this can be a nice way to add a touch of something friendlier to the user experience and &lt;a class="reference external" href="https://sphinx-design.readthedocs.io/en/furo-theme/"&gt;Sphinx Design&lt;/a&gt; makes this pretty straightforward. It can be clunky to write the syntax for it all - but then if it was too easy, we'd overdose our users with accordions, grid layouts, and everything else. Remember: Content is King.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="show-last-updated-date"&gt;
&lt;h3&gt;Show last updated date&lt;/h3&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://github.com/jdillard/sphinx-gitstamp"&gt;Sphinx GitStamp&lt;/a&gt; plugin that easily adds to the template the last time the source of the page was changed. This probably isn't desirable on every site but for a developer portal for a very technical audience, this extra metadata is valuable to the reader. It's also pretty valuable to the maintainers to know if things might be a bit stale.&lt;/p&gt;
&lt;blockquote&gt;
The author, &lt;a class="reference external" href="https://jareddillard.com/"&gt;Jared Dillard&lt;/a&gt;, makes a bunch of excellent Sphinx things. The next extension on the list is also theirs, and they wrote an &lt;a class="reference external" href="https://jareddillard.com/blog/common-ways-to-customize-sphinx-themes.html"&gt;great blog post about customizing Sphinx&lt;/a&gt;.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="map-your-site"&gt;
&lt;h3&gt;Map your site&lt;/h3&gt;
&lt;p&gt;Having a site map is a boost for humans and robots alike. It can help SEO since the search engines can find their way around and understand your site structure easily. The &lt;a class="reference external" href="https://github.com/jdillard/sphinx-sitemap"&gt;Sphinx Sitemap extension&lt;/a&gt; extension creates this with minimum effort.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="endlessly-maintainable-diagrams-with-mermaid"&gt;
&lt;h3&gt;Endlessly maintainable diagrams with Mermaid&lt;/h3&gt;
&lt;p&gt;What's better than diagrams in documentation? Text-based, maintainable, source-controlled diagrams of course! I've been a fan of &lt;a class="reference external" href="https://mermaid-js.github.io/mermaid/#/"&gt;MermaidJS&lt;/a&gt; for a long time, so of course I use &lt;a class="reference external" href="https://sphinxcontrib-mermaid-demo.readthedocs.io/en/latest/"&gt;Sphinx Mermaid&lt;/a&gt; in the documentation portals that I work on.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2022/11/mermaid-diagram.png"&gt;&lt;img alt="Flow chart showing how the data flows from the source through the Flink processing element and to a sink" class="aligncenter size-large wp-image-4561" src="https://lornajane-net.pages.dev/images/2022/11/mermaid-diagram-1024x239.png" style="width: 584px; height: 136px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As a side benefit, we will be able to render the diagrams differently for dark and light themes. And as a user with some accessibility needs, I can also create the diagrams using any text editor and contribute in the same way as everyone else does.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pages-just-in-case"&gt;
&lt;h3&gt;404 pages, just in case&lt;/h3&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://sphinx-notfound-page.readthedocs.io/en/latest/index.html"&gt;Sphinx NotFound Page extension&lt;/a&gt; adds the 404 page generation that really should be in Sphinx by default. No fuss, just a polite, well-rendered page presented to a user who mistypes or invents a URL they wish existed, with customisable options including the template file to use.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="special-mention-furo-theme"&gt;
&lt;h3&gt;Special mention: Furo Theme&lt;/h3&gt;
&lt;p&gt;Bending the rules since the final addition here isn't an extension at all, it's a theme. I've found Furo to be modern, robust, and clean. We've adapted and styled it but it gave lots of functionality (secondary navigation, dark theme) out of the box, and we know our users enjoy these features, so it's getting a special mention in this post!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What would you add? I'd love to hear your recommendations for the magic sprinkles that make your developer portal experiences better!&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="docs"/><category term="docs-as-code"/><category term="sphinx"/></entry><entry><title>Internal DevRel: Colleague Enablement</title><link href="https://lornajane-net.pages.dev/posts/2022/internal-devrel-colleague-enablement" rel="alternate"/><published>2022-09-26T15:09:00+01:00</published><updated>2022-09-26T15:09:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2022-09-26:/posts/2022/internal-devrel-colleague-enablement</id><summary type="html">&lt;p class="first last"&gt;I work in Developer Relations for a very technical company (&lt;a class="reference external" href="https://aiven.io"&gt;Aiven&lt;/a&gt;), and I usually describe my job as half explaining my employer's technology to developers, and half explaining developers to my employers. However in the last year or so, I've realised that there is a variation on this theme that is impactful for my internal colleagues: explaining technology and developers to people who are experts in something else. I work with specialists in various aspects of sales and marketing (DevRel reports into Marketing) and my colleagues are genuinely curious to know more about the domain we work in! I thought I'd share more about how I enable my colleagues, and why I think it works for us.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I work in Developer Relations for a very technical company (&lt;a class="reference external" href="https://aiven.io"&gt;Aiven&lt;/a&gt;), and I usually describe my job as half explaining my employer's technology to developers, and half explaining developers to my employers. However in the last year or so, I've realised that there is a variation on this theme that is impactful for my internal colleagues: explaining technology and developers to people who are experts in something else. I work with specialists in various aspects of sales and marketing (DevRel reports into Marketing) and my colleagues are genuinely curious to know more about the domain we work in! I thought I'd share more about how I enable my colleagues, and why I think it works for us.&lt;/p&gt;
&lt;div class="section" id="tech-support-for-marketing"&gt;
&lt;h3&gt;Tech Support for Marketing&lt;/h3&gt;
&lt;p&gt;This story begins when I ran out of calendar space. As the people in Marketing with the best understanding of the company's technology, DevRel takes lots of questions from the specialists around us who know their own field, but not necessarily the cloud data platforms we offer as our product. In an attempt to remain available but manage my time better, I suggested doing a regular office-hours style thing to try to remain available but at a manageable level - and so it was that &lt;em&gt;Let's Talk Aiven Tech&lt;/em&gt; was born.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-idea"&gt;
&lt;h3&gt;The Idea&lt;/h3&gt;
&lt;p&gt;The basic idea is that half the company knows our technology and developer culture really well - but the other half knows a whole raft of other things that we also need but which are not the technology. Asking our deeply technical staff questions involving the technology was often returning overly technical explanations; this is a genuine problem where different departments speak what is essentially different languages. What we need id someone who can meet this not-obviously-technical crowd (spoiler: this lot are geeks, they're just different to the engineers you're used to) where they are, and share an appropriate level of knowledge with them in words that they knew.&lt;/p&gt;
&lt;p&gt;Communicating technical concepts appropriately to different audiences is definitely a Developer Relations superpower, so once I understood this problem and how much impact it would make if we could solve it, I knew this was something I wanted to tackle. I was too busy to create the internal curriculum I wished we had, but I had knowledge in my brain and I was prepared to dedicate an hour a week to trying to transfer it out to where others could use it!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-platform"&gt;
&lt;h3&gt;The Platform&lt;/h3&gt;
&lt;p&gt;Engineers are famous for over-engineering solutions but I can honestly say that I under-engineered this one! The whole scheme started as a weekly virtual meeting with four invited guests and an editable calendar invitation. There was a slab page where we kept a list of the upcoming sessions, the past topics with recordings, and another section for topic requests. Some weeks, I remembered to post a reminder in a slack channel the day before to advertise the topic, and ask people to react with a particular emoji (different ones each time) if they wanted to be added to the calendar invite.&lt;/p&gt;
&lt;p&gt;The important thing was: it's super casual, people can show up or not, or watch the recording later. It's absolutely opt-in, and I wasn't sure how much use all this tech content was going to be but with a small number of people asking to give it a shot, we just started and were prepared to iterate. In the event, what started as four invitees turned quickly into a large number of people who wanted to be added to the invitation. We've grown from about 300 to 500 people this year, and the calendar invite had between 75 and 100 people on it most of the way through - usually 20-30 people are there for the live session. The hardest part was persuading technical people not to show up and ask their own questions about the technologies; this will silence anyone who can't follow that conversation and it is difficult to recover from that.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="session-format"&gt;
&lt;h3&gt;Session Format&lt;/h3&gt;
&lt;p&gt;Since I don't have the bandwidth for any prep at all, I usually show up to these sessions with 5 or 6 bullet points to remind me of key points. However this turned out to be a blessing in disguise, because of course I am not the expert on what other people need in their daily work - they are. We settled into a format where I would give a 5-10 minute overview of a tool or technology, trying to capture at a high level what the thing was FOR, what people used it for and WHY they would use this one rather than something else. These are technical topics, but not especially technical sessions. After the intro, I pause and see what questions are already in the room. Sometimes this is enough to get things moving for the whole hour, and the crowd follows on from one another's questions. Very often, they ask questions that I can't answer but someone else can (other technical staff, someone with a better handle on our data dashboards, someone who attended a meeting about that thing), and I just facilitate.&lt;/p&gt;
&lt;p&gt;If conversation dies, I usually demo the thing that is the focus of the session, working through our getting started docs or a typical use case. A few times we have also run this as hands-on labs for things that we can easily do together in the time. Either way, seeing it in action almost always produces more questions, even if it's &amp;quot;why would someone want to do THAT&amp;quot;? I don't think I've had a session that ran more than 5 minutes short in months of running this initiative - my colleagues really are curious about everything.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(As a very well-rehearsed conference speaker, this complete lack of preparation scares me every single week, but it forces me to get out of the way and the audience has to drive the session to the places that they need to visit.)&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="speaking-the-language"&gt;
&lt;h3&gt;Speaking the Language&lt;/h3&gt;
&lt;p&gt;The sillier the story, the easier it is to remember. I am pretty sure that some of my utterly ridiculous analogies were nonsensical but I tried very hard to conjure some mental images to represent the ideas. For something that is a lot bigger than another thing: one is a wheelbarrow, the other is more like a container ship. Logs become an expensive bar tab, and Terraform is a building site supervisor. The other advantage of keeping things fairly silly is that they don't feel too intimidating and jargon-filled. There's still a lot of technical terms and I'm sure there were people that didn't feel they could speak up, but others told me that it did help to understand the concepts.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;The hardest part about this whole exercise for me as a technical person was how to answer the questions in a way that the asker could use the information! We could put together a reasonably long outtakes reel of me being completely floored by really excellent questions. Here are a few of my favourites:&lt;/div&gt;
&lt;div class="line"&gt;* You showed us a different graphs thing last week, why do we need more than one of those? (Relating to Grafana and OpenSearch Dashboards, but we had several variations on this: Postman seems like Terraform when you demo one small thing because they both call the API to make something happen. MySQL and PostgreSQL really ARE similar, and lots of projects could use either)&lt;/div&gt;
&lt;div class="line"&gt;* This week you said that developers prefer the CLI but last week you said they prefer the API, which do you really use? (this is brilliant because I clearly use both and trying to articulate which task would use which tool made my brain hurt, I might have to write that one as a separate blog post one day)&lt;/div&gt;
&lt;div class="line"&gt;* So this little dog (the DataDog agent, it was another analogy that got out of hand) is on all our services. Why did we let the dogs in? Aren't there any other options, should we have them all?&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="serving-an-internal-audience"&gt;
&lt;h3&gt;Serving an Internal Audience&lt;/h3&gt;
&lt;p&gt;The key to successful DevRel is always to meet the users needs, in the words they know, in the place that they hang out. I had no idea that so many of my Marketing colleagues were so keen to know this much about the very technical tools on the Aiven platform, but after 9 months (with a few breaks for holidays and conferences), I know that wherever I work and whatever I do, I'll always want to serve the internal audience as well as the external ones. DevRel is about combining empathy with technical skills to enable and empower the audience - and this achieves that goal in spades.&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="colleague"/><category term="devrel"/><category term="teaching"/><category term="work"/></entry><entry><title>Outline your writing to ease the creative process</title><link href="https://lornajane-net.pages.dev/posts/2022/outline-your-writing-to-ease-the-creative-process" rel="alternate"/><published>2022-04-11T09:39:00+01:00</published><updated>2022-04-11T09:39:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2022-04-11:/posts/2022/outline-your-writing-to-ease-the-creative-process</id><summary type="html">&lt;p class="first last"&gt;For most of my career I've been a software developer, but now I'm mostly a communicator. As a manager, I give the right level of detail to many different audiences, and I deliver that in words they can understand. As a Developer Advocate, I explain complex technical concepts in useful, memorable, and occasionally entertaining ways. However the best software developers are lazy and I'm still always looking for ways to get things done with less effort on my part! For writing, the best process I know feels like unnecessary overhead, but it's always worth it in the end so today I'm sharing my secrets: create an outline before you start.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;For most of my career I've been a software developer, but now I'm mostly a communicator. As a manager, I give the right level of detail to many different audiences, and I deliver that in words they can understand. As a Developer Advocate, I explain complex technical concepts in useful, memorable, and occasionally entertaining ways. However the best software developers are lazy and I'm still always looking for ways to get things done with less effort on my part! For writing, the best process I know feels like unnecessary overhead, but it's always worth it in the end so today I'm sharing my secrets: create an outline before you start.&lt;/p&gt;
&lt;div class="section" id="why-outline-your-writing"&gt;
&lt;h3&gt;Why outline your writing&lt;/h3&gt;
&lt;p&gt;With an outline, you can tell before you've started whether you:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;understand the topic&lt;/li&gt;
&lt;li&gt;can get your points into a coherent order&lt;/li&gt;
&lt;li&gt;know what's in scope&lt;/li&gt;
&lt;li&gt;need to fill in any gaps before writing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If, like me, you get interrupted while you're working, the outline also means that you don't need to hold a grand plan in your head. The structure is laid out before you start, making it easier to stay on track with your train of thought even if you are jumping in and out of this task among other activities.&lt;/p&gt;
&lt;blockquote&gt;
If you had the sort of education where you were taught to create an essay plan before writing, you already have the skills you need for outlining. If not, I'm about to tell you how to do it anyway, so keep reading.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="how-to-create-an-outline"&gt;
&lt;h3&gt;How to create an outline&lt;/h3&gt;
&lt;p&gt;Think about who you are writing for, and what they need to know. Then write a list of 4-6 subheadings that cover the main points. If you need more points than this (do you, really?) then nesting levels of subheadings can also help, especially if it's a longer document.&lt;/p&gt;
&lt;p&gt;That's literally it. You created an outline!&lt;/p&gt;
&lt;p&gt;Sometimes the outline needs quite a lot of work but this early stage is the best possible time to do it. It's much easier now to run the idea past someone else for their input, or to reflect on whether you could move one section to a separate document and just reference it, than it will be once all the words are there getting in the way.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="when-to-use-outlines"&gt;
&lt;h3&gt;When to use outlines&lt;/h3&gt;
&lt;p&gt;Try using outlines on everything that's more than two paragraphs long, and see what sticks! This approach works for me when I'm working on everything from difficult emails to documentation, from blog posts to project updates. I create outlines when I'm writing conference talks too, something about the pre-planned structure gives a nice cadence to the different sections of the talk or written piece when it's finished. Almost as if there had been some sort of plan at the start!&lt;/p&gt;
&lt;p&gt;The hardest part of writing outlines is just like the hardest part of writing specifications for software: &lt;strong&gt;I'd rather be writing the words/code!&lt;/strong&gt; The outline always saves time in the end, and makes me really think hard about the overall aims of the thing before I put pen to paper and start wondering if my sentences are too long. For this lazy software developer, the best process is the one that gets the task done soonest, and outlines always win.&lt;/p&gt;
&lt;/div&gt;
</content><category term="work"/><category term="content"/><category term="speaking"/><category term="tech communication"/><category term="writing"/></entry><entry><title>Who are you writing that commit message for?</title><link href="https://lornajane-net.pages.dev/posts/2022/who-are-you-writing-that-commit-message-for" rel="alternate"/><published>2022-03-31T20:55:00+01:00</published><updated>2022-03-31T20:55:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2022-03-31:/posts/2022/who-are-you-writing-that-commit-message-for</id><summary type="html">&lt;p&gt;I read a lot of commit messages that make me wonder who the committer had in mind when they wrote it. If you don't read commit messages yourself, I think that can make it even more difficult to think about who the audience is, or when someone would be reading …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I read a lot of commit messages that make me wonder who the committer had in mind when they wrote it. If you don't read commit messages yourself, I think that can make it even more difficult to think about who the audience is, or when someone would be reading those entries. Perhaps you're writing for nobody, or work in a team that doesn't value the metadata that a single sentence written in the moment can deliver.&lt;/p&gt;
&lt;p&gt;Next time you write a commit message, try some of these suggestions as your imaginary audience:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Yourself, next week, when you finally get back to working on this thing and can't remember where you were up to&lt;/li&gt;
&lt;li&gt;Yourself, when you get a pull request review and can't remember which commit something is in that needs to be removed&lt;/li&gt;
&lt;li&gt;Yourself, debugging how this ended up like this, 6 months from now&lt;/li&gt;
&lt;li&gt;Your colleague, eyeballing your work to see how you are getting on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Personally, I think of it as a note to myself. Like an alibi, if someone asks you what's already been done, or what this commit that removes one specific line from a long config file. Yes, I worked as a git consultant for a while, the delete-a-single-line with the commit message &amp;quot;Fixed&amp;quot; is &lt;em&gt;always&lt;/em&gt; the culprit!&lt;/p&gt;
&lt;p&gt;Further reading: &lt;a class="reference external" href="https://cbea.ms/git-commit/"&gt;https://cbea.ms/git-commit/&lt;/a&gt;&lt;/p&gt;
</content><category term="tech"/><category term="git"/><category term="tips"/></entry><entry><title>Weekly Planning with Obsidian</title><link href="https://lornajane-net.pages.dev/posts/2022/weekly-planning-with-obsidian" rel="alternate"/><published>2022-02-16T13:01:00+00:00</published><updated>2022-02-16T13:01:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2022-02-16:/posts/2022/weekly-planning-with-obsidian</id><summary type="html">&lt;p class="first last"&gt;Keeping on top of tasks at work has been a work in progress throughout my career. However now I'm doing a more managerial role, I also need to keep track of all the meetings and preparation/followup work in my meetings. I find my daily log fills so many pages of notebook that I moved my weekly plan to a separate section - and then to my preferred digital notes tool, &lt;a class="reference external" href="https://obsidian.md/"&gt;Obsidian&lt;/a&gt;. If you're interested in my setup, read on.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Keeping on top of tasks at work has been a work in progress throughout my career. However now I'm doing a more managerial role, I also need to keep track of all the meetings and preparation/followup work in my meetings. I find my daily log fills so many pages of notebook that I moved my weekly plan to a separate section - and then to my preferred digital notes tool, &lt;a class="reference external" href="https://obsidian.md/"&gt;Obsidian&lt;/a&gt;. If you're interested in my setup, read on.&lt;/p&gt;
&lt;div class="section" id="weekly-structure"&gt;
&lt;h3&gt;Weekly structure&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Meetings&lt;/strong&gt; is always the longest section, I am essentially a professional meeting attendee at this point!&lt;/p&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Shipped&lt;/strong&gt; reminds me to capture things that actually do get done, in case they get lost and I forget I have achieved things&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;ToDo&lt;/strong&gt; collects currently-need-doing items. Usually I don't duplicate things assigned to me on GitHub or any of our ticket tracking software tools.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="meetings"&gt;
&lt;h3&gt;Meetings&lt;/h3&gt;
&lt;p&gt;I look at my calendar and add a heading for each meeting that is on my calendar for the week, and I do this &lt;em&gt;before&lt;/em&gt; Monday morning. In theory, Friday afternoon and in practice, Sunday night. It's like ironing shirts for work except I don't iron any more :)&lt;/p&gt;
&lt;p&gt;For each meeting, I look at whether there is preparation work to do. Am I running the meeting? Is there a pre-read or other work I can do beforehand to make it more useful? I write a list of talking points or other things that I might want to mention in each slot. This helps a lot because my schedule is typically very full, with consecutive meetings that can have VERY different topics. Having the notes ready helps transition from one thing and think clearly about the next one.&lt;/p&gt;
&lt;p&gt;For my regular meetings with my manager, and with my reports, I usually start the week with only a few points, but if I want to ask them something I just add it to the section for their session when I think of it. This works very well since I don't need to flip between days, the whole week is in one document.&lt;/p&gt;
&lt;p&gt;I don't take notes into the same document, I mostly use a pen in a typical bullet journal daily log style, with some sort of adapted dash-plus notation so that after several hours of meetings, I can roll back through the notes and spot the items that needed further action from me.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="shipped"&gt;
&lt;h3&gt;Shipped&lt;/h3&gt;
&lt;p&gt;Almost always the shortest section, which is probably the right thing for someone who is head of a department! My rules here are that it has to be something that is actually finished. If I recorded a video but it isn't on YouTube yet, it's not &amp;quot;shipped&amp;quot; until it's live and all the work has been done.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="todo"&gt;
&lt;h3&gt;ToDo&lt;/h3&gt;
&lt;p&gt;This is things I need to do, I don't copy over things from other systems, so I have Asana, JIRA, and GitHub also open essentially all the time. I add things here if they are not on this list, for example right now the list is reminding me to sort out a thing with my health insurance, email a speaker for an upcoming event, and to follow up on a thing I saw on Twitter.&lt;/p&gt;
&lt;p&gt;Obsidian is a markdown tool so it supports lists with checkboxes in - I check the box when I'm done and then when I do the weekly reset, I can see what didn't get done and needs migrating. Every week I grab the stuff that isn't marked &amp;quot;done&amp;quot; from the previous week, decide what just isn't ever going to happen and remove it, and then try to identify from any unprocessed notes the week before which todo items haven't been transferred anywhere useful yet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="weekly-planning-cadence"&gt;
&lt;h3&gt;Weekly planning cadence&lt;/h3&gt;
&lt;p&gt;Doing a weekly reset has worked well for me, and the digital format makes it infinitely adjustable so I never run out of space as I would on paper! Doing it this way, the meetings for the day are all next to one another in a document and I just have it open at the edge of my monitor so I can glance over if I need it.&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="productivity"/></entry><entry><title>Keeping your finger on the (digital) pulse</title><link href="https://lornajane-net.pages.dev/posts/2022/keeping-your-finger-on-the-digital-pulse" rel="alternate"/><published>2022-02-15T18:25:00+00:00</published><updated>2022-02-15T18:25:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2022-02-15:/posts/2022/keeping-your-finger-on-the-digital-pulse</id><summary type="html">&lt;p class="first"&gt;Whether you are building your own presence, or representing a brand or employer online, it's useful to know what people are saying! Keeping up with all those possible places you should look for news or mentions can be pretty laborious and when things are busy, it's difficult to keep up with this work unless you have some decent tooling in place. There are some great automation offerings around but here's my very basic strategy.&lt;/p&gt;
&lt;p class="last"&gt;&lt;strong&gt;TL;DR a bookmarks folder in my browser to open a bunch of saved searches all at once&lt;/strong&gt;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Whether you are building your own presence, or representing a brand or employer online, it's useful to know what people are saying! Keeping up with all those possible places you should look for news or mentions can be pretty laborious and when things are busy, it's difficult to keep up with this work unless you have some decent tooling in place. There are some great automation offerings around but here's my very basic strategy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR a bookmarks folder in my browser to open a bunch of saved searches all at once&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I created a folder of bookmarks in my browser, which is Firefox; it has support to open all the links in a folder at once. These are saved searches for all the things I like to keep an eye on. They are things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;Search for newest issues across our whole GitHub org so we don't miss something on one of our smaller or less-used repos because it was lost in a big raft of email notifications. The same for pull requests.&lt;/p&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Searches on &lt;a class="reference external" href="https://stackoverflow.com"&gt;StackOverflow&lt;/a&gt;, &lt;a class="reference external" href="https://reddit.com"&gt;Reddit&lt;/a&gt; and &lt;a class="reference external" href="https://news.ycombinator.com/"&gt;HackerNews&lt;/a&gt; to see what people are saying. I wouldn't recommend diving into the middle of all the conversations here, but more to hear the general sentiment. I don't have a search on &lt;a class="reference external" href="https://twitter.com"&gt;Twitter&lt;/a&gt; since I basically live there full time anyway, but you might find it useful.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Dashboards for whatever the main metrics are for your team, for example I look at both Google Analytics and the GitHub stats for our docs project to understand how people access and read the content, and also how they engage/contribute to it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep all the searches very narrow and specific - I should take my own advice on this since I have a few too many tags in my StackOverflow search and it makes for FAR too many results when I just want to check things quickly. Sort the results so that the newest are first and only the last 30 days are included if that's an option, there is no point seeing the same 2-year-old post every week.&lt;/p&gt;
&lt;p&gt;Now set a 30 minute meeting in your calendar every week to check on things, quickly open the bookmarks collection, and then go through seeing what's happening. If there are followup actions then do anything that takes 3 minutes or less immediately, such as sharing it with your team channel, or replying with a comment. For anything more, instead take the time to create a ticket linking to what you found and describing what action should be taken. For me that's often &amp;quot;uh oh, should we have some documentation about this thing?&amp;quot; type tickets, and I really enjoy being able to go back to a question later and offer both some knowledge and also a link. Even if it's too late for the original poster, sites like StackOverflow bring our collective knowledge together in a public place for the next person to use, so it's always worth adding whatever you have!&lt;/p&gt;
&lt;p&gt;Just like answering a StackOverflow question, this post came to you when I was giving some advice to someone doing a similar role to mine. A folder of bookmarks isn't rocket science, but it game them a quick start into one tiny part of their day to day job - and sometimes the smallest changes make the biggest difference! What are your &amp;quot;really not rocket science&amp;quot; tips that you'd share? Tell me, I want them all :)&lt;/p&gt;
</content><category term="work"/><category term="devrel"/><category term="productivity"/><category term="stackoverflow"/></entry><entry><title>VSCode setup for technical writers</title><link href="https://lornajane-net.pages.dev/posts/2022/vscode-setup-for-technical-writers" rel="alternate"/><published>2022-01-05T14:23:00+00:00</published><updated>2022-01-05T14:23:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2022-01-05:/posts/2022/vscode-setup-for-technical-writers</id><summary type="html">&lt;p class="first last"&gt;I'm working almost entirely with docs-as-code setups for prose at work these days and while I do use vim for most of what I do, VSCode is increasingly in the mix. It's just about accessible enough with keyboard shortcuts and the command palette for me, and I thought I'd share the extensions I'm using and find helpful.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I'm working almost entirely with docs-as-code setups for prose at work these days and while I do use vim for most of what I do, VSCode is increasingly in the mix. It's just about accessible enough with keyboard shortcuts and the command palette for me, and I thought I'd share the extensions I'm using and find helpful.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2022/01/Screenshot-from-2022-01-01-22-55-58.png"&gt;&lt;img alt="screenshot showing markup in one pane, preview HTML in the other" class="aligncenter size-large wp-image-4483" src="https://lornajane-net.pages.dev/images/2022/01/Screenshot-from-2022-01-01-22-55-58-1024x640.png" style="width: 584px; height: 365px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As a former software engineer, I kind of expect certain features from my text editor, but not all writers have had such experience to tools. I encourage everyone to have both good syntax support for the file types they work with, and good previews. VSCode is an ideal writer's tool because the two-panes view shows things as they happen! We're using some RestructuredText and some Markdown so I have &lt;a class="reference external" href="https://marketplace.visualstudio.com/items?itemName=searKing.preview-vscode"&gt;Preview&lt;/a&gt; which handles both. For syntax hightlighting, I have a &lt;a class="reference external" href="https://marketplace.visualstudio.com/items?itemName=trond-snekvik.simple-rst"&gt;RestructuredText syntax highlight extension&lt;/a&gt; and also one for &lt;a class="reference external" href="https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml"&gt;YAML highlighting&lt;/a&gt;. With the restructured text one, I found it helpful to disable the language server because it wasn't familiar with a lot of the directives our Sphinx site uses (the config setting is &lt;tt class="docutils literal"&gt;restructuredtext.languageServer.disabled&lt;/tt&gt; - set it to true).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro-tip&lt;/strong&gt; for anyone working with yaml or anything else using indents: &lt;a class="reference external" href="https://marketplace.visualstudio.com/items?itemName=oderwat.indent-rainbow"&gt;Indent-Rainbow&lt;/a&gt; gives a lovely visual clue of how indented things are. It's subtle, but it's just enough.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2022/01/Screenshot-from-2022-01-01-22-50-32.png"&gt;&lt;img alt="screenshot of the rainbow indent plugin on a yaml file" class="aligncenter size-large wp-image-4481" src="https://lornajane-net.pages.dev/images/2022/01/Screenshot-from-2022-01-01-22-50-32-1024x640.png" style="width: 584px; height: 365px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We're using GitHub for collaboration and while I do have the &lt;a class="reference external" href="https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github"&gt;GitHub Pull Requests and Issues plugin&lt;/a&gt; and recommend it to everyone else, I almost exclusively use a combination of the web interface and the &lt;tt class="docutils literal"&gt;`gh&lt;/tt&gt; &amp;lt;&lt;a class="reference external" href="https://cli.github.com/"&gt;https://cli.github.com/&lt;/a&gt;&amp;gt;`__. The web interface is almost accessible, usually there's one regression or another (currently: the one-line text box to reply to comments doesn't turn into a full comment box when I focus it, I have to click) but between the CLI and the web tools, I manage. I also heartily recommend the &lt;a class="reference external" href="https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph"&gt;Git Graph extension&lt;/a&gt; for teams that use branches. We have what I will politely describe as a mixed set of git skills in my team and I find the visualisation can really help all of us understand what went where.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2022/01/Screenshot-from-2022-01-01-21-48-07.png"&gt;&lt;img alt="screenshot of git graph showing the branches and commits" class="aligncenter size-large wp-image-4478" src="https://lornajane-net.pages.dev/images/2022/01/Screenshot-from-2022-01-01-21-48-07-1024x640.png" style="width: 584px; height: 365px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We're a globally distributed team which means we can't just look over one another's shoulders to fix things. The &lt;a class="reference external" href="https://marketplace.visualstudio.com/items?itemName=MS-vsliveshare.vsliveshare"&gt;Live Share&lt;/a&gt; extension helps a lot when we need to put our heads or hands together to get something done.&lt;/p&gt;
&lt;p&gt;As a final addition, I'd like to give a special mention to the newest addition to my extension collection, &lt;a class="reference external" href="https://marketplace.visualstudio.com/items?itemName=errata-ai.vale-server&amp;amp;ssr=false#overview"&gt;Vale extension for VSCode&lt;/a&gt;. I'm working on a docs project (it's &lt;a class="reference external" href="https://developer.aiven.io"&gt;Aiven Developer&lt;/a&gt; in case you are interested, also has a public GitHub repo if you want to dig around the details on any of this stuff) that has a CI build and previews for the documentation. It uses &lt;a class="reference external" href="https://github.com/errata-ai/vale"&gt;Vale&lt;/a&gt; to keep us organised with things like spelling (and capitalising) product names correctly, using consistent uppercase in titles, you get the idea. We can also add rules to it as we go along. We have a lot of occasional contributors and this tool is so complete, it has a CLI, a GitHub action, and a VSCode extension - I mostly use it as a &lt;tt class="docutils literal"&gt;make&lt;/tt&gt; command, but the VSCode plugin will run on save (or as you type, you choose), and I am looking forward to using it more instead of realising the build failed and then trying to work out what to fix ...&lt;/p&gt;
&lt;p&gt;I hope my list gave you some ideas of extensions to improve your experience, and I would love to hear what other tools or extensions you would include in a collection like this! Comments very welcome :)&lt;/p&gt;
</content><category term="tech"/><category term="aiven"/><category term="restructuredtext"/><category term="tech writing"/><category term="tips"/><category term="vscode"/></entry><entry><title>What Happened to your Blog?</title><link href="https://lornajane-net.pages.dev/posts/2021/what-happened-to-your-blog" rel="alternate"/><published>2021-12-12T20:20:00+00:00</published><updated>2021-12-12T20:20:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2021-12-12:/posts/2021/what-happened-to-your-blog</id><summary type="html">&lt;p class="first last"&gt;What happened to my blog? Nothing happened, I still blog ... except then I looked at my site the other day and realised, it's been a while.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;What happened to my blog? Nothing happened, I still blog ... except then I looked at my site the other day and realised, it's been a while.&lt;/p&gt;
&lt;p&gt;The blog here at lornajane.net is &lt;em&gt;mumble&lt;/em&gt; years old. It is full of things I wanted to write down, and tells the story of most of my career to date. As a software engineer, open source project maintainer, author, speaker and teacher, this made perfect sense and I've been thankful many, many times for the thoughtful explanations of past-Lorna that have helped me along my way.&lt;/p&gt;
&lt;p&gt;This year, my career changed. I'm a manager now, and the problems I solve don't lend themselves so well to writing or sharing. As a manager of people, I'm both too early in my career growth to have much to say, and also conscious of writing about &amp;quot;my&amp;quot; humans, in a way that might not work for either or both of us. I still work with a lot of technology, and more formally with developer experiences than ever before, but this year has been a professional challenge beyond anything I've tackled for a while (and looking back, quiet patches on the blog have in the past been mostly when I was writing a book, which is also a professional challenge!)&lt;/p&gt;
&lt;p&gt;As an engineer, a writer, a developer experience enthusiast and as an open source project maintainer/contributor, I still have a lot to say. Somehow, the combination of a demanding year and also a sizeable side project&lt;tt class="docutils literal"&gt;*&lt;/tt&gt; doesn't leave me much spare time - and I mostly fill it with management textbooks and Stardew Valley! It has felt like a year of mostly listening and learning, more than sharing.&lt;/p&gt;
&lt;p&gt;That's not to say that I didn't write. I really did! Just ... not here (the &lt;a class="reference external" href="/resources.html"&gt;more Lornajane&lt;/a&gt; has the links if you're interested). See also: internal docs, and a whole new developer portal &lt;a class="reference external" href="https://developer.aiven.io"&gt;developer.aiven.io&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So what happens now? Nothing big. Just sitting down to write this reminded me why I love having a blog to write on in the first place. It's been a strange year (more than one, for almost everyone), and between two big work transitions - a new employer, then a big promotion once I got there - and half a year of having the kitchen rebuilt, I didn't have words to spare.&lt;/p&gt;
&lt;p&gt;I have learned so much this year, and while it would look very different to most of the previous posts here, I would still like to write some of it down. This blog began as a way for me to capture things I wanted to share or reflect on in the future. Sometimes, other people read it too. But the purpose hasn't changed, and it took me time to realise that it makes perfect sense for the content to change as my career does.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;*&lt;/tt&gt; I was preparing a Technical Communications workshop but unfortunately it won't be going ahead. However there are a lot of topics there that I've done a bunch of research and figured out how I want to share, so I'll be putting some of them here when I get chance. Topic requests in the comments, please.&lt;/p&gt;
</content><category term="work"/><category term="blog"/><category term="work"/></entry><entry><title>Sphinx front matter and template variables</title><link href="https://lornajane-net.pages.dev/posts/2021/sphinx-front-matter-and-template-variables" rel="alternate"/><published>2021-09-10T21:47:00+01:00</published><updated>2021-09-10T21:47:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2021-09-10:/posts/2021/sphinx-front-matter-and-template-variables</id><summary type="html">&lt;p class="first last"&gt;I'm building a docs-as-code site at work at the moment (you can peek if you like &lt;a class="reference external" href="https://developer.aiven.io"&gt;https://developer.aiven.io&lt;/a&gt;) and working with ReStructuredText in this setup is new to me. One thing I really missed was the concept of &lt;a class="reference external" href="https://jekyllrb.com/docs/front-matter/"&gt;front matter&lt;/a&gt;, which I'm not sure is even official or documented but seems to be reasonably widely supported (I promise to save the rant about markdown for another day). It took me a while to figure this out for Sphinx/rst but now I'm looking at it working, I had better write it down before I forget!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I'm building a docs-as-code site at work at the moment (you can peek if you like &lt;a class="reference external" href="https://developer.aiven.io"&gt;https://developer.aiven.io&lt;/a&gt;) and working with ReStructuredText in this setup is new to me. One thing I really missed was the concept of &lt;a class="reference external" href="https://jekyllrb.com/docs/front-matter/"&gt;front matter&lt;/a&gt;, which I'm not sure is even official or documented but seems to be reasonably widely supported (I promise to save the rant about markdown for another day). It took me a while to figure this out for Sphinx/rst but now I'm looking at it working, I had better write it down before I forget!&lt;/p&gt;
&lt;blockquote&gt;
All this is on Sphinx 4.0 and Python 3.8, in case this information matters to someone reading this post 10 years from now.&lt;/blockquote&gt;
&lt;div class="section" id="add-fields-to-your-content"&gt;
&lt;h3&gt;Add fields to your content&lt;/h3&gt;
&lt;p&gt;Sphinx calls this extra information &lt;a class="reference external" href="https://www.sphinx-doc.org/en/master/usage/restructuredtext/field-lists.html#file-wide-metadata"&gt;file-wide metadata&lt;/a&gt;, variables that apply in the context of the file they appear at the top of. I'm not sure how many Sphinx installations use something other than one-file-per-page but file-wide works perfectly for me.&lt;/p&gt;
&lt;p&gt;Here's how I added a description field to a page (I'm not using the meta markup because I'm actually going to use this field in a twitter card implementation):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
:description: This page has an excellent description of how to apply cucumber and unicorns to the problem

Heading 1
#########

More words on the page .....
&lt;/pre&gt;
&lt;p&gt;Note that the extra fields have to be the first thing in the file, before the first heading or other content. This adds any field name you care to invent, and a string value (check the docs for more detail on supported types but basically: use a string) to the &lt;tt class="docutils literal"&gt;meta&lt;/tt&gt; field for this pae.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="using-this-data-in-a-template"&gt;
&lt;h3&gt;Using this data in a template&lt;/h3&gt;
&lt;p&gt;I'm adding basic Twitter Card support by pasting it straight into &lt;tt class="docutils literal"&gt;base.html&lt;/tt&gt;. Probably the comments on this post will help me to understand better ways to solve this problem, which is why I love blogging. If there's a specific description set, I want the tag to use it - and if not, we'll just grab the first 120 characters of the page itself. Ready?&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;lt;!-- Twitter Cards --&amp;gt;
&amp;lt;meta name=&amp;quot;twitter:title&amp;quot; content=&amp;quot;{{ docstitle|striptags|e }}&amp;quot;&amp;gt;
&amp;lt;meta name=&amp;quot;twitter:description&amp;quot; content=&amp;quot;{{ meta.description or body|striptags|e|truncate(120) }}&amp;quot;&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;{{ meta.description }}&lt;/tt&gt; field in the template holds the value we defined in each page, and you can also see the &lt;tt class="docutils literal"&gt;or&lt;/tt&gt; syntax there in case we need to fall back to just using a bit of page content (which hopefully does introduce the intent of the page pretty well anyway!).&lt;/p&gt;
&lt;p&gt;This was harder to find an example of than I had expected so hopefully this will help someone else running into it next time.&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="html"/><category term="sphinx"/></entry><entry><title>Use Database Connection Strings with Laravel 8</title><link href="https://lornajane-net.pages.dev/posts/2021/use-database-connection-strings-with-laravel-8" rel="alternate"/><published>2021-08-18T08:56:00+01:00</published><updated>2021-08-18T08:56:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2021-08-18:/posts/2021/use-database-connection-strings-with-laravel-8</id><summary type="html">&lt;p class="first last"&gt;I've been doing a lot of database stuff lately, and not much PHP, so when I returned to make my first &lt;a class="reference external" href="https://laravel.com/"&gt;Laravel&lt;/a&gt; project for a while, I had to check the docs to remind myself how some of this works. I noticed that the default approach to database credentials is still to use separate credentials for the host, port, and other variables. I'm using &lt;a class="reference external" href="https://aiven.io"&gt;Aiven&lt;/a&gt; databases (because I work there and managed databases are great for demo apps as well as real ones!) which supply connection strings, but Laravel supports these too.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've been doing a lot of database stuff lately, and not much PHP, so when I returned to make my first &lt;a class="reference external" href="https://laravel.com/"&gt;Laravel&lt;/a&gt; project for a while, I had to check the docs to remind myself how some of this works. I noticed that the default approach to database credentials is still to use separate credentials for the host, port, and other variables. I'm using &lt;a class="reference external" href="https://aiven.io"&gt;Aiven&lt;/a&gt; databases (because I work there and managed databases are great for demo apps as well as real ones!) which supply connection strings, but Laravel supports these too.&lt;/p&gt;
&lt;p&gt;For MySQL, the database connection string from Aiven is in the format:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
mysql://username:password&amp;#64;hostname:port/dbname
&lt;/pre&gt;
&lt;p&gt;You can remove the configuration for &lt;tt class="docutils literal"&gt;DB_*&lt;/tt&gt; and instead use the variable &lt;tt class="docutils literal"&gt;DATABASE_URL&lt;/tt&gt; with the full string.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro-tip&lt;/strong&gt;: Laravel's built in migrations (I was using &lt;a class="reference external" href="https://laravel.com/docs/8.x/starter-kits#laravel-breeze"&gt;Laravel Breeze&lt;/a&gt;) expect you to have &lt;tt class="docutils literal"&gt;mysql.sql_require_primary_key&lt;/tt&gt; disabled, I'm not sure why. It's default enabled on a lot of systems, including Aiven and Digital Ocean from what I've seen so far, so you may need change this setting if your migrations don't succeed.&lt;/p&gt;
&lt;p&gt;For PostgreSQL, the database connection string looks very similar to MySQL but with a pg-specific prefix:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
postgres://username:password&amp;#64;hostname:port/dbname
&lt;/pre&gt;
&lt;p&gt;Again, this connection string goes in the variable called &lt;tt class="docutils literal"&gt;DATABASE_URL&lt;/tt&gt;. For PostgreSQL, you also need to set &lt;tt class="docutils literal"&gt;DB_CONNECTION&lt;/tt&gt; to &lt;tt class="docutils literal"&gt;pgsql&lt;/tt&gt; so that Laravel knows to use this connection.&lt;/p&gt;
&lt;p&gt;It's not a big deal to separate the different variables needed for the database connection but especially as a person that spins up a lot of temporary databases to try stuff out and therefore has to update the connections all the time, this was a neat trick that saves me a minute or two every time.&lt;/p&gt;
</content><category term="mysql"/><category term="database"/><category term="laravel"/><category term="mysql"/><category term="php"/><category term="postgresql"/><category term="tech"/></entry><entry><title>Hold Attention on the Virtual Stage</title><link href="https://lornajane-net.pages.dev/posts/2021/hold-attention-on-the-virtual-stage" rel="alternate"/><published>2021-08-08T22:37:00+01:00</published><updated>2021-08-08T22:37:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2021-08-08:/posts/2021/hold-attention-on-the-virtual-stage</id><summary type="html">&lt;p class="first"&gt;How do I ensure that I keep my attendees' attention when I'm presenting virtually?&lt;/p&gt;
&lt;p class="last"&gt;&lt;strong&gt;I don't.&lt;/strong&gt;&lt;/p&gt;
</summary><content type="html">&lt;p&gt;How do I ensure that I keep my attendees' attention when I'm presenting virtually?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I don't.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I have seen a few &lt;strong&gt;tactics for holding people's attention&lt;/strong&gt; during virtual events. One is to require everyone present to perform a particular action such as typing a message or clicking an icon, several times during a session. Another is to require cameras on if the platform allows it. I find this patronising and also unnecessary. Perhaps because my audiences are adults, and my only obligation is to deliver great material - not to spoonfeed anyone.&lt;/p&gt;
&lt;p&gt;When I teach a class in person, I tell my attendees that the most important thing I am providing is space. &lt;strong&gt;Space for them to learn&lt;/strong&gt;, to ask, to try things, to really take on something new. Sure, I'm providing valuable knowledge and support but really they learn because there's a space for them to do so. That's why conference videos seem flat if you watch them after the event. But if you schedule your conference video viewing time, take lots of notes, and talk about what you saw afterwards? Then the virtual experience can come close (and I need to publish that draft blog post about watch parties).&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2021/08/Screenshot-from-2021-08-08-22-13-17.png"&gt;&lt;img alt="image1" class="alignnone size-large wp-image-4428" src="https://lornajane-net.pages.dev/images/2021/08/Screenshot-from-2021-08-08-22-13-17-1024x576.png" style="width: 584px; height: 329px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When I teach a virtual class, I can only create my end of the space. I invite and welcome my attendees' participation. &lt;strong&gt;I strive to make my digital content compelling&lt;/strong&gt; even though we're all tired of video, and I encourage people to use whatever is on the virtual platform to engage with the topic, each other, and me. That's all I can do.&lt;/p&gt;
&lt;p&gt;If you want to listen with half an ear, that's your decision although I feel a little sad on your behalf about the opportunity lost. If you want &lt;strong&gt;to empty your hands, to lean in&lt;/strong&gt; and let me lead you through a journey of knowledge, to take this time that we have together and take everything that you can from it? Also your decision and I love that we can make this happen across space (and time, if you're watching a pre-recorded me).&lt;/p&gt;
&lt;p&gt;Presenters can never force attention, all we can do is invite it, encourage it, and hold up our own side of the bargain. The rest is up to each attendee to choose whether they invest in themselves - or not.&lt;/p&gt;
</content><category term="tech"/><category term="events"/><category term="learning"/><category term="speaking"/></entry><entry><title>Define Navigation Structure in a Sphinx Project</title><link href="https://lornajane-net.pages.dev/posts/2021/define-navigation-structure-in-a-sphinx-project" rel="alternate"/><published>2021-06-13T19:11:00+01:00</published><updated>2021-06-13T19:11:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2021-06-13:/posts/2021/define-navigation-structure-in-a-sphinx-project</id><summary type="html">&lt;p class="first last"&gt;As a newcomer to &lt;a class="reference external" href="https://www.sphinx-doc.org/"&gt;Sphinx&lt;/a&gt;, I found many things confusing, but the table of contents or &amp;quot;toc&amp;quot; configuration was the thing that almost made me change my mind and pick a different platform! I now have a setup that works pretty well, using &lt;a class="reference external" href="https://github.com/executablebooks/sphinx-external-toc"&gt;Sphinx External TOC&lt;/a&gt;, so I thought I'd share my setup.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;As a newcomer to &lt;a class="reference external" href="https://www.sphinx-doc.org/"&gt;Sphinx&lt;/a&gt;, I found many things confusing, but the table of contents or &amp;quot;toc&amp;quot; configuration was the thing that almost made me change my mind and pick a different platform! I now have a setup that works pretty well, using &lt;a class="reference external" href="https://github.com/executablebooks/sphinx-external-toc"&gt;Sphinx External TOC&lt;/a&gt;, so I thought I'd share my setup.&lt;/p&gt;
&lt;p&gt;What I really like about the external_toc plugin is that all the navigation is defined in one place. I configured my project to look for a &lt;tt class="docutils literal"&gt;_toc.yml&lt;/tt&gt; file, and that contains all the entries that will be included, and what order they will be included in. I'm using the &lt;a class="reference external" href="https://github.com/pradyunsg/furo"&gt;Furo theme&lt;/a&gt; so the table of contents populates the whole of the left hand navigation bar and I find it helpful to have it all in one place rather than looking for &lt;tt class="docutils literal"&gt;toctree&lt;/tt&gt; directives nested all in different files and being assembled (or not, as I discovered) to create the navigation.&lt;/p&gt;
&lt;p&gt;Since each file can only be included in the navigation once, a particular page such as the &amp;quot;getting started&amp;quot; file can be listed specifically, with others from the same directory included using the &lt;tt class="docutils literal"&gt;glob&lt;/tt&gt; keyword to match the path with a wildcard. This is very helpful and I use it in a few places!&lt;/p&gt;
&lt;p&gt;However your navigation is constructed, you can use the &lt;tt class="docutils literal"&gt;.. tableofcontents&lt;/tt&gt; directive in a page at any level. Just like when using a &lt;tt class="docutils literal"&gt;toctree&lt;/tt&gt; directive, this displays the navigation entries below the current page, so it's useful for detailing subsections without needing to curate lists or indexes.&lt;/p&gt;
&lt;p&gt;If you're building a sphinx project yourself and have tips to add, I'd love to hear them. It's a great tool but I know that I have much to learn as well :)&lt;/p&gt;
</content><category term="tech"/><category term="documentation"/><category term="sphinx"/></entry><entry><title>Considering Developer Relations as a Career Move</title><link href="https://lornajane-net.pages.dev/posts/2021/considering-developer-relations" rel="alternate"/><published>2021-05-24T11:02:00+01:00</published><updated>2021-05-24T11:02:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2021-05-24:/posts/2021/considering-developer-relations</id><summary type="html">&lt;p class="first"&gt;I think that Developer Relations is a fabulous career, and I'm often asked how I got started, or how someone else could get started. There are a lot of people hiring for Developer Relations roles just now (including me!) so here are my tips.&lt;/p&gt;
&lt;p class="last"&gt;There are so many different Developer Relations roles (called &amp;quot;DevRel&amp;quot;) that I am definitely going to fall short of covering them all in any sort of detail! My own background is in Software Engineering, and I've mostly worked as a Developer Advocate since making the move into DevRel, so that's my context here.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I think that Developer Relations is a fabulous career, and I'm often asked how I got started, or how someone else could get started. There are a lot of people hiring for Developer Relations roles just now (including me!) so here are my tips.&lt;/p&gt;
&lt;p&gt;There are so many different Developer Relations roles (called &amp;quot;DevRel&amp;quot;) that I am definitely going to fall short of covering them all in any sort of detail! My own background is in Software Engineering, and I've mostly worked as a Developer Advocate since making the move into DevRel, so that's my context here.&lt;/p&gt;
&lt;div class="section" id="are-you-interested-in-people"&gt;
&lt;h3&gt;Are you Interested in People?&lt;/h3&gt;
&lt;p&gt;I am not sure why there's a stereotype that technical people aren't interested in other people. Sure, some people fit that description, but many don't. In DevRel, it's really all about other people. Trying to understand what someone else would find useful that they don't already know - and what they do already know so they don't get bored and wander off before you get to the point!&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Signs you may be interested in people include:&lt;/div&gt;
&lt;div class="line"&gt;* Having a blog where you write stuff down for others to use&lt;/div&gt;
&lt;div class="line"&gt;* Posting answers on StackOverflow for questions on topics you konw&lt;/div&gt;
&lt;div class="line"&gt;* Speaking at a usergroup to share your knowledge, or helping to organise one&lt;/div&gt;
&lt;div class="line"&gt;* Sharing your knowledge at work through presentations or written resources&lt;/div&gt;
&lt;div class="line"&gt;* Helping out with open source projects, especially getting people onboarded&lt;/div&gt;
&lt;div class="line"&gt;* Teaching classes to help others learn technical subjects&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;None of us has unlimited free time, but if you've done a little bit of some of these activities, and you'd like to do more, then I would consider you to be a good candidate for DevRel. My most frequent response to a CV is &amp;quot;why does this person think they want to do DevRel?&amp;quot; - if I can spot any two of: writing, speaking, teaching, or open source? Then that's a great indicator that this could be a good fit.&lt;/p&gt;
&lt;p&gt;The other big thing about all these items is that they are all within reach of all of us. So if you're considering a job in DevRel, then writing a blog post, giving a talk, or making open source contributions are all really good ways for you to work out if you like those things enough to have to do them often! They also make excellent portfolio pieces, but that's less important than giving yourself a chance to figure out what works for you, or what you'd like to get better at.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="empathy-and-the-other-requirements"&gt;
&lt;h3&gt;Empathy, and the Other Requirements&lt;/h3&gt;
&lt;p&gt;Empathy and acting in the interests of (mostly unknown) others is the basic requirement for Developer Relations. However there are some other traits that I see in successful DevRel types (and isn't that the point? To choose a job you'll enjoy and succeed in?).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Proactive&lt;/strong&gt;: The number one piece of advice I give to all new DevRel types is: we're all just making it up.&lt;/p&gt;
&lt;p&gt;There's no magic spell, no perfect approach, no one true strategy. Every product is different, every community is different, and sometimes you'll be far from home with a problem to overcome (or at least hundreds of strangers to make a good impression on). If you're the sort of person that can think of three possible options and pick one, in any scenario, you'll be fine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Organised&lt;/strong&gt;: I always talk about how creative DevRel is as a career, and it is, but if you are that rare combination of very creative and fiendishly well-organised, then I think you've found your place! The whole idea that a person can only be logical or creative, and you have to choose, has never made sense to me - and now I have a job that allows both brain-halves to be busy.&lt;/p&gt;
&lt;p&gt;On a more serious note, DevRel is often a very large number of small things all in flight at the same time. Even just keeping track of conference deadlines, acceptances, dates, preparation time .... you get the idea! Add in all the demo apps, blog posts, documentation/tutorials and whatever else it is we do (videos? podcasts? digital communities?) and there is a lot to keep track of. If you've ever been camping and not forgotten anything then consider yourself qualified :)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Communicative&lt;/strong&gt;: Most DevRel roles are remote anyway, since most teams want to have people spread out to get to know more communities. Even if you are office based, the communities that you serve will probably not be very local! So being able to communicate in a friendly manner, in person and in writing and on video, more or less all the time, is key. Keeping people updated and getting conversations going are important skills, but most of all it's about being prepared to put in the time to nurture the connections and build on them.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="taking-the-first-steps"&gt;
&lt;h3&gt;Taking the First Steps&lt;/h3&gt;
&lt;p&gt;So now you're convinced, how do you get started? Take a look at the skills that you have already and the job postings that are open. DevRel roles are very much about following your interests - the job is made much easier when your are advocating for something you actually like and use yourself :) Remember that DevRel is typically much more mixed than other technical teams in terms of the obvious, visible diversities - but it's also an excellent choice for career changers or anyone else with an unusual skill combination. You might feel that having started &amp;quot;late&amp;quot; in a tech career is a disadvantage but I have almost always found that the opposite is true.&lt;/p&gt;
&lt;p&gt;Are you thinking of moving into a DevRel career? What sorts of things get in your way? And if you're already there, what advice would you give someone following the same path?&lt;/p&gt;
&lt;/div&gt;
</content><category term="work"/><category term="career"/><category term="devrel"/></entry><entry><title>Best Things About Developer Advocacy</title><link href="https://lornajane-net.pages.dev/posts/2021/best-things-about-devrel" rel="alternate"/><published>2021-04-26T20:07:00+01:00</published><updated>2021-04-26T20:07:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2021-04-26:/posts/2021/best-things-about-devrel</id><summary type="html">&lt;p class="first last"&gt;There are some excellent articles around about what a Developer Advocate does, but have you ever wondered WHY we do what we do? I tried to articulate this when chatting with another developer lately, and I'm still thinking about it! So I thought I'd write it down to share a little more widely why I love this strange, misshapen career as much as I do.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;There are some excellent articles around about what a Developer Advocate does, but have you ever wondered WHY we do what we do? I tried to articulate this when chatting with another developer lately, and I'm still thinking about it! So I thought I'd write it down to share a little more widely why I love this strange, misshapen career as much as I do.&lt;/p&gt;
&lt;div class="section" id="it-s-alarmingly-varied"&gt;
&lt;h3&gt;It's Alarmingly Varied&lt;/h3&gt;
&lt;p&gt;Sometimes when I'm doing career development talks or outreach (I'm a &lt;a class="reference external" href="https://www.stem.org.uk/stem-ambassadors"&gt;STEM Ambassador&lt;/a&gt; ) people will ask &amp;quot;Can you describe a typical day?&amp;quot; and the answer is ... not really! Advocacy requires a lot of different skillsets and you can expect in any given week to be planning, writing, speaking, recording, coding, fixing, and attending as many meetings as anyone else. Even when I think my schedule is pretty much under control, there's always someone with a technical question, a new issue or pull request, something that needs my review, or a request from another department.&lt;/p&gt;
&lt;p&gt;Honestly? I love it. I cannot at all imagine going back to doing a fairly steady engineering job and reliably delivering applications. Even when I last did that sort of role, I was part-time and doing advocacy/consulting/training for the rest of the time - mostly because I enjoy the variety! Being able to handle interruptions and handle the context-switching are both very key skills for a job like this. Deep work does require blocks of time and I still write and code fairly in-depth things, but it's all in maximum 90 minute blocks! I will always be convinced that the years of doing open source projects with whatever time I had was the best possible training for this working style.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="it-s-surprisingly-technical"&gt;
&lt;h3&gt;It's Surprisingly Technical&lt;/h3&gt;
&lt;p&gt;Developer Advocacy has been an incredible way to pick up a broad swathe of new technical skills in a way that I simply didn't get the opportunity to do in a full time engineering role. When I took my first Developer Advocacy job, I knew one programming language very well. I'm now baseline competent in four tech stacks and I can wrangle the dependency managers and code from another few. There is much less gatekeeping of what you're &amp;quot;qualified&amp;quot; to do so if you can produce some content or a plugin that could help a developer: then you just do it. It's also made me absolutely fearless with new technology because I meet a new codebase at least every week and I greatly value this confidence now that I've found it.&lt;/p&gt;
&lt;p&gt;Just like tech support, the DevRel roles put you on the spot with any of your employer's products, combined with any tech stack the user might have picked, at more or less any time. This should be horrifying but in fact I find it fascinating and refreshing. The different tech communities have different approaches to so many different things, and finding the familiar reference points for someone to understand your explanation is challenging. It's incredibly rewarding to be able to help many different people with their questions and feel like you're making a positive impact.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="it-s-a-helping-profession"&gt;
&lt;h3&gt;It's a Helping Profession&lt;/h3&gt;
&lt;p&gt;This part might be too sappy but bear with me, if you can. As a Developer Advocate, I help people and I empower them to reach greater achievements. I love being a multiplier - as an engineer I can only build so many things myself. But as a writer and a speaker, I can aid and abet a whole community of developers who are all making awesome things themselves. It's an amazing privilege to choose a profession where I so regularly feel like I'm making a difference to others.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="it-s-deeply-creative"&gt;
&lt;h3&gt;It's Deeply Creative&lt;/h3&gt;
&lt;p&gt;Developer Advocacy is incredibly creative. Software engineering by itself is already very creative, by definition you're creating things. But Developer Advocacy, to me at least, is like being asked to write at least one very geeky fairy tale every day of the week. Thinking of examples, ways to explain things, concepts to inspire and stories to help humans digest big technical ideas - then putting together a strategy to tie it all together ... Developer Advocates are ideas people because that's the job!&lt;/p&gt;
&lt;p&gt;Creating content is all about educating and informing, and helping your audience to take away something of value from the time they spent with you and/or your content. As a day job, this can be slightly stressful &amp;quot;Quick! Be inspiring!&amp;quot; but having the freedom to think about how to present the idea or demonstrate the feature is something that I'll always value.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="it-s-not-forever"&gt;
&lt;h3&gt;It's Not Forever&lt;/h3&gt;
&lt;p&gt;Developer Advocacy improves the not-just-code skills that are so valuable in very senior engineers: the ability to communicate well in written form, the ability to present complex ideas to others, the ability to handle multiple incompatible tasks all at once. I'm generalising for engineers when Developer Advocates are also Data Analysts, Ops people, consultants, and all sorts - but the point still stands.&lt;/p&gt;
&lt;p&gt;Doing a role or two in DevRel makes well-rounded technical employees and it's more approachable than people realise. On a very personal level, this has been the career that found me and made me realise I could be good at something, and I don't think we share the joys widely enough! What do you love about your job? Tell me :)&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="career"/><category term="devrel"/><category term="work"/></entry><entry><title>Measuring Repo Community Health with GitHub's API</title><link href="https://lornajane-net.pages.dev/posts/2021/measuring-github-community-health" rel="alternate"/><published>2021-04-14T09:19:00+01:00</published><updated>2021-04-14T09:19:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2021-04-14:/posts/2021/measuring-github-community-health</id><summary type="html">&lt;p class="first"&gt;I'm on record saying that &lt;a class="reference external" href="https://developerrelations.com/developer-experience/github-is-your-documentation-landing-page"&gt;GitHub is your Landing Page&lt;/a&gt; and when I think about companies having open source profiles, I think about how many developers will have the first contact with them on GitHub. If it's a code example you're looking for then like it or not, GitHub is considered a search engine by many developers.&lt;/p&gt;
&lt;p&gt;With that in mind, I wanted to look at GitHub's &lt;a class="reference external" href="https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions"&gt;Community Health measure&lt;/a&gt; of the repositories I'm responsible for. You can view each repo's community page separately through the web interface (look under &amp;quot;Insights&amp;quot;) but that's not especially scalable if you have a lot of projects to track.&lt;/p&gt;
&lt;p class="last"&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2021/04/Screenshot_2021-04-13-lornajane-PHP-Web-Services.png"&gt;&lt;img alt="screeshot of the project's /community page, showing low completion" class="alignnone size-large wp-image-4375" src="https://lornajane-net.pages.dev/images/2021/04/Screenshot_2021-04-13-lornajane-PHP-Web-Services-1024x509.png" style="width: 584px; height: 290px;" /&gt;&lt;/a&gt;&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I'm on record saying that &lt;a class="reference external" href="https://developerrelations.com/developer-experience/github-is-your-documentation-landing-page"&gt;GitHub is your Landing Page&lt;/a&gt; and when I think about companies having open source profiles, I think about how many developers will have the first contact with them on GitHub. If it's a code example you're looking for then like it or not, GitHub is considered a search engine by many developers.&lt;/p&gt;
&lt;p&gt;With that in mind, I wanted to look at GitHub's &lt;a class="reference external" href="https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions"&gt;Community Health measure&lt;/a&gt; of the repositories I'm responsible for. You can view each repo's community page separately through the web interface (look under &amp;quot;Insights&amp;quot;) but that's not especially scalable if you have a lot of projects to track.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2021/04/Screenshot_2021-04-13-lornajane-PHP-Web-Services.png"&gt;&lt;img alt="screeshot of the project's /community page, showing low completion" class="alignnone size-large wp-image-4375" src="https://lornajane-net.pages.dev/images/2021/04/Screenshot_2021-04-13-lornajane-PHP-Web-Services-1024x509.png" style="width: 584px; height: 290px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So for a more repeatable approach, I wanted to use the GitHub API. Frustratingly the community health metric isn't in the collection endpoints, you need to fetch data per-repository to get it. Here's the script that I use; it fetches the first 100 public repos (if you have more than 100, you'll need to loop and fetch additional pages of results) of the given org or user. It prints all repos and number of stars, then for repos with more than 10 stars, it also fetches and output the community health measure.&lt;/p&gt;
&lt;p&gt;The whole thing outputs some sort of pipe-separated-sort-of format ... one day I will make my hacky code more perfect before I share it, but today is not that day. LibreOffice had no problem ingesting the result when stored in a text file, so I'm calling it &amp;quot;good enough&amp;quot;!&lt;/p&gt;
&lt;p&gt;Anyway, here's the script - you should set a &lt;a class="reference external" href="https://github.com/settings/tokens"&gt;GitHub access token&lt;/a&gt; as &lt;tt class="docutils literal"&gt;GITHUB_TOKEN&lt;/tt&gt; and the org or user to use as &lt;tt class="docutils literal"&gt;GITHUB_ORG&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code python3 literal-block"&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;requests&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GITHUB_TOKEN&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GITHUB_ORG&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;Authorization&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;token &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;Accept&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;application/vnd.github.v3+json&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# all public repos&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://api.github.com/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;repos_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;orgs/&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/repos?type=public&amp;amp;per_page=100&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;repos_req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repos_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;repos_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repos_req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# print header row&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Project | Stars | Health&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;repos_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'full_name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; | &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'stargazers_count'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'stargazers_count'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;repos/&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'full_name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/community/profile&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;| &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'health_percentage'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;% he&lt;/span&gt;&lt;span class="s2"&gt;alth&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# if this is 100, it's time to build pagination&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; public repos in total&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;I was surprised when I looked around that I couldn't find an existing script for this, so I thought I had better share mine. That's how the open source community works, after all! Tweaks, suggestions and additions are all welcome via the comments box, I'm happy to hear if this is useful and how you evolved it for your own needs.&lt;/p&gt;
</content><category term="tech"/><category term="community"/><category term="github"/><category term="metrics"/><category term="python"/></entry><entry><title>From Spreadsheet to Database with ddlgenerator</title><link href="https://lornajane-net.pages.dev/posts/2021/from-spreadsheet-to-database-with-ddlgenerator" rel="alternate"/><published>2021-03-22T16:33:00+00:00</published><updated>2021-03-22T16:33:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2021-03-22:/posts/2021/from-spreadsheet-to-database-with-ddlgenerator</id><summary type="html">&lt;p class="first last"&gt;I've been using some &lt;a class="reference external" href="https://www.kaggle.com/datasets"&gt;fun open data sets&lt;/a&gt; recently but I'm more familiar with databases than with traditional data science formats (this one is CSV). &lt;a class="reference external" href="https://www.enterprisedb.com/postgres-tutorials/how-import-and-export-data-using-csv-files-postgresql"&gt;Importing CSV into Postgres&lt;/a&gt; is fairly well-established, but you need a table structure to import into ... and I was looking at a CSV with 50 columns (for my &lt;a class="reference external" href="https://aiven.io/blog/discover-exoplanets-with-postgresql"&gt;post about exoplanets on the Aiven blog&lt;/a&gt;). Enter this neat utility: &lt;a class="reference external" href="https://pypi.org/project/ddlgenerator/"&gt;ddlgenerator&lt;/a&gt;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've been using some &lt;a class="reference external" href="https://www.kaggle.com/datasets"&gt;fun open data sets&lt;/a&gt; recently but I'm more familiar with databases than with traditional data science formats (this one is CSV). &lt;a class="reference external" href="https://www.enterprisedb.com/postgres-tutorials/how-import-and-export-data-using-csv-files-postgresql"&gt;Importing CSV into Postgres&lt;/a&gt; is fairly well-established, but you need a table structure to import into ... and I was looking at a CSV with 50 columns (for my &lt;a class="reference external" href="https://aiven.io/blog/discover-exoplanets-with-postgresql"&gt;post about exoplanets on the Aiven blog&lt;/a&gt;). Enter this neat utility: &lt;a class="reference external" href="https://pypi.org/project/ddlgenerator/"&gt;ddlgenerator&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I used it for a CSV file and put the data into PostgreSQL but on closer inspection I realised that this tool can ingest a great selection of different formats, including JSON, YAML and even HTML. And it can create the table structure for any of the dialects that &lt;a class="reference external" href="https://docs.sqlalchemy.org/en/14/"&gt;SQLAlchemy&lt;/a&gt; can speak - I expected PostgreSQL and MySQL support but the selection is pretty extensive, including SQLite, SQL Server and even Oracle databases.&lt;/p&gt;
&lt;p&gt;The tool itself is so simple it's almost disappointing! Install it with &lt;tt class="docutils literal"&gt;pip&lt;/tt&gt;, and it's a CLI tool.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ pip install ddlgenerator
$ ddlgenerator postgres planets.csv
&lt;/pre&gt;
&lt;p&gt;The output is the SQL statements to run on the platform you specified (e.g. &lt;tt class="docutils literal"&gt;postgres&lt;/tt&gt;) to create the table structure for the input data (e.g. &lt;tt class="docutils literal"&gt;planets.csv&lt;/tt&gt;). I used the built in support to import the CSV file to my new PostgreSQL database, but &lt;tt class="docutils literal"&gt;ddlgenerator&lt;/tt&gt; can also generate all the &lt;tt class="docutils literal"&gt;INSERT&lt;/tt&gt; statements you'd need to get SQL statements to put the data in the table as well, so that's neat.&lt;/p&gt;
&lt;p&gt;If you're also wrangling data, this is one more tool for the box. And now I've written it down, I might remember it next time I need it too :)&lt;/p&gt;
</content><category term="tech"/></entry><entry><title>Intro to RPi Pico with Mini Memory Game</title><link href="https://lornajane-net.pages.dev/posts/2021/intro-to-rpi-pico-with-mini-memory-game" rel="alternate"/><published>2021-02-08T21:49:00+00:00</published><updated>2021-02-08T21:49:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2021-02-08:/posts/2021/intro-to-rpi-pico-with-mini-memory-game</id><summary type="html">&lt;p class="first"&gt;I am the proud owner of a &lt;a class="reference external" href="https://www.raspberrypi.org/documentation/pico/getting-started/"&gt;Raspberry Pi Pico&lt;/a&gt; which is a dinky microcontroller from the lovely people at Raspberry Pi. I &lt;em&gt;also&lt;/em&gt; have the &lt;a class="reference external" href="https://shop.pimoroni.com/products/pico-rgb-keypad-base"&gt;RGB Pico Keypad Base&lt;/a&gt;, because my husband was buying something else from &lt;a class="reference external" href="https://shop.pimoroni.com/"&gt;Pimoroni&lt;/a&gt; (3D printer filament, more on that another day) and we love them and ... here we are :)&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2021/02/wp-1612817806936-scaled.jpg"&gt;&lt;img alt="image1" class="alignnone size-medium wp-image-4362" src="https://lornajane-net.pages.dev/images/2021/02/wp-1612817806936-300x169.jpg" style="width: 300px; height: 169px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p class="last"&gt;I've tried to write down and link to the stuff I found useful along the way with this project, in case it helps you too. Well, no, really so I can come back to it next time I'm using one of these!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I am the proud owner of a &lt;a class="reference external" href="https://www.raspberrypi.org/documentation/pico/getting-started/"&gt;Raspberry Pi Pico&lt;/a&gt; which is a dinky microcontroller from the lovely people at Raspberry Pi. I &lt;em&gt;also&lt;/em&gt; have the &lt;a class="reference external" href="https://shop.pimoroni.com/products/pico-rgb-keypad-base"&gt;RGB Pico Keypad Base&lt;/a&gt;, because my husband was buying something else from &lt;a class="reference external" href="https://shop.pimoroni.com/"&gt;Pimoroni&lt;/a&gt; (3D printer filament, more on that another day) and we love them and ... here we are :)&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2021/02/wp-1612817806936-scaled.jpg"&gt;&lt;img alt="image1" class="alignnone size-medium wp-image-4362" src="https://lornajane-net.pages.dev/images/2021/02/wp-1612817806936-300x169.jpg" style="width: 300px; height: 169px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I've tried to write down and link to the stuff I found useful along the way with this project, in case it helps you too. Well, no, really so I can come back to it next time I'm using one of these!&lt;/p&gt;
&lt;div class="section" id="hardware-setup"&gt;
&lt;h3&gt;Hardware Setup&lt;/h3&gt;
&lt;p&gt;The picos ship without the legs soldered on, so that was step 1. There's some good info in the &lt;a class="reference external" href="https://learn.pimoroni.com/tutorial/hel/getting-started-with-pico"&gt;Pimoroni getting started tutorial for pico&lt;/a&gt; but I would also recommend the &lt;a class="reference external" href="https://www.tomshardware.com/uk/how-to/solder-pins-raspberry-pi-pico"&gt;How to solder pins to your RPi Pico&lt;/a&gt; article from Tom's Hardware, it was a lot like having &lt;a class="reference external" href="https://twitter.com/biglesp"&gt;&amp;#64;biglesp&lt;/a&gt; actually here to help me!&lt;/p&gt;
&lt;p&gt;Tl;DR put the legs into a breadboard and then solder the board on top, that way everything is aligned correctly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="firmware"&gt;
&lt;h3&gt;Firmware&lt;/h3&gt;
&lt;p&gt;I used the &lt;a class="reference external" href="https://github.com/pimoroni/pimoroni-pico/releases"&gt;custom firmware from Pimoroni&lt;/a&gt; because I needed the library for the rgb keypad. To add the firmware, connect the chip to your computer in bootloader mode (hold down the button when you connect and for another couple of seconds) and it will appear as USB storage. Put the firmware &lt;tt class="docutils literal"&gt;.uf2&lt;/tt&gt; file into the USB storage and it will reboot itself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="tools"&gt;
&lt;h3&gt;Tools&lt;/h3&gt;
&lt;p&gt;A lot of the tutorials suggest something called Thonny but I'm a software developer with accessibility needs, so I steered clear of that. Instead I used a neat tool called &lt;a class="reference external" href="https://github.com/dhylands/rshell"&gt;rshell&lt;/a&gt; which you can install via &lt;tt class="docutils literal"&gt;pip&lt;/tt&gt;. It is designed for micropython and has a repl and some file handling features; I found it did everything I needed.&lt;/p&gt;
&lt;p&gt;One thing to look out for is that the current release &lt;tt class="docutils literal"&gt;0.0.28&lt;/tt&gt; seemed to be a little bit strange about both its default values and it had some problems with file uploads just hanging on particular files. However it's an active project and the &lt;tt class="docutils literal"&gt;pico&lt;/tt&gt; branch worked like a charm. Clone the repo, check out the branch, and use &lt;tt class="docutils literal"&gt;r.py&lt;/tt&gt; in place of your usual rshell command. This information will probably be outdated quite quickly though so it's just here in case it helps.&lt;/p&gt;
&lt;p&gt;Start &lt;tt class="docutils literal"&gt;rshell&lt;/tt&gt; with your pico already attached:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
rshell -p /dev/ttyAMC0 --buffer-size=512
&lt;/pre&gt;
&lt;p&gt;When it starts, it'll tell you what it thinks your board is called, probably &lt;tt class="docutils literal"&gt;pyboard&lt;/tt&gt;, in which case the files on the device will appear as being in the path &lt;tt class="docutils literal"&gt;/pyboard/&lt;/tt&gt;. The RPi Pico runs &lt;tt class="docutils literal"&gt;main.py&lt;/tt&gt; when it boots, and you can copy it up with a command like:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
cp main.py /pyboard/
&lt;/pre&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://datasheets.raspberrypi.org/pico/raspberry-pi-pico-python-sdk.pdf"&gt;Raspberry Pi Pico DataSheet&lt;/a&gt; has some excellent information about getting set up and connected, and also has some good examples. It looks like a serious document but the &amp;quot;Say Hello World&amp;quot; and the &amp;quot;Blink an LED&amp;quot; section should work with any repl and any Pico.&lt;/p&gt;
&lt;blockquote&gt;
It's &lt;tt class="docutils literal"&gt;Ctrl+x&lt;/tt&gt; to exit the rshell repl. It does helpfully point this out when it starts, but of course that's not the moment when you need the info.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="actual-development-cycle"&gt;
&lt;h3&gt;Actual development cycle&lt;/h3&gt;
&lt;p&gt;I find with a lot of hardware projects that I can get to &amp;quot;hello world&amp;quot; quite quickly but then there's a whole cycle of ejecting the disk or attaching to get debug output that makes the feedback loop pretty tedious. With &lt;tt class="docutils literal"&gt;rshell&lt;/tt&gt; I am using this one-liner after each time I've saved my &lt;tt class="docutils literal"&gt;mydemo.py&lt;/tt&gt; file:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
rshell cp ../pi-pico/mydemo.py /pyboard/ &amp;amp;&amp;amp; rshell repl '~' import mydemo '~'
&lt;/pre&gt;
&lt;p&gt;Take the &lt;tt class="docutils literal"&gt;'~'&lt;/tt&gt; off the end if you want to keep the repl active and watch the debug output.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="mini-memory-game-the-actual-software"&gt;
&lt;h3&gt;Mini Memory Game (The Actual Software)&lt;/h3&gt;
&lt;p&gt;This bit is the most fun because it's when the lights light up and there are buttons to press :)&lt;/p&gt;
&lt;p&gt;&lt;p&gt;&lt;a class="reference external image-reference" href="https://www.flickr.com/photos/magickevin/50923670126/"&gt;&lt;img alt="IMG_5503" src="https://live.staticflickr.com/31337/50923670126_671de76b7a_w.jpg" style="width: 202px; height: 360px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"&gt;&lt;/script&gt;&lt;/p&gt;&lt;p&gt;I made a game! It works like this:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A pattern of lights is shown for a few seconds&lt;/li&gt;
&lt;li&gt;The lights are shuffled around the board&lt;/li&gt;
&lt;li&gt;The user presses pairs of buttons to swap lights over until the lights match the first pattern&lt;/li&gt;
&lt;li&gt;You win! Press the flashing green button in the corner to get the next level&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Posting all the code into a blog post doesn't seem that useful so instead, check out my &lt;a class="reference external" href="https://github.com/lornajane/fun-with-rpi-pico"&gt;GitHub repo of fun RPi Pico things&lt;/a&gt;.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;There are a bunch of other resources that I found useful:&lt;/div&gt;
&lt;div class="line"&gt;- &lt;a class="reference external" href="https://github.com/pimoroni/pimoroni-pico/tree/main/micropython/examples/pico_rgb_keypad"&gt;An excellent demo&lt;/a&gt; from Pimoroni but hidden in a micropython directory so look closely!&lt;/div&gt;
&lt;div class="line"&gt;- Also from Pimoroni &lt;a class="reference external" href="https://github.com/pimoroni/pimoroni-pico/tree/main/libraries/pico_rgb_keypad"&gt;A library with docs and examples&lt;/a&gt;&lt;/div&gt;
&lt;div class="line"&gt;- &lt;a class="reference external" href="https://github.com/pimoroni/pimoroni-pico/tree/main/libraries/pico_rgb_keypad"&gt;Everything about RPi Pico on Tom's Hardware&lt;/a&gt;, that's a link to the roundup post and I had 3 or 4 tabs open at one point, it is all excellent.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This Raspberry Pi Pico is a very different beast from the traditional approach of a tiny linux computer, and is more like the ESPs that I've used before. But they're quite approachable and MicroPython is fairly friendly to code with as well, so I'm looking forward to hearing what everyone is making!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="hardware"/><category term="python"/><category term="raspi"/></entry><entry><title>Lab Book Meets Bullet Journal</title><link href="https://lornajane-net.pages.dev/posts/2021/lab-book-meets-bullet-journal" rel="alternate"/><published>2021-01-31T21:39:00+00:00</published><updated>2021-01-31T21:39:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2021-01-31:/posts/2021/lab-book-meets-bullet-journal</id><summary type="html">&lt;p class="first last"&gt;I enjoy a productivity hack as much as the next person, and I'm often asked about my setup. My background is in engineering (I hold an MEng in Electronic Engineering from York) and recognise the legacy of the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Lab_notebook"&gt;lab book&lt;/a&gt; in much of what I do. If you're new to the lab book concept, check out this &lt;a class="reference external" href="https://colinpurrington.com/tips/lab-notebooks/"&gt;excellent article on how to maintain one properly&lt;/a&gt;. My practice breaks a LOT of the lab book rules and is also heavily influenced by the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Bullet_journal"&gt;Bullet Journal&lt;/a&gt; trend (if you have time to spare, the &lt;a class="reference external" href="https://www.instagram.com/explore/tags/bulletjournal/"&gt;instagram #bulletjournal hashtag&lt;/a&gt; gives a prettier introduction), which is a way to combine the benefits of productivity and processing information by writing it. Today's post gives a quick tour of my process.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I enjoy a productivity hack as much as the next person, and I'm often asked about my setup. My background is in engineering (I hold an MEng in Electronic Engineering from York) and recognise the legacy of the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Lab_notebook"&gt;lab book&lt;/a&gt; in much of what I do. If you're new to the lab book concept, check out this &lt;a class="reference external" href="https://colinpurrington.com/tips/lab-notebooks/"&gt;excellent article on how to maintain one properly&lt;/a&gt;. My practice breaks a LOT of the lab book rules and is also heavily influenced by the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Bullet_journal"&gt;Bullet Journal&lt;/a&gt; trend (if you have time to spare, the &lt;a class="reference external" href="https://www.instagram.com/explore/tags/bulletjournal/"&gt;instagram #bulletjournal hashtag&lt;/a&gt; gives a prettier introduction), which is a way to combine the benefits of productivity and processing information by writing it. Today's post gives a quick tour of my process.&lt;/p&gt;
&lt;div class="section" id="lab-book-and-daily-log"&gt;
&lt;h3&gt;Lab Book and Daily Log&lt;/h3&gt;
&lt;p&gt;The lab book is supposed to be a running record of what you did, whether that's a phone appointment, experiment setup, results, things you were thinking about, or anything else. I can't lay my hands on my last &amp;quot;official&amp;quot; lab books but even as a university student I know there were many colours of pen and the occasional glittery sticker!&lt;/p&gt;
&lt;p&gt;In bullet journal language, this is a &amp;quot;Daily Log&amp;quot; although I chuck everything in there including meeting notes, plans, whatever. Bullet Journalling has its own rules about the bullets to use but I was already using an adapted (err, probably misremembered!) version of &lt;a class="reference external" href="https://patrickrhone.com/dashplus/"&gt;DashPlus&lt;/a&gt; so I kind of do my own thing. As long as you have a system and it isn't confusing to you, then it's fine.&lt;/p&gt;
&lt;p&gt;My symbols can be summarised as &amp;quot;todo&amp;quot;, &amp;quot;done&amp;quot;, &amp;quot;info&amp;quot; and &amp;quot;todo but captured somewhere more useful than here&amp;quot;, and that seems to cover most things. The main advantage is that I just stick everything on the page in front of me, with the pen I'm holding, and then work the rest out later.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="bullet-journal-additions"&gt;
&lt;h3&gt;Bullet Journal Additions&lt;/h3&gt;
&lt;p&gt;First of all, I'm using an A5 filofax, which is all sorts of no-no for a lab book! I love it though, it's everything I liked about an A5 notebook, but with added printables and none of the pain of changing to a new notebook. And yes, I own the special hole punch. I punch scrappy bits of paper, handouts, printed things, leaflets ... it's so good.&lt;/p&gt;
&lt;p&gt;I keep a few long-running pages, that sit in sections away from the noise of the daily log. One is a list of content ideas for this blog (suggestions always welcome). Another is a list labelled &amp;quot;people&amp;quot; where I capture the things to say to a particular individual when I next meet with them. It saves interrupting people that you have a sync with anyway, and also saves forgetting things.&lt;/p&gt;
&lt;p&gt;At the back of the planner I have recently added a &amp;quot;Projects&amp;quot; section, keeping track of the bigger things that have several ingredients, and will run over several weeks. This is something I've had in mind for a while but it's too soon to say if it's helpful. I'm also experimenting with &lt;a class="reference external" href="https://notes.andymatuschak.org/Evergreen_notes"&gt;evergreen notes&lt;/a&gt; and I might not need both.&lt;/p&gt;
&lt;p&gt;I do use a weekly spread but since each day is basically a list of meetings and/or tasks, and maybe a meal plan, I use a vertical spread. You don't see many of these but it makes sense to my brain! I sometimes make my own layout and then it has some custom weekly checkboxes with &amp;quot;phone parents&amp;quot; and cleaning tasks and things, which I find useful. I use the space to add all my appointments first (from work and personal calendars), then I work out my work priorities for the week and where they can fit in. No plan survives contact with the enemy but I like to try!&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2021/01/20210131_210643-scaled.jpg"&gt;&lt;img alt="Filofax with dotted paper and homemade dividers, and week plan with columns for Mon/Tue/Wed" class="alignnone size-medium wp-image-4355" src="https://lornajane-net.pages.dev/images/2021/01/20210131_210643-300x169.jpg" style="width: 300px; height: 169px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The shot above shows both the planner and the week layout so you have some sense of what I'm working with. I'm a dotted paper kind of girl although squares are good too! Sometimes I print other templates (the printer takes A5 paper which is handy) for lists but not consistently.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="weekly-reset"&gt;
&lt;h3&gt;Weekly Reset&lt;/h3&gt;
&lt;p&gt;Each week (usually Sunday evening) I create a list for the week. It contains anything from last week's list that is still worth doing (if something gets copied over multiple weeks running, it's a great time to reflect on why, or if it should be here at all), plus all the things I captured in the last week. I leave a bit of space around it, new stuff often shows up here during the week as well and that's OK. I use a highlighter on the items I want to do first/next so I can quickly get started on Monday morning.&lt;/p&gt;
&lt;p&gt;The other thing that I do (either Friday afternoon or in the Sunday evening slot) is write down the &amp;quot;shipped&amp;quot; things from the week just finished. Some of the work I do is very tangible and you can count the blog posts or pull requests - but quite a lot of it is planning, talking, connecting, listening, and it's important to remember that this is all important too! I make a quick list of what was done in the week, and for any big achievements, I transfer them to my &amp;quot;scrapbook&amp;quot;. The scrapbook is a text file in cloud storage where I keep a month-by-month list of things I'm really proud of. It really helps at annual review time and also when I need to remember when things have gone well (usually in a moment where that isn't the case).&lt;/p&gt;
&lt;p&gt;With last week's &amp;quot;shipped&amp;quot; items, a fresh list for the new week, and a weekly spread - I'm ready for Monday!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-about-digital"&gt;
&lt;h3&gt;What about Digital?&lt;/h3&gt;
&lt;p&gt;I use a digital calendar with reminders for everything that is time-sensitive, such as which night the bin goes out, and my work calendar is also digital. I share a set of Evernote lists with my husband which are mostly for grocery shopping and hardware store shopping, so we can just check the list when we're in either of those places.&lt;/p&gt;
&lt;p&gt;In theory I also use todotxt for personal stuff but at the moment a) I'm having problems with android apps and todotxt and Nextcloud and b) we're in lockdown anyway which really seems to have reduced the workload! So a post-it note in my planner works fine for now but it does have &amp;quot;fix todo system&amp;quot; on it :)&lt;/p&gt;
&lt;p&gt;I haven't moved to digital note-taking although I'm not morally opposed to it, I just haven't found a system that made me want to jump yet. Pen and paper is so flexible/portable/reliable/accessible and I know I process and retain information better this way. One day, both the input mechanism and the abliity to search what I wrote will win me over I am sure.&lt;/p&gt;
&lt;p&gt;What's your setup? I'm always on the lookout for more ideas and improvements, so if you would like you share your story, then I would like to hear it!&lt;/p&gt;
&lt;/div&gt;
</content><category term="work"/><category term="productivity"/></entry><entry><title>Digital Developer Relations: Past and Future</title><link href="https://lornajane-net.pages.dev/posts/2020/digital-developer-relations-past-and-future" rel="alternate"/><published>2020-12-23T09:24:00+00:00</published><updated>2020-12-23T09:24:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-12-23:/posts/2020/digital-developer-relations-past-and-future</id><summary type="html">&lt;p class="first last"&gt;I work in Developer Relations, or &amp;quot;DevRel&amp;quot;, and 2020 was a strange year with travel restrictions that meant that the entire industry did everything online, or digitally. I see this as a very positive progression of the way we work as DevRel professionals, and I understand that might be controversial, but I'd like to back up my position so hear me out (and then let me know what you think!).&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I work in Developer Relations, or &amp;quot;DevRel&amp;quot;, and 2020 was a strange year with travel restrictions that meant that the entire industry did everything online, or digitally. I see this as a very positive progression of the way we work as DevRel professionals, and I understand that might be controversial, but I'd like to back up my position so hear me out (and then let me know what you think!).&lt;/p&gt;
&lt;p&gt;Digital is an obvious way to do DevRel. Where are the developers? On the internet, on &lt;a class="reference external" href="https://stackoverflow.com/"&gt;StackOverflow&lt;/a&gt; and reading &lt;a class="reference external" href="https://dev.to/"&gt;dev.to&lt;/a&gt;, chatting on Discord and Twitter. To meet all those developers, where they are at, is a developer advocacy superpower, not a second-best option for the year where we couldn't travel.&lt;/p&gt;
&lt;div class="section" id="digital-devrel-is-inclusive"&gt;
&lt;h3&gt;Digital DevRel is Inclusive&lt;/h3&gt;
&lt;p&gt;In 2020 I interacted with a whole lot of new people and new types of people than I normally meet in a year. People were joining &amp;quot;local&amp;quot; user groups from all over the world to hear about a specific topic they had an interest in. People were joining their first meetups or conferences because they can't travel in other years either and so this year they can be part of the crowd, just like every one else. My friend in New Zealand gushed to me about how many events were online and how much she'd enjoyed being able to access content that is normally locked up in a one-time-and-place-only offering. And no, recordings from things that are supposed to be in-person are not the same thing.&lt;/p&gt;
&lt;p&gt;There is work to do on figuring out the event and content formats that work well, but I don't want to tell all those new voices that I've talked to that we aren't interested in them any more now we've got our &amp;quot;proper&amp;quot; events back. We all belong in this industry, including people who won't ever be at an expensive conference with me. As a side benefit, content delivered digitally often works pretty well asynchronously too. A recorded talk that was played back with a lively text channel alongside it is a decent conference experience AND a decent catch-up experience! Building content this way is inclusive across time as well, because someone new to this topic in two years' time can still access my talk as a first-class citizen at the moment they need that content.&lt;/p&gt;
&lt;p&gt;The much more inclusive nature of digital working makes a difference for DevRel as a group of people as well as for the communities we serve. Without the travel component that is &amp;quot;normal&amp;quot; in these types of roles, many more excellent humans could contribute to our efforts to reach and support more developer communities. We wouldn't have to change roles if life brings us reasons to travel less or work more flexibly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="digital-devrel-is-impactful"&gt;
&lt;h3&gt;Digital DevRel is Impactful&lt;/h3&gt;
&lt;p&gt;Without the noise and stress of travel, my personal experience was that I had a very impactful year. I spoke at digital events and the preparation time was the same but there was no jetlag/travel overhead and I was able focus much more and create more thoughtful and meaningful content, code and support. As a side effect I was much more available to my team and my communities, and more effective in what I could produce.&lt;/p&gt;
&lt;p&gt;The digital world enabled a new depth to connection. I would interact with someone at an event, who became part of my network on Twitter, joined a Twitch stream, participated in other events I was at ... I genuinely felt that I built more valuable connections this year than I do when I'm standing by a table on the other side of the world giving out t-shirts, because of the echoes of the digital connection and how we can surface ongoing touchpoints on those platforms.&lt;/p&gt;
&lt;p&gt;Digital interactions are built for scale - StackOverflow is a great example of one type of interaction; Twitch is a great example of a totally different type of interaction. This year of being forced to think about designing content for new platforms I hope will encourage us all to keep thinking hard about this stuff and keep on learning.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Written content is the gold standard of how to share information.&lt;/strong&gt; Words need relatively little bandwidth to deliver, and can convey a lot of information; this really matters to a large portion of the world. Words can be read aloud, translated, or simply displayed bigger and with better contrast ... the increased reach because of how versatile words are should never be overlooked. Perhaps most important of all, we all know how to scan written text to find the piece of the puzzle that we need. Developers in particular are very advanced skip-readers and will pick-and-mix bits of three different tutorials to fill the gaps they have in some completely unrelated application they are building. If you want to reach developer communities, all of them, everywhere, now and in the future: begin with the written word. That didn't change this year and isn't changing any time soon.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="digital-devrel-is-intentional"&gt;
&lt;h3&gt;Digital DevRel is Intentional&lt;/h3&gt;
&lt;p&gt;There's no substitute for in-person connections, and we can and should still come together on occasion. But let us be more intentional about when we choose to have many people travel to one place. The world is changing and many of us have made some conscious lifestyle choices (when did you last use a disposable plastic carrier bag?), it is time to bring that into our work lives too. A lot of what we do can be delivered asynchronously - the in-depth conference workshops in particular would be better as a distance learning series with some real-time support available.&lt;/p&gt;
&lt;p&gt;There is so much that DevRel can learn from established open source project practices. Communication is one - mailing lists, commit history and IRC channels can cover all the knowledge transfer that are needed, and they all scale up to include as many people as they need to. It allows people to contribute to the conversation with a much lower barrier of entry than flying lots of people to one location. Let's make the most of the in-person events, but let's make fewer of them and find ways to connect to one another and our communities beyond the patterns we fell into by default, that we inherited from sales teams who only wanted to reach the top brass. Developers are today's decision makers, and that's why Developer Relations is a rapidly growing profession.&lt;/p&gt;
&lt;p&gt;Developer advocacy is about meeting developers where they are, and enabling them to reach their goals. Digital is how we reach the developers hanging out &amp;quot;on the internet&amp;quot; and I am looking forward to more inclusive, impactful and intentional encounters with all of them in the future.&lt;/p&gt;
&lt;/div&gt;
</content><category term="work"/><category term="devrel"/><category term="opinion"/></entry><entry><title>What's New in OpenAPI 3.1</title><link href="https://lornajane-net.pages.dev/posts/2020/whats-new-in-openapi-3-1" rel="alternate"/><published>2020-12-14T09:54:00+00:00</published><updated>2020-12-14T09:54:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-12-14:/posts/2020/whats-new-in-openapi-3-1</id><summary type="html">&lt;p class="first"&gt;With OpenAPI 3.1 coming &amp;quot;soon&amp;quot;, I gave a talk at APIDays Paris about what to expect. But I'm a great fan of the written word as reference so here's a written recap of what's in the next OpenAPI release.&lt;/p&gt;
&lt;div class="last line-block"&gt;
&lt;div class="line"&gt;Top features:&lt;/div&gt;
&lt;div class="line"&gt;- Compatible with JSON Schema 2020-12&lt;/div&gt;
&lt;div class="line"&gt;- Webhook support&lt;/div&gt;
&lt;div class="line"&gt;- Many other minor improvements&lt;/div&gt;
&lt;/div&gt;
</summary><content type="html">&lt;p&gt;With OpenAPI 3.1 coming &amp;quot;soon&amp;quot;, I gave a talk at APIDays Paris about what to expect. But I'm a great fan of the written word as reference so here's a written recap of what's in the next OpenAPI release.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Top features:&lt;/div&gt;
&lt;div class="line"&gt;- Compatible with JSON Schema 2020-12&lt;/div&gt;
&lt;div class="line"&gt;- Webhook support&lt;/div&gt;
&lt;div class="line"&gt;- Many other minor improvements&lt;/div&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="version-numbering"&gt;
&lt;h3&gt;Version Numbering&lt;/h3&gt;
&lt;p&gt;As of OpenAPI 3.1, the OpenAPI project doesn't follow semantic versioning. That sounds like a totally irresponsible decision but it actually makes some sense for a standard where every API description clearly states the version of OpenAPI it relates to. Also, they don't let me make the rules, sadly! It's not a major release, but some things had to be undone and redone to accommodate JSON Schema.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="json-schema-2020-12"&gt;
&lt;h3&gt;JSON Schema 2020-12&lt;/h3&gt;
&lt;p&gt;This is really big news, so much of OpenAPI is used alongside JSON Schema and the 3.0 release was quite a long time ago and didn't quite accommodate everything that makes sense today in JSON Schema. This post won't cover what's new in JSON Schema itself but I'll try to round up the main points from the perspective of OpenAPI users.&lt;/p&gt;
&lt;p&gt;First of all: types can now be &lt;strong&gt;arrays of types&lt;/strong&gt;, so something could be of type &lt;tt class="docutils literal"&gt;[string,number]&lt;/tt&gt;. The available types also include &lt;tt class="docutils literal"&gt;null&lt;/tt&gt;, so more common will be &lt;tt class="docutils literal"&gt;[string, 'null']&lt;/tt&gt;. This does impact on existing OpenAPI documents as from 3.1, the &lt;tt class="docutils literal"&gt;nullable&lt;/tt&gt; keyword is removed - use the array of types and one of the types is null.&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;friendly-label&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;query&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'null'&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;OpenAPI 3.1 is getting support for the &lt;tt class="docutils literal"&gt;examples&lt;/tt&gt; keyword within a schema, allowing an &lt;strong&gt;array of examples&lt;/strong&gt;. Often the array only contains one element though. The original singular &lt;tt class="docutils literal"&gt;example&lt;/tt&gt; keyword is still valid, but &lt;tt class="docutils literal"&gt;examples&lt;/tt&gt; is recommended and if both are present then &lt;tt class="docutils literal"&gt;examples&lt;/tt&gt; is preferred. Be aware that this array of values is in contrast with the &lt;em&gt;other&lt;/em&gt; use of &lt;tt class="docutils literal"&gt;examples&lt;/tt&gt; in OpenAPI, in a MediaType content object, where the &lt;tt class="docutils literal"&gt;examples&lt;/tt&gt; field is a map with string keys. Confused? So are we. There is &lt;a class="reference external" href="https://phil.tech/2020/openapi-examples/"&gt;a post all about OpenAPI examples&lt;/a&gt; from Phil that explains it all!&lt;/p&gt;
&lt;p&gt;A couple of other things are coming in from JSON Schema into the OpenAPI schemas. First of all: arbitrary keys are allowed in a schema; OpenAPI has relaxed its constraints on which fields can appear where to accommodate JSON Schema format objects. Also it's possible to &lt;tt class="docutils literal"&gt;$ref&lt;/tt&gt; to an object, then put keys alongside it which are considered in addition to what is defined in the component.&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;'application/json'&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;$ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'#/components/schemas/style'&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;hue&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;None of these changes are huge, but having the ability to use JSON Schema within OpenAPI is brilliant for anyone wanting to use the two together so to have this commitment to supporting the ever-improving JSON Schema is excellent news.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="webhooks"&gt;
&lt;h3&gt;Webhooks&lt;/h3&gt;
&lt;p&gt;I'm totally biased because I proposed this feature. I couldn't believe that OpenAPI didn't already support this common use case and it took time to realise that it wasn't that I didn't understand how to do something - that thing really was outside what OpenAPI 3.0 expected APIs to describe.&lt;/p&gt;
&lt;p&gt;OpenAPI 3.0 does have support for &lt;strong&gt;callbacks&lt;/strong&gt; so if the user should make an API call, supply a URL, and stand by for incoming HTTP requests to that URL as a result, that's already supported. This is ideal when an endpoint asynchronously returns data, or for some situations where an API call &amp;quot;subscribes&amp;quot; to an event and gives a URL to send things to.&lt;/p&gt;
&lt;p&gt;OpenAPI 3.1 goes a step further and allows &lt;strong&gt;webhooks&lt;/strong&gt; which are incoming HTTP requests that are in response to some external event or stimulus. A great example would be if you've ever linked anything to a GitHub push event, or an incoming order/payment/message (and I've been working for a communications company for a few years, so you can immediately understand how I got entangled in this). The webhooks are described a LOT like the existing callbacks are, and indeed both are much like the existing request descriptions, so I hope this change will be easily adopted by everyone who has a two-way API like this.&lt;/p&gt;
&lt;p&gt;The new &lt;tt class="docutils literal"&gt;webhooks&lt;/tt&gt; keyword is a top-level element, alongside &lt;tt class="docutils literal"&gt;paths&lt;/tt&gt;. There are also changes to the required fields: OpenAPI 3.0 required &lt;tt class="docutils literal"&gt;openapi&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;info&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;paths&lt;/tt&gt; but in OpenAPI 3.1 only &lt;tt class="docutils literal"&gt;openapi&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;info&lt;/tt&gt; are always required, but the document must also contain at least one of &lt;tt class="docutils literal"&gt;paths&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;webhooks&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;components&lt;/tt&gt;. This is brilliant because it allows API descriptions to contain only outgoing API calls, only incoming webhooks, only components that might be referred to by other documents, or any combination or all of these - and still be valid in its own right.&lt;/p&gt;
&lt;p&gt;Anyway, back to the webhooks.&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;inbound-sms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;inbound-sms&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;application/json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;...&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Within the webhooks section, each incoming &amp;quot;thing&amp;quot; has a key (such as &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;inbound-sms&lt;/span&gt;&lt;/tt&gt; in the example above) and then it goes on just ... looking like a &lt;tt class="docutils literal"&gt;pathItem&lt;/tt&gt;, because that is all it is. You don't need to specify the URL path that the webhook will come in on (often the user can nominate that anyway), just explain what will arrive and you are done. Oh and related to this: &lt;tt class="docutils literal"&gt;pathItem&lt;/tt&gt; is now allowed in the &lt;tt class="docutils literal"&gt;components&lt;/tt&gt; section and you can &lt;tt class="docutils literal"&gt;$ref&lt;/tt&gt; to a &lt;tt class="docutils literal"&gt;pathItem&lt;/tt&gt; from either a &lt;tt class="docutils literal"&gt;path&lt;/tt&gt; or a &lt;tt class="docutils literal"&gt;callback&lt;/tt&gt; or a &lt;tt class="docutils literal"&gt;webhook&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Fancy a sneak preview of what the webhooks will look like when the tools get support for it? &lt;a class="reference external" href="https://github.com/Redocly/redoc"&gt;Redoc&lt;/a&gt; already has preview support if you use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;x-webhooks&lt;/span&gt;&lt;/tt&gt; in your 3.0 OpenAPI documents! I mean, it just looks like really competent API documentation but that's what we need here :)&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;a class="reference external image-reference" href="https://lornajane-net.pages.dev/images/2020/12/redoc-webhooks.png"&gt;&lt;img alt="redoc screenshot showing the incoming sms webhook" class="alignnone size-large wp-image-4335" src="https://lornajane-net.pages.dev/images/2020/12/redoc-webhooks-1024x508.png" style="width: 584px; height: 290px;" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="line"&gt;A note about webhooks and callbacks. Quite a few endpoints could be considered as either a webhook or a callback, and I have already started getting questions about which to use. It probably doesn't matter but if there is no preceeding API call that the callback is a response to, then it's definitely a webhook. Where there is some previous API call with a URL, then it's probably up to you how you want it to work. For example at Vonage the configuration for where to send events for an incoming Message is at the application level, do you do that with the Application API - but I'd rather have the API description with the incoming Message webhook detail shown next to the send Message API call, in the same document, tagged and grouped together in the Message API description. The &lt;tt class="docutils literal"&gt;webhooks&lt;/tt&gt; keyword gives you the flexbility to approach this as you will.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="small-but-perfectly-formed-upgrades"&gt;
&lt;h3&gt;Small But Perfectly-Formed Upgrades&lt;/h3&gt;
&lt;p&gt;There are so many small things that have been added in OpenAPI 3.1 but I'm picking my favourites to include! As with every &lt;tt class="docutils literal"&gt;*.1&lt;/tt&gt; release, there are things that seemed like a good idea for the &lt;tt class="docutils literal"&gt;*.0&lt;/tt&gt; release that can now be tidied up a little now we've all tried them out in anger, it's a good thin.&lt;/p&gt;
&lt;p&gt;I'll start with the one I want to implement immediately (or as soon as the tools allow): &lt;tt class="docutils literal"&gt;$ref&lt;/tt&gt; can now have &lt;tt class="docutils literal"&gt;summary&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;description&lt;/tt&gt; as siblings, and they override any existing fields on the referred-to component.&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;/items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;$ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'#/components/parameters/item'&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;The specific item in question&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;There are a few minor things in the &lt;tt class="docutils literal"&gt;info&lt;/tt&gt; section as well:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Inside &lt;tt class="docutils literal"&gt;info&lt;/tt&gt;, you can now have &lt;tt class="docutils literal"&gt;summary&lt;/tt&gt; alongside the &lt;tt class="docutils literal"&gt;description&lt;/tt&gt; field. Both are optional - &lt;tt class="docutils literal"&gt;title&lt;/tt&gt; is still required.&lt;/li&gt;
&lt;li&gt;In the &lt;tt class="docutils literal"&gt;license&lt;/tt&gt; object you can use an &lt;a class="reference external" href="https://spdx.org/licenses"&gt;SPDX&lt;/a&gt; code in the new &lt;tt class="docutils literal"&gt;identifier&lt;/tt&gt; field instead of a &lt;tt class="docutils literal"&gt;url&lt;/tt&gt; alongside the required &lt;tt class="docutils literal"&gt;name&lt;/tt&gt; field if you prefer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, paths are no longer &lt;em&gt;required&lt;/em&gt; to have &lt;tt class="docutils literal"&gt;responses&lt;/tt&gt; field for every endpoint. This is useful when an OpenAPI document is under construction because it means that it will validate even when you're still only sketching out the endpoints that the API definition will include.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="further-reading"&gt;
&lt;h3&gt;Further Reading&lt;/h3&gt;
&lt;p&gt;I'll share links to my talk when the recording is published (the &lt;a class="reference external" href="https://noti.st/lornajane/YRdDlZ/whats-new-in-openapi-specification-3-1"&gt;slides are on notist&lt;/a&gt; but keep up with the current status of the OpenAPI 3.1 release and read the (much better) changelogs on the project itself for more information &lt;a class="reference external" href="https://github.com/OAI/OpenAPI-Specification/releases"&gt;https://github.com/OAI/OpenAPI-Specification/releases&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="openapi"/></entry><entry><title>Bystanders Guide to Repository Maintenance</title><link href="https://lornajane-net.pages.dev/posts/2020/bystanders-guide-to-repository-maintenance" rel="alternate"/><published>2020-12-04T20:20:00+00:00</published><updated>2020-12-04T20:20:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-12-04:/posts/2020/bystanders-guide-to-repository-maintenance</id><summary type="html">&lt;p class="first last"&gt;Today's post is about bystanders and open source. We sometimes have anxiety about doing things on &amp;quot;other people's&amp;quot; projects but the thing about open source is that those are not &amp;quot;their&amp;quot; projects, those are &amp;quot;our&amp;quot; projects, and they only exist because people other than their nominated gatekeepers chip in now and then.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Today's post is about bystanders and open source. We sometimes have anxiety about doing things on &amp;quot;other people's&amp;quot; projects but the thing about open source is that those are not &amp;quot;their&amp;quot; projects, those are &amp;quot;our&amp;quot; projects, and they only exist because people other than their nominated gatekeepers chip in now and then.&lt;/p&gt;
&lt;div class="section" id="bystanders"&gt;
&lt;h3&gt;Bystanders&lt;/h3&gt;
&lt;p&gt;A &amp;quot;bystander&amp;quot; is someone who is nearby but not directly involved in something. There is also the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Bystander_effect"&gt;&amp;quot;bystander effect&amp;quot;&lt;/a&gt; which basically says that as individuals we are more likely to help out with an emergency if we're the only person present. If several others people are also near enough to help, none of us do.&lt;/p&gt;
&lt;p&gt;On the open internet it's easy to feel like you're in a crowd. Surely someone else will see this, help here, fix that thing. The thing is, when you're looking at a particular project ... most of the time, there is nobody else around. That one small thing? Is up to you.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="getting-started-in-open-source"&gt;
&lt;h3&gt;Getting Started in Open Source&lt;/h3&gt;
&lt;p&gt;There are many articles on how to get involved in open source, I've written at least one of them myself, but this is about &lt;strong&gt;micrcontributions&lt;/strong&gt;. The act of giving a moment of attention to every project whose path crosses yours. This post has specific examples that I hope seem approachable - so have a read and then look out for opportunities to lend a little of your skill, time and energy to these open projects that are powered by many small actions by many passing individuals!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="add-information-to-existing-discussions"&gt;
&lt;h3&gt;Add information to existing discussions&lt;/h3&gt;
&lt;p&gt;When you search for an error message and find it in an open issue on a project, go ahead and read the discussion thread. Often &lt;strong&gt;the answer is here or in a linked pull request&lt;/strong&gt; (in which case you're about to learn how to build a library from source. Please prepare to open your own pull request if any updates are needed to the installation instructions - most project maintainers aren't installing their projects from fresh on a regular basis so this area of a project can easily get neglected!). If you can, post about the software versions you're using and clarify if any workarounds helped you.&lt;/p&gt;
&lt;p&gt;The &amp;quot;me too&amp;quot; posts are tempting but they can really add to maintainer notification fatigue. If you are also experiencing an existing issue, or would also like to see a particular feature implemented, go ahead and &lt;strong&gt;use an emoji reaction&lt;/strong&gt; to indicate this rather than adding an new entry to a thread. Comment if you have new information. Not opinions. Information.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="answer-questions"&gt;
&lt;h3&gt;Answer questions&lt;/h3&gt;
&lt;p&gt;I work in Developer Relations, which requires troubleshooting of any of our products, in any tech stack, with any related dependencies, at any time. This makes me an expert in error messages and suggesting that running the dependency manager command again might help! In fact I think that as developers we can often help one another without any &amp;quot;specialist&amp;quot; knowledge needed. To this end I like to &lt;strong&gt;read the first few issues&lt;/strong&gt; on every project I see and check if there's anything I can help with. Even if I don't know the library at all, &amp;quot;try redirecting stderr to stdout and see if you get any output&amp;quot; can go a long way and &amp;quot;unstick&amp;quot; someone.&lt;/p&gt;
&lt;p&gt;It's not your project, and you don't have specific knowledge, but it's likely you have some related knowledge so share it if you do! Even if it's just a &amp;quot;first response&amp;quot; to ask for the error message, what platform they are using ... you have saved the maintainer a little bit of time AND started the clock ticking on waiting for a response. It's more valuable than you can know.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="informal-bug-report-triage"&gt;
&lt;h3&gt;Informal bug report triage&lt;/h3&gt;
&lt;p&gt;Open source projects grow very unevenly. Usually they start as just one or two people making a thing and sharing it. The early adopters are probably also contributors as well, and that works. Then there's a whole middle size of project which is just ... fine. Quiet, manageable. At the other end of the scale we've got huge, high-profile projects with (hopefully!) a team of maintainers. But before that I see quite a lot of projects that have a LOT of users, of varying levels, and a very small number of maintainers who have other jobs and commitments. At all stages, but especially this last one, &lt;strong&gt;a little help goes a really long way&lt;/strong&gt;. It's very easy for maintainers to be so swamped by issue chatter that they can't ever get out of the backlog of communications to work on something, and without being negative at all, bulk handling of support tickets may not be the best use the universe could make of their time and skills!&lt;/p&gt;
&lt;p&gt;To help with this situation, take another look at the issue list, but this time dive a little deeper and look at reported issues that genuinely seem like bugs. You're going to triage by looking at the bug report, &lt;strong&gt;trying to reproduce&lt;/strong&gt; the bug on your system, and then reporting whether you can reproduce or not (including your system information). You get bonus points if you can also write a test for the failure, document a workaround, or even offer a patch :)&lt;/p&gt;
&lt;p&gt;For example, I maintain a project but I'm a Linux user and my co-maintainer has a mac. The Windows bugs are going to be there for a long time unless someone else chimes in - just a &amp;quot;looks OK for me on Windows 10&amp;quot; would help us a lot. Many projects also use a tag like &amp;quot;help wanted&amp;quot; for issues where another pair of hands would be especially welcome so if you have a project you'd like to support you can look out for those as an easy way to lend a hand.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="be-the-myth-you-want-to-see-in-the-world"&gt;
&lt;h3&gt;Be the myth you want to see in the world&lt;/h3&gt;
&lt;p&gt;It's a common misconception that open source software is made by noble individuals for the good of the world. In fact, it's cobbled together from a whole raft of passing comments, code copied from another website, and one-line contributions from random and unknown humans. Open source is a movement, and it is strong when we all participate in one way or another. You don't need to become a full-time maintainer to make a difference; &lt;strong&gt;make microcontributions part of your normal practice&lt;/strong&gt; and be part of the strong future of open source. Even the smallest contributions help every project to inch forward, and lets the maintainers know there is someone out there!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="github"/><category term="open source"/></entry><entry><title>Making SDKs a Value-Added Experience</title><link href="https://lornajane-net.pages.dev/posts/2020/making-sdks-a-value-added-experience" rel="alternate"/><published>2020-11-16T10:18:00+00:00</published><updated>2020-11-16T10:18:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-11-16:/posts/2020/making-sdks-a-value-added-experience</id><summary type="html">&lt;p class="first last"&gt;I've been working with SDKs quite a bit this year, simple ones that wrap a bunch of API endpoints. The best bits of these SDKs isn't just the delightful developer experience of using the clunky endpoints with the elegant SDK wrapping them. I mean, it is, but I already &lt;a class="reference external" href="https://nordicapis.com/build-consistent-and-delightful-sdks-for-your-apis/"&gt;wrote about consistent and Delightful SDKs for Nordic APIs&lt;/a&gt;; today I want to cover the some of the &amp;quot;extra&amp;quot; things that make a big difference in the APIs.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've been working with SDKs quite a bit this year, simple ones that wrap a bunch of API endpoints. The best bits of these SDKs isn't just the delightful developer experience of using the clunky endpoints with the elegant SDK wrapping them. I mean, it is, but I already &lt;a class="reference external" href="https://nordicapis.com/build-consistent-and-delightful-sdks-for-your-apis/"&gt;wrote about consistent and Delightful SDKs for Nordic APIs&lt;/a&gt;; today I want to cover the some of the &amp;quot;extra&amp;quot; things that make a big difference in the APIs.&lt;/p&gt;
&lt;div class="section" id="provide-escape-routes"&gt;
&lt;h3&gt;Provide Escape Routes&lt;/h3&gt;
&lt;p&gt;Don't force your users to be all-or-nothing with your SDK. A great example is with APIs that use access tokens of any kind; I work with a few that use JWTs. As a nice helper function to the user, the SDK will generate the JWT and send it with your request, which is great. The extra value is added when the user can also generate a JWT for their own use; you expose the functionality that the library uses so that developers can use it themselves if they want to.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="save-repetition"&gt;
&lt;h3&gt;Save Repetition&lt;/h3&gt;
&lt;p&gt;If there's a particular piece of functionality that every consumer of the API is going to have to do, however small or well-documented, then add it to your SDK. I spent quite a lot of my life answering questions about how to handle a particular signature check to improve security on our APIs. I updated documentation, improved it, added examples .... once we put the feature into the SDKs, I don't think I've thought at all about this feature since!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="helpers"&gt;
&lt;h3&gt;Helpers&lt;/h3&gt;
&lt;p&gt;Don't be afraid to go a little beyond the boundaries of just a wrapper for an API call in what you do. For example, some of our APIs accept quite complicated JSON structures in the body of the requests. Every programming language can produce JSON but some make it easier than others. In PHP or JavaScript, you can pretty much throw some sort of data structure at the JSON function and something will happen. But when I came to create a &lt;a class="reference external" href="https://github.com/Vonage/vonage-go-sdk"&gt;Golang SDK&lt;/a&gt;, I found that things are not quite so simple in strongly-typed languages. Creating defined data structures that could be quickly used and then marshalled to JSON made the library much quicker to get up and running with - for me, as well as for the users I hope will follow and make good use of it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="delightful-but-unobtrusive"&gt;
&lt;h3&gt;Delightful but Unobtrusive&lt;/h3&gt;
&lt;p&gt;Keeping the presence of the SDK as a helper for a particular tech stack, but not a requirement, is a great aim here. SDKs give us tech-stack-aware functionality, and smoother experiences with autocomplete and supplementary dependencies included. But developers may well need to get away from the beaten track in ways that you can't anticipate so I'll always advocate adding small pieces but keeping the responsibility with the developer on choosing to mix in only the pieces that make sense for her project. Hopefully the examples here give you some ideas for your next library, too!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="api"/><category term="devx"/><category term="sdk"/></entry><entry><title>DevContainers, VSCode and GitHub Codespaces</title><link href="https://lornajane-net.pages.dev/posts/2020/devcontainers-vscode-and-github-codespaces" rel="alternate"/><published>2020-11-15T13:31:00+00:00</published><updated>2020-11-15T13:31:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-11-15:/posts/2020/devcontainers-vscode-and-github-codespaces</id><summary type="html">&lt;p class="first last"&gt;I've seen quite a bit of chatter later about the &lt;a class="reference external" href="https://github.com/features/codespaces"&gt;GitHub Codespaces&lt;/a&gt; feature lately, and I have beta access to it so I thought I'd share my first impressions and make some notes of what I've tried that might be useful to others.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've seen quite a bit of chatter later about the &lt;a class="reference external" href="https://github.com/features/codespaces"&gt;GitHub Codespaces&lt;/a&gt; feature lately, and I have beta access to it so I thought I'd share my first impressions and make some notes of what I've tried that might be useful to others.&lt;/p&gt;
&lt;p&gt;First up: you can prepare for all this without getting access to Codespaces because it's basically VSCode in the cloud. You can use your local VSCode and be ready to go when GitHub is :) Also since Codespaces is a chargeable feature, I expect it will make sense to run on your local platform if you have one and in the cloud if you're on another device.&lt;/p&gt;
&lt;div class="section" id="devcontainers-setup"&gt;
&lt;h3&gt;DevContainers Setup&lt;/h3&gt;
&lt;p&gt;Get the &lt;a class="reference external" href="https://github.com/features/codespaces"&gt;Remote - Containers extension for VSCode&lt;/a&gt; installed to begin. This uses a &lt;a class="reference external" href="https://code.visualstudio.com/docs/remote/devcontainerjson-reference"&gt;standard devcontainer configuration&lt;/a&gt; to describe the docker platform that your project runs on. This is useful when you have a lot of projects, a lot of developers, and therefore many platforms to manage. Now I've seen it, I'll be adding it to plenty of my projects!&lt;/p&gt;
&lt;p&gt;To add this sort of setup to an existing project, go and rummage in the &lt;a class="reference external" href="https://github.com/microsoft/vscode-dev-containers"&gt;vscode-dev-containers repo&lt;/a&gt; in the &lt;tt class="docutils literal"&gt;containers&lt;/tt&gt; folder for something that would fit your stack, or at least give you a starting point. You're looking for a &lt;tt class="docutils literal"&gt;.devcontainer&lt;/tt&gt; folder that you will copy, wholesale, into your own project. This will contain some dockery things, maybe a README with specific instructions (although all the files seem to be really well commented which I always appreciate), and the all-important &lt;tt class="docutils literal"&gt;devcontainer.json&lt;/tt&gt; file that describes to VSCode how the whole thing is going to fit together. Copy it in (or use the Remote - Containers plugin to do this for you), and configure as desired for your setup.&lt;/p&gt;
&lt;p&gt;Once the devcontainer is set up, opening the project in VSCode will cause you to be prompted whether you want to re-open the project in the container.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;vscode-dev-containers&lt;/span&gt;&lt;/tt&gt; repo README also has links to some &amp;quot;try it&amp;quot; projects for common stacks that I found useful.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="vscode-setup"&gt;
&lt;h3&gt;VSCode Setup&lt;/h3&gt;
&lt;p&gt;There's another piece of the puzzle but again it's VSCode-specific: the &lt;a class="reference external" href="https://code.visualstudio.com/Docs/editor/debugging"&gt;launch configuration&lt;/a&gt; for your project. I'm not sure why it's described as &amp;quot;debugging&amp;quot; because I think this is how I make the project run. I'm fairly new to VSCode though so please add advice in the comments!&lt;/p&gt;
&lt;p&gt;The launch configuration lives in &lt;tt class="docutils literal"&gt;.vscode/launch.json&lt;/tt&gt;. This is a great way to share this information but check if &lt;tt class="docutils literal"&gt;.vscode/&lt;/tt&gt; is included in the project's &lt;tt class="docutils literal"&gt;.gitignore&lt;/tt&gt; file - I would not normally advocate including proprietary-tool-specific settings in a repo but here we are! The simplest way to get started seems to be to press the &amp;quot;Debug&amp;quot; button without any configuration in place, and VSCode will hold your hand through the process.&lt;/p&gt;
&lt;p&gt;With the launch config in place, you should be able to &amp;quot;Debug&amp;quot; your project and something should ... happen!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="github-codespaces"&gt;
&lt;h3&gt;GitHub Codespaces&lt;/h3&gt;
&lt;p&gt;When you get access to the Codespaces feature, you'll probably notice the &amp;quot;Codespaces&amp;quot; item that appears on the top menu between &amp;quot;Pull Requests&amp;quot;/&amp;quot;Issues&amp;quot; and &amp;quot;Marketplace&amp;quot;. You will also see new options popping up: The Code download/clone button includes the option to open the project in a codespace, and pull requests will also have a new option under their &amp;quot;Open With&amp;quot; button.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;This will provide you with:&lt;/div&gt;
&lt;div class="line"&gt;* VSCode opens in the browser with the project or pull request in&lt;/div&gt;
&lt;div class="line"&gt;* The devcontainer in this project gets spun up (be patient!)&lt;/div&gt;
&lt;div class="line"&gt;* When you &amp;quot;Run&amp;quot; our &amp;quot;Debug&amp;quot; the project, you'll get a notification when the server is ready. You can even Ctrl+Click to open the &amp;quot;localhost&amp;quot; addresses which are then rewritten to your new cloud workspace URLs&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="considerations"&gt;
&lt;h3&gt;Considerations&lt;/h3&gt;
&lt;p&gt;The convenience makes the whole thing compelling and I imagine I'll have this set up on many of my own projects. However I feel a word of caution is appropriate here. Git is an open standard; GitHub is just one option for a hosted interface to go with git. No project should &lt;em&gt;require&lt;/em&gt; use of a specific tool, even a lovely and widely-used one like VSCode. I'm a Linux user with accessibility needs; anything that dictates the tools I use locally is a non-starter (I can't imagine actually &lt;em&gt;working&lt;/em&gt; with Codepsaces, it's just a nice way of including other people or quickly testing something from the train on a mobile device).&lt;/p&gt;
&lt;p&gt;If you take one thing away, I'd love it to be this: &lt;strong&gt;Let's pledge to include these features in our projects, and to include the &amp;quot;standard&amp;quot; usage/install/contributing instructions too.&lt;/strong&gt; It's a Docker file and some commands to run, no special equipment required.&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="containers"/><category term="docker"/><category term="github"/><category term="tips"/><category term="vscode"/></entry><entry><title>Ubuntu Command for Mic Mute with Pulsemixer</title><link href="https://lornajane-net.pages.dev/posts/2020/ubuntu-command-for-mic-mute-with-pulsemixer" rel="alternate"/><published>2020-11-12T17:27:00+00:00</published><updated>2020-11-12T17:27:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-11-12:/posts/2020/ubuntu-command-for-mic-mute-with-pulsemixer</id><summary type="html">&lt;p class="first last"&gt;One of the major problems I run into with video conferencing tools is how many of them are not at all accessible. I'm a keyboard-only user so if I need to hover a pointer in a particular area of the screen and then click an icon to mute ... then I can't do that. And if you mute me when I join because there are a lot of people here, then I can't participate at all. To work around this, I need a one-liner to mute (and unmute) my mic ... so here it is.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;One of the major problems I run into with video conferencing tools is how many of them are not at all accessible. I'm a keyboard-only user so if I need to hover a pointer in a particular area of the screen and then click an icon to mute ... then I can't do that. And if you mute me when I join because there are a lot of people here, then I can't participate at all. To work around this, I need a one-liner to mute (and unmute) my mic ... so here it is.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
pulsemixer --id source-5 --toggle-mute
&lt;/pre&gt;
&lt;p&gt;Seems easy, right? There's a catch!&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;source-5&lt;/span&gt;&lt;/tt&gt; identifier changes on boot. Check out your list of sources:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
pulsemixer --source-list
&lt;/pre&gt;
&lt;p&gt;Pick your mic from the list, run the toggle command so it's in your command history, and then repeat as needed. You could even wrap it up in a shell script, or as I do, hook it up to your streamdeck or other external input. It would be even better if you could automate finding your source .... I have this dumped in a comment in the code but I haven't dared to use it for real yet:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
pulsemixer --list-sources | cut -f3 | grep 'UMC404HD 192k Multichannel' | cut -d ',' -f 1 | cut -c 6-
&lt;/pre&gt;
&lt;p&gt;I will leave it here just in case it's useful to you!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro-tip&lt;/strong&gt;: More and more of the video conference tools seem to be introducing support for muting and unmuting you when your system microphone is muted or unmuted. This is brilliant because people can still see that you actively unmuted to say something!&lt;/p&gt;
</content><category term="tech"/><category term="streamdeck"/><category term="tips"/><category term="ubuntu"/></entry><entry><title>Keyboard-only Web Browser</title><link href="https://lornajane-net.pages.dev/posts/2020/keyboard-only-web-browser" rel="alternate"/><published>2020-10-22T13:53:00+01:00</published><updated>2020-10-22T13:53:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-10-22:/posts/2020/keyboard-only-web-browser</id><summary type="html">&lt;p class="first last"&gt;I haven't used a pointing device in over a decade, and I get a lot of questions about how I navigate the web using only the keyboard. The short answer is: &lt;a class="reference external" href="https://vimium.github.io/"&gt;Vimium&lt;/a&gt; and funnily enough, it's a productivity tool rather than an accessibility tool. Curious? Let me show you around!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I haven't used a pointing device in over a decade, and I get a lot of questions about how I navigate the web using only the keyboard. The short answer is: &lt;a class="reference external" href="https://vimium.github.io/"&gt;Vimium&lt;/a&gt; and funnily enough, it's a productivity tool rather than an accessibility tool. Curious? Let me show you around!&lt;/p&gt;
&lt;p&gt;Vimium is available as a plugin/extension for both Chrome and Firefox. Basically, you install it, and then press &lt;tt class="docutils literal"&gt;f&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="lornajane.net search results, with vimium labels showing on each link" class="alignnone size-large wp-image-4278" src="https://lornajane-net.pages.dev/images/2020/09/Screenshot_2020-09-13-Search-Results-for-“keyboard”-–-LornaJane-1024x465.png" style="width: 584px; height: 265px;" /&gt;&lt;/p&gt;
&lt;p&gt;The little yellow labels pop up on everything you can interact with. If there's anything that &amp;quot;doesn't work&amp;quot;, that is almost always an inaccessible web element rather than a fault with Vimium. Yes, the web is a bit rubbish in places!&lt;/p&gt;
&lt;p&gt;OK so you know how to click on links or input boxes, what else makes a productive web user? Here are a few tips:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Use &lt;tt class="docutils literal"&gt;j&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;k&lt;/tt&gt; to scroll up and down gradually. &lt;tt class="docutils literal"&gt;spacebar&lt;/tt&gt; goes down a page at a time and is good for low-friction reading longer form content.&lt;/li&gt;
&lt;li&gt;Typing &lt;tt class="docutils literal"&gt;F&lt;/tt&gt; rather than &lt;tt class="docutils literal"&gt;f&lt;/tt&gt; opens the link in a new tab.&lt;/li&gt;
&lt;li&gt;Knowing your browser-level keyboard shortcuts is probably also handy! Try &lt;tt class="docutils literal"&gt;ctrl+t&lt;/tt&gt; for a new tab, &lt;tt class="docutils literal"&gt;ctrl+w&lt;/tt&gt; to close a tab and &lt;tt class="docutils literal"&gt;ctrl+pgup/pgdown&lt;/tt&gt; to move between tabs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For working one-handed, whether that's a permanent thing or your other hand is currently occupied by either a beverage or a child, tools like this can be a real power up. And for me, two hands but I can't move them a lot, a tool like Vimium is life-changing. Whether you are in it for accessibility or productivity, install it and give it a whirl!&lt;/p&gt;
</content><category term="tech"/><category term="a11y"/><category term="accessibility"/><category term="vimium"/></entry><entry><title>Combine Multiple Field Tags in Go</title><link href="https://lornajane-net.pages.dev/posts/2020/combine-multiple-field-tags-in-go" rel="alternate"/><published>2020-10-21T09:46:00+01:00</published><updated>2020-10-21T09:46:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-10-21:/posts/2020/combine-multiple-field-tags-in-go</id><summary type="html">&lt;p class="first last"&gt;I work a lot with JSON APIs in my Go projects, so almost every struct has some field tags on it to translate &lt;tt class="docutils literal"&gt;AccountID&lt;/tt&gt; into &lt;tt class="docutils literal"&gt;account_id&lt;/tt&gt; and that sort of thing. Then one day I needed to load data from config using &lt;a class="reference external" href="https://pkg.go.dev/github.com/mitchellh/mapstructure"&gt;mapstructure&lt;/a&gt; and it took a few attempts with the search engine to find the syntax I needed, so it's here for next time I need it (or in case you need it too).&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I work a lot with JSON APIs in my Go projects, so almost every struct has some field tags on it to translate &lt;tt class="docutils literal"&gt;AccountID&lt;/tt&gt; into &lt;tt class="docutils literal"&gt;account_id&lt;/tt&gt; and that sort of thing. Then one day I needed to load data from config using &lt;a class="reference external" href="https://pkg.go.dev/github.com/mitchellh/mapstructure"&gt;mapstructure&lt;/a&gt; and it took a few attempts with the search engine to find the syntax I needed, so it's here for next time I need it (or in case you need it too).&lt;/p&gt;
&lt;div class="section" id="combining-json-and-mapstructure"&gt;
&lt;h3&gt;Combining JSON and Mapstructure&lt;/h3&gt;
&lt;p&gt;An example truly is worth a thousand words! (This one is from the code that drives the &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2020/neopixel-shelf"&gt;neopixel shelf&lt;/a&gt;):&lt;/p&gt;
&lt;pre class="code go literal-block"&gt;
&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LEDColour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Red&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`mapstructure:&amp;quot;red&amp;quot; json:&amp;quot;red&amp;quot;`&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Green&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`mapstructure:&amp;quot;green&amp;quot; json:&amp;quot;green&amp;quot;`&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Blue&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`mapstructure:&amp;quot;blue&amp;quot; json:&amp;quot;blue&amp;quot;`&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;White&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`mapstructure:&amp;quot;white&amp;quot; json:&amp;quot;white&amp;quot;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;It turns out that the reason I couldn't find examples of how to combine struct field tags is because you don't! You just add each one required, followed by a space, and then the next, and so on. How simple, how elegant ... how Go!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="go"/><category term="tips"/></entry><entry><title>Adding Tables of Contents to Markdown</title><link href="https://lornajane-net.pages.dev/posts/2020/adding-tables-of-contents-to-markdown" rel="alternate"/><published>2020-10-18T20:34:00+01:00</published><updated>2020-10-18T20:34:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-10-18:/posts/2020/adding-tables-of-contents-to-markdown</id><summary type="html">&lt;p class="first last"&gt;Once any document gets over a certain length, a table of contents can be useful. I have a favourite markdown table-of-contents tool but it usually takes me a trip to the search engine to remember what it is! (tl;dr: &lt;a class="reference external" href="https://github.com/ekalinin/github-markdown-toc"&gt;github-markdown-toc&lt;/a&gt; )&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Once any document gets over a certain length, a table of contents can be useful. I have a favourite markdown table-of-contents tool but it usually takes me a trip to the search engine to remember what it is! (tl;dr: &lt;a class="reference external" href="https://github.com/ekalinin/github-markdown-toc"&gt;github-markdown-toc&lt;/a&gt; )&lt;/p&gt;
&lt;p&gt;This tool generates a neat, nested table of contents based on the titles in the file you give it, and outputs it to standard out. My command looks something like:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
gh-md-toc /path/to/project/README.md
&lt;/pre&gt;
&lt;p&gt;Capture the output, put it at the top of your file, and you are all set! For example, &lt;a class="reference external" href="https://github.com/Vonage/vonage-go-sdk"&gt;a Go SDK project I was working on recently&lt;/a&gt;. It's such a small thing but it improves user experience and only takes a moment. When it's time to update, just delete what you have and regenerate a new one!&lt;/p&gt;
</content><category term="tech"/><category term="github"/><category term="markdown"/><category term="tips"/></entry><entry><title>Package Webpages as Apps with Nativefier</title><link href="https://lornajane-net.pages.dev/posts/2020/package-webpages-as-apps-with-nativefier" rel="alternate"/><published>2020-09-15T11:22:00+01:00</published><updated>2020-09-15T11:22:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-09-15:/posts/2020/package-webpages-as-apps-with-nativefier</id><summary type="html">&lt;p class="first last"&gt;There are a couple of specific web pages I use in separate windows from my other browser tabs, so I can always find them. However this quickly leads to a too-many-browser-windows problem. Instead, I have been turning them into basic standalone applications with a javascript tool: &lt;a class="reference external" href="https://github.com/jiahaog/nativefier"&gt;nativefier&lt;/a&gt; and I thought I'd share my recipes.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;There are a couple of specific web pages I use in separate windows from my other browser tabs, so I can always find them. However this quickly leads to a too-many-browser-windows problem. Instead, I have been turning them into basic standalone applications with a javascript tool: &lt;a class="reference external" href="https://github.com/jiahaog/nativefier"&gt;nativefier&lt;/a&gt; and I thought I'd share my recipes.&lt;/p&gt;
&lt;p&gt;First: the problem. I'm a keyboard-only user and that is less of a problem than you'd think for day-to-day things. I can drive a terminal, browser, text editor, and slack, which is basically all I need for 95% of my week. However I've recently started streaming on Twitch (you can follow &lt;a class="reference external" href="https://twitch.tv/lornajanetv"&gt;lornajanetv&lt;/a&gt; if you'd like this sort of content in real time) and managing the stream as well as showing what I'm doing is .... basically I'm streaming a lot of &lt;tt class="docutils literal"&gt;alt+tab&lt;/tt&gt; overlays!&lt;/p&gt;
&lt;p&gt;What I needed was the ability to focus a window immediately from a command. I have a streamdeck, so I can hook the command up to a button. I had some success with &lt;tt class="docutils literal"&gt;wmctrl&lt;/tt&gt; which can raise a window by title, but since I use multiple workspaces on my main monitor (workspaces do not span displays, configured with &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;gnome-tweak-tool&lt;/span&gt;&lt;/tt&gt;, so the second monitor holds all the stream-related stuff, and is not really on a workspace), raising one firefox window would switch workspaces as well. I needed a new approach.&lt;/p&gt;
&lt;p&gt;Enter: Nativefier. It claims to package webpages as electron apps, and since I have OBS doing what I want it to with the window raising behaviour, I thought this might work well.&lt;/p&gt;
&lt;div class="section" id="package-a-web-page-with-nativefier"&gt;
&lt;h3&gt;Package a Web Page with Nativefier&lt;/h3&gt;
&lt;p&gt;Honestly, this tool does what it says on the tin! I wanted to put my Stream Manager page into an app, so I installed the tool as a global command and then used this:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
nativefier&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;StreamManagerLJ&lt;span class="w"&gt; &lt;/span&gt;--single-instance&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;https://dashboard.twitch.tv/u/lornajanetv/stream-manager&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;--internal-urls&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.*?\.twitch\.tv\.*?&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;--inject&lt;span class="w"&gt; &lt;/span&gt;inject.js
&lt;/pre&gt;
&lt;p&gt;There's a lot going on here so I'll try to break it down.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Name:&lt;/strong&gt; Use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--name&lt;/span&gt;&lt;/tt&gt; to give your application a name. This is useful as you'll want to refer to it later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Single instance:&lt;/strong&gt; By making your app single instance, attempting to run another one will simply focus the existing one. I added this because I was still having some workspace changes when I tried to focus the apps with &lt;tt class="docutils literal"&gt;wmctrl&lt;/tt&gt; and it works brilliantly. As a side-benefit, I have one streamdeck button that either launches or finds this, regardless of what state it is in.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;URL:&lt;/strong&gt; The page to wrap in an app. Same-domain links will work inside the app, but for everything else you'll need to specify what is an internal URL.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Internal URLs:&lt;/strong&gt; Add a domain or regex here (you can also send multiple of this switch) to tell the app which URLs should be opened inside it. Everything else will break out into your default browser. If you need to auth, you'll need to add the domains of those pages so that it happens inside the app.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inject:&lt;/strong&gt; You can include &lt;tt class="docutils literal"&gt;*.js&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;*.css&lt;/tt&gt; files to add to the application. More on this later (even I am not putting a tangent this size into a bullet point!)&lt;/p&gt;
&lt;p&gt;The command outputs information about where it put the executable - I get a &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;StreamManagerLJ-linux-64/StreamManagerLJ&lt;/span&gt;&lt;/tt&gt; that I can run. Then I can copy that executable somewhere more useful if I want to although I usually leave it where it is and then &lt;cite&gt;create a `&lt;/cite&gt;.desktop`` file &amp;lt;{filename}/create-desktop-files-for-ubuntu.rst&amp;gt;`__ to make it executable, set its icon, etc.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="injecting-custom-js-for-nativefier"&gt;
&lt;h3&gt;Injecting Custom JS for Nativefier&lt;/h3&gt;
&lt;p&gt;One cool thing is that you can also add some scripts that aren't part of the official webpage. I spent some time trying to work out how to package &lt;a class="reference external" href="https://vimium.github.io/"&gt;Vimium&lt;/a&gt;, which how I use my browser from the keyboard usually. However, Chrome plugins are intentionally not included, presumably because it would get out of hand really fast! Anyway hopefully I'll figure this out at some point but for now ... a little hackery gives me all I need :)&lt;/p&gt;
&lt;p&gt;In the stream manager, I basically need to be able to reliably focus the chat window. For everything else (which is rare), I will use my touch screen or pointing device since I can't actually stream completely keyboard-only so I plug in a wacom tablet for this anyway. Here's the &lt;tt class="docutils literal"&gt;inject.js&lt;/tt&gt; that I created:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'keydown'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;doKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;doKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;KeyC&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByClassName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chat-input__textarea'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;firstChild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;It's not much, but it's just enough to give me focus where I need it, very easily. I could also expand to add more keys and more selectors or something. Probably this will evolve over time, but if what I have helps you, then I want to share! Helpfully, the apps do come with devtools enabled so that can help with identifying elements and debugging things.&lt;/p&gt;
&lt;p&gt;I came to nativefier as a workaround for a window management problem but I see it as a productivity hack as well, the community seems friendly and has lots of people using it for their most-used apps (trello, gmail) that they wish were desktop-native. The tool was easy to use and I can also patch it to extend it if I need to, which is always reassuring. Let me know what you package and how it goes!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="nativefier"/><category term="twitch"/><category term="ubuntu"/></entry><entry><title>Create .desktop files for Ubuntu</title><link href="https://lornajane-net.pages.dev/posts/2020/create-desktop-files-for-ubuntu" rel="alternate"/><published>2020-09-13T11:21:00+01:00</published><updated>2020-09-13T11:21:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-09-13:/posts/2020/create-desktop-files-for-ubuntu</id><summary type="html">&lt;p class="first last"&gt;Having created a couple of &lt;tt class="docutils literal"&gt;.desktop&lt;/tt&gt; files lately to make launching apps easier, I thought I'd write myself a quick reminder of what I did and how it works. If it's useful to you too, then great! I have one application in a weird path, and another that needs a specific environment variable set for it to work.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Having created a couple of &lt;tt class="docutils literal"&gt;.desktop&lt;/tt&gt; files lately to make launching apps easier, I thought I'd write myself a quick reminder of what I did and how it works. If it's useful to you too, then great! I have one application in a weird path, and another that needs a specific environment variable set for it to work.&lt;/p&gt;
&lt;p&gt;Let's start with OBS. On 20.04 and because I'm using a Wacom Tablet, there is a &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2020/obs-ubuntu-20-04-and-a-wacom-tablet"&gt;magic incantation&lt;/a&gt; required when it runs. By creating a &lt;tt class="docutils literal"&gt;.desktop&lt;/tt&gt; file for it, I can add that and run it from the usual launcher rather than needed a special terminal script or alias.&lt;/p&gt;
&lt;p&gt;The file is &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.local/share/applications/obs.desktop&lt;/span&gt;&lt;/tt&gt; and looks like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[Desktop Entry]
Type=Application
Encoding=UTF-8
Name=OBS Studio
Icon=/path/to/images/obs-logo.png
Exec=env QT_XCB_TABLET_LEGACY_COORDINATES=1 obs
&lt;/pre&gt;
&lt;p&gt;I also have this one pinned as a favourite so it launches on a &lt;tt class="docutils literal"&gt;Super + [number]&lt;/tt&gt; keyboard combination!&lt;/p&gt;
&lt;p&gt;Another example is the webpages-as-apps I've been packaging lately (blog post coming on that too I promise but tl;dr try &lt;a class="reference external" href="https://github.com/jiahaog/nativefier"&gt;nativefier&lt;/a&gt;!), the executables are in a weird path and since I expect to re-package fairly often, I don't want to have to copy things around all the time. No problem! The desktop file doesn't care, and puts them in my proper launcher regardless:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[Desktop Entry]
Type=Application
Encoding=UTF-8
Name=Twitch Stream Manager
Exec=/wild/long/and/unlikely/path/StreamManagerLJ
Icon=/path/to/images/twitch-logo.png
&lt;/pre&gt;
&lt;p&gt;Hopefully now I've written this down, I'll remember how this works next time without having to come and look up all the instructions ... AGAIN :)&lt;/p&gt;
</content><category term="tech"/><category term="ubuntu"/></entry><entry><title>Sharing Screen and Camera from Android to Linux</title><link href="https://lornajane-net.pages.dev/posts/2020/sharing-screen-and-camera-from-android-to-linux" rel="alternate"/><published>2020-08-17T09:38:00+01:00</published><updated>2020-08-17T09:38:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-08-17:/posts/2020/sharing-screen-and-camera-from-android-to-linux</id><summary type="html">&lt;p class="first last"&gt;With all the video calls, &lt;a class="reference external" href="https://twitch.tv/lornajanetv"&gt;twitch streaming&lt;/a&gt; and product demo work I've been doing recently, I've been building up a library of tricks for using my android devices within the video content I produce from my laptop, so I thought I'd share. I've worked remotely for many years but I think there's more of us not going back to the office any time soon, so strong video communication skills are important!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;With all the video calls, &lt;a class="reference external" href="https://twitch.tv/lornajanetv"&gt;twitch streaming&lt;/a&gt; and product demo work I've been doing recently, I've been building up a library of tricks for using my android devices within the video content I produce from my laptop, so I thought I'd share. I've worked remotely for many years but I think there's more of us not going back to the office any time soon, so strong video communication skills are important!&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;My setup:&lt;/div&gt;
&lt;div class="line"&gt;* Lenovo Thinkpad X1 running Ubuntu 20.04&lt;/div&gt;
&lt;div class="line"&gt;* Android Moto G6 mostly, although I have screenshared my Samsung S2 tablet as well when I wanted to draw something!&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;(I also have the Behringer UMC404 audio interface, meaning I can route the phone headset audio directly in to my computer but honestly my earlier solution of plugging from 3.5mm headphone socket on the phone into a basic bluetooth speaker and turning that right up worked almost as nicely! It only matters because I work for a communications company and sometimes need to demo phone calls)&lt;/p&gt;
&lt;div class="section" id="screenshare"&gt;
&lt;h3&gt;Screenshare&lt;/h3&gt;
&lt;p&gt;I tried &lt;tt class="docutils literal"&gt;`scrcpy&lt;/tt&gt; &amp;lt;&lt;a class="reference external" href="https://github.com/Genymobile/scrcpy"&gt;https://github.com/Genymobile/scrcpy&lt;/a&gt;&amp;gt;`__ and it's been great so far. It connects over USB and uses &lt;a class="reference external" href="https://developer.android.com/studio/command-line/adb"&gt;adb tools&lt;/a&gt; (that I had installed anyway). I have used this approach in a couple of different ways:&lt;/p&gt;
&lt;p&gt;First: I wanted to draw something, and a stylus and a tablet is the easiest way for me to do that. Since I only want to use a local application on the tablet, I simply put the device into airplane mode to avoid any notifications, embarrassing or otherwise! That worked well (this picture from a &lt;a class="reference external" href="https://youtu.be/GnPmccXrTs0"&gt;stream about git&lt;/a&gt; that I did for work).&lt;/p&gt;
&lt;p&gt;&lt;img alt="terrible drawing of git branches in different colours, shown on a landscape android tablet screen" class="alignnone size-medium wp-image-4264" src="https://lornajane-net.pages.dev/images/2020/08/android-screenshare-tablet-300x169.png" style="width: 300px; height: 169px;" /&gt;&lt;/p&gt;
&lt;p&gt;The other time I use this approach is when I'm demoing something that is actually on my phone, such as the new &lt;a class="reference external" href="https://developer.nexmo.com/messages/overview"&gt;WhatsApp Messaging Features from Vonage&lt;/a&gt; (I work there, so I am supposed to be able to operate this stuff and show it to others). There are desktop apps for most of these things but I think it's nice to really have the phone in the video.&lt;/p&gt;
&lt;p&gt;&lt;img alt="screenshare of a mobile phone window on a desktop" class="alignnone size-medium wp-image-4263" src="https://lornajane-net.pages.dev/images/2020/08/wahtsapp-screenshare-300x168.png" style="width: 300px; height: 168px;" /&gt;&lt;/p&gt;
&lt;p&gt;Do put the phone into &amp;quot;do not disturb&amp;quot; mode before you start - I use a work phone that has hardly anything installed on it so I don't get notifications on that, which helps!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="android-phone-as-a-webcam"&gt;
&lt;h3&gt;Android Phone as a Webcam.&lt;/h3&gt;
&lt;p&gt;I've been using &lt;a class="reference external" href="https://www.dev47apps.com/"&gt;DroidCam&lt;/a&gt; who seem to be much better at making android apps than they are at making marketing websites! It connects over USB (using adb again) or over wifi. I found that some days only one or the other will connect but once it's stayed connected for 30 seconds then it has usually been rock solid for me - although it does seem to like to be reinstalled if Ubuntu has had updates!&lt;/p&gt;
&lt;p&gt;I have been using it as a second camera, in addition to my built in laptop webcam, so that I can include my hardware projects in my Twitch streams. If I work on software, then I only need screensharing but for something that is not on a screen, the second webcam is really handy. At some point I might get an actual webcam, but I started doing this at the start of the pandemic and getting a webcam was pretty difficult - and the phone works fine for the hobby level stuff I'm doing.&lt;/p&gt;
&lt;p&gt;&lt;img alt="screenshare of code, with both lorna and her streamdeck in different webcams on the right hand side" class="alignnone size-medium wp-image-4262" src="https://lornajane-net.pages.dev/images/2020/08/two-cameras-300x169.png" style="width: 300px; height: 169px;" /&gt;&lt;/p&gt;
&lt;p&gt;Again, this one is my basic work phone, so it doesn't get much in the way of notifications or anything which I think helps.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="adding-android-devices-into-the-video-mix"&gt;
&lt;h3&gt;Adding Android Devices into the Video Mix&lt;/h3&gt;
&lt;p&gt;It takes time to figure out the tools and set them up in advance of whatever you need them for, but I've found it to be worth it. I use the phone with a gorillapod and basic phone tripod mount and I've found it useful for sharing many different things (including a storybook at bedtime for the little ones that I can't put to bed myself right now!). Let me know what you're doing with your extra devices in your video setup? I would love to hear!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="android"/><category term="streaming"/><category term="ubuntu"/><category term="video"/></entry><entry><title>An Intro to Your ESP8266</title><link href="https://lornajane-net.pages.dev/posts/2020/an-intro-to-your-esp8266" rel="alternate"/><published>2020-08-08T20:54:00+01:00</published><updated>2020-08-08T20:54:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-08-08:/posts/2020/an-intro-to-your-esp8266</id><summary type="html">&lt;p class="first"&gt;I've been using a microchip on my &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2020/neopixel-shelf"&gt;neopixel shelf project&lt;/a&gt; but I kind of set it up, changed one thing, and didn't look at it again until this week. I'd like to have my neopixels do something more interesting than one solid colour or another, so I needed to remind myself of what this thing is and how I do it. These are my notes, for my Ubuntu platform, but you're more than welcome to them!&lt;/p&gt;
&lt;p class="last"&gt;&lt;img alt="strip of pink LEDs embedded in a shelf, with an esp8266 on a wire in the foreground" class="alignnone size-full wp-image-4259" src="https://lornajane-net.pages.dev/images/2020/08/esp8266-pink.png" style="width: 395px; height: 218px;" /&gt;&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've been using a microchip on my &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2020/neopixel-shelf"&gt;neopixel shelf project&lt;/a&gt; but I kind of set it up, changed one thing, and didn't look at it again until this week. I'd like to have my neopixels do something more interesting than one solid colour or another, so I needed to remind myself of what this thing is and how I do it. These are my notes, for my Ubuntu platform, but you're more than welcome to them!&lt;/p&gt;
&lt;p&gt;&lt;img alt="strip of pink LEDs embedded in a shelf, with an esp8266 on a wire in the foreground" class="alignnone size-full wp-image-4259" src="https://lornajane-net.pages.dev/images/2020/08/esp8266-pink.png" style="width: 395px; height: 218px;" /&gt;&lt;/p&gt;
&lt;div class="section" id="start-with-firmware"&gt;
&lt;h3&gt;Start with Firmware&lt;/h3&gt;
&lt;p&gt;The first thing you need is some firmware to base things on. I used a &lt;a class="reference external" href="https://nodemcu.readthedocs.io/en/master/"&gt;NodeMCU build&lt;/a&gt;; there are a few ways to obtain one of these but I used the charming donation-driven site &lt;a class="reference external" href="https://nodemcu-build.com"&gt;https://nodemcu-build.com&lt;/a&gt;. You pick your options, supply an email address, and wait. The link to your custom build will show up in a few minutes. The chip doesn't have a lot of resources, so the custom build means you can pick and choose what to include. Here's what's in mine:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;color_utils&lt;/li&gt;
&lt;li&gt;file&lt;/li&gt;
&lt;li&gt;gpio&lt;/li&gt;
&lt;li&gt;http&lt;/li&gt;
&lt;li&gt;mqtt&lt;/li&gt;
&lt;li&gt;net&lt;/li&gt;
&lt;li&gt;node&lt;/li&gt;
&lt;li&gt;sjson&lt;/li&gt;
&lt;li&gt;tmr&lt;/li&gt;
&lt;li&gt;uart&lt;/li&gt;
&lt;li&gt;wifi&lt;/li&gt;
&lt;li&gt;ws2812&lt;/li&gt;
&lt;li&gt;ws2812_effects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(those last two entries are the neopixel bits!)&lt;/p&gt;
&lt;p&gt;While you wait for the build to arrive, connect the ESP8266 to your laptop and install &lt;a class="reference external" href="https://github.com/espressif/esptool"&gt;esptool&lt;/a&gt;, probably from &lt;tt class="docutils literal"&gt;pip&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Ready? Cool! Upload the build:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
esptool --baud 115200 write_flash -fm dio 0x00000 [build file from earlier].bin
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="write-some-code"&gt;
&lt;h3&gt;Write Some Code&lt;/h3&gt;
&lt;p&gt;These little chips have a few options for code but I'm using &lt;a class="reference external" href="https://www.lua.org/"&gt;Lua&lt;/a&gt; which I hadn't seen before, but which is pretty approachable. I like it, but I am not sure I will need a Lua category on this blog any time soon!&lt;/p&gt;
&lt;p&gt;I began with a file named &lt;tt class="docutils literal"&gt;init.lua&lt;/tt&gt; - by default the built will pick this up when the chip is powered up or reset.&lt;/p&gt;
&lt;p&gt;To put the code onto the board, my command looks like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
nodemcu-tool upload --port=/dev/ttyUSB0 init.lua
&lt;/pre&gt;
&lt;p&gt;The light on the back will show &amp;quot;chatter&amp;quot; while the transfer is happening, then you can press &amp;quot;reset&amp;quot; to power cycle the board and see your code in action!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="debugging-tips"&gt;
&lt;h3&gt;Debugging Tips&lt;/h3&gt;
&lt;p&gt;I needed a lot of these. Start with the obvious and keep your code in source control, only change one thing at a time!&lt;/p&gt;
&lt;p&gt;You can add &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;print('hello&lt;/span&gt; world')&lt;/tt&gt; commands in your code, and you will them when you attach to the running device with screen, like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
screen /dev/ttyUSB0 115200
&lt;/pre&gt;
&lt;p&gt;If you're unfamiliar with screen, you may find yourself stuck in this screen! Press &lt;tt class="docutils literal"&gt;Ctrl + a&lt;/tt&gt; (all screen commands start that way) and then &lt;tt class="docutils literal"&gt;k&lt;/tt&gt;, followed by &lt;tt class="docutils literal"&gt;y&lt;/tt&gt; to confirm.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; You need to exit screen to be able to use the serial connection to upload a new version of code!&lt;/p&gt;
&lt;p&gt;I'll write about my actual application another day, there's enough going on there that it needs a post of its own - but dumping my stuck-in-a-handy-textfile commands here will make it easier for me to find them next time. If you're doing a similar project and refer to these instructions, I would love to hear what you're building!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="esp"/><category term="esp8266"/><category term="lua"/><category term="microcontroller"/></entry><entry><title>Creating a Simple OpenAPI Spec</title><link href="https://lornajane-net.pages.dev/posts/2020/creating-a-simple-openapi-spec" rel="alternate"/><published>2020-08-06T13:44:00+01:00</published><updated>2020-08-06T13:44:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-08-06:/posts/2020/creating-a-simple-openapi-spec</id><summary type="html">&lt;p class="first last"&gt;Having API descriptions in your workflow is a game-changer, but how do you get from nothing to a functioning spec in the first place? I recently made a very simple spec for a very simple API, and thought it might make a good example for anyone looking to create a spec for the first time. OpenAPI specs are very verbose so this very tiny API makes quite a manageable example!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Having API descriptions in your workflow is a game-changer, but how do you get from nothing to a functioning spec in the first place? I recently made a very simple spec for a very simple API, and thought it might make a good example for anyone looking to create a spec for the first time. OpenAPI specs are very verbose so this very tiny API makes quite a manageable example!&lt;/p&gt;
&lt;blockquote&gt;
&lt;strong&gt;For the impatient:&lt;/strong&gt; if you just want to look at an example without any additional context, there's a spec here &lt;a class="reference external" href="https://github.com/lornajane/flask-planets-and-webhooks/blob/master/openapi.yaml"&gt;https://github.com/lornajane/flask-planets-and-webhooks/blob/master/openapi.yaml&lt;/a&gt;&lt;/blockquote&gt;
&lt;div class="section" id="about-the-api"&gt;
&lt;h3&gt;About the API&lt;/h3&gt;
&lt;p&gt;I've been using a very trivial API example project in a few of my recent talks on API-adjacent topics, with just a couple of endpoints. There's a &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2020/planets-and-webhooks-a-simple-flask-app"&gt;blog post about the Planets API&lt;/a&gt; but in short you can get a list of either one planet or many, with requests like:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
GET http://localhost:5000/planets
GET http://localhost:5000/planets/3
&lt;/pre&gt;
&lt;p&gt;Now you have a decent grasp of what this thing does, I'll start the description.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="create-file-configure-tools"&gt;
&lt;h3&gt;Create File, Configure Tools&lt;/h3&gt;
&lt;p&gt;I'll be using YAML, you can use JSON instead if you prefer. My file is called &lt;tt class="docutils literal"&gt;openapi.yaml&lt;/tt&gt; which is a reasonably common file name but you can name your file what you like.&lt;/p&gt;
&lt;p&gt;I never attempt to create a file like this without some sort of validation tool. Something like &lt;a class="reference external" href="https://stoplight.io/studio/"&gt;Stoplight Studio&lt;/a&gt; will do this for you, but I mostly use my usual editor so I'm using Stoplight's standalone open source linting tool &lt;a class="reference external" href="https://stoplight.io/open-source/spectral/"&gt;spectral&lt;/a&gt; combined with &lt;a class="reference external" href="https://linux.die.net/man/1/watch"&gt;watch&lt;/a&gt;, something like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
watch -n 3 spectral lint openapi.yaml
&lt;/pre&gt;
&lt;p&gt;Spectral is fairly Opinionated (with an intentional capital O) but it's a decent starting point.&lt;/p&gt;
&lt;p&gt;With this in place, I started adding a few lines to the file:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;3.0.3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Planets and Webhooks Demo API&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;0.0.1&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Simple flask-backed API showing some example API endpoints and with webhook debugging features.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Lorna Mitchell&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;https://github.com/lornajane/flask-planets-and-webhooks&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;github&amp;#64;lornajane.net&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;http://localhost:5000&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Sample project running locally&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;externalDocs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;GitHub project for the code and information about this API&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;https://github.com/lornajane/flask-planets-and-webhooks&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;You'll probably still see some validation errors, in this version of OpenAPI 3.0.3 it's required to have a &lt;tt class="docutils literal"&gt;paths:&lt;/tt&gt; item at the top level, but you get the idea of how the start of the file should look.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="add-api-endpoints"&gt;
&lt;h3&gt;Add API Endpoints&lt;/h3&gt;
&lt;p&gt;This API has two endpoints, so it's a nice, small problem space to try to describe! The &lt;tt class="docutils literal"&gt;/planets&lt;/tt&gt; route returns JSON, an array of objects where each object describes a planet. The &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/planets/{id}&lt;/span&gt;&lt;/tt&gt; route returns just one object, but it is identical to the object returned by the collection. OpenAPI supports a special &lt;tt class="docutils literal"&gt;$ref&lt;/tt&gt; field for reusing definitions across different parts of the spec. You can use it to structure your spec in a more readable way - but its superpower is in making sure that things that are the same are only defined once, making it easier to keep everything consistent and maintainable.&lt;/p&gt;
&lt;p&gt;I prefer to start with an endpoint without any reuse, then refactor to components later. As an example, here's the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/planets/{id}&lt;/span&gt;&lt;/tt&gt; endpoint in its first iteration:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;/planets/{planetId}&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;onePlanet&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Fetch one planet by position&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Get the data relating to one planet&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;planetId&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;path&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;number&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;4&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s"&gt;'200'&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Planets in a list&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nt"&gt;application/json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nt"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;object&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nt"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Name of planet&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Saturn&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nt"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;number&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Order in place from the sun&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;6&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nt"&gt;moons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;number&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Number of moons, according to NASA&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;62&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;I did say it was verbose! The value is in the detail though - every tool that consumes this description has all this information and examples available.&lt;/p&gt;
&lt;p&gt;I know I'll need the actual planet object again though, so here's the same sample again, refactored to put the planet object into &lt;tt class="docutils literal"&gt;components&lt;/tt&gt; as a reusable element:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;/planets/{planetId}&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;onePlanet&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Fetch one planet by position&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Get the data relating to one planet&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;planetId&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;path&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;number&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;4&lt;/span&gt;&lt;span class="w"&gt;

      &lt;/span&gt;&lt;span class="nt"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s"&gt;'200'&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Planets in a list&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nt"&gt;application/json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nt"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nt"&gt;$ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#/components/schemas/planet&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;planet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;object&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Name of planet&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Saturn&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;number&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Order in place from the sun&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;6&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;moons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;number&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Number of moons, according to NASA&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;62&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;With the reusable component ready, have a look at how the collection looks using it:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;/planets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;allPlanets&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;List all planets&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Returns a list of all the planets that are stored in the system.&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s"&gt;'200'&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Planets in a list&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nt"&gt;application/json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nt"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;array&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nt"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nt"&gt;$ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#/components/schemas/planet&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;It's a little different as it's an array of objects, but we can reuse that single object as an array item.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="documentation-as-output"&gt;
&lt;h3&gt;Documentation as Output&lt;/h3&gt;
&lt;p&gt;There are so many things you can do with an OpenAPI spec but a very human-centric one is to generate documentation. There are a few options for this, again Stoplight Studio has it built in, but I mostly use open source tool &lt;a class="reference external" href="https://github.com/Redocly/redoc/"&gt;ReDoc&lt;/a&gt; - their 2.0 release isn't official yet but it is great! Importantly, the project was very receptive to, and quick to fix, the accessibility issues I logged.&lt;/p&gt;
&lt;p&gt;&lt;img alt="screenshot of redoc rendering openapi.yaml" class="alignnone size-large wp-image-4248" src="https://lornajane-net.pages.dev/images/2020/08/Screenshot_2020-08-02-ReDoc-1024x492.png" style="width: 584px; height: 281px;" /&gt;&lt;/p&gt;
&lt;p&gt;Generating some documentation as you go along can help show how things are going when you come to describe your own APIs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="final-touches"&gt;
&lt;h3&gt;Final Touches&lt;/h3&gt;
&lt;p&gt;If you're playing along at home, you'll notice that spectral is still having Opinions! That's because we haven't added tags yet. Tags aren't that useful on an OpenAPI spec this size, but for larger APIs they are an excellent innovation. The previous yaml example showing the collection endpoint has a tag, and to finish, all I need to show you is the &lt;tt class="docutils literal"&gt;tags:&lt;/tt&gt; element in the spec:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;planets&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;API containing solar system data&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;For tags, parameters, respond fields, endpoints, and really everything else, the descriptions and examples are at least as important as the field name and data type information! Not all of us are talented writers but the best specifications are those that do include these extra-value items - the improved Developer Experience (or as I like to call it &amp;quot;developer delight factor&amp;quot;) is absolutely worth it!&lt;/p&gt;
&lt;p&gt;Let me know if you found this post useful or if there's anything more detailed you'd like to see covered. I'm still figuring a bunch of these things out myself but I'm happy to share :)&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="api"/><category term="openapi"/><category term="planet"/></entry><entry><title>Using Environments in Postman</title><link href="https://lornajane-net.pages.dev/posts/2020/using-environments-in-postman" rel="alternate"/><published>2020-07-23T16:55:00+01:00</published><updated>2020-07-23T16:55:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-07-23:/posts/2020/using-environments-in-postman</id><summary type="html">&lt;p class="first last"&gt;I'm using &lt;a class="reference external" href="https://postman.com"&gt;Postman&lt;/a&gt; more lately and showing off the Environments feature really often. I really like this feature for giving easy ways to handle values I use frequently, but it seems to be a bit of a hidden gem, so I thought I'd write about when I find it useful, in case you do too!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I'm using &lt;a class="reference external" href="https://postman.com"&gt;Postman&lt;/a&gt; more lately and showing off the Environments feature really often. I really like this feature for giving easy ways to handle values I use frequently, but it seems to be a bit of a hidden gem, so I thought I'd write about when I find it useful, in case you do too!&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Environments are essentially a collection of variables. Most of mine contain:&lt;/div&gt;
&lt;div class="line"&gt;* API credentials, either a token or a username/password&lt;/div&gt;
&lt;div class="line"&gt;* Base URLs so I can change which platform I'm targetting&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The controls for Environments are in the top left, no down a little, that's it! A dropdown, an eye, and a settings cog :)&lt;/p&gt;
&lt;p&gt;&lt;img alt="image1" class="alignnone size-medium wp-image-4221" src="https://lornajane-net.pages.dev/images/2020/07/postman-envs-300x90.png" style="width: 300px; height: 90px;" /&gt;&lt;/p&gt;
&lt;p&gt;You can set up different Environments for test/live platforms, for different geographical regions, or whatever you need. I use &amp;quot;Lorna Nexmo&amp;quot; for my live Nexmo credentials and API base URLs, and &amp;quot;Prism&amp;quot; for when I'm using &lt;a class="reference external" href="https://github.com/stoplightio/prism"&gt;prism&lt;/a&gt; as a local mock version of an API. Switching between the two becomes very quick and convenient, and I find it a real timesaver!&lt;/p&gt;
&lt;p&gt;Once you have variables set up in an Environment, it makes it very easy to use them whenever you need them. This is great because I don't have to keep looking up credentials every time I want to add them to a request. I can just type &lt;tt class="docutils literal"&gt;{{&lt;/tt&gt; and see what autocompletes!&lt;/p&gt;
&lt;p&gt;&lt;img alt="start typing {{ and get a menu of entries to use" class="alignnone size-medium wp-image-4224" src="https://lornajane-net.pages.dev/images/2020/07/postman-envs2-300x100.png" style="width: 300px; height: 100px;" /&gt;&lt;/p&gt;
&lt;p&gt;Such a simple feature but very handy. Also, see in the screenshot it previews the values? I can use the &amp;quot;Prism&amp;quot; Environment so that I don't display my actual credentials when I'm streaming/speaking/blogging, and then just flip it in the Environments dropdown. Very useful!&lt;/p&gt;
</content><category term="tech"/><category term="api"/><category term="postman"/><category term="tips"/></entry><entry><title>Planets and Webhooks: a simple Flask app</title><link href="https://lornajane-net.pages.dev/posts/2020/planets-and-webhooks-a-simple-flask-app" rel="alternate"/><published>2020-07-22T14:50:00+01:00</published><updated>2020-07-22T14:50:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-07-22:/posts/2020/planets-and-webhooks-a-simple-flask-app</id><summary type="html">&lt;p class="first last"&gt;As a Developer Advocate for an API company, I spend a lot of time talking about APIs and webhooks and HTTP in general. Recently I've been focussing on HTTP tools, but I really wanted a very simple example API that I could use that would return JSON but really let me focus on the tools, not the API. So I created a &amp;quot;Planets and Webhooks&amp;quot; API with a couple of &lt;tt class="docutils literal"&gt;GET&lt;/tt&gt; endpoints to return JSON data, and another endpoint to receive and log incoming webhook data.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;As a Developer Advocate for an API company, I spend a lot of time talking about APIs and webhooks and HTTP in general. Recently I've been focussing on HTTP tools, but I really wanted a very simple example API that I could use that would return JSON but really let me focus on the tools, not the API. So I created a &amp;quot;Planets and Webhooks&amp;quot; API with a couple of &lt;tt class="docutils literal"&gt;GET&lt;/tt&gt; endpoints to return JSON data, and another endpoint to receive and log incoming webhook data.&lt;/p&gt;
&lt;div class="section" id="planets-flask-app"&gt;
&lt;h3&gt;Planets Flask App&lt;/h3&gt;
&lt;p&gt;The project is on GitHub: &lt;a class="reference external" href="https://github.com/lornajane/flask-planets-and-webhooks"&gt;https://github.com/lornajane/flask-planets-and-webhooks&lt;/a&gt; so you can also grab it yourself to use when testing out any of the HTTP tools or getting to know webhooks that I covered in my talk (this probably makes more sense if you've seen one of my HTTP talks/streams, but hopefully still can be useful by itself).&lt;/p&gt;
&lt;p&gt;It very simply returns a little bit of info about a planet. For example:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ curl http://localhost:5000/planets/5
{&amp;quot;name&amp;quot;: &amp;quot;Jupiter&amp;quot;, &amp;quot;position&amp;quot;: 5, &amp;quot;moons&amp;quot;: 79}
&lt;/pre&gt;
&lt;p&gt;You can use it to check out how different HTTP clients (&lt;a class="reference external" href="https://curl.haxx.se/"&gt;curl&lt;/a&gt;, &lt;a class="reference external" href="https://httpie.org"&gt;httpie&lt;/a&gt; and friends) handle JSON, or how tools like &lt;a class="reference external" href="https://stedolan.github.io/jq/"&gt;jq&lt;/a&gt; or &lt;a class="reference external" href="https://github.com/antonmedv/fx"&gt;fx&lt;/a&gt; can help.&lt;/p&gt;
&lt;p&gt;The repo also includes some of the tools I usually mention: &lt;a class="reference external" href="https://github.com/lornajane/flask-planets-and-webhooks/#http-tools"&gt;https://github.com/lornajane/flask-planets-and-webhooks/#http-tools&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="debugging-webhooks"&gt;
&lt;h3&gt;Debugging Webhooks&lt;/h3&gt;
&lt;p&gt;Sometimes it is useful to have an endpoint that can receive and correctly acknowledge incoming webhooks without doing a whole lot else. This application has a simple GET/POST parameter logger on the &lt;tt class="docutils literal"&gt;/webhook&lt;/tt&gt; endpoint. You can use &lt;a class="reference external" href="https://ngrok.com"&gt;Ngrok&lt;/a&gt; to allow an external webhook service such as Zapier to access your local running flask app.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="fun-with-http"&gt;
&lt;h3&gt;Fun With HTTP&lt;/h3&gt;
&lt;p&gt;Sharing the code I used in my demo is mostly useful for people who saw the demo but if it's useful to you too, then great! I'm not the world's finest python developer (yet) but I had fun building and using this application so let me know if you do too?&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="api"/><category term="http"/><category term="python"/><category term="tools"/></entry><entry><title>Neopixel Shelf</title><link href="https://lornajane-net.pages.dev/posts/2020/neopixel-shelf" rel="alternate"/><published>2020-07-18T17:47:00+01:00</published><updated>2020-07-18T17:47:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-07-18:/posts/2020/neopixel-shelf</id><summary type="html">&lt;p class="first"&gt;In these days of video calling in place of any other social contact, I've seen a lot of excellent video backgrounds. Perhaps I'm old-fashioned but I went the traditional route, and set up my computer close to a wall (so you can't see the mess in the room) and added a nice pinboard behind me. I looked a bit flat against the wall though, so I did what anyone else would do ... and built a handmade wooden shelf with neopixels in it!&lt;/p&gt;
&lt;p class="last"&gt;&lt;img alt="webcam's eye view, showing radiator, shelf with blue lights, and pinboard above" class="alignnone size-medium wp-image-4233" src="https://lornajane-net.pages.dev/images/2020/07/DSC_6168-300x169.jpg" style="width: 300px; height: 169px;" /&gt;&lt;/p&gt;
</summary><content type="html">&lt;p&gt;In these days of video calling in place of any other social contact, I've seen a lot of excellent video backgrounds. Perhaps I'm old-fashioned but I went the traditional route, and set up my computer close to a wall (so you can't see the mess in the room) and added a nice pinboard behind me. I looked a bit flat against the wall though, so I did what anyone else would do ... and built a handmade wooden shelf with neopixels in it!&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;img alt="webcam's eye view, showing radiator, shelf with blue lights, and pinboard above" class="alignnone size-medium wp-image-4233" src="https://lornajane-net.pages.dev/images/2020/07/DSC_6168-300x169.jpg" style="width: 300px; height: 169px;" /&gt;&lt;/div&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;According to Instagram, I published pictures there in April, but I realised when &lt;a class="reference external" href="https://twitch.tv/lornajanetv"&gt;streaming&lt;/a&gt; (about controlling my LEDs while streaming, obviously!) that I had never written the blog post about this project. So, here goes :)&lt;/p&gt;
&lt;p&gt;I started with a leftover bit of aborted piano stool project. This is lockdown, so buying wood is not easy and I love repurposing things anyway.&lt;/p&gt;
&lt;p&gt;&lt;img alt="reclaimed wood with dowels sticking out and glue in patches" class="alignnone size-medium wp-image-4230" src="https://lornajane-net.pages.dev/images/2020/07/DSC_6153-300x169.jpg" style="width: 300px; height: 169px;" /&gt;&lt;/p&gt;
&lt;p&gt;I pulled out (or cut off) the dowels, then cut the piece into two wider pieces (I needed to join bits to make the length of shelf I needed) and faced them. To join, I drilled into the ends and used dowels and glue to hold things together. Using dowel punches makes this step easier but I still had to re-face the finished product as it didn't perfectly align.&lt;/p&gt;
&lt;p&gt;Ours is a rather period home (technically it's Edwardian) so everything has twiddly edges! I routed a pretty edge onto my self, and then also routed a groove near the back of the shelf for the neopixels to sit in so that their light, but not really they, can be seen.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Shelf top, with routed edges and the groove for the lights" class="alignnone size-medium wp-image-4231" src="https://lornajane-net.pages.dev/images/2020/07/DSC_6157-300x169.jpg" style="width: 300px; height: 169px;" /&gt;&lt;/p&gt;
&lt;p&gt;With the last of the remaining top and some shelf supports cut by my handy bystanding CNC owner, I was ready to assemble the main part of the shelf. Using a solid back like this makes everything super sturdy and also gives me an easy way to hang the shelf on the wall with keyhole hangers.&lt;/p&gt;
&lt;p&gt;&lt;img alt="shelf and supports clamed in a really chaotic workshop" class="alignnone size-medium wp-image-4229" src="https://lornajane-net.pages.dev/images/2020/07/DSC_6158-300x169.jpg" style="width: 300px; height: 169px;" /&gt;&lt;/p&gt;
&lt;p&gt;Looking back at these photos reminds me that the workshop was really chaotic earlier this year, we had a lot of new tools delivered literally as lockdown was announced, but we were also storing a bunch of things for family who had just moved. The space is much easier to use now, as you can tell from these shots, things were quite cramped at one point!&lt;/p&gt;
&lt;p&gt;Anyway, add the neopixels and a microcontroller (I'll write about that bit in another post, it's an ESP8266 running Lua), add support for sending different colours over MQTT from the streamdeck on your desk and here you have it:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Shelf with LEDs, now on the wall" class="alignnone size-medium wp-image-4232" src="https://lornajane-net.pages.dev/images/2020/07/DSC_6167-300x169.jpg" style="width: 300px; height: 169px;" /&gt;&lt;/p&gt;
&lt;p&gt;I don't write much about my woodwork or craft projects on my blog these days, it's very software-oriented, but since I'll also be sharing more of the software side of this project I thought I'd start by sharing the shelf itself! Do you have a low-tech background for videos? I would love to know :)&lt;/p&gt;
</content><category term="craft"/><category term="craft"/><category term="iot"/><category term="leds"/><category term="woodwork"/><category term="tech"/></entry><entry><title>Add a Screenshot Button to Streamdeck with Golang</title><link href="https://lornajane-net.pages.dev/posts/2020/add-a-screenshot-button-to-streamdeck-with-golang" rel="alternate"/><published>2020-07-06T10:09:00+01:00</published><updated>2020-07-06T10:09:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-07-06:/posts/2020/add-a-screenshot-button-to-streamdeck-with-golang</id><summary type="html">&lt;p class="first last"&gt;I'm the proud owner of a Streamdeck XL but as an Ubuntu user, the tool support isn't great. There's a Python library that gives a bit of a GUI but I found it hard to use and I'd have needed to put each piece of functionality as a commandline script that this program could call. Instead, I am using &lt;a class="reference external" href="https://github.com/magicmonkey/go-streamdeck"&gt;go-streamdeck&lt;/a&gt; to create a custom application - and I'm having fun! Today's example adds a single button that runs a command to take a screenshot.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I'm the proud owner of a Streamdeck XL but as an Ubuntu user, the tool support isn't great. There's a Python library that gives a bit of a GUI but I found it hard to use and I'd have needed to put each piece of functionality as a commandline script that this program could call. Instead, I am using &lt;a class="reference external" href="https://github.com/magicmonkey/go-streamdeck"&gt;go-streamdeck&lt;/a&gt; to create a custom application - and I'm having fun! Today's example adds a single button that runs a command to take a screenshot.&lt;/p&gt;
&lt;p&gt;&lt;img alt="the streamdeck XL with &amp;quot;Pic&amp;quot; on the button on the top left" class="alignnone size-large wp-image-4216" src="https://lornajane-net.pages.dev/images/2020/07/streamdeck-screenshot-1024x576.jpg" style="width: 584px; height: 329px;" /&gt;&lt;/p&gt;
&lt;div class="section" id="command-to-take-a-screenshot"&gt;
&lt;h3&gt;Command to Take a Screenshot&lt;/h3&gt;
&lt;p&gt;I'm on Ubuntu so I can take a screenshot, of the active window, after 2 seconds, with a command like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
/usr/bin/gnome-screenshot -w -d 2
&lt;/pre&gt;
&lt;p&gt;I wanted to quickly run this from my streamdeck so I could take screenshots while I work (I write quite a lot of technical blog posts, this comes up pretty often). It works a treat!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro-tip:&lt;/strong&gt; Use a script to resize the window you are taking screenshots of (see also: &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2013/accurate-ubuntu-window-sizing-with-wmctrl"&gt;earlier post about resizing windows&lt;/a&gt;), this way they will all be the same size, a useful aspect ratio and if you need to replace one, it will match too.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="add-the-command-to-a-streamdeck-button"&gt;
&lt;h3&gt;Add the Command to a Streamdeck Button&lt;/h3&gt;
&lt;p&gt;Here's a simple example script that puts a button labelled &amp;quot;Pic&amp;quot; in the first slot on the streamdeck and sends my screenshot command when it is pressed.&lt;/p&gt;
&lt;pre class="code go literal-block"&gt;
&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fmt&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;io/ioutil&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os/exec&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sync&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/magicmonkey/go-streamdeck&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/magicmonkey/go-streamdeck/actionhandlers&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/magicmonkey/go-streamdeck/buttons&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/magicmonkey/go-streamdeck/devices&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nx"&gt;sd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;streamdeck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nx"&gt;myButton&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewTextButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Pic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;shotaction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;actionhandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CustomAction&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;shotaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SetHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;btn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;streamdeck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;takeScreenshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;myButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SetActionHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shotaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;sd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;myButton&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WaitGroup&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;takeScreenshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Taking screenshot with delay...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/usr/bin/gnome-screenshot&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-w&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StderrPipe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StdoutPipe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nx"&gt;slurp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ioutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%s\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slurp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;slurp2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ioutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%s\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slurp2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Taken screenshot&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Using the go routine means that if I have other functionality in this application then I don't need to wait for the command to complete before I can press another button - especially important because this command has a delay in it.&lt;/p&gt;
&lt;p&gt;I know I'll be referring back to this post when I want to add commands to buttons in my streamdeck project, so if it's helpful for you as well, then that is even better :) If you want to see my project (under active development just now) then it's &lt;a class="reference external" href="https://github.com/lornajane/streamdeck-tricks"&gt;on GitHub&lt;/a&gt;. Let me know if you use it!&lt;/p&gt;
&lt;/div&gt;
</content><category term="golang, tech"/><category term="go"/><category term="open source"/><category term="streamdeck"/><category term="ubuntu"/></entry><entry><title>Accessing Nested Config with Viper</title><link href="https://lornajane-net.pages.dev/posts/2020/accessing-nested-config-with-viper" rel="alternate"/><published>2020-06-25T21:28:00+01:00</published><updated>2020-06-25T21:28:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-06-25:/posts/2020/accessing-nested-config-with-viper</id><summary type="html">&lt;p class="first last"&gt;I'm writing a Go application that glues together a bunch of other things, so it has a whole bunch of basically unrelated config, dumped in a yaml file. I was struggling a little with this non-standard use of &lt;a class="reference external" href="https://github.com/spf13/viper"&gt;Viper&lt;/a&gt; but actually, it does everything I needed. And, presumaby, more besides. I thought I would put some examples here to show how to handle this.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I'm writing a Go application that glues together a bunch of other things, so it has a whole bunch of basically unrelated config, dumped in a yaml file. I was struggling a little with this non-standard use of &lt;a class="reference external" href="https://github.com/spf13/viper"&gt;Viper&lt;/a&gt; but actually, it does everything I needed. And, presumaby, more besides. I thought I would put some examples here to show how to handle this.&lt;/p&gt;
&lt;div class="section" id="a-slice-of-structs"&gt;
&lt;h3&gt;A Slice of Structs&lt;/h3&gt;
&lt;p&gt;Let's start with the configured colours to send to my LED shelf (which I realised I didn't blog about, but I should!) In the &lt;tt class="docutils literal"&gt;config.yaml&lt;/tt&gt; file it is a series of objects, with properties &lt;tt class="docutils literal"&gt;red&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;green&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;blue&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
shelf_lights:
  - red: 120
    blue: 200
  - red: 120
    green: 150
  - blue: 200
    green: 40
    red: 60
&lt;/pre&gt;
&lt;p&gt;In my code, I have a struct called &lt;tt class="docutils literal"&gt;LEDColour&lt;/tt&gt; to represent each of these objects, and it uses field tags to explain how the yaml data fits into the struct - similar to how the JSON field tags work if you've seen those. Viper uses a neat package called &lt;a class="reference external" href="https://github.com/mitchellh/mapstructure"&gt;mapstructure&lt;/a&gt; to handle this part. My struct looks like this:&lt;/p&gt;
&lt;pre class="code go literal-block"&gt;
&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LEDColour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Red&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`mapstructure:&amp;quot;red&amp;quot;`&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Green&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`mapstructure:&amp;quot;green&amp;quot;`&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Blue&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`mapstructure:&amp;quot;blue&amp;quot;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;To wrangle the yaml data into this structure, I declare the variable it will go into, then Viper has an &lt;tt class="docutils literal"&gt;UnmarshalKey&lt;/tt&gt; which allows me to grab &lt;em&gt;just&lt;/em&gt; the &lt;tt class="docutils literal"&gt;shelf_lights&lt;/tt&gt; section from config and work on that.&lt;/p&gt;
&lt;pre class="code go literal-block"&gt;
&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lights&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;LEDColour&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;viper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UnmarshalKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;shelf_lights&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;lights&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;There was a lot of preamble but the actual moment it happens is neat!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="map-of-structs-with-string-keys"&gt;
&lt;h3&gt;Map of Structs with String Keys&lt;/h3&gt;
&lt;p&gt;This is pretty similar to above but I think a specific example might help. With a section of the yaml config file like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
obs_scenes:
  Camera:
    name: Camera
    image: &amp;quot;/camera.png&amp;quot;
  Offline:
    name: Offline
    image: &amp;quot;/offline.png&amp;quot;
  Secrets:
    name: Secrets
    image: &amp;quot;/secrets.png&amp;quot;
&lt;/pre&gt;
&lt;p&gt;I have another struct, also with mapstructure features:&lt;/p&gt;
&lt;pre class="code go literal-block"&gt;
&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ObsScene&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`mapstructure:&amp;quot;name&amp;quot;`&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`mapstructure:&amp;quot;image&amp;quot;`&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;ButtonId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;ButtonId&lt;/tt&gt; field isn't updated until the scene is assigned to a button dynamically (this is from my streamdeck utility, OBS is a video streaming tool, don't worry if the words don't mean anything! One OBS has many scenes) so we don't need to include it when we're reading from config. Which looks like this:&lt;/p&gt;
&lt;pre class="code go literal-block"&gt;
&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;buttons_obs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ObsScene&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;buttons_obs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ObsScene&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;viper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UnmarshalKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;obs_scenes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;buttons_obs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;As usual, Viper has us covered and while for many applications it makes sense to read in the whole config file and refer to each setting as you need it, this ability to transform sections of config is very handy indeed!&lt;/p&gt;
&lt;/div&gt;
</content><category term="golang"/><category term="golang"/><category term="tips"/><category term="tech"/></entry><entry><title>Use a Local Version of a Library in Go</title><link href="https://lornajane-net.pages.dev/posts/2020/use-a-local-version-of-a-library-in-go" rel="alternate"/><published>2020-06-15T09:51:00+01:00</published><updated>2020-06-15T09:51:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-06-15:/posts/2020/use-a-local-version-of-a-library-in-go</id><summary type="html">&lt;p class="first last"&gt;I have a couple of projects in Go where I need to work with a branch that isn't the released version of a library that my code depends on. This happens when I'm the developer of the library and an application to use it, or when I'm a contributor to the library so I have my own fork and will want to check out branches to submit or test patches. Go has a pretty need way to allow this: using the &lt;tt class="docutils literal"&gt;replace&lt;/tt&gt; keyword in the &lt;tt class="docutils literal"&gt;go.mod&lt;/tt&gt; file.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I have a couple of projects in Go where I need to work with a branch that isn't the released version of a library that my code depends on. This happens when I'm the developer of the library and an application to use it, or when I'm a contributor to the library so I have my own fork and will want to check out branches to submit or test patches. Go has a pretty need way to allow this: using the &lt;tt class="docutils literal"&gt;replace&lt;/tt&gt; keyword in the &lt;tt class="docutils literal"&gt;go.mod&lt;/tt&gt; file.&lt;/p&gt;
&lt;div class="section" id="set-up-git-remotes"&gt;
&lt;h3&gt;Set up Git Remotes&lt;/h3&gt;
&lt;p&gt;Start by cloning the library into the place in your &lt;tt class="docutils literal"&gt;GOPATH&lt;/tt&gt; that this library would usually live. In this example, I'm using the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;magicmonkey/go-streamdeck&lt;/span&gt;&lt;/tt&gt; library (lots more to write about this library so watch this space) so it lives at &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;$GOPATH/src/github.com/magicmonkey/go-streamdeck&lt;/span&gt;&lt;/tt&gt; and is a clone of that main GitHub repo at &lt;a class="reference external" href="https://github.com/magicmonkey/go-streamdeck"&gt;https://github.com/magicmonkey/go-streamdeck&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since I'm also contributing changes to this library, I forked the GitHub project and have my own copy (that I can commit to branches on) at &lt;a class="reference external" href="https://github.com/lornajane/go-streamdeck"&gt;https://github.com/lornajane/go-streamdeck&lt;/a&gt;. Grabbing the clone URL, I added this repo as another remote to the existing &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-streamdeck&lt;/span&gt;&lt;/tt&gt; library:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
git remote add lornajane [paste clone URL]
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Pro-tip:&lt;/strong&gt; git does not check this remote exists or that you can access it, it just believes you and stores the URL. Run &lt;tt class="docutils literal"&gt;git fetch lornajane&lt;/tt&gt; (or whatever you named your remote) to actually link with it.&lt;/p&gt;
&lt;p&gt;Now the library is all set up, and I need to configure my own project to use the local version of the code, not look for the latest public release.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configure-go-mod-to-use-local-dependency"&gt;
&lt;h3&gt;Configure Go.Mod to use Local Dependency&lt;/h3&gt;
&lt;p&gt;In the &lt;tt class="docutils literal"&gt;go.mod&lt;/tt&gt; file for the project that uses this library, the first few lines look like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
module github.com/lornajane/streamdeck-tricks

go 1.14

replace github.com/magicmonkey/go-streamdeck =&amp;gt; /home/lorna/go/src/github.com/magicmonkey/go-streamdeck
&lt;/pre&gt;
&lt;p&gt;... then the usual &lt;tt class="docutils literal"&gt;require&lt;/tt&gt; block is below.&lt;/p&gt;
&lt;p&gt;This setup means that changes I make on disk are reflected immediately in my project, I don't even need to commit. For creating and testing patches to a library, it's excellent and I have to look up the syntax every time so it's here for fast(er) reference!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; Completely failed to link &lt;a class="reference external" href="https://thewebivore.com/using-replace-in-go-mod-to-point-to-your-local-module/"&gt;this excellent post on using replace from Pam Selle&lt;/a&gt; which covers the same stuff but better.&lt;/p&gt;
&lt;/div&gt;
</content><category term="golang"/><category term="golang"/><category term="streamdeck"/><category term="tech"/></entry><entry><title>OBS Studio, Ubuntu 20.04 and a Wacom Tablet</title><link href="https://lornajane-net.pages.dev/posts/2020/obs-ubuntu-20-04-and-a-wacom-tablet" rel="alternate"/><published>2020-05-31T20:31:00+01:00</published><updated>2020-05-31T20:31:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-05-31:/posts/2020/obs-ubuntu-20-04-and-a-wacom-tablet</id><summary type="html">&lt;p&gt;When I upgraded my computers to Ubuntu 20.04, I noticed a weird problem with OBS when using an old Wacom Bamboo tablet (because, have you ever tried to use OBS without a pointing device?). I couldn't actually click anything! The pointer seemed to be ... pointing to the wrong place …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When I upgraded my computers to Ubuntu 20.04, I noticed a weird problem with OBS when using an old Wacom Bamboo tablet (because, have you ever tried to use OBS without a pointing device?). I couldn't actually click anything! The pointer seemed to be ... pointing to the wrong place. After some research (and then some help with my research), I came across &lt;a class="reference external" href="https://obsproject.com/forum/threads/mouse-offset-on-obs-window.109957/"&gt;this post on OBS forums&lt;/a&gt; suggesting that this fixes it:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
QT_XCB_TABLET_LEGACY_COORDINATES=1 obs
&lt;/pre&gt;
&lt;p&gt;It does, and I never want to do the research for this again, so I'm posting it here and hoping I remember to look here when it happens!&lt;/p&gt;
</content><category term="tech"/><category term="obs"/><category term="streaming"/><category term="ubuntu"/></entry><entry><title>Eye Contact Owl</title><link href="https://lornajane-net.pages.dev/posts/2020/eye-contact-owl" rel="alternate"/><published>2020-05-26T20:11:00+01:00</published><updated>2020-05-26T20:11:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-05-26:/posts/2020/eye-contact-owl</id><summary type="html">&lt;p class="first last"&gt;I don't post as much handicraft on this blog as I used to (it's mostly &lt;a class="reference external" href="https://www.instagram.com/lornajane_net/"&gt;on my instagram account&lt;/a&gt; with fewer words now), but I wanted to share my new handmade owl. He solves a genuine problem: how to make eye contact with your webcam.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I don't post as much handicraft on this blog as I used to (it's mostly &lt;a class="reference external" href="https://www.instagram.com/lornajane_net/"&gt;on my instagram account&lt;/a&gt; with fewer words now), but I wanted to share my new handmade owl. He solves a genuine problem: how to make eye contact with your webcam.&lt;/p&gt;
&lt;p&gt;&lt;p&gt;&lt;a class="reference external image-reference" href="https://www.flickr.com/photos/lornajane/49938612073/in/datetaken/"&gt;&lt;img alt="Untitled" src="https://live.staticflickr.com/65535/49938612073_211cd2e9ca_z.jpg" style="width: 640px; height: 360px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"&gt;&lt;/script&gt;&lt;/p&gt;&lt;div class="section" id="the-idea"&gt;
&lt;h3&gt;The Idea&lt;/h3&gt;
&lt;p&gt;I see a lot of people looking at another screen or some space below the webcam and for a built-in laptop webcam, it's genuinely difficult to look up at the webcam rather than at the video of a person you are talking to, and I don't think anyone minds.&lt;/p&gt;
&lt;p&gt;Where it really matters though is when you are presenting slides to a meeting or online conference. There's nothing to look at and so you ... don't. It's weird for the audience and I wanted to make myself somethin gthat could help.&lt;/p&gt;
&lt;p&gt;I've seen some excellent hacks (I think &lt;a class="reference external" href="https://medium.com/design-intelligence/https-medium-com-alexacurtis-eye-contact-during-video-chats-89fd9f2b48b7"&gt;this with the post-it notes&lt;/a&gt; is my favourite and it's a great article too) and I wanted something. So I thought about it and ended up quite quickly at &amp;quot;owl&amp;quot;. They have big eyes to make eye contact with and they're not a complicated shape to create!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="ingredients-and-method"&gt;
&lt;h3&gt;Ingredients and method&lt;/h3&gt;
&lt;p&gt;Using PVA glue, I stuck a piece of leftover fabric from another project to a bit of amazon box (I'd already traced an owl so I knew what size I was aiming for). Apply heavy book, wait for that to dry.&lt;/p&gt;
&lt;p&gt;Then I cut out two sizes of felt circles for eyes (they're not very circular, I don't think the owl minds) and also made a beak. I found two matching buttons but I'm sure that more felt would have also looked fine here. More glue! Assemble face, and put cardboard on top (to protect the buttons) and put the heavy book back. Wait for that to dry; luckily it was a long weekend and I had time for waiting for things to dry.&lt;/p&gt;
&lt;p&gt;Finally, glue two tufts and an ordinary wooden clothes peg to the back of the cardboard at an angle that amuses you. I was worried about the heavy book bending the cardboard since it no longer had a flat side so I put a couple more pegs (without glue) alongside the glued peg and stood the glue pot on top to give it some weight without crushing it.&lt;/p&gt;
&lt;p&gt;And there you have it, the secret to video calls, online presentations, and the obligatory lockdown family zoom calls! Let me know if you make your own? Mine is pretty lonely by himself.&lt;/p&gt;
&lt;/div&gt;
</content><category term="craft"/><category term="craft"/><category term="owl"/><category term="peg"/><category term="webcam"/></entry><entry><title>Custom OpenAPI Style Rules with Spectral</title><link href="https://lornajane-net.pages.dev/posts/2020/custom-openapi-style-rules-with-spectral" rel="alternate"/><published>2020-05-21T15:54:00+01:00</published><updated>2020-05-21T15:54:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-05-21:/posts/2020/custom-openapi-style-rules-with-spectral</id><summary type="html">&lt;p class="first"&gt;&lt;strong&gt;Edit: There's a&lt;/strong&gt; &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2024/lint-apis-with-redocly-cli"&gt;newer post on this topic using Redocly CLI&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I work quite a bit with OpenAPI specs and with lots of specs and lots of collaborators, keeping the specs all functional (never mind tidy, consistent, or other dreamwords) is a challenge! We use &lt;a class="reference external" href="https://github.com/stoplightio/spectral"&gt;spectral&lt;/a&gt; to check our specs, both when we work on them and in the build process. Spectral is great but it has Opinions(TM)!&lt;/p&gt;
&lt;p class="last"&gt;For most users, running Spectral out of the box gives quite a lot of output even on an otherwise valid spec. I do think the default ruleset for Spectral is pretty good, but every situation is different so having your own ruleset to use is a good idea. This post shows how to use a ruleset and some examples.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;strong&gt;Edit: There's a&lt;/strong&gt; &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2024/lint-apis-with-redocly-cli"&gt;newer post on this topic using Redocly CLI&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I work quite a bit with OpenAPI specs and with lots of specs and lots of collaborators, keeping the specs all functional (never mind tidy, consistent, or other dreamwords) is a challenge! We use &lt;a class="reference external" href="https://github.com/stoplightio/spectral"&gt;spectral&lt;/a&gt; to check our specs, both when we work on them and in the build process. Spectral is great but it has Opinions(TM)!&lt;/p&gt;
&lt;p&gt;For most users, running Spectral out of the box gives quite a lot of output even on an otherwise valid spec. I do think the default ruleset for Spectral is pretty good, but every situation is different so having your own ruleset to use is a good idea. This post shows how to use a ruleset and some examples.&lt;/p&gt;
&lt;div class="section" id="disabling-spectral-rules"&gt;
&lt;h3&gt;Disabling Spectral Rules&lt;/h3&gt;
&lt;p&gt;(All these examples are for OpenAPI version 3.0.0 or later)&lt;/p&gt;
&lt;p&gt;Spectral (and if you're using &lt;a class="reference external" href="https://stoplight.io/studio/"&gt;Stoplight Studio&lt;/a&gt; this as well) will pick up a file called &lt;tt class="docutils literal"&gt;.spectral.yml&lt;/tt&gt; in the root of the project. In this file we can pick which style rules to follow, enable/disable rules and add our own rules. For a very simple starting point, try this:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;spectral:oas&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;openapi-tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;operation-tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;false&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Spectral is very keen that every endpoint should have a tag but in fact this isn't required by the spec. I work with a large number of small API specs and most don't have tags (since we'd just tag them all the same thing) so by using the &lt;tt class="docutils literal"&gt;spectral:oas&lt;/tt&gt; ruleset as a starting point, then disabling the two tag-related rules, I can run Spectral to get the benefit of all the other checks without getting error output from these tag rules.&lt;/p&gt;
&lt;p&gt;To know which rules to disable, check your output. Spectral gives very detailed error information including which rule was included and which line of the spec it happened on. It's your choice whether to fix the spec or silence the rule :)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="custom-functions"&gt;
&lt;h3&gt;Custom Functions&lt;/h3&gt;
&lt;p&gt;What happens when you need to add rules that don't already exist? You make your own!&lt;/p&gt;
&lt;p&gt;Spectral has some good built in functions that you can use and re-use as needed. For example, here is a rule I'm using to check that the version field in the info block is actually a version number (after we discovered one with version set to &amp;quot;beta&amp;quot; that broke some of our automated tagging stuff!)&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c1"&gt;# Require 3-part Semantic Versions as the spec versions&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;semver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;recommended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Specs should follow semantic versioning. {{value}} is not a valid version.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;given&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;$.info.version&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;then&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;pattern&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;functionOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;^([0-9]+.[0-9]+.[0-9]+)$&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Add this to the &lt;tt class="docutils literal"&gt;spectral.yml&lt;/tt&gt; file and it will pick up any spec where the version field in the info block isn't a number in the format &lt;tt class="docutils literal"&gt;x.x.x&lt;/tt&gt; - very handy!&lt;/p&gt;
&lt;p&gt;One thing that tripped me up was the JSON Path expressions needed to refer to different parts of the spec so here is a more deeply nested example: checking that all the response fields (that we always put in &lt;tt class="docutils literal"&gt;components/schemas&lt;/tt&gt;) are in &lt;tt class="docutils literal"&gt;snake_case&lt;/tt&gt;.&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;response-property-names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;warn&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Invalid&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;property&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;name:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{property}}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(expected:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;lowercase&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;underscores)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;recommended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;given&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$..components.schemas.*.properties[*]~&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;then&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;pattern&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;functionOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;^[a-z_]+$&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;By turning off the rules we don't need (or aren't ready to meet yet!), the style checking features of Spectral become much more useful. With the ability to use the tools locally and in our build chain (and of course included in Stoplight Studio), that makes it very easy for all our contributors to do the &amp;quot;right&amp;quot; thing rather than make changes and then have either me or the pull request build criticise their hard work!! Tools like this really improve the quality of the OpenAPI specs - but the detail can be complicated. Hopefully sharing this post helps a bit (and I know I'll be using it for reference in the future too).&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="openapi"/><category term="spectral"/></entry><entry><title>The python-is-python2 package on Ubuntu Focal Fossa</title><link href="https://lornajane-net.pages.dev/posts/2020/the-python-is-python2-package-on-ubuntu-focal-fossa" rel="alternate"/><published>2020-04-24T19:15:00+01:00</published><updated>2020-04-24T19:15:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-04-24:/posts/2020/the-python-is-python2-package-on-ubuntu-focal-fossa</id><summary type="html">&lt;p&gt;I did a fresh install of Ubuntu 20.04 Focal Fossa on my laptop, and was surprised at what happened when I went to install python:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ sudo apt install python
Reading package lists... Done
Building dependency tree
Reading state information... Done
Note, selecting 'python-is-python2' instead of 'python'
&lt;/pre&gt;
&lt;p&gt;Um, what?&lt;/p&gt;
&lt;p&gt;It …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I did a fresh install of Ubuntu 20.04 Focal Fossa on my laptop, and was surprised at what happened when I went to install python:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ sudo apt install python
Reading package lists... Done
Building dependency tree
Reading state information... Done
Note, selecting 'python-is-python2' instead of 'python'
&lt;/pre&gt;
&lt;p&gt;Um, what?&lt;/p&gt;
&lt;p&gt;It turns out that since the &lt;tt class="docutils literal"&gt;python&lt;/tt&gt; package has historically been Python 2, and there's a separate &lt;tt class="docutils literal"&gt;python3&lt;/tt&gt; package (and the commands match the package names, also &lt;tt class="docutils literal"&gt;pip3&lt;/tt&gt; etc), that default has remained in an attempt to break things for fewer people. Which is nice, but how do I get current, stable python?&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo apt install python-is-python3
&lt;/pre&gt;
&lt;p&gt;There's a sister package called &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;python-is-python3&lt;/span&gt;&lt;/tt&gt; and installing that makes my &lt;tt class="docutils literal"&gt;python&lt;/tt&gt; command use version 3 as I expected! It's a fairly good solution to the problem but it took me a moment to work out how to install it so I thought I'd write it down for next time! Hope it helps you too :)&lt;/p&gt;
</content><category term="tech"/><category term="apt"/><category term="python"/><category term="ubuntu"/></entry><entry><title>A First Netlify Function in Golang</title><link href="https://lornajane-net.pages.dev/posts/2020/a-first-netlify-function-in-golang" rel="alternate"/><published>2020-04-15T11:20:00+01:00</published><updated>2020-04-15T11:20:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-04-15:/posts/2020/a-first-netlify-function-in-golang</id><summary type="html">&lt;p class="first last"&gt;I love all things serverless, and &lt;a class="reference external" href="https://www.netlify.com/products/functions/"&gt;Netlify Functions&lt;/a&gt; is a neat and convenient way to access AWS Lambda. I do also love AWS Lambda but it's so powerful and flexible that creating something like a webhook receiver can be hard going by the time you have all the permissions and API Gateway setup sorted out - for a simple webhook receiver, Netlify functions is a nice and easy way to get going. Best of all: it supports Golang but the docs are very JS-heavy so I am writing my notes here in case I need to refer back to them some day.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I love all things serverless, and &lt;a class="reference external" href="https://www.netlify.com/products/functions/"&gt;Netlify Functions&lt;/a&gt; is a neat and convenient way to access AWS Lambda. I do also love AWS Lambda but it's so powerful and flexible that creating something like a webhook receiver can be hard going by the time you have all the permissions and API Gateway setup sorted out - for a simple webhook receiver, Netlify functions is a nice and easy way to get going. Best of all: it supports Golang but the docs are very JS-heavy so I am writing my notes here in case I need to refer back to them some day.&lt;/p&gt;
&lt;div class="section" id="write-a-function"&gt;
&lt;h3&gt;Write a function&lt;/h3&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://docs.netlify.com/functions/build-with-go/"&gt;Netlify docs for golang&lt;/a&gt; are a great starting point. I am deploying this project manually, so I need to build the thing myself rather than letting the Netlify CI servers do that.&lt;/p&gt;
&lt;p&gt;Here's a very simple example with a handler declared in &lt;tt class="docutils literal"&gt;main()&lt;/tt&gt; and then a function that will greet you by name if you supply a &lt;tt class="docutils literal"&gt;name&lt;/tt&gt; parameter in the query (this code lives in &lt;tt class="docutils literal"&gt;src/first.go&lt;/tt&gt;):&lt;/p&gt;
&lt;pre class="code go literal-block"&gt;
&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;context&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/aws/aws-lambda-go/events&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/aws/aws-lambda-go/lambda&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIGatewayProxyRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIGatewayProxyResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;QueryStringParameters&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;World&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIGatewayProxyResponse&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Hi &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Great, we wrote a function! Now what? Time to build the code before deploying it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="build-the-binary"&gt;
&lt;h3&gt;Build the binary&lt;/h3&gt;
&lt;p&gt;You can push code to GitHub and instruct the Netlify CI on how to use the source code to build what you want ... in this case I'm just pushing one function as an example so I'll do the building myself. I like to use a Makefile for this:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
build:&lt;span class="w"&gt;
    &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;functions&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;functions/first&lt;span class="w"&gt; &lt;/span&gt;./src/first.go
&lt;/pre&gt;
&lt;p&gt;Note that for &lt;tt class="docutils literal"&gt;go build&lt;/tt&gt; setting the appropriate config for Lambda is important here - on my Ubuntu machine, probably this would build correctly, but by being specific in my &lt;tt class="docutils literal"&gt;Makefile&lt;/tt&gt; I'm less likely to screw this up in future :)&lt;/p&gt;
&lt;p&gt;When I run this by typing &lt;tt class="docutils literal"&gt;make&lt;/tt&gt;, it puts the compiled &lt;tt class="docutils literal"&gt;first&lt;/tt&gt; binary into the &lt;tt class="docutils literal"&gt;functions/&lt;/tt&gt; directory. This is important as we work up to deploying in the next step.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="deploy-to-netlify"&gt;
&lt;h3&gt;Deploy to Netlify&lt;/h3&gt;
&lt;p&gt;I'm doing a manual deploy because I'm too lazy to set up a pipeline for a one-off project, so I am using the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;`netlify-cli&lt;/span&gt;&lt;/tt&gt; &amp;lt;&lt;a class="reference external" href="https://docs.netlify.com/cli/get-started/#installation"&gt;https://docs.netlify.com/cli/get-started/#installation&lt;/a&gt;&amp;gt;`__ command line tool here. Before I do though, I'll configure the settings in a &lt;tt class="docutils literal"&gt;netlify.toml&lt;/tt&gt; file in the root of my project. Mine looks like this:&lt;/p&gt;
&lt;pre class="code bash literal-block"&gt;
&lt;span class="o"&gt;[&lt;/span&gt;build&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;make build&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;functions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;functions&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;publish&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The important part here is the &lt;tt class="docutils literal"&gt;functions&lt;/tt&gt; setting - my build step above put the binaries into the &lt;tt class="docutils literal"&gt;functions/&lt;/tt&gt; directory, and that's what Netlify will deploy. There's more information in the &lt;tt class="docutils literal"&gt;`.netlify.toml&lt;/tt&gt; docs &amp;lt;&lt;a class="reference external" href="https://docs.netlify.com/configure-builds/file-based-configuration/"&gt;https://docs.netlify.com/configure-builds/file-based-configuration/&lt;/a&gt;&amp;gt;`__ too.&lt;/p&gt;
&lt;p&gt;Now the deploy command! By default this has a really neat feature where you can deploy a draft and check it before promoting to production. For this project though, I'm just going to go straight for the live platform. You only live once, after all.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
netlify deploy --prod
&lt;/pre&gt;
&lt;p&gt;This will upload whatever I put in the &lt;tt class="docutils literal"&gt;functions/&lt;/tt&gt; folder and then come back to me with a &amp;quot;Live URL&amp;quot; that it deployed to.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="call-the-function"&gt;
&lt;h3&gt;Call the function&lt;/h3&gt;
&lt;p&gt;Now I can call the function! The URL is a bit funky: [&lt;tt class="docutils literal"&gt;Live URL&lt;/tt&gt;]/.netlify/functions/first - but Netlify make this easy to find through the web interface, look in the Site you are working on, choose functions from the top bar and when you click on the function name, you get detailed information including its &amp;quot;Endpoint&amp;quot; and also some logs (the output of &lt;tt class="docutils literal"&gt;fmt.Print*&lt;/tt&gt; goes to the logs here, very useful for debugging!).&lt;/p&gt;
&lt;p&gt;I can append &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;?name=Jane&lt;/span&gt;&lt;/tt&gt; to see my function greet Jane rather than World, which is rather simple but neat. You can access a load of the request info AND the serverless function context from within the function so many more things are possible. I tend to think that the hard part is usually the moving parts and not the code, so hopefully this will help me next time I want to do this. If it helped you too, feel free to share more tips in the comments box!&lt;/p&gt;
&lt;/div&gt;
</content><category term="golang"/><category term="golang"/><category term="netlify"/><category term="serverless"/><category term="tech"/></entry><entry><title>Wget Direct to S3 with Golang Streams</title><link href="https://lornajane-net.pages.dev/posts/2020/wget-direct-to-s3-with-golang-streams" rel="alternate"/><published>2020-04-08T16:30:00+01:00</published><updated>2020-04-08T16:30:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-04-08:/posts/2020/wget-direct-to-s3-with-golang-streams</id><summary type="html">&lt;p class="first last"&gt;One thing I find very elegant about working with golang is that it is very open minded about where data is flowing in from or going out to. I recently built an application that downloaded an audio file from one location and pushed it to s3 - and golang makes it very easy to do that without needing to write an intermediate file and then upload that. Here's my code, in case I need to do this again some day :)&lt;/p&gt;
</summary><content type="html">&lt;p&gt;One thing I find very elegant about working with golang is that it is very open minded about where data is flowing in from or going out to. I recently built an application that downloaded an audio file from one location and pushed it to s3 - and golang makes it very easy to do that without needing to write an intermediate file and then upload that. Here's my code, in case I need to do this again some day :)&lt;/p&gt;
&lt;div class="section" id="downloading-a-file"&gt;
&lt;h3&gt;Downloading a File&lt;/h3&gt;
&lt;p&gt;By creating an HTTP request, I get a basic request object that I can change other parameters of if I need to. This example sets an &lt;tt class="docutils literal"&gt;Authorization&lt;/tt&gt; header too, to give you an idea of how the other configuration works.&lt;/p&gt;
&lt;pre class="code go literal-block"&gt;
&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RecordingURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Authorization&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Bearer &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;We haven't actually &lt;em&gt;done&lt;/em&gt; anything with the request yet, but by deferring the close we can access the &lt;tt class="docutils literal"&gt;resp.Body&lt;/tt&gt; as a stream when we come to send the file over to S3.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="uploading-to-s3"&gt;
&lt;h3&gt;Uploading to S3&lt;/h3&gt;
&lt;p&gt;It turns out that AWS already has a very nice SDK for working with Golang - it's not as popular as some of the other SDKs so I didn't find lots of code examples for working with it, but it was a very easy starting point! The SDK has a few ways to supply credentials but I like to use environment variables:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;AWS_ACCESS_KEY_ID&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;AWS_SECRET_ACCESS_KEY&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With these credentials set, and the &lt;tt class="docutils literal"&gt;resp&lt;/tt&gt; from the section above to read from, I can upload the file to S3. This is a &lt;tt class="docutils literal"&gt;.mp3&lt;/tt&gt; file so the &lt;tt class="docutils literal"&gt;ContentType&lt;/tt&gt; is set accordingly - change this for other file types in your own applications.&lt;/p&gt;
&lt;pre class="code go literal-block"&gt;
&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;audio/mpeg&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;sess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;us-east-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nx"&gt;uploader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s3manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewUploader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uploader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;s3manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UploadInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;my-bucket&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;uploaded-file.mp3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;I don't need to do anything with the response here, but if there's an error I do want to catch it and respond, so I've assigned &lt;tt class="docutils literal"&gt;err&lt;/tt&gt; in the example above.&lt;/p&gt;
&lt;p&gt;Just a very quick and simple example but it took me a little while to line up all the pieces I needed so I thought I'd share what I did!&lt;/p&gt;
&lt;/div&gt;
</content><category term="golang"/><category term="aws"/><category term="golang"/><category term="s3"/><category term="stream"/><category term="tech"/></entry><entry><title>HTTPMock for Testing a Golang API Client</title><link href="https://lornajane-net.pages.dev/posts/2020/httpmock-for-testing-a-golang-api-client" rel="alternate"/><published>2020-03-30T20:08:00+01:00</published><updated>2020-03-30T20:08:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-03-30:/posts/2020/httpmock-for-testing-a-golang-api-client</id><summary type="html">&lt;p class="first last"&gt;I'm working on an API client for some &lt;a class="reference external" href="https://developer.nexmo/com/api"&gt;Nexmo APIs&lt;/a&gt; and having tests to work through the various responses that the API can return and check that it does what I expect has been very handy so I thought I'd share my thoughts. I've got a bit of a head start here too since the OpenAPI specs have example responses in them that I can grab and feed to the mocking tool I'm using in my tests!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I'm working on an API client for some &lt;a class="reference external" href="https://developer.nexmo/com/api"&gt;Nexmo APIs&lt;/a&gt; and having tests to work through the various responses that the API can return and check that it does what I expect has been very handy so I thought I'd share my thoughts. I've got a bit of a head start here too since the OpenAPI specs have example responses in them that I can grab and feed to the mocking tool I'm using in my tests!&lt;/p&gt;
&lt;p&gt;For Golang, I'm using &lt;a class="reference external" href="https://github.com/jarcoal/httpmock"&gt;HTTPMock&lt;/a&gt;. It allows me to configure a particular response to send when a particular request is received. In fact, you can queue up a few responses but I'm writing a really thin API client so nothing needs more than one call so far.&lt;/p&gt;
&lt;p&gt;Inside each test, I configure the request to respond to and the response to send, something like this:&lt;/p&gt;
&lt;pre class="code go literal-block"&gt;
&lt;span class="nx"&gt;httpmock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Activate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;httpmock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DeactivateAndReset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nx"&gt;httpmock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RegisterResponder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://api.nexmo.com/verify/json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;httpmock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewStringResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`
{
    &amp;quot;request_id&amp;quot;: &amp;quot;abcdef0123456789abcdef0123456789&amp;quot;,
    &amp;quot;status&amp;quot;: &amp;quot;0&amp;quot;
}
`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

        &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;application/json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;What's great about this is that the rest of my test looks &amp;quot;normal&amp;quot; - this tool hijacks the web requests my code would have made, and returns its own responses instead. So I go ahead and put in the test basically the code I'd put in an application that used the library I'm working on.&lt;/p&gt;
&lt;pre class="code go literal-block"&gt;
&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CreateAuthFromKeySecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;12345678&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;456&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NewVerifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// the next line calls the code that actually makes the HTTP request that HTTPMock answers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;44777000777&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;NexmoGoTest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VerifyOpts&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Request ID: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RequestId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Request ID: abcdef0123456789abcdef0123456789&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Verify request failed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;In contrast to the other languages I've used with XUnit-type testing, golang doesn't really use the &amp;quot;assert&amp;quot; approach for testing, the idea is that you write code and if it doesn't work, there's your error. There are assertion libraries around but since I'm writing all new tests for basically new code, I thought I'd try &amp;quot;the go way&amp;quot; and see how it worked out. So far it's definitely a new way of thinking but I feel equally confident in the tests I'm creating.&lt;/p&gt;
&lt;p&gt;The magic really comes when I start adding failure cases to the tests, that way I can check that things fail the way I'd expect - and of course I can invent responses that the API should never be sending and check that the code also copes with the unexpected!&lt;/p&gt;
</content><category term="golang"/><category term="golang"/><category term="http"/><category term="tests"/><category term="tech"/></entry><entry><title>Remove Accidental Content from Git Commit</title><link href="https://lornajane-net.pages.dev/posts/2020/remove-accidental-content-from-git-commit" rel="alternate"/><published>2020-03-15T20:24:00+00:00</published><updated>2020-03-15T20:24:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-03-15:/posts/2020/remove-accidental-content-from-git-commit</id><summary type="html">&lt;p class="first last"&gt;When I teach git I try to show loads of good practice about how to inspect what's staged before commit, etc etc. Good practice is good of course, but knowing how to undo a mess you created is better - and mistakes will happen. For example, accidentally including a &lt;tt class="docutils literal"&gt;node_modules&lt;/tt&gt; directory in your otherwise excellent and useful commit. This post will walk you through how to fix the problem without losing any of your work.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;When I teach git I try to show loads of good practice about how to inspect what's staged before commit, etc etc. Good practice is good of course, but knowing how to undo a mess you created is better - and mistakes will happen. For example, accidentally including a &lt;tt class="docutils literal"&gt;node_modules&lt;/tt&gt; directory in your otherwise excellent and useful commit. This post will walk you through how to fix the problem without losing any of your work.&lt;/p&gt;
&lt;div class="section" id="oops-i-committed-node-modules-to-my-repo"&gt;
&lt;h3&gt;Oops, I committed node_modules to my repo&lt;/h3&gt;
&lt;p&gt;This is a real scenario (and it wasn't actually my mistake this time but honestly, I'm confident fixing this thing because I've done it more than once!!), a developer unintentionally included &lt;tt class="docutils literal"&gt;node_modules/&lt;/tt&gt; in the work he was doing. At this point, the project looks like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ git log --oneline
7a6528c (HEAD -&amp;gt; awesome-feature) Try to fix the problem
2144ece Make a brilliant change
70dbb37 (master) Project going well
&lt;/pre&gt;
&lt;p&gt;It's pretty common to try to remove things that shouldn't have been included in a repo by adding a commit to remove it but in lots of cases that doesn't solve the problem. If you accidentally committed your API credentials for example, they're still present in git history. Equally, if you included dependencies as this example does, and the problem is that they are too large to be accepted by GitHub: you still won't be able to push because the intermediate commit has the large files in.&lt;/p&gt;
&lt;p&gt;So what to do? First of all, keep calm!&lt;/p&gt;
&lt;p&gt;I also like to create an extra branch to point to the current &lt;tt class="docutils literal"&gt;HEAD&lt;/tt&gt; commit so I don't lose anything before I perform git surgery of any kind. Try this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
git branch safety
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="get-back-to-the-original-problem"&gt;
&lt;h3&gt;Get back to the original problem&lt;/h3&gt;
&lt;p&gt;Here, it's the second commit in the list that's a problem - but I already tried to fix it by adding another commit. That's not the right solution, so I'm going to start by throwing away the last commit (that tries to fix the problem) completely.&lt;/p&gt;
&lt;p&gt;Here's git log again so we can check what's going on:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ git log --oneline
7a6528c (HEAD -&amp;gt; awesome-feature, safety) Try to fix the problem
2144ece Make a brilliant change
70dbb37 (master) Project going well
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Pro-tip:&lt;/strong&gt; Check which branch you're on before you proceed. I have the branch name in my prompt but &lt;tt class="docutils literal"&gt;git status&lt;/tt&gt; will also include the current branch name.&lt;/p&gt;
&lt;p&gt;To undo the &amp;quot;fix&amp;quot; commit (that didn't fix things at all), make your current branch point to the previous commit &lt;em&gt;(if you run ``git reset --hard`` with uncommitted changes, they will be lost forever. Commit, make the safety branch, and breathe deeply before you rush in to this)&lt;/em&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
git reset --hard HEAD~1
&lt;/pre&gt;
&lt;p&gt;Where I used &lt;tt class="docutils literal"&gt;HEAD~1&lt;/tt&gt;, you can also supply the git commit reference. Using &lt;tt class="docutils literal"&gt;HEAD~1&lt;/tt&gt; just means &amp;quot;the commit before this one that I'm on now&amp;quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="undo-your-commit-but-not-your-changes"&gt;
&lt;h3&gt;Undo your commit but not your changes&lt;/h3&gt;
&lt;p&gt;For your next trick, we're going to use another &lt;tt class="docutils literal"&gt;reset&lt;/tt&gt; command but this time &lt;em&gt;without&lt;/em&gt; the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--hard&lt;/span&gt;&lt;/tt&gt;. The &lt;tt class="docutils literal"&gt;git reset&lt;/tt&gt; command can affect either the local disk state (what's in the real files on your computer), or the git metadata such as what commit &lt;tt class="docutils literal"&gt;HEAD&lt;/tt&gt; points to, or both. You already did &lt;tt class="docutils literal"&gt;git reset &lt;span class="pre"&gt;--hard&lt;/span&gt;&lt;/tt&gt; which does both and puts your working directory exactly to the state of a specific commit (and brings the &lt;tt class="docutils literal"&gt;HEAD&lt;/tt&gt; pointer to the same place).&lt;/p&gt;
&lt;p&gt;Now you're going to use &lt;tt class="docutils literal"&gt;git reset&lt;/tt&gt; without the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--hard&lt;/span&gt;&lt;/tt&gt; to move the &lt;tt class="docutils literal"&gt;HEAD&lt;/tt&gt; pointer but &lt;em&gt;not change any files at all&lt;/em&gt;.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
git reset HEAD~1
&lt;/pre&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Run &lt;tt class="docutils literal"&gt;git log &lt;span class="pre"&gt;--oneline&lt;/span&gt;&lt;/tt&gt; again and we're pointing to the earliest commit, before all the changes (good and unintentional) were made. But check &lt;tt class="docutils literal"&gt;git status&lt;/tt&gt; next and you should see all your changes there and ready for you to stage only the ones you intended to include! Some tips:&lt;/div&gt;
&lt;div class="line"&gt;* if you only need to add parts of a file, try &lt;tt class="docutils literal"&gt;git add &lt;span class="pre"&gt;-p&lt;/span&gt;&lt;/tt&gt;&lt;/div&gt;
&lt;div class="line"&gt;* use &lt;tt class="docutils literal"&gt;.gitignore&lt;/tt&gt; files to avoid committing dependencies, config files with secrets in, build directories ... whatever makes sense&lt;/div&gt;
&lt;div class="line"&gt;* run &lt;tt class="docutils literal"&gt;git status&lt;/tt&gt; one more time before you commit (this is advice for every day for me!)&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;When you're happy that only the good changes are included, go ahead and commit.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pushing-changes"&gt;
&lt;h3&gt;Pushing changes&lt;/h3&gt;
&lt;p&gt;If you already pushed changes, and then undid and redid them following this post, your push will be rejected. That's because git never allows us to rewrite history without leaving evidence that we did that!&lt;/p&gt;
&lt;p&gt;To push a branch that has changed history, you need to force push - and before you do that, you need to understand the consequences:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;If anyone has pushed to the branch since you did, you will lose their changes. Do &lt;tt class="docutils literal"&gt;git pull &lt;span class="pre"&gt;--rebase&lt;/span&gt;&lt;/tt&gt; to make sure you have everything&lt;/li&gt;
&lt;li&gt;If anyone has started a new branch from your branch, their work will be affected and while it's not unrecoverable, it's a tricky situation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is why we work on feature branches, kids. It's also why most master branches will not allow force pushes.&lt;/p&gt;
&lt;p&gt;If you're sure that force pushing is the right thing, for example if you are the only collaborator on the branch, then go ahead:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
git push -f
&lt;/pre&gt;
&lt;p&gt;And you're done! Well done for rescuing what could have been a tricky situation without having to do all that work again :) Go ahead and delete that &lt;tt class="docutils literal"&gt;safety&lt;/tt&gt; branch while you're thinking about it.&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="git"/><category term="tips"/></entry><entry><title>Teach Your API Test Platform to Send Callbacks</title><link href="https://lornajane-net.pages.dev/posts/2020/teach-your-api-test-platform-to-send-callbacks" rel="alternate"/><published>2020-02-18T15:50:00+00:00</published><updated>2020-02-18T15:50:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-02-18:/posts/2020/teach-your-api-test-platform-to-send-callbacks</id><summary type="html">&lt;p class="first last"&gt;I already wrote about &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2020/instant-test-api-platforms-with-prism"&gt;using Prism as an API test platform&lt;/a&gt; but I didn't include an example with callbacks so this post is to fill that gap! If you didn't read the previous post, the tl;dr is that &lt;a class="reference external" href="https://stoplight.io/open-source/prism"&gt;Prism&lt;/a&gt; is a nodejs open source tool that can take your &lt;a class="reference external" href="https://www.openapis.org/"&gt;OpenAPI&lt;/a&gt; specification and then do an impression of your live API, validating the API calls sent to it and responding with the example data described in the spec. It can also follow up the API response and send an HTTP request of its own to mock the callbacks described in your OpenAPI spec.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I already wrote about &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2020/instant-test-api-platforms-with-prism"&gt;using Prism as an API test platform&lt;/a&gt; but I didn't include an example with callbacks so this post is to fill that gap! If you didn't read the previous post, the tl;dr is that &lt;a class="reference external" href="https://stoplight.io/open-source/prism"&gt;Prism&lt;/a&gt; is a nodejs open source tool that can take your &lt;a class="reference external" href="https://www.openapis.org/"&gt;OpenAPI&lt;/a&gt; specification and then do an impression of your live API, validating the API calls sent to it and responding with the example data described in the spec. It can also follow up the API response and send an HTTP request of its own to mock the callbacks described in your OpenAPI spec.&lt;/p&gt;
&lt;div class="section" id="describing-callbacks-in-openapi"&gt;
&lt;h3&gt;Describing Callbacks in OpenAPI&lt;/h3&gt;
&lt;p&gt;I'm working with the Nexmo APIs quite a bit so here's an easy example from one of their specs. It's from the &amp;quot;Number Insight&amp;quot; API - you provide a phone number and the API returns you information about that number. There's a few different levels of information (and different levels of cost for each), but the &amp;quot;Advanced&amp;quot; level is pretty unreliable if you call it synchronously - so instead Nexmo will respond to your API call, then send a followup request to the URL you specify with a payload of all the data in it.&lt;/p&gt;
&lt;p&gt;Here's how the OpenAPI spec looks:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="s"&gt;&amp;quot;/advanced/async/{format}&amp;quot;&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;$ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#/components/parameters/format&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;getNumberInsightAsync&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Advanced Number Insight (async)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;$ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#/components/parameters/callback&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;$ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#/components/parameters/number&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;200&amp;quot;&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;OK&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;application/json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nt"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nt"&gt;$ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#/components/schemas/niResponseAsync&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;onData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{$request.query.callback}&amp;quot;&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nt"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;asyncCallback&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Asynchronous response&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Contains the response to your Number Insight Advanced API request.&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nt"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nt"&gt;application/json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nt"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nt"&gt;$ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#/components/schemas/niResponseJsonAdvanced&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nt"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;200&amp;quot;&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;OK&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;(if you want to see the whole thing, it's &lt;a class="reference external" href="https://github.com/Nexmo/api-specification/blob/master/definitions/number-insight.yml"&gt;on GitHub&lt;/a&gt; )&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;callbacks&lt;/tt&gt; section details the incoming requests to expect as a followup to the client's API call, and what response to send. In the initial request, the &lt;tt class="docutils literal"&gt;callback&lt;/tt&gt; parameter specifies where the &lt;tt class="docutils literal"&gt;callback&lt;/tt&gt; will be directed to - and Prism can do that too.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="mocking-the-api-and-callbacks"&gt;
&lt;h3&gt;Mocking The API and Callbacks&lt;/h3&gt;
&lt;p&gt;I can start up the &amp;quot;pretend&amp;quot; API with Prism like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
prism mock number-insight.yml
&lt;/pre&gt;
&lt;p&gt;Prism will output the endpoints that it got from the spec and then report its location - for me that is usually &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;http://localhost:4010&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;In order to receive the callback, I'll need to pass a URL that Prism can reach. This is one scenario where you won't need a public URL to test a webhook - because the webhook is coming from another local tool. In this case, I've got a simple webserver running on port 8080 that will simply output the data it receives so I can inspect it.&lt;/p&gt;
&lt;p&gt;Ready? Here's my curl command to request the async endpoint from the local mock API, and gives the callback to send the data payload to:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
curl &amp;quot;http://localhost:4010/advanced/async/json?api_key=123&amp;amp;api_secret=456&amp;amp;number=44777000777&amp;amp;callback=http://localhost:8080&amp;quot;
&lt;/pre&gt;
&lt;p&gt;I get a response to my API call, containing a &lt;tt class="docutils literal"&gt;request_id&lt;/tt&gt; and some other information, then (but seemingly instantly) Prism follows up by sending more example data to the &lt;tt class="docutils literal"&gt;callback&lt;/tt&gt; URL I specified before.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="testing-api-callbacks-locally"&gt;
&lt;h3&gt;Testing API Callbacks Locally&lt;/h3&gt;
&lt;p&gt;This setup is brilliant for a situation like this where I want to develop something to handle this callback - but I don't want to keep on hitting the live API (not to mention that I will run out of credit if I keep hitting this one!). I can work locally, regardless of whether my connectivity is great or even whether the API is up (or even exists yet).&lt;/p&gt;
&lt;p&gt;In the future, Prism is expected (by me at least) to also have support for the new Webhooks feature that is slated for OpenAPI 3.1 - enabling us to use a local mock server to send incoming requests as well as handling our outgoing ones. I, for one, can't wait!&lt;/p&gt;
&lt;/div&gt;
</content><category term="javascript"/><category term="api"/><category term="openapi"/><category term="prism"/><category term="stoplight"/><category term="tech"/></entry><entry><title>Make Thumbnails of PDF Pages with ImageMagick</title><link href="https://lornajane-net.pages.dev/posts/2020/make-thumbnails-of-pdf-pages-with-imagemagick" rel="alternate"/><published>2020-02-16T15:06:00+00:00</published><updated>2020-02-16T15:06:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-02-16:/posts/2020/make-thumbnails-of-pdf-pages-with-imagemagick</id><summary type="html">&lt;p class="first last"&gt;All my talk slides are PDF - I use &lt;a class="reference external" href="https://rst2pdf.org"&gt;rst2pdf&lt;/a&gt; to transform text-based ReStructuredText content into presentation slides. With all these PDFs hanging around, it can be very handy to have them as thumbnails. I use the images both in the printable speaker notes that I produce (and I should blog that too now I've mentioned it), and to share on twitter - especially the resources slide that everyone photographs! My image file is much more readable than your cameraphone picture in terrible lighting :) So here's my script for thumbnails in case you want to do the same; most presentation tools will export to PDF if you're not already working in that format.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;All my talk slides are PDF - I use &lt;a class="reference external" href="https://rst2pdf.org"&gt;rst2pdf&lt;/a&gt; to transform text-based ReStructuredText content into presentation slides. With all these PDFs hanging around, it can be very handy to have them as thumbnails. I use the images both in the printable speaker notes that I produce (and I should blog that too now I've mentioned it), and to share on twitter - especially the resources slide that everyone photographs! My image file is much more readable than your cameraphone picture in terrible lighting :) So here's my script for thumbnails in case you want to do the same; most presentation tools will export to PDF if you're not already working in that format.&lt;/p&gt;
&lt;div class="section" id="imagemagick"&gt;
&lt;h3&gt;ImageMagick&lt;/h3&gt;
&lt;p&gt;I'm a huge &lt;a class="reference external" href="https://imagemagick.org/index.php"&gt;ImageMagick&lt;/a&gt; fan and it's one of the open source tools that I rely on hugely. There are probably graphical frontends that use it too, but I'm a command-line sort of a girl and I also automatically regenerate the thumbnails when I update my slides.&lt;/p&gt;
&lt;p&gt;The imagemagick command to work with images is &lt;tt class="docutils literal"&gt;convert&lt;/tt&gt; so to get the first slide as an image, I can do something like:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
convert presentation.pdf[0] slide1.png
&lt;/pre&gt;
&lt;p&gt;This outputs an image, which is nice. But I want all the slides as thumbnails so I need to go a step further.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="building-imagemagick-commands-with-python"&gt;
&lt;h3&gt;Building ImageMagick Commands with Python&lt;/h3&gt;
&lt;p&gt;There is probably a neater way to do this (answers in the comments please!) but I already use &lt;a class="reference external" href="https://pypi.org/project/PyPDF2/"&gt;PyPDF2&lt;/a&gt; for a bunch of PDF manipulation such as grabbing out my speaker notes, so I stuck with that approach and simply asked python to generate and then run a &lt;tt class="docutils literal"&gt;convert&lt;/tt&gt; command for each of the slides it finds in my PDF.&lt;/p&gt;
&lt;p&gt;Here's the code; it lives in a file called &lt;tt class="docutils literal"&gt;thumbnails.py&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;PyPDF2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;traceback&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;subprocess&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;'/path/to/my/file.pdf'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;input1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyPDF2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PdfFileReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rb&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;nPages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getNumPages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nPages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# generate the thumbnail&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'convert '&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'['&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'] thumbnails/thumbnail'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'.png'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Run it with &lt;tt class="docutils literal"&gt;python3 thumbnails.py presentation.pdf&lt;/tt&gt; and it will write a series of thumbnails to the &lt;tt class="docutils literal"&gt;thumbnails/&lt;/tt&gt; directory. Which you should create before you run the script, I just didn't mention it until now, sorry.&lt;/p&gt;
&lt;p&gt;Hope it's useful!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="imagemagick"/><category term="pdf"/><category term="python"/></entry><entry><title>Instant Test API Platforms with Prism</title><link href="https://lornajane-net.pages.dev/posts/2020/instant-test-api-platforms-with-prism" rel="alternate"/><published>2020-01-23T11:17:00+00:00</published><updated>2020-01-23T11:17:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-01-23:/posts/2020/instant-test-api-platforms-with-prism</id><summary type="html">&lt;p class="first last"&gt;I've been writing a bit about OpenAPI lately and I realised that I didn't share how I'm creating local, test versions of production APIs to work with while I develop new client code or test SDK features. The recipe is simple: you will need an API spec, the excellent open source tool &lt;a class="reference external" href="https://stoplight.io/open-source/prism"&gt;Prism&lt;/a&gt; and an HTTP client. Mix it up and be ready to change your API development workflows forever.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've been writing a bit about OpenAPI lately and I realised that I didn't share how I'm creating local, test versions of production APIs to work with while I develop new client code or test SDK features. The recipe is simple: you will need an API spec, the excellent open source tool &lt;a class="reference external" href="https://stoplight.io/open-source/prism"&gt;Prism&lt;/a&gt; and an HTTP client. Mix it up and be ready to change your API development workflows forever.&lt;/p&gt;
&lt;div class="section" id="start-with-an-openapi-spec"&gt;
&lt;h3&gt;Start with an OpenAPI Spec&lt;/h3&gt;
&lt;p&gt;If you're already working with OpenAPI, try your own. Otherwise, check if your favourite API provider has one. I'm working at Nexmo and we have &lt;a class="reference external" href="https://developer.nexmo.com/api"&gt;quite a selection&lt;/a&gt; so pick one of these and look for the &amp;quot;Download&amp;quot; button on the API reference.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="use-prism-to-pretend-to-be-your-api"&gt;
&lt;h3&gt;Use Prism to Pretend to be Your API&lt;/h3&gt;
&lt;p&gt;Prism runs as a local server, and it &amp;quot;pretends&amp;quot; to be your API. When it starts up, it announces which URLs it knows about, and when you make requests to those URLs, it will attempt to respond appropriately, using the default or example values from the OpenAPI spec. If you provided complete responses, it will use those.&lt;/p&gt;
&lt;p&gt;Prism is a NodeJS tool, so install it like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
npm install -g &amp;#64;stoplight/prism-cli
&lt;/pre&gt;
&lt;p&gt;Then if your OpenAPI spec is &lt;tt class="docutils literal"&gt;openapi.yml&lt;/tt&gt;, start the server with a command like:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
prism mock openapi.yml
&lt;/pre&gt;
&lt;p&gt;Give it a few seconds and it'll announce itself (usually on &lt;a class="reference external" href="https://localhost:4010"&gt;https://localhost:4010&lt;/a&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="use-the-mock-server"&gt;
&lt;h3&gt;Use the Mock Server&lt;/h3&gt;
&lt;p&gt;Now you can use any HTTP client to make requests to your new API platform and see how it performs! For a quick way to get started, try &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2019/ready-to-wear-api-collections-with-openapi-and-postman"&gt;importing your OpenAPI spec into Postman&lt;/a&gt; to get a collection of requests that are ready to go!&lt;/p&gt;
&lt;p&gt;&lt;img alt="output of prism running, showing the server starting up and some requests coming in" class="alignnone size-large wp-image-4147" src="https://lornajane-net.pages.dev/images/2020/01/Screenshot-from-2020-01-23-02-42-52-1024x158.png" style="width: 584px; height: 90px;" /&gt;&lt;/p&gt;
&lt;p&gt;Remember that Prism only has the information in the OpenAPI spec file, it can't check your real credentials or do any logic on the server side.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="take-prism-to-the-next-level"&gt;
&lt;h3&gt;Take Prism to the Next Level&lt;/h3&gt;
&lt;p&gt;By specifying multiple responses, you can teach Prism all the various responses that might come back from your &amp;quot;real&amp;quot; API. This is great for testing success cases, but it becomes really really powerful when it comes to testing failure cases. I'd really prefer customers use this rather than flatten our servers with traffic trying to generate the rate limit failure message, for example :)&lt;/p&gt;
&lt;p&gt;In OpenAPI, you can specify as many responses as you like, and give them names. Here's a snippet from a Nexmo OpenAPI spec:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
content:
  application/json:
    examples:
      throttled:
        summary: Request limit exceeded
        value:
          status: &amp;quot;1&amp;quot;
          error_text: Throttled

      missing-param:
        summary: Missing a parameter
        value:
          status: &amp;quot;2&amp;quot;
          error_text: Missing api_key
&lt;/pre&gt;
&lt;p&gt;Here you can see a couple of examples of error responses. I've added the examples in full to the spec and named them. Using the names like &lt;tt class="docutils literal"&gt;throttled&lt;/tt&gt;, I can then get Prism to return exactly this response (by default it returns the first applicable one it finds) by using the &lt;tt class="docutils literal"&gt;__example&lt;/tt&gt; query parameter when sending the request.&lt;/p&gt;
&lt;p&gt;So if I usually send a request like &lt;tt class="docutils literal"&gt;GET &lt;span class="pre"&gt;/verify/search?request_id=123456&lt;/span&gt;&lt;/tt&gt; then I can instead send &lt;tt class="docutils literal"&gt;GET &lt;span class="pre"&gt;/verify/search?request_id=123456&amp;amp;__example=throttled&lt;/span&gt;&lt;/tt&gt; to get a specific response. I use this extensively to make sure that the code I write (and sometimes that's library code so it can easily end up in other people's projects!) responds predictably and in a sane manner to all the possible responses that might come back from the API.&lt;/p&gt;
&lt;p&gt;Having tools like this is moving OpenAPI beyond being &amp;quot;just&amp;quot; a documentation tool, and already I can't imagine working with APIs without it.&lt;/p&gt;
&lt;/div&gt;
</content><category term="javascript"/><category term="js"/><category term="openapi"/><category term="prism"/><category term="stoplight"/><category term="tech"/></entry><entry><title>Grab Annotations from a PDF with pypdf2</title><link href="https://lornajane-net.pages.dev/posts/2020/grab-annotations-from-a-pdf-with-pypdf2" rel="alternate"/><published>2020-01-15T20:37:00+00:00</published><updated>2020-01-15T20:37:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2020-01-15:/posts/2020/grab-annotations-from-a-pdf-with-pypdf2</id><summary type="html">&lt;p class="first"&gt;If you've noticed a lot of PDF content around here lately, that's because I work with PDF a lot! Most of all, all my slide decks are in PDF and in the last year or so I've started using speaker notes in my presentations. Yes, this means that if you saw me speak in the &lt;em&gt;first&lt;/em&gt; ten years of my speaking career, that was without speaker notes.&lt;/p&gt;
&lt;p class="last"&gt;There are some situations where I don't have access to my speaker notes. Usually this is a good reason, such as I have mirrored my displays so I can demo or play a video without fiddling with my display settings in the middle of a talk. Sometimes, it's because something bad happened and I'm presenting from someone else's machine or a laptop that's completely off stage and I only have the comfort monitor. For those situations I use a printed set of backup speaker notes so I thought I'd share the script that creates these.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;If you've noticed a lot of PDF content around here lately, that's because I work with PDF a lot! Most of all, all my slide decks are in PDF and in the last year or so I've started using speaker notes in my presentations. Yes, this means that if you saw me speak in the &lt;em&gt;first&lt;/em&gt; ten years of my speaking career, that was without speaker notes.&lt;/p&gt;
&lt;p&gt;There are some situations where I don't have access to my speaker notes. Usually this is a good reason, such as I have mirrored my displays so I can demo or play a video without fiddling with my display settings in the middle of a talk. Sometimes, it's because something bad happened and I'm presenting from someone else's machine or a laptop that's completely off stage and I only have the comfort monitor. For those situations I use a printed set of backup speaker notes so I thought I'd share the script that creates these.&lt;/p&gt;
&lt;div class="section" id="first-a-complete-aside"&gt;
&lt;h3&gt;First, a complete aside&lt;/h3&gt;
&lt;p&gt;If you'd like to be ready to support a conference speaker with tech fail, read the post about &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2018/presenting-from-pdf"&gt;presenting from PDF&lt;/a&gt; and install the tools on your laptop. A bunch of my colleagues past and present have done this and I've hugely appreciated that support! Almost every presentation tool can export to PDF so if the borrowed laptop has presentation tools you at least get the view that has the timer and the next slide.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="script-to-drag-titles-and-annotations-out-of-a-pdf"&gt;
&lt;h3&gt;Script to drag titles and annotations out of a PDF&lt;/h3&gt;
&lt;p&gt;Speaker notes are usually applied to PDF slides as annotations that are not in the visible space (they're like off the top left or something). You can use this approach for other PDF annotations too.&lt;/p&gt;
&lt;p&gt;With the following python code in &lt;tt class="docutils literal"&gt;notes.py&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;PyPDF2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;traceback&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pprint&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;subprocess&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;'/path/to/my/file.pdf'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# put the role into the rst file&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.. role:: slide-title'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;input1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyPDF2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PdfFileReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rb&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;nPages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getNumPages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nPages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# get the data from this PDF page (first line of text, plus annotations)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extractText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;':slide-title:`'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'`'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;annot&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'/Annots'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Other subtypes, such as /Link, cause errors&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;subtype&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;annot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getObject&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s1"&gt;'/Subtype'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;subtype&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/Text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;annot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getObject&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s1"&gt;'/Contents'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# there are no annotations on this page&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;pass&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;To use this, run &lt;tt class="docutils literal"&gt;python notes.py [pdf file name]&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;The script will output &lt;tt class="docutils literal"&gt;rst&lt;/tt&gt; content (that I then use with &lt;a class="reference external" href="https://rst2pdf.org"&gt;rst2pdf&lt;/a&gt;) with the first line/title of each slide and the annotations associated with it. Even if there are no annotations, the title is still added which can be useful for just keeping track of which slides are coming up when you don't have any other support information.&lt;/p&gt;
&lt;p&gt;It's a simple script but I found it handy (and will probably find it here another day and use it for something else, which is exactly the point of a blog). I hope it's useful to you too!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="pdf"/><category term="python"/></entry><entry><title>Raspberry Pi SenseHat Night Clock</title><link href="https://lornajane-net.pages.dev/posts/2019/raspberry-pi-sensehat-night-clock" rel="alternate"/><published>2019-12-28T09:36:00+00:00</published><updated>2019-12-28T09:36:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2019-12-28:/posts/2019/raspberry-pi-sensehat-night-clock</id><summary type="html">&lt;p class="first last"&gt;I recently upgraded my smartwatch and the new model needs charging daily (whether that's really an upgrade is a separate discussion!) so I drop it on the charger by my bed overnight. As a result, I can't tell the time if I wake up in the night which is only annoying because I don't know if it's midnight or if the alarm is going to go off in ten minutes! So I made myself a colourful fuzzy time clock with a raspberry pi and a SenseHat that were lying around in my office.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I recently upgraded my smartwatch and the new model needs charging daily (whether that's really an upgrade is a separate discussion!) so I drop it on the charger by my bed overnight. As a result, I can't tell the time if I wake up in the night which is only annoying because I don't know if it's midnight or if the alarm is going to go off in ten minutes! So I made myself a colourful fuzzy time clock with a raspberry pi and a SenseHat that were lying around in my office.&lt;/p&gt;
&lt;p&gt;(note: it's quite hard to photograph neopixels, I tried)&lt;/p&gt;
&lt;p&gt;&lt;img alt="sensehat showing green in the centre and white lights around the sides" class="alignnone size-medium wp-image-4109" src="https://lornajane-net.pages.dev/images/2019/12/DSC_5638-1-300x300.jpg" style="width: 300px; height: 300px;" /&gt;&lt;img alt="sensehat showing yellow in the centre and white lights around the sides" class="alignnone size-medium wp-image-4110" src="https://lornajane-net.pages.dev/images/2019/12/DSC_5636-small-300x300.jpg" style="width: 300px; height: 300px;" /&gt;&lt;img alt="sensehat showing orange in the centre and white lights around the sides" class="alignnone size-medium wp-image-4108" src="https://lornajane-net.pages.dev/images/2019/12/DSC_5639-1-300x300.jpg" style="width: 300px; height: 300px;" /&gt;&lt;/p&gt;
&lt;p&gt;The basic idea is that the colour in the middle changes with the hour, and the white lights around the outside indicate sort of approximate areas on the clock. This way, I avoid staring at the clock reading &lt;tt class="docutils literal"&gt;03:32&lt;/tt&gt; but can still tell that it is time to go back to sleep!&lt;/p&gt;
&lt;p&gt;The code is python; run the script &lt;tt class="docutils literal"&gt;python3 clock.py&lt;/tt&gt; and the script will check the time on the pi, set the lights and exit. To keep the clock running, try setting up a cron job that does this every minute:&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;* * * * * /usr/bin/python &lt;span class="pre"&gt;~/night-clock/clock.py&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;It's much simpler than trying to keep a long-running process going.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro-tip: Neopixels are really bright&lt;/strong&gt; - much too bright to have anywhere near you in the dark! Look for a variable called &lt;tt class="docutils literal"&gt;on&lt;/tt&gt; that serves as the basic light level (everything else works off that setting). The max is 255 but I'm working off about 80 and even that is plenty bright enough :)&lt;/p&gt;
&lt;p&gt;The code is on GitHub: &lt;a class="reference external" href="https://github.com/lornajane/sensehat-night-clock"&gt;https://github.com/lornajane/sensehat-night-clock&lt;/a&gt; and you're more than welcome to try it, amend it, let me know how you go!&lt;/p&gt;
</content><category term="tech"/></entry><entry><title>Counting Duplicate Commit Messages</title><link href="https://lornajane-net.pages.dev/posts/2019/counting-duplicate-commit-messages" rel="alternate"/><published>2019-12-16T16:24:00+00:00</published><updated>2019-12-16T16:24:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2019-12-16:/posts/2019/counting-duplicate-commit-messages</id><summary type="html">&lt;p class="first last"&gt;When chatting about source control good practice the other day, I got a question about repeated git commit messages. In general, I would always advise that the same commit messages appearing multiple times in a project's history is a definite red flag - and if I'm responsible for that repository I will probably make fun of you for doing it.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;When chatting about source control good practice the other day, I got a question about repeated git commit messages. In general, I would always advise that the same commit messages appearing multiple times in a project's history is a definite red flag - and if I'm responsible for that repository I will probably make fun of you for doing it. Workplace harrassment aside, if you can repeat your commit messages, then they are probably not descriptive enough. There are always projects that break this rule for some reason, but in all the years I've worked in software, I have never worked on a project where I'd make an exception for something like that.&lt;/p&gt;
&lt;p&gt;To support my point, I also checked one of the larger repos at work for duplicate commit messages. It was simple to do but I thought I'd share my script in case anyone else wants to use it on their own repos and offer constructive feedback to their own colleagues!&lt;/p&gt;
&lt;pre class="literal-block"&gt;
git log --oneline | cut -c 10- | sort | uniq -c | sort -n
&lt;/pre&gt;
&lt;p&gt;This shows every commit message in the history of the project, with a count of how many times it appears - and it sorts them by that count (increasing, so that the most repeated messages appear immediately above your cursor when the command completes). Typically I do see quite a few &amp;quot;Merge branch master into ....&amp;quot; type messages and we also have some automation that produces some very similar messages - all that is fair enough. When I find the person who thinks that &amp;quot;Update [filename]&amp;quot; is an acceptable commit message though, I will be taking some time to ~point and laugh~ offer some constructive advice.&lt;/p&gt;
</content><category term="tech"/><category term="git"/><category term="tips"/></entry><entry><title>Ruby for non-Rubyists with Snap</title><link href="https://lornajane-net.pages.dev/posts/2019/ruby-for-non-rubyists-with-snap" rel="alternate"/><published>2019-10-07T09:52:00+01:00</published><updated>2019-10-07T09:52:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2019-10-07:/posts/2019/ruby-for-non-rubyists-with-snap</id><summary type="html">&lt;p&gt;I'm an Ubuntu user and I enjoy most server-side scripting languages but I haven't done much Ruby and I never get the feeling that Ruby is pleased to see me when I do. Something is always out of date or has an incompatible dependency or ... and since I don't use …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm an Ubuntu user and I enjoy most server-side scripting languages but I haven't done much Ruby and I never get the feeling that Ruby is pleased to see me when I do. Something is always out of date or has an incompatible dependency or ... and since I don't use it regularly, I don't have the familiarity that enables me to easily debug these things with other platforms.&lt;/p&gt;
&lt;p&gt;I've been using the &lt;a class="reference external" href="https://www.ruby-lang.org/en/news/2018/11/08/snap/"&gt;Ruby Snap&lt;/a&gt; on Ubuntu for a while now, and I &lt;em&gt;think&lt;/em&gt; it's a bit better? I've referred to my notes every time I've done it though so I thought I had better put them somewhere I'll find them even when I'm using a new notebook :)&lt;/p&gt;
&lt;div class="section" id="what-versions-are-available"&gt;
&lt;h3&gt;What versions are available?&lt;/h3&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;snap info ruby&lt;/tt&gt; will show your choices of versions to install. I usually just need a &lt;tt class="docutils literal"&gt;major.minor&lt;/tt&gt; version combination so I set the version with:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo snap switch ruby --channel=2.5/stable
sudo snap refresh
&lt;/pre&gt;
&lt;p&gt;If you see a lot of &amp;quot;extension not built&amp;quot; anger then try this: &lt;tt class="docutils literal"&gt;ruby.gem pristine &lt;span class="pre"&gt;--extensions&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="using-ruby-and-gem-and-bundle"&gt;
&lt;h3&gt;Using Ruby and Gem and Bundle&lt;/h3&gt;
&lt;p&gt;The snap actually ships with all these commands available as separate commands:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;ruby&lt;/tt&gt; (just check this is the right one! Should be the same as &lt;tt class="docutils literal"&gt;/snap/bin/ruby&lt;/tt&gt; - if it's not check what order things are in your &lt;tt class="docutils literal"&gt;$PATH&lt;/tt&gt;)&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;ruby.gem&lt;/tt&gt; is the gem executable for this snap ruby&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;ruby.bundle&lt;/tt&gt; is the bundler gem for this snap ruby (I use this one the most since my ruby usage is basically &lt;tt class="docutils literal"&gt;bundle exec jekyll serve&lt;/tt&gt; most of the time!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Beware that the 2.5 channel has bundler v2 and the 2.6 channel has v1, I have no idea why but it's tripped me up at least four times now&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="ruby"/><category term="snap"/><category term="ubuntu"/></entry><entry><title>Sniglet is a Font for Avoiding Font Loading Mistakes</title><link href="https://lornajane-net.pages.dev/posts/2019/sniglet-is-a-font-for-avoiding-font-loading-mistakes" rel="alternate"/><published>2019-09-16T20:45:00+01:00</published><updated>2019-09-16T20:45:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2019-09-16:/posts/2019/sniglet-is-a-font-for-avoiding-font-loading-mistakes</id><summary type="html">&lt;p class="first"&gt;I am not a designer, nor will I ever be. Things look perfectly fine to me a very long time before they look OK to anyone else! Along with that, I don't always find it easy to tell fonts apart. I can do serif-ish and sans-ish but if the fixed-width font has serifs on it I sometimes miss that detail if I'm not looking for it - which also means that I have no idea if I've loaded the fonts I was thinking of or not.&lt;/p&gt;
&lt;p class="last"&gt;To get around this, I test everything by switching the font to &lt;a class="reference external" href="https://www.theleagueofmoveabletype.com/sniglet"&gt;Sniglet&lt;/a&gt; from the League of Moveable Type. It's ... umistakable!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I am not a designer, nor will I ever be. Things look perfectly fine to me a very long time before they look OK to anyone else! Along with that, I don't always find it easy to tell fonts apart. I can do serif-ish and sans-ish but if the fixed-width font has serifs on it I sometimes miss that detail if I'm not looking for it - which also means that I have no idea if I've loaded the fonts I was thinking of or not.&lt;/p&gt;
&lt;p&gt;To get around this, I test everything by switching the font to &lt;a class="reference external" href="https://www.theleagueofmoveabletype.com/sniglet"&gt;Sniglet&lt;/a&gt; from the League of Moveable Type. It's ... umistakable!&lt;/p&gt;
&lt;p&gt;Take this simple presentation slide:&lt;/p&gt;
&lt;p&gt;&lt;img alt="image1" class="alignnone size-large wp-image-4096" src="https://lornajane-net.pages.dev/images/2019/09/serif-1024x576.png" style="width: 584px; height: 329px;" /&gt;&lt;/p&gt;
&lt;p&gt;Maybe you can tell that this has correctly loaded my preferred font (Gentium, in case you're interested) rather than fallen back to a Times New Roman or something. I am never really sure about it (or at this point I'm completely convinced that I don't know what I'm doing, either way) so I test by loading a different font by the same mechanism: Sniglet.&lt;/p&gt;
&lt;p&gt;&lt;img alt="image2" class="alignnone size-large wp-image-4095" src="https://lornajane-net.pages.dev/images/2019/09/sniglet-1024x576.png" style="width: 584px; height: 329px;" /&gt;&lt;/p&gt;
&lt;p&gt;So NOW I can tell if the font has loaded because even I can tell Sniglet apart from a default/fallback font!! I also love it, it's cute and fun, and makes me smile :) What top tips do you have for catching errors that you might otherwise overlook? I love little tricks like this so leave me a comment if you have something to share!&lt;/p&gt;
</content><category term="tech"/><category term="fonts"/></entry><entry><title>OpenAPI Description using API key and secret</title><link href="https://lornajane-net.pages.dev/posts/2019/openapi-description-using-api-key-secret" rel="alternate"/><published>2019-08-15T18:10:00+01:00</published><updated>2019-08-15T18:10:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2019-08-15:/posts/2019/openapi-description-using-api-key-secret</id><summary type="html">&lt;p class="first last"&gt;I've been working on a few OpenAPI descriptions of APIs lately, and really enjoying the benefits they bring. In particular the &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2019/ready-to-wear-api-collections-with-openapi-and-postman"&gt;ability to import into Postman as a collection&lt;/a&gt; I think is a &amp;quot;killer feature&amp;quot; for APIs looking to get developers up and running quickly. The catch? I found that an API that needs an API key and secret, or a username and password/token, is supposed to be described with multiple security schemes - but this does not play nicely with tools like Postman.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've been working on a few OpenAPI descriptions of APIs lately, and really enjoying the benefits they bring. In particular the &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2019/ready-to-wear-api-collections-with-openapi-and-postman"&gt;ability to import into Postman as a collection&lt;/a&gt; I think is a &amp;quot;killer feature&amp;quot; for APIs looking to get developers up and running quickly. The catch? I found that an API that needs an API key and secret, or a username and password/token, is supposed to be described with multiple security schemes - but this does not play nicely with tools like Postman.&lt;/p&gt;
&lt;p&gt;To work around this problem, I changed the security schemes to describe a single API key field (it's a query parameter). This is well understood by all tools, and also most of them will just pick the first one when multiple are specified. Then I added the API secret field as a required parameter to all the endpoints.&lt;/p&gt;
&lt;p&gt;This isn't an ideal situation, because the secret really is part of the auth, not a parameter. However I did find that it helped not only my Postman import, but also use of &lt;a class="reference external" href="https://github.com/stoplightio/prism"&gt;Prism&lt;/a&gt; as a mock server, and that some documentation tools rendered the required fields for each endpoint more clearly.&lt;/p&gt;
&lt;p&gt;As things stand, I have a single security scheme listed in the &lt;tt class="docutils literal"&gt;components:&lt;/tt&gt; section:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
securitySchemes:
  apiKey:
    type: apiKey
    name: api_key
    in: query
    description: &amp;gt;-
      You can find your API key in your Nexmo account [developer
      dashboard](https://dashboard.nexmo.com/)
&lt;/pre&gt;
&lt;p&gt;Then for each endpoint, I added the additional parameter (described in the &lt;tt class="docutils literal"&gt;parameters:&lt;/tt&gt; section of &lt;tt class="docutils literal"&gt;components:&lt;/tt&gt; and using a &lt;tt class="docutils literal"&gt;$ref&lt;/tt&gt;). This is perhaps less formally correct, but more working, than what I had before - so I'm living with it for now.&lt;/p&gt;
&lt;p&gt;If you want to see the spec in action, it's here: &lt;a class="reference external" href="https://github.com/Nexmo/api-specification/blob/master/definitions/verify.yml"&gt;https://github.com/Nexmo/api-specification/blob/master/definitions/verify.yml&lt;/a&gt; and if you have any better ideas, I'd love to hear them!&lt;/p&gt;
</content><category term="tech"/><category term="nexmo"/><category term="openapi"/></entry><entry><title>The Laravel Synchronous Queue</title><link href="https://lornajane-net.pages.dev/posts/2019/the-laravel-synchronous-queue" rel="alternate"/><published>2019-05-20T15:43:00+01:00</published><updated>2019-05-20T15:43:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2019-05-20:/posts/2019/the-laravel-synchronous-queue</id><summary type="html">&lt;p class="first last"&gt;Using queues for asynchronous processing is one of my favourite tricks for offloading hard work from web servers. When working with &lt;a class="reference external" href="https://laravel.com/"&gt;Laravel&lt;/a&gt; recently I was pleased to find that it supports &lt;a class="reference external" href="https://beanstalkd.github.io/"&gt;beanstalkd&lt;/a&gt; out of the box. I've got opinions about frameworks with Opinions but I did find one thing I really liked in the way Laravel uses queues: the &lt;strong&gt;sync&lt;/strong&gt; queue option that runs your queue synchronously on your development platform so you can develop and debug your work, then switch the queue platform you use later.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Using queues for asynchronous processing is one of my favourite tricks for offloading hard work from web servers. When working with &lt;a class="reference external" href="https://laravel.com/"&gt;Laravel&lt;/a&gt; recently I was pleased to find that it supports &lt;a class="reference external" href="https://beanstalkd.github.io/"&gt;beanstalkd&lt;/a&gt; out of the box. I've got opinions about frameworks with Opinions but I did find one thing I really liked in the way Laravel uses queues: the &lt;strong&gt;sync&lt;/strong&gt; queue option that runs your queue synchronously on your development platform so you can develop and debug your work, then switch the queue platform you use later.&lt;/p&gt;
&lt;p&gt;It's quite tricky to debug Laravel &amp;quot;Jobs&amp;quot; as they call the worker code - if errors occur, they don't show up in the general output. You can use &lt;tt class="docutils literal"&gt;error_log()&lt;/tt&gt; and see that output, but if there's a PHP error, you won't see that error. However you can configure the &lt;tt class="docutils literal"&gt;QUEUE_CONNECTION&lt;/tt&gt; in &lt;tt class="docutils literal"&gt;.env&lt;/tt&gt; to be &lt;tt class="docutils literal"&gt;&amp;quot;sync&amp;quot;&lt;/tt&gt;. The &lt;tt class="docutils literal"&gt;sync&lt;/tt&gt; queue type is not asynchronous at all - instead of putting the job onto your chosen queue backend, it goes right ahead and processes the job in the current thread of execution. This completely defeats the object of using a queue but is a brilliant approach for debugging how the data passes from the dispatching context (where the data is put into the job and onto a queue) to the worker context (where the job comes back off the queue and actually gets processed) and how the job is processed.&lt;/p&gt;
&lt;p&gt;For development use, it's a nice feature, but do take care to enable your &amp;quot;real&amp;quot; queue configuration when deploying to a production environment so that you get the advantage of the queues. There are some good queueing services available for your cloud applications - specifically for Laravel, look at &lt;a class="reference external" href="https://horizon.laravel.com/"&gt;Laravel Horizon&lt;/a&gt; which is Redis with nicer dashboards attached :)&lt;/p&gt;
</content><category term="php"/><category term="beanstalkd"/><category term="laravel"/><category term="tech"/></entry><entry><title>The First Thousand Blog Posts</title><link href="https://lornajane-net.pages.dev/posts/2019/the-first-thousand-blog-posts" rel="alternate"/><published>2019-04-30T09:51:00+01:00</published><updated>2019-04-30T09:51:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2019-04-30:/posts/2019/the-first-thousand-blog-posts</id><summary type="html">&lt;p class="first last"&gt;This is the 1000th post on &lt;a class="reference external" href="https://beta.lornajane.net"&gt;https://beta.lornajane.net&lt;/a&gt;. People ask &amp;quot;how do you even do that?&amp;quot; and the short answer is &amp;quot;start in 2006&amp;quot;, but the long answer isn't all that long.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is the 1000th post on &lt;a class="reference external" href="https://beta.lornajane.net"&gt;https://beta.lornajane.net&lt;/a&gt;. People ask &amp;quot;how do you even do that?&amp;quot; and the short answer is &amp;quot;start in 2006&amp;quot;, but the long answer isn't all that long.&lt;/p&gt;
&lt;p&gt;It all began because I couldn't hold down a job. After graduating, I had five jobs in five years, which was fabulous because I learned a lot. What was not great was losing all my useful scripts every time because I didn't know what I might need again in the future and BYOD hadn't been invented yet. So, I began to write things down in an internet-enabled location.&lt;/p&gt;
&lt;p&gt;Storing my notes publicly is incredibly useful. I can send links to other people who ask me questions I once knew the answer to (people who know me well ask the blog for help before they ask me, it's a more reliable witness). I also discovered after a few years that my favourite search engine has a much better idea what I've already written than I have - I regularly find results on &lt;a class="reference external" href="https://beta.lornajane.net"&gt;https://beta.lornajane.net&lt;/a&gt; appearing in my search results and quite often it's exactly what I needed!&lt;/p&gt;
&lt;p&gt;At first, it didn't matter what I wrote, nobody read it and I was just throwing things out for future reference. I'd like to write now about how much my approach has improved but I would be lying!! The truth is that I'm still just throwing things out for my future reference. All the bystanders of the internet are very welcome to read it, but almost all of it is written for me in case I need to come back to it. Many bloggers or aspiring bloggers are stuck on not being good enough, not having enough ideas, not having finished building their blog. Perhaps I should have worried about some of those things but it seems a bit late now!&lt;/p&gt;
&lt;div class="section" id="unintended-consequences"&gt;
&lt;h3&gt;Unintended Consequences&lt;/h3&gt;
&lt;p&gt;Keeping a blog made me a better writer, mostly because it made me actually write from time to time. Also there was a time where people read blogs and wrote comments and I got some excellent feedback through that (there is some irony that this is the first blog post I've cross-posted to Medium, times do change!). The other thing about blogging is that you write how to do something and then four people chime in and show you at least three better ways to do it!! This has been the best thing ever, I have learned so much from those commenters who took the time to explain very politely that I was giving terrible advice. The moral of &lt;em&gt;that&lt;/em&gt; story is that you should always read the comments on blog posts :)&lt;/p&gt;
&lt;p&gt;Another side effect has been that my career has included some writing in a professional capacity. From writing from my own blog, to work blogs, to industry publications, to eventually writing a couple of books ... now I'm doing a job that includes writing documentation and tutorials. Combining the written word with solid software engineering skills is pretty rare, and without the blog I don't know if I'd have realised I could write.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="blog-as-a-brain"&gt;
&lt;h3&gt;Blog As A Brain&lt;/h3&gt;
&lt;p&gt;My blog is basically an outboard brain, it's full of things I once knew but have completely forgotten. If there's a technology I've worked on, there's probably a post about it on my blog. It's a rare week where I don't come here looking for something or other, whether it's how to set up my IRC client, or the syntax for a particular coding library. Some years I've blogged more than others, and I'm still terrible at metrics, marketing, engagement, and all those other things - it's a wordpress blog with a CDN in front, I just type my thoughts in markdown into the textarea while I'm drinking my coffee in the morning.&lt;/p&gt;
&lt;p&gt;As the saying goes &amp;quot;content is king&amp;quot; and I do really believe that. By taking the time to write up my experience into blog posts, I've helped myself to understand countless technical topics. I've given myself an incredible reference library that I can use whenever I need it. And if anyone else finds it useful too, then that just makes a good thing better.&lt;/p&gt;
&lt;p&gt;Here's to the next thousand posts!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="writing"/></entry><entry><title>Handy Beanstalkd Admin Console</title><link href="https://lornajane-net.pages.dev/posts/2019/handy-beanstalkd-admin-console" rel="alternate"/><published>2019-04-08T09:13:00+01:00</published><updated>2019-04-08T09:13:00+01:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2019-04-08:/posts/2019/handy-beanstalkd-admin-console</id><summary type="html">&lt;p class="first last"&gt;I've been building apps with queues lately and mostly using &lt;a class="reference external" href="https://beanstalkd.github.io/"&gt;beanstalkd&lt;/a&gt; as my queue because it is very simple, very fast and on my platform it is &lt;tt class="docutils literal"&gt;[apt install beanstalkd]&lt;/tt&gt; -easy to install. I have also been using a handy web interface for beanstalkd which I like so much that I felt I ought to share! It's &lt;a class="reference external" href="https://github.com/ptrofimov/beanstalk_console"&gt;beanstalk-console&lt;/a&gt;, which is a PHP-based web interface to one (or many) beanstalkd servers.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've been building apps with queues lately and mostly using &lt;a class="reference external" href="https://beanstalkd.github.io/"&gt;beanstalkd&lt;/a&gt; as my queue because it is very simple, very fast and on my platform it is &lt;tt class="docutils literal"&gt;[apt install beanstalkd]&lt;/tt&gt; -easy to install. I have also been using a handy web interface for beanstalkd which I like so much that I felt I ought to share! It's &lt;a class="reference external" href="https://github.com/ptrofimov/beanstalk_console"&gt;beanstalk-console&lt;/a&gt;, which is a PHP-based web interface to one (or many) beanstalkd servers.&lt;/p&gt;
&lt;p&gt;Beanstalkd is a very simple queueing system and I've enjoyed using it from a few different tech stacks. It uses &amp;quot;tubes&amp;quot; rather than channels, supports priorities, and uses a simple string for the body of the &amp;quot;job&amp;quot; on the queue.&lt;/p&gt;
&lt;p&gt;The console has a very simple Composer-based setup and is also ready-to-run in a docker container so should be accessible to most users, with or without PHP skills/installation handy. It allows you to configure servers and then the web interface shows these servers. You get a list of tubes with some information about each one:&lt;/p&gt;
&lt;p&gt;&lt;img alt="image1" class="alignnone size-large wp-image-4064" src="https://lornajane-net.pages.dev/images/2019/04/beanstalkd-queues-list-1024x561.png" style="width: 584px; height: 320px;" /&gt;&lt;/p&gt;
&lt;p&gt;That's pretty cool and I use it both when I'm developing applications and when demo-ing code in presentations. The real magic happens when you drill further in though; get down to the individual tube level and you can inspect, create and &amp;quot;kick&amp;quot; (delete) jobs from the queues from the web interface.&lt;/p&gt;
&lt;p&gt;&lt;img alt="image2" class="alignnone size-large wp-image-4065" src="https://lornajane-net.pages.dev/images/2019/04/beanstalkd-jobs-list-1024x561.png" style="width: 584px; height: 320px;" /&gt;&lt;/p&gt;
&lt;p&gt;Having a good admin tool can give insight into the contents of a queue which would be bewildering for a production application under any sort of load but which is ideal for tracing problems or showing off smaller pieces of a larger application during demo or development phases. I've really enjoyed this particular tool so I wanted to share in case it's useful for anyone else too!&lt;/p&gt;
</content><category term="tech"/><category term="beanstalkd"/><category term="devtools"/><category term="php"/><category term="tools"/></entry><entry><title>HTTP Toolbox</title><link href="https://lornajane-net.pages.dev/posts/2019/http-toolbox" rel="alternate"/><published>2019-02-27T09:53:00+00:00</published><updated>2019-02-27T09:53:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2019-02-27:/posts/2019/http-toolbox</id><summary type="html">&lt;p class="first"&gt;As Web Developers, we need to know how to work with HTTP from every angle. I gave a 2-hour tutorial at PHP UK that included some of my most trusted tools - but it was sold out and a bunch of people asked me if there was video (there wasn't, tutorials make little sense when videoed). Instead, I thought I'd try to set out a self-study version of the workshop (I rarely teach these days so I'm unlikely to deliver it anywhere else).&lt;/p&gt;
&lt;p class="last"&gt;There's a slide deck, some exercises and a sample repo on GitHub ... let's dive in!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;As Web Developers, we need to know how to work with HTTP from every angle. I gave a 2-hour tutorial at PHP UK that included some of my most trusted tools - but it was sold out and a bunch of people asked me if there was video (there wasn't, tutorials make little sense when videoed). Instead, I thought I'd try to set out a self-study version of the workshop (I rarely teach these days so I'm unlikely to deliver it anywhere else).&lt;/p&gt;
&lt;p&gt;There's a slide deck, some exercises and a sample repo on GitHub ... let's dive in!&lt;/p&gt;
&lt;div class="section" id="step-0-get-the-materials"&gt;
&lt;h3&gt;Step 0: Get the Materials&lt;/h3&gt;
&lt;p&gt;You're more than welcome to grab the materials I used for the workshop, they are all in a GitHub repo here: &lt;a class="reference external" href="https://github.com/lornajane/fun-with-http"&gt;https://github.com/lornajane/fun-with-http&lt;/a&gt; - look in the &lt;tt class="docutils literal"&gt;workshop/&lt;/tt&gt; folder for the slide deck and all the exercises. The slides are probably hard to follow by themselves so I'll try to put some basic outline and links into this post.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="step-1-understand-http"&gt;
&lt;h3&gt;Step 1: Understand HTTP&lt;/h3&gt;
&lt;p&gt;You think you understand HTTP already, I know :) And I'm not going to write an essay here - I already &lt;a class="reference external" href="http://www.tkqlhce.com/click-7049572-11260198-1448292209000?url=http%3A%2F%2Fshop.oreilly.com%2Fproduct%2F0636920042860.do%3Fcmp%3Daf-webplatform-books-videos-product_cj_9781491933091_%2525zp&amp;amp;cjsku=0636920042860"&gt;wrote the book&lt;/a&gt; so if you need words, they're available.&lt;/p&gt;
&lt;p&gt;You should set up the sample app (check the &lt;tt class="docutils literal"&gt;README&lt;/tt&gt; in the project repo) at this point.&lt;/p&gt;
&lt;p&gt;Then, try an HTTP client of your choice (maybe try something new?) and make simple requests to the app. These are the suggestions I offered in the workshop:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;curl &lt;a class="reference external" href="https://curl.haxx.se/"&gt;https://curl.haxx.se/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Postman &lt;a class="reference external" href="https://www.getpostman.com/"&gt;https://www.getpostman.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;HTTPie &lt;a class="reference external" href="https://httpie.org/"&gt;https://httpie.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;http-console &lt;a class="reference external" href="https://github.com/cloudhead/http-console"&gt;https://github.com/cloudhead/http-console&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Paw (Mac only) &lt;a class="reference external" href="https://paw.cloud/client"&gt;https://paw.cloud/client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Insomnia &lt;a class="reference external" href="https://insomnia.rest/"&gt;https://insomnia.rest/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;probably your IDE?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I've also &lt;a class="reference external" href="https://lornajane-net.pages.dev/posts/2017/http-tools-roundup"&gt;blogged about this before&lt;/a&gt; and there are some excellent recommendations on that post so check that out also.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="step-2-get-comfortable-with-data-formats"&gt;
&lt;h3&gt;Step 2: Get comfortable with data formats&lt;/h3&gt;
&lt;p&gt;I kept it pretty simple and focussed on form-encoded and JSON formats in the workshop but you may also come across XML and there's a trend for binary formats too so you may need more tools than I'm covering here, depending what you are doing.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Recommended tools for JSON:&lt;/div&gt;
&lt;div class="line"&gt;- &lt;tt class="docutils literal"&gt;jq&lt;/tt&gt; &lt;a class="reference external" href="https://stedolan.github.io/jq/"&gt;https://stedolan.github.io/jq/&lt;/a&gt;&lt;/div&gt;
&lt;div class="line"&gt;- &lt;tt class="docutils literal"&gt;fx&lt;/tt&gt; &lt;a class="reference external" href="https://github.com/antonmedv/fx"&gt;https://github.com/antonmedv/fx&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="step-3-making-simple-api-calls-with-php-and-debugging-them"&gt;
&lt;h3&gt;Step 3: Making simple API calls with PHP - and debugging them&lt;/h3&gt;
&lt;p&gt;For this section I showed two options for making simple POST requests from PHP: firstly using streams, which is very powerful and does not need dependencies - and secondly using &lt;a class="reference external" href="guzzlephp.org"&gt;Guzzle&lt;/a&gt; which is fabulous, also very powerful, and a bit more user-friendly. Here's the two sets of example code so you can compare and contrast:&lt;/p&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="x"&gt;$url = &amp;quot;https://httpbin.org/post&amp;quot;;
$data = [&amp;quot;name&amp;quot; =&amp;gt; &amp;quot;lornajane&amp;quot;, &amp;quot;message&amp;quot; =&amp;gt; &amp;quot;Hi there&amp;quot;];

$context = stream_context_create(
    [&amp;quot;http&amp;quot; =&amp;gt; [
        &amp;quot;method&amp;quot; =&amp;gt; &amp;quot;POST&amp;quot;,
        &amp;quot;content&amp;quot; =&amp;gt; http_build_query($data)
    ]
]);

$response = file_get_contents($url, false, $context);&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And with Guzzle:&lt;/p&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="x"&gt;require &amp;quot;vendor/autoload.php&amp;quot;;

$url = &amp;quot;https://httpbin.org/post&amp;quot;;
$data = [&amp;quot;name&amp;quot; =&amp;gt; &amp;quot;lornajane&amp;quot;, &amp;quot;message&amp;quot; =&amp;gt; &amp;quot;Hi there&amp;quot;];

$client = new GuzzleHttpClient();
$response = $client-&amp;gt;request(&amp;quot;POST&amp;quot;, $url, [
    &amp;quot;form_params&amp;quot; =&amp;gt; $data
]);&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;At this point it would be good to mention a couple of tools that I use when I need to diagnose if my PHP application is making the HTTP request I hope it is! Sometimes when integrating multiple applications it isn't clear at which end the problem is, and these tools can help.&lt;/p&gt;
&lt;p&gt;First up: &lt;a class="reference external" href="http://httpbin.org"&gt;HTTPBin&lt;/a&gt; is a hosted service which offers a series of endpoints that return known data, headers, status codes, whatever you need for testing. This is very useful for testing error cases that are hard to replicate on the real API you are using.&lt;/p&gt;
&lt;p&gt;My preferred tool in this space is &lt;a class="reference external" href="https://github.com/lornajane/requestbin"&gt;RequestBin&lt;/a&gt; - please note that this link points to my own GitHub fork since the project itself seems to have been abandoned and there were a couple of patches in the pull request queue that are needed to run this on Heroku. It's a brilliant tool for testing your outgoing web requests and can run in docker on your laptop or on the cloud very easily.&lt;/p&gt;
&lt;p&gt;I challenged the workshop attendees to complete a half-made route in the sample application for this part of the workshop. We also stopped for coffee, so you might like to do that too :)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="step-4-ngrok-for-development-platform-access"&gt;
&lt;h3&gt;Step 4: Ngrok for development platform access&lt;/h3&gt;
&lt;p&gt;If you're not already familiar with &lt;a class="reference external" href="https://ngrok.com"&gt;ngrok&lt;/a&gt;, you might like to be! It is an application that runs on your development platform, and makes your local webserver accessible from the open internet via a secure tunnel. I use it a LOT, mostly for testing incoming webhooks but also sometimes to see how something will work on mobile, or to let someone in another location take a look at something I'm working on.&lt;/p&gt;
&lt;p&gt;&lt;img alt="image1" class="alignnone size-full wp-image-4054" src="https://lornajane-net.pages.dev/images/2019/02/Ngrok.png" style="width: 920px; height: 215px;" /&gt;&lt;/p&gt;
&lt;p&gt;Ngrok also allows you to inspect the full request and response of the traffic going over the tunnel, which is fantastic especially at the development stage. And that isn't even the coolest part! The best bit is the &amp;quot;Replay&amp;quot; button available alongside the details of each incoming request: if your code doesn't quite do the right thing, you can edit it and then run the request again, see what happens the next time! This is such a gift especially when the event that triggers an incoming requests is expensive or annoying to reproduce - again, I use this mostly for webhooks and you can probably see why!&lt;/p&gt;
&lt;p&gt;Take the time to try the feature you made in the previous step via Ngrok.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="step-5-proxies-for-debugging"&gt;
&lt;h3&gt;Step 5: Proxies for Debugging&lt;/h3&gt;
&lt;p&gt;OK so one of these tools is not a proxy (Wireshark actually copies your traffic rather than it passing through the appplication) but I use them all for the same sorts of problems! Knowing how to use proxy tools gives you a very easy way to inspect all the requests and responses and this can be really valuable for allowing detailed debugging of your application without having to make a lot of diagnostic additions to your actual project.&lt;/p&gt;
&lt;p&gt;My recommended tools in this category:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Charles Proxy (paid product, free trial available) &lt;a class="reference external" href="https://www.charlesproxy.com/"&gt;https://www.charlesproxy.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mitmproxy (open source) &lt;a class="reference external" href="https://mitmproxy.org/"&gt;https://mitmproxy.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Wireshark (slightly lower level tool but brilliant for buggy HTTP) &lt;a class="reference external" href="https://www.wireshark.org/"&gt;https://www.wireshark.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have used all three of these and especially Charles Proxy has been a huge boost. It enables you to play tricks such as rewriting URLs (because the images are hardcoded to the wrong path and don't work on test, perhaps) and simulating slower network speeds so you get a sense of what the experience will be for a real user. Another big feature with Charles is that you can replay requests, and also store and export them - very handy for attaching to an open issue in a bug tracker that shows exactly what happens, and will allow someone else to replay that request again later!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="you-had-to-be-there"&gt;
&lt;h3&gt;You had to be there&lt;/h3&gt;
&lt;p&gt;Hopefully some of the links and suggestions here are useful, the content wasn't designed for a blog post but if you're interested and motivated to work through the exercises for each step as a &amp;quot;self study&amp;quot; exercise then you're really welcome to do so. I'm also happy to take questions if you have them - just leave me a comment!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="charles"/><category term="curl"/><category term="http"/><category term="jq"/><category term="ngrok"/><category term="php"/><category term="phpuk"/><category term="training"/><category term="wireshark"/></entry><entry><title>Using Tags in your OpenAPI Spec</title><link href="https://lornajane-net.pages.dev/posts/2019/using-tags-in-your-openapi-spec" rel="alternate"/><published>2019-02-26T10:15:00+00:00</published><updated>2019-02-26T10:15:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2019-02-26:/posts/2019/using-tags-in-your-openapi-spec</id><summary type="html">&lt;p class="first last"&gt;Working with &lt;a class="reference external" href="https://www.openapis.org/"&gt;OpenAPI&lt;/a&gt; is bringing so many possibilities to the way that developers work with APIs, it's exciting! The spec is very comprehensive though and I've found myself answering questions on individual aspects of it recently, so I thought I'd capture one or two of those things here. Today: tags.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Working with &lt;a class="reference external" href="https://www.openapis.org/"&gt;OpenAPI&lt;/a&gt; is bringing so many possibilities to the way that developers work with APIs, it's exciting! The spec is very comprehensive though and I've found myself answering questions on individual aspects of it recently, so I thought I'd capture one or two of those things here. Today: tags.&lt;/p&gt;
&lt;p&gt;Tags in OpenAPI are optional but very useful! If you use &lt;a class="reference external" href="http://speccy.io/"&gt;Speccy&lt;/a&gt; then it has Opinions(TM) and will suggest that you need tags on every endpoint. The formal definition doesn't require it however and I tend to turn off these rules for small APIs.&lt;/p&gt;
&lt;p&gt;When tags really come into their own is on larger APIs, perhaps an application only has one API and everything goes there - or there is one &amp;quot;utility&amp;quot; API that's a catchall for anything not important or big enough to need its own API. Using tags on these larger APIs can really help users to find their way around.&lt;/p&gt;
&lt;div class="section" id="adding-tags-to-an-openapi-spec"&gt;
&lt;h3&gt;Adding Tags to an OpenAPI Spec&lt;/h3&gt;
&lt;p&gt;To add a tag to your OpenAPI spec, you need to do two things:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Define the tags in a top-level &lt;tt class="docutils literal"&gt;tags&lt;/tt&gt; element&lt;/li&gt;
&lt;li&gt;Add a tag to each operation in the spec&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;tags&lt;/tt&gt; element looks something like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
tags:
  - name: Account
    description: Relating to an account (the user must have access to the account)
  - name: User
    description: Manage a user account (either your own, or one you have permissions to access)
  - name: Admin
    description: Only available to superusers
&lt;/pre&gt;
&lt;p&gt;I'd recommend adding descriptions here, it helps to make it clearer both to developers and to future maintainers of the API and its description what the tag was intended for.&lt;/p&gt;
&lt;p&gt;Then to add the tag to a path, the &lt;tt class="docutils literal"&gt;tags&lt;/tt&gt; array goes at the same level as &lt;tt class="docutils literal"&gt;operationID&lt;/tt&gt;, like this (remember you can add as many tags as make sense):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
/accounts/{api_key}/secrets:
  get:
    summary: Retrieve API Secrets
    operationId: retrieveAPISecrets
    tags:
      - Account
&lt;/pre&gt;
&lt;p&gt;Quite a few of the documentation tools (including &lt;a class="reference external" href="https://github.com/Rebilly/ReDoc"&gt;ReDoc&lt;/a&gt; which is my personal favourite) will render the endpoints grouped by tag which is very valuable. Speccy recommends having the tags alphabetically but I tend to turn this rule off where alphabetical doesn't make sense (for example above where I have a few endpoints tagged &amp;quot;Admin&amp;quot; but most users can't access those so I have it last in the list).&lt;/p&gt;
&lt;p&gt;Using tags can be a valuable addition to your OpenAPI spec so I hope that sharing this example is helpful!&lt;/p&gt;
&lt;/div&gt;
</content><category term="tech"/><category term="openapi"/></entry><entry><title>Windows removed my grub menu</title><link href="https://lornajane-net.pages.dev/posts/2019/windows-removed-my-grub-menu" rel="alternate"/><published>2019-01-28T10:45:00+00:00</published><updated>2019-01-28T10:45:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2019-01-28:/posts/2019/windows-removed-my-grub-menu</id><summary type="html">&lt;p&gt;I know this will happen to me again at a bad moment so I'm putting it here on my blog and hoping I can find it in my future hour of need ...&lt;/p&gt;
&lt;p&gt;My work machine is a Windows PC, with dual boot to the Ubuntu partition that I actually use …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I know this will happen to me again at a bad moment so I'm putting it here on my blog and hoping I can find it in my future hour of need ...&lt;/p&gt;
&lt;p&gt;My work machine is a Windows PC, with dual boot to the Ubuntu partition that I actually use. Sometimes, when booting Windows, it &amp;quot;repairs&amp;quot; its disks and removes my grub menu, booting straight into Windows without showing me the grub menu.&lt;/p&gt;
&lt;p&gt;To fix this: interrupt the startup, choose the boot device and pick the Ubuntu partition from the list.&lt;/p&gt;
&lt;p&gt;Once booted, you can &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;boot-repair&lt;/span&gt;&lt;/tt&gt; to sort out grub - I also travel with a boot disk, just in case!&lt;/p&gt;
</content><category term="tech"/><category term="ubuntu"/></entry><entry><title>Ready-to-wear API Collections with OpenAPI and Postman</title><link href="https://lornajane-net.pages.dev/posts/2019/ready-to-wear-api-collections-with-openapi-and-postman" rel="alternate"/><published>2019-01-08T12:50:00+00:00</published><updated>2019-01-08T12:50:00+00:00</updated><author><name>lornajane</name></author><id>tag:lornajane-net.pages.dev,2019-01-08:/posts/2019/ready-to-wear-api-collections-with-openapi-and-postman</id><summary type="html">&lt;p class="first last"&gt;Have you noticed that API providers are starting to offer &amp;quot;descriptions&amp;quot; of their APIs? These are usually &lt;a class="reference external" href="https://www.openapis.org/"&gt;OpenAPI&lt;/a&gt; specifications, computer-readable documents that describe every aspect of an API's endpoints. They can be used to generate documentation, SDKs, but now they can also be used for my favourite API-related activity: poking around! &lt;a class="reference external" href="https://www.getpostman.com/"&gt;Postman&lt;/a&gt; just added a feature to allow importing OpenAPIs and I love it.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Have you noticed that API providers are starting to offer &amp;quot;descriptions&amp;quot; of their APIs? These are usually &lt;a class="reference external" href="https://www.openapis.org/"&gt;OpenAPI&lt;/a&gt; specifications, computer-readable documents that describe every aspect of an API's endpoints. They can be used to generate documentation, SDKs, but now they can also be used for my favourite API-related activity: poking around! &lt;a class="reference external" href="https://www.getpostman.com/"&gt;Postman&lt;/a&gt; just added a feature to allow importing OpenAPIs and I love it.&lt;/p&gt;
&lt;p&gt;The premise is simple: choose Import, and supply an OpenAPI specification. At Nexmo, we hold ours on GitHub so I just click the &amp;quot;Raw&amp;quot; button and paste the URL as a link to import, e.g. &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;https://raw.githubusercontent.com/Nexmo/api-specification/master/definitions/number-insight.yml&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;You get a collection with all the described endpoints and when you select one, the request is already crafted such that all you need to do is fill in the values you want to send (Pro tip for Nexmo APIs: you'll usually need to add the &lt;tt class="docutils literal"&gt;api_secret&lt;/tt&gt; field yourself).&lt;/p&gt;
&lt;p&gt;&lt;img alt="showing the collection and a precanned request" class="alignnone size-large wp-image-4038" src="https://lornajane-net.pages.dev/images/2019/01/Screenshot-from-2019-01-08-12-38-10-1024x576.png" style="width: 584px; height: 329px;" /&gt;&lt;/p&gt;
&lt;p&gt;What I love about this is how quickly I can start to poke around in unfamiliar APIs, with very easy access to request formats, descriptions of what should be in each field, and so on. The world of OpenAPI is wide and growing all the time, and as more and more API providers offer API descriptions, more and more developers will have the ability to play in the sand using familiar tools and a new API.&lt;/p&gt;
</content><category term="tech"/><category term="openapi"/><category term="postman"/></entry></feed>