<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="/feed_style.xsl" type="text/xsl"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="https://www.rssboard.org/media-rss"><channel><title>Badger Badger Badger Badger</title><link>https://badgerbadgerbadgerbadger.dev/</link><description>Recent content on Badger Badger Badger Badger</description><generator>Hugo -- gohugo.io</generator><language>en-gb</language><copyright>Shaun Das - [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/).</copyright><lastBuildDate>Mon, 01 Sep 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://badgerbadgerbadgerbadger.dev/index.xml" rel="self" type="application/rss+xml"/><icon>https://badgerbadgerbadgerbadger.dev/logo.png</icon><item><title>Gophercon EU 2025 // Berlin</title><link>https://badgerbadgerbadgerbadger.dev/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/</link><pubDate>Mon, 01 Sep 2025 00:00:00 +0000</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/</guid><description><![CDATA[<style>
    .wrapper-0 {
      text-align: center;
    }
    .grid-0 {
      display: flex;
      flex-direction: column;
      gap: 1rem;
      flex-wrap: wrap;
    }
     
    .grid-0 figure {
      margin: 0;
      max-width: 100%;
    }
    .grid-0 img {
      width: 100%;
      height: auto;
      object-fit: contain;
    }
    .wrapper-0 .global-caption {
      text-align: center;
      font-size: 0.9em;
      color: var(--dim-fg);
      margin-top: 0.5rem;
    }
    @media (min-width: 768px) {
      .grid-0 {
        flex-direction: row;
        gap: 2rem;
      }
      .grid-0 figure {
        flex: 1 1 calc(50% - 1rem);
      }
    }
  </style>

  <div class="wrapper-0">
    <div class="grid-0"><figure>
            <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-welcome-back_hu20c1a9f7016d13706126984f6ac3ed9c_1843895_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-welcome-back_hu20c1a9f7016d13706126984f6ac3ed9c_1843895_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-welcome-back_hu20c1a9f7016d13706126984f6ac3ed9c_1843895_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-welcome-back_hu20c1a9f7016d13706126984f6ac3ed9c_1843895_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 350px, (min-width: 768px) 50vw, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="">
          </figure><figure>
            <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-welcome-back-2_hu5ab85a45be8954cdec30d81ea35d23b1_2146568_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-welcome-back-2_hu5ab85a45be8954cdec30d81ea35d23b1_2146568_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-welcome-back-2_hu5ab85a45be8954cdec30d81ea35d23b1_2146568_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-welcome-back-2_hu5ab85a45be8954cdec30d81ea35d23b1_2146568_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 350px, (min-width: 768px) 50vw, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="">
          </figure></div>
  </div>
<p>The Gophercon EU Berlin 2025 talks are now available on youtube, which means it is time for me to rewatch them and ponder again about the things I learned, the things that made me laugh, and the things that reignited my love for Go.</p>
<p>This was my second in-person Gophercon, the last one being Gophercon EU 2022, also in Berlin, and I think I enjoyed this one more. I get the feeling that the organizers get better at this every year, with small touches that increase inclusivity and make a more diverse group of people feel welcome. Not to mention some cute ways to spread the word of the Gopher.</p>
<p>At this one, there was a little booth where you could assemble a little gopher out of stickers, put it all on a postcard, write down a message and address, and they would mail the postcards anywhere in the world.</p>
<p>The quality of the talks themselves always vary based on the speakers but this year was special for me. You see, I got to see Dave Cheney in person and got to tell him in a slightly high-pitched fanboy voice about how much his writings inspired me to pick up Go. I did have to then look Matt Ryer in the eye and tell him (he was standing right next to Dave) that I enjoyed his writing as well, just not as much as Dave&rsquo;s. Matt, being the gracious human being that he is, told me that he felt the same way. I think he was the second-biggest fan of Dave&rsquo;s at that conference.</p>
<p>Levity aside, there were some quality talks given by funny and passionate people and some sad reminders of what is currently wrong with the world (a speaker unable to attend due to the current geopolitical situation).</p>
<p>I won&rsquo;t go through all the talks, you can do so yourself at <a href="https://www.youtube.com/playlist?list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7">GopherCon Europe: Berlin 2025</a>. I will go through a few specific ones that I enjoyed more than the rest.</p>
<h2 id="talks">Talks</h2>
<blockquote>
<p>The titles are also links to the youtube videos.</p>
</blockquote>
<h3 id="the-things-i-find-myself-repeating-about-go-by-_dave-cheney_httpswwwyoutubecomwatchvrze8ojn7goolistpltovum73amsk3dsr-dxssukuz0-mf3bq7index1"><a href="https://www.youtube.com/watch?v=RZe8ojn7goo&amp;list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7&amp;index=1">The Things I Find Myself Repeating About Go by <em>Dave Cheney</em></a></h3>
<style>
    .wrapper-1 {
      text-align: center;
    }
    .grid-1 {
      display: flex;
      flex-direction: column;
      gap: 1rem;
      flex-wrap: wrap;
    }
     
    .grid-1 figure {
      margin: 0;
      max-width: 100%;
    }
    .grid-1 img {
      width: 100%;
      height: auto;
      object-fit: contain;
    }
    .wrapper-1 .global-caption {
      text-align: center;
      font-size: 0.9em;
      color: var(--dim-fg);
      margin-top: 0.5rem;
    }
    @media (min-width: 768px) {
      .grid-1 {
        flex-direction: row;
        gap: 2rem;
      }
      .grid-1 figure {
        flex: 1 1 calc(50% - 1rem);
      }
    }
  </style>

  <div class="wrapper-1">
    <div class="grid-1"><figure>
            <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/matt-introing-dave_huda2536cc3e3ef768fc6c7e9ee87745d6_4104018_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/matt-introing-dave_huda2536cc3e3ef768fc6c7e9ee87745d6_4104018_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/matt-introing-dave_huda2536cc3e3ef768fc6c7e9ee87745d6_4104018_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/matt-introing-dave_huda2536cc3e3ef768fc6c7e9ee87745d6_4104018_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 350px, (min-width: 768px) 50vw, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="">
          </figure><figure>
            <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/matt-introin-dave-3_hu190b1402a8f2d3bdc889d136711b1bd2_3178534_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/matt-introin-dave-3_hu190b1402a8f2d3bdc889d136711b1bd2_3178534_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/matt-introin-dave-3_hu190b1402a8f2d3bdc889d136711b1bd2_3178534_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/matt-introin-dave-3_hu190b1402a8f2d3bdc889d136711b1bd2_3178534_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 350px, (min-width: 768px) 50vw, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="">
          </figure><figure>
            <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/matt-introing-dave-2_hu50c777fb28de0660c8c68dbefeece82e_1486376_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/matt-introing-dave-2_hu50c777fb28de0660c8c68dbefeece82e_1486376_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/matt-introing-dave-2_hu50c777fb28de0660c8c68dbefeece82e_1486376_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/matt-introing-dave-2_hu50c777fb28de0660c8c68dbefeece82e_1486376_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="">
          </figure></div>
  </div>
<p>I&rsquo;ve always been a big fan of <a href="http://www.cleancoder.com/products">Uncle Bob</a>&rsquo;s Clean Code school of programming. He used Java in his examples but the ideas he put forward are applicable to any programming langauge.</p>
<p>Although I quite enjoy deep technical talks (and there were plenty of those), I love it when someone with long experience in the field talks about the basics of writing good code in a way that spans languages and technology stacks. Especially when they do it with as much of a mix of gravitas and humor as Dave brings to the stage.</p>
<style>
    .wrapper-2 {
      text-align: center;
    }
    .grid-2 {
      display: flex;
      flex-direction: column;
      gap: 1rem;
      flex-wrap: wrap;
    }
     
    .grid-2 figure {
      margin: 0;
      max-width: 100%;
    }
    .grid-2 img {
      width: 100%;
      height: auto;
      object-fit: contain;
    }
    .wrapper-2 .global-caption {
      text-align: center;
      font-size: 0.9em;
      color: var(--dim-fg);
      margin-top: 0.5rem;
    }
    @media (min-width: 768px) {
      .grid-2 {
        flex-direction: row;
        gap: 2rem;
      }
      .grid-2 figure {
        flex: 1 1 calc(50% - 1rem);
      }
    }
  </style>

  <div class="wrapper-2">
    <div class="grid-2"><figure>
            <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/dave-lord-help_hu44b69004067295269c509eb73771e2e2_1853133_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/dave-lord-help_hu44b69004067295269c509eb73771e2e2_1853133_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/dave-lord-help_hu44b69004067295269c509eb73771e2e2_1853133_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/dave-lord-help_hu44b69004067295269c509eb73771e2e2_1853133_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 350px, (min-width: 768px) 50vw, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="">
          </figure><figure>
            <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/dave-too-many_hu1bffefa79bd8bde9f1c682920a9bea21_1682373_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/dave-too-many_hu1bffefa79bd8bde9f1c682920a9bea21_1682373_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/dave-too-many_hu1bffefa79bd8bde9f1c682920a9bea21_1682373_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/dave-too-many_hu1bffefa79bd8bde9f1c682920a9bea21_1682373_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 350px, (min-width: 768px) 50vw, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="">
          </figure></div>
  </div>
<p>That was what Dave&rsquo;s talk was all about: how to write easy-to-read, maintainable code. I didn&rsquo;t agree with all that he said (is it a sign of maturity, being able to disagree with your heroes?) but I appreciated all of it.</p>
<p>Some takeaways:</p>
<ul>
<li>Good naming is worth more than a sea of comments (though comments are never a bad idea). Sometimes even the lack of a full name can be good naming based on the context.</li>
<li>Reduce conditionals to reduce the load on our fleshy thinking blobs.</li>
<li>Keep to the left. Left-aligned code reads faster and easier. If you can get rid of an else statement by a little code-rearrangement, it&rsquo;s often (not always) worth doing it.</li>
<li><code>main.run</code>! A pattern I had already picked up from <a href="https://grafana.com/blog/2024/02/09/how-i-write-http-services-in-go-after-13-years/#func-main-only-calls-run">Matt Ryer&rsquo;s blog post</a>. If you can make your entry-point accept environment-agnostic parameters, you can ship your code to various environments much more easily, not to mention making it a lot more testable. Stdout becomes an io.Writer, os.Args becomes a []string, context.Background becomes just another context.Context and probably more things you can think of.</li>
<li>Make the default case the true case. If your users don&rsquo;t want to give you their data, call the conditional variable <code>DenyTelemetry</code> instead of having a <code>AllowTelemetry</code> and then setting it to false. As Dave says &ldquo;Bad things happen when you accidentally ingest data for a user who has denied telemetry.&rdquo;, and this applies to all kinds of scenarios.</li>
<li>Helpers, write lots and lots of helpers. Code in a helper is named and it is testable.</li>
</ul>
<p>There were many more gems in his talk and I won&rsquo;t go over them all or in much detail because I want you to go to youtube and see his talk. It&rsquo;s a pleasure I dare not deny you.</p>
<blockquote>
<p>Am I fanboying too hard right now?</p>
</blockquote>
<style>
    #img-3 {
      width: 100%;
      height: auto;
    }
    @media (min-width: 768px) {
      #img-3 {
        width: 100%;
      }
    }
  </style>
  <figure style="text-align: center;">
    <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/dave-describe-language_huc4f238348045acc93c79f4abc1111e64_3448086_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/dave-describe-language_huc4f238348045acc93c79f4abc1111e64_3448086_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/dave-describe-language_huc4f238348045acc93c79f4abc1111e64_3448086_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/dave-describe-language_huc4f238348045acc93c79f4abc1111e64_3448086_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="" id="img-3">
  </figure>
<h3 id="panel-with-the-go-team-by-_damien-neil-jonathan-amsterdam-michael-pratt-michael-stapelberg_httpsyoutubeetl1z8t4b9glistpltovum73amsk3dsr-dxssukuz0-mf3bq7"><a href="https://youtu.be/etl1Z8T4B9g?list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7">Panel with the Go Team by <em>Damien Neil, Jonathan Amsterdam, Michael Pratt, Michael Stapelberg</em></a></h3>
<style>
    .wrapper-4 {
      text-align: center;
    }
    .grid-4 {
      display: flex;
      flex-direction: column;
      gap: 1rem;
      flex-wrap: wrap;
    }
     
    .grid-4 figure {
      margin: 0;
      max-width: 100%;
    }
    .grid-4 img {
      width: 100%;
      height: auto;
      object-fit: contain;
    }
    .wrapper-4 .global-caption {
      text-align: center;
      font-size: 0.9em;
      color: var(--dim-fg);
      margin-top: 0.5rem;
    }
    @media (min-width: 768px) {
      .grid-4 {
        flex-direction: row;
        gap: 2rem;
      }
      .grid-4 figure {
        flex: 1 1 calc(50% - 1rem);
      }
    }
  </style>

  <div class="wrapper-4">
    <div class="grid-4"><figure>
            <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/go-panel_hu54366edc639fc694cd6fc8d2baa62619_1816900_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/go-panel_hu54366edc639fc694cd6fc8d2baa62619_1816900_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/go-panel_hu54366edc639fc694cd6fc8d2baa62619_1816900_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/go-panel_hu54366edc639fc694cd6fc8d2baa62619_1816900_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 350px, (min-width: 768px) 50vw, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="">
          </figure><figure>
            <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/go-panel-2_hu8a7066918a2d9739ada15e3c438ca2c1_2008384_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/go-panel-2_hu8a7066918a2d9739ada15e3c438ca2c1_2008384_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/go-panel-2_hu8a7066918a2d9739ada15e3c438ca2c1_2008384_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/go-panel-2_hu8a7066918a2d9739ada15e3c438ca2c1_2008384_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 350px, (min-width: 768px) 50vw, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="">
          </figure></div>
  </div>
<p>A classic Q&amp;A session with some of the Go Team, featuring Michael Stapleberg (Protobuf), Damian Neil (Security), Michael Pratt (Runtime/Performance), and Jonathan Amsterdam (Static Analysis/MCP SDK).</p>
<p>Questions were submitted beforehand, and again, I won&rsquo;t go into all of them, but just some I find interesting. The bold part of each sentence is also a timestamped link to that part of the video.</p>
<ul>
<li>
<p><a href="https://www.youtube.com/watch?v=etl1Z8T4B9g&amp;list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7&amp;index=3&amp;t=191s"><strong>Support immutable datatypes</strong></a>: the team (and especially Michael Pratt) agree that it would be a wonderful feature to have but they don&rsquo;t know how they would do it. I don&rsquo;t know about you but I&rsquo;ve often held up people like the Go Team on a sort of intellectual pedestal and it&rsquo;s nice to see that they too are just people trying to solve challenging problems and sometimes the answer is: &ldquo;We&rsquo;re not sure how to do this so we&rsquo;re leaving the issue be for now.&rdquo;</p>
</li>
<li>
<p><a href="https://www.youtube.com/watch?v=etl1Z8T4B9g&amp;list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7&amp;index=3&amp;t=417s"><strong>Data Science/ML Libraries</strong></a>: the team sees Go&rsquo;s role in all this quite similar to my own: Go is a language that helps productionize what&rsquo;s already built in, say, Python. They are committed to implementing important AI specs (such as MCP SDKs) but apart from that they don&rsquo;t see great value in directly involving Go in ML training.</p>
</li>
<li>
<p><a href="https://www.youtube.com/watch?v=etl1Z8T4B9g&amp;list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7&amp;index=3&amp;t=555s"><strong>Bare Metal Go</strong></a>: not something they&rsquo;re actively considering, it seems, though they are interested in a <a href="https://github.com/golang/go/issues/73608">new proposal</a> submitted by people from the <a href="https://github.com/usbarmory/tamago">Tamago project</a>.</p>
</li>
<li>
<p><a href="https://www.youtube.com/watch?v=etl1Z8T4B9g&amp;list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7&amp;index=3&amp;t=264s"><strong>Most Useless Feature of Go (lang and stdlib)</strong></a>: This mostly centered around things they would remove from the standard library, mainly libraries that are badly designed or badly designed APIs. Can&rsquo;t say I haven&rsquo;t had thoughts like that about my own code every few weeks.</p>
</li>
<li>
<p><a href="https://www.youtube.com/watch?v=etl1Z8T4B9g&amp;list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7&amp;index=3&amp;t=606s"><strong>Error Handling 😬</strong></a>: One of the biggest points of contention within the Go ecosystem, the Go team&rsquo;s answer was (and I&rsquo;m paraphrasing): &ldquo;we don&rsquo;t know how to make it better in a way that makes the majority happy, we&rsquo;ve given up for now and will focus on more impactful matters&rdquo;. They&rsquo;ve <a href="https://go.dev/blog/error-syntax">blogged</a> about this recently as well. Can&rsquo;t blame them, to be honest. I&rsquo;m sure a huge number of people find Go&rsquo;s error handling to be a PITA. I&rsquo;m not one of them. For me, it&rsquo;s a mild annoyance. There are also many for whom it doesn&rsquo;t matter and may even be preferable. And to change something so fundamental would take a concerted effort from the Go team. And without a <em>clear</em> majority benefit I can see why they would rather spend that effort somewhere else.</p>
</li>
<li>
<p><a href="https://www.youtube.com/watch?v=etl1Z8T4B9g&amp;list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7&amp;index=3&amp;t=689s"><strong>Non-nil Function Arguments</strong></a>: I&rsquo;ve seen nil-pointer dereferncing called <a href="https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/">The Billion Dollar Mistake</a>. Maybe I&rsquo;ve lived for too little a time to appreciate this, and perhaps my elders have horror stories that would curl my toes, but I&rsquo;ve never really had a problem with nil (null) pointers. I can count on the fingers of my hands the number of times in my 10+ year career I&rsquo;ve had a nil pointer reference error. Perhaps others&rsquo; mileage has varied. Claude tells me (you can do more thorough research of your own if you wish) that the total valuation of the tech industry in 2009 (when Tony Hoare gave that talk) was around 11-12 Trillion USD. Which means Tony Hoare&rsquo;s billion-dollar mistake caused a 0.0000909091 % or so of damage. I don&rsquo;t think it&rsquo;s anything to lose sleep over. Write me and convince me I&rsquo;m wrong if you&rsquo;re so inclined. I would love a different perspective on this.</p>
<p>The Go team&rsquo;s take on this was similar to the immutability problem: it would be nice to have, but currently they don&rsquo;t see an elegant way to solve it. By this I suppose they mean the guts of the Go compiler. Syntax and DevX-wise I think <a href="https://doc.rust-lang.org/std/option/">Rust&rsquo;s Option</a> type is the GOAT.</p>
<p>For some added hilarity, do sympathize with <a href="https://www.wired.com/2015/11/null/">Christopher Null</a>, especially if you ever wrote code that made the life of people like him more difficult than it needed to be.</p>
</li>
<li>
<p><a href="https://www.youtube.com/watch?v=etl1Z8T4B9g&amp;list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7&amp;index=3&amp;t=1937s"><strong>Performance of Garbage Collector</strong></a>: The hardware landscape keeps changing. One day you&rsquo;re running on a dual-core machine, and then suddenly you&rsquo;re on a <a href="https://www.tomshardware.com/pc-components/cpus/amds-256-core-epyc-venice-cpu-in-the-labs-now-coming-in-2026">256-core</a> one. I can imagine scaling garbage collection in such an environment is a unique challenge. <a href="https://michael.express/">Michael Knyszek</a> is the person making sure this scaling happens and he has a proposal for something called the <a href="https://github.com/golang/go/issues/73581">Green Tea 🍵 Garbage Collector</a>.</p>
</li>
</ul>
<p>There was a lot more they talked about. This was just a little taste of a few things I personally found interesting. Do check out the full talk.</p>
<h3 id="refactoring-go-in-large-codebases-by-_brittany-ellich_httpsyoutubefhlnan0dsuelistpltovum73amsk3dsr-dxssukuz0-mf3bq7"><a href="https://youtu.be/fhlnan0dSUE?list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7">Refactoring Go in Large Codebases by <em>Brittany Ellich</em></a></h3>
<style>
    .wrapper-5 {
      text-align: center;
    }
    .grid-5 {
      display: flex;
      flex-direction: column;
      gap: 1rem;
      flex-wrap: wrap;
    }
     
    .grid-5 figure {
      margin: 0;
      max-width: 100%;
    }
    .grid-5 img {
      width: 100%;
      height: auto;
      object-fit: contain;
    }
    .wrapper-5 .global-caption {
      text-align: center;
      font-size: 0.9em;
      color: var(--dim-fg);
      margin-top: 0.5rem;
    }
    @media (min-width: 768px) {
      .grid-5 {
        flex-direction: row;
        gap: 2rem;
      }
      .grid-5 figure {
        flex: 1 1 calc(50% - 1rem);
      }
    }
  </style>

  <div class="wrapper-5">
    <div class="grid-5"><figure>
            <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/refactoring-go_hu9f052cbad199a024fc2ffa273e6e9087_1657471_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/refactoring-go_hu9f052cbad199a024fc2ffa273e6e9087_1657471_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/refactoring-go_hu9f052cbad199a024fc2ffa273e6e9087_1657471_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/refactoring-go_hu9f052cbad199a024fc2ffa273e6e9087_1657471_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 350px, (min-width: 768px) 50vw, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="">
          </figure><figure>
            <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/refactoring-go-2_hucfce056242e4c2fb20d696f7cdc65112_1734308_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/refactoring-go-2_hucfce056242e4c2fb20d696f7cdc65112_1734308_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/refactoring-go-2_hucfce056242e4c2fb20d696f7cdc65112_1734308_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/refactoring-go-2_hucfce056242e4c2fb20d696f7cdc65112_1734308_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 350px, (min-width: 768px) 50vw, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="">
          </figure><figure>
            <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/refactoring-go-3_hu04b3ac8e791ab3f4f019296e04459050_1742677_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/refactoring-go-3_hu04b3ac8e791ab3f4f019296e04459050_1742677_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/refactoring-go-3_hu04b3ac8e791ab3f4f019296e04459050_1742677_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/refactoring-go-3_hu04b3ac8e791ab3f4f019296e04459050_1742677_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="">
          </figure></div>
  </div>
<p>Brittany started off by reminding us all that as software engineers there are certain ways of thinking we relegate ourselves to. In the context of updating existing software we look at our old messy piece of code and think to ourselves: &ldquo;I can do better if I rewrite this.&rdquo; It&rsquo;s the kind of trap we all fall into as succinctly summarized in Brittany&rsquo;s talk (each line links to the part of the talk where she talks about it):</p>
<ul>
<li><a href="https://youtu.be/fhlnan0dSUE?list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7&amp;t=204">It will be faster to just rewrite it</a></li>
<li><a href="https://youtu.be/fhlnan0dSUE?list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7&amp;t=274">We will move faster with clean code</a></li>
<li><a href="https://youtu.be/fhlnan0dSUE?list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7&amp;t=339">The current technology stack is holding us back</a></li>
</ul>
<p>Watch the full talk to learn what advice she gives on how to go about refactoring (instead of rewriting) software.</p>
<h3 id="go-protobuf-by-_michael-stapelberg_httpsyoutubescnyfvtd5zmlistpltovum73amsk3dsr-dxssukuz0-mf3bq7"><a href="https://youtu.be/scNYFVtD5ZM?list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7">Go Protobuf by <em>Michael Stapelberg</em></a></h3>
<style>
    #img-6 {
      width: 100%;
      height: auto;
    }
    @media (min-width: 768px) {
      #img-6 {
        width: 50%;
      }
    }
  </style>
  <figure style="text-align: center;">
    <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/go-protobuf_hu1623b63afafdf44d9ee597e29a2d27fd_1655860_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/go-protobuf_hu1623b63afafdf44d9ee597e29a2d27fd_1655860_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/go-protobuf_hu1623b63afafdf44d9ee597e29a2d27fd_1655860_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/go-protobuf_hu1623b63afafdf44d9ee597e29a2d27fd_1655860_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 360px, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="" id="img-6">
  </figure>
<p>Michael talked about the what, why and how&rsquo;s of Protobuf, especially as it relates to Go and then moves on to talk about the <a href="https://go.dev/blog/protobuf-opaque">Opaque API</a>.</p>
<p>The Opaque API has been around since the end of 2024 but isn&rsquo;t something I have given much thought until this talk reminded me that I really should be using getters instead of direct field access. Call it my failing at avoiding <a href="https://www.hyrumslaw.com/">Hyrum&rsquo;s Law</a>. It would have made much of my code cleaner.</p>
<p>Fortunately for me, smarter people than I (such as Michael) are about to force my hand. At some point in the future, field access will be replaced by a <code>xxx_hidden_</code> prefix (wonder who came up with that one 🤔) and I will have to update all my code to use accessor methods.</p>
<p>Luckily for me, the smart people are also considerate people, and they&rsquo;ve created a tool (<a href="https://github.com/golang/open2opaque">open2opaque</a>) that rewrites your existing code to conform to the Opaque API.</p>
<p>The Opaque API, apart from providing a cleaner API to work with and promoting safer coding practices, also allow for memory optimizations which provide significant speedups by reducing allocation counts. I encourage you to both read the blog post and watch the talk for yourself.</p>
<blockquote>
<p>Michael not only mentions the open2opaque tool but also talks about how it was written.</p>
</blockquote>
<blockquote>
<p>Michael also mentions <a href="https://github.com/protocolbuffers/protoscope">protoscope</a>: Cool little tool (and representational format) for inspecting the protobuf wire format.</p>
</blockquote>
<h3 id="faster-go-maps-with-swiss-tables-by-_michael-pratt_httpsyoutubeaqtim5ak9t4listpltovum73amsk3dsr-dxssukuz0-mf3bq7"><a href="https://youtu.be/aqtIM5AK9t4?list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7">Faster Go Maps With Swiss Tables by <em>Michael Pratt</em></a></h3>
<style>
    #img-7 {
      width: 100%;
      height: auto;
    }
    @media (min-width: 768px) {
      #img-7 {
        width: 50%;
      }
    }
  </style>
  <figure style="text-align: center;">
    <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/swiss-tables_hu27c720afcd182eef187b2ef081bc1fef_1884997_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/swiss-tables_hu27c720afcd182eef187b2ef081bc1fef_1884997_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/swiss-tables_hu27c720afcd182eef187b2ef081bc1fef_1884997_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/swiss-tables_hu27c720afcd182eef187b2ef081bc1fef_1884997_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 360px, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="" id="img-7">
  </figure>
<p>Fascinating deep talk on how Go maps work in general, the challenges involved in an operation such as increasing a map&rsquo;s size, and how Swiss Tables come in and make everyone&rsquo;s lives faster.</p>
<p>I won&rsquo;t talk about it much, myself, I cannot keep all the details in my head no matter how many times I go over them, but I find them fascinating, nonetheless and I think you should watch the talk.</p>
<h3 id="for-the-love-of-go-how-to-contribute-really-by-_arati-rana_httpswwwyoutubecomwatchvc0mntirvtvalistpltovum73amsk3dsr-dxssukuz0-mf3bq7index12ppiaqb"><a href="https://www.youtube.com/watch?v=c0MntiRvTVA&amp;list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7&amp;index=12&amp;pp=iAQB">For the Love of Go: How to Contribute Really by <em>Arati Rana</em></a></h3>
<style>
    #img-8 {
      width: 100%;
      height: auto;
    }
    @media (min-width: 768px) {
      #img-8 {
        width: 50%;
      }
    }
  </style>
  <figure style="text-align: center;">
    <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/how-to-contribute_huf944768827b34e870ab85040b9d01769_1915658_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/how-to-contribute_huf944768827b34e870ab85040b9d01769_1915658_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/how-to-contribute_huf944768827b34e870ab85040b9d01769_1915658_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/how-to-contribute_huf944768827b34e870ab85040b9d01769_1915658_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 360px, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="" id="img-8">
  </figure>
<p>The beauty of this talk lies in the humility and courage of its presenter. Arati Rana took her first steps into contributing to Go, struggled once and again, and kept at it. She shares the story of adding <a href="https://github.com/golang/go/issues/59928"><code>T.Output</code></a> to go, a process that was filled with a lot of twisting paths.</p>
<h3 id="y-recursion-the-hard-way-by-_eleanor-mchugh_httpsyoutube1hqn2buwv4ylistpltovum73amsk3dsr-dxssukuz0-mf3bq7"><a href="https://youtu.be/1hQN2BuwV4Y?list=PLtoVuM73AmsK3DSr-DxSsUkuz0-Mf3bQ7">Y: Recursion The Hard Way by <em>Eleanor McHugh</em></a></h3>
<p>I left this one for last. <a href="https://about.me/eleanor_mchugh">Eleanor McHugh</a> is probably the funniest person I&rsquo;ve come across in a while who doesn&rsquo;t do comedy for a living. Her talk softened my brain, then took the little melty bits and rearranged them in interesting ways.</p>
<p>I think I&rsquo;ll need to watch it at least 15 more times to make any sense of it but that&rsquo;s okay because watching her give the talk is one of the most pleasant experiences in my life, so far.</p>
<p>I won&rsquo;t try to explain the talk. My fingers might start melting as well. In short, it is about doing recursion without a function calling itself.</p>
<p>Wait, that doesn&rsquo;t sound right. Or does it? 🤔</p>
<h2 id="other-things">Other Things</h2>
<p>Between sessions, beautiful artworks made by talented gophers were displayed on the screen.</p>
<style>
    .wrapper-9 {
      text-align: center;
    }
    .grid-9 {
      display: flex;
      flex-direction: column;
      gap: 1rem;
      flex-wrap: wrap;
    }
     
    .grid-9 figure {
      margin: 0;
      max-width: 100%;
    }
    .grid-9 img {
      width: 100%;
      height: auto;
      object-fit: contain;
    }
    .wrapper-9 .global-caption {
      text-align: center;
      font-size: 0.9em;
      color: var(--dim-fg);
      margin-top: 0.5rem;
    }
    @media (min-width: 768px) {
      .grid-9 {
        flex-direction: row;
        gap: 2rem;
      }
      .grid-9 figure {
        flex: 1 1 calc(50% - 1rem);
      }
    }
  </style>

  <div class="wrapper-9">
    <div class="grid-9"><figure>
            <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-backdrop_huc1750c4172bef70588001ed7295598c9_5007549_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-backdrop_huc1750c4172bef70588001ed7295598c9_5007549_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-backdrop_huc1750c4172bef70588001ed7295598c9_5007549_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-backdrop_huc1750c4172bef70588001ed7295598c9_5007549_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 350px, (min-width: 768px) 50vw, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="">
          </figure><figure>
            <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-gopher-art_hu59210b162c9739a33fbbdf4c4d109c18_2369678_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-gopher-art_hu59210b162c9739a33fbbdf4c4d109c18_2369678_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-gopher-art_hu59210b162c9739a33fbbdf4c4d109c18_2369678_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-gopher-art_hu59210b162c9739a33fbbdf4c4d109c18_2369678_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 350px, (min-width: 768px) 50vw, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="">
          </figure></div>
    <figcaption class="global-caption">AI could never!</figcaption>
  </div>
<hr>
<p>Lunch was served up by <a href="https://kerbberlin.de/kitchens/fraulein-kimchi/">Fraulein Kimchi</a> and <a href="https://papa-africa-kitchen.eatbu.com">Papa Africa Kitchen</a>, simple hearty meals that gets one through a day of talks.</p>
<style>
    #img-10 {
      width: 100%;
      height: auto;
    }
    @media (min-width: 768px) {
      #img-10 {
        width: 100%;
      }
    }
  </style>
  <figure style="text-align: center;">
    <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/food-3_hu7560213defd93cd1a94e3dd197c6146b_1952206_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/food-3_hu7560213defd93cd1a94e3dd197c6146b_1952206_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/food-3_hu7560213defd93cd1a94e3dd197c6146b_1952206_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/food-3_hu7560213defd93cd1a94e3dd197c6146b_1952206_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="Matt was disturbingly invested in a potential romance between the culinary personifications of these food outlets." id="img-10">
    <figcaption style="text-align: center; font-size: 0.9em; color: var(--dim-fg);">Matt was disturbingly invested in a potential romance between the culinary personifications of these food outlets.</figcaption>
  </figure>
<p>I had the fufu with peanut sauce from Papa Africa.</p>
<style>
    #img-11 {
      width: 100%;
      height: auto;
    }
    @media (min-width: 768px) {
      #img-11 {
        width: 50%;
      }
    }
  </style>
  <figure style="text-align: center;">
    <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/food-2_hu8e127201bb92649dad38906410fe940d_2615321_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/food-2_hu8e127201bb92649dad38906410fe940d_2615321_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/food-2_hu8e127201bb92649dad38906410fe940d_2615321_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/food-2_hu8e127201bb92649dad38906410fe940d_2615321_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 360px, 100vw" width="960" height="720" loading="lazy" decoding="async" alt="It wasn&#39;t as good as I had hoped." id="img-11">
    <figcaption style="text-align: center; font-size: 0.9em; color: var(--dim-fg);">It wasn&rsquo;t as good as I had hoped.</figcaption>
  </figure>
<p>Breakfast (and snackies thoughout the day) was served by I don&rsquo;t know who but I was a big fan of these long round pastries filled with spinach. I wish I had taken a picture. I think I ate more of those than actual lunch or the much more healthier option of fruit. There was a lot of fruit also on offer.</p>
<hr>
<p>I stayed in a housing complex of sorts with lots of small rooms arranged around a central courtyard. You have to pass through multiple doors to get to where you need to go, so I slept peacefully knowing any would-be murderers were probably guests staying there. I would rather be killed by hotel cutlery than a shank from a street hobo.</p>
<style>
    #img-12 {
      width: 100%;
      height: auto;
    }
    @media (min-width: 768px) {
      #img-12 {
        width: 50%;
      }
    }
  </style>
  <figure style="text-align: center;">
    <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/stay_hu4e95e3023ce5b45e6b2c2c7e3e621fa3_5712791_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/stay_hu4e95e3023ce5b45e6b2c2c7e3e621fa3_5712791_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/stay_hu4e95e3023ce5b45e6b2c2c7e3e621fa3_5712791_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/stay_hu4e95e3023ce5b45e6b2c2c7e3e621fa3_5712791_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 360px, 100vw" width="960" height="1280" loading="lazy" decoding="async" alt="The almost hostel style housing complex I stated at." id="img-12">
    <figcaption style="text-align: center; font-size: 0.9em; color: var(--dim-fg);">The almost hostel style housing complex I stated at.</figcaption>
  </figure>
<p>The room I was in (and most rooms there, I can imagine) was functional. That&rsquo;s the only way I can really describe it. It wasn&rsquo;t sparse, it wasn&rsquo;t messy or dirty, it just felt extremely functional. The furniture was old and well-used, there was only as much furniture as one could need, everything was quite minimal. It felt&hellip;minimal. I am at a loss for words to describe it because I still have mixed feelings about it.</p>
<p>On the one hand it does not meet my expectations of even a basic hotel room. It lacked pizazz! At the same time it was cleaner and more well-maintained than almost any hotel I&rsquo;ve been to. It confounds my senses even as I think back on it. And I like it. And I would go back 11/10.</p>
<hr>
<p>On my way back to the airport, I came across something&hellip;disturbing.</p>












<figure class="center" style="text-align: center;">
    
    <video autoplay loop muted playsinline controls preload="metadata" style="max-width: 100%; width: 60%;">
        
            <source src='/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/trauma-hq.mp4' type="video/mp4" />
        
        
        
        
        
    </video>
    
        <figcaption>
            
                <p>Real life is often weirder than AI-generated nonsense.</p>
            
            
        </figcaption>
    
</figure>

<hr>
<p>At the airport, the German version of the TSA kept my Oathbringer keychain. This was the last picture I managed to keep of it.</p>
<style>
    #img-14 {
      width: 100%;
      height: auto;
    }
    @media (min-width: 768px) {
      #img-14 {
        width: 50%;
      }
    }
  </style>
  <figure style="text-align: center;">
    <img src="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/tragedy_huf29afb7bee52ac7f4a864d90efc75946_7634313_960x0_resize_q75_h2_box.webp" srcset="/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/tragedy_huf29afb7bee52ac7f4a864d90efc75946_7634313_480x0_resize_q75_h2_box.webp 480w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/tragedy_huf29afb7bee52ac7f4a864d90efc75946_7634313_960x0_resize_q75_h2_box.webp 960w, /posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/tragedy_huf29afb7bee52ac7f4a864d90efc75946_7634313_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 360px, 100vw" width="960" height="1280" loading="lazy" decoding="async" alt="Why, Almighty? WHY?" id="img-14">
    <figcaption style="text-align: center; font-size: 0.9em; color: var(--dim-fg);">Why, Almighty? WHY?</figcaption>
  </figure>
]]></description><media:thumbnail url="https://badgerbadgerbadgerbadger.dev/posts/misc/2025-09-01-gophercon-eu-20205-berlin-recap/media/conf-stage_hu50f6fcb9bb1eb1616516d5a98bedb2e5_5222657_1200x0_resize_q80_box.jpg"/></item><item><title>Getting an RTSP Stream from a Mac Camera</title><link>https://badgerbadgerbadgerbadger.dev/posts/tips/2025-07-02-mac-camera-to-rtsp/</link><pubDate>Wed, 02 Jul 2025 00:00:00 +0000</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/tips/2025-07-02-mac-camera-to-rtsp/</guid><description><![CDATA[<p>I&rsquo;ve been working on a certain project which I am not ready to share yet, but which involves reading from RTP streams.</p>
<p>Before I ordered myself some RTSP compatible cameras, I needed a quick way to get an RTP stream to test out the general idea. The only cameras I have on hand are my iPhone, my MacBook and an old IP camera from which I&rsquo;ve so far failed to extract an RTP stream.</p>
<p>I decided to see what I could do with my MacBook camera and after some trial and error landed on using <a href="https://github.com/bluenviron/mediamtx">mediamtx</a>.</p>
<p>From it&rsquo;s Github README:</p>
<blockquote>
<p><em>MediaMTX is a ready-to-use and zero-dependency real-time media server and media proxy that allows to publish, read, proxy, record and playback video and audio streams. It has been conceived as a &ldquo;media router&rdquo; that routes media streams from one end to the other.</em></p>
</blockquote>
<p>I used mediamtx in conjunction with good old ffmpeg to get the RTP stream from the Mac camera. ffmpeg takes the camera input, does some encoding voodoo and then sends it to mediamtx which serves it as an RTP stream.</p>
<p>This is the script I ended up with after a lot of help from Claude (and with added comments for clarity):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="c1"># Exit immediately if any command fails</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> -e
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># RTSP Stream Manager</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Get camera device from first argument, default to device 0 if not provided</span>
</span></span><span class="line"><span class="cl"><span class="nv">DEVICE</span><span class="o">=</span><span class="si">${</span><span class="nv">1</span><span class="k">:-</span><span class="nv">0</span><span class="si">}</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Variables to store process IDs for cleanup</span>
</span></span><span class="line"><span class="cl"><span class="nv">MEDIAMTX_PID</span><span class="o">=</span><span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">FFMPEG_PID</span><span class="o">=</span><span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Cleanup function to gracefully stop both services</span>
</span></span><span class="line"><span class="cl">cleanup<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;Stopping services...&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Kill FFmpeg process if it&#39;s running</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">[</span> ! -z <span class="s2">&#34;</span><span class="nv">$FFMPEG_PID</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="nb">kill</span> -9 <span class="nv">$FFMPEG_PID</span> 2&gt;/dev/null
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;FFmpeg stopped&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Kill MediaMTX process if it&#39;s running</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">[</span> ! -z <span class="s2">&#34;</span><span class="nv">$MEDIAMTX_PID</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="nb">kill</span> -9 <span class="nv">$MEDIAMTX_PID</span> 2&gt;/dev/null
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;MediaMTX stopped&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="nb">exit</span> <span class="m">0</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Set up signal traps to run cleanup on script interruption or exit</span>
</span></span><span class="line"><span class="cl"><span class="nb">trap</span> cleanup SIGINT SIGTERM EXIT
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start MediaMTX streaming server in background</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Starting MediaMTX server...&#34;</span>
</span></span><span class="line"><span class="cl">mediamtx mediamtx.yml <span class="p">&amp;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Store the process ID for cleanup</span>
</span></span><span class="line"><span class="cl"><span class="nv">MEDIAMTX_PID</span><span class="o">=</span><span class="nv">$!</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Give MediaMTX time to initialize before starting FFmpeg</span>
</span></span><span class="line"><span class="cl">sleep <span class="m">2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Display connection information to user</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Starting camera stream from device </span><span class="nv">$DEVICE</span><span class="s2">...&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;View RTSP: rtsp://localhost:8554/live&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;View Web: http://localhost:8889/live&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Press Ctrl+C to stop both services&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start FFmpeg to capture camera and stream to MediaMTX</span>
</span></span><span class="line"><span class="cl">ffmpeg -f avfoundation <span class="se">\ </span>          <span class="c1"># Use macOS AVFoundation for camera access</span>
</span></span><span class="line"><span class="cl">       -video_size 1280x720 <span class="se">\ </span>     <span class="c1"># Set capture resolution</span>
</span></span><span class="line"><span class="cl">       -framerate <span class="m">30</span> <span class="se">\ </span>            <span class="c1"># Set capture frame rate</span>
</span></span><span class="line"><span class="cl">       -i <span class="s2">&#34;</span><span class="nv">$DEVICE</span><span class="s2">&#34;</span> <span class="se">\ </span>             <span class="c1"># Input device (camera number)</span>
</span></span><span class="line"><span class="cl">       -c:v libx264 <span class="se">\ </span>             <span class="c1"># Use x264 video codec</span>
</span></span><span class="line"><span class="cl">       -preset ultrafast <span class="se">\ </span>        <span class="c1"># Use fastest encoding preset (lower CPU usage)</span>
</span></span><span class="line"><span class="cl">       -tune zerolatency <span class="se">\ </span>        <span class="c1"># Optimize for low latency streaming</span>
</span></span><span class="line"><span class="cl">       -pix_fmt yuv420p <span class="se">\ </span>         <span class="c1"># Set pixel format for compatibility</span>
</span></span><span class="line"><span class="cl">       -f flv <span class="se">\ </span>                   <span class="c1"># Output format (FLV for RTMP)</span>
</span></span><span class="line"><span class="cl">       <span class="s2">&#34;rtmp://localhost:1935/live&#34;</span> <span class="p">&amp;</span>  <span class="c1"># Send to MediaMTX via RTMP</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Store FFmpeg process ID for cleanup</span>
</span></span><span class="line"><span class="cl"><span class="nv">FFMPEG_PID</span><span class="o">=</span><span class="nv">$!</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Wait for FFmpeg process to complete or be terminated</span>
</span></span><span class="line"><span class="cl"><span class="nb">wait</span> <span class="nv">$FFMPEG_PID</span>
</span></span></code></pre></div><blockquote>
<p><strong>Note</strong>: I also learned about the <code>trap</code> command in bash which allows you to run a cleanup function when the script is interrupted or exits. I read further on it at <a href="https://www.linuxjournal.com/content/bash-trap-command">The Bash Trap Command on Linux Journal by Mitch Frazier</a>.</p>
</blockquote>
<p><code>mediamtx.yml</code> is an extremely barebones file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># Minimal MediaMTX config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">all</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></span></span></code></pre></div><p>I needed to have <em>something</em>, for without it mediamtx would complain about missing authentication configuration. This file doesn&rsquo;t <em>have</em> any authentication configuration, so I&rsquo;m guessing mediamtx sees it and is like: &ldquo;I have an explicit config file, but it doesn&rsquo;t have any authentication, so I won&rsquo;t require any&rdquo;. Good enough, I suppose.</p>
<p>On running, the script gives us both an RTSP stream and something we can see on a web page. I am not quite sure what that&rsquo;s doing. Though by looking at the network requests, I see there&rsquo;s a <code>Content-Type application/sdp</code> response, which suggests to me WebRTC since an SDP (Session Description Protocol) is typically used in WebRTC. Pretty neat!</p>
<p><img src="/posts/tips/2025-07-02-mac-camera-to-rtsp/sdp-content-type_hu8c13b36baf1f2331d2e98a1820c3d98e_100836_480x0_resize_q75_h2_box_3.webp" srcset="/posts/tips/2025-07-02-mac-camera-to-rtsp/sdp-content-type_hu8c13b36baf1f2331d2e98a1820c3d98e_100836_480x0_resize_q75_h2_box_3.webp 480w" sizes="(min-width: 800px) 720px, 100vw" width="480" height="193" loading="lazy" decoding="async" alt="sdp-content-type.png"></p>
<p>I can view the RTSP stream in VLC by going to <code>File -&gt; Open Network</code> and entering <code>rtsp://localhost:8554/live</code>.</p>
<p><img src="/posts/tips/2025-07-02-mac-camera-to-rtsp/vlc-open-network_hue0e3b7067d65b51c4b198794e1e04a7d_91010_960x0_resize_q75_h2_box_3.webp" srcset="/posts/tips/2025-07-02-mac-camera-to-rtsp/vlc-open-network_hue0e3b7067d65b51c4b198794e1e04a7d_91010_480x0_resize_q75_h2_box_3.webp 480w, /posts/tips/2025-07-02-mac-camera-to-rtsp/vlc-open-network_hue0e3b7067d65b51c4b198794e1e04a7d_91010_960x0_resize_q75_h2_box_3.webp 960w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="537" loading="lazy" decoding="async" alt="vlc-open-network.png"></p>
<p>Hope you had fun reading!</p>
]]></description></item><item><title>Building a Golang Protoc Plugin to SQL Scan+Value Enums</title><link>https://badgerbadgerbadgerbadger.dev/posts/automation/2025-06-30-building-a-golang-protoc-plugin-to-sql-scan-value-enums/</link><pubDate>Mon, 30 Jun 2025 00:00:00 +0000</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/automation/2025-06-30-building-a-golang-protoc-plugin-to-sql-scan-value-enums/</guid><description><![CDATA[<p>At <a href="https://sentiance.com/">Sentiance</a> (where I work as a Software Engineer), we use <a href="https://grpc.io/">gRPC</a> and <a href="https://protobuf.dev/">protobuf</a> to communicate between services in a typesafe manner.</p>
<p>Here&rsquo;s a recent problem I faced. I had several enums in protobuf files that I wanted to save as database columns. But instead of their integer representation, I wanted to store their string representations.</p>
<p>Let&rsquo;s say this is the enum I wanted to store.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nn">userama</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">enum</span> <span class="n">UserType</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="n">UNSPECIFIED</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="n">SUPER</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="n">COMMON</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span></code></pre></div><p>The generated Golang code for our enum looks like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">UserType</span> <span class="kt">int32</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="kd">const</span> <span class="p">(</span>  
</span></span><span class="line"><span class="cl">    <span class="nx">UserType_UNSPECIFIED</span> <span class="nx">UserType</span> <span class="p">=</span> <span class="mi">0</span>  
</span></span><span class="line"><span class="cl">    <span class="nx">UserType_SUPER</span>       <span class="nx">UserType</span> <span class="p">=</span> <span class="mi">1</span>  
</span></span><span class="line"><span class="cl">    <span class="nx">UserType_COMMON</span>      <span class="nx">UserType</span> <span class="p">=</span> <span class="mi">2</span>  
</span></span><span class="line"><span class="cl"><span class="p">)</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="c1">// Enum value maps for UserType.  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">var</span> <span class="p">(</span>  
</span></span><span class="line"><span class="cl">    <span class="nx">UserType_name</span> <span class="p">=</span> <span class="kd">map</span><span class="p">[</span><span class="kt">int32</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span>  
</span></span><span class="line"><span class="cl">       <span class="mi">0</span><span class="p">:</span> <span class="s">&#34;UNSPECIFIED&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">       <span class="mi">1</span><span class="p">:</span> <span class="s">&#34;SUPER&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">       <span class="mi">2</span><span class="p">:</span> <span class="s">&#34;COMMON&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">    <span class="p">}</span>  
</span></span><span class="line"><span class="cl">    <span class="nx">UserType_value</span> <span class="p">=</span> <span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int32</span><span class="p">{</span>  
</span></span><span class="line"><span class="cl">       <span class="s">&#34;UNSPECIFIED&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">       <span class="s">&#34;SUPER&#34;</span><span class="p">:</span>       <span class="mi">1</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">       <span class="s">&#34;COMMON&#34;</span><span class="p">:</span>      <span class="mi">2</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">    <span class="p">}</span>  
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">...</span><span class="nx">more</span> <span class="nx">stuff</span>
</span></span></code></pre></div><p>Note the two exported maps, <code>&lt;EnumName&gt;_name</code> and <code>&lt;EnumName&gt;_value</code>. These allow us to convert between the string and int32 representations of our enum values.</p>
<p>Let&rsquo;s say this is the struct representing the database row.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">User</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">ID</span>    <span class="kt">string</span>           <span class="s">`pg:&#34;id&#34;`</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Name</span>  <span class="kt">string</span>           <span class="s">`pg:&#34;name&#34;`</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Email</span> <span class="kt">string</span>           <span class="s">`pg:&#34;email&#34;`</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Type</span>  <span class="nx">userama</span><span class="p">.</span><span class="nx">UserType</span> <span class="s">`pg:&#34;type&#34;`</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Since the underlying type of the generated enum code in Golang are int32s, storing this struct naively in the database (using a library that uses the corresponding struct tags to map the fields to column names) would result in the <code>type</code> column storing an integer value.</p>
<p>For a value:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">user</span> <span class="o">:=</span> <span class="nx">User</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Id</span><span class="p">:</span> <span class="s">&#34;dwedwedwdwd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Name</span><span class="p">:</span> <span class="s">&#34;Shaunion&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Email</span><span class="p">:</span> <span class="s">&#34;Shaunion@Onion.on&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Type</span><span class="p">:</span> <span class="nx">userama</span><span class="p">.</span><span class="nx">UserType_SUPER</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We get the table:</p>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>email</th>
<th>type</th>
</tr>
</thead>
<tbody>
<tr>
<td>dwedwedwdwd</td>
<td>Shaunion</td>
<td><a href="mailto:Shaunion@Onion.on">Shaunion@Onion.on</a></td>
<td>1</td>
</tr>
</tbody>
</table>
<p>But those aren&rsquo;t very human readable if, for some reason, we need to inspect your database columns directly. We want to store the string representation of the enum in the database and then cast it to the typed int32 when reading it back.</p>
<p>How do we do this?</p>
<h1 id="sql-scanner--valuer-interfaces">SQL Scanner + Valuer Interfaces</h1>
<p>We can implement the <a href="https://pkg.go.dev/database/sql#Scanner">Scanner</a> and <a href="https://pkg.go.dev/database/sql/driver#Valuer">Valuer</a> interfaces, using a wrapper that takes advantages of the exported maps.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">UserTypeWrapper</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">userama</span><span class="p">.</span><span class="nx">UserType</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Value implements the SQL Valuer interface, allowing us
</span></span></span><span class="line"><span class="cl"><span class="c1">// to map a Golang value into a postgres column.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">u</span> <span class="o">*</span><span class="nx">UserTypeWrapper</span><span class="p">)</span> <span class="nf">Value</span><span class="p">()</span> <span class="p">(</span><span class="nx">sql</span><span class="p">.</span><span class="nx">Value</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// Since the underlying type is just an int32, you could technically
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// assign any value, whether predefined or not, to the field. It&#39;s
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// good to have a check.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="k">if</span> <span class="nx">stringVal</span><span class="p">,</span> <span class="nx">present</span> <span class="o">:=</span> <span class="nx">userama</span><span class="p">.</span><span class="nx">UserType_name</span><span class="p">[</span><span class="nx">u</span><span class="p">.</span><span class="nx">UserType</span><span class="p">];</span> <span class="nx">present</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">stringVal</span><span class="p">,</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;%d is not a valid value&#34;</span><span class="p">,</span> <span class="nx">u</span><span class="p">.</span><span class="nx">UserType</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Scan implements the SQL Scanner interface, allowing us to
</span></span></span><span class="line"><span class="cl"><span class="c1">// map a value from a postgres column to a variable.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">u</span> <span class="o">*</span><span class="nx">UserTypeWrapper</span><span class="p">)</span> <span class="nf">Scan</span><span class="p">(</span><span class="nx">src</span> <span class="nx">any</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">asString</span><span class="p">,</span> <span class="nx">cast</span> <span class="o">:=</span> <span class="nx">src</span><span class="p">.(</span><span class="kt">string</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">!</span><span class="nx">cast</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;%v is not a valid string&#34;</span><span class="p">,</span> <span class="nx">src</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">int32Val</span><span class="p">,</span> <span class="nx">present</span> <span class="o">:=</span> <span class="nx">userama</span><span class="p">.</span><span class="nx">UserType_value</span><span class="p">[</span><span class="nx">asString</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">!</span><span class="nx">present</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;%s is not a valid value&#34;</span><span class="p">,</span> <span class="nx">asString</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">u</span><span class="p">.</span><span class="nx">UserType</span> <span class="p">=</span> <span class="nx">int32Val</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><blockquote>
<p>Note: We could have also used <code>u.UserType.String()</code> to get the string version of the enum, the only problem with that being the lack of any validation. If we were to assign the value <code>56</code> to <code>u.UserType</code>, <code>String()</code> wouldn&rsquo;t complain. It would fall back to printing <code>56</code>, which is a very confusing error to try to debug.</p>
</blockquote>
<p>The only problem with this approach is that we will have to do this for every single enum type. We will have to only do this once for each new enum type you introduce, but introducing a new enum type now comes with extra work.</p>
<p>We can do better by creating a protobuf plugin to handle this for us.</p>
<h1 id="parts-of-a-protobuf-plugin">Parts of a Protobuf Plugin</h1>
<p>When generating go code using the protoc tool like so.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">protoc --go_out<span class="o">=</span>. --go_opt<span class="o">=</span><span class="nv">paths</span><span class="o">=</span>source_relative
</span></span></code></pre></div><p>We&rsquo;re making use of at least two separate binaries (that I am aware of): <code>protoc</code> and <code>protoc-gen-go</code>.</p>
<h2 id="protoc">Protoc</h2>
<p><code>protoc</code> is the protobuf compiler. It is installed on my machine via homebrew (there are also <a href="https://protobuf.dev/installation/">other ways</a> to install it).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  ~ which protoc
</span></span><span class="line"><span class="cl">/opt/homebrew/bin/protoc
</span></span></code></pre></div><p>It is the entry point for protobuf compilation (transpilation?). It parses and validates incoming protobuf files, resolving any dependencies along the way, ensuring type safety and any other goodness for which we prefer strict data schemas.</p>
<p>It&rsquo;s output is <em>not</em> Golang code, but something called a <a href="https://github.com/protocolbuffers/protobuf/blob/1016f1b1a1a3db9425ceaba620e2dd667f0f4de4/src/google/protobuf/compiler/plugin.proto#L43">Code Generation Request</a> (itself represented in protobuf wire format). This is then piped to the Stdin of whatever code generator <em>plugin</em> we want to use to generate our final output.</p>
<p>That&rsquo;s what the <code>--go_out</code> and <code>--go_opt</code> flags do. They indicate to the <code>protoc</code> binary that we want to generate Golang code and store it at a certain path (and with some options).</p>
<p>As you might have guessed, <code>protoc</code> looks for a binary in our path named <code>protoc-gen-go</code> or more generally for a given flag <code>--&lt;name&gt;_out</code> it will look for a binary <code>protoc-gen-&lt;name&gt;</code>.</p>
<h2 id="protoc-go-plugin">Protoc Go Plugin</h2>
<p><a href="https://pkg.go.dev/google.golang.org/protobuf"><code>protoc-gen-go</code></a> is the official plugin for generating Golang code from a protobuf file. It takes as its input a Code Generation Request via Stdin and then writes out the corresponding Golang code to its Stdout. This is then read back by the protoc compiler and stored in the correct paths.</p>
<p>If we want to pass options to the plugin we can do so via the <a href="https://protobuf.dev/reference/go/go-generated/"><code>--go_opt</code> flag</a>.</p>
<h1 id="building-our-own-plugin">Building our own Plugin</h1>
<p>Let&rsquo;s build a plugin that augments the Golang code generated by <code>protoc-gen-go</code> and adds the methods that would make it satisfy the Scanner and Valuer interfaces.</p>
<p>We start off by defining the basic structure.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="o">...</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;google.golang.org/protobuf/compiler/protogen&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">run</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span> <span class="nx">os</span><span class="p">.</span><span class="nx">Args</span><span class="p">[</span><span class="mi">1</span><span class="p">:],</span> <span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">);</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">		<span class="nx">os</span><span class="p">.</span><span class="nf">Exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c1">// Only exit point in the program.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="p">}</span> 
</span></span><span class="line"><span class="cl"><span class="p">}</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">run</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">args</span> <span class="p">[]</span><span class="kt">string</span><span class="p">,</span> <span class="nx">stdout</span> <span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">	<span class="c1">// Handle version command.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="nx">args</span><span class="p">)</span> <span class="p">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="nx">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s">&#34;version&#34;</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">		<span class="nx">fmt</span><span class="p">.</span><span class="nf">Fprintln</span><span class="p">(</span><span class="nx">stdout</span><span class="p">,</span> <span class="nx">version</span><span class="p">)</span> 
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="kc">nil</span> 
</span></span><span class="line"><span class="cl">	<span class="p">}</span> 
</span></span><span class="line"><span class="cl">	<span class="c1">// Main plugin logic.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">protogen</span><span class="p">.</span><span class="nx">Options</span><span class="p">{}.</span><span class="nf">Run</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">gen</span> <span class="o">*</span><span class="nx">protogen</span><span class="p">.</span><span class="nx">Plugin</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">		<span class="c1">// Process proto files here.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="k">return</span> <span class="kc">nil</span> 
</span></span><span class="line"><span class="cl">	<span class="p">})</span> 
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="kc">nil</span> 
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Handling the version command isn&rsquo;t strictly necessary but is a nice quality of life addition to our plugin.</p>
<p>After some basic boiler plate our plugin code will start executing by invoking <code>protogen.Optipns{}.Run</code>. It takes a callback which provides us with the parsed (and validated) protobuf file. <code>gen.Files</code> is where each of the files that were fed to the compiler are available.</p>
<p>Since we are targeting enums, we need to loop over the input files and gather up the enums.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">	<span class="o">...</span>
</span></span><span class="line"><span class="cl">	<span class="nx">protogen</span><span class="p">.</span><span class="nx">Options</span><span class="p">{}.</span><span class="nf">Run</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">gen</span> <span class="o">*</span><span class="nx">protogen</span><span class="p">.</span><span class="nx">Plugin</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">		<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">f</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">gen</span><span class="p">.</span><span class="nx">Files</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">!</span><span class="nx">f</span><span class="p">.</span><span class="nx">Generate</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">				<span class="k">continue</span> <span class="c1">// Skip files not marked for generation, this includes Google&#39;s well-known types as well as any imports or dependencies which the compiler would have pulled in for type-checking but which we aren&#39;t inrerested in.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>			<span class="p">}</span> 
</span></span><span class="line"><span class="cl">			
</span></span><span class="line"><span class="cl">			<span class="c1">// TODO: process the file.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="kc">nil</span> 
</span></span><span class="line"><span class="cl">	<span class="p">})</span> 
</span></span><span class="line"><span class="cl">	<span class="o">...</span>
</span></span></code></pre></div><p>Protobuf enums can appear in two places: top-level enums, and nested enums.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nn">userama</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">enum</span> <span class="n">UserType</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="n">UNSPECIFIED</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="n">SUPER</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="n">COMMON</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">User</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="kd">enum</span> <span class="n">Type</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>		<span class="n">UNSPECIFIED</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>		<span class="n">SUPER</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>		<span class="n">COMMON</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span></code></pre></div><p>Both are valid ways of defining enums and which one you pick would depend on your usecase. If an enum is hyper-specific to a message type, you might define it as part of that message type itself. In this case you would refer to it as <code>User.Type</code> instead of <code>UserType</code>. For enums that might be shared by many messages it makes more sense to create a package level (or top level) enum.</p>
<p>We need to account for both cases in our code.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// TemplateData holds the data structure to be used by Golang&#39;s text/template package.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">TemplateData</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">PackageName</span> <span class="kt">string</span>  
</span></span><span class="line"><span class="cl">    <span class="nx">Enums</span>       <span class="p">[]</span><span class="nx">EnumData</span>  
</span></span><span class="line"><span class="cl">    <span class="nx">Version</span>     <span class="kt">string</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">EnumData</span> <span class="kd">struct</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="nx">Name</span>        <span class="kt">string</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Next we collect our enums.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">f</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">gen</span><span class="p">.</span><span class="nx">Files</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">data</span> <span class="o">:=</span> <span class="nx">TemplateData</span><span class="p">{</span>  
</span></span><span class="line"><span class="cl">	    <span class="nx">PackageName</span><span class="p">:</span> <span class="nb">string</span><span class="p">(</span><span class="nx">file</span><span class="p">.</span><span class="nx">GoPackageName</span><span class="p">),</span>  
</span></span><span class="line"><span class="cl">	    <span class="nx">Enums</span><span class="p">:</span>       <span class="nb">make</span><span class="p">([]</span><span class="nx">EnumData</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>  
</span></span><span class="line"><span class="cl">	    <span class="nx">Version</span><span class="p">:</span>     <span class="nx">version</span> <span class="c1">// It&#39;s a good idea to include the version of your tool that generated this code.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">!</span><span class="nx">f</span><span class="p">.</span><span class="nx">Generate</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">		<span class="k">continue</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> 
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="c1">// Top-level enums.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">enum</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">f</span><span class="p">.</span><span class="nx">Enums</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">		<span class="nx">data</span><span class="p">.</span><span class="nx">Enums</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">Enums</span><span class="p">,</span> <span class="nx">EnumData</span><span class="p">{</span> 
</span></span><span class="line"><span class="cl">			<span class="nx">Name</span><span class="p">:</span> <span class="nx">enum</span><span class="p">.</span><span class="nx">GoIdent</span><span class="p">.</span><span class="nx">GoName</span><span class="p">,</span> <span class="c1">// Go type name.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="p">})</span> 
</span></span><span class="line"><span class="cl">	<span class="p">}</span> 
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="c1">// Nested enums from messages.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">message</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">f</span><span class="p">.</span><span class="nx">Messages</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">		<span class="nx">data</span><span class="p">.</span><span class="nx">Enums</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">Enums</span><span class="p">,</span> <span class="nf">collectNestedEnums</span><span class="p">(</span><span class="nx">message</span><span class="p">)</span><span class="o">...</span><span class="p">)</span> 
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span></code></pre></div><p>Since messages can contain other messages, we need to recursively check them and collect enums.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">collectNestedEnums</span><span class="p">(</span><span class="nx">message</span> <span class="o">*</span><span class="nx">protogen</span><span class="p">.</span><span class="nx">Message</span><span class="p">)</span> <span class="p">[]</span><span class="nx">EnumData</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">enums</span> <span class="p">[]</span><span class="nx">EnumData</span> <span class="c1">// Direct enums in this message.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	
</span></span><span class="line"><span class="cl">	<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">enum</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">message</span><span class="p">.</span><span class="nx">Enums</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">		<span class="nx">enums</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">enums</span><span class="p">,</span> <span class="nx">EnumData</span><span class="p">{</span><span class="nx">Name</span><span class="p">:</span> <span class="nx">enum</span><span class="p">.</span><span class="nx">GoIdent</span><span class="p">.</span><span class="nx">GoName</span><span class="p">})</span> 
</span></span><span class="line"><span class="cl">	<span class="p">}</span> 
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="c1">// Recurse into nested messages.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">nestedMessage</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">message</span><span class="p">.</span><span class="nx">Messages</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">		<span class="nx">enums</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">enums</span><span class="p">,</span> <span class="nf">collectNestedEnums</span><span class="p">(</span><span class="nx">nestedMessage</span><span class="p">)</span><span class="o">...</span><span class="p">)</span> 
</span></span><span class="line"><span class="cl">	<span class="p">}</span> 
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">enums</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Once collected, we will use <code>text/template</code> to generate our enum code. This is the template we will use.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">enumScannerTemplate</span> <span class="p">=</span> <span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;enum_scanner&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="s">`
</span></span></span><span class="line"><span class="cl"><span class="s">// Code generated by protoc-gen-enum-wrappers </span><span class="cp">{{</span><span class="na">.Version</span><span class="cp">}}</span><span class="s">. DO NOT EDIT.
</span></span></span><span class="line"><span class="cl"><span class="s">package </span><span class="cp">{{</span><span class="na">.PackageName</span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">import (
</span></span></span><span class="line"><span class="cl"><span class="s">    &#34;database/sql/driver&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">)
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s"></span><span class="cp">{{</span><span class="k">range</span><span class="w"> </span><span class="na">.Enums</span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">// Value implements driver.Valuer for database storage.
</span></span></span><span class="line"><span class="cl"><span class="s">func (e </span><span class="cp">{{</span><span class="na">.Name</span><span class="cp">}}</span><span class="s">) Value() (driver.Value, error) {
</span></span></span><span class="line"><span class="cl"><span class="s">    if stringVal, present := </span><span class="cp">{{</span><span class="na">.Name</span><span class="cp">}}</span><span class="s">_name[e]; present {
</span></span></span><span class="line"><span class="cl"><span class="s">		return stringVal, nil
</span></span></span><span class="line"><span class="cl"><span class="s">	}
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">	return nil, fmt.Errorf(&#34;%d is not a valid value&#34;, e)
</span></span></span><span class="line"><span class="cl"><span class="s">}
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">// Scan implements sql.Scanner for database retrieval.
</span></span></span><span class="line"><span class="cl"><span class="s">func (e *</span><span class="cp">{{</span><span class="na">.Name</span><span class="cp">}}</span><span class="s">) Scan(src any) error {
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">	// No point in trying to scan a nil value.
</span></span></span><span class="line"><span class="cl"><span class="s">	if src == nil {
</span></span></span><span class="line"><span class="cl"><span class="s">		return nil
</span></span></span><span class="line"><span class="cl"><span class="s">	}
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">    asString, cast := src.(string)
</span></span></span><span class="line"><span class="cl"><span class="s">	if !cast {
</span></span></span><span class="line"><span class="cl"><span class="s">		return fmt.Errorf(&#34;%v is not a valid string&#34;, src)
</span></span></span><span class="line"><span class="cl"><span class="s">	}
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">	int32Val, present := </span><span class="cp">{{</span><span class="na">.Name</span><span class="cp">}}</span><span class="s">_value[asString]
</span></span></span><span class="line"><span class="cl"><span class="s">	if !present {
</span></span></span><span class="line"><span class="cl"><span class="s">		return fmt.Errorf(&#34;%s is not a valid value&#34;, asString)
</span></span></span><span class="line"><span class="cl"><span class="s">	}
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">	*e = int32Val
</span></span></span><span class="line"><span class="cl"><span class="s">	return nil
</span></span></span><span class="line"><span class="cl"><span class="s">}
</span></span></span><span class="line"><span class="cl"><span class="s"></span><span class="cp">{{</span><span class="k">end</span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">`</span><span class="p">))</span>
</span></span></code></pre></div><p><code>{{range .Enums}}</code> helps us loop over our collected enums, and then it&rsquo;s a matter of inserting the enum names in the right places.</p>
<p>The final part consists of executing our template and generating a file to store the results.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">filename</span> <span class="o">:=</span> <span class="nx">file</span><span class="p">.</span><span class="nx">GeneratedFilenamePrefix</span> <span class="o">+</span> <span class="s">&#34;_enum_scanners.pb.go&#34;</span> 
</span></span><span class="line"><span class="cl"><span class="nx">g</span> <span class="o">:=</span> <span class="nx">gen</span><span class="p">.</span><span class="nf">NewGeneratedFile</span><span class="p">(</span><span class="nx">filename</span><span class="p">,</span> <span class="nx">file</span><span class="p">.</span><span class="nx">GoImportPath</span><span class="p">)</span>
</span></span></code></pre></div><p>The <code>GeneratedFilenamePrefix</code> comes from the original <code>.proto</code> filename. For <code>user.proto</code>, this creates <code>user_enum_scanners.pb.go</code>. The <code>.pb.go</code> suffix follows protobuf conventions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">buf</span> <span class="nx">bytes</span><span class="p">.</span><span class="nx">Buffer</span> 
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">enumScannerTemplate</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">buf</span><span class="p">,</span> <span class="nx">data</span><span class="p">);</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">	<span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">g</span><span class="p">.</span><span class="nf">P</span><span class="p">(</span><span class="nx">buf</span><span class="p">.</span><span class="nf">String</span><span class="p">())</span>
</span></span></code></pre></div><p><code>g.P()</code> writes lines to the generated file.</p>
<p>And with that we&rsquo;ve written our plugin to implement valuer/scanner on enums. The <code>google.golang.org/protobuf/compiler/protogen</code> package takes care of the rest, converting our output to a <a href="https://github.com/protocolbuffers/protobuf/blob/1016f1b1a1a3db9425ceaba620e2dd667f0f4de4/src/google/protobuf/compiler/plugin.proto#L83">CodeGeneratorResponse</a>, that is then read back by <code>protoc</code>.</p>
<p>Going back to our example:</p>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>email</th>
<th>type</th>
</tr>
</thead>
<tbody>
<tr>
<td>dwedwedwdwd</td>
<td>Shaunion</td>
<td><a href="mailto:Shaunion@Onion.on">Shaunion@Onion.on</a></td>
<td>SUPER</td>
</tr>
<tr>
<td>Much more readable!</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
]]></description></item><item><title>Alexa, when did baby last pee?</title><link>https://badgerbadgerbadgerbadger.dev/posts/automation/2025-04-04-hey-baby-when-did-baby-last-pee/</link><pubDate>Fri, 04 Apr 2025 00:00:00 +0000</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/automation/2025-04-04-hey-baby-when-did-baby-last-pee/</guid><description><![CDATA[<p>People have complicated feelings about AI Coding assistants. I won&rsquo;t wade into that debate. For me, personally, while I enjoy learning about and playing with software, I also enjoy achieving end-goals. For this particular project, <a href="https://claude.ai/">Claude</a> has been a boon. The reality of being the father to a nearly one-year old is that I no longer get a lot of time for my hobbies. But with Claude, I was able to do in days what might have taken me weeks.</p>
<p>When we first found out we were having our baby boy, I was overjoyed. The terrifying reality of parenthood did not set in until I held my child in my arms for the first time.</p>
<p>Those first few days were the hardest. The first few weeks were all but a blur, and the first few months had us sleep deprived and somewhat cranky. All normal things, I am told by many a parent.</p>
<p>It was not until recently that I have become comfortable enough looking after my child and family that I am able to let my brain wander into its more hobbyish moods. And when it did, it decided to start solving some real world problems, though I leave it up to you to decide if the returns are worth the trouble.</p>
<p>We have been using one of the many baby tracking apps on the market to keep track of when we feed our baby, when he sleeps, how often he poops, and other beautiful bodily functions. It&rsquo;s a simple app in its engineering, but exquisite in its interface and UX.</p>
<p>It maintains a &ldquo;baby&rdquo; as an entity for which it records various activities: food, bottle, sleep, etc. Some activities like &ldquo;bottle&rdquo; or &ldquo;diaper&rdquo; which are points in time have a time field for when they occurred. Activities like &ldquo;sleep&rdquo; have a start and end time. Activities can also have groups (such as &ldquo;fruit&rdquo; or &ldquo;vegetables&rdquo; for the &ldquo;food&rdquo; activity), and all activities can have a &ldquo;notes&rdquo; field. I and my partner&rsquo;s user accounts are added to the baby as &ldquo;caregivers.&rdquo; When we update an activity on our phones, it syncs to both devices with detaials on who added the record.</p>
<p>It&rsquo;s a system that has worked well for us and one we&rsquo;ve used for months now. Recently, I wanted it to work even better (or I got lazier?).</p>
<p>We have Google nest devices scattered around the house. We use them to play music, play white noise for when baby sleeps, control smart lights, learn about the weather, all the usual smart home stuff. So I asked myself: wouldn&rsquo;t it be nice if it could answer questions like &ldquo;when did baby last have a bottle?&rdquo;, &ldquo;how long was baby&rsquo;s last nap?&rdquo;, &ldquo;how much did baby drink last week?&rdquo;.</p>
<p>And thus started my journey.</p>
<h1 id="syncamajiggy">Syncamajiggy</h1>
<p>Since the baby tracking app did not have an API I could access, step one was to figure out how to sync that data to a server I controlled. I don&rsquo;t have any mobile dev experience, but with Claude&rsquo;s help I figured out how to get a rooted Android emulator running on my Mac.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  ~ sdkmanager <span class="s2">&#34;system-images;android-30;google_apis;arm64-v8a&#34;</span>
</span></span></code></pre></div><p>I had downloaded an APK of the tracker app from <a href="https://www.apkmirror.com/">https://www.apkmirror.com/</a> and based on its metadata, Claude informed me this would be a good system image. The command above downloads it to my system.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  ~ avdmanager create avd -n rooted_emulator -k <span class="s2">&#34;system-images;android-30;google_apis;arm64-v8a&#34;</span> -d pixel_4
</span></span></code></pre></div><p>This command creates an <a href="https://developer.android.com/studio/run/managing-avds">Android Virtual Device</a>(AVD) based on a Pixel 4 device definition and the system image I had downloaded earlier. I named it <code>rooted_emulator</code>.</p>
<p>Finally&hellip;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  ~ emulator -avd rooted_emulator -writable-system -no-snapshot -selinux permissive
</span></span></code></pre></div><p>This command launches the virtual device in an emulator making the system partition writable (<code>-writable-system</code>, we&rsquo;ll soon see why that&rsquo;s needed), foregoes saving any state (<code>-no-snapshort</code>) and does not block security policy violations (<code>-selinux permissive</code>).</p>
<p>When it came to installing the APK into the emulator, I had my first surprise. My last brush with anything Android was back in 2011 when life was <em>much</em> simpler. You got a slightly shady APK from somewhere and installed it on your rooted device and enjoyed some fleeting pleasures not readily available on the Play Store.</p>
<p>These days APKs can come in multiple parts! Apparently Google Play can use <a href="https://developer.android.com/guide/app-bundle">Android App Bundles</a> to bundle together parts specific to the device its being downloaded to. The split nature of the APK could account for resources for varying screen sizes and densities, languages, and even dynamic features! Very cool stuff.</p>
<p>I installed the APK using&hellip;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  ~ adb install-multiple base.apk split_config.*.apk
</span></span></code></pre></div><p>Once installed I could launch the app, log into my account and view my data. Now it&rsquo;s time to figure out how this app is talking to the backend.</p>
<p>I used the free version of <a href="https://httptoolkit.com/">HTTP Toolkit</a> a brilliant piece of software that automates the process of intercepting HTTP calls.</p>
<p><img src="/images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/f0997774-7c22-4a90-9116-1783c99af646_hud72aa0abccb73676fdfbb7386747263a_418786_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/f0997774-7c22-4a90-9116-1783c99af646_hud72aa0abccb73676fdfbb7386747263a_418786_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/f0997774-7c22-4a90-9116-1783c99af646_hud72aa0abccb73676fdfbb7386747263a_418786_960x0_resize_q75_h2_box_3.webp 960w, /images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/f0997774-7c22-4a90-9116-1783c99af646_hud72aa0abccb73676fdfbb7386747263a_418786_1600x0_resize_q75_h2_box_3.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="538" loading="lazy" decoding="async" alt="image"></p>
<p>It supports many applications and for Android in particular it has a plethora of options.</p>
<p><img src="/images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/4533cd61-fccb-4e14-8af2-168b690506d8_hua41648012140cfb6d27c6295b373db8f_333774_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/4533cd61-fccb-4e14-8af2-168b690506d8_hua41648012140cfb6d27c6295b373db8f_333774_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/4533cd61-fccb-4e14-8af2-168b690506d8_hua41648012140cfb6d27c6295b373db8f_333774_960x0_resize_q75_h2_box_3.webp 960w, /images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/4533cd61-fccb-4e14-8af2-168b690506d8_hua41648012140cfb6d27c6295b373db8f_333774_1600x0_resize_q75_h2_box_3.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="539" loading="lazy" decoding="async" alt="image"></p>
<p>For me, it was as easy as choosing <code>Android Device via ADB</code> (which becomes ungrayed when it detects an active device via <a href="https://developer.android.com/tools/adb"><code>adb</code></a>). The program requires a rooted device with a writable system. This is because in order to successfully decrypt HTTPS traffic it needs to inject an SSL certificate as a <a href="https://medium.com/hackers-secrets/adding-a-certificate-to-android-system-trust-store-ae8ca3519a85">system level certificate</a>.</p>
<p>Let me explain.</p>
<p>Android phones (devices?) support configuring a proxy server. While that allows us to intercept the traffic from the apps, it&rsquo;s not much use since pretty much all traffic these days is encrypted via <a href="https://www.cloudflare.com/en-gb/learning/ssl/what-is-ssl/">SSL certificates</a>.</p>
<p>HTTP Toolkit solves this by injecting its own certificate into the system certificate store of the device. It has to be on the system level since most apps don&rsquo;t support the <a href="https://support.google.com/pixelphone/answer/2844832?hl=en">user certificate store</a>. And for this we need a rooted, writable system.</p>
<p>A rooted emulator with a writable system partition solves this. So with that&hellip;</p>
<p><img src="/images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/57406026-b424-48b1-b04d-764f7ec85ddd_hu25db38621b5a055785dc729248343107_581247_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/57406026-b424-48b1-b04d-764f7ec85ddd_hu25db38621b5a055785dc729248343107_581247_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/57406026-b424-48b1-b04d-764f7ec85ddd_hu25db38621b5a055785dc729248343107_581247_960x0_resize_q75_h2_box_3.webp 960w, /images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/57406026-b424-48b1-b04d-764f7ec85ddd_hu25db38621b5a055785dc729248343107_581247_1600x0_resize_q75_h2_box_3.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="539" loading="lazy" decoding="async" alt="image"></p>
<p>&hellip; we have <del>inception</del> interception!</p>
<p>It took me a while to figure out which requests were from the baby tracker app. HTTP Toolkit intercepts all traffic from the device and I don&rsquo;t know if there&rsquo;s a way to filter down to a single app.</p>
<p>With trial and error I found the requests relevant to my quest. After some false starts I discovered the tracker app was talking to a <a href="https://firebase.google.com/docs/storage">Firebase Cloud Storage</a> system. The crucial request is <code>https://firestore.googleapis.com/google.firestore.v1.Firestore/Listen</code> which subscribes to updates from collections (which are analogous to SQL tables). This was what kept the app in sync with event updates and what I had to replicate on the server side.</p>
<p><img src="/images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/901235aa-6655-4da7-bdc0-cc9463bb1eb1_hu39c97b4bd43640d5587219197429c2ec_361573_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/901235aa-6655-4da7-bdc0-cc9463bb1eb1_hu39c97b4bd43640d5587219197429c2ec_361573_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/901235aa-6655-4da7-bdc0-cc9463bb1eb1_hu39c97b4bd43640d5587219197429c2ec_361573_960x0_resize_q75_h2_box_3.webp 960w, /images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/901235aa-6655-4da7-bdc0-cc9463bb1eb1_hu39c97b4bd43640d5587219197429c2ec_361573_1600x0_resize_q75_h2_box_3.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="540" loading="lazy" decoding="async" alt="image"></p>
<p>Using the <a href="https://pkg.go.dev/cloud.google.com/go/firestore">cloud.google.com/go/firestore</a> Golang module, and a generous amount of guidance from Claude, I was able to build a simple backend server that keeps listening to the specific collection that records baby&rsquo;s daily events and syncs them to a locally hosted postgres server.</p>
<p>The next step was to hook this up to a voice assistant.</p>
<h1 id="dialog-what">Dialog what?</h1>
<p>Trying to figure out how do this with a Google Nest device was frustrating. Their <a href="https://dialogflow.cloud.google.com">DialogFlow</a> thing seems to be deprecated (or not well supported?), their Conversational Agents thing does not seem to work with Nest devices, and navigating their myriad of products and technical jargon was giving me a headache.</p>
<p>It&rsquo;s a problem I bashed my skull against for a while. I even considered somehow sending the commands from the Nest to my HomeAssistant instance and using HA&rsquo;s voice tech. But that just felt <em>convoluted</em>. I kept having this feeling that this shouldn&rsquo;t be <em>this</em> hard.</p>
<p>And then I remembered why I was having this feeling. I had once built a very simple <a href="https://www.amazon.com/alexa-skills/b?ie=UTF8&amp;node=13727921011">Skill</a> for an old Amazon Echo device. Although I remembered nothing about the process, I remembered it being relatively painless. I also remembered that the Amazon Echo ecosystem supports a Skills marketplace, something the Google Home ecosystem doesn&rsquo;t seem to. At least not without pain.</p>
<p>It led me to unearthing my old Amazon Echo (1st or 2nd gen, I believe). Building a basic skill that sent a request to my server and replied with a nicely formatted string took less than 2 hours. The ease of use compared to Google&rsquo;s nightmare was so stark that I nearly wept.</p>
<p>After that, there was no looking back.</p>
<h1 id="be-intentional">Be Intentional</h1>
<p>Alexa skills are built around the concept of <a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/create-intents-utterances-and-slots.html">&ldquo;intents&rdquo;</a> - essentially things users want to accomplish. When we create a skill, we define these intents and provide sample utterances that might trigger them. The model reminded me of pattern matching in a language like Rust or Elixir.</p>
<p>The lifecycle of an Alexa request looks something like this:</p>
<ol>
<li>User says: &ldquo;Alexa, ask hey baby when was baby&rsquo;s last bottle?&rdquo;.</li>
<li>Alexa identifies &ldquo;hey baby&rdquo; as my skill&rsquo;s invocation name.</li>
<li>It matches &ldquo;when was baby&rsquo;s last bottle&rdquo; to my <code>LastActionIntent</code> with the slot <code>actionType</code> set to &ldquo;bottle&rdquo; (more on Slots below).</li>
<li>My backend receives this intent and slot data as a JSON payload.</li>
<li>My code queries the database and returns a response.</li>
<li>Alexa speaks the response to the user.</li>
</ol>
<blockquote>
<p>Since I am not planning on publishing this Skill to the Alexa marketplace (or whatever it&rsquo;s called, I can&rsquo;t say: &ldquo;Alexa, when was baby&rsquo;s last bottle?&rdquo;. That&rsquo;s something that can be achieved with [Name-free interactions])(<a href="https://developer.amazon.com/en-US/docs/alexa/custom-skills/understand-name-free-interaction-for-custom-skills.html)">https://developer.amazon.com/en-US/docs/alexa/custom-skills/understand-name-free-interaction-for-custom-skills.html)</a>, which are only available for published skills. Name-free interactions trigger Alexa to look for the skill that best matches the utterance and use that to process the request.</p>
</blockquote>
<p>Looking at the JSON I&rsquo;ve defined in my interaction model (which is just a fancy way of saying &ldquo;the configuration of my skill&rdquo;), you can see I&rsquo;ve focused on a few key use cases.</p>
<p>For example, one of the most common questions we have is about the last time our baby had a bottle:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;LastActionIntent&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;slots&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;actionType&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;actionType&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;samples&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;last {actionType}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;when did baby last have {actionType}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;when was baby&#39;s last {actionType}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;when was the last {actionType}&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This intent captures various ways we might ask about the last occurrence of an action. The <code>actionType</code> slot can be filled with values like &ldquo;bottle&rdquo;, &ldquo;diaper&rdquo;, &ldquo;nap&rdquo;, etc. I&rsquo;ve defined these in a custom slot type:</p>
<blockquote>
<p>Slots in Alexa skills function like variables in programming - they&rsquo;re placeholders for dynamic values that users provide when they speak. We can define our own slot types (like <code>actionType</code> above) with a specific set of valid values and synonyms, similar to enums in programming. Amazon also provides built-in slot types like <code>AMAZON.DATE</code>, <code>AMAZON.NUMBER</code>, and <code>AMAZON.TIME</code> that handle common data formats and natural language variations without us having to implement them ourselves.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;actionType&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;values&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;value&#34;</span><span class="p">:</span> <span class="s2">&#34;bottle&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;synonyms&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;drink&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;formula&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Beyond just knowing when things happened, we also wanted to track quantities like how much formula was consumed:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;LastBottleVolumeIntent&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;slots&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;samples&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;how many milliliters was baby&#39;s last bottle&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;what was the volume of baby&#39;s last bottle&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;how much was baby&#39;s last bottle&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;last bottle volume&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;how big was the last bottle&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And we can get quite specific with stats over time periods:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;BottleStatsIntent&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;slots&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;bottleMetric&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;bottleMetricType&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;datePeriod&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;AMAZON.DATE&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;samples&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;how {bottleMetric} did baby drink {datePeriod}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;how {bottleMetric} did baby have in bottles {datePeriod}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;how {bottleMetric} formula did baby have {datePeriod}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;how {bottleMetric} did baby drink&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;how {bottleMetric} from bottles&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The <code>bottleMetricType</code> slot configuration.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;bottleMetricType&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;values&#34;</span><span class="p">:</span> <span class="p">[</span>  
</span></span><span class="line"><span class="cl">        <span class="p">{</span>  
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;value&#34;</span><span class="p">:</span> <span class="s2">&#34;bottles&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;synonyms&#34;</span><span class="p">:</span> <span class="p">[</span>  
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;many bottles&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;number of bottles&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;times&#34;</span>  
</span></span><span class="line"><span class="cl">                <span class="p">]</span>  
</span></span><span class="line"><span class="cl">            <span class="p">}</span>  
</span></span><span class="line"><span class="cl">        <span class="p">},</span>  
</span></span><span class="line"><span class="cl">        <span class="p">{</span>  
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;value&#34;</span><span class="p">:</span> <span class="s2">&#34;ml&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;synonyms&#34;</span><span class="p">:</span> <span class="p">[</span>  
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;milliliters&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;volume&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;much&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;quantity&#34;</span>  
</span></span><span class="line"><span class="cl">                <span class="p">]</span>  
</span></span><span class="line"><span class="cl">            <span class="p">}</span>  
</span></span><span class="line"><span class="cl">        <span class="p">},</span>  
</span></span><span class="line"><span class="cl">        <span class="p">{</span>  
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;value&#34;</span><span class="p">:</span> <span class="s2">&#34;total&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;synonyms&#34;</span><span class="p">:</span> <span class="p">[</span>  
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;all&#34;</span>  
</span></span><span class="line"><span class="cl">                <span class="p">]</span>  
</span></span><span class="line"><span class="cl">            <span class="p">}</span>  
</span></span><span class="line"><span class="cl">        <span class="p">}</span>  
</span></span><span class="line"><span class="cl">    <span class="p">]</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I found myself being quite thorough with the sample utterances. Unlike traditional UI design where you know exactly what controls are available to users, voice interfaces are much more open-ended. I don&rsquo;t always know how I am going to phrase my question when I get around to asking.</p>
<p>On the backend side, handling these intents is straightforward. I built a small Go server that receives requests from Alexa, maps the intents to database queries, and returns formatted responses. The handler for the <code>LastActionIntent</code> looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">handleLastActionIntent</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">request</span> <span class="nx">alexa</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span> <span class="p">(</span><span class="nx">alexa</span><span class="p">.</span><span class="nx">Response</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">actionType</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">request</span><span class="p">.</span><span class="nf">GetSlotValue</span><span class="p">(</span><span class="s">&#34;actionType&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">Wrap</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="s">&#34;failed to get action type&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// Query the database for the last entry of the given action type
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">entry</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">db</span><span class="p">.</span><span class="nf">GetLastEntry</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">actionType</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">Wrap</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="s">&#34;failed to get last entry&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// Format the response
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">timeAgo</span> <span class="o">:=</span> <span class="nx">humanize</span><span class="p">.</span><span class="nf">Time</span><span class="p">(</span><span class="nx">entry</span><span class="p">.</span><span class="nx">Timestamp</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">text</span> <span class="o">:=</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;Baby&#39;s last %s was %s&#34;</span><span class="p">,</span> <span class="nx">actionType</span><span class="p">,</span> <span class="nx">timeAgo</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">alexa</span><span class="p">.</span><span class="nf">NewSimpleResponse</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;Last %s&#34;</span><span class="p">,</span> <span class="nx">actionType</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="nx">text</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="p">),</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>When the request comes in, I extract the slot value, query my database, and format a human-friendly response like &ldquo;Baby&rsquo;s last bottle was 2 hours ago.&rdquo; The Alexa SDK handles converting this text response to speech.</p>
<p>You might have noticed that slot values can have synonyms. In my code I map these to their canonical value before converting them into a query. This is made easy because for any slot synonym the Alexa request includes the canonical value.</p>
<p>Alexa&rsquo;s built-in slot types impressed me. The <code>AMAZON.DATE</code> type, for example, handles natural language date expressions like &ldquo;yesterday&rdquo; or &ldquo;last week&rdquo; without me having to implement date parsing myself. There are similar built-ins for numbers, durations, and other common data types.</p>
<p>Testing the skill was also fairly painless. The Alexa Developer Console has a simulator that lets you type or speak test commands and see exactly how your skill would respond, including the full JSON of the request and response.</p>
<p><img src="/images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/c23e6634-1bfd-4510-a14d-1758be16ad32_hu1b7ba7299035d9c5b1e78754ba82e679_641750_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/c23e6634-1bfd-4510-a14d-1758be16ad32_hu1b7ba7299035d9c5b1e78754ba82e679_641750_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/c23e6634-1bfd-4510-a14d-1758be16ad32_hu1b7ba7299035d9c5b1e78754ba82e679_641750_960x0_resize_q75_h2_box_3.webp 960w, /images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/c23e6634-1bfd-4510-a14d-1758be16ad32_hu1b7ba7299035d9c5b1e78754ba82e679_641750_1600x0_resize_q75_h2_box_3.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="469" loading="lazy" decoding="async" alt="image"></p>
<h1 id="trust-but-verify">Trust but Verify</h1>
<p>Since this backend exposes the data about my dear baby boy, security wasn&rsquo;t optional. Thankfully, Amazon provides mechanisms to verify that requests are genuinely coming from Alexa and not someone particularly interested in how many diapers I&rsquo;m going through.</p>
<p>Each request sent to the skill&rsquo;s endpoint includes two crucial security elements:</p>
<ol>
<li>A URL pointing to an Amazon certificate.</li>
<li>A cryptographic signature of the request body.</li>
</ol>
<p>The verification process is more complex than I initially expected.</p>
<p>First, we need to validate the certificate URL itself (found in the <code>SignatureCertChainUrl</code> header). Amazon requires it to follow a specific format - it must be HTTPS, hosted on Amazon&rsquo;s S3 servers, and follow a particular path structure. This prevents attackers from pointing to their own certificates.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Simplified example of URL validation
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="p">!</span><span class="nx">strings</span><span class="p">.</span><span class="nf">HasPrefix</span><span class="p">(</span><span class="nx">parsedURL</span><span class="p">.</span><span class="nx">Path</span><span class="p">,</span> <span class="s">&#34;/echo.api/&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;certificate URL path does not start with /echo.api/&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Next comes timestamp validation. Each Alexa request includes a timestamp, and we need to verify it&rsquo;s recent (within 150 seconds). This prevents replay attacks where someone could capture a valid request and resend it later.</p>
<p>The real cryptographic magic happens in the signature verification. We download Amazon&rsquo;s certificate, verify it&rsquo;s valid and specifically issued for Alexa services, and then use it to check the signature against the request body.</p>
<p>For performance reasons, we also cache the certificate. The certificate doesn&rsquo;t change frequently, so there&rsquo;s no need to download it for every request.</p>
<p>Finally, we verify that the skill ID in the request matches the specific skill&rsquo;s ID, ensuring that only that skill can access the backend endpoint.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Verify the application ID
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="nx">requestAppID</span> <span class="o">!=</span> <span class="nx">s</span><span class="p">.</span><span class="nx">config</span><span class="p">.</span><span class="nx">AlexaSkillID</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;Expected skill ID: %s, got: %s&#34;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">                        <span class="nx">s</span><span class="p">.</span><span class="nx">config</span><span class="p">.</span><span class="nx">AlexaSkillID</span><span class="p">,</span> <span class="nx">requestAppID</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And with that&hellip;</p>

<audio controls preload="auto"><source src="https://all-i-see-is-static.netlify.app/audio/babys-last-nap.mp4"></audio>


]]></description><media:thumbnail url="https://badgerbadgerbadgerbadger.dev/images/posts/2025-04-04-hey-baby-when-did-baby-last-pee/ea47af13-87d1-4a57-98ce-4cf3deb640e1_hub857e83e931d8309e87d241017bde613_468040_1200x0_resize_q80_bgffffff_box_3.jpg"/></item><item><title>Exploring How Protobuf OneOfs Are Represented</title><link>https://badgerbadgerbadgerbadger.dev/posts/misc/2024-12-05-oneofs-in-protobuf/</link><pubDate>Thu, 05 Dec 2024 00:00:00 +0100</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/misc/2024-12-05-oneofs-in-protobuf/</guid><description><![CDATA[<p>This is a short exploration of how Protobuf3 OneOf fields are represented using Golang as our exploration medium.</p>
<p>OneOf types, aka <a href="https://en.wikipedia.org/wiki/Tagged_union">Tagged Unions</a> are data structures that are used to hold one of a finite list of distinct types. A variable of a tagged union type can hold a value of one of several types defined for that tagged union type. This might be easier to understand with an example via pseudocode.</p>
<pre tabindex="0"><code>type Media = Movie | Show | Short

var favorite1: Media = Movie{}
var favorite2: Media = Show{}
var favorite3: Media = Short{}

var favorites: [Media] = [favorite1, favorite2, favorite3]
</code></pre><p>Variables <code>favorite1</code>, <code>favorite2</code>, and <code>favorite3</code> all have the data type <code>Media</code> but have values <code>Movie</code>, <code>Show</code> and <code>Short</code>, which works because the <code>Media</code> data type has been defined as a Tagged Union of all 3 other data types. Consequently, the <code>favorites</code> variable, which is defined as an array of <code>Media</code> values, can have values of the 3 defined data types in it.</p>
<p>Crucially, the values all take up the same space in memory and only one can be set at any given time. My knowledge here becomes fuzzy, but typically, the memory allocated for a OneOf corresponds to the largest possible field within it, with additional space for internal bookkeeping.</p>
<p><a href="https://protobuf.dev/"><strong>Protobuf</strong></a> has <a href="https://protobuf.dev/programming-guides/proto3/#oneof">OneOf</a> as its implementation of the Tagged Union data type. A message field can be marked as <code>oneof</code> with the variants specified.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="kd">message</span> <span class="nc">Event</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="k">oneof</span> <span class="n">media</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>		<span class="n">Movie</span> <span class="n">movie</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>		<span class="n">Show</span> <span class="n">show</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>		<span class="n">Short</span> <span class="n">short</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="kt">int64</span> <span class="n">timestamp</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="k">optional</span> <span class="kt">bool</span> <span class="n">had_fun</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">Movie</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="kt">string</span> <span class="n">title</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="kt">string</span> <span class="n">director</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">Show</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="kt">string</span> <span class="n">title</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="kt">string</span> <span class="n">runner</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">Short</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="kt">string</span> <span class="n">title</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="kt">string</span> <span class="n">shorty</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="c1">// I couldn&#39;t think of something unique, here.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span><span class="err">
</span></span></span></code></pre></div><p>Protobuf allows assigning only one of the 3 fields out of <code>movie</code>, <code>show</code> and <code>short</code>, and setting one automatically clears any of the others set, ensuring that memory for only one of the types gets consumed.</p>
<p>Protobuf&rsquo;s <a href="https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/descriptor.proto"><code>descriptor.proto</code></a> is a protobuf file that describes the structure of protobuf files. It hurts my head a little to think about it.</p>
<p>Let&rsquo;s inspect the <a href="https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/descriptor.proto#L134"><code>DescriptorProto</code></a> message, which is the schema for a protobuf <a href="https://protobuf.dev/programming-guides/proto3/#simple">Message</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="c1">// Describes a message type.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">message</span> <span class="nc">DescriptorProto</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">optional</span> <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">repeated</span> <span class="n">FieldDescriptorProto</span> <span class="n">field</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="o">...</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="o">...</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">repeated</span> <span class="n">OneofDescriptorProto</span> <span class="n">oneof_decl</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="o">...</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span></code></pre></div><p>Let&rsquo;s look at the relevant parts of <code>FieldDescriptorProto</code> and <code>OneOfDescriptorProto</code> and then we&rsquo;ll talk about them.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="c1">// Describes a field within a message.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">message</span> <span class="nc">FieldDescriptorProto</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="o">...</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="c1">// If set, gives the index of a oneof in the containing type&#39;s oneof_decl
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// list.  This field is a member of that oneof.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="k">optional</span> <span class="kt">int32</span> <span class="n">oneof_index</span> <span class="o">=</span> <span class="mi">9</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="o">...</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>	<span class="c1">// If true, this is a proto3 &#34;optional&#34;. When a proto3 field is optional, it
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// tracks presence regardless of field type.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// When proto3_optional is true, this field must belong to a oneof to signal
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// to old proto3 clients that presence is tracked for this field. This oneof
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// is known as a &#34;synthetic&#34; oneof, and this field must be its sole member
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// (each proto3 optional field gets its own synthetic oneof). Synthetic oneofs
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// exist in the descriptor only, and do not generate any API. Synthetic oneofs
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// must be ordered after all &#34;real&#34; oneofs.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// For message fields, proto3_optional doesn&#39;t create any semantic change,
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// since non-repeated message fields always track presence. However it still
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// indicates the semantic detail of whether the user wrote &#34;optional&#34; or not.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// This can be useful for round-tripping the .proto file. For consistency we
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// give message fields a synthetic oneof also, even though it is not required
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// to track presence. This is especially important because the parser can&#39;t
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// tell if a field is a message or an enum, so it must always create a
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// synthetic oneof.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// Proto2 optional fields do not set this flag, because they already indicate
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// optional with `LABEL_OPTIONAL`.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="k">optional</span> <span class="kt">bool</span> <span class="n">proto3_optional</span> <span class="o">=</span> <span class="mi">17</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="c1">// Describes a oneof.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">message</span> <span class="nc">OneofDescriptorProto</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">optional</span> <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">optional</span> <span class="n">OneofOptions</span> <span class="n">options</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span></code></pre></div><p>I pasted the whole documentation for the <code>proto3_optional</code> field since it has heavy implications on how OneOfs are implemented. <code>OneOfDescriptorProto</code> is relatively simple and for us only the <code>name</code> field matters.</p>
<p>To fully grasp how to detect and make use of OneOf fields, we must understand both how OneOfs are represented and how optional fields are represented.</p>
<blockquote>
<p>Remember that <code>optional bool had_fun</code> field? That wasn&rsquo;t there just for flavor but to demonstrate how optional fields need to be taken into account when making use of OneOfs.</p>
</blockquote>
<p>Looking at <code>DescriptorProto</code> we see a field <code>repeated OneofDescriptorProto oneof_decl = 8;</code>. This field is what lists all OneOf fields in the message. Using our very first example of <code>message Event</code>, <code>oneof_decl</code> would contain 1 value:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">DescriptorProto</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Name</span><span class="p">:</span> <span class="s">&#34;Event&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nx">OneofDecl</span><span class="p">:</span> <span class="p">[]</span><span class="nx">OneofDescriptorProto</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">Name</span><span class="p">:</span> <span class="s">&#34;media&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">Name</span><span class="p">:</span> <span class="s">&#34;_had_fun&#34;</span> <span class="c1">// Keep an eye on this one. We&#39;ll get back to it.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Wait, what&rsquo;s that <code>_had_fun</code> doing in there? We didn&rsquo;t define that as an OneOf! Keep reading.</p>
<p>The fields of the message would have info on each field:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">DescriptorProto</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Name</span><span class="p">:</span> <span class="s">&#34;Event&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Field</span><span class="p">:</span> <span class="p">[]</span><span class="nx">FieldDescriptorProto</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">Name</span><span class="p">:</span> <span class="s">&#34;movie&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nx">OneOfIndex</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">Name</span><span class="p">:</span> <span class="s">&#34;show&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nx">OneOfIndex</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">Name</span><span class="p">:</span> <span class="s">&#34;short&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nx">OneOfIndex</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">Name</span><span class="p">:</span> <span class="s">&#34;timestamp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nx">OneOfIndex</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">Name</span><span class="p">:</span> <span class="s">&#34;had_fun&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nx">OneOfIndex</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Of interest to is the value of the <code>OneOfIndex</code> property. The description of the property tells us how it works.</p>
<blockquote>
<p>If set, gives the index of an OneOf in the containing type&rsquo;s list. This field is a member of that OneOf.</p>
</blockquote>
<p>That seems straightforward enough. In the <code>Event</code> message, we have two OneOfs listed (even though we did not list the second one, ourselves). And each of the fields tells us whether it is part of one of the OneOfs (indicating by index which OneOf it belongs to) or not (if the value is <code>nil</code>).</p>
<p>As expected, <code>movie</code>, <code>show</code>, and <code>short</code> have the <code>OneOfIndex</code> of 0, part of the OneOf located at the 0th index of the <code>OneofDecl</code> list, <code>media</code>.</p>
<p>But why is <code>had_fun</code> is also a part of an OneOf?</p>
<p>Turns out, because of some backwards compatibility shenanigans, optional fields are also represented as OneOfs, with the two options being (I suppose) either the concrete type or nothing.</p>
<p>So marking <code>had_fun</code> with the <code>optional</code> label causes the descriptor to generate a <em>synthetic</em> OneOf. A OneOf that&rsquo;s not a real OneOf.</p>
<p>When parsing this schema for our own purposes we have to be aware of this synthetic OneOf. We detect <em>real</em> OneOfs by checking if the <code>OneOfIndex</code> is not <code>nil</code> and the <code>Proto3Optional</code> is <code>false</code>. If both conditions are met, we can be sure that the field is part of a real OneOf.</p>
<p>All this was discovered over a frustrating afternoon of reading through documentation and experimenting. It was annoying but also rewarding. Hope you enjoyed the read.</p>
]]></description><media:thumbnail url="https://badgerbadgerbadgerbadger.dev/images/posts/2024-12-05-oneofs-in-protobuf/0f6d39aa-3af9-4691-8e6c-f17b8a84ac00_hua9e4bcdc83b061e62cab34421c56b6d7_174521_1024x0_resize_q80_box.jpg"/></item><item><title>Scraping VRT News Journaal with Puppeteer and Github Actions</title><link>https://badgerbadgerbadgerbadger.dev/posts/misc/2024-10-21-scraping-vrt-journaal/</link><pubDate>Mon, 21 Oct 2024 00:00:00 +0100</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/misc/2024-10-21-scraping-vrt-journaal/</guid><description><![CDATA[<p>My girlfriend works in the field of language and education research and sometimes makes lessons for the online Dutch language learning platform <a href="https://www.nedbox.be/">Nedbox</a>. The lessons are often based around real world events and having a way to _gather interesting real world events is invaluable. She asked me for help listing news events from the show <a href="https://www.vrt.be/vrtmax/a-z/vrt-nws-journaal">VRT NWS journaal</a> on <a href="https://www.vrt.be/vrtmax/">VRT Max</a>.</p>
<p>Their page looks like this.
<img src="/images/posts/2024-10-21-scraping-vrt-journaal/9e969878-fa0c-4748-ae1e-97703f0c0bfe_hud1052259e8c246f5f86bf004b11bf0ad_2321137_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2024-10-21-scraping-vrt-journaal/9e969878-fa0c-4748-ae1e-97703f0c0bfe_hud1052259e8c246f5f86bf004b11bf0ad_2321137_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2024-10-21-scraping-vrt-journaal/9e969878-fa0c-4748-ae1e-97703f0c0bfe_hud1052259e8c246f5f86bf004b11bf0ad_2321137_960x0_resize_q75_h2_box_3.webp 960w, /images/posts/2024-10-21-scraping-vrt-journaal/9e969878-fa0c-4748-ae1e-97703f0c0bfe_hud1052259e8c246f5f86bf004b11bf0ad_2321137_1600x0_resize_q75_h2_box_3.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="529" loading="lazy" decoding="async" alt="image"></p>
<p>Each day there are two episodes, one for the 13h news and another for the 19h news. Clicking through, we can see that each episode has chapters within it with timestamps and titles. It&rsquo;s a list of these titles with links to that chapter (for quick viewing) that my girlfriend was interested in gathering, without having to manually click on each episode, each day, every day.</p>
<p><img src="/images/posts/2024-10-21-scraping-vrt-journaal/d5fe96b9-dadf-42ed-8784-0089f95ea96c_hu50b4995109c7ac2569f419999bb0263f_262916_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2024-10-21-scraping-vrt-journaal/d5fe96b9-dadf-42ed-8784-0089f95ea96c_hu50b4995109c7ac2569f419999bb0263f_262916_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2024-10-21-scraping-vrt-journaal/d5fe96b9-dadf-42ed-8784-0089f95ea96c_hu50b4995109c7ac2569f419999bb0263f_262916_960x0_resize_q75_h2_box_3.webp 960w, /images/posts/2024-10-21-scraping-vrt-journaal/d5fe96b9-dadf-42ed-8784-0089f95ea96c_hu50b4995109c7ac2569f419999bb0263f_262916_1600x0_resize_q75_h2_box_3.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="503" loading="lazy" decoding="async" alt="vrt-news-journaal-episode-page"></p>
<p>I started off by exploring the html source of the landing page to find content already present. There&rsquo;s no need for complicated scraping if I can just grab the relevant html section, perhaps something between <code>&lt;ul&gt;</code> or <code>&lt;ol&gt;</code> tags.</p>
<p>No such luck. The initial document that loads is pretty barebones.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="cp">&lt;!DOCTYPE html&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;nl&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">		<span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;UTF-8&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">		<span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>VRT MAX<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">		<span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span><span class="kd">var</span> <span class="nx">s</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;script&#39;</span><span class="p">);</span> <span class="nx">s</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="s1">&#39;/vrtnu-resources/loaders/scriptLoader.js&#39;</span><span class="p">;</span> <span class="nx">s</span><span class="p">.</span><span class="kr">async</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span> <span class="nb">document</span><span class="p">.</span><span class="nx">head</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">s</span><span class="p">);&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">		<span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;root&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;&lt;</span><span class="nt">noscript</span><span class="p">&gt;</span>U moet JavaScript inschakelen om deze app te gebruiken.<span class="p">&lt;/</span><span class="nt">noscript</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">		<span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="nx">loadBodyScripts</span><span class="p">();&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>I have elided much from the head but the body is as you see it: sparse. It looks as if all the actual content is loaded via Javascript. Not the best design, if you ask me. I&rsquo;m not sure there is enough interactivity on a page like this to justify denying non-Javascript enabled users access to the page.</p>
<p>Okay, so we can&rsquo;t do anything without running Javascript. So let&rsquo;s do something to run Javascript.</p>
<p>I originally wanted to program this in Go since that is my daily driver. But after exploring libraries, I couldn&rsquo;t find anything that beats <a href="https://github.com/puppeteer/puppeteer">puppeteer</a> for the sheer number of features available, not to mention ease of use, wealth of documentation, and example projects.</p>
<p>I started off by inspecting the page after all the Javascript had run and found my content.
<img src="/images/posts/2024-10-21-scraping-vrt-journaal/b3263ca6-8831-4dda-b424-d72e92eda37e_hu2e160991a5d74498256eba19cf2ff765_69729_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2024-10-21-scraping-vrt-journaal/b3263ca6-8831-4dda-b424-d72e92eda37e_hu2e160991a5d74498256eba19cf2ff765_69729_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2024-10-21-scraping-vrt-journaal/b3263ca6-8831-4dda-b424-d72e92eda37e_hu2e160991a5d74498256eba19cf2ff765_69729_960x0_resize_q75_h2_box_3.webp 960w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="663" loading="lazy" decoding="async" alt="vrt-news-journaal-episode-page-html"></p>
<p>After playing around a bit I decided on this CSS selector: <code>#main ul</code>. Short and gets the job done. Time to extract the list.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">async</span> <span class="kd">function</span> <span class="nx">extractEpisodeList</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// Launch the browser
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="kr">const</span> <span class="nx">browser</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">puppeteer</span><span class="p">.</span><span class="nx">launch</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="c1">// Create a new page
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="kr">const</span> <span class="nx">page</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newPage</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="c1">// Navigate to the specified URL and wait for the network to be idle
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="p">{</span> <span class="nx">waitUntil</span><span class="o">:</span> <span class="s1">&#39;networkidle0&#39;</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="c1">// Wait for any client-side rendering to complete
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="kr">await</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(</span><span class="nx">resolve</span> <span class="p">=&gt;</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="mi">2000</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="c1">// Get the fully rendered HTML content
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="kr">const</span> <span class="nx">content</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">content</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="c1">// Extract the list items and their links
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="kr">const</span> <span class="nx">listItems</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">evaluate</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">			<span class="kr">const</span> <span class="nx">list</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;#main ul&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">list</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="k">return</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">list</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">&#39;li&#39;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">				<span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">li</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			
</span></span><span class="line"><span class="cl">					<span class="kr">const</span> <span class="nx">a</span> <span class="o">=</span> <span class="nx">li</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;a&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">					<span class="k">return</span> <span class="nx">a</span> <span class="o">?</span> <span class="p">{</span>				
</span></span><span class="line"><span class="cl">						<span class="nx">href</span><span class="o">:</span> <span class="nx">a</span><span class="p">.</span><span class="nx">getAttribute</span><span class="p">(</span><span class="s1">&#39;href&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">						<span class="nx">content</span><span class="o">:</span> <span class="nx">a</span><span class="p">.</span><span class="nx">getAttribute</span><span class="p">(</span><span class="s1">&#39;aria-label&#39;</span><span class="p">)</span> <span class="o">||</span> <span class="nx">a</span><span class="p">.</span><span class="nx">textContent</span><span class="p">.</span><span class="nx">trim</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">					<span class="p">}</span> <span class="o">:</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">				<span class="p">})</span>
</span></span><span class="line"><span class="cl">				<span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">item</span> <span class="p">=&gt;</span> <span class="nx">item</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">});</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="c1">// Close the browser
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">listItems</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">throw</span> <span class="nx">error</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The code should be pretty self-explanatory. I had to fiddle a bit to find the right query selectors to extract the link and title after I got the list. It&rsquo;s important we use <a href="https://pptr.dev/api/puppeteer.waitforoptions"><code>waitUntil</code></a> when loading the page to ensure <em>most</em> network activity is complete. <a href="https://pptr.dev/api/puppeteer.puppeteerlifecycleevent"><code>networkidle0</code></a> waits for at least 500 ms of no new network connections.</p>
<p>The end result is a list of episodes.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">	<span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;href&#34;</span><span class="p">:</span> <span class="s2">&#34;...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;content&#34;</span><span class="p">:</span> <span class="s2">&#34;...&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><blockquote>
<p>If you spend some time on that page you&rsquo;ll notice the episode list is a sort of carousel within which new episodes get loaded as you scroll horizontally to the right. By default only 13 episodes or so are listed. But this is fine for us since we will check this page every day and we only care about new episodes.</p>
</blockquote>
<p>Before we start grabbing the chapters for each episode, let&rsquo;s talk about a very small optimization. We <em>could</em> scrape the entire list of episodes every day, but given that we have only 2 new episodes each day, we could be re-scraping 11 episodes we have already scraped, every single day. Let&rsquo;s perform a very small optimization step.</p>
<p>We will maintain a list of episodes we have already processed, and each day process only new episodes. This is achieved by this function (which itself calls the function above to grab the episode list).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">async</span> <span class="kd">function</span> <span class="nx">updateEpisodeList</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="kr">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="s1">&#39;https://www.vrt.be/vrtmax/a-z/vrt-nws-journaal/&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="kr">const</span> <span class="nx">renderedContent</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">extractEpisodeList</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="c1">// Read the local file &#34;episode-list.json&#34;
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="kd">let</span> <span class="nx">localEpisodes</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">			<span class="kr">const</span> <span class="nx">rawData</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFile</span><span class="p">(</span><span class="nx">episodeListFilePath</span><span class="p">,</span> <span class="s1">&#39;utf8&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="nx">localEpisodes</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">rawData</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">			<span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s1">&#39;Error reading or parsing local file:&#39;</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="c1">// Compare extracted episodes with local episodes
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="kr">const</span> <span class="nx">extractedSet</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Set</span><span class="p">(</span><span class="nx">renderedContent</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">item</span> <span class="p">=&gt;</span> <span class="nx">item</span><span class="p">.</span><span class="nx">href</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">		<span class="kr">const</span> <span class="nx">localSet</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Set</span><span class="p">(</span><span class="nx">localEpisodes</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">item</span> <span class="p">=&gt;</span> <span class="nx">item</span><span class="p">.</span><span class="nx">href</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="kr">const</span> <span class="nx">newEpisodes</span> <span class="o">=</span> <span class="nx">renderedContent</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">item</span> <span class="p">=&gt;</span> <span class="o">!</span><span class="nx">localSet</span><span class="p">.</span><span class="nx">has</span><span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">href</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="kr">const</span> <span class="nx">removedEpisodes</span> <span class="o">=</span> <span class="nx">localEpisodes</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">item</span> <span class="p">=&gt;</span> <span class="o">!</span><span class="nx">extractedSet</span><span class="p">.</span><span class="nx">has</span><span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">href</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="c1">// Print the diff
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Diff between extracted and local episodes:&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="nx">newEpisodes</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">			<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;\nNew episodes:&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			
</span></span><span class="line"><span class="cl">			<span class="nx">newEpisodes</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">episode</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`- </span><span class="si">${</span><span class="nx">episode</span><span class="p">.</span><span class="nx">content</span><span class="si">}</span><span class="sb"> (</span><span class="si">${</span><span class="nx">episode</span><span class="p">.</span><span class="nx">href</span><span class="si">}</span><span class="sb">)`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="p">});</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="nx">removedEpisodes</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">			<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;\nRemoved episodes:&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			
</span></span><span class="line"><span class="cl">			<span class="nx">removedEpisodes</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">episode</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`- </span><span class="si">${</span><span class="nx">episode</span><span class="p">.</span><span class="nx">content</span><span class="si">}</span><span class="sb"> (</span><span class="si">${</span><span class="nx">episode</span><span class="p">.</span><span class="nx">href</span><span class="si">}</span><span class="sb">)`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="p">});</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="nx">newEpisodes</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="nx">removedEpisodes</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;No differences found.&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">newEpisodes</span><span class="o">:</span> <span class="nx">newEpisodes</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nx">removedEpisodes</span><span class="o">:</span> <span class="nx">removedEpisodes</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nx">localEpisodes</span><span class="o">:</span> <span class="nx">localEpisodes</span>
</span></span><span class="line"><span class="cl">		<span class="p">};</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">throw</span> <span class="nx">error</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Once again, most of the code should be self-explanatory. We read our existing episodes list from a file, compare against the scraped list and generate lists of new episodes (and some extra stuff but those aren&rsquo;t relevant).</p>
<p>Armed with new episodes to scrape, we need to figure how to extract the chapters of the episodes, mainly their title and the timestamp at which they start. And if we can generate a link that will take you to the exact part of the video, even better.</p>
<p>For this I started inspecting the episode page itself. As with the main page, the episode page is sparse and uses JS to load everything, but looking at the XHR calls, I noticed something.</p>
<p><img src="/images/posts/2024-10-21-scraping-vrt-journaal/a7b42447-d434-4dcd-8d12-146f8fdd7f8a_huac15a8bb860c52a165e5c4a758fc7edd_48557_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2024-10-21-scraping-vrt-journaal/a7b42447-d434-4dcd-8d12-146f8fdd7f8a_huac15a8bb860c52a165e5c4a758fc7edd_48557_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2024-10-21-scraping-vrt-journaal/a7b42447-d434-4dcd-8d12-146f8fdd7f8a_huac15a8bb860c52a165e5c4a758fc7edd_48557_960x0_resize_q75_h2_box_3.webp 960w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="287" loading="lazy" decoding="async" alt="vrt-nws-journaal-graphql"></p>
<p>Is that a Graphql endpoint mine eyes spy?! I definitely know how to use one of those.</p>
<p>This made the whole process much simpler. I inspected the Graphql response, found the part with the episode chapters and the timstamps and bundled them up in a message I could send to a Slack channel. I won&rsquo;t show that code here as it&rsquo;s fairly mundane.</p>
<blockquote>
<p>Afterwards I had the thought that perhaps the main page also loaded its episode list via a Graphql call and I did not need to scrape anything. But I already had so no point thinking about it further.</p>
</blockquote>
<p>At this point I had to decide where to run it. This wasn&rsquo;t planned to be a live service, just a script that ran every day so something like Fly.io wasn&rsquo;t necessary. I remembered a tech meetup I had attended earlier in the year at <a href="https://www.theoplayer.com/">Theo Technologies</a> and the talk on <a href="https://youtu.be/kabFMYS0TxY?t=3760">Zero Budget Solution Engineering Toolbox</a> by <a href="https://www.linkedin.com/in/danieldallos/">Daniel Dallos</a>. He had talked about using Github actions as a way to run jobs on a schedule, while at the same time storing small amounts of data in the Github repository as a JSON file. I highly recommend watching the talk as he mentions a lot of useful tricks and tools.</p>
<p>So I whipped up a Github a workflow yaml file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Scrape  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">workflow_dispatch</span><span class="p">:</span><span class="w">  </span><span class="c"># this allows us to manually run this workflow</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schedule</span><span class="p">:</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Runs at 12:00 UTC every day  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">cron</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;0 12 * * *&#39;</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">run-script</span><span class="p">:</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Step 1: Check out the repository  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Checkout repository  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v3  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Step 2: Set up Node.js (adjust version as needed)  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Set up Node.js  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-node@v3  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">node-version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;20&#39;</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Step 3: Cache npm dependencies  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Cache dependencies  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v3  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">~/.npm  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${{ runner.OS }}-node-${{ hashFiles(&#39;**/package-lock.json&#39;) }}  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">restore-keys</span><span class="p">:</span><span class="w"> </span><span class="l">|  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="l">${{ runner.OS }}-node-  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Step 4: Install dependencies (if you have a package.json)  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Install dependencies  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">npm ci  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Step 5: Run your JavaScript script  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Run the JavaScript script  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">node scrape.js  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Step 6: Commit and push changes back to the repository  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Commit and push changes  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">|  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="l">git config --global user.name &#39;github-actions[bot]&#39;  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="l">git config --global user.email &#39;github-actions[bot]@users.noreply.github.com&#39;  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="l">git add .  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="l">git diff --quiet &amp;&amp; git diff --staged --quiet || (git commit -m &#34;Update results from scheduled script&#34; &amp;&amp; git push)  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">env</span><span class="p">:</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="c"># Ensure you have write access to the repository via a GitHub token  </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w">
</span></span></span></code></pre></div><p>There is some stuff in there about loading NodeJS, cacheing dependencies and whatnot, but the meat of the code is in Steps 5 and 6 where we run our scraping and then commit the results back to the repository.</p>
<p>And with that I had a tool that would let me know every day about the episodes of the VRT NWS Journaal.</p>
<p><img src="/images/posts/2024-10-21-scraping-vrt-journaal/90a280d1-6eb9-4ab6-9d6d-23167fcf8250_hu023ad3715ae3cb34d3e02617dc17051f_135758_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2024-10-21-scraping-vrt-journaal/90a280d1-6eb9-4ab6-9d6d-23167fcf8250_hu023ad3715ae3cb34d3e02617dc17051f_135758_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2024-10-21-scraping-vrt-journaal/90a280d1-6eb9-4ab6-9d6d-23167fcf8250_hu023ad3715ae3cb34d3e02617dc17051f_135758_960x0_resize_q75_h2_box_3.webp 960w, /images/posts/2024-10-21-scraping-vrt-journaal/90a280d1-6eb9-4ab6-9d6d-23167fcf8250_hu023ad3715ae3cb34d3e02617dc17051f_135758_1600x0_resize_q75_h2_box_3.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="703" loading="lazy" decoding="async" alt="vrt-nws-journaal-slack-message"></p>
<p>Hope you enjoyed the read.</p>
]]></description><media:thumbnail url="https://badgerbadgerbadgerbadger.dev/images/posts/2024-10-21-scraping-vrt-journaal/11722f4d-01c2-4db0-bed2-e7a3a336c10a_hu05d3a3fb59d611d3ae30fd23338ecb32_631420_1200x0_resize_q80_box.jpg"/></item><item><title>Protopath Problems in Go</title><link>https://badgerbadgerbadgerbadger.dev/posts/misc/2024-09-11-protopath-problems-in-go/</link><pubDate>Wed, 11 Sep 2024 00:00:00 +0100</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/misc/2024-09-11-protopath-problems-in-go/</guid><description><![CDATA[<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">Failed to compute <span class="nb">set</span> of methods to expose - Symbol not found
</span></span></code></pre></div><p>This cryptic error has reared its ugly head a few times in the past when working with <a href="https://grpc.io/">grpc</a> <a href="https://github.com/golang/protobuf">in</a> <a href="https://go.dev/">Golang</a>. I can&rsquo;t remember how I&rsquo;ve solved it before. Probably with some combination of stackoverflow answers and random github gists with advice that I have now forgotten. When it happened most recently, I decided to try to actually understand what was going on under the hood.</p>
<blockquote>
<p>Note: For the purposes of this post I am using Golang v1.22.3, <a href="https://github.com/protocolbuffers/protobuf">libprotoc</a> 3.21.12, <a href="https://pkg.go.dev/google.golang.org/grpc">google.golang.org/grpc</a> v1.63.2, and <a href="https://pkg.go.dev/google.golang.org/protobuf">google.golang.org/protobuf</a> v1.34.1.</p>
</blockquote>
<p>I had gone a long time without encountering this error, only stumbling upon on it again while using the <a href="https://github.com/bufbuild/protovalidate">protovalidate</a> library for validating protobuf messages. Used in an <a href="https://grpc.io/docs/guides/interceptors/">interceptor</a> it provides a way to validate input messages on gRPCs.</p>
<p>According to the <a href="https://github.com/bufbuild/protovalidate?tab=readme-ov-file#usage">docs</a>, I should use the <code>validate.proto</code> files by importing them into my project and referencing them in my primary proto files.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="n">syntax</span> <span class="o">=</span> <span class="s">&#34;proto3&#34;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">import</span> <span class="s">&#34;buf/validate/validate.proto&#34;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">User</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="c1">// User&#39;s name, must be at least 1 character long.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="mi">1</span> <span class="p">[(</span><span class="n">buf.validate.field</span><span class="p">)</span><span class="o">.</span><span class="kt">string</span><span class="o">.</span><span class="n">min_len</span> <span class="o">=</span> <span class="mi">1</span><span class="p">];</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span></code></pre></div><p>I am not a fan of having to add files under arbitrary directory structures so I did not store my <code>validate.proto</code> (and <code>expression.proto</code> and some others that are dependencies of those two) under the <code>buf</code> directory. I stored them under the <code>validate</code> directory. This is what things looked like.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">├── services.proto
</span></span><span class="line"><span class="cl">└── validate
</span></span><span class="line"><span class="cl">    ├── expression.proto
</span></span><span class="line"><span class="cl">    ├── priv
</span></span><span class="line"><span class="cl">    │   └── private.proto
</span></span><span class="line"><span class="cl">    └── validate.proto
</span></span></code></pre></div><p><code>services.proto</code> being my primary proto file of interest. It imported <code>validate.proto</code> like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="k">import</span> <span class="s">&#34;validate/validate.proto&#34;</span><span class="p">;</span><span class="err">
</span></span></span></code></pre></div><p>My generation script does not generate any code from <code>validate.proto</code>, since we&rsquo;re using the schema for its options only, that then get baked into the output for my <code>services.proto</code> file.</p>
<p>For performing the validations on these baked, generated options, I&rsquo;m using <a href="https://github.com/bufbuild/protovalidate-go">github.com/bufbuild/protovalidate-go</a>. Code example from its github repo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;fmt&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;time&#34;</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="nx">pb</span> <span class="s">&#34;github.com/path/to/generated/protos&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/bufbuild/protovalidate-go&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;google.golang.org/protobuf/types/known/timestamppb&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">msg</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="nx">pb</span><span class="p">.</span><span class="nx">Transaction</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Id</span><span class="p">:</span>           <span class="mi">1234</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Price</span><span class="p">:</span>        <span class="s">&#34;$5.67&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">PurchaseDate</span><span class="p">:</span> <span class="nx">timestamppb</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nf">Now</span><span class="p">()),</span>
</span></span><span class="line"><span class="cl">		<span class="nx">DeliveryDate</span><span class="p">:</span> <span class="nx">timestamppb</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nf">Now</span><span class="p">().</span><span class="nf">Add</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Hour</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">v</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">protovalidate</span><span class="p">.</span><span class="nf">New</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;failed to initialize validator:&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="p">=</span> <span class="nx">v</span><span class="p">.</span><span class="nf">Validate</span><span class="p">(</span><span class="nx">msg</span><span class="p">);</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;validation failed:&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;validation succeeded&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Compiling the protobuf into Golang works: the gRPC server registers the RPC server defined in the schema and my program runs. If I call any of the RPCs, it works fine.</p>
<p>It&rsquo;s when I try to use the reflection server is when we run into trouble. I&rsquo;ve built a sample project to demonstrate this over at <a href="https://github.com/BadgerBadgerBadgerBadger/protoreflect-error-test/tree/main/error-state">https://github.com/BadgerBadgerBadgerBadger/protoreflect-error-test/tree/main/error-state</a>.</p>
<p>Our <code>proto</code> folder looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── dependencies
</span></span><span class="line"><span class="cl">│   └── secondary
</span></span><span class="line"><span class="cl">│       └── secondary.proto
</span></span><span class="line"><span class="cl">├── gen.sh
</span></span><span class="line"><span class="cl">└── primary.proto
</span></span></code></pre></div><p>Our <code>gen.sh</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">set</span> -e<span class="p">;</span>  
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="c1"># Get the directory where the script is located  </span>
</span></span><span class="line"><span class="cl"><span class="nv">scriptDir</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span> <span class="nb">cd</span> -- <span class="s2">&#34;</span><span class="k">$(</span> dirname -- <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASH_SOURCE</span><span class="p">[0]</span><span class="k">:-</span><span class="nv">$0</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">)</span><span class="s2">&#34;</span> <span class="p">&amp;</span>&gt; /dev/null <span class="o">&amp;&amp;</span> <span class="nb">pwd</span> 2&gt; /dev/null<span class="p">;</span> <span class="k">)</span><span class="s2">&#34;</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;script directory: </span><span class="nv">$scriptDir</span><span class="s2">&#34;</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ensure output directory exists</span>
</span></span><span class="line"><span class="cl"><span class="nv">outDir</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$scriptDir</span><span class="s2">/lang_go&#34;</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl">mkdir -p <span class="nv">$outDir</span>/primary<span class="p">;</span>  
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># build the secondary proto</span>
</span></span><span class="line"><span class="cl">protoc -I<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$scriptDir</span><span class="s2">/dependencies&#34;</span> <span class="se">\ </span> 
</span></span><span class="line"><span class="cl">    --go_out<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$outDir</span><span class="s2">&#34;</span> --go_opt<span class="o">=</span><span class="nv">paths</span><span class="o">=</span>source_relative <span class="se">\ </span> 
</span></span><span class="line"><span class="cl">    --go-grpc_out<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$outDir</span><span class="s2">&#34;</span> --go-grpc_opt<span class="o">=</span><span class="nv">paths</span><span class="o">=</span>source_relative <span class="se">\ </span> 
</span></span><span class="line"><span class="cl">    secondary/secondary.proto  
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># build the primary proto</span>
</span></span><span class="line"><span class="cl">protoc -I<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$scriptDir</span><span class="s2">&#34;</span> <span class="se">\ </span> 
</span></span><span class="line"><span class="cl">    --go_out<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$outDir</span><span class="s2">/primary&#34;</span> --go_opt<span class="o">=</span><span class="nv">paths</span><span class="o">=</span>source_relative <span class="se">\ </span> 
</span></span><span class="line"><span class="cl">    --go-grpc_out<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$outDir</span><span class="s2">/primary&#34;</span> --go-grpc_opt<span class="o">=</span><span class="nv">paths</span><span class="o">=</span>source_relative <span class="se">\ </span> 
</span></span><span class="line"><span class="cl">    primary.proto
</span></span></code></pre></div><p>Our <code>primary.proto</code> and <code>secondary.proto</code> files:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="n">syntax</span> <span class="o">=</span> <span class="s">&#34;proto3&#34;</span><span class="p">;</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">option</span> <span class="n">go_package</span> <span class="o">=</span> <span class="s">&#34;github.com/BadgerBadgerBadgerBadger/protoreflect-error-test/proto/lang_go/primary&#34;</span><span class="p">;</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kn">package</span> <span class="nn">protoreflect</span><span class="o">.</span><span class="n">error.test.primary</span><span class="p">;</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">import</span> <span class="s">&#34;dependencies/secondary/secondary.proto&#34;</span><span class="p">;</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">service</span> <span class="n">Main</span> <span class="p">{</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">rpc</span> <span class="n">GetPrimary</span> <span class="p">(</span><span class="n">PrimaryRequest</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">PrimaryResponse</span><span class="p">)</span> <span class="p">{}</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">PrimaryRequest</span> <span class="p">{</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="n">secondary.SecondaryMessage</span> <span class="n">primary_request</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">PrimaryResponse</span> <span class="p">{</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="n">secondary.SecondaryMessage</span> <span class="n">primary_response</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="n">syntax</span> <span class="o">=</span> <span class="s">&#34;proto3&#34;</span><span class="p">;</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">option</span> <span class="n">go_package</span> <span class="o">=</span> <span class="s">&#34;github.com/BadgerBadgerBadgerBadger/protoreflect-error-test/proto/lang_go/secondary&#34;</span><span class="p">;</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kn">package</span> <span class="nn">protoreflect</span><span class="o">.</span><span class="n">error.test.secondary</span><span class="p">;</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">SecondaryMessage</span> <span class="p">{</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">value</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>  <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span></code></pre></div><p>I won&rsquo;t paste the go code here that runs the server since it&rsquo;s of less interest to us but you can find that <a href="https://github.com/BadgerBadgerBadgerBadger/protoreflect-error-test/blob/main/error-state/cmd/service/main.go">here</a>.</p>
<p>When we run this, and then try to use <a href="https://github.com/fullstorydev/grpcui">grpcui</a> to introspect the server, we get:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  ~ grpcui -plaintext localhost:8000
</span></span><span class="line"><span class="cl">Failed to compute <span class="nb">set</span> of methods to expose: Symbol not found: protoreflect.error.test.primary.Main
</span></span><span class="line"><span class="cl">caused by: File not found: dependencies/secondary/secondary.proto
</span></span></code></pre></div><p>This is the error that gave me a significant amount of headache before I fully understood not only what causes it but also what causes it in my specific situation.</p>
<p>I stumbled across this <a href="https://github.com/fullstorydev/grpcurl/issues/22">github issue</a>. If you&rsquo;re smart (which I am not) you will immediately understand the problem and figure out a solution. It took me a bit longer since I also like to understand how things work.</p>
<p>Since <code>dependencies/secondary/secondary.proto</code> is the culprit let&rsquo;s look at two things:</p>
<ul>
<li>How are we importing that dependency?</li>
<li>How are we <em>building</em> that dependency?</li>
</ul>
<p>Looking at our <code>primary.proto</code> file, we are importing it as:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="k">import</span> <span class="s">&#34;dependencies/secondary/secondary.proto&#34;</span><span class="p">;</span><span class="err">
</span></span></span></code></pre></div><p>And while the reflection server is trying to find it by that path, it can&rsquo;t seem to.</p>
<p>Looking at our <code>gen.sh</code> script, we see:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">protoc -I<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$scriptDir</span><span class="s2">/dependencies&#34;</span> <span class="se">\ </span> 
</span></span><span class="line"><span class="cl">    --go_out<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$outDir</span><span class="s2">&#34;</span> --go_opt<span class="o">=</span><span class="nv">paths</span><span class="o">=</span>source_relative <span class="se">\ </span> 
</span></span><span class="line"><span class="cl">    --go-grpc_out<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$outDir</span><span class="s2">&#34;</span> --go-grpc_opt<span class="o">=</span><span class="nv">paths</span><span class="o">=</span>source_relative <span class="se">\ </span> 
</span></span><span class="line"><span class="cl">    secondary/secondary.proto
</span></span></code></pre></div><p>We are <em>building</em> our dependency with the path <code>secondary/secondary.proto</code>.</p>
<p>&ldquo;But Badger&rdquo;, you might ask, &ldquo;Why does it matter what path we use to reference our dependency while building?&rdquo; And I might answer: &ldquo;I didn&rsquo;t think it would, but apparently it does!&rdquo;</p>
<p>As <a href="https://github.com/jhump">jhump</a> says in his comment to that github issue I linked:</p>
<blockquote>
<p>this is a problem in the Go protobuf runtime regarding how file descriptors that are compiled into your binary are &ldquo;linked&rdquo;.</p>
<p>They are linked purely by name. What that means is that the name (and relative path) used to compile a proto with protoc <em>must exactly match</em> how all other files will import it.</p>
</blockquote>
<p>Which essentially means that the name you use while building a certain dependency gets encoded in the Golang code of the generated file as the <em>path</em> for that dependency. To me this feels silly, but without jumping into <a href="https://pkg.go.dev/google.golang.org/protobuf">google.golang.org/protobuf</a>&rsquo;s code and understanding why they built it this way, it would be similarly silly to pass judgement.</p>
<p>While jhump&rsquo;s example pointed to <a href="https://github.com/jamisonhyatt/grpc-multi-pkg-protos/blob/master/pkg/external/location/location.pb.go#L227">this file</a> I could not find a similar example in my own code due to the differences in library and tool versions.</p>
<p>I looked at the Golang code generated and followed this code path:</p>
<ul>
<li><a href="https://github.com/BadgerBadgerBadgerBadger/protoreflect-error-test/blob/main/error-state/proto/lang_go/secondary/secondary.pb.go#L145">https://github.com/BadgerBadgerBadgerBadger/protoreflect-error-test/blob/main/error-state/proto/lang_go/secondary/secondary.pb.go#L145</a></li>
<li><a href="https://github.com/protocolbuffers/protobuf-go/blob/v1.34.1/internal/filetype/build.go#L121">https://github.com/protocolbuffers/protobuf-go/blob/v1.34.1/internal/filetype/build.go#L121</a></li>
<li><a href="https://github.com/protocolbuffers/protobuf-go/blob/v1.34.1/internal/filetype/build.go#L138">https://github.com/protocolbuffers/protobuf-go/blob/v1.34.1/internal/filetype/build.go#L138</a></li>
<li><a href="https://github.com/protocolbuffers/protobuf-go/blob/v1.34.1/internal/filedesc/build.go#L91">https://github.com/protocolbuffers/protobuf-go/blob/v1.34.1/internal/filedesc/build.go#L91</a></li>
<li><a href="https://github.com/protocolbuffers/protobuf-go/blob/v1.34.1/internal/filedesc/build.go#L112">https://github.com/protocolbuffers/protobuf-go/blob/v1.34.1/internal/filedesc/build.go#L112</a></li>
<li><a href="https://github.com/protocolbuffers/protobuf-go/blob/v1.34.1/reflect/protoregistry/registry.go#L114">https://github.com/protocolbuffers/protobuf-go/blob/v1.34.1/reflect/protoregistry/registry.go#L114</a></li>
</ul>
<p>I&rsquo;m sure there&rsquo;s an easier way to do this, but I did it by stepping through the code using <a href="https://www.jetbrains.com/go/">Goland&rsquo;s</a> debugging tools.</p>
<p><code>func (r *Files) RegisterFile(file protoreflect.FileDescriptor) error</code> is the function responsible for registering a protobuf file to the <a href="https://pkg.go.dev/google.golang.org/protobuf/reflect/protoregistry">Global</a> <a href="https://protobuf.dev/reference/go/faq/#namespace-conflict">Registry</a></p>
<p>Debugging our way to <a href="https://github.com/protocolbuffers/protobuf-go/blob/v1.34.1/reflect/protoregistry/registry.go#L125">line 125</a> we see that the path has been encoded as <code>secondary/secondary.proto</code>, which is the path that we used <em>when building</em> the file.</p>
<p>![[shows-path-of-compiled-protobuf-file.png]]
It looks to be that the path used to build the file is the one that gets embedded as the path <em>to</em> the file. And the gRPC reflection server tries to look up the file using that path.</p>
<p>Since the file does not actually reside at that path, the reflection fails.</p>
<p>The solution is easy enough: use the same path while building the dependency as used to reference the dependency.</p>
<p>The corrected code is at  <a href="https://github.com/BadgerBadgerBadgerBadger/protoreflect-error-test/tree/main/working-state">https://github.com/BadgerBadgerBadgerBadger/protoreflect-error-test/tree/main/working-state</a></p>
<p>I hope you enjoyed the post and if you know why things were implemented this way, let me know.</p>
]]></description><media:thumbnail url="https://badgerbadgerbadgerbadger.dev/images/posts/2024-09-11-protopath-problems-in-go/5a8d3279-ada4-4c2c-8714-e8832b4544fe_huc05ad5f93d79ee241d58bd88e2943980_212159_1024x0_resize_q80_box.jpg"/></item><item><title>Setting up DSMR Meter Readings via a Raspberry Pi</title><link>https://badgerbadgerbadgerbadger.dev/posts/automation/2024-05-23-setting-up-dsmr-readings-via-raspberry-pi/</link><pubDate>Thu, 23 May 2024 00:00:00 +0000</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/automation/2024-05-23-setting-up-dsmr-readings-via-raspberry-pi/</guid><description><![CDATA[<p>The internet is probably full of tutorials on how to do this right, but I had to struggle a bit to figure it out for myself. My Pi randomly crashed, the other day, and I&rsquo;m having to do some of the setup again and having done this for the first time months ago (and having now mostly forgotten what I did), I decided I should blog about it as I retread my steps and recreate my setup.</p>
<blockquote>
<p><strong>Aside</strong>: The <a href="https://en.wikipedia.org/wiki/Unix_shell">internet</a> tells me that the C shell was the first to introduce the <code>history</code> command. It would not be hyperbolic to say that without <code>history</code> my task of recreating my setup would be significantly more difficult. I used it extensively to figure out what I ran when I needed to do this all those months ago. I should probably containerise or chefise or ansibilise or something.</p>
</blockquote>
<p>I live in a country that uses <a href="https://www.domoticz.com/wiki/Dutch_DSMR_smart_meter_with_P1_port">DSMR</a> meters and have one in my home. My girlfriend wanted a closer look at our electricity consumption, so I decided to hook up a raspberry Pi to the meter&rsquo;s <a href="https://www.fluvius.be/sites/fluvius/files/2020-03/1901-fluvius-technical-specification-user-ports-digital-meter.pdf">P1</a> port and send those readings to my home lab server using the Pi as a relay.</p>
<p>I bought a RJ12 &ndash;&gt; USB cable off of Amazon and tried to read from my Pi&rsquo;s USB port by using first <a href="https://linux.die.net/man/1/cu"><code>cu</code></a> and then <a href="https://pythonhosted.org/pyserial/index.html">pyserial</a> (which is a much friendlier interface).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># Using cu</span>
</span></span><span class="line"><span class="cl">cu -l /dev/ttyUSB0 -s <span class="m">115200</span> --parity<span class="o">=</span>none
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Using pyserial</span>
</span></span><span class="line"><span class="cl">python3 -m serial.tools.miniterm /dev/ttyUSB0 <span class="m">115200</span> --xonxoff
</span></span></code></pre></div><p>Initially neither tool gave me any readings and I nearly tore my luscious hair out before finally realising that it was a bad cable I was using. I returned it and bought a more respectable cable from <a href="https://www.robbshop.nl/slimme-meter-kabel-usb-p1-1-meter">robbshop.nl</a>.</p>
<p>And that gave me results!</p>
<p><img src="/images/posts/2024-05-23-setting-up-dsmr-readings-via-raspberry-pi/bdc7ee3d-9f61-480a-b3ec-22e04f5e558e_hu6a55b9184a75d3167f066fd12bddcc44_176232_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2024-05-23-setting-up-dsmr-readings-via-raspberry-pi/bdc7ee3d-9f61-480a-b3ec-22e04f5e558e_hu6a55b9184a75d3167f066fd12bddcc44_176232_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2024-05-23-setting-up-dsmr-readings-via-raspberry-pi/bdc7ee3d-9f61-480a-b3ec-22e04f5e558e_hu6a55b9184a75d3167f066fd12bddcc44_176232_960x0_resize_q75_h2_box_3.webp 960w, /images/posts/2024-05-23-setting-up-dsmr-readings-via-raspberry-pi/bdc7ee3d-9f61-480a-b3ec-22e04f5e558e_hu6a55b9184a75d3167f066fd12bddcc44_176232_1600x0_resize_q75_h2_box_3.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="462" loading="lazy" decoding="async" alt="image"></p>
<p>I had a few different ways of turning the meter readings into useful insights:</p>
<ol>
<li><strong>Run HomeAssistant on my Pi with direct access to the port</strong>: Not an option since my HA instance was already running on my home lab server. The Pi was meant to be a relay only.</li>
<li><strong>DSMR to MQTT</strong>: I could run something that would read the DSMR telegrams and send them to an MQTT broker to which I could subscribe my HA integration. Definitely an option.</li>
<li><strong>Serial to Network Proxy</strong>: The option I ended up going with since it felt the simplest to me. I would run a program that would expose the DSMR telegrams over a TCP connection.</li>
</ol>
<p>The program I landed on for doing this is <a href="https://ser2net.sourceforge.net/">ser2net</a> With a little help from ChatGPT I figured out this <a href="https://github.com/chargebyte/ser2net/blob/master/ser2net.conf">ser2net config</a>. Comments describe what the various fields do.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># defines an alias confver, I&#39;m not entirely sure if this does anything</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">define</span><span class="p">:</span><span class="w"> </span><span class="cp">&amp;confver</span><span class="w"> </span><span class="m">1.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># begins a new connection definition and assigns it an alias `con00`.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">connection</span><span class="p">:</span><span class="w"> </span><span class="cp">&amp;con00</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># specifies how incoming network connections will be accepted, in this case the Telnet protocol with RFC 2217 support (I no clue what that means), and the connection will be over TCP and listen on port 2013c (this part I understand)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">accepter</span><span class="p">:</span><span class="w"> </span><span class="l">telnet(rfc2217),tcp,2013</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># specifies how the serial device will be connected, includes path to the serial device file and port settings, and it is to be configured in local mode (not sure what local mode means)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">connector</span><span class="p">:</span><span class="w"> </span><span class="l">serialdev,/dev/ttyUSB0,115200n81,local</span><span class="w">
</span></span></span></code></pre></div><p>I paired this with HA&rsquo;s <a href="https://www.home-assistant.io/integrations/dsmr">DSMR Slimme Meter</a> integration with it set to listen on the network, pointed at my Pi on port 2013.</p>
<p>And we have lift-off!</p>
<p><img src="/images/posts/2024-05-23-setting-up-dsmr-readings-via-raspberry-pi/f7a28b8c-afc3-4119-bbca-8f468227f24f_huf405d0ac751e66659b50e9da3505c340_261062_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2024-05-23-setting-up-dsmr-readings-via-raspberry-pi/f7a28b8c-afc3-4119-bbca-8f468227f24f_huf405d0ac751e66659b50e9da3505c340_261062_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2024-05-23-setting-up-dsmr-readings-via-raspberry-pi/f7a28b8c-afc3-4119-bbca-8f468227f24f_huf405d0ac751e66659b50e9da3505c340_261062_960x0_resize_q75_h2_box_3.webp 960w, /images/posts/2024-05-23-setting-up-dsmr-readings-via-raspberry-pi/f7a28b8c-afc3-4119-bbca-8f468227f24f_huf405d0ac751e66659b50e9da3505c340_261062_1600x0_resize_q75_h2_box_3.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="568" loading="lazy" decoding="async" alt="image"></p>
<blockquote>
<p><strong>Note</strong>: Solar readings are coming in via a different integrations and configured together with the DSMR readings using HomeAssistant&rsquo;s Energy Dashboard.</p>
</blockquote>
]]></description><media:thumbnail url="https://badgerbadgerbadgerbadger.dev/images/posts/2024-05-23-setting-up-dsmr-readings-via-raspberry-pi/367da639-c5d0-4ec0-a7cf-3fda8eec61cd_hudf17fac43419b2c2e3f26afb9073cdef_287092_1024x0_resize_q80_box.jpg"/></item><item><title>Is it Dry Yet?</title><link>https://badgerbadgerbadgerbadger.dev/posts/misc/2024-04-08-is-it-dry-yet/</link><pubDate>Mon, 08 Apr 2024 00:00:00 +0100</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/misc/2024-04-08-is-it-dry-yet/</guid><description><![CDATA[<blockquote>
<p><strong>Clarification</strong>: This is literally about my laundry. I am not being metaphorical. I am not talking about my feelings or my secrets. I am talking about my clothes.</p>
</blockquote>
<h2 id="intro">Intro</h2>
<p>Our washing machine is a Samsung something-something that sends me a notification on the <a href="https://www.samsung.com/be/smartthings/app/">SmartThings app</a> when it completes a wash cycle. This is useful since we almost always need to follow up a washing cycle with a drying one and maybe even another load of laundry. The machine isn&rsquo;t a combined-washer-dryer so someone has to move the wet clothes from our smart washer to our dumb dryer.</p>
<p>The fact that our dryer is &ldquo;dumb&rdquo; is what led me to my automation journey. If it was Wifi-enabled and also sending out notifications as it completed a cycle, I wouldn&rsquo;t be writing this post.</p>
<p>Having been spoiled by knowing exactly when my washing has completed (so I know when to go move things to the dryer) I decided to do something similar for the dryer. This is especially useful, for example, when the clothes need an extra drying cycle or when there are more wet clothes that are waiting in the washer. In these times it is useful to know if the dryer has finished a cycle.</p>
<p>And since it is not a smart device, I have to add the brains, somehow.</p>
<h2 id="plan-of-attack">Plan of Attack</h2>
<p>This is how I decided to approach the problem:</p>
<ul>
<li>I have a <a href="https://www.shelly.com/en-be/products/shop/shelly-plus-plug-s">Shelly smart plug</a> that I would put between Drew (the dryer is called Drew) and the wall.</li>
<li>The plug would transmit power readings to my <a href="https://www.home-assistant.io/">Home Assistant</a> setup.</li>
<li>I would do <em>something</em> to detect when the power reading goes down from some high number to some very low number (I&rsquo;m assuming it never goes to 0 since the little LED panel needs power).</li>
<li>I will notify myself of this <em>somehow</em>.</li>
</ul>
<p>It&rsquo;s the <em>something</em> and <em>somehow</em> parts I was unsure of. My Home Assistant setup is as basic as it gets. I only started my home automation journey a month or two ago and was unfamiliar with the possibilities. However, this felt like something that should be within the realm of HASS&rsquo;s capabilities.</p>
<h2 id="discovering-automations">Discovering Automations</h2>
<p>The first thing I discovered as I dug further into Home Assistant was <a href="https://www.home-assistant.io/docs/automation/">Automation</a>s. In Home Assistant you create automations via the Automations system. You can turn on a light when a motion sensor senses someone in the room; you can turn on the vent when humidity reaches a certain level; you can make all the lights turn pink and romantic music play over the speakers when it is February 14th and your beloved comes home from work. The possibilities are <em>endless</em>.</p>
<h2 id="the-automation-setup">The Automation Setup</h2>
<p>Figuring out the Automation setup I wanted was straightforward. There are 3 key components to the Home Assistant Automation system.</p>
<ol>
<li><a href="https://www.home-assistant.io/docs/automation/trigger/">Triggers</a>: The thing that kicks off an Automation. These can be of many types ranging from a number on a sensor changing, to an incoming MQTT payload.</li>
<li><a href="https://www.home-assistant.io/docs/automation/condition/">Conditions</a>: An optional extra check to ensure that when the Automation triggers everything is in the state you want it to be. You can check the value of sensors, check the on/off status of devices, or even <a href="https://www.home-assistant.io/docs/scripts/conditions/#sun-condition">check if the sun is up</a>.</li>
<li><a href="https://www.home-assistant.io/docs/automation/action/">Actions</a>: This where we <em>do</em> something in our Automation. This is where you will want to set up that electric rooster crowing when the sun comes up (if you want to build your own weird alarm clock for some reason), or send a message on a webhook when the kids are using too much electricity.</li>
</ol>
<p>Thinking over my needs I decided on the following for my Trigger/Condition/Action setup:</p>
<ol>
<li><strong>Trigger</strong>: I want the Automation to start when Drew&rsquo;s power draw falls below 10 W (as reported by Sheila, the smart plug), and has remained under 10 W for more than 5 minutes.</li>
<li><strong>Condition</strong>: I want to ensure this Automation only fires if we have consumed more than a 100 Watts of power sometime in the last 5 minutes.</li>
<li><strong>Action</strong>: Inform me somehow that this has happened.</li>
</ol>
<p>While the Trigger and Condition were straightforward, the Action was a bit more nebulous. At first, I wasn&rsquo;t entirely sure how to notify myself. I looked into <a href="https://www.home-assistant.io/integrations/notify.rest/">Home Assistant&rsquo;s Restful Notifications</a> but could not make it work for the life of me. Then I decided to check if I could send an email. I found <a href="https://www.home-assistant.io/integrations/smtp/">Home Assistant&rsquo;s SMTP Notifications</a> and decided to give it a go. Again, I could not make it work. It asked me to enter my Gmail password in the Home Assistant configuration file, but I was not comfortable doing that. I decided to look for another way.</p>
<p>I finally stumbled upon the <a href="https://www.home-assistant.io/integrations/ios/">Home Assistant iOS App</a> which I installed on my phone and pointed to my Home Assistant server. This allowed me to send notifications to my phone (my phone became available as a target for notifications).</p>
<p>The full Automation setup looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Is it dry yet?</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Notifies when Drew (the dryer) has finished a drying cycle.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">Sheila Power Below 10 W</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">numeric_state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity_id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">sensor.sheila_power</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">below</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">for</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">minutes</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">condition</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">numeric_state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.sheila_power_5_minutes_ago</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">above</span><span class="p">:</span><span class="w"> </span><span class="m">100</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">notify.mobile_app_sharpie</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">Drew has finished a cycle. You should unload him.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">Dryer Cycle Finished</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">notify.mobile_app_buttercups_android</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">Drew has finished a cycle. You should unload him.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">Dryer cycle finished.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.play_media</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">target</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.thuis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">announce</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">media_content_type</span><span class="p">:</span><span class="w"> </span><span class="l">mp3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">media_content_id</span><span class="p">:</span><span class="w"> </span><span class="l">https://&lt;static-host&gt;/drew-finished-drying.mp3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="l">single</span><span class="w">
</span></span></span></code></pre></div><p><code>notify.mobile_app_sharpie</code> and <code>notify.mobile_app_buttercups_android</code> are the entities exposed by the Home Assistant iOS and Android apps on my phone and my girlfriend&rsquo;s phone respectively. <code>media_player.thuis</code> is the entity exposed by my Google Nest speaker group. Not being simply content to send a notification to my phone, I also wanted to play a sound on my Google Nest speaker group. The sound is a mp3 file that I host on a static host.</p>
<p><code>sensor.sheila_power</code> is the sensor entity exposed by my smart plug and what lets me monitor the power draw of the dryer. <code>sensor.sheila_power_5_minutes_ago</code> is a sensor that I created using the <a href="https://www.home-assistant.io/integrations/sql">SQL Integration</a>. I created a custom sensor to get the power draw 5 minutes ago.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="o">*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">from</span><span class="w"> </span><span class="n">states</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">where</span><span class="w"> </span><span class="n">metadata_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="k">select</span><span class="w"> </span><span class="n">states_meta</span><span class="p">.</span><span class="n">metadata_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                     </span><span class="k">from</span><span class="w"> </span><span class="n">states_meta</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                     </span><span class="k">where</span><span class="w"> </span><span class="n">states_meta</span><span class="p">.</span><span class="n">entity_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;sensor.sheila_power&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">and</span><span class="w"> </span><span class="n">datetime</span><span class="p">(</span><span class="n">last_updated_ts</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;unixepoch&#39;</span><span class="p">)</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">datetime</span><span class="p">(</span><span class="s1">&#39;now&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;-5 minutes&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">order</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="n">last_updated_ts</span><span class="w"> </span><span class="k">desc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">limit</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span></code></pre></div><p>In combination this ensures that this Automation only triggers when our power draw falls below 10 W for at least 5 minutes, and we have consumed more than 100 W of power just before. This gives me a good indication that the dryer has finished a cycle and I should go unload it.</p>
<h2 id="exposing-to-the-world">Exposing to the World</h2>
<p>I should mention that my Home Assistant server was not available to the internet at that point. All integrations were over local Wi-Fi with firewall rules to prevent any external access. I did not have to expose my Home Assistant server to the internet to get notifications on my phone since they are both part of a <a href="https://tailscale.com/kb/1136/tailnet">Tailnet</a>. This meant that as long as I had the <a href="https://tailscale.com/download/ios">Tailscale App</a>  on my phone and Tailscale VPN enabled, I could access my Home Assistant server from anywhere in the world.</p>
<p>And this would have been good enough for me, but my girlfriend also needs notifications on <em>her</em> phone and asking her to install Tailscale and turn it on/off before she could access the notifications was a bit much.</p>
<p>So I decided to expose my Home Assistant server to the internet.</p>
<p>My first attempt was using Let&rsquo;s Encrypt and DuckDNS, following a guide (or three) similar to <a href="https://www.makeuseof.com/access-home-assistant-server-remotely-duckdns-letsencrypt/">this</a>. I won&rsquo;t go into all the details, but I will say that it was a <em>pain</em>. Ultimately, after struggling for several days, I gave up. I gave up and decided to go back to my girlfriend, head bowed, and give her the bad news.</p>
<p>But then I stumbled upon <a href="https://tailscale.com/kb/1223/funnel">Tailscale Funnels</a>. Since I was using Tailscale anyway, with a single additional invocation, I could have a secure, encrypted, tunnel to my Home Assistant server.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  ~ tailscale funnel --https<span class="o">=</span><span class="m">8443</span> --bg <span class="m">8123</span>
</span></span></code></pre></div><p>No extra DNS required since I can use my Tailnet&rsquo;s name and no extra certificates required since Tailscale handles all of that for me.</p>
<p>The only caveat to this approach is that Tailscale does not yet support domain name handling. So I can&rsquo;t assign a nice domain name to my Home Assistant server. Furthermore, I only have <a href="https://tailscale.com/kb/1223/funnel#limitations">3 ports available to me</a>. This means that if I were to have other services on that machine that I want to expose to the world, I can at most have 2 more, and they will have to be referenced by their port numbers.</p>
<p>This is something I will have to come back to later, but for now both me and my girlfriend have access to our Home Assistant server from anywhere in the world and are happy ducks.</p>
<p>Of course, I limited her account to only have access to the notifications and some views. I don&rsquo;t want her messing with my automations and I don&rsquo;t want a third party poking around my smart home setup.</p>
]]></description><media:thumbnail url="https://badgerbadgerbadgerbadger.dev/images/posts/2024-04-08-is-it-dry-yet/7b71e898-f7bc-4d72-91e6-27fd20a74a88_hu17ec3395750e302e75e4ebf34623ed4e_910975_1200x0_resize_q80_bgffffff_box_3.jpg"/></item><item><title>Alerting on Process Completion</title><link>https://badgerbadgerbadgerbadger.dev/posts/automation/2024-04-04-alerting-on-process-complete/</link><pubDate>Thu, 04 Apr 2024 00:00:00 +0000</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/automation/2024-04-04-alerting-on-process-complete/</guid><description><![CDATA[<p>Most of my technical endeavours have something to do with enabling me to work as little as possible for as much reward as possible. And yet, paradoxically, in a pursuit of doing less I often end up doing more. I have this idea that in the long term this will eventually lead me to doing <em>much</em> less. I&rsquo;m not sure if that&rsquo;s true, but it&rsquo;s a nice thought and keeps me going. Plus, I learn a lot of fun things along the way.</p>
<p>Whenever I have a long-running process on my home-lab I start it in a <a href="https://linux.die.net/man/1/screen">screen session</a>. I check back on it occasionally to see if it has completed. The same could be true of something I&rsquo;m running on my local machine, though, in that case, it is in a live terminal session. The latter is usually during the course of my day-job and is somewhat of an easier problem to solve since I am at my machine for the duration of the process.</p>
<p>For my local machine (which is a MacBook), I use the <a href="https://ss64.com/mac/say.html">say</a> command extensively. It&rsquo;s trivial to add a <code>say &quot;Done&quot;</code> at the end of a command chain to aurally inform me of a task&rsquo;s completion. But that wasn&rsquo;t quite spicy enough for me, so I came up with something more interesting.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  ~ which sdd
</span></span><span class="line"><span class="cl">sdd <span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="o">[</span> <span class="nv">$#</span> -eq <span class="m">0</span> <span class="o">]</span>
</span></span><span class="line"><span class="cl">	<span class="k">then</span>
</span></span><span class="line"><span class="cl">		<span class="nv">title</span><span class="o">=</span><span class="s2">&#34;Done&#34;</span>
</span></span><span class="line"><span class="cl">		<span class="nv">text</span><span class="o">=</span><span class="s2">&#34;Done&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="k">else</span>
</span></span><span class="line"><span class="cl">		<span class="nv">title</span><span class="o">=</span><span class="nv">$1</span>
</span></span><span class="line"><span class="cl">		<span class="nb">shift</span>
</span></span><span class="line"><span class="cl">		<span class="nv">text</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="k">fi</span>
</span></span><span class="line"><span class="cl">	osascript -e <span class="s2">&#34;display notification \&#34;</span><span class="nv">$text</span><span class="s2">\&#34; with title \&#34;</span><span class="nv">$title</span><span class="s2">\&#34;&#34;</span> <span class="o">&amp;&amp;</span> afplay ~/Documents/star-wars-b1-battle-droid_kampfdroide-roger-roger-sound.mp3
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>Behold the <code>sdd</code> function I add to the end of every script and command chain. I can invoke it with a title and an optional message, or invoke it with nothing and let it default to &ldquo;Done&rdquo; and &ldquo;Done&rdquo;. When invoked, I will be shown a <a href="https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/DisplayNotifications.html">notification</a> on my screen along with an audible &ldquo;Roger! Roger!&rdquo;</p>
<p><img src="/images/posts/2024-04-04-alerting-on-process-complete/630f82c6-758c-4b62-8869-863a457c26c4_hu34e084ec20e1042bb5a5b64fb481335a_119081_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2024-04-04-alerting-on-process-complete/630f82c6-758c-4b62-8869-863a457c26c4_hu34e084ec20e1042bb5a5b64fb481335a_119081_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2024-04-04-alerting-on-process-complete/630f82c6-758c-4b62-8869-863a457c26c4_hu34e084ec20e1042bb5a5b64fb481335a_119081_960x0_resize_q75_h2_box_3.webp 960w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="281" loading="lazy" decoding="async" alt="image"></p>
<blockquote>
<p><strong>Disclaimer</strong><br>
I have no idea if this is required. I find copyright laws almost maliciously confusing. Anyway, just to cover my backside:</p>
<p><em><strong>I do not own any rights to the &ldquo;Roger, Roger&rdquo; voice line, which is associated with the Star Wars franchise created by George Lucas and owned by Lucasfilm Ltd. and The Walt Disney Company. This phrase is used here under the principles of fair use for educational or informational purposes only.</strong></em></p>
</blockquote>
<p>This is all fine and dandy for my local machine, but what about my home-lab? I don&rsquo;t want to keep a live SSH session running all the time, nor do I want to have to check back on it at odd intervals. Nor am I <em>at</em> a terminal all the time!</p>
<p>The solution to that comes from my Home Assistant setup (look out for another post that talks about my Home Assistant and dryer smartification journey).</p>
<p>I have created an <a href="https://www.home-assistant.io/docs/automation/">Automation</a> that listens to a webhook and sends a notification to my phone whenever that webhook fires.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Notify Sharpie</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">webhook</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">allowed_methods</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">POST</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">local_only</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">webhook_id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&lt;webhook-id&gt;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">notify.mobile_app_sharpie</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">data_template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">Notification</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ trigger.json.message }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="l">single</span><span class="w">
</span></span></span></code></pre></div><blockquote>
<p>Sharpie is the name of my phone.</p>
</blockquote>
<p>Now I can curl from any device anywhere and receive a notification on my phone!</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">curl -i -H <span class="s2">&#34;Content-Type: application/json&#34;</span> -d <span class="s2">&#34;{\&#34;message\&#34;: \&#34;Sup?\&#34;}&#34;</span> https://&lt;home-assistant-server&gt;/api/webhook/&lt;wehook-id&gt;
</span></span></code></pre></div><p><img src="/images/posts/2024-04-04-alerting-on-process-complete/d4e33a9d-bc6c-499b-92fb-156874c155df_hua2b02c898e2b956e335054cce95a3fd0_185181_400x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2024-04-04-alerting-on-process-complete/d4e33a9d-bc6c-499b-92fb-156874c155df_hua2b02c898e2b956e335054cce95a3fd0_185181_400x0_resize_q75_h2_box_3.webp 400w" sizes="(min-width: 800px) 720px, 100vw" width="400" height="865" loading="lazy" decoding="async" alt="image"></p>
<p>I can plop this down at the end of any command chain or script and be notified when it completes.</p>
<p>But I figured out all this after the fact. During the fact (is that even a phrase?), I had already started a long-running process that I wanted to monitor and be informed of when it terminated. So there was no way for me to append a notification curl at the end of the chain without stopping the process.</p>
<p>Enter the <a href="https://man7.org/linux/man-pages/man1/ps.1.html">ps</a> command with the <a href="https://medium.com/@linuxschooltech/what-is-ps-p-command-in-linux-aede7e5f0751">-p</a> flag. This can be passed a process ID and gives you information about the process.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  ~ <span class="nb">echo</span> <span class="nv">$$</span>
</span></span><span class="line"><span class="cl"><span class="m">266386</span>
</span></span><span class="line"><span class="cl">➜  ~ ps -p <span class="m">266386</span>
</span></span><span class="line"><span class="cl">    PID TTY          TIME CMD
</span></span><span class="line"><span class="cl"> <span class="m">266386</span> pts/1    00:00:00 zsh
</span></span><span class="line"><span class="cl">➜  ~ <span class="nb">echo</span> <span class="nv">$?</span>
</span></span><span class="line"><span class="cl"><span class="m">0</span>
</span></span></code></pre></div><blockquote>
<p><code>$$</code> is the PID of the current shell process.</p>
</blockquote>
<p>Note that the exit code returned by the <code>ps -p</code> command is <code>0</code>. If we were to try a PID that does not exist, we will get a <code>1</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  ~ ps -p <span class="m">42069</span>
</span></span><span class="line"><span class="cl">    PID TTY          TIME CMD
</span></span><span class="line"><span class="cl">➜  ~ <span class="nb">echo</span> <span class="nv">$?</span>
</span></span><span class="line"><span class="cl"><span class="m">1</span>
</span></span></code></pre></div><p>Thus, without knowing the exact details or output of using <code>ps -p</code> on our target process, we can check the exit code and find out if the process is running. This leads us to this helper function.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># Check if the process with PID exists</span>
</span></span><span class="line"><span class="cl">process_exists<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    ps -p <span class="nv">$1</span> &gt; /dev/null 2&gt;<span class="p">&amp;</span><span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>Checking if our target process is still running, now becomes a matter of a sleepy loop (to avoid spamming the command).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># Loop until the process no longer exists</span>
</span></span><span class="line"><span class="cl"><span class="k">while</span> process_exists <span class="nv">$PID</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    sleep <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Process </span><span class="nv">$PID</span><span class="s2"> has stopped.&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Send the notification</span>
</span></span><span class="line"><span class="cl">send_message <span class="nv">$MESSAGE</span>
</span></span></code></pre></div><p>The <code>send_message</code> function can take advantage of our webhook and notify us when the process completes!</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">send_message<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  curl -i -H <span class="s2">&#34;Content-Type: application/json&#34;</span> -d <span class="s2">&#34;{\&#34;message\&#34;: \&#34;</span><span class="nv">$1</span><span class="s2">\&#34;}&#34;</span> https://&lt;home-assistant-server&gt;/api/webhook/&lt;wehook-id&gt;
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>And with that we have a script that can monitor a process and let us know when it completes.</p>
<h1 id="wrapping-it-up">Wrapping it Up</h1>
<p>I hope you enjoyed this brief exploration of figuring out how to monitor a live process. If you were to try something like this yourself, you could, no doubt, switch out the notification part with any number of other services. Perhaps <a href="https://www.twilio.com/en-us/messaging/channels/sms">send yourself an SMS</a> or blink the lights in your house on and off 21 times.</p>
]]></description><media:thumbnail url="https://badgerbadgerbadgerbadger.dev/images/posts/2024-04-04-alerting-on-process-complete/6ce2cd86-7228-4180-bbb6-36ab539bd69a_hu8950ba79eedb545a235c4ffda35454eb_960101_1200x0_resize_q80_bgffffff_box_3.jpg"/></item><item><title>Short Tale of Intel -> Apple Silicon Migration</title><link>https://badgerbadgerbadgerbadger.dev/posts/misc/2024-03-13-intel-to-apple-silicon/</link><pubDate>Wed, 13 Mar 2024 00:00:00 +0100</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/misc/2024-03-13-intel-to-apple-silicon/</guid><description><![CDATA[<blockquote>
<p><strong>Note</strong>: I will be using the terms <code>amd64</code>, <code>x86_64</code>, and <code>Intel</code> interchangeably in this post. They all refer to the same architecture. Similarly <code>arm64</code> and <code>Apple Silicon</code> are used interchangeably.</p>
</blockquote>
<p>I recently got handed a new MacBook at work. It&rsquo;s a MacBook Pro with an M3 chip and my first encounter with Apple Silicon. I have been hearing many good things about Apple Silicon and having experienced it for myself for the past day I must say that my old Intel MacBook can&rsquo;t compare. I know that comparing my 2020 Intel MacBook, which has been used and abused for 4 years, to a brand new, out-of-the-box 2023 M3 Mac is not an <a href="https://media.tenor.com/a2gst_5S-RAAAAAi/uarrr-finger-guns.gif">Apples to Apples comparision</a>. So I will take the word of tech reviewers through the years as well as my own biased experience (I no longer have to wait several seconds for a compilation step) and say that Apple Silicon is a game changer.</p>
<p>The point of this post is not to talk up Apple&rsquo;s chips but to talk about the challenges I faced getting up and running after moving my stuff from an Intel Mac to an Arm Mac.</p>
<h2 id="migration-assistant">Migration Assistant</h2>
<p>While not specific to the Intel -&gt; Arm transfer, I was reminded once again that I should probably be running Migration Assistant over a cable instead of WiFi. It took all night and the way the numbers kept going up instead of down, sometimes, gave me a ton of anxiety. The next time I do this I have to remind myself that taking 5 extra minutes to get that cable out and connected will save me <em>hours</em> later.</p>
<h2 id="it-does-not-come-with-rosetta">It Does Not Come with Rosetta</h2>
<p>I&rsquo;m not entirely sure if this is always the case or if it ended up being the case because I performed a Migration, but <a href="https://support.apple.com/en-us/102527">Rosetta</a> was not already installed on my system. Instead, any time I tried to run a process that was Intel based, I got a pop-up that asked me if I want to install Rosetta.</p>
<p><img src="/images/posts/2024-03-13-intel-to-apple-silicon/2e55563b-7f61-4ad9-aeb9-1ff42469ff98_hu8ec00b90e875b06f4d35a87759e2cd1b_173244_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2024-03-13-intel-to-apple-silicon/2e55563b-7f61-4ad9-aeb9-1ff42469ff98_hu8ec00b90e875b06f4d35a87759e2cd1b_173244_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2024-03-13-intel-to-apple-silicon/2e55563b-7f61-4ad9-aeb9-1ff42469ff98_hu8ec00b90e875b06f4d35a87759e2cd1b_173244_960x0_resize_q75_h2_box_3.webp 960w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="466" loading="lazy" decoding="async" alt="image"></p>
<p>I put it off for as long as I could. I wanted to have everything running as natively Apple Silicon as I could possibly manage. I finally did cave when I discovered a few tools did not have an Arm counterpart, but I think I managed to reinstall a lot of things before I hit that stage.</p>
<h2 id="you-can-see-whats-running-on-rosetta">You Can See What&rsquo;s Running on Rosetta</h2>
<p>If you open the <a href="https://support.apple.com/guide/activity-monitor/welcome/mac">Activity Monitor</a>, you can <a href="https://thenextweb.com/news/how-to-check-app-running-m1-native-version-on-mac">check which apps are running on Rosetta</a>. In the CPU tab, look at the Kind column. It will either say <em>Intel</em> or <em>Apple</em>.</p>
<p>I managed to discover several things running on Rosetta and then went and found Apple Silicon versions of them. For example, I have some bash scripts that open tunnels to my company&rsquo;s internal network. For managing these multiple tunnels the script uses <a href="https://github.com/tmux/tmux/wiki">tmux</a>. I discovered in Activity Monitor that I had several <code>bash</code> processes running as Kind <code>Intel</code>. I suspected <code>tmux</code> spawned these processes, so I did a quick check:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  which tmux
</span></span><span class="line"><span class="cl">/usr/local/bin/tmux
</span></span><span class="line"><span class="cl">➜  file /usr/local/bin/tmux
</span></span><span class="line"><span class="cl">/usr/local/bin/tmux: Mach-O 64-bit executable x86_64
</span></span></code></pre></div><p>It looks like <code>tmux</code> is still an x86 executable.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  which bash
</span></span><span class="line"><span class="cl">/bin/bash
</span></span><span class="line"><span class="cl">➜  file /bin/bash
</span></span><span class="line"><span class="cl">/bin/bash: Mach-O universal binary with <span class="m">2</span> architectures: <span class="o">[</span>x86_64:Mach-O 64-bit executable x86_64<span class="o">]</span> <span class="o">[</span>arm64e:Mach-O 64-bit executable arm64e<span class="o">]</span>
</span></span><span class="line"><span class="cl">/bin/bash <span class="o">(</span><span class="k">for</span> architecture x86_64<span class="o">)</span>:	Mach-O 64-bit executable x86_64
</span></span><span class="line"><span class="cl">/bin/bash <span class="o">(</span><span class="k">for</span> architecture arm64e<span class="o">)</span>:	Mach-O 64-bit executable arm64e
</span></span></code></pre></div><p><code>bash</code> on the other hand, is a <a href="https://en.wikipedia.org/wiki/Universal_binary">universal binary</a>, an app format that encapsulates multiple architectures and acts as one of the bridges between Intel and Arm Macs.</p>
<p>But since these bash processes were spawned by an <em>x86_64</em> <code>tmux</code>, it was the <em>x86_64</em> version that got spawned. Once I replaced <code>tmux</code> with the arm version, the bash processes also spawned with their arm counterparts. So far so makes sense!</p>
<h2 id="a-scriptological-journey">A Scriptological Journey</h2>
<blockquote>
<p><strong>Warning</strong>: This section ended up being longer than anticipated and is filled with arcane unix tools and commands. It was also the most fun section to write!</p>
</blockquote>
<p>I wanted to find other binaries that were still x86_64 and see if we could clean them up. So I went into the <code>/usr/local/bin/</code> directory and ran the following command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  find -L . -type f -exec file <span class="o">{}</span> + <span class="p">|</span> grep <span class="s1">&#39;x86_64&#39;</span> <span class="p">|</span> grep -v <span class="s1">&#39;arm64&#39;</span>
</span></span></code></pre></div><p>Breaking it down:</p>
<ul>
<li><code>find -L . -type f</code> searches in the current directory and subdirectory, limited to items that are files. The <code>-L</code> option follows symbolic links as well.</li>
<li><code>-exec file {} +</code> executes the <a href="https://en.wikipedia.org/wiki/File_(command)">file</a> command on each file.</li>
<li><code>grep 'x86_64'</code> finds us instances of <code>x86_64</code> appearing in the output (the output for x86_64 binaries is <code>Mach-O 64-bit executable x86_64</code>).</li>
<li><code>grep -v 'arm64'</code> excludes instances where there is a universal binary (like <code>bash</code>, as mentioned before), which has both x86_64 and arm variants available in one package.</li>
</ul>
<p>That last grep is to explicitly ensure that if I have a universal binary with both x86_64 and arm64 architectures, we keep that universal binary.</p>
<blockquote>
<p><strong>Side Note</strong>: The <code>+</code> modifier is an interesting one. It instructs <code>find</code> to accumulate the found files and execute the command passed to exec (in this case <code>file</code>) command on them in a single invocation. So while without <code>+</code> each of the found files would have <code>file</code> executed against them one by one, with <code>+</code> the files are passed to the command in one go.</p>
</blockquote>
<p>The result of running this command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">./vagrant-go <span class="o">(</span><span class="k">for</span> architecture x86_64<span class="o">)</span>:	Mach-O 64-bit executable x86_64
</span></span><span class="line"><span class="cl">./dummy:                                        Mach-O 64-bit executable x86_64
</span></span><span class="line"><span class="cl">./taste:                                        Mach-O 64-bit executable x86_64
</span></span><span class="line"><span class="cl">./python3.10-intel64:                           Mach-O universal binary with <span class="m">1</span> architecture: <span class="o">[</span>x86_64:Mach-O 64-bit executable x86_64<span class="o">]</span>
</span></span><span class="line"><span class="cl">./python3.10-intel64 <span class="o">(</span><span class="k">for</span> architecture x86_64<span class="o">)</span>:	Mach-O 64-bit executable x86_64
</span></span></code></pre></div><p>If you look closely, you&rsquo;ll notice that the command is not quite completely giving us the result we want. For <code>./python3.10-intel64</code>  (which is a universal binary with only 1 architecture) we are getting two lines of output. And for <code>./vagrant-go</code> (which we can deduce to be a universal binary with both architectures), we are getting the x86_64 line of output when we actually want none at all. Recall that the ultimate goal is to delete the Intel-only binaries, for which we want a clean list of files.</p>
<p><code>awk</code> to the rescue!</p>
<p>For universal binaries with two architectures, <code>file</code> gives us the following.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜ file vagrant-go
</span></span><span class="line"><span class="cl">vagrant-go: Mach-O universal binary with <span class="m">2</span> architectures: <span class="o">[</span>arm64:Mach-O 64-bit executable arm64<span class="o">]</span> <span class="o">[</span>x86_64<span class="o">]</span>
</span></span><span class="line"><span class="cl">vagrant-go <span class="o">(</span><span class="k">for</span> architecture arm64<span class="o">)</span>:	Mach-O 64-bit executable arm64
</span></span><span class="line"><span class="cl">vagrant-go <span class="o">(</span><span class="k">for</span> architecture x86_64<span class="o">)</span>:	Mach-O 64-bit executable x86_64
</span></span></code></pre></div><p>The first line tells us that this is a universal binary with 2 architectures and the next two lines give more details on those architectures, one line being for each architecture.</p>
<p>We can&rsquo;t treat this as a line-by-line problem, anymore. We need to process the whole 3-line block. We need to recognize that we&rsquo;re processing a 2-architecture universal binary and then figure out a way to exclude the whole block.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  find -L . -type f -exec file <span class="o">{}</span> + <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    awk <span class="s1">&#39;/Mach-O universal binary with 2 architectures/ &amp;&amp; /arm64/ &amp;&amp; /x86_64/ { getline; getline; next } { print }&#39;</span> <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    grep <span class="s1">&#39;x86_64&#39;</span> <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    grep -v <span class="s1">&#39;arm64&#39;</span>
</span></span></code></pre></div><p>We&rsquo;ve added an <code>awk</code> invocation after listing our files via <code>find</code> (and <code>file</code>) and before we start grepping them. Breaking down the command:</p>
<ul>
<li><code>awk</code> is our invocation of the <a href="https://www.gnu.org/software/gawk/manual/gawk.html">awk</a> tool.</li>
<li><code>/Mach-O universal binary with 2 architectures/ &amp;&amp; /arm64/ &amp;&amp; /x86_64/</code> is the pattern that matches the first line output by <code>file</code>.</li>
<li><code>{ getline; getline; next }</code> is the action block that executes on the lines matched by our pattern. We call <a href="https://www.baeldung.com/linux/awk-getline">getline</a> twice to skip over the two architecture lines. <code>next</code> stops processing the current line since we don&rsquo;t want to do anything with the architecture lines.</li>
<li><code>{ print }</code> the default action block to apply to anything that does not match our pattern. It lets them pass through and be printed.</li>
</ul>
<p>So to put it all together, our <code>awk</code> invocation looks for lines that indicate a universal binary with two architectures and skips over the architecture lines, essentially ignoring the whole block.</p>
<p>The result of this invocation is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">./dummy:                                        Mach-O 64-bit executable x86_64
</span></span><span class="line"><span class="cl">./taste:                                        Mach-O 64-bit executable x86_64
</span></span><span class="line"><span class="cl">./python3.10-intel64:                           Mach-O universal binary with <span class="m">1</span> architecture: <span class="o">[</span>x86_64:Mach-O 64-bit executable x86_64<span class="o">]</span>
</span></span><span class="line"><span class="cl">./python3.10-intel64 <span class="o">(</span><span class="k">for</span> architecture x86_64<span class="o">)</span>:	Mach-O 64-bit executable x86_64
</span></span></code></pre></div><p>Now we no longer get <code>./vagrant-go</code> as part of our results, but <code>./python3.10-intel64</code> still appears twice. This is easily remedied by tacking on another <code>grep -v &quot;universal binary&quot;</code> to the end of our commands.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  find -L . -type f -exec file <span class="o">{}</span> + <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    awk <span class="s1">&#39;/Mach-O universal binary with 2 architectures/ &amp;&amp; /arm64/ &amp;&amp; /x86_64/ { getline; getline; next } { print }&#39;</span> <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    grep <span class="s1">&#39;x86_64&#39;</span> <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    grep -v <span class="s1">&#39;arm64&#39;</span> <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    grep -v <span class="s1">&#39;universal binary&#39;</span>
</span></span><span class="line"><span class="cl">./dummy:                                        Mach-O 64-bit executable x86_64
</span></span><span class="line"><span class="cl">./taste:                                        Mach-O 64-bit executable x86_64
</span></span><span class="line"><span class="cl">./python3.10-intel64 <span class="o">(</span><span class="k">for</span> architecture x86_64<span class="o">)</span>:	Mach-O 64-bit executable x86_64
</span></span></code></pre></div><p>At this point our command chain is getting a bit too long. For the sake of sanity I will put it all in a file called <code>find86_64.sh</code>.</p>
<blockquote>
<p><strong>Side Note</strong>: If you&rsquo;re wondering what <code>dummy</code> is, let me show you the trick to creating a dummy <a href="https://en.wikipedia.org/wiki/Mach-O">Mach-O x86_64</a> executable file for testing purposes.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  <span class="nb">printf</span> <span class="s1">&#39;\xcf\xfa\xed\xfe\x07\x00\x00\x01\x03\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&#39;</span> &gt; dummy
</span></span></code></pre></div><p>This file won&rsquo;t execute, but running <code>file</code> on it gives us</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">dummy: Mach-O 64-bit executable x86_64
</span></span></code></pre></div><p>Which is good enough for testing.</p>
</blockquote>
<p>The next step is to extract <em>just</em> the name of the file. We can leave in the leading <code>./</code> since <code>ls</code> (which we will run next) will accept that just fine.</p>
<p><code>awk</code> to the rescue again!</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜ find86_64.sh <span class="p">|</span> awk -F<span class="s1">&#39;:| &#39;</span> <span class="s1">&#39;{print $1}&#39;</span>
</span></span><span class="line"><span class="cl">./dummy
</span></span><span class="line"><span class="cl">./taste
</span></span><span class="line"><span class="cl">./python3.10-intel64
</span></span></code></pre></div><p>Breaking it down:</p>
<ul>
<li><code>awk -F':| </code> uses either <code>:</code> or <em>space</em> as a field separator to split the line into multiple fields.</li>
<li><code>{print $1}</code> prints the first field, which, in our case, is the file name.</li>
</ul>
<p>Our updated script now looks like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="cp">#!/bin/sh
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>find -L . -type f -exec file <span class="o">{}</span> + <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    awk <span class="s1">&#39;/Mach-O universal binary with 2 architectures/ &amp;&amp; /arm64/ &amp;&amp; /x86_64/ { getline; getline; next } { print }&#39;</span> <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    grep <span class="s1">&#39;x86_64&#39;</span> <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    grep -v <span class="s1">&#39;arm64&#39;</span> <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    grep -v <span class="s1">&#39;universal binary&#39;</span> <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    awk -F<span class="s1">&#39;:| &#39;</span> <span class="s1">&#39;{print $1}&#39;</span>
</span></span></code></pre></div><p>Pipe that through <code>xargs ls -l</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜ find86_64.sh <span class="p">|</span> xargs ls -l
</span></span><span class="line"><span class="cl">-rw-r--r--  <span class="m">1</span> shak  admin  <span class="m">48</span>  <span class="m">9</span> Mar 12:47 ./dummy
</span></span><span class="line"><span class="cl">lrwxr-xr-x  <span class="m">1</span> shak  admin  <span class="m">81</span>  <span class="m">9</span> Mar 12:57 ./python3.10-intel64 -&gt; ../../../Library/Frameworks/Python.framework/Versions/3.10/bin/python3.10-intel64
</span></span><span class="line"><span class="cl">-rw-r--r--  <span class="m">1</span> shak  admin  <span class="m">48</span>  <span class="m">8</span> Mar 23:50 ./taste
</span></span></code></pre></div><p>Note that we have a mix of regular files and symlinks (the <code>l</code> at the beginning of the permission string indicates a symlink, which we can also see by the arrow pointing from <code>./python3.10-intel64</code> to what it is linked to). This is not unexpected. Package managers and installation scripts often write their binaries to a location of choice and make them reachable for execution by symlinking from places like <code>/usr/local/bin/</code>.</p>
<blockquote>
<p><strong>Side Note</strong>: If we were to pipe anything into <code>ls</code> by invoking <code>| ls -l</code>, it would ignore the piped input and run <code>ls -l</code> in the working directory, which is clearly not the behavior we want. This is because <code>ls</code> does not accept input piped into it. <a href="https://man7.org/linux/man-pages/man1/xargs.1.html">xargs</a> converts the piped in input to arguments for <code>ls</code> to consume.</p>
</blockquote>
<p>If we were to pipe the output of <code>find86_64.sh</code> directly into <code>xargs rm</code>, we would remove the files stored in <code>/usr/local/bin</code>. Which works well for the binaries that are stored in this directory but for the ones that are symlinked, we will only have their symlinks removed and not the underlying files.</p>
<p>After a bit of research I found the <a href="https://www.geeksforgeeks.org/readlink-command-in-linux-with-examples/">readlink</a> command which can be used to find the target of a symbolic link.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜ readlink ./python3.10-intel64
</span></span><span class="line"><span class="cl">../../../Library/Frameworks/Python.framework/Versions/3.10/bin/python3.10-intel64
</span></span><span class="line"><span class="cl">➜ <span class="nb">echo</span> <span class="nv">$?</span>
</span></span><span class="line"><span class="cl"><span class="m">0</span>
</span></span></code></pre></div><p>On regular files it outputs nothing and exits with a <code>1</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜ readlink ./dummy
</span></span><span class="line"><span class="cl">➜ <span class="nb">echo</span> <span class="nv">$?</span>
</span></span><span class="line"><span class="cl"><span class="m">1</span>
</span></span></code></pre></div><p>With this knowledge in mind, we can extend our script to read each line and do something different based on whether it&rsquo;s a symlink or a regular file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="cp">#! /bin/bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>find -L . -type f -exec file <span class="o">{}</span> + <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    awk <span class="s1">&#39;/Mach-O universal binary with 2 architectures/ &amp;&amp; /arm64/ &amp;&amp; /x86_64/ { getline; getline; next } { print }&#39;</span> <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    grep <span class="s1">&#39;x86_64&#39;</span> <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    grep -v <span class="s1">&#39;arm64&#39;</span> <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    grep -v <span class="s1">&#39;universal binary&#39;</span> <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    awk -F<span class="s1">&#39;:| &#39;</span> <span class="s1">&#39;{print $1}&#39;</span> <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    <span class="k">while</span> <span class="nb">read</span> -r file<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="o">[</span> -L <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> 
</span></span><span class="line"><span class="cl">            <span class="nv">target</span><span class="o">=</span><span class="k">$(</span>readlink <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">            <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2"> is a symlink targeting </span><span class="nv">$target</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">            <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2"> is a regular file&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="k">done</span>
</span></span></code></pre></div><p>Breaking it down:</p>
<ul>
<li><code>while read -r file</code> reads each line, the <code>-r</code> prevents any backslashes from being processed as escape sequences, preserving the original content of the line.</li>
<li><code>if [ -L &quot;$file&quot; ]</code> <a href="https://koenwoortman.com/bash-script-check-if-file-is-symlink/">checks if the file is a symlink</a></li>
</ul>
<p>And the rest is just <code>echo</code>s. You can imagine how this could be replaced with a bunch of <code>rm</code>s to clean up our unwanted binaries. Disinclined to take responsibility for shenanigans apart from my own, I will leave that final bit of scripting to the reader.</p>
<h2 id="easier-than-i-imagined">Easier Than I Imagined</h2>
<p>I talked to a colleague who got his Apple Silicon Mac more than three years ago when these things were fresh out of the oven. His tales of trial and tribulations made me grateful that I am making the move now when so many projects have adopted Apple Silicon and have arm versions released. I code primarily in Golang, and it was almost trivial to clean up my old Golang versions and install Apple Silicon compatible ones.</p>
<h2 id="wrapping-it-up">Wrapping it Up</h2>
<p>I started a draft for this post when I began my migration journey. I imagined I would have to go through a lot more trouble than I actually ended up going through. Almost every app had an Apple Silicon version that was a simple download+replace operation and the story held true for nearly everything I needed to install via brew. I was up and running in a matter of hours.</p>
<p>Having said that, the scripting journey was a lot of fun. I had to dust off some old Unix tools and learn a few new ones. I also got to play with some of the more arcane features of these tools. I hope you had as much fun reading about it as I did writing about it.</p>
<p>I thank everyone who have been experimenting with Apple Silicon these last few years. They walked with hobbling steps, so people like me could run.</p>
]]></description><media:thumbnail url="https://badgerbadgerbadgerbadger.dev/images/posts/2024-03-13-intel-to-apple-silicon/0fd8a03e-97ac-4afd-8dbb-d93f233945c8_huf20902d106379e3665544e36fe5c508e_927363_1200x0_resize_q80_bgffffff_box_3.jpg"/></item><item><title>Rust Macros (while Crafting Interpreters)</title><link>https://badgerbadgerbadgerbadger.dev/posts/misc/2023-12-23-rust-macros-and-crafting-interpreters/</link><pubDate>Sun, 31 Dec 2023 00:00:00 +0100</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/misc/2023-12-23-rust-macros-and-crafting-interpreters/</guid><description><![CDATA[<p>I have never used <a href="https://www.rust-lang.org/">Rust</a> extensively. The few times I have tried, have ended up in me giving up due to unwinnable fights against the <a href="https://doc.rust-lang.org/1.8.0/book/references-and-borrowing.html">borrow-checker</a>. By the recommendation of a friend I am giving this another shot.</p>
<p>I&rsquo;m making my way through <a href="https://journal.stuffwithstuff.com/">Robert Nystrom&rsquo;s</a> <a href="https://craftinginterpreters.com/">Crafting Interpreters</a> as well as doing <a href="https://adventofcode.com/2023">2023&rsquo;s Advent of Code</a>. I am late to the latter but I am enjoying myself regardless. I am using Rust as my language of choice for both endeavours.</p>
<p>I am on the <a href="https://craftinginterpreters.com/representing-code.html">Representing Code</a> chapter of Crafting Interpreters where Robert mentions using a separate tool for generating the classes for various Expression types instead of hand-writing each since they share a tedious amount of boilerplate. Had I been writing this in Go, this is the path I would have followed. However, with Rust, we have the power of <a href="https://doc.rust-lang.org/book/ch19-06-macros.html">macros</a> which presents me with the perfect opportunity to learn how they work.</p>
<p>In Java (which is the language of choice for the first of the two interpreters we write with Robert, in the book), Robert recommends structuring Expressions as subclasses of an <a href="https://www.digitalocean.com/community/tutorials/abstract-class-in-java">abstract</a> Expression class. The following code shows this, along with the <code>Binary</code> class that would extend the abstract Expression class:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nn">com.craftinginterpreters.lox</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">Expr</span> <span class="o">{</span> 
</span></span><span class="line"><span class="cl">  <span class="kd">static</span> <span class="kd">class</span> <span class="nc">Binary</span> <span class="kd">extends</span> <span class="n">Expr</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Binary</span><span class="o">(</span><span class="n">Expr</span> <span class="n">left</span><span class="o">,</span> <span class="n">Token</span> <span class="n">operator</span><span class="o">,</span> <span class="n">Expr</span> <span class="n">right</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">this</span><span class="o">.</span><span class="na">left</span> <span class="o">=</span> <span class="n">left</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">this</span><span class="o">.</span><span class="na">operator</span> <span class="o">=</span> <span class="n">operator</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">this</span><span class="o">.</span><span class="na">right</span> <span class="o">=</span> <span class="n">right</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">final</span> <span class="n">Expr</span> <span class="n">left</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">final</span> <span class="n">Token</span> <span class="n">operator</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">final</span> <span class="n">Expr</span> <span class="n">right</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">  <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Other expressions...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="o">}</span>
</span></span></code></pre></div><p>Since Rust does not have inheritance, we will need to look to something else. With the help of Github Copilot, I decided to go with using enums representing expressions like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">use</span><span class="w"> </span><span class="k">crate</span>::<span class="n">token</span>::<span class="n">Token</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">enum</span> <span class="nc">Expr</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Binary</span><span class="p">(</span><span class="n">Binary</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">Binary</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">left</span>: <span class="nb">Box</span><span class="o">&lt;</span><span class="n">Expr</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">operator</span>: <span class="nc">Token</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">right</span>: <span class="nb">Box</span><span class="o">&lt;</span><span class="n">Expr</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">Binary</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">left</span>: <span class="nc">Expr</span><span class="p">,</span><span class="w"> </span><span class="n">operator</span>: <span class="nc">Token</span><span class="p">,</span><span class="w"> </span><span class="n">right</span>: <span class="nc">Expr</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Binary</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">left</span>: <span class="nb">Box</span>::<span class="n">new</span><span class="p">(</span><span class="n">left</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">operator</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">right</span>: <span class="nb">Box</span>::<span class="n">new</span><span class="p">(</span><span class="n">right</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>But similar to Robert&rsquo;s approach, instead of writing these out by hand, we shall automate. I&rsquo;m thinking of a macro which might look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="fm">create_expr!</span><span class="p">(</span><span class="n">Binary</span><span class="p">,</span><span class="w"> </span><span class="n">left</span>: <span class="nc">Expr</span><span class="p">,</span><span class="w"> </span><span class="n">operator</span>: <span class="nc">Token</span><span class="p">,</span><span class="w"> </span><span class="n">right</span>: <span class="nc">Expr</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p>Which will, hopefully, result in the definition of the <code>Binary</code> struct as shown above as well as a constructor function. We will define the <code>Expr</code> enum by hand and <code>Token</code> is already available as a type I&rsquo;ve previously defined.</p>
<blockquote>
<p><strong>Note</strong>: Our macro accepts two arguments, the first being the name of the expression type and the second being a variadic list of its fields. In the <code>Binary</code> case, <code>Binary</code> is the name of the expression and <code>left</code>, <code>operator</code> and <code>right</code> would be its fields. For a different Expression type, say, <code>Unary</code> this would be <code>create_expr!(Unary, operator: Token, right: Expr);</code>.</p>
</blockquote>
<p>Now let&rsquo;s read up on how macros work in rust. My first stab at it involved <a href="https://doc.rust-lang.org/rust-by-example/macros.html">this page</a> on the <a href="https://doc.rust-lang.org/stable/rust-by-example/"><em>rust-by_example</em></a> site. While it reads like a perfectly passable statement of facts, I find the language difficult to parse.</p>
<p>When I decided to give learning Rust another go I looked for articles which could explain the <em>magic</em> behind Rust in an intuitive way. One of my first stumbling blocks was the module system and it kept baffling me until I came across <a href="https://www.sheshbabu.com/posts/rust-module-system/">this article</a> and the secrets were finally laid bare. Something in it <em>clicked</em> in my head. I recommend reading it if you have a few minutes to spare. It&rsquo;s not long but very informative.</p>
<p>The tutorial from the <a href="https://blog.logrocket.com/macros-in-rust-a-tutorial-with-examples/">LogRocket blog</a> told me that I could use repeating arguments for macros and have repeating blocks of code generated for those arguments. It also reminded me that I can&rsquo;t write the <code>enum Expr</code> by hand, nor can I do multiple invocations of my <code>create_expr</code> macro with for each of my Expression types. The base <code>enum Expr</code> needs to know all its variants right at the beginning. So taking that into account, I need to reach for something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="fm">macro_rules!</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Binary</span><span class="p">,</span><span class="w"> </span><span class="n">left</span>: <span class="nc">Expr</span><span class="p">,</span><span class="w"> </span><span class="n">operator</span>: <span class="nc">Token</span><span class="p">,</span><span class="w"> </span><span class="n">right</span>: <span class="nc">Expr</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Grouping</span><span class="p">,</span><span class="w"> </span><span class="n">expression</span><span class="w"> </span><span class="n">Expr</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Literal</span><span class="p">,</span><span class="w"> </span><span class="n">value</span>: <span class="nc">Literal</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Unary</span><span class="p">,</span><span class="w"> </span><span class="n">operator</span>: <span class="nc">Token</span><span class="p">,</span><span class="w"> </span><span class="n">right</span><span class="w"> </span><span class="n">Expr</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">)</span><span class="w">
</span></span></span></code></pre></div><p>This is the analog of this block in Robert&rsquo;s Java code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">defineAst</span><span class="o">(</span><span class="n">outputDir</span><span class="o">,</span> <span class="s">&#34;Expr&#34;</span><span class="o">,</span> <span class="n">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Binary   : Expr left, Token operator, Expr right&#34;</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Grouping : Expr expression&#34;</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Literal  : Object value&#34;</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Unary    : Token operator, Expr right&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">));</span>
</span></span></code></pre></div><p>After reading through <a href="https://earthly.dev/blog/rust-macros/">another article</a> that did not help me much (I find the examples too simplistic), I finally came across <a href="https://danielkeep.github.io/practical-intro-to-macros.html">A Practical Intro to Macros in Rust 1.0</a> by <a href="https://github.com/DanielKeep">Daniel Keep</a> which has just the kind of real-world use case with which I like to learn things, not only because it provides the necessary complexity to dive deeper into a system but also for the personal insights the author has gleamed from having attempted a task.</p>
<blockquote>
<p><strong>Side Note</strong>: Daniel only has one other article on his blog, <a href="https://danielkeep.github.io/itercheat_baked.html">Rust Iterator Cheat Sheet</a> which is also a fun read. He seems to be one of those amazing writers who only have the one or two pieces out but from whom you&rsquo;d wish to read a lot more. I&rsquo;ve let him know via a <a href="https://github.com/DanielKeep/DanielKeep.github.io/issues/18">Github issue</a> since that&rsquo;s the only way I could see to reach him. I hope he writes more.</p>
</blockquote>
<p>This was the first stab at defining the <code>create_expr</code> macro:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="n">macro_rules</span><span class="o">!</span><span class="w"> </span><span class="n">create_expr</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">(</span><span class="cp">$($name</span>:<span class="nc">ident</span><span class="p">,</span><span class="w"> </span><span class="cp">$($field</span>:<span class="nc">ident</span><span class="w"> </span>: <span class="cp">$typ</span>: <span class="nc">ty</span><span class="p">),</span><span class="o">+</span><span class="p">);</span><span class="o">+</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">pub</span><span class="w"> </span><span class="k">enum</span> <span class="nc">Expr</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="cp">$(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="cp">$name</span><span class="p">(</span><span class="cp">$name</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">)</span><span class="o">*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="cp">$(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="cp">$name</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">impl</span><span class="w"> </span><span class="cp">$name</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">new</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="nc">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="cp">$name</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                        </span><span class="cp">$($field</span>: <span class="cp">$typ</span><span class="p">),</span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">)</span><span class="o">*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Let&rsquo;s break this down, more for my own understanding than yours.</p>
<p>We are capturing a repeating list of items, separated by a <code>;</code>. That looks like <code>$(&lt;something&gt;);+</code> where <code>&lt;something&gt;</code> can be replaced by one of Rust&rsquo;s <a href="https://doc.rust-lang.org/rust-by-example/macros/designators.html">designators</a>. Whatever <code>&lt;something&gt;</code> turns out to be, we want to capture <em>one or more of it</em>, that&rsquo;s what the <code>+</code> is for (<a href="https://chortle.ccsu.edu/finiteautomata/Section07/sect07_19.html">just like in regex!</a>).</p>
<p>Our <code>&lt;something&gt;</code> happens to be <code>$name:ident, $($field:ident : $typ: ty),+</code>. There are two pieces here:</p>
<ol>
<li><code>$name:ident</code></li>
<li><code>$($field:ident : $typ: ty),+</code></li>
</ol>
<p>The first is for capturing our syntax for defining the name of the expression type, while the second is for capturing a list of one or more fields. You&rsquo;ll notice the second piece also has the form <code>$(&lt;something&gt;),+</code> but this time with a <code>,</code> separator. And our <code>&lt;something&gt;</code> in this case happens to be <code>$field:ident : $typ: ty</code> which matches syntax of the form <code>left: Expr</code> (for an explanation of what <code>ty</code> and <code>ident</code> mean, I recommend reading through some of the blog posts I&rsquo;ve already linked).</p>
<p>Trying to compile it, it works!</p>
<p>But trying to use it with an invocation</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="fm">create_expr!</span><span class="p">(</span><span class="n">Binary</span><span class="p">,</span><span class="w"> </span><span class="n">left</span>: <span class="nc">Expr</span><span class="p">,</span><span class="w"> </span><span class="n">operator</span>: <span class="nc">Token</span><span class="p">,</span><span class="w"> </span><span class="n">right</span>: <span class="nc">Expr</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">             </span><span class="n">Grouping</span><span class="p">,</span><span class="w"> </span><span class="n">expression</span>: <span class="nc">Expr</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">             </span><span class="n">Literal</span><span class="p">,</span><span class="w"> </span><span class="n">value</span>: <span class="nc">Token</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">             </span><span class="n">Unary</span><span class="p">,</span><span class="w"> </span><span class="n">operator</span>: <span class="nc">Token</span><span class="p">,</span><span class="w"> </span><span class="n">right</span>: <span class="nc">Expr</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p>does not work!</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">error: expected expression, found <span class="sb">`</span>Expr<span class="sb">`</span>
</span></span><span class="line"><span class="cl">  --&gt; src/expr/mod.rs:19:35
</span></span><span class="line"><span class="cl">   <span class="p">|</span>
</span></span><span class="line"><span class="cl"><span class="m">18</span> <span class="p">|</span>                       <span class="nv">$name</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">   <span class="p">|</span>                       ----- <span class="k">while</span> parsing this struct
</span></span><span class="line"><span class="cl"><span class="m">19</span> <span class="p">|</span>                           <span class="k">$(</span><span class="nv">$field</span>: <span class="nv">$typ</span><span class="k">)</span>,+
</span></span><span class="line"><span class="cl">   <span class="p">|</span>                                     ^^^^ expected expression
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl"><span class="m">27</span> <span class="p">|</span> / create_expr!<span class="o">(</span>Binary, left: Expr, operator: Token, right: Expr<span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="m">28</span> <span class="p">|</span> <span class="p">|</span>              Grouping, expression: Expr<span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="m">29</span> <span class="p">|</span> <span class="p">|</span>              Literal, value: Token<span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="m">30</span> <span class="p">|</span> <span class="p">|</span>              Unary, operator: Token, right: Expr
</span></span><span class="line"><span class="cl"><span class="m">31</span> <span class="p">|</span> <span class="p">|</span>             <span class="o">)</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">   <span class="p">|</span> <span class="p">|</span>_____________- in this macro invocation
</span></span><span class="line"><span class="cl">   <span class="p">|</span>
</span></span><span class="line"><span class="cl">   <span class="o">=</span> note: this error originates in the macro <span class="sb">`</span>create_expr<span class="sb">`</span> <span class="o">(</span>in Nightly builds, run with -Z macro-backtrace <span class="k">for</span> more info<span class="o">)</span>
</span></span></code></pre></div><p>I have no idea what I did wrong here. So I will turn to Github Copilot and see if it can help me. <a href="https://docs.github.com/en/copilot/github-copilot-chat/about-github-copilot-chat">Copilot Chat</a> to be specific; it seems to be a version of ChatGPT restricted to answering programming-related questions.</p>
<p>It&rsquo;s answers pointed out that I had made some glaring initial mistakes. I blame my girlfriend&rsquo;s vacuuming (that was a joke, don&rsquo;t @ me, I love her very much).</p>
<p>This is the offending piece of code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">$(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="cp">$name</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">impl</span><span class="w"> </span><span class="cp">$name</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">new</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="nc">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="cp">$name</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="cp">$($field</span>: <span class="cp">$typ</span><span class="p">),</span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">)</span><span class="o">*</span><span class="w">
</span></span></span></code></pre></div><p>I forgot to use <code>$($field: $typ),+</code> in the struct definition block and then accept arguments for the <code>new</code> method that are then assigned to the struct declaration, like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">$(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="cp">$name</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="cp">$($field</span>: <span class="cp">$typ</span><span class="p">),</span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">impl</span><span class="w"> </span><span class="cp">$name</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="cp">$($field</span>: <span class="cp">$typ</span><span class="p">),</span><span class="o">+</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="bp">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="cp">$($field</span><span class="p">),</span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">)</span><span class="o">*</span><span class="w">
</span></span></span></code></pre></div><p>Oh and I need to call the macro with <a href="https://dhghomon.github.io/easy_rust/Chapter_53.html">Boxed</a> <code>Expr</code> types to avoid having to allocate infinite heap 👼.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="fm">create_expr!</span><span class="p">(</span><span class="n">Binary</span><span class="p">,</span><span class="w"> </span><span class="n">left</span>: <span class="nb">Box</span><span class="o">&lt;</span><span class="n">Expr</span><span class="o">&gt;</span><span class="p">,</span><span class="w"> </span><span class="n">operator</span>: <span class="nc">Token</span><span class="p">,</span><span class="w"> </span><span class="n">right</span>: <span class="nb">Box</span><span class="o">&lt;</span><span class="n">Expr</span><span class="o">&gt;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">             </span><span class="n">Grouping</span><span class="p">,</span><span class="w"> </span><span class="n">expression</span>: <span class="nb">Box</span><span class="o">&lt;</span><span class="n">Expr</span><span class="o">&gt;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">             </span><span class="n">Literal</span><span class="p">,</span><span class="w"> </span><span class="n">value</span>: <span class="nc">Token</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">             </span><span class="n">Unary</span><span class="p">,</span><span class="w"> </span><span class="n">operator</span>: <span class="nc">Token</span><span class="p">,</span><span class="w"> </span><span class="n">right</span>: <span class="nb">Box</span><span class="o">&lt;</span><span class="n">Expr</span><span class="o">&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p>And this finally compiles!</p>
<blockquote>
<p><strong>Random Aside</strong>: I&rsquo;ve always struggled with understanding <a href="https://doc.rust-lang.org/rust-by-example/scope/lifetime.html">Lifetimes</a> in Rust. However, I recall an evening when a former colleague and I were walking to a guy&rsquo;s place to play some DnD. We had taken a tram to the nearest tram stop to said guy&rsquo;s place and were walking from the tram stop to his house. We were both eager and beginner rustaceans and were talking to each other about our own intuitions regarding various Rust systems. Somewhere along that walk I had an epiphany and I felt in my <em>bones</em> that I finally understood Rust&rsquo;s lifetime system. Too bad I did not immediately make a note because, for the life of me, I cannot recall what the epiphany was and here I am, back to not really understanding Lifetimes.</p>
</blockquote>
<p>Now let&rsquo;s try to run this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">exp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">expr</span>::<span class="n">Unary</span>::<span class="n">new</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">token</span>::<span class="n">Token</span>::<span class="n">new</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">token</span>::<span class="n">TokenType</span>::<span class="n">Minus</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;-&#34;</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nb">None</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Box</span>::<span class="n">new</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">expr</span>::<span class="n">Expr</span>::<span class="n">Literal</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">expr</span>::<span class="n">Literal</span>::<span class="n">new</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">token</span>::<span class="n">Literal</span>::<span class="n">Number</span><span class="p">(</span><span class="mf">123.0</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="fm">println!</span><span class="p">(</span><span class="s">&#34;Welcome to rlox! </span><span class="si">{:?}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">exp</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p>And after a generous sprinkling of <code>#[derive(Debug)]</code>, it works!</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">Welcome to rlox! Unary <span class="o">{</span> operator: Token <span class="o">{</span> token_type: Minus, lexeme: <span class="s2">&#34;-&#34;</span>, line: 1, literal: None, column: <span class="m">0</span> <span class="o">}</span>, right: Literal<span class="o">(</span>Literal <span class="o">{</span> value: Number<span class="o">(</span>123.0<span class="o">)</span> <span class="o">})</span> <span class="o">}</span>
</span></span></code></pre></div><p>At least for this very contrived, manual invocation. As I continue writing the interpreter, I will no doubt come across some bugs and rough edges. But I am glad I was able to figure this out in Rust&rsquo;s own style of doing things.</p>
<blockquote>
<p>Note: <code>rlox</code> is the name of the first interpreter I&rsquo;m building, from the book. Robert implements the first in Java and calls it <em>jlox</em>, and the second in C and calls it <em>clox</em>. I will be writing both in Rust, so once I&rsquo;m done with <code>rlox</code>, I&rsquo;m not quite sure what to name the second.</p>
</blockquote>
<blockquote>
<p><strong>Random Aside</strong>: I avoided asking Githup Copilot to do all my work for me. I think I could have fed it the macro syntax I wanted to use and the result I was looking for and it would have spat out exactly the macro that I needed. But I feel that would have robbed me of the opportunity to learn how macros in Rust actually work. For example, did you know that unlike, say, <a href="https://www.programiz.com/c-programming/c-preprocessor-macros">C</a>, macros in rust do not expand into text but rather into <a href="https://rustc-dev-guide.rust-lang.org/macro-expansion.html">directly into AST</a>. I feel these are the fascinating tidbits are what you miss unless you do your own research. However, for running through and making sense of error messages, I find it to be the perfect tool.</p>
</blockquote>
]]></description></item><item><title>Love for Expression Languages</title><link>https://badgerbadgerbadgerbadger.dev/posts/misc/2023-12-26-love-for-expression-based-languages/</link><pubDate>Tue, 26 Dec 2023 00:00:00 +0100</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/misc/2023-12-26-love-for-expression-based-languages/</guid><description><![CDATA[<p>I love expression languages. In Rust I can do this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">last_integer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="n">last_number_which_list</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">word_to_integer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">last_number_in_words</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">last_number_in_words</span><span class="p">.</span><span class="n">parse</span>::<span class="o">&lt;</span><span class="kt">i32</span><span class="o">&gt;</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">};</span><span class="w">
</span></span></span></code></pre></div><p>While in Go, to achieve the same result I would have to do something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">last_integer</span> <span class="o">:=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="nx">last_number_which_list</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">last_integer</span> <span class="p">=</span> <span class="nf">word_to_integer</span><span class="p">(</span><span class="nx">last_number_in_words</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">last_integer</span> <span class="p">=</span> <span class="nx">strconv</span><span class="p">.</span><span class="nf">Atoi</span><span class="p">(</span><span class="nx">last_number_in_words</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Or what feels clunkier but comes closer to an expression:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">last_integer</span> <span class="p">=</span> <span class="kd">func</span><span class="p">()</span> <span class="kt">int</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="nx">last_number_which_list</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nf">word_to_integer</span><span class="p">(</span><span class="nx">last_number_in_words</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">strconv</span><span class="p">.</span><span class="nf">Atoi</span><span class="p">(</span><span class="nx">last_number_in_words</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}()</span>
</span></span></code></pre></div><blockquote>
<p>Note: Error handling has been elided in all code samples for the sake of brevity. The idiomatic naming conventions and casing styles have also been ignored in order to provide as similar code samples as possible.</p>
</blockquote>
<p>I wonder what the costs are associated with having expression blocks in your programming language.</p>
]]></description></item><item><title>Building Rant</title><link>https://badgerbadgerbadgerbadger.dev/posts/misc/2022-11-14-building-rant/</link><pubDate>Mon, 14 Nov 2022 21:13:25 -0400</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/misc/2022-11-14-building-rant/</guid><description><![CDATA[<h2 id="intro">Intro</h2>
<p>Sometime in mid to late 2021 (which is a period I&rsquo;ve entirely made up because I don&rsquo;t <em>actually</em> remember when any of this happened), I needed to rant on my company Slack. Something had happened to ruin my day and I needed to get my feelings out.</p>
<p>But ranting takes more effort than you&rsquo;d think. If you want your rant to have <em>impact</em> there needs to be just the right amount of <code>!</code>s in just the right places. Too little, and your words may as well be the kisses of tiny wind fairies for all the notice they&rsquo;ll get. Too many and you&rsquo;re overwhelming the fundamental message of the rant. Similarly a <code>!</code> where it doesn&rsquo;t belong is a crime against rehetoric. You don&rsquo;t want people to lose the flow of your beautiful ranty sentences and focus overmuch on the <code>!</code>s.</p>
<p>Moreover, in this age of &ldquo;show don&rsquo;t tell&rdquo;, emojis have a greater impact than even words. And they too require injection in all the right places.</p>
<p>So yeah, ranting takes effort. And the problem with effort is that you cannot spill your guts in a stream-of-conciousness rant <em>and</em> format it to have the right impact all at the same time. Try to inject <code>!</code>s or emojis in your rants, and if you want to do a good job of it, you <em>will</em> have to calm down, you <em>will</em> have to think it through. Rare is the person who can both give in to their inner child and throw a text tantrum while simultenously channeling their inner Millenial teen.</p>
<p>Enter, Rant! a toy project to help me, well, rant!</p>
<p>Rant! reads in my ranty text, whether it be a line or a paragraph and injects <code>!</code>s and emojis in all the right places. To say that Rant! has taken my ranting to the next level would be an understatement. While before my rants barely conveyed the depth of my outrage, now they scream themselves out to the world. No, really, all the letters come out in uppercase.</p>
<p>For example, if I wanted to rant the following rant:</p>
<pre tabindex="0"><code>Chinese demand is raising the price. I TOLD YOU SO! Only I can say horrible untrue things about me, it may be an open blank. Please send me flowers &amp; a total bust!
</code></pre><p>I <em>could</em> just post it into Slack, or I could invoke Rant! to do it for me!</p>
<pre tabindex="0"><code>CHINESE DEMAND IS RAISING THE PRICE!!! 😠 I TOLD YOU SO!!! 🤬  ONLY I CAN SAY HORRIBLE UNTRUE THINGS ABOUT ME, IT MAY BE AN OPEN BLANK!!! 😡 PLEASE SEND ME FLOWERS &amp; A TOTAL BUST!!! 😤
</code></pre><p>So how did I go about building Rant!?</p>
<h2 id="you-never-know">You Never Know</h2>
<p>You never know when a random thing that you learned could come in use. A long time back I remember going through <a href="https://craftinginterpreters.com/">Robert Nystrom&rsquo;s Crafting Interpreters</a> blog/book. I never got past the lexing and parsing stages, but it turns out that was enough to help me when I wanted to build Rant!</p>
<p>Crafting Interpreters was my first introduction to the world of programming language design and implementation. I learned what it means to lex tokens and parse them into an AST. I learned&hellip;well, not much beyond this, but as I&rsquo;ve already mentioned, just knowing the lexing and parsing stages was all I needed to get working on Rant! In fact, I didn&rsquo;t even need to know the parsing stage, being able to lex a source string into tokens was enough.</p>
<p>The long-winded point I&rsquo;m trying to make is that you never know what could end up being useful. So learn random things.</p>
<p>I started with a few rules in mind for what I wanted to happen when I used Rant! Some of these things in no particular order are:</p>
<ul>
<li>All text should be in upper case. An input of <code>I'm so mad</code> should give an output of <code>I'M SO MAD</code></li>
<li>When a sentence ends with a period, I wanted to replace that with three (THREE, I tell you!!!) <code>!</code>s and an angry emoji. Given <code>I'm so mad.</code>, I wanted <code>I'M SO MAD!!! 😡</code></li>
<li>When a sentence ended with a <code>?</code>, I wanted it replaced by a <code>?!</code> and an angry emoji. I think you&rsquo;re startingt to see the pattern.</li>
<li>Whenever either a single <code>!</code>, a double <code>!!</code> or a triple <code>!!!</code> is encountered, replace them all with a triple <code>!!!</code>s and an angry emoji. Any sequence of <code>!</code>s beyond that should be preserved as is.</li>
</ul>
<p>I&rsquo;m not sure if I&rsquo;ve covered all the rules here but it should be sufficient to paint a picture.</p>
<h2 id="scanninglexingtokenization">Scanning/Lexing/Tokenization</h2>
<p>First scan the input string using the lexing approach Robert talks about in the <a href="https://craftinginterpreters.com/scanning.html">Scanning</a> chapter of his excellent book. I convert the string into tokens that I can then operate on. Once I have tokens such as <code>Bang</code>, <code>Period</code>, <code>BangBang</code>, etc. I can loop over them and either print them as is (like the case for the <code>Dot</code> token which represents a <code>.</code> without any preceeding or succeeding space, indicating it&rsquo;s probably not being used as a period at the end of a sentence), or replaced by something more exciting (such as a <code>Bang</code> token being replaced by <code>!!!</code> + <code>&lt;random-angry-emoji</code>&gt;).</p>
<p>Honestly, that&rsquo;s it, that&rsquo;s the bulk of the ranting logic. There&rsquo;s code in the repo (check the end of the post) that performs the Slack integration, Oauth and a web API, but none of that is really that interesting.</p>
<p>The final interesting bit is not wanting to return the same random emoji twice in a row. No one wants to see two 😡s one after the other. So I keep track of the emojis returned by the random emoji method and remove it from the pool of potential emojis. I&rsquo;m sure the algorithm I use can be improved upon, but it does what I need it to and I&rsquo;m not worrying about performance at the moment.</p>
<h2 id="the-code">The Code</h2>
<p>You can find the code on <a href="https://github.com/BadgerBadgerBadgerBadger/rant">Github</a> and maybe <a href="https://rant.badgerbadgerbadgerbadger.dev/">give it a try</a> and give me ideas for improvement. Hope you enjoyed reading about this fun little project.</p>
]]></description></item><item><title>Yet Another mTLS Tutorial</title><link>https://badgerbadgerbadgerbadger.dev/posts/misc/2021-03-05-yet-another-mtls-tutorial/</link><pubDate>Thu, 04 Mar 2021 21:19:25 -0400</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/misc/2021-03-05-yet-another-mtls-tutorial/</guid><description><![CDATA[<p>I&rsquo;ve come across many mTLS tutorials on the internet but none of them took me through the process, end-to-end, in a satisfying fashion. I hope to do in this tutorial is to start with the ideas of TLS and mTLS and conclude with an implementation you could reasonably productionize. Although we&rsquo;ll be using a Golang server as our backend and Traefik as our reverse proxy, these ideas translate to other technologies as well.</p>
<h1 id="lets-start-with-the-barebones">Let&rsquo;s Start with the Barebones</h1>
<h2 id="serve-me">Serve Me!</h2>
<p>In a traditional TLS setup, the kind you encounter every time you see that green padlock icon in your browser&rsquo;s address bar, it is the browser verifying the server&rsquo;s identity. When you go to a website you access it via an address such as <a href="https://badgerbadgerbadgerbadger.dev">badgerbadgerbadgerbadger.dev</a>. If the website has TLS support (and if you encounter one in 2021 that <em>does not</em> have TLS support, seriously, don&rsquo;t use it), the browser will ask it to send over its server-side certificate in order to verify that the website is actually who it says it is. The website sends back such a certificate which has a <a href="https://www.godaddy.com/garage/whats-a-fully-qualified-domain-name-fqdn-and-whats-it-good-for/"><em>CommonName</em> (also known as a <em>Fully Qualified Domain Name</em>, or <em>FQDN</em>)</a> field which <em>should</em> match the address you entered in the address bar. For <a href="https://badgerbadgerbadgerbadger.dev">https://badgerbadgerbadgerbadger.dev</a> the returned certificate <em>should</em> have a <em>CommonName</em> of <strong>badgerbadgerbadgerbadger.dev</strong>. Your browser will see that the <em>CommonName</em> and the name in the address match and it will be satisfied that you are indeed visiting a website who is what it claims to be.</p>
<p>&ldquo;But wait!&rdquo; I hear you say, &ldquo;Couldn&rsquo;t anyone send back a certificate with a <em>CommonName</em> field set to whatever?&rdquo; Astute observation. Indeed, anyone can create a certificate, attach whatever <em>CommonName</em> they choose and send it back to your browser&hellip;</p>
<p>&hellip;except, they can&rsquo;t.</p>
<p>That&rsquo;s where <a href="https://support.dnsimple.com/articles/what-is-certificate-authority/"><em>Certificate Authorities (CAs)</em></a> come in. These are organizations that have (allegedly) gone through rigorous audits and whose trustworthiness has been verified to an extent that if they say that a website is who it says it is, that&rsquo;s good enough for browsers to trust that website. And how does this process work, you say? We&rsquo;ll get to that when we get to the more hands-on section of this post.</p>
<h2 id="time-to-reciprocate">Ti(m)e to Reciprocate</h2>
<p>Alright, so web browsers know who the server is. The server can be trusted. But can the browser be trusted? Maybe, maybe not. But to make our use case more practical let&rsquo;s move away from the example of an end-user using a browser and move to two backend machines talking to each other. Let&rsquo;s imagine we have a traditional client-server architecture where <em>Machine A</em> needs to get data from <em>Machine B</em> at random intervals, and the way that <em>Machine B</em> has decided it wants <em>Machine A</em> to authenticate is Mutual TLS.</p>
<p>If you understood how a server identified itself to the client by using a certificate trusted by a third party, you&rsquo;re already most of the way to understanding mTLS: it&rsquo;s kind of in the name.</p>
<p>In mTLS (<em>Mutual</em> TLS) the server sends its certificate as usual, but the client has to send one too. Let&rsquo;s say <em>Machine B</em> (the server) identifies itself as <code>machineb.com</code> and has a certificate with a matching <em>CommonName</em>, that&rsquo;s one side of the equation. In order for the other side (<em>Machine A</em>, the client) to do its part, it also needs to obtain a certificate. This certificate doesn&rsquo;t have to have a specific <em>CommonName</em>. It doesn&rsquo;t <em>have</em> to be <code>machinea.com</code>. But if we&rsquo;re only using public CAs, the easiest thing for <em>Machine A</em> to do is to prove to a CA that they own <code>machinea.com</code> and then be issued a certificate for that <em>CommonName</em>. <em>Machine B</em>, on its end will know to expect requests from <em>Machine A</em> authenticated by a certificate that has the <em>CommonName</em> of <code>machinea.com</code>.</p>
<p>Are there other ways to do this? Sure. <em>Machine B</em> could host its own CA and issue a certificate to <em>Machine A</em>. That way <em>Machine B</em> knows exactly what to expect since it&rsquo;s the one that issued the certificate.</p>
<p>I&rsquo;ll end this section with a link to a <a href="https://medium.com/sitewards/the-magic-of-tls-x509-and-mutual-authentication-explained-b2162dec4401">really nice article that has some informative diagrams</a>.</p>
<h1 id="lets-get-our-hands-dirty">Let&rsquo;s Get Our Hands Dirty</h1>
<p>It&rsquo;s time to get into some hands-on learning. Let&rsquo;s start with our backend.</p>
<h2 id="reflect-thine-headers">Reflect Thine Headers</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;fmt&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;net/http&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">http</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/headers&#34;</span><span class="p">,</span> <span class="nx">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">http</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">(</span><span class="s">&#34;:6969&#34;</span><span class="p">,</span> <span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">headers</span><span class="p">(</span><span class="nx">w</span> <span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span> <span class="nx">req</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">for</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">headers</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">req</span><span class="p">.</span><span class="nx">Header</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">h</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">headers</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">fmt</span><span class="p">.</span><span class="nf">Fprintf</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="s">&#34;%v: %v\n&#34;</span><span class="p">,</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">h</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>If you&rsquo;re unfamiliar with <a href="https://golang.org/">Golang</a> here&rsquo;s what the above code does:</p>
<ul>
<li>starts a http server on port <a href="https://i.pinimg.com/474x/32/c6/db/32c6db68576a9535537cdc0ce82a3a1f.jpg">6969</a></li>
<li>responds with any headers sent to it</li>
</ul>
<p>Feel free to implement this in your language of choice if you don&rsquo;t have the Golang toolchain set up. Once you have your backend running, let&rsquo;s move on to the frontend.</p>
<h2 id="curl-it">Curl It</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl -H <span class="s2">&#34;heyo: mayo&#34;</span> http://localhost:6969/headers
</span></span></code></pre></div><p>What, did you think we were going to write some HTML and JS code? Do I look like I&rsquo;m in a masochistic mood? <a href="https://curl.se/">Curl</a> is good enough for our needs. If you don&rsquo;t have it installed, google (or your choice of search engine) is your friend.</p>
<p>If you managed to run that curl command and your backend has been running, you should get a response like this:</p>
<pre tabindex="0"><code>User-Agent: curl/7.64.1
Accept: */*
Heyo: mayo
</code></pre><p>As expected, we&rsquo;ve had our headers reflected back to us. So far so good.</p>
<p>We <em>could</em> go ahead and implement mTLS right there in our backend Golang code but in a robust production system you want your concerns to be separated. Our backend shouldn&rsquo;t have to worry about TLS protocol stuff, so let&rsquo;s move all of that to our <a href="https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/">reverse proxy</a>.</p>
<p>For our choice of reverse proxy there&rsquo;s always good old <a href="https://www.nginx.com/">Nginx</a> which has a straightforward way to <a href="https://smallstep.com/hello-mtls/doc/server/nginx">configure mTLS</a>. If you&rsquo;re feeling especially nostalgic you might even find yourself reaching for <a href="https://httpd.apache.org/">Apache</a> where you can also <a href="https://blog.behrang.org/2019/08/11/ssl-mututal-authentication-apache-http-client.html">configure mTLS</a>.</p>
<p>But we&rsquo;re hip and cool (or at least we like to think so) so we&rsquo;re going to be using the (relatively) new kid on the block <a href="https://traefik.io/traefik/">Traefik Proxy</a>. I&rsquo;m sure you can figure out how to <a href="https://doc.traefik.io/traefik/getting-started/install-traefik/">install it</a>, by yourself. Do that and come back once you&rsquo;ve read up on <a href="https://doc.traefik.io/traefik/getting-started/configuration-overview/">Traefik&rsquo;s configuration</a> management.</p>
<h2 id="traefiking">Traefiking</h2>
<p>Back? Figured out how to load <a href="https://doc.traefik.io/traefik/getting-started/configuration-overview/#the-static-configuration">static</a> and <a href="https://doc.traefik.io/traefik/getting-started/configuration-overview/#the-dynamic-configuration">dynamic</a> configuration in Traefik? Good, so here&rsquo;s what we&rsquo;ll be using.</p>
<blockquote>
<p><strong>Note</strong>: I&rsquo;m using version 2.2.1, the latest (at the time of this writing) is 2.4.x, but I don&rsquo;t think there should be a big difference in our experiences using either one.</p>
</blockquote>
<h3 id="static-config">Static Config</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c">## Static configuration</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">log</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">level</span> <span class="p">=</span> <span class="s2">&#34;DEBUG&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">api</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">dashboard</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"><span class="nx">debug</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"><span class="nx">insecure</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">entryPoints</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">entryPoints</span><span class="p">.</span><span class="nx">web</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">address</span> <span class="p">=</span> <span class="s2">&#34;:8089&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">entryPoints</span><span class="p">.</span><span class="nx">websecure</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">address</span> <span class="p">=</span> <span class="s2">&#34;:443&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">providers</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">providers</span><span class="p">.</span><span class="nx">file</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">directory</span> <span class="p">=</span> <span class="s2">&#34;path/to/dynamic-config/directory&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">accessLog</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">accessLog</span><span class="p">.</span><span class="nx">fields</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">defaultMode</span> <span class="p">=</span> <span class="s2">&#34;keep&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">accessLog</span><span class="p">.</span><span class="nx">fields</span><span class="p">.</span><span class="nx">names</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">defaultMode</span> <span class="p">=</span> <span class="s2">&#34;keep&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">accessLog</span><span class="p">.</span><span class="nx">fields</span><span class="p">.</span><span class="nx">headers</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">defaultMode</span> <span class="p">=</span> <span class="s2">&#34;keep&#34;</span>
</span></span></code></pre></div><p>Here&rsquo;s what&rsquo;s happening:</p>
<ul>
<li>We&rsquo;re starting a dashboard where we can view our configurations</li>
<li>We&rsquo;re starting a web listener (regular http) at port 8089</li>
<li>We&rsquo;re starting a secure listener (https) at port 443</li>
<li>We&rsquo;re providing the <em>directory</em> where our dynamic config is stored (you can have a ton of dynamic config files and they all get merged into one big config)</li>
</ul>
<p>Let&rsquo;s look at the dynamic config.</p>
<h3 id="dynamic-config">Dynamic Config</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">routers</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">routers</span><span class="p">.</span><span class="nx">traeifk-api</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">rule</span> <span class="p">=</span> <span class="s2">&#34;Host(`traefik.local`)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">service</span> <span class="p">=</span> <span class="s2">&#34;api@internal&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">routers</span><span class="p">.</span><span class="nx">mtls-tut</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">rule</span> <span class="p">=</span> <span class="s2">&#34;Host(`server.mtls-tut.local`)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">service</span> <span class="p">=</span> <span class="s2">&#34;mtls-tut&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">services</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">services</span><span class="p">.</span><span class="nx">mtls-tut</span><span class="p">.</span><span class="nx">loadBalancer</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">http</span><span class="p">.</span><span class="nx">services</span><span class="p">.</span><span class="nx">mtls-tut</span><span class="p">.</span><span class="nx">loadBalancer</span><span class="p">.</span><span class="nx">servers</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="nx">url</span> <span class="p">=</span> <span class="s2">&#34;http://127.0.0.1:6969/&#34;</span>
</span></span></code></pre></div><p>Here&rsquo;s what&rsquo;s happening:</p>
<ul>
<li>We&rsquo;re defining a <a href="https://doc.traefik.io/traefik/routing/routers/"><em>router</em></a> to route any requests that match the <a href="https://doc.traefik.io/traefik/routing/routers/#rule"><em>rule</em></a> <em>Host(<code>server.mtls-tut.local</code>)</em>. Any requests that arrive with the host value set to <code>server.mtls-tut.local</code> will get picked up by the router <code>mtls-tut</code>. The same is done for the traefik api (which is used by its dashboard).</li>
<li>We&rsquo;re defining a <a href="https://doc.traefik.io/traefik/routing/services/"><em>service</em></a> called <code>mtls-tut</code> which load-balances to our backend server running at port <a href="https://i.imgflip.com/4k39g9.jpg">6969</a>. In our router configuration we&rsquo;ve defined this service as the one that should be serving all requests for <code>server.mtls-tut.local</code></li>
</ul>
<p>Now start Traefik however you prefer (docker, binary, whatever), make sure it can access both the static and dynamic files and you&rsquo;ve pointed it to pick up the static config. If you did everything right Traefik will start up and print logs.</p>
<pre tabindex="0"><code>INFO[0000] Configuration loaded from file: /Users/shak/installs/traefik/traefik.toml
INFO[2023-02-04T10:01:47+01:00] Traefik version 2.2.1 built on 2020-04-29T18:09:41Z
DEBU[2023-02-04T10:01:47+01:00] Static configuration loaded {&#34;global&#34;:{&#34;checkNewVersion&#34;:true},&#34;serversTransport&#34;:{&#34;maxIdleConnsPerHost&#34;:200},&#34;entryPoints&#34;:{&#34;traefik&#34;:{&#34;address&#34;:&#34;:8080&#34;,&#34;transport&#34;:{&#34;lifeCycle&#34;:{&#34;graceTimeOut&#34;:10000000000},&#34;respondingTimeouts&#34;:{&#34;idleTimeout&#34;:180000000000}},&#34;forwardedHeaders&#34;:{},&#34;http&#34;:{}},&#34;web&#34;:{&#34;address&#34;:&#34;:8089&#34;,&#34;transport&#34;:{&#34;lifeCycle&#34;:{&#34;graceTimeOut&#34;:10000000000},&#34;respondingTimeouts&#34;:{&#34;idleTimeout&#34;:180000000000}},&#34;forwardedHeaders&#34;:{},&#34;http&#34;:{}},&#34;websecure&#34;:{&#34;address&#34;:&#34;:443&#34;,&#34;transport&#34;:{&#34;lifeCycle&#34;:{&#34;graceTimeOut&#34;:10000000000},&#34;respondingTimeouts&#34;:{&#34;idleTimeout&#34;:180000000000}},&#34;forwardedHeaders&#34;:{},&#34;http&#34;:{}}},&#34;providers&#34;:{&#34;providersThrottleDuration&#34;:2000000000,&#34;file&#34;:{&#34;directory&#34;:&#34;/Users/shak/projects/mtls-example/traeifk/dynamic&#34;,&#34;watch&#34;:true}},&#34;api&#34;:{&#34;insecure&#34;:true,&#34;dashboard&#34;:true,&#34;debug&#34;:true},&#34;log&#34;:{&#34;level&#34;:&#34;DEBUG&#34;,&#34;format&#34;:&#34;common&#34;},&#34;accessLog&#34;:{&#34;format&#34;:&#34;common&#34;,&#34;filters&#34;:{},&#34;fields&#34;:{&#34;defaultMode&#34;:&#34;keep&#34;,&#34;names&#34;:{&#34;defaultMode&#34;:&#34;keep&#34;},&#34;headers&#34;:{&#34;defaultMode&#34;:&#34;keep&#34;}}}}
...
...
INFO[2023-02-04T10:01:47+01:00] Starting provider *file.Provider {&#34;directory&#34;:&#34;/Users/shak/projects/mtls-example/traeifk/dynamic&#34;,&#34;watch&#34;:true}
INFO[2023-03-04T10:01:47+01:00] Starting provider *traefik.Provider {}
DEBU[2023-03-04T10:01:47+01:00] Configuration received from provider file: {&#34;http&#34;:{&#34;routers&#34;:{&#34;mtls-tut&#34;:{&#34;service&#34;:&#34;mtls-tut&#34;,&#34;rule&#34;:&#34;Host(`server.mtls-tut.local`)&#34;},&#34;traeifk-api&#34;:{&#34;service&#34;:&#34;api@internal&#34;,&#34;rule&#34;:&#34;Host(`traefik.local`)&#34;}},&#34;services&#34;:{&#34;mtls-tut&#34;:{&#34;loadBalancer&#34;:{&#34;servers&#34;:[{&#34;url&#34;:&#34;http://127.0.0.1:6969/&#34;}],&#34;passHostHeader&#34;:null}}}},&#34;tcp&#34;:{},&#34;udp&#34;:{},&#34;tls&#34;:{}}  providerName=file
</code></pre><p>Traefik started with the static config we provided and then loaded the dynamic config. Let&rsquo;s make sure the dashboard is up. By default Traefik starts the dashboard on port <a href="http://localhost:8080">8080</a></p>
<p>If you see this page</p>
<p><img src="/images/posts/2021-03-05-yet-another-mtls-tutorial/110094067-af563f00-7d9b-11eb-96ce-12d82cadaf9f_hu9a2df35e97fe440da5bfd20fdc3bc3da_261487_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2021-03-05-yet-another-mtls-tutorial/110094067-af563f00-7d9b-11eb-96ce-12d82cadaf9f_hu9a2df35e97fe440da5bfd20fdc3bc3da_261487_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2021-03-05-yet-another-mtls-tutorial/110094067-af563f00-7d9b-11eb-96ce-12d82cadaf9f_hu9a2df35e97fe440da5bfd20fdc3bc3da_261487_960x0_resize_q75_h2_box_3.webp 960w, /images/posts/2021-03-05-yet-another-mtls-tutorial/110094067-af563f00-7d9b-11eb-96ce-12d82cadaf9f_hu9a2df35e97fe440da5bfd20fdc3bc3da_261487_1600x0_resize_q75_h2_box_3.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="471" loading="lazy" decoding="async" alt="image"></p>
<p>Traefik is up and running. If we go to <code>/dashboard/#/http/routers/mtls-tut@file</code></p>
<p><img src="/images/posts/2021-03-05-yet-another-mtls-tutorial/110095621-656e5880-7d9d-11eb-96d8-54aa3e8dc790_hu1ddbf98b87114c6536382313d910226c_223385_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2021-03-05-yet-another-mtls-tutorial/110095621-656e5880-7d9d-11eb-96d8-54aa3e8dc790_hu1ddbf98b87114c6536382313d910226c_223385_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2021-03-05-yet-another-mtls-tutorial/110095621-656e5880-7d9d-11eb-96d8-54aa3e8dc790_hu1ddbf98b87114c6536382313d910226c_223385_960x0_resize_q75_h2_box_3.webp 960w, /images/posts/2021-03-05-yet-another-mtls-tutorial/110095621-656e5880-7d9d-11eb-96d8-54aa3e8dc790_hu1ddbf98b87114c6536382313d910226c_223385_1600x0_resize_q75_h2_box_3.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="647" loading="lazy" decoding="async" alt="image"></p>
<p>We can confirm that our desired configurations were picked up by Traefik. Note the empty <strong>TLS</strong> and <strong>Middlewares</strong> sections. We&rsquo;ll be filling those soon.</p>
<p>The final piece of this first part of the puzzle is modifying our <code>/etc/hosts</code> file and adding a new entry:</p>
<pre tabindex="0"><code>127.0.0.1 server.mtls-tut.local
</code></pre><p>Let&rsquo;s try curl again.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl -H <span class="s2">&#34;heyo: mayo&#34;</span> http://server.mtls-talk.local:8089/headers
</span></span></code></pre></div><p>Note that we are now accessing the http entrypoint of our reverse proxy and not our backend. Your response should look something like:</p>
<pre tabindex="0"><code>X-Real-Ip: 127.0.0.1
Accept-Encoding: gzip
Heyo: mayo
X-Forwarded-For: 127.0.0.1
X-Forwarded-Host: server.mtls-tut.local:8089
X-Forwarded-Port: 8089
X-Forwarded-Proto: http
User-Agent: curl/7.64.1
Accept: */*
</code></pre><p>All those extra headers are telling us that Traefik is successfully routing our request to our backend at port <a href="https://camo.githubusercontent.com/42cef8be0203e843fc1c25b4be92bb6314721e180e3c23cfd1019387d2de628e/687474703a2f2f69302e6b796d2d63646e2e636f6d2f70686f746f732f696d616765732f6f726967696e616c2f3030302f3435302f3135342f3832302e6a706567">6969</a>.</p>
<p>Phew! All that work and we haven&rsquo;t even started on the TLS part of things.</p>
<h2 id="tls-time">TLS Time</h2>
<p>First we&rsquo;re going to need a CA. You <em>could</em> go to the trouble of getting a certificate online. If you have two domain names you own, getting certificates from <a href="https://letsencrypt.org/">Let&rsquo;s Encrypt</a> is an inexpensive solution. But for our tutorial we&rsquo;ll stick with our local resources. Let&rsquo;s make a CA!</p>
<h3 id="break-open-that-bottle-of-openssl">Break Open that Bottle of openssl</h3>
<p>Making your own CA is <a href="https://gist.github.com/fntlnz/cf14feb5a46b2eda428e000157447309">surprisingly simple</a> (getting others to trust it is a different matter). You&rsquo;ll need <a href="https://github.com/openssl/openssl">openssl</a> which <em>should</em> already be installed on your platform of choice but if it isn&rsquo;t, google is your friend, again.</p>
<p>Once you have it, it&rsquo;s time to make the most important decision so far: what are you going to call your CA? I&rsquo;ll keep things simple for myself and call mine <code>mtls-tut.ca</code>. Not very imaginative but it&rsquo;s been a <em>long</em> week.</p>
<p>The first thing we need to do now that we&rsquo;ve decided a name is to create the <em><a href="https://www.thesslstore.com/blog/root-certificates-intermediate/">Root</a> <a href="https://www.kinamo.be/en/support/faq/what-are-root-and-intermediate-ssl-certificates">Certificate</a></em>. The <em>Root Certificate</em> is a <strong>Big Deal™</strong>. This is the certificate that will be acting as&ndash; you guessed it&ndash; the root of all certificates issued by our CA. Other certificates will draw their authority <em>from</em> this Root Certificate.</p>
<p>First, we need a private key.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ openssl genrsa <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -out mtls-tut.ca.key <span class="m">4096</span>
</span></span></code></pre></div><p>The <a href="https://sectigo.com/resource-library/public-key-vs-private-key">private key</a> is basically a really big random number. We need to keep this number secret. Most CAs don&rsquo;t even keep these keys online. If it can&rsquo;t be accessed via a network it (probably) can&rsquo;t be hacked. CAs take the security of their private keys <em><a href="https://security.stackexchange.com/questions/24896/how-do-certification-authorities-store-their-private-root-keys">very seriously</a></em>. In our case, we can choose to be somewhat lackadaisical about the security of our private key. I&rsquo;m going to store it in a directory called <code>mtls-tut</code> and pretend it is guarded by digital pitbulls.</p>
<p><img src="/images/posts/2021-03-05-yet-another-mtls-tutorial/110125634-d32d7b00-7dc3-11eb-9c2f-c8f27adb255d_hu41a1f29a7dd03d8ca80fbf71e0ba996a_1219175_960x0_resize_q75_h2_box.webp" srcset="/images/posts/2021-03-05-yet-another-mtls-tutorial/110125634-d32d7b00-7dc3-11eb-9c2f-c8f27adb255d_hu41a1f29a7dd03d8ca80fbf71e0ba996a_1219175_480x0_resize_q75_h2_box.webp 480w, /images/posts/2021-03-05-yet-another-mtls-tutorial/110125634-d32d7b00-7dc3-11eb-9c2f-c8f27adb255d_hu41a1f29a7dd03d8ca80fbf71e0ba996a_1219175_960x0_resize_q75_h2_box.webp 960w, /images/posts/2021-03-05-yet-another-mtls-tutorial/110125634-d32d7b00-7dc3-11eb-9c2f-c8f27adb255d_hu41a1f29a7dd03d8ca80fbf71e0ba996a_1219175_1600x0_resize_q75_h2_box.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="640" loading="lazy" decoding="async" alt="timothy-perry-G9_4owLR9b4-unsplash (1)"></p>
<p>If you ran the above command you should see output like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">Generating RSA private key, <span class="m">4096</span> bit long modulus
</span></span><span class="line"><span class="cl">..............................................................................................................++
</span></span><span class="line"><span class="cl">.........................++
</span></span><span class="line"><span class="cl">e is <span class="m">65537</span> <span class="o">(</span>0x10001<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">❯ ls
</span></span><span class="line"><span class="cl">mtls-tut.ca.key
</span></span><span class="line"><span class="cl">~/mtls-tut ❯
</span></span></code></pre></div><p>With a key in hand we need to derive a <a href="https://blog.keyfactor.com/what-is-x509-certificate">X509 certificate</a>. I will let you read up on what the X509 format looks like. For our purposes we need to know that the certificate is a collection of fields such as <em>country</em>, <em>city</em>, <em>region</em>, some others, and most importantly, the <em>CommonName</em>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ openssl req -x509 -new -nodes <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -key mtls-tut.ca.key <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -sha256 -days <span class="m">1024</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -out mtls-talk.ca.crt
</span></span></code></pre></div><p>You will get an interactive prompt asking you to enter values for the fields I mentioned above.</p>
<pre tabindex="0"><code>You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter &#39;.&#39;, the field will be left blank.
-----
Country Name (2 letter code) []:BE
State or Province Name (full name) []:Flanders
Locality Name (eg, city) []:
Organization Name (eg, company) []:
Organizational Unit Name (eg, section) []:
Common Name (eg, fully qualified host name) []:mtls-tut.ca
Email Address []:
</code></pre><p>Most of it is informational except the <em>CommonName</em> which is <em>very important</em>. Get that one right.</p>
<pre tabindex="0"><code>❯ ll
-rw-r--r-- mtls-talk.ca.crt
-rw-r--r-- mtls-tut.ca.key
</code></pre><p>We now have a <em>private key</em> and a <em>X509 certificate</em> for our CA. And that&rsquo;s enough to get us started on the next step: creating the server-side certificate. The process is <em>very similar</em> to what we did for the CA certificate with one key difference.</p>
<p>We&rsquo;ll start the same as before by generating a <em>private key</em>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ openssl genrsa -out server.mtls-tut.local.key <span class="m">2048</span>
</span></span></code></pre></div><p>Next we&rsquo;re going to derive a <em>X509 certificate</em> from this private key, <em>however</em>, we will also sign this certificate with the CA key and CA cert we generated earlier.</p>
<p>To do this we first need a <a href="https://www.sslshopper.com/what-is-a-csr-certificate-signing-request.html">CSR (Certificate Signing Request)</a>. A CSR is a <em>request</em> that is signed by the private key of the server and submitted to the CA. If the CA is happy with it, they will issue a <em>certificate</em> based on this CSR.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ openssl req -new -sha256 -key server.mtls-tut.local.key <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -subj <span class="s2">&#34;/C=BE/ST=AN/CN=server.mtls-tut.local&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -out server.mtls-tut.local.csr
</span></span></code></pre></div><p>The CSR includes the same fields we used to generate the certificate of our CA (I decided to include it as part of the command, this time, instead of starting an interactive session). In essence they serve the same function: identification of an entity, be it a server, organization, or other. In the CA&rsquo;s case there isn&rsquo;t any higher authority to authenticate it. It <em>is</em> the highest digital authority. For a server certificate, the CA is the higher authority that authenticates the server certificate. Since we have our own CA, we can sign our own certificate, giving it our stamp of approval.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ openssl x509 -req -in server.mtls-tut.local.csr <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -CA mtls-talk.ca.crt -CAkey mtls-tut.ca.key <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -CAcreateserial -out server.mtls-tut.local.crt <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -days <span class="m">500</span> -sha256
</span></span></code></pre></div><p>If we break down the command a bit, we&rsquo;ll see that we&rsquo;re taking the CSR as an input, accepting the CA key and cert, and apart from a few config options, outputting a <em>X509 certificate</em>.</p>
<p>Phew again! We have our CA and we managed to get some server certificates out of it. Can we see some TLS action already?!</p>
<h2 id="putting-it-all-together">Putting it All Together</h2>
<p>Now we&rsquo;re going to talk to our backend via our reverse proxy using <em>https</em>. Instead of making the request to <a href="http://server.mtls-tut.local:8089/headers">http://server.mtls-tut.local:8089/headers</a> we&rsquo;ll be making it to <a href="https://server.mtls-tut.local/headers">https://server.mtls-tut.local/headers</a> (the 443 is implied). If we try it right now&hellip;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl -H <span class="s2">&#34;heyo: mayo&#34;</span> https://server.mtls-tut.local/headers
</span></span><span class="line"><span class="cl">curl: <span class="o">(</span>60<span class="o">)</span> SSL certificate problem: unable to get <span class="nb">local</span> issuer certificate
</span></span><span class="line"><span class="cl">More details here: https://curl.haxx.se/docs/sslcerts.html
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">curl failed to verify the legitimacy of the server and therefore could not
</span></span><span class="line"><span class="cl">establish a secure connection to it. To learn more about this situation and
</span></span><span class="line"><span class="cl">how to fix it, please visit the web page mentioned above.
</span></span></code></pre></div><p>&hellip;curl is going to complain. If you visit the address in your browser you&rsquo;ll see:</p>
<p><img src="/images/posts/2021-03-05-yet-another-mtls-tutorial/110142147-28728800-7dd6-11eb-8912-e7293bc97999_hu4ecb2476f79b4f4e5698d6c66fc4d4d7_211600_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2021-03-05-yet-another-mtls-tutorial/110142147-28728800-7dd6-11eb-8912-e7293bc97999_hu4ecb2476f79b4f4e5698d6c66fc4d4d7_211600_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2021-03-05-yet-another-mtls-tutorial/110142147-28728800-7dd6-11eb-8912-e7293bc97999_hu4ecb2476f79b4f4e5698d6c66fc4d4d7_211600_960x0_resize_q75_h2_box_3.webp 960w, /images/posts/2021-03-05-yet-another-mtls-tutorial/110142147-28728800-7dd6-11eb-8912-e7293bc97999_hu4ecb2476f79b4f4e5698d6c66fc4d4d7_211600_1600x0_resize_q75_h2_box_3.webp 1600w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="815" loading="lazy" decoding="async" alt="image"></p>
<p>If we go ahead and <a href="https://www.howtogeek.com/292076/how-do-you-view-ssl-certificate-details-in-google-chrome/">inspect the certificate</a> that&rsquo;s being returned to the browser:</p>
<p><img src="/images/posts/2021-03-05-yet-another-mtls-tutorial/110142288-4b9d3780-7dd6-11eb-9969-1bd4c7e66345_hu229402af687a0d4c664e0f0472018063_192503_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2021-03-05-yet-another-mtls-tutorial/110142288-4b9d3780-7dd6-11eb-9969-1bd4c7e66345_hu229402af687a0d4c664e0f0472018063_192503_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2021-03-05-yet-another-mtls-tutorial/110142288-4b9d3780-7dd6-11eb-9969-1bd4c7e66345_hu229402af687a0d4c664e0f0472018063_192503_960x0_resize_q75_h2_box_3.webp 960w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="547" loading="lazy" decoding="async" alt="image"></p>
<p>Hey, that&rsquo;s not right! We went to all that trouble to generate a server-side certificate but Traeifk isn&rsquo;t even using it! What gives?</p>
<p>Well, we give. I think. Never really explored that expression. We haven&rsquo;t configured Traefik to use our certificates, yet. Let&rsquo;s open up the dynamic configuration and add a few lines.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">routers</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">routers</span><span class="p">.</span><span class="nx">traeifk-api</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">rule</span> <span class="p">=</span> <span class="s2">&#34;Host(`traefik.local`)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">service</span> <span class="p">=</span> <span class="s2">&#34;api@internal&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">routers</span><span class="p">.</span><span class="nx">mtls-tut</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">rule</span> <span class="p">=</span> <span class="s2">&#34;Host(`server.mtls-tut.local`)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">service</span> <span class="p">=</span> <span class="s2">&#34;mtls-tut&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">services</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">services</span><span class="p">.</span><span class="nx">mtls-tut</span><span class="p">.</span><span class="nx">loadBalancer</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">http</span><span class="p">.</span><span class="nx">services</span><span class="p">.</span><span class="nx">mtls-tut</span><span class="p">.</span><span class="nx">loadBalancer</span><span class="p">.</span><span class="nx">servers</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="nx">url</span> <span class="p">=</span> <span class="s2">&#34;http://127.0.0.1:6969/&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tls</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">tls</span><span class="p">.</span><span class="nx">certificates</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="nx">certFile</span> <span class="p">=</span> <span class="s2">&#34;/path/to/mtls-tut/server.mtls-tut.local.crt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">keyFile</span> <span class="p">=</span> <span class="s2">&#34;/path/to/mtls-tut/server.mtls-tut.local.key&#34;</span>
</span></span></code></pre></div><p>We added our server cert and key to Traeifk&rsquo;s TLS Certificate store. Now Traefik won&rsquo;t have to rely on its default certificate and can serve up ours, instead.</p>
<p>Now let&rsquo;s try curling again:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl -H <span class="s2">&#34;heyo: mayo&#34;</span> https://server.mtls-tut.local/headers
</span></span><span class="line"><span class="cl">curl: <span class="o">(</span>60<span class="o">)</span> SSL certificate problem: unable to get <span class="nb">local</span> issuer certificate
</span></span><span class="line"><span class="cl">More details here: https://curl.haxx.se/docs/sslcerts.html
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">curl failed to verify the legitimacy of the server and therefore could not
</span></span><span class="line"><span class="cl">establish a secure connection to it. To learn more about this situation and
</span></span><span class="line"><span class="cl">how to fix it, please visit the web page mentioned above.
</span></span></code></pre></div><p>Dang it! It still doesn&rsquo;t work. Let&rsquo;s take a look at the browser:</p>
<p><img src="/images/posts/2021-03-05-yet-another-mtls-tutorial/110143321-6ae89480-7dd7-11eb-9e98-abb3f9a9c82b_hude13b4fe15ad5a7618a29a526eadc9f6_199582_960x0_resize_q75_h2_box_3.webp" srcset="/images/posts/2021-03-05-yet-another-mtls-tutorial/110143321-6ae89480-7dd7-11eb-9e98-abb3f9a9c82b_hude13b4fe15ad5a7618a29a526eadc9f6_199582_480x0_resize_q75_h2_box_3.webp 480w, /images/posts/2021-03-05-yet-another-mtls-tutorial/110143321-6ae89480-7dd7-11eb-9e98-abb3f9a9c82b_hude13b4fe15ad5a7618a29a526eadc9f6_199582_960x0_resize_q75_h2_box_3.webp 960w" sizes="(min-width: 800px) 720px, 100vw" width="960" height="549" loading="lazy" decoding="async" alt="image"></p>
<p>Alright, at least it&rsquo;s returning the right certificate (and not some default ones generated by Traefik), but the browser and curl both still complain. Why?</p>
<h3 id="certificate-stores">Certificate Stores</h3>
<p>Each install of an OS (Operating System) comes bundled with a store of Root Certificates. Remember that Root Certificate we generated a few paragraphs earlier that identified our CA and had all those fields about location and name? Every CA has Root Certificates that they distribute (not the private key, mind you, <em>never</em> the private key) once the CA has been deemed trustworthy. OSes and even browsers can choose to include these Root Certificates in their Certificate Store.</p>
<p>What this means is that when a certificate issued by one of these CAs to a server makes its way to a browser, the browser verifies if one of the Root Certificates it holds in its Certificate Store has signed this certificate. If that&rsquo;s the case, the browser knows the certificate can be trusted. Similarly curl relies on the OSes certificate store (it is entirely possible for your browser and your OS to have different Certificate Stores, though <em>most</em>, if not all, of the Root Certificates in their stores will be identical) to figure out the validity of a server certificate.</p>
<p>In our case our CA that we made five whole minutes back isn&rsquo;t known to either our browser or to curl.</p>
<h3 id="trust">Trust</h3>
<p>One way to remedy that is by adding our CA&rsquo;s Root Certificate to these stores, but that&rsquo;s drastic. There&rsquo;s a reason those stores only have trusted Root Certificates in them and you shouldn&rsquo;t mess around with that.</p>
<p>What we can do instead is to tell curl to trust our CA for the duration of a specific request:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl --cacert /path/to/mtls-tut/mtls-talk.ca.crt <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>	-H <span class="s2">&#34;heyo: mayo&#34;</span> https://server.mtls-tut.local/headers
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Accept-Encoding: gzip
</span></span><span class="line"><span class="cl">Accept: */*
</span></span><span class="line"><span class="cl">X-Forwarded-Proto: https
</span></span><span class="line"><span class="cl">X-Real-Ip: 127.0.0.1
</span></span><span class="line"><span class="cl">X-Forwarded-For: 127.0.0.1
</span></span><span class="line"><span class="cl">X-Forwarded-Host: server.mtls-tut.local
</span></span><span class="line"><span class="cl">X-Forwarded-Port: <span class="m">443</span>
</span></span><span class="line"><span class="cl">User-Agent: curl/7.64.1
</span></span><span class="line"><span class="cl">Heyo: mayo
</span></span></code></pre></div><p>Et voila! Now the certificate returned by our server is trusted because we have explicitly told curl which Root Certificate to trust as the higher authority.</p>
<h2 id="finally-mtls">Finally, mTLS!</h2>
<p>We&rsquo;re in the final stretch, only a little bit more, I promise.</p>
<p>Let&rsquo;s finally add mTLS to our reverse proxy. We start by updating our Traefik dynamic config, again:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">routers</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">routers</span><span class="p">.</span><span class="nx">traeifk-api</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">rule</span> <span class="p">=</span> <span class="s2">&#34;Host(`traefik.local`)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">service</span> <span class="p">=</span> <span class="s2">&#34;api@internal&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">routers</span><span class="p">.</span><span class="nx">mtls-tut</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">rule</span> <span class="p">=</span> <span class="s2">&#34;Host(`server.mtls-tut.local`)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">service</span> <span class="p">=</span> <span class="s2">&#34;mtls-tut&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">routers</span><span class="p">.</span><span class="nx">mtls-tut</span><span class="p">.</span><span class="nx">tls</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">options</span> <span class="p">=</span> <span class="s2">&#34;myTLSOptions&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">services</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">services</span><span class="p">.</span><span class="nx">mtls-tut</span><span class="p">.</span><span class="nx">loadBalancer</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">http</span><span class="p">.</span><span class="nx">services</span><span class="p">.</span><span class="nx">mtls-tut</span><span class="p">.</span><span class="nx">loadBalancer</span><span class="p">.</span><span class="nx">servers</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="nx">url</span> <span class="p">=</span> <span class="s2">&#34;http://127.0.0.1:6969/&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tls</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">tls</span><span class="p">.</span><span class="nx">certificates</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="nx">certFile</span> <span class="p">=</span> <span class="s2">&#34;/path/to/mtls-tut/server.mtls-tut.local.crt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">keyFile</span> <span class="p">=</span> <span class="s2">&#34;/path/to/mtls-tut/server.mtls-tut.local.key&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tls</span><span class="p">.</span><span class="nx">options</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tls</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">myTLSOptions</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tls</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">myTLSOptions</span><span class="p">.</span><span class="nx">clientAuth</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">clientAuthType</span> <span class="p">=</span> <span class="s2">&#34;RequireAnyClientCert&#34;</span>
</span></span></code></pre></div><p>This time not only did we add a new section for configuring client-auth, we also applied that config to our router. Now our router should require <em>some</em> client certificate to be present. And if we try curling&hellip;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl --cacert mtls-talk.ca.crt <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>	-H <span class="s2">&#34;heyo: mayo&#34;</span> https://server.mtls-tut.local/headers
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">curl: <span class="o">(</span>35<span class="o">)</span> error:1401E412:SSL routines:CONNECT_CR_FINISHED:sslv3 alert bad certificate
</span></span></code></pre></div><p>&hellip; we see that it is so. The server won&rsquo;t let us in unless we provide a client certificate. So let&rsquo;s generate one.</p>
<h3 id="the-same-old-song-and-dance">The Same Old Song and Dance</h3>
<p>The process of generating a client-side certificate is <em>identical</em> to creating a server-side one. Remember how we made a private key for the server, made a CSR out of it, then got the CA to sign that CSR giving us the final cert? We&rsquo;re going to repeat the exact same steps, only this time changing the <em>CommonName</em> to <code>client.mtls-tut.local</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ openssl genrsa -out client.mtls-tut.local.key <span class="m">2048</span>
</span></span><span class="line"><span class="cl">Generating RSA private key, <span class="m">2048</span> bit long modulus
</span></span><span class="line"><span class="cl">............................+++
</span></span><span class="line"><span class="cl">..................+++
</span></span><span class="line"><span class="cl">e is <span class="m">65537</span> <span class="o">(</span>0x10001<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">❯ openssl req -new -sha256 -key client.mtls-tut.local.key <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -subj <span class="s2">&#34;/C=BE/ST=AN/CN=client.mtls-tut.local&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -out client.mtls-tut.local.csr
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">❯ openssl x509 -req -in client.mtls-tut.local.csr <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -CA mtls-talk.ca.crt -CAkey mtls-tut.ca.key <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -CAcreateserial -out client.mtls-tut.local.crt <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -days <span class="m">500</span> -sha256
</span></span><span class="line"><span class="cl">Signature ok
</span></span><span class="line"><span class="cl"><span class="nv">subject</span><span class="o">=</span>/C<span class="o">=</span>BE/ST<span class="o">=</span>AN/CN<span class="o">=</span>client.mtls-tut.local
</span></span><span class="line"><span class="cl">Getting CA Private Key
</span></span></code></pre></div><p>If all went well, we should have 3 new files:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ ll
</span></span><span class="line"><span class="cl">-rw-r--r-- client.mtls-tut.local.crt
</span></span><span class="line"><span class="cl">-rw-r--r-- client.mtls-tut.local.csr
</span></span><span class="line"><span class="cl">-rw-r--r-- client.mtls-tut.local.key
</span></span><span class="line"><span class="cl">-rw-r--r-- mtls-talk.ca.crt
</span></span><span class="line"><span class="cl">-rw-r--r-- mtls-talk.srl
</span></span><span class="line"><span class="cl">-rw-r--r-- mtls-tut.ca.key
</span></span><span class="line"><span class="cl">-rw-r--r-- server.mtls-tut.local.crt
</span></span><span class="line"><span class="cl">-rw-r--r-- server.mtls-tut.local.csr
</span></span><span class="line"><span class="cl">-rw-r--r-- server.mtls-tut.local.key
</span></span></code></pre></div><p>Awesome! Now we need to ask curl to <em>use</em> these files when making a request:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl --key client.mtls-tut.local.key --cert client.mtls-tut.local.crt <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>	--cacert mtls-talk.ca.crt -H <span class="s2">&#34;heyo: mayo&#34;</span> https://server.mtls-tut.local/headers
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Accept: */*
</span></span><span class="line"><span class="cl">X-Forwarded-For: 127.0.0.1
</span></span><span class="line"><span class="cl">X-Forwarded-Port: <span class="m">443</span>
</span></span><span class="line"><span class="cl">Accept-Encoding: gzip
</span></span><span class="line"><span class="cl">User-Agent: curl/7.64.1
</span></span><span class="line"><span class="cl">Heyo: mayo
</span></span><span class="line"><span class="cl">X-Forwarded-Host: server.mtls-tut.local
</span></span><span class="line"><span class="cl">X-Forwarded-Proto: https
</span></span><span class="line"><span class="cl">X-Real-Ip: 127.0.0.1
</span></span></code></pre></div><p>We have successfully implemented mTLS! Break out da champagne&hellip;</p>
<p>&hellip;later.</p>
<p>We still have one last thing to do (I <em>promise</em>, this is the last bit).</p>
<h3 id="trust-again">Trust, Again</h3>
<p>Eagle-eyed readers will have noticed that our Traefik configuration is trusting any certificate that we send. For curl to work we had to tell it exactly from which CA to expect the server certifcate. But our Traefik configuration is requiring <em>any</em> client cert. It&rsquo;s in the name <code>RequireAnyClientCert</code>. The problem with this being how easy it is to create your own CA and generate certificates. We just did it in a few minutes.</p>
<p>So in order to restrict Traefik to trusting only <em>specific</em> CAs we have to submit those Root Certificates into Traeifk&rsquo;s trust store and set the value of <code>clientAuthType</code> to <code>RequireAndVerifyClientCert</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">routers</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">routers</span><span class="p">.</span><span class="nx">traeifk-api</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">rule</span> <span class="p">=</span> <span class="s2">&#34;Host(`traefik.local`)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">service</span> <span class="p">=</span> <span class="s2">&#34;api@internal&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">routers</span><span class="p">.</span><span class="nx">mtls-tut</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">rule</span> <span class="p">=</span> <span class="s2">&#34;Host(`server.mtls-tut.local`)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">service</span> <span class="p">=</span> <span class="s2">&#34;mtls-tut&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">routers</span><span class="p">.</span><span class="nx">mtls-tut</span><span class="p">.</span><span class="nx">tls</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">options</span> <span class="p">=</span> <span class="s2">&#34;myTLSOptions&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">services</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http</span><span class="p">.</span><span class="nx">services</span><span class="p">.</span><span class="nx">mtls-tut</span><span class="p">.</span><span class="nx">loadBalancer</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">http</span><span class="p">.</span><span class="nx">services</span><span class="p">.</span><span class="nx">mtls-tut</span><span class="p">.</span><span class="nx">loadBalancer</span><span class="p">.</span><span class="nx">servers</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="nx">url</span> <span class="p">=</span> <span class="s2">&#34;http://127.0.0.1:6969/&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tls</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">tls</span><span class="p">.</span><span class="nx">certificates</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="nx">certFile</span> <span class="p">=</span> <span class="s2">&#34;/path/to/mtls-tut/server.mtls-tut.local.crt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">keyFile</span> <span class="p">=</span> <span class="s2">&#34;/path/to/mtls-tut/server.mtls-tut.local.key&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tls</span><span class="p">.</span><span class="nx">options</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tls</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">myTLSOptions</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tls</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">myTLSOptions</span><span class="p">.</span><span class="nx">clientAuth</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">clientAuthType</span> <span class="p">=</span> <span class="s2">&#34;RequireAndVerifyClientCert&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">caFiles</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;/path/to/mtls-tut/client.mtls-tut.local.crt&#34;</span><span class="p">]</span>
</span></span></code></pre></div><p>If you curl again:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl --key client.mtls-tut.local.key --cert client.mtls-tut.local.crt <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>	--cacert mtls-talk.ca.crt -H <span class="s2">&#34;heyo: mayo&#34;</span> https://server.mtls-tut.local/headers
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Accept-Encoding: gzip
</span></span><span class="line"><span class="cl">X-Forwarded-Host: server.mtls-tut.local
</span></span><span class="line"><span class="cl">X-Real-Ip: 127.0.0.1
</span></span><span class="line"><span class="cl">Accept: */*
</span></span><span class="line"><span class="cl">Heyo: mayo
</span></span><span class="line"><span class="cl">X-Forwarded-For: 127.0.0.1
</span></span><span class="line"><span class="cl">X-Forwarded-Port: <span class="m">443</span>
</span></span><span class="line"><span class="cl">X-Forwarded-Proto: https
</span></span><span class="line"><span class="cl">User-Agent: curl/7.64.1
</span></span></code></pre></div><p>Well of course it will work! The certificates we&rsquo;re using for our client-auth was generated by the CA we&rsquo;ve told Traefik to trust. But if we try to use a certificate generated by a different CA or change our Traefik config to trust a different CA for client-auth, this call would fail.</p>
<p>Verifying that last claim is left as an exercise for the reader.</p>
]]></description><media:thumbnail url="https://badgerbadgerbadgerbadger.dev/images/posts/2021-03-05-yet-another-mtls-tutorial/47cdf97b-50ae-4ee3-8a47-aa5c2094a118_hu37cda7dcdc5bc354567bf3e1d6d96743_745639_1200x0_resize_q80_bgffffff_box_3.jpg"/></item><item><title>Generating the Mandelbrot Set</title><link>https://badgerbadgerbadgerbadger.dev/posts/misc/2019-05-17-generating-mandelbrot-fractals/</link><pubDate>Tue, 11 Jun 2019 21:19:25 -0400</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/misc/2019-05-17-generating-mandelbrot-fractals/</guid><description><![CDATA[<h2 id="update">Update</h2>
<blockquote>
<p>The shared github repo for the end product of this post is in version 2 state. <a href="https://github.com/lovasoa">@lovasoa</a> kindly contributed a pull request which massivley improved performance and allowed the rendering to happen at full screen with much better speed and taught me many things in the process. I&rsquo;ve added these improvements as an updated section at the end of the post. Many thanks <a href="https://github.com/lovasoa">@lovasoa</a>. The first version, on which most of this post is based is still available for perusal at <a href="https://github.com/ScionOfBytes/smooth-mandelbrot/tree/v1.0">https://github.com/ScionOfBytes/smooth-mandelbrot/tree/v1.0</a>.</p>
</blockquote>
<h2 id="intro">Intro</h2>
<p>A few weeks ago, somewhere in late April, I became fascinated with the Mandelbrot Set. I came across an interesting <a href="https://www.youtube.com/watch?v=FFftmWSzgmk">video</a> by <a href="https://www.bensparks.co.uk/">Ben Sparks</a> on <a href="https://www.youtube.com/channel/UCoxcjq-8xIDTYp3uz647V5A">Numberphile</a> where he speaks of them. He starts off with the idea of stable iterations first, on a one-dimensional number line and then, on a two dimensional complex plane, demonstrating how&ndash; on iterating on the square of a given number&ndash; the result can either blow up and tend towards infinity or remain stable and tend towards 0. Or at least that&rsquo;s the idea I got. Someone more math-y correct me if I&rsquo;m wrong.</p>
<p>The visualisations in the video were quite nicely done and I got an itch to try them myself. My best friend has been studying Daniel Shiffman&rsquo;s <a href="https://www.youtube.com/watch?v=HerCR8bw_GE&amp;list=PLRqwX-V7Uu6Zy51Q-x9tMWIv9cueOFTFA&amp;index=1">Code! Programming with p5.js for Beginners</a> and that reminded me of my own early years in programming when I kept myself motivated by building trippy animations and other cool stuff you can do with a library like p5.js (though back then only <a href="https://processing.org">Processing</a> existed).</p>
<p>So I thought, why not try my hand at generating the Mandelbrot Set?</p>
<h2 id="complex-numbers">Complex Numbers</h2>
<p>First, we need to talk about Complex Numbers. There are <a href="https://www.youtube.com/watch?v=gHUHZXjpwOE">a ton</a> <a href="https://www.youtube.com/watch?v=sZrOxm5Gszk">of videos</a> <a href="https://www.youtube.com/watch?v=SP-YJe7Vldo">on YouTube</a> about Complex Numbers. I won&rsquo;t go into the details of what they are <em>exactly</em> or why they exist. Let&rsquo;s just say that for our purposes we can think of complex numbers existing on a line perpindicular to our regular number line. If you take a piece of graph paper and mark a line from left to right, and then another line that crosses that line at 90 degrees, from top to bottom, you can imagine that the horizontal line represents the real number line, and the vertical line represents the complex number line. This gives us a Complex <em>Plane</em> to work with.</p>
<p>I highly suggest watching the videos I&rsquo;ve linked to really grasp the ideas behind Complex Numbers and how they represent a plane, but for the purpose of this post, let&rsquo;s just say we have a grid and two numbers to work with, one number representing a horizontal position on the grid, and one representing a vertical. And these give us a point.</p>
<h2 id="the-mandelbrot-set">The Mandelbrot Set</h2>
<p>The Mandelbrot set starts with the idea of iteration. Say you have a number <strong>z</strong>. This is a complex number of two parts, but for now let&rsquo;s imagine it&rsquo;s a simple number.</p>
<p>Let&rsquo;s square <strong>z</strong> giving us <strong>z<!-- raw HTML omitted -->2<!-- raw HTML omitted --></strong>. Let&rsquo;s take another number <strong>c</strong> and add it to our result to get <strong>z<!-- raw HTML omitted -->2<!-- raw HTML omitted --> + c</strong>. Let this be our new <strong>z</strong>.</p>
<p>We have the operation <strong>z&rsquo; = z<!-- raw HTML omitted -->2<!-- raw HTML omitted --> + c</strong></p>
<p>We can do this again but this time, instead of our original <strong>z</strong> we are using our new value <strong>z&rsquo;</strong>. This way, we keep feeding the result of the previous operation into the next one. We keep iterating like this until we have a reason to stop. We can start with a value <strong>z = 0</strong>, but it&rsquo;s the <strong>c</strong> that&rsquo;s noteworthy here.</p>
<p>The Mandelbrot Set, as I understand it, is the set of all numbers in the Complex Plane that, when put under this iteration, does not blow up. The <strong>c</strong> in our equation is the number we&rsquo;re testing.</p>
<p>In this context, if, upon iteration the value of <strong>z</strong> becomes larger than 2 at any time, we consider it to have <em>blown up</em>. At this point we know that it will tend towards infinity.</p>
<p>So, when speaking of the Mandelbrot set what we do is, for each point in our grid, we test to see if, when put under iteration via the <strong>z&rsquo; = z<!-- raw HTML omitted -->2<!-- raw HTML omitted --> + c</strong> operation, where <strong>c = the point</strong>, if the value blows up, then the point is not part of the Mandelbrot Set. And if it does not, then it is.</p>
<p>Honestly, this is all very math-y and I am going to stop with the Math now. I highly recommend you watch the above videos for a solid understanding of Complex Numbers and the Mandelbrot Set. From here on out, I will be talking mostly code.</p>
<p>Let&rsquo;s say we have a grid of pixels, and each pixel can be represented by <code>x / y</code> coordinates. Let&rsquo;s also say that we have a function (the programming kind, not the math kind) <code>testMandelbrot</code> which takes a coordinate <code>c</code> and tells us if <code>c</code> is in the Mandelbrot Set or not. That function and most of the math we need to perform is in <a href="https://github.com/ScionOfBytes/smooth-mandelbrot/blob/master/js/compy-stuff.js#L20">this file</a>. As you can see, it requires very little code.</p>
<p>For a proper understanding of why all this math is happening (and especially if my attempt to explain wasn&rsquo;t sufficient), have a look at these videos.</p>
<ol>
<li><a href="https://www.youtube.com/watch?v=NGMRB4O922I&amp;t=16s">The Mandelbrot Set - Numberphile</a> - More about the Mandelbrot Set.</li>
<li><a href="https://www.youtube.com/watch?v=MwjsO6aniig">Mandelbrot Set on Vsauce</a> - A very entertaining and educational video by the beloved Michael Stevens.</li>
</ol>
<p>And then read this excellent little page by Karl Sims&ndash; <a href="https://www.karlsims.com/julia.html">Understanding Julia and Mandelbrot Sets by Karl Sims</a>&ndash; this one helped me a <em>lot</em>.</p>
<h2 id="early-attempts">Early Attempts</h2>
<p>My first attempt was semi-successful. I made many mistakes, wrote a lot of messy code, and managed to get something going that rendered very slowly even for a 450 x 200 resolution canvas. You can find it <a href="https://editor.p5js.org/scionofbytes/full/UfCfqKVrY">here</a>. If you play around with the resolution, you will notice that upping it slightly to 600 x 350 will make it render significantly slower than the initial 400 x 200 setting. This is no surprise. It might only be an increase of 200 pixels horizontally and 150 pixels vertically, but overall, there are now 30K more pixels to scan than before.</p>
<p>I also have a version of this with cleaner code on <a href="https://openprocessing.org">https://openprocessing.org</a> (<a href="https://www.openprocessing.org/sketch/707203">this one</a>), but I stressed it out too much in terms of resolution and openprocessing is taking too long to render it.</p>
<p>This is how it looks, by the way:</p>
<p><img src="https://i.imgur.com/rKzuPXj.png" loading="lazy" decoding="async" alt="p5 editor result"></p>
<p>I decided it was running slow because it was in Javascript. I am ashamed to say that even after five years of working in the industry I still have such naive thoughts. The truth is my code was merely bad and lacking any sort of polish or optimisation.</p>
<p>So the next step of my plan was to try to do the same thing in <a href="https://processing.org">Processing 3</a>.</p>
<p>I started small:</p>
<p><img src="https://i.imgur.com/QyoLM9U.png" loading="lazy" decoding="async" alt="kotlin version low res"></p>
<p>And then went a tiny bit bigger.</p>
<p><img src="https://i.imgur.com/OxFtnsN.png" loading="lazy" decoding="async" alt="kotlin version higher res"></p>
<p>These took way too long to generate, though, with the first one clocking in at 2251 ms and the second at 7869 ms.</p>
<p>I was still making mistakes here. I was using a full package to do the complex math and several other overly complicated things. There wasn&rsquo;t a noticeable performance gain over the JS code.</p>
<p>And then I decided to do something <em>really</em> stupid. I wanted to see a highly zoomed-in version of the Mandelbrot Set and admire the beautiful fractal patterns. So I thought, why not make a <em>really</em> large image and then zoom into that using a regular image viewer and admire the fractals that way?</p>
<p>So I tried to generate a 12000x10000 image. That&rsquo;s 120 million pixels. A 120 mega-pixel image. I tried to generate that on my laptop. And I made a royal mess of it, too. Not only did it take over an hour to do, I forgot to save it, so just to show you guys that image I&rsquo;ll have to let my laptop run hot for an hour, again, and it won&rsquo;t even be that great an image. You&rsquo;ll get to see it anyway.</p>
<p>But let&rsquo;s take a step back.</p>
<p>I didn&rsquo;t want to just make a 120 megapixel image. I wanted to make a 12 gigapixel one. But the moment I tried to allocate an array that large, JVM told me to stick my head where the sun don&rsquo;t shine. Okay, 12K x 10K it would be. I wanted this image to look extra special so I decided to make some smaller ones look really good as practice.</p>
<p>First a 1200 x 1000 that took 38,785ms:</p>
<p><img src="https://i.imgur.com/nI51GLq.png" loading="lazy" decoding="async" alt="kotlin version 1200x1000"></p>
<p>This is pretty nice looking, actually, but let&rsquo;s see if we can do something prettier.</p>
<p><img src="https://i.imgur.com/fHBJM8q.png" loading="lazy" decoding="async" alt="kotlin version 1200x1000 with extra greys"></p>
<p>For each of the points <em>not</em> inside the Mandelbrot set, I&rsquo;m taking the number of iterations it took before it blew up and mapping that from 0 - 200 (since 200 is the maximum number of iterations I&rsquo;m checking) to 0 - 255 (8 bit greyscale color range).</p>
<p>Now we can not only see the scattered Mandelbrot islands, we can also see the connections between them. These connections are too thin to show up on such a low resolution image, but the surrounding shading gives them away.</p>
<p>Let&rsquo;s try something in another color.</p>
<p><img src="https://i.imgur.com/d0Mt31G.png" loading="lazy" decoding="async" alt="kotlin version 1200x1000 with red borders"></p>
<p>For this I decided to color the inside of the Set fully black, and let the outside be mapped from 0 (full black) to #FF0000(full red), giving us a vivid red border.</p>
<p>Let&rsquo;s do something even more colorful.</p>
<p><img src="https://i.imgur.com/xvEKaDS.png" loading="lazy" decoding="async" alt="kotlin version 1200x1000 with multi-color borders"></p>
<p>This looks closer to what I see in most online visualisations. Here I&rsquo;m using the same iterations-till-blowup value and then mapping that to a hue (think <a href="https://en.wikipedia.org/wiki/HSL_and_HSV">HSL</a>).</p>
<p>Now I&rsquo;ll leave you with the 12K x 10K resolution version of this image. I won&rsquo;t render it on this page directly because that would tank its load time. You can click on <a href="https://i.imgur.com/HWGjDy4.jpg">this link</a> to view it. If you compare it to the low res ones you should see a significantly greater amount of detail visible.</p>
<h2 id="a-more-sensible-approach">A More Sensible Approach</h2>
<p>I wanted to see the tiniest details in the fractal pattern and I thought the way to go about doing that would be to create a very high resolution image. But there&rsquo;s only so much a tiny computer can do. And once you cross the screen resolutions currently available (4K is only 3840 x 2160, compared to the 12K x 10K image I generated which can never be fully represented on a 4K screen), you&rsquo;ll have to zoom in a lot to see details. There is a never a time when you can see the full fractal in absolute detail.</p>
<p>Then there&rsquo;s the matter of how long it takes to generate one of these. If you want to play around with iteration values or colors or any other paramter, you would have to generate the static image all over again. Not fun.</p>
<p>I decided to watch some more YouTube videos and figure out how to do this better. I turned to Daniel Shiffman, one of the best YouTube educators I know of in the programming space.</p>
<!-- raw HTML omitted -->
<p>A new plan formed in my mind.</p>
<h2 id="zooming-and-panning">Zooming and Panning</h2>
<p>I fired up the p5 editor again. Since I was planning to keep the resolution fairly small, I figured I could keep things in the browser. Or rather, <em>because</em> I wanted to keep it in the browser, I decided to go with the low, low resolution of 400 x 400.</p>
<p>I also made it so the image can be zoomed using <code>+</code> and <code>-</code> and panned using the arrow keys. It still happens really slowly, but at least it works.</p>
<!-- raw HTML omitted -->
<blockquote>
<p>Note: In case the embed is not working, go <a href="https://editor.p5js.org/scionofbytes/full/kdAFrs4jI">here</a>.</p>
</blockquote>
<p>I decided to get a little bit fancy and let the image appear iteration by iteration all the way up to 200. The iterations here are the maximum number of iterations at which we stop testing if the <strong>z</strong> value will grow beyond 2 (blow up). As you can see, the more iterations performed, the more detailed the picture. This effect is clearer when you are zoomed into one of the edges. You can see the details becoming finer.</p>
<p>Zooming and panning is the way to go forward. If you are to look at only a part of the image at a time, why bother rendering the rest? Graphics programmers have known this since ages beyond reckoning. It took me a failed attempt at generating a high res image at speed to think of doing the same thing (and ideas from Shiffman&rsquo;s video).</p>
<h2 id="making-it-smoother">Making It Smoother</h2>
<p>This rendering is still very clunky, and quite slow. Even at smaller iterations. I want at least 200 iterations to get good fractal detail which will make this run really slowly. But Javascript is single-threaded and there isn&rsquo;t any more I can squeeze out of it. Or can I?</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers">Web Workers</a> have been a thing for a while. <a href="https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas">OffScreen Canvases</a> are somewhat newer (and not very well supported on non-Chrome browsers). The demo I present here might not work if you&rsquo;re not on the latest Chrome and I apologise for that. Making it work in other ways is possible but too tedious and not something I want to do.</p>
<blockquote>
<p>I&rsquo;ve since learned that a Uint8ClampedArray works much better than an OffScreenCanvas both in terms of performance and browser compatibility. It needs more code for computing array indices and colors but overall much better outcome. See the end of the post for details.</p>
</blockquote>
<p>Play around with it. ~Click on any part of the image to center it~ Click and drag to pan and use <code>alt/option + up arrow/down arrow</code> or the scroll wheel/pad to zoom in and out. You can zoom in pretty deep before the fractal finally gives up; numbers on a computer only have so much precision.</p>
<!-- raw HTML omitted -->
<p>The entire code for it (which is small), can be found here: <a href="https://github.com/BadgerBadgerBadgerBadger/smooth-mandelbrot">https://github.com/BadgerBadgerBadgerBadger/smooth-mandelbrot</a></p>
<p>Let&rsquo;s talk about how it works and why it is so much smoother than the previous version (though not as smooth as I would ultimately like it to be).</p>
<h2 id="divide-and-conquer">Divide and Conquer</h2>
<p>Web Workers allow us to run honest-to-goodness OS threads and offload tasks to them. My main goals for my worker threads were:</p>
<ol>
<li>Free up the main thread for processing user input.</li>
<li>Break up the rendering task between multiple workers to speed it up.</li>
</ol>
<p>We start with the <code>sketch.js</code> as our entry point.</p>
<!-- raw HTML omitted -->
<p>We set up a canvas, attach a click and keyboard listener, and add it to our DOM. We also start the <code>painter.js</code> worker which, as the name suggests, will be doing all the work of painting our canvas. And then we send a <code>setup</code> command to the painter asking it to set everything up and do the first paint.</p>
<p>The interesting bits here are</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">offScreen</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">transferControlToOffscreen</span><span class="p">()</span>
</span></span></code></pre></div><p>and</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">message</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">message</span><span class="o">:</span> <span class="s1">&#39;setup&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">canvas</span><span class="o">:</span> <span class="nx">offScreen</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">transfers</span> <span class="o">=</span> <span class="p">[</span><span class="nx">offScreen</span><span class="p">]</span>
</span></span></code></pre></div><p>First, we <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/transferControlToOffscreen">transfer control of our canvas to an offscreen version</a> which we then send to our painter. We are detaching the painting part and the displaying part. Though the canvas is still part of our DOM and displayed as such, the painting is done by the painter in a different thread and the results show up automatically. Also note the function call we make to send the offscreen canvas to our painter worker. That second parameter is an array of <a href="https://developer.mozilla.org/en-US/docs/Web/API/Transferable">Transferrable</a> objects to send to the worker.</p>
<p>Transferrable objects, in short, are those that you can send to a Web Worker and it will be a true memory transfer. The sending thread loses reference to the transferrable (if I try to do anything with <code>offScreen</code> after this bit of code, I will get an error), and the receiving thread assumes full control.</p>
<p>Which is exactly what we want&ndash; we let the painter do the job of painting so our main thread is free to interact with the user. It does so in two ways. A mouse click event and a key type event. I won&rsquo;t go into the code of those. They aren&rsquo;t very interesting or pertinent to this article. Let&rsquo;s just say, they work and they send a <code>draw</code> command to the painter with some parameters.</p>
<!-- raw HTML omitted -->
<p>There is a lot going on here but we need not focus on all of it. Let&rsquo;s look at the interesting bits.</p>
<p>First, the <code>setup</code>. I&rsquo;m assigning the canvas and its context, retrieved from the setup event sent by the main thread, to the global variables for the painter. Then I start divvying up the tasks. I decided, somewhat arbitrarily, that I want the task of painting to be broken up into 8 parts. I&rsquo;m sure there is a way to figure out what the optimal number should be, but I just decided to go for 8. So I create 8 <code>acolytes</code>. These acolytes get only a portion of the canvas to work on. I&rsquo;m slicing up the canvas into 8 vertical slices. We need to know the width of each segment: <code>segmentLength = canvas.width / numOfAcolytes</code>.</p>
<p>Note that I&rsquo;m not actually slicing up the original canvas. Instead, I&rsquo;m creating baby canvases, each given to one of the acolytes. Later, once the acolytes have drawn on one of these canvases, we will take the resulting image and paint it onto our main canvas. I hope this analogy of a master painter and their acolytes provides a good analogy for how this process works.</p>
<p>Something I should have talked about much earlier is that the Mandelbrot Set lives somewhere between -3 and 1 on the Complex Line and -2 and 2 on the Real Line. Since the canvas is hundreds of pixels wide and similarly high, we need to map these values from a large range into a smaller one. Which is easy enough to do with a simple <code>map</code> function.</p>
<p>But the acolytes, unless they know exactly by how much each of their canvases are offset from the main canvas, won’t do this computation correctly. Hence, we need to provide them with this info. They also need to know the dimensions of the original canvas so we send that in, too. Furthermore, they need to know what range they need to map their canvases to. So we also pass in the ranges for the <strong>x</strong> direction and for the <strong>y</strong> direction.</p>
<p>Most of this info is only sent during setup. We don&rsquo;t change the size of the original canvas or the number of acolytes (and thus the width of each acolyte&rsquo;s segment) during the lifetime of the app. Only the boundaries change when we pan or zoom and we send this information when we issue <code>draw</code> commands.</p>
<p>So let&rsquo;s talk about the <code>draw</code> command, our second case in the switch-case. When receiving a <code>draw</code> command, the painter could receive mouse or keyboard events from the main thread. We allow users to pan the canvas by clicking on a point in the image and we pan so that that point becomes the new center of the image. The <code>if (mousePos.x !== undefined) {</code> block of the <code>if</code> statement takes care of this. I won&rsquo;t explain the math here. Suffice it to say, that based on the position of the mouse click, we adjust the boundaries. Similarly, in the <code>} else if (zoomBy) {</code> block, we adjust the boundaries so we are zooming in/out of the image.</p>
<p>Either way, once the new boundaries are computed, the global <code>xBounds</code> and <code>yBounds</code> variables are updated and the <code>draw</code> function of the painter is called. This is a really simple function that sends the boundaries and number of iterations to the acolytes and asks them to paint their pieces of the canvas. The acolytes do that (we&rsquo;ll talk about that next), and send these images back which we capture in the <code>onAcolyteMessage</code> function. We know which acolyte sent the image, hence we know which piece of the canvas they were given and we paint the image onto the canvas at that place.</p>
<p>Note, I am not waiting to collect all the responses from each acolyte for each draw command before updating the canvas. That would probably lead to smoother looking updates. Right now, if you pan or zoom quickly you can see the update happening in bands as each acolyte draws their portion of the image.</p>
<!-- raw HTML omitted -->
<p>The code for the acolytes is quite simple as well. In the <code>setup</code> command we capture the canvas and context for drawing and we capture the boundaries. And then it draws once. Further, when it receives more draw commands, it updates its knowledge of the boundaries and draws its part of the image again.</p>
<p>The <code>draw</code> function itself isn&rsquo;t very different from what I&rsquo;ve done in previous versions of the generator. Iterate over the pixels, map it into the correct range, figure out if it is part of the set or not and color accordingly. It then gets a copy of the image from its canvas and sends that to the painter (again via Transferrable). It seems while a normal canvas can have its drawing part detached and in a different thread with updates to the drawing appearing on the canvas, the same is not true if you use an OffscreenCanvas instance. This is still an experimental API so it will be really interesting to see how it develops.</p>
<blockquote>
<p>Note: You might have noticed that the acolytes are receiving <code>maxIters</code> as well. Currently they aren&rsquo;t doing anything interesting with this variable. I&rsquo;ve set it to a fixed value and it stays there. I could potentially play with it but I don&rsquo;t want to.</p>
</blockquote>
<p>And that&rsquo;s mostly it. I have a main thread which kicks everything off and then handles user input and sends to the painter. The painter decides the image boundaries based on user input (panning/zooming), and then offloads the drawing of sections of the canvas to the acolytes.</p>
<p>Hope you enjoyed the post. Feedback is appreciated.</p>
<h2 id="updates">Updates</h2>
<p><a href="https://github.com/lovasoa">@lovasoa</a> was kind enough to contribute several optimisation updates that make smooth mandelbrot worth its name. These include but are not limited to:</p>
<ul>
<li>Using Uint8ClampedArray in the Acolytes for setting color values and passing that back instead of the heavier Canvas objects. Additionally with the non-standard OffScreenCanvas not in use the demo actually works on more browsers.</li>
<li>Inlining a lot of the Complex math.</li>
<li>Introducing a mouse-based scroll.</li>
<li>Flooring numbers using a bitwise OR operator that looks like black magic.</li>
</ul>
<p>I&rsquo;d recommend taking a look at his <a href="https://github.com/ScionOfBytes/smooth-mandelbrot/pull/1">PR</a> to get a feel for the changes.</p>
]]></description></item><item><title>My ES Lint and Why</title><link>https://badgerbadgerbadgerbadger.dev/posts/code-craftsmanship/2018-09-21-my-es-lint-and-why/</link><pubDate>Fri, 21 Sep 2018 16:17:25 -0400</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/code-craftsmanship/2018-09-21-my-es-lint-and-why/</guid><description><![CDATA[<p>I recently updated my company&rsquo;s main NodeJS repository to include formatting via <a href="https://github.com/prettier/prettier-eslint">prettier-eslint</a> and linting via <a href="https://eslint.org/">eslint</a>. My colleague asked me to explain my choice of rules since it&rsquo;s tedious to go through each and every rule and figure out why it was chosen and I thought it was worth getting a  blog post out of it, so here we go.</p>
<p>This is the eslint config I plan to use:</p>
<pre tabindex="0"><code>{
    &#34;env&#34;: {
        &#34;node&#34;: true,
        &#34;es6&#34;: true
    },
    &#34;extends&#34;: &#34;eslint:recommended&#34;,
    &#34;rules&#34;: {
        &#34;arrow-spacing&#34;: &#34;error&#34;,
        &#34;curly&#34;: &#34;error&#34;,
        &#34;dot-notation&#34;: &#34;error&#34;,
        &#34;eol-last&#34;: &#34;error&#34;,
        &#34;generator-star-spacing&#34;: [
            &#34;error&#34;,
            {
                &#34;before&#34;: false,
                &#34;after&#34;: true
            }
        ],
        &#34;indent&#34;: [&#34;error&#34;, 4],
        &#34;no-buffer-constructor&#34;: &#34;error&#34;,
        &#34;no-confusing-arrow&#34;: &#34;error&#34;,
        &#34;no-magic-numbers&#34;: [
            &#34;error&#34;,
            {
                &#34;enforceConst&#34;: true
            }
        ],
        &#34;no-multi-spaces&#34;: &#34;error&#34;,
        &#34;no-path-concat&#34;: &#34;error&#34;,
        &#34;no-var&#34;: &#34;error&#34;,
        &#34;no-self-compare&#34;: &#34;error&#34;,
        &#34;no-throw-literal&#34;: &#34;error&#34;,
        &#34;no-trailing-spaces&#34;: &#34;error&#34;,
        &#34;no-unused-expressions&#34;: &#34;error&#34;,
        &#34;no-useless-concat&#34;: &#34;error&#34;,
        &#34;padding-line-between-statements&#34;: [
            &#34;error&#34;,
            { &#34;blankLine&#34;: &#34;always&#34;, &#34;prev&#34;: &#34;directive&#34;, &#34;next&#34;: &#34;*&#34; }
        ],
        &#34;prefer-const&#34;: &#34;error&#34;,
        &#34;prefer-promise-reject-errors&#34;: &#34;error&#34;,
        &#34;prefer-template&#34;: &#34;error&#34;,
        &#34;quotes&#34;: [&#34;error&#34;, &#34;double&#34;],
        &#34;object-shorthand&#34;: &#34;error&#34;,
        &#34;semi&#34;: [&#34;error&#34;, &#34;never&#34;],
        &#34;strict&#34;: [&#34;error&#34;, &#34;global&#34;],
        &#34;yoda&#34;: &#34;error&#34;
    }
}
</code></pre><p>I&rsquo;ve set the env as <code>node</code> and <code>es6</code>, I hope those choices are fairly obvious. I&rsquo;m also extending the <a href="https://eslint.org/docs/rules/">eslint:recommended</a>(look for the ones with the ✔️ before them), most of whose rules I&rsquo;ve gone over and satisfied myself that I agree with.</p>
<p>On top of this I have added extra rules, all of them as errorables (they&rsquo;ll throw an error when they fail, not a warning). Below I&rsquo;ll be going through each and giving a short description of what the rule does (very tl;dr, go to the rule page for comprehensive details), and why I personally chose it.</p>
<p><strong>arrow-spacing</strong></p>
<p>This rule normalizes style of spacing before/after an arrow function’s arrow(=&gt;) The default is to have one space before and one space after.</p>
<p>I find it easier to read when we have <code>(arg =&gt; expression)</code> as opposed to <code>(arg=&gt; expression)</code> or <code>(arg =&gt;expression)</code></p>
<p><strong>curly</strong></p>
<p>JavaScript allows the omission of curly braces when a block contains only one statement. However, it is considered by many to be best practice to never omit curly braces around blocks, even when they are optional, because it can lead to bugs and reduces code clarity. By default this rule requires curly braces for all block statements.</p>
<p>I like my block statements to be clearly defined. Blocks imply scope and when I&rsquo;m reading code it&rsquo;s much better that a curly easily tells me that this is a different scope.</p>
<p><strong>dot-notation</strong></p>
<p>In JavaScript, one can access properties using the dot notation (foo.bar) or square-bracket notation (foo[&ldquo;bar&rdquo;]). However, the dot notation is often preferred because it is easier to read, less verbose, and works better with aggressive JavaScript minimizers.</p>
<p>The dot notation syntax is terser. <code>object.field</code> is not harder to read than <code>object[&quot;field&quot;]</code> or <code>object['field']</code> but uses less characters. In some cases, of course, you need square-bracket syntax (<code>object[&quot;field-name&quot;]</code>) and the rule respects that.</p>
<p><strong>eol-last</strong></p>
<p>Trailing newlines in non-empty files are a common UNIX idiom. Benefits of trailing newlines include the ability to concatenate or append to files as well as output files to the terminal without interfering with shell prompts.</p>
<p>Enough said.</p>
<p><strong>generator-star-spacing</strong></p>
<p>Generators are a new type of function in ECMAScript 6 that can return multiple values over time. These special functions are indicated by placing an * after the function keyword. To keep a sense of consistency when using generators this rule enforces a single position for the *.</p>
<p>I prefer <code>function* genName</code> over <code>function * genName</code> or <code>function *genName</code> for a very simple reason. A generator function is not the same as a normal function. It has different calling semantics. Syntactically the <code>*</code> is the only thing that differentiates a generator function from a regular function. It makes sense to me that this syntax is attached to the <code>function</code> keyword rather the name (which can be arbitrary).</p>
<p><strong>indent</strong></p>
<p>I hardly need to explain this one.</p>
<p><strong>no-buffer-constructor</strong></p>
<p>In Node.js, the behavior of the Buffer constructor is different depending on the type of its argument. Passing an argument from user input to Buffer() without validating its type can lead to security vulnerabilities such as remote memory disclosure and denial of service. As a result, the Buffer constructor has been deprecated and should not be used. Use the producer methods <code>Buffer.from</code>, <code>Buffer.alloc</code>, and <code>Buffer.allocUnsafe</code> instead.</p>
<p><strong>no-confusing-arrow</strong></p>
<p>Arrow functions (=&gt;) are similar in syntax to some comparison operators (&gt;, &lt;, &lt;=, and &gt;=). This rule warns against using the arrow function syntax in places where it could be confused with a comparison operator. Even if the arguments of the arrow function are wrapped with parens, this rule still warns about it unless allowParens is set to true.</p>
<p>I love being able to read code fast with my brain doing minimal work in trying to parse text.</p>
<p><strong>no-magic-numbers</strong></p>
<p>‘Magic numbers’ are numbers that occur multiple time in code without an explicit meaning. They should preferably be replaced by named constants. The no-magic-numbers rule aims to make code more readable and refactoring easier by ensuring that special numbers are declared as constants to make their meaning explicit.</p>
<ol>
<li>You think this is only important if someone else will read your code. Well, guess what? 6 months from the time of coding, you will be a very different person, and I guarantee your own code won&rsquo;t make sense to you if you use magic numbers.</li>
<li>Refactoring becomes <em>so</em> much easier when the number is used in more than one place.</li>
</ol>
<p><strong>no-multi-spaces</strong></p>
<p>Multiple spaces in a row that are not used for indentation are typically mistakes. This rule aims to disallow multiple whitespace around logical expressions, conditional expressions, declarations, array elements, object properties, sequences and function parameters.</p>
<p>Multiple spaces are often hard to read especially if you aren&rsquo;t vieweing your code in your personal, fully-configured IDE.</p>
<p><strong>no-path-concat</strong></p>
<p>In Node.js, the <code>__dirname</code> and <code>__filename</code> global variables contain the directory path and the file path of the currently executing script file, respectively. Sometimes, developers try to use these variables to create paths to other files, such as:</p>
<pre tabindex="0"><code>var fullPath = __dirname + &#34;/foo.js&#34;;
</code></pre><p>However, there are a few problems with this. First, you can’t be sure what type of system the script is running on. Node.js can be run on any computer, including Windows, which uses a different path separator. It’s very easy, therefore, to create an invalid path using string concatenation and assuming Unix-style separators. There’s also the possibility of having double separators, or otherwise ending up with an invalid path.</p>
<p>In order to avoid any confusion as to how to create the correct path, Node.js provides the path module. This module uses system-specific information to always return the correct value. So you can rewrite the previous example as:</p>
<pre tabindex="0"><code>var fullPath = path.join(__dirname, &#34;foo.js&#34;);
</code></pre><p>This example doesn’t need to include separators as <code>path.join()</code> will do it in the most appropriate manner. Alternately, you can use <code>path.resolve()</code> to retrieve the fully-qualified path:</p>
<pre tabindex="0"><code>var fullPath = path.resolve(__dirname, &#34;foo.js&#34;);
</code></pre><p>Both <code>path.join()</code> and <code>path.resolve()</code> are suitable replacements for string concatenation wherever file or directory paths are being created.</p>
<p><strong>no-var</strong></p>
<p>ECMAScript 6 allows programmers to create variables with block scope instead of function scope using the let and const keywords. This rule is aimed at discouraging the use of var and encouraging the use of const or let instead.</p>
<p><code>const</code> and <code>let</code> have been a godsend in terms of indicating intent.</p>
<p><strong>no-self-compare</strong></p>
<p>Comparing a variable against itself is usually an error, either a typo or refactoring error. It is confusing to the reader and may potentially introduce a runtime error.</p>
<p>The only time you would compare a variable against itself is when you are testing for NaN. However, it is far more appropriate to use typeof x === &rsquo;number&rsquo; &amp;&amp; isNaN(x) or the Number.isNaN ES2015 function for that use case rather than leaving the reader of the code to determine the intent of self comparison.</p>
<p>This error is raised to highlight a potentially confusing and potentially pointless piece of code. There are almost no situations in which you would need to compare something to itself.</p>
<p><strong>no-throw-literal</strong></p>
<p>It is considered good practice to only throw the Error object itself or an object using the Error object as base objects for user-defined exceptions. The fundamental benefit of Error objects is that they automatically keep track of where they were built and originated.</p>
<p>This rule restricts what can be thrown as an exception. When it was first created, it only prevented literals from being thrown (hence the name), but it has now been expanded to only allow expressions which have a possibility of being an Error object.</p>
<p><strong>no-trailing-spaces</strong></p>
<p>Sometimes in the course of editing files, you can end up with extra whitespace at the end of lines. These whitespace differences can be picked up by source control systems and flagged as diffs, causing frustration for developers. While this extra whitespace causes no functional issues, many code conventions require that trailing spaces be removed before check-in.</p>
<p>This rule disallows trailing whitespace (spaces, tabs, and other Unicode whitespace characters) at the end of lines.</p>
<p><strong>no-unused-expressions</strong></p>
<p>An unused expression which has no effect on the state of the program indicates a logic error.</p>
<p>For example, <code>n + 1;</code> is not a syntax error, but it might be a typing mistake where a programmer meant an assignment statement <code>n += 1;</code> instead.</p>
<p><strong>no-useless-concat</strong></p>
<p>It’s unnecessary to concatenate two strings together, such as: <code>var foo = &quot;a&quot; + &quot;b&quot;;</code> This code is likely the result of refactoring where a variable was removed from the concatenation (such as &ldquo;a&rdquo; + b + &ldquo;b&rdquo;). In such a case, the concatenation isn’t important and the code can be rewritten as: <code>var foo = &quot;ab&quot;;</code></p>
<p><strong>padding-line-between-statements</strong></p>
<p>This rule requires or disallows blank lines between the given 2 kinds of statements. Properly blank lines help developers to understand the code.</p>
<p>For example, the following configuration requires a blank line between a variable declaration and a return statement.</p>
<pre tabindex="0"><code>/*eslint padding-line-between-statements: [
    &#34;error&#34;,
    { blankLine: &#34;always&#34;, prev: &#34;var&#34;, next: &#34;return&#34; }
]*/

function foo() {
    var a = 1;

    return a;
}
</code></pre><p>A configuration is an object which has 3 properties; <code>blankLine</code>, <code>prev</code> and <code>next</code>. For example, <code>{ blankLine: &quot;always&quot;, prev: &quot;var&quot;, next: &quot;return&quot; }</code> means “it requires one or more blank lines between a variable declaration and a return statement.” You can supply any number of configurations. If a statement pair matches multiple configurations, the last matched configuration will be used.</p>
<p>I have just started fleshing out this rule. I&rsquo;d like a newline after a directive, please. Makes it easier to read, for me.</p>
<p><strong>prefer-const</strong></p>
<p>If a variable is never reassigned, using the <code>const</code> declaration is better.</p>
<p><code>const</code> declaration tells readers, “this variable is never reassigned,” reducing cognitive load and improving maintainability.</p>
<p>This rule is aimed at flagging variables that are declared using <code>let</code> keyword, but never reassigned after the initial assignment.</p>
<p><strong>prefer-promise-reject-errors</strong></p>
<p>It is considered good practice to only pass instances of the built-in Error object to the <code>reject()</code> function for user-defined errors in Promises. Error objects automatically store a stack trace, which can be used to debug an error by determining where it came from. If a Promise is rejected with a non-Error value, it can be difficult to determine where the rejection occurred.</p>
<p>This rule aims to ensure that Promises are only rejected with Error objects.</p>
<p><strong>prefer-template</strong></p>
<p>In ES2015 (ES6), we can use template literals instead of string concatenation. This rule is aimed to flag usage of <code>+</code> operators with strings.</p>
<p>Templated strings are easier to read than their <code>+</code> concatenated cousins since they follow a more natural flow.</p>
<p><strong>quotes</strong></p>
<p>JavaScript allows you to define strings in one of three ways: double quotes, single quotes, and backticks (as of ECMAScript 6). Each of these lines creates a string and, in some cases, can be used interchangeably. The choice of how to define strings in a codebase is a stylistic one outside of template literals (which allow embedded expressions to be interpreted).</p>
<p>Double quotes were chosen arbitralily.</p>
<p><strong>object-shorthand</strong></p>
<p>ECMAScript 6 provides a concise form for defining object literal methods and properties. This syntax can make defining complex object literals much cleaner.</p>
<p>This rule enforces the use of the shorthand syntax. This applies to all methods (including generators) defined in object literals and any properties defined where the key name matches name of the assigned variable.</p>
<p>Having <code>{name: name}</code> conveys no extra information.</p>
<p><strong>semi</strong></p>
<p>Typing mistakes and misunderstandings about where semicolons are required can lead to semicolons that are unnecessary. While not technically an error, extra semicolons can cause confusion when reading code.</p>
<p>Explained <a href="https://scionofbytes.me/code-craftsmanship/semico-what/">here</a>.</p>
<p><strong>strict</strong></p>
<p>A strict mode directive is a &ldquo;use strict&rdquo; literal at the beginning of a script or function body. It enables strict mode semantics.</p>
<p>I&rsquo;ve seen issues with non-strict mode especially that it allows variable assignment without declaration. It&rsquo;s a buggy nightmare.</p>
<p><strong>yoda</strong></p>
<p>Yoda conditions are so named because the literal value of the condition comes first while the variable comes second. For example, the following is a Yoda condition:</p>
<pre tabindex="0"><code>if (&#34;red&#34; === color) {
    // ...
}
</code></pre><p>This is called a Yoda condition because it reads as, “if red equals the color”, similar to the way the Star Wars character Yoda speaks. Compare to the other way of arranging the operands:</p>
<pre tabindex="0"><code>if (color === &#34;red&#34;) {
    // ...
}
</code></pre><p>This typically reads, “if the color equals red”, which is arguably a more natural way to describe the comparison.</p>
<p>Proponents of Yoda conditions highlight that it is impossible to mistakenly use = instead of == because you cannot assign to a literal value. Doing so will cause a syntax error and you will be informed of the mistake early on. This practice was therefore very common in early programming where tools were not yet available.</p>
<p>Opponents of Yoda conditions point out that tooling has made us better programmers because tools will catch the mistaken use of = instead of == (ESLint will catch this for you). Therefore, they argue, the utility of the pattern doesn’t outweigh the readability hit the code takes while using Yoda conditions.</p>
]]></description></item><item><title>Dealing With Errors in Go Projects</title><link>https://badgerbadgerbadgerbadger.dev/posts/code-craftsmanship/2017-09-13-error-handling-in-go/</link><pubDate>Wed, 13 Sep 2017 14:17:25 -0400</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/code-craftsmanship/2017-09-13-error-handling-in-go/</guid><description><![CDATA[<h1 id="dealing-with-errors-in-go-projects">Dealing With Errors in Go Projects</h1>
<blockquote>
<p>Note: In this post I talk both about the purely technical reasons for doing certain things and some practices that have made debugging production systems easier for me. I&rsquo;m still fairly new to Go and still learning so if you find any issues with my approach or have a better way to approach the problem, please leave a comment.</p>
</blockquote>
<p>I come from a NodeJS background and my coding habits have been heavily influenced by the paradigms most used in Node. I started coding in Go quite recently and I immediately fell in love with the language. I love a lot of things about Go, but let&rsquo;s not get into that right now or we&rsquo;ll never get to the point.</p>
<p>Let&rsquo;s talk, instead, of what caused me a lot of headache and frustration: <strong>errors</strong>.</p>
<p>I didn&rsquo;t know what to do with the darned things. The idiomatic way in Go is to return errors as a second value and that&rsquo;s what pretty much all libraries do. There is a panic-recover mechanism but I&rsquo;ve found it to be used to recover from things like illegal memory access or nil pointer dereference (these shouldn&rsquo;t happen if you code properly).</p>
<p>I was looking down the barrel of either handling and logging errors on the spot, or returning them up the chain of function calls. Neither sounded like a good idea. Why? Here&rsquo;s why:</p>
<p><strong>If you handle errors on the spot and log them</strong> you lose context of the steps that led to this error. You can log variables local to the function where the error occured, but you have no idea what path your program took to finally call this function and face this error.</p>
<p><strong>If you return errors up the chain</strong> and then handle them at the topmost level, you end up with almost the same thing. You have the error and you know which entry point of your function eventually led to that error, but now you&rsquo;ve lost context of the function where the error originated.</p>
<p>Neither of them is a good idea, especially not at 4 in the morning when your boss calls you up to debug some obscure bug made more obscure by insufficient information. You need to know where the error originated, which entry point led to this and what path it followed. And of course, the details of the error itself.</p>
<p>To work around these issues, I had this idea of annotating the error message at each stage. So say I have the following extremely contrived code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">contrived</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;thirdparty/lib&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;errors&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">HandleRequest</span><span class="p">(</span><span class="nx">req</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">res</span> <span class="nx">Response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">result</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">ControllerCall</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">Id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">log</span><span class="p">(</span><span class="nx">err</span><span class="p">.</span><span class="nf">Error</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="nx">res</span><span class="p">.</span><span class="nf">BadResponse</span><span class="p">(</span><span class="s">&#34;...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="nx">res</span><span class="p">.</span><span class="nf">GoodResponse</span><span class="p">(</span><span class="s">&#34;...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">ControllerCall</span><span class="p">(</span><span class="nx">id</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="kt">string</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">result</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">DbCall</span><span class="p">(</span><span class="nx">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;controller failed to blah blah blah &#34;</span><span class="o">+</span><span class="nx">err</span><span class="p">.</span><span class="nf">Error</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">result</span><span class="p">,</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">DbCall</span><span class="p">(</span><span class="nx">id</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="kt">string</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">result</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">lib</span><span class="p">.</span><span class="nf">DoSomething</span><span class="p">(</span><span class="nx">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;db call failed for id: &#34;</span><span class="o">+</span><span class="nx">id</span><span class="o">+</span><span class="s">&#34; &#34;</span><span class="o">+</span><span class="nx">err</span><span class="p">.</span><span class="nf">Error</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">result</span><span class="p">,</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>By concatenating the errors at each step I retain context. At the top level, when I log, I can see the calls that led to the error. This is better than not having much context but is extremely tedious in practice.</p>
<p>Then I came across <a href="https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package">this excellent blog post</a> by Dave Cheney. And once you&rsquo;ve read through it (seriously, read through it before continuing), you&rsquo;ll see that he&rsquo;s implemented the same idea I had but done it a gazillion times better. So I started perusing more things he&rsquo;d written. The presentation link includes most of the content of the preceding articles.</p>
<ul>
<li><a href="https://dave.cheney.net/2016/04/07/constant-errors">Constant Errors</a></li>
<li><a href="https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully">Don’t just check errors, handle them gracefully</a></li>
<li><a href="https://dave.cheney.net/2014/12/24/inspecting-errors">Inspecting Errors</a></li>
<li><a href="https://godoc.org/github.com/pkg/errors">pkg/errors</a></li>
<li>Dave Cheney&rsquo;s presentation on Error Handling <a href="https://www.youtube.com/watch?v=lsBF58Q-DnY">youtube</a> <a href="https://dave.cheney.net/paste/gocon-spring-2016.pdf">pdf</a></li>
</ul>
<p>After reading through all that and tinkering around for a while, I came up with a set of sensible guidelines for handling errors. Some of them are taken directly from Dave Cheney&rsquo;s blog, some I added based on my own experiences.</p>
<h2 id="lets-begin">Let&rsquo;s Begin</h2>
<ul>
<li><strong>Always</strong> check for errors if the function returns one. I know that sounds obvious but you&rsquo;d be surprised at how much code I&rsquo;ve seen that doesn&rsquo;t do this.</li>
<li>Create custom error types for the errors you <em>know</em> you are gonna face (bad input, duplicate request, etc.). Return these, wrapped by the <code>errors</code> package, when you face them.</li>
<li>When dealing with errors returned from a library, wrap it with <code>errors.Wrap</code> or <code>errors.Wrapf</code> and return.</li>
<li>When dealing with errors returned from your own code, if:
<ul>
<li>it requires no further context, just return it.</li>
<li>if it requires some extra context (perhaps data points captured within that function), use <code>Wrap</code> or <code>Wrapf</code>.</li>
</ul>
</li>
<li>Handle errors without further propagation at the entry point of your program (commands in a cli or handlers in a web server). Unwrap it with <code>errors.Cause</code> and test it:
<ul>
<li>If it is of the custom type you have defined for your project, handle accordingly (you might wanna log these at level Error or Warning since these were expected). If it&rsquo;s an endpoint facing your users, you might wanna have a contract with the client on what codes to return and act accordingly (you might even end up returing a generic error message). If it is an internal endpoint (thinking microservices), you could return an appropriate status code and message while logging the same.</li>
<li>If it is of an unknown type, you might wanna log a fatal. This is an error that should never have happened. After that it&rsquo;s up to you.</li>
</ul>
</li>
</ul>
<p>I use these guidelines to make sure I have enough information during debugging to figure what&rsquo;s going wrong. I still have to do <code>error != nil</code> everywhere but I think that&rsquo;s not as bad a thing as I first thought. Unlike exception propagation, this way of doing things makes me stop and think at each step if I wanna let the error propagate on its own or add more context.</p>
<h2 id="lastly">Lastly</h2>
<p>This approach works for me, for now. Is there a better way to do this? I don&rsquo;t know. There&rsquo;s still a lot for me to learn and I&rsquo;m hoping that as I keep coding, I&rsquo;ll face enough new challenges to keep me reaching for better ways to code.</p>
]]></description></item><item><title>Template Literals, All the Way!</title><link>https://badgerbadgerbadgerbadger.dev/posts/code-craftsmanship/2017-02-23-template-literals-all-the-way/</link><pubDate>Thu, 23 Feb 2017 14:17:25 -0400</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/code-craftsmanship/2017-02-23-template-literals-all-the-way/</guid><description><![CDATA[<p>Today, while at work, I had to turn a single-quoted string into a <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals">template literal</a> because mid-way through the string I realized I had to put in an apostrophe. This has happened often enough that I want to stop thinking in this dual single-quoted/template-literal mode and just go template-literal all the time.</p>
<p>Now before I say anything more I&rsquo;d like to clarify that I&rsquo;m working with NodeJS almost all the time with full feature support for template strings and <strong>no</strong> issues with things like minification. So anything in my research that had to do with <code>not supported in all environments</code> was discarded as invalid.</p>
<p>After an initial bout of googling I didn&rsquo;t find much to deter me from using template literals.</p>
<p><a href="http://softwareengineering.stackexchange.com/a/309623/199195">This</a> SO answer speaks of (among other things) IDE support and how having it makes working with template literals easier coz placeholders get highlighted. The <a href="https://github.com/airbnb/javascript#strings">Airbnb Style Guide</a> prefers single-quoted strings and using template literals for interpolation. It doesn&rsquo;t say we <em>shouldn&rsquo;t</em> use template literals all the time. And then there&rsquo;s <a href="https://www.reddit.com/r/javascript/comments/52ions/your_opinion_on_using_backtick_as_the_default/">this thread on reddit</a> that leads to a bunch of different places, most of which talk about browser support and minification and stuff that doesn&rsquo;t matter in my controlled NodeJs environment.</p>
<p><a href="https://www.reddit.com/r/javascript/comments/52ions/your_opinion_on_using_backtick_as_the_default/d7otae8/">This guy</a> ran tests to figure out if using template literals poorly impacts performance but according to the results template literals are actually faster.</p>
<p>So I decided to see if there was any other research around performance impact. One would assume that template literals would be slower than a plain old string (when not interpolating) because a plain old string is just a plain old string but a template literal would check for interpolation even when no placeholders exist (though a good compiler would account for this and optimize accordingly).</p>
<p>Someone did a test of ES6 features on a bunch of platforms (including Node v6.9.3) and the <a href="https://kpdecker.github.io/six-speed/">results</a> don&rsquo;t tell me anything about single quoted strings vs. template literals in a non-interpolating string (in general the results don&rsquo;t look very promising for ES6 features, though; eek!).</p>
<p>Most research seems to be centered around whether it&rsquo;s more performant to concatenate strings or use template literals when interpolation is required. No one seems to have a strong stance on whether template literals should or should not be used for everything.</p>
<p>Some arguments I came across for why they <em>shouldn&rsquo;t</em> be used all the time (and my rebuttal):</p>
<ul>
<li><strong>What if you need to have a <code>`</code> or <code>${}</code> in your code?</strong>
I cannot recall a single instance in my nearly three years of coding in NodeJs that I&rsquo;ve had to use these in my strings. I doubt I&rsquo;ll come across more than a few such cases in my entire lifetime. And if I have to, I can always escape them <a href="https://www.youtube.com/watch?v=Ioh-oU1zn0w">#BackslashFTW</a>.</li>
<li><strong>My keyboard has an European layout and using those backticks is a pain in the buttocks!</strong>
I can sympathize with this. If your tools go against you, there is little you can do. But in my personal case (and the case of my team), we don&rsquo;t have this issue. Hence, another point that I need not take into consideration (though you definitely should if you have such a keyboard).</li>
</ul>
<p>So it doesn&rsquo;t look like there is much compelling research around why I <em>shouldn&rsquo;t</em> be using those oh-so-handy template literals. How about you guys find some for me and let me know?</p>
<p>And if you think I got anything else in this post wrong, let me know about that too.</p>
]]></description></item><item><title>Semico what?</title><link>https://badgerbadgerbadgerbadger.dev/posts/code-craftsmanship/2016-12-21-semico-what/</link><pubDate>Wed, 21 Dec 2016 14:17:25 -0400</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/code-craftsmanship/2016-12-21-semico-what/</guid><description><![CDATA[<p>Recently, at my workplace, a colleague felt a <a href="http://knowyourmeme.com/memes/that-really-rustled-my-jimmies">rustle up his jimmies</a> due to our lack of semicolon usage in our NodeJs repos. The number of projects without semicolons were in the tens and there really wasn&rsquo;t a good way to introduce semicolons in them even had the team agreed to do so. But that got me thinking.</p>
<blockquote>
<p>Do we really need semi-colons?</p>
</blockquote>
<p>Why have them or not have them? What are the arguments for and against the issue? What do people argue with when they want to defend their position? So I started doing some light research.</p>
<blockquote>
<p>And yes, this was light research. I don&rsquo;t care enough about the issues to have spent more than an hour on it. Do your own research if you care so much.</p>
</blockquote>
<h2 id="stuff-i-read">Stuff I Read</h2>
<p>I read some stuff while researching this but these are the ones that I think you should start with. You <em>should</em> do your own research as well: don&rsquo;t take my word for it. Branch out from these links and find your own perspective on the matter. If you write a more comprehensive post on the issue, hit me up coz I&rsquo;d like to read that.</p>
<ul>
<li><a href="http://blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding">An Open Letter to JavaScript Leaders Regarding Semicolons</a></li>
<li><a href="http://blog.izs.me/post/3393190720/how-this-works">How this works&hellip;</a></li>
<li><a href="http://stackoverflow.com/questions/33644285/javascript-and-semicolon">JavaScript and semicolon</a></li>
<li><a href="https://github.com/expressjs/body-parser/issues/99">Why no semicolons?</a></li>
<li><a href="https://news.ycombinator.com/item?id=1547647">Poll: Semicolons in JavaScript</a></li>
</ul>
<h2 id="this-is-what-people-seem-to-be-saying-heavily-paraphrased">This is What People Seem to be Saying (heavily paraphrased)</h2>
<h3 id="semico-yes">Semico-yes:</h3>
<ul>
<li>Not using it introduces weird bugs.</li>
<li>My linter says so.</li>
<li>Douglas Crockford says so.</li>
<li>It&rsquo;s what I&rsquo;ve always done.</li>
<li>It&rsquo;s the standard!</li>
</ul>
<h3 id="semico-no">Semico-no:</h3>
<ul>
<li>It leads to less code.</li>
<li>It reminds me of Ruby,Python,<a href="http://www.muppetlabs.com/~breadbox/bf/">Brainfuck</a>.</li>
<li>Isaac Schlueter says so.</li>
<li>It&rsquo;s what I&rsquo;ve always done.</li>
</ul>
<blockquote>
<p>Right.</p>
</blockquote>
<h2 id="my-unsolicited-thoughts-on-the-matter">My Unsolicited Thoughts on the Matter</h2>
<ul>
<li>Using or not using semicolons in a language shouldn&rsquo;t be because you <a href="https://news.ycombinator.com/item?id=1547930">miss using them/not using them in a different language</a>.</li>
<li>With the way things are today, our interpreters and engines being smarter than ever, a lot of things come down to a matter of preference, semicolon being one of them.</li>
<li>That said certain things make sense about having semi-colons:
<ul>
<li>They <em>explicitly</em> terminate a statement. Anything that&rsquo;s explicit requires less of your mind-power to grok. You don&rsquo;t have to check the next line to see if a function call was terminated or chained.</li>
<li>-crickets chirping-</li>
</ul>
</li>
</ul>
<h2 id="final-thoughts">Final Thoughts</h2>
<p>I like semicolons for their explicitness. But at the end of the day, either style works. I use semi-colons in my personal code and don&rsquo;t in my professional. And I don&rsquo;t find it that hard to switch between the styles (especially since I offload any checking to my linters). So, use what you want, and if you can&rsquo;t because of whatever reasons, don&rsquo;t make a big deal out of it. It matters very little.</p>
<h2 id="notes">Notes:</h2>
<ul>
<li><a href="http://blog.izs.me">http://blog.izs.me</a> is this guy: <a href="https://github.com/isaacs">Isaac Schlueter</a></li>
<li>If you want to understand statement termination in javascript really well, <a href="http://inimino.org/~inimino/blog/javascript_semicolons">check this out</a>.</li>
</ul>
]]></description></item><item><title>Bookmarklets Made My Life Easier</title><link>https://badgerbadgerbadgerbadger.dev/posts/tips/2016-12-07-bookmarklets-made-my-life-easier/</link><pubDate>Sat, 10 Dec 2016 14:17:25 -0400</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/tips/2016-12-07-bookmarklets-made-my-life-easier/</guid><description><![CDATA[<h3 id="update">Update</h3>
<p>I&rsquo;ve put the bookmarklets that I mostly frequently use in every day life on <a href="https://github.com/scionofbytes/bookmarklets">this repository</a>.</p>
<h2 id="being-a-developer-is-fun">Being a Developer is Fun</h2>
<p>One of the advantages of browsing the web as a web-developer vs. as a layman is the fact that you get to do some pretty cool stuff to make your life easier.</p>
<p>For example, I remember once being on a website where a certain action kept failing with no visible errors. I’d click a button and nothing would happen. It was frustrating and a layman would have been forced to quit at that point or write in to the website’s maintainers.</p>
<p>And that&rsquo;s what I did too. I wrote in to the site&rsquo;s maintainers.</p>
<p>But before I did that I fired up the <a href="https://developers.google.com/web/tools/chrome-devtools/console/">developer console on Chrome</a> and started debugging the issue. It turned out to be an error in the script that was trying to handle the button&rsquo;s click event. The event registered to the handler had a typo in the name.</p>
<p>Since I could fix the code on the client, I did so and moved on with my life. I never returned to the site and the developers never wrote back. I hope they found the bug-report useful and it helped better their site.</p>
<blockquote>
<p>Where do bookmarklets come in?</p>
</blockquote>
<p>One of the things I take advantage of most are <a href="http://www.bookmarklets.com/">bookmarklets</a>. These are tiny pieces of javascript code that run in the context of whatever page you’re on and have full access to the page’s DOM. They live in the form of a clickable bookmark on your toolbar and do their thing at the click of a mouse button. You can even configure them to ask for input before taking actions making them incredibly versatile.</p>
<blockquote>
<p>So how have you taken advantage of bookmarklets?</p>
</blockquote>
<h3 id="running-queries-on-rockmongo">Running Queries on Rockmongo.</h3>
<p>Anyone who has used the tool <a href="http://rockmongo.com/">Rockmongo</a> knows how irritating it can be to click through multiple pages to get to a collection’s query interface and then run the query. But once I realized that this action could be represented as a query string, I quickly scripted a little querybuilder that would prompt me for an <a href="https://docs.mongodb.com/manual/core/document/#document-id-field">_id</a> and would perform a lookup against the relevant collection. This improved my productivity a <em>lot</em>.</p>
<blockquote>
<p>Show me what it looks like.</p>
</blockquote>
<p>Here&rsquo;s the code for one of the Rockmongo query builders:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">javascript</span><span class="o">:</span><span class="p">(</span><span class="kd">function</span> <span class="nx">getDocumentById</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="nb">encodeURI</span><span class="p">(</span><span class="s2">&#34;http://&lt;host&gt;/rockmongo/index.php?db=&lt;db_name&gt;&amp;collection=&lt;CollectionName&gt;&amp;action=collection.index&amp;format=json&amp;criteria=%7B%0D%0A%09_id%3A+ObjectId%28%27&#34;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">	<span class="nx">prompt</span><span class="p">()</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">	<span class="s2">&#34;%27%29%0D%0A%7D&amp;newobj=%7B%0D%0A%09%27%24set%27%3A+%7B%0D%0A%09%09%2F%2Fyour+attributes%0D%0A%09%7D%0D%0A%7D&amp;field%5B%5D=_id&amp;order%5B%5D=desc&amp;field%5B%5D=&amp;order%5B%5D=asc&amp;field%5B%5D=&amp;order%5B%5D=asc&amp;field%5B%5D=&amp;order%5B%5D=asc&amp;limit=0&amp;pagesize=10&amp;command=findAll&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">})();</span>
</span></span></code></pre></div><p>It looks quite messy, unfortunately, but it gets the job done. Just replace the stuff in angle brackets with your own values and this should be up and running (if it isn&rsquo;t, <a href="mailto:shuvophoenix@gmail.com">let me know</a>).</p>
<h3 id="getting-email-records-on-the-mandrill-dashboard">Getting Email Records on the Mandrill Dashboard</h3>
<p>Another example would be of querying in <a href="http://www.mandrill.com/">Mandrill</a>. If you&rsquo;ve used <a href="https://mailchimp.com/">Mailchimp</a>&rsquo;s Mandrill service, you&rsquo;ll know just how frustrating it can be to log in to the Mandrill dashboard and query for certain tags on certain dates. The interface is clunky and slow and you get logged out often for no reason.</p>
<p>The only way I could maintain my sanity was to use a handy little bookmarklet to quickly run the queries that I most regularly accessed.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">javascript</span><span class="o">:</span><span class="p">(</span><span class="kd">function</span> <span class="nx">getTodaysTag</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kd">function</span> <span class="nx">getToday</span><span class="p">()</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">	  <span class="k">return</span> <span class="nx">formatDate</span><span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="kd">function</span> <span class="nx">formatDate</span><span class="p">(</span><span class="nx">date</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	  <span class="k">return</span> <span class="p">(</span><span class="nx">date</span><span class="p">.</span><span class="nx">getMonth</span><span class="p">()</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="s1">&#39;/&#39;</span> <span class="o">+</span> <span class="nx">date</span><span class="p">.</span><span class="nx">getDate</span><span class="p">()</span> <span class="o">+</span> <span class="s1">&#39;/&#39;</span> <span class="o">+</span> <span class="nx">date</span><span class="p">.</span><span class="nx">getFullYear</span><span class="p">();</span>  
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> 
</span></span><span class="line"><span class="cl">		<span class="s2">&#34;https://mandrillapp.com/activity?date_format=mm%2Fdd%2Fyy&amp;q=&amp;date_range=custom&amp;start_date=&#34;</span> <span class="o">+</span> 
</span></span><span class="line"><span class="cl">		<span class="nb">encodeURIComponent</span><span class="p">(</span><span class="nx">getToday</span><span class="p">())</span> <span class="o">+</span> 
</span></span><span class="line"><span class="cl">		<span class="s2">&#34;&amp;stop_date=&#34;</span> <span class="o">+</span> 
</span></span><span class="line"><span class="cl">		<span class="nb">encodeURIComponent</span><span class="p">(</span><span class="nx">getToday</span><span class="p">())</span> <span class="o">+</span> 
</span></span><span class="line"><span class="cl">		<span class="s2">&#34;&amp;tag=&#34;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">		<span class="s2">&#34;&lt;your-tag&gt;&#34;</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">		<span class="s2">&#34;&amp;sender=&amp;search-select-q=&amp;api_key=&amp;__csrf_token=&lt;your-token&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">})();</span>
</span></span></code></pre></div><p>The above script builds a query string that would fetch for you all the emails tagged with a particular tag (which you can also accept dynamically by calling <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt"><code>prompt()</code></a>), that were sent on the current date. You don&rsquo;t have to click through multiple boxes, you just click on one little bookmarklet.</p>
<p>One caveat is that these two bookmarklets work by building querystrings which are then accessed like any normal page. This only works if you&rsquo;re already logged in to Rockmongo or Mandrill. If you&rsquo;re not, you&rsquo;ll be redirected to the login page, after which you&rsquo;ll come to the dashboard&rsquo;s standard homepage and will have to click on the bookmarklet again. Still better than the alternative.</p>
<blockquote>
<p>That sounds pretty cool but what else can I do? I don&rsquo;t use Mandrill or Rockmongo.</p>
</blockquote>
<p>These are just two examples of what you can do with bookmarklets and that&rsquo;s not the end of it. Tumblr&rsquo;s <a href="https://www.tumblr.com/docs/en/lesser_known_features">Post to Tumblr</a> bookmarklet let&rsquo;s you quickly share the contents of any page to your tumblr blog.</p>
<p>Similarly many other services use bookmarklets to for sharing content or simply providing better readability. <a href="www.hongkiat.com">Hongkiat</a> has this <a href="http://www.hongkiat.com/blog/100-useful-bookmarklets-for-better-productivity-ultimate-list/">beautiful list of bookmarklets</a> that you can copy to your bookmarks bar and start using immediately. And since they are just pieces of javascript code, you can customize them as much as you want and have your own version of the bookmarklet tailored to your needs.</p>
<p>So there you go, the power of bookmarklets. I hope this will help you become more productive in your everyday browsing or at least alleviate some of the pains. If you have any questions, send me an <a href="mailto:shuvophoenix@gmail.com">email</a> or drop me a <a href="https://twitter.com/scionofbytes">tweet</a>.</p>
]]></description></item><item><title>Styleguides: Hapi Vs. Airbnb</title><link>https://badgerbadgerbadgerbadger.dev/posts/code-craftsmanship/2016-12-04-styleguides-hapi-vs-airbnb/</link><pubDate>Sun, 04 Dec 2016 14:17:25 -0400</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/code-craftsmanship/2016-12-04-styleguides-hapi-vs-airbnb/</guid><description><![CDATA[<p>This post was inspired by my writing of another post which you can read [here]({{ site.baseurl }}{% post_url 2016-12-01-letter-casing-in-names-of-imported-nodejs-modules %}). On that one I talk about letter casing conventions in the names of imported NodeJS modules. Not the catchiest of titles but if it&rsquo;s something that interests you, have a look.</p>
<p>I&rsquo;d been a strict adherer of the <a href="https://github.com/airbnb/javascript">Airbnb javascript styleguide</a> for more than a year. In the previous company I&rsquo;d worked at, we needed a styleguide that made sense, was intuitive, and made reading our code easier. So we did our research, found the airbnb styleguide to be the most suitable, forked it from airbnb&rsquo;s repo, made some modifications to suit our usecases (while retaining over 95% of the original rules), and moved on with our lives, worrying less about the style and more about our product.</p>
<p>Airbnb&rsquo;s is the kind of styleguide that let&rsquo;s you do that. Once you&rsquo;ve gone through it, understood the reasoning behind the rules and worked within their bounds for a few weeks, they become second nature. Mostly because most of the rules make intuitive sense in the first place. You quickly find yourself focusing less on the style and more on the meat of the code, because the style has already become second nature.</p>
<p>The company I&rsquo;m working at right now uses a subset of <a href="http://hapijs.com/">Hapi</a>&rsquo;s <a href="http://hapijs.com/styleguide">styleguide</a>. At first I was excited to be practicing code in a new style. Every new style is an opportunity to enter the minds of the people who wrote it and gain a new perspective. And every new perspective improves us in subtle ways.</p>
<p>And the Hapi styleguide <em>did</em> do that for me. I came across the PascalCase naming convention, realized the reasoning behind it, and wondered why I hadn&rsquo;t used it before when it made <a href="https://scionofbytes.github.io/code-craftsmanship/letter-casing-in-names-of-imported-nodejs-modules/#project-modules">so much sense</a>.</p>
<p>But here&rsquo;s the thing: I had to figure out the reasoning behind it myself. The Hapi styleguide did not do that for me.</p>
<p>The entire tone of the Hapi styleguide reads like a list of commandments. <em>Do this</em>, <em>Don&rsquo;t do that</em>, <em>This is not allowed.</em> I feel like I&rsquo;m being dictated to and not taught. There are plenty of <em>rules</em> but no <em>reasoning</em>.</p>
<p>If you compare this to the airbnb styleguide, you&rsquo;ll find that most of their rules come with some sort of reasoning behind it. <em>Do this. Why? Because this leads to this and thus you&rsquo;ll experience this benefit.</em> That&rsquo;s the entire tone of the airbnb styleguide- one of teaching and building intuition.</p>
<p>Try this. Open up each styleguide and look for the word <em>why</em>. I did this in order to quickly test out how often airbnb explains itself and how often Hapi does the same. For airbnb I got 53 hits on doing a search in Chrome. For Hapi I got 0.</p>
<p>Styleguides exist to enforce consistency across a codebase. If all your engineers are following the same styleguide, your code will look the same all over the place. This makes it easier for the entire company to work on the code and for one person to contribute to another&rsquo;s work.</p>
<p>But a good styleguide also introduces patterns that improve your thinking and the architecture of your code. It makes you question why certain things should be done a certain way and come up with answers that make the most <em>sense</em>. It makes you a better engineer in the long run.</p>
<p>And that&rsquo;s all I have to say on that.</p>
]]></description></item><item><title>Letter Casing in Names of Imported NodeJS Modules</title><link>https://badgerbadgerbadgerbadger.dev/posts/code-craftsmanship/2016-12-01-letter-casing-in-names-of-imported-nodejs-modules/</link><pubDate>Thu, 01 Dec 2016 14:17:25 -0400</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/code-craftsmanship/2016-12-01-letter-casing-in-names-of-imported-nodejs-modules/</guid><description><![CDATA[<p><em>Disclaimer: This isn&rsquo;t a tutorial, guide, or holy writ. This is just how I do things and I&rsquo;d like to share why it makes sense to me. If it resonates with you, follow it. If it doesn&rsquo;t, follow whatever makes sense to you and your team.</em></p>
<p>Styleguides are something I take very seriously. To me a programmer who has great style and writes clean, readable code is worth a lot more than someone who&rsquo;s just clever with algorithms. Cleaner code results in better collaboration within teams and better maintenance of the project going forward.</p>
<p>Now one of the things every good styleguide has is a sensible naming convention. This can mean both conventions for variables and functions on a very broad level, as well as a subtler level- things such as the letter casing of imported modules.</p>
<blockquote>
<p>Yes, I wrote an entire post about how I case the names of my modules.</p>
</blockquote>
<p>I follow a very specific naming convention for when I import modules in NodeJS. Though they differ for on-site modules and vendor modules, for each, the rules are [mostly] consistent.</p>
<p>Here&rsquo;s what I do and why I do it.</p>
<h2 id="project-modules">Project Modules</h2>
<p>These are the modules you write as part of your own project. I won&rsquo;t go into how you should structure your source files or name them: that&rsquo;s beyond the scope of this article. But let&rsquo;s talk about how you can name them once imported into another module.</p>
<p>Previously, I used to be of the <em>name everything in camelCase</em> camp. I literally used to name everything in camelCase without any thought to context or suitability. Why? Because everyone else did it, so why not?</p>
<p>After I joined the company I&rsquo;m working at as of this writing, I was introduced to a new styleguide, one of the rules of which was: <strong>Use uppercase variable names for imported modules.</strong> I went along with it coz I did not see a reason not to. It&rsquo;s actually one of the conventions picked up from the <a href="http://hapijs.com/styleguide">Hapi styleguide</a>.</p>
<p>Now the Hapi styleguide honestly sucks. Not because any of the conventions are wrong: I&rsquo;m not sure if conventions, by their very definition, <em>can</em> be wrong (<a href="mailto:shuvophoenix@gmail.com">correct me if I&rsquo;m wrong on this point</a>). Conventions are conventions. They just <em>are</em>.</p>
<blockquote>
<p>I&rsquo;m thinking of writing a whole other post to talk about why I dislike Hapi&rsquo;s styleguide.</p>
</blockquote>
<p>After thinking about it for a while I realized how PascalCased names can be advantageous in certain situations.</p>
<p>In Java, with its concept of classes and interfaces, a class or interface would be PascalCased to indicate that it&rsquo;s not an instance but a class. Its functions would be static i.e. without an object context. Instantiated objects would be camelCased and have non-static methods.</p>
<p>Javascript, though object-oriented, does not have classes or interfaces; its inheritance is based on <a href="http://javascriptissexy.com/javascript-prototype-in-plain-detailed-language/">prototypes</a>. Moreover the <em>object</em> in Javascript can also be used (and <em>is</em> extensively used) as what would be a dictionary type in other languages.</p>
<p>Now, what&rsquo;s the advantage of naming modules <code>required</code> from within your own project in PascalCase? You can immediately differentiate between those that are instances vs. those that are just plain objects.</p>
<p>Consider a module imported like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">MyModule</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;path/to/my_module&#39;</span><span class="p">)</span>
</span></span></code></pre></div><p>Most likely it will be composed of functions that should be used as-is, without a <code>this</code> context attached to them. Those functions are just functions and not <em>methods</em> i.e. they don&rsquo;t have an <em>object</em> they are bound to. You can freely pass these around without any issues.</p>
<p>On the other hand, when you have a module that exposes an object instantiated from a  <em>class</em> (I know I said Javascript doesn&rsquo;t have classes, and it doesn&rsquo;t; read <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes">this</a> for clarity), or a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor">constructor function</a>, you expose it as such.</p>
<p>So say I have an instance of an AWS Lambda service. I&rsquo;d expose it as so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">AWS</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;aws-sdk&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">lambda</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AWS</span><span class="p">.</span><span class="nx">Lambda</span><span class="p">({</span><span class="nx">apiVersion</span><span class="o">:</span> <span class="s1">&#39;2015-03-31&#39;</span><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">lambda</span><span class="p">;</span>
</span></span></code></pre></div><p>And I import it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">lambda</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;path/to/lambda.js&#39;</span><span class="p">)</span>
</span></span></code></pre></div><p>Vendors like AWS also use this convention where a module which has functions that are free of a <code>this</code> context are in PascalCase to indicate that they are <em>class like</em>, while the instantiated object itself is in camelCase.</p>
<p>It&rsquo;s a subtle difference and it&rsquo;s not necessarily the <em>best</em> convention (if you find the <em>best</em> one, do let me know), but it makes sense and it works for me.</p>
<p>To summarize:</p>
<ul>
<li>If I&rsquo;m exposing a module with functions not bound to any objects, I think of that module like you would a class in Java where all the functions are static ones. These are always PascalCased.</li>
<li>If I&rsquo;m exposing a module which is already instantiated- hence its methods are bound to the object- I think of these like instantiated objects in Java. These are camelCased.</li>
</ul>
<p>It&rsquo;s that simple, really.</p>
<h2 id="vendor-modules">Vendor Modules</h2>
<p>These are the third-party libraries you get from the npm registry (pretty-much 99% of the time), or directly via a <a href="http://stackoverflow.com/a/17509764/2584375">github remote url</a>.</p>
<p>My policy with vendor modules is this: I try to name them the same way the vendors themselves have in their example code on github | npm | website | blog.</p>
<blockquote>
<p>Why?</p>
</blockquote>
<p>To make things more readable.</p>
<blockquote>
<p>How does following the vendor&rsquo;s own convention for their library&rsquo;s usage help in code readability?</p>
</blockquote>
<p>Here&rsquo;s how:</p>
<ul>
<li>
<p>Using libraries the way that the owner/maintainer uses it means that when someone looks at your code and looks at the vendor&rsquo;s examples, they&rsquo;ll be seeing the same thing. They can map those examples to your code much faster.</p>
</li>
<li>
<p>Since the usage is what&rsquo;s shown on the library owner&rsquo;s GitHub/Npm page, that&rsquo;s most likely how other people have used it too. Stackoverflow posts, tutorials, blog posts, examples, what-will-you; reading other people&rsquo;s code/blogs about the library becomes easier if you see a library being used in the same way.</p>
</li>
</ul>
<p>This seems like a small thing, but when you&rsquo;re reading code a certain number of weeks down the line, or debugging stuff by googling it, having vendor libraries named in the same way everywhere really helps.</p>
<p>There are exceptions to every rule, of course, and one of mine is the <a href="http://bluebirdjs.com/docs/api/promise.coroutine.html"><code>coroutine</code></a> function from <a href="http://bluebirdjs.com/">Bluebird</a>.</p>
<p>I always import it as:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">co</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;bluebird&#39;</span><span class="p">).</span><span class="nx">coroutine</span><span class="p">;</span>
</span></span></code></pre></div><blockquote>
<p>Why?</p>
</blockquote>
<ul>
<li>
<p>I make use of the coroutine pattern extensively (at least once in every asynchronous function) in my code and writing <code>co</code> instead of <code>Promise.coroutine</code> simplifies life.</p>
</li>
<li>
<p>Using <code>co</code> this way is not something I came up with on my own. <a href="https://github.com/tj">TJ Holowaychuk</a>&rsquo;s <a href="https://github.com/tj/co"><code>co</code> library</a> has the same pattern. I use Bluebird&rsquo;s implementation because it&rsquo;s faster (sorry TJ 😅) but TJ&rsquo;s code is familiar enough that I think others (and myself) will understand what the intent of the function is.</p>
</li>
</ul>
<h2 id="in-conclusion">In Conclusion&hellip;</h2>
<p>&hellip;I love having a set of rules defining code style. It helps me read code faster, helps me grok it faster, and makes me more productive. But even the most stringent rules have to be broken at times and that&rsquo;s what it really comes down to in code craftsmanship: knowing when to follow the rules and when to break them.</p>
]]></description></item><item><title>Working with SFML::Textures — The White Box Problem</title><link>https://badgerbadgerbadgerbadger.dev/posts/game-programming/2016-10-26-working-with-sfml-textures-the-white-box-problem/</link><pubDate>Wed, 26 Oct 2016 14:17:25 -0400</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/game-programming/2016-10-26-working-with-sfml-textures-the-white-box-problem/</guid><description><![CDATA[<p>So I started using textures in my code, and it’s not been a fun time, mainly because I was missing some key understanding of how c++ keeps things in scope. Sadly I still don’t understand how what I did solve things, but I guess I’ll find out with more research.</p>
<p>Anyway, I’ll document what I did so that anyone trying this out can see exactly what I did wrong. Or rather which steps were probably wrong and how I ended up making things work. I still don’t fully understand the root cause.</p>
<p>I first started out by defining a Ball class (I’m making a Pong! game), like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Ball</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">        <span class="kt">unsigned</span> <span class="n">collider_size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">scionofbytes</span><span class="o">::</span><span class="n">MovementComponent</span> <span class="n">movement</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">scionofbytes</span><span class="o">::</span><span class="n">GraphicComponent</span> <span class="n">graphic</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">Ball</span><span class="p">(</span><span class="n">u_int</span> <span class="n">init_collider_size</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">texture_path</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">collider_size</span> <span class="o">=</span> <span class="n">init_collider_size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">movement</span> <span class="o">=</span> <span class="n">scionofbytes</span><span class="o">::</span><span class="n">MovementComponent</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="n">graphic</span> <span class="o">=</span> <span class="n">scionofbytes</span><span class="o">::</span><span class="n">GraphicComponent</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="n">u_int</span><span class="p">)</span> <span class="n">collider_size</span><span class="o">/</span><span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">(</span><span class="n">u_int</span><span class="p">)</span> <span class="n">collider_size</span><span class="o">/</span><span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">texture_path</span>
</span></span><span class="line"><span class="cl">            <span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>The Ball class has a collider size (which should probably be its own component), a movement component and a graphic component. The Ball constructor would receive a <code>texture_path</code> as a string and pass it on to the <code>GraphicComponent</code>&rsquo;s constructor.</p>
<p>The GraphicComponent looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">GraphicComponent</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="n">height</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="n">width</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">sf</span><span class="o">::</span><span class="n">Texture</span> <span class="n">texture</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">sf</span><span class="o">::</span><span class="n">Sprite</span> <span class="n">sprite</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">GraphicComponent</span><span class="p">()</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">GraphicComponent</span><span class="p">(</span><span class="kt">unsigned</span> <span class="n">init_height</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="n">init_width</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">texture_path</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">width</span> <span class="o">=</span> <span class="n">init_width</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">height</span> <span class="o">=</span> <span class="n">init_height</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="n">texture</span><span class="p">.</span><span class="n">loadFromFile</span><span class="p">(</span><span class="n">texture_path</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">sprite</span><span class="p">.</span><span class="n">setTexture</span><span class="p">(</span><span class="n">texture</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>I use the <code>texture_path</code> to load the texture into the <code>sf::Texture</code> type, and then pass that on to the <code>sf::Sprite</code> (the sprite doesn’t store a copy of the texture, only a pointer to it; the texture must exist for as long as the sprite is using it).</p>
<p>Now the point about needing to keep the texture alive for as long as the sprite was using it is something I understood. What I couldn’t figure is why the texture kept getting destroyed once I’d instantiated the <code>Ball</code> class like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">scionofbytes</span><span class="o">::</span><span class="n">Ball</span> <span class="n">ball</span> <span class="o">=</span> <span class="n">scionofbytes</span><span class="o">::</span><span class="n">Ball</span><span class="p">(</span><span class="mi">50</span><span class="p">,</span> <span class="s">&#34;path_to_texture&#34;</span><span class="p">);</span>
</span></span></code></pre></div><p>I’m creating the texture as part of the new ball object and as far as I could figure, it’s created as a local member of that object. So if the object is alive, the member is alive. Hence, passing the texture to the sprite shouldn’t be an issue. If the <code>ball</code> object is alive, so its <code>GraphicComponent</code> and so is the texture.</p>
<p>But there was still something going wrong and the texture was being lost before the system started drawing the sprite to the screen.</p>
<blockquote>
<p>And what happens when you draw a <code>sf::Sprite</code> to the screen whose texture is lost? You get a White Box (of death|frustration|sadness).</p>
</blockquote>
<p>So how did I go about fixing this? I created a holder for the textures that would always be in scope and then passed the texture’s pointer to the sprite.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">scionofbytes</span><span class="o">::</span><span class="n">TextureManager</span> <span class="n">textureManager</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">textureManager</span><span class="p">.</span><span class="n">add_texture</span><span class="p">(</span><span class="s">&#34;ball&#34;</span><span class="p">,</span> <span class="s">&#34;path_to_texture&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">scionofbytes</span><span class="o">::</span><span class="n">Ball</span> <span class="n">ball</span> <span class="o">=</span> <span class="n">scionofbytes</span><span class="o">::</span><span class="n">Ball</span><span class="p">(</span><span class="mi">50</span><span class="p">,</span> <span class="n">textureManager</span><span class="p">.</span><span class="n">get_texture</span><span class="p">(</span><span class="s">&#34;ball&#34;</span><span class="p">));</span>
</span></span></code></pre></div><p>The <code>TextureManager</code> class just stores textures in a map of type <code>std::map&lt;std::string, sf::Texture&gt;</code> and the <code>get_texture</code> method returns a pointer to a texture by its key. This pointer is then passed on to the constructors which have been changed to accept pointers.</p>
<p>The <code>GraphicComponent</code> class now has a <code>sf::Texture*</code> (pointer) type instead of a texture itself and passes on the same to the sprite (which was storing a pointer, anyway).</p>
<p>And it all works.</p>
<p><img src="http://i.imgur.com/kx5lejh.png" loading="lazy" decoding="async" alt="KirbyBall"></p>
]]></description></item><item><title>First Foray into C++ with SFML</title><link>https://badgerbadgerbadgerbadger.dev/posts/game-programming/2016-10-23-first-foray-into-cpp-with-sfml/</link><pubDate>Sun, 23 Oct 2016 14:17:25 -0400</pubDate><guid>https://badgerbadgerbadgerbadger.dev/posts/game-programming/2016-10-23-first-foray-into-cpp-with-sfml/</guid><description><![CDATA[<p>Game-programming has always been one of my interests ever since I coded a simple game of <a href="https://www.openprocessing.org/sketch/158668">Pong!</a> back in college using <a href="https://processing.org/">Processing</a>. But my first job ended up being in web-development and from then on that’s all I’ve been doing. It is easier to get a foot in through the door in web-development than it is in game-development (I don’t have a formal Computer Science education), and hence, two years into being a earning member of society, I’m still in web-development.</p>
<p>But my interest in game-development never went away and so recently I picked up C++ and the SFML library to try to see what I can learn and what I can make.</p>
<p>After figuring out how to set up <a href="http://www.sfml-dev.org/tutorials/2.4/start-osx.php">SFML for XCode</a>, I got into the thick of things and started coding. It’s been a helluva 10 days and here are some of the things I think are worth noting down because, well, they gave me trouble. And it’s always worth remembering the things that give you trouble.</p>
<p>My goals with my first program were simple. Make a sprite that moves across the screen based on user input (Left, Right, Up, Down). If the user keeps holding the button, the sprite accelerates. If the user lets go of the button, the sprite keeps moving with whatever velocity it had achieved during acceleration. There is also a constant drag force acting in the opposite direction of the sprite’s movements that would eventually bring it to a stop when no longer acted upon.</p>
<blockquote>
<p>Simple enough.</p>
</blockquote>
<h1 id="headers-and-headerguards">Headers and Header Guards?</h1>
<p>One of the first things I had to conceptually decide on was how to structure my header files. Header files and the whole concept of forward declarations doesn’t exist in Java and Javascript (the two languages I’ve used in the past) and so working with header files was a completely new experience for me, especially when it came to project organization. I faced a binary choice here (if there are more ways of doing this, someone do let me know): </p>
<p>Do I keep them along with the source file, each pair being stored as a unit? Or do I keep all the headers in one place?</p>
<p><a href="http://www.codeblocks.org/">Code::Blocks</a> organizes it’s projects by separating out its files into <code>source</code> and <code>header</code> trees, and the SFML library itself has headers stored in one folder under a hierarchical structure. Although coding in modern IDEs is easier than ever (you move a file and the IDE <strong>updates all the hundreds of references</strong> for you!), I still wanted all of my headers to be in one place and referable from one root location. So I put them all under the <code>header</code> directory in the root of my project.</p>
<p>Another thing that tripped me up were the <a href="http://www.learncpp.com/cpp-tutorial/1-10a-header-guards/">header guards</a>. I understood what they were and I understood how to use them, but I let the IDE do the job of creating the guards when I created new header files (the guards were named after the path of the file inside the <code>src</code> folder; example: <code>MOVE_COMPONENT_GRAPHIC_H</code>) with the result that after I’d moved and renamed some files, the header guards did not update (the IDE is not that smart), so I ended up with two header files with the same guard.</p>
<blockquote>
<p>And what happened when I included both in my main.cpp?</p>
</blockquote>
<p>The second one did not import and I couldn’t figure out why my struct wasn’t usable in the current scope. It took me a good hour to realize that there was nothing wrong with the IDE or the compiler. I had simply not taken the header guards into account.</p>
<p>I still let the IDE generate the guards for me (no point in having an IDE if it doesn’t do the heavy-lifting), but I always double-check to ensure that no other files have the same guard. Doing a quick text-search across the project does the trick.</p>
<h1 id="nan-nan-nan-nannan">nan, nan, nan, nan, nan…</h1>
<p>I didn’t realize, initially, that SFML had its own Vector classes (I didn’t RTFM, just jumped into code; Kids, don’t do this. Always <em><strong>RTFM!</strong></em>), and so went ahead and wrote one of my own. A 2D vector class that had x and y members and the usual methods for vector computations, including a method to find the vector’s magnitude and one to normalize it. <a href="http://www.fundza.com/vectors/normalize/">Now here is how you normalize a vector.</a> As you can see, if the magnitude is zero, you’re in for some trouble.</p>
<p>Now in Java a division by zero is thrown as an <a href="http://www.studytonight.com/java/exception-handling.php">Exception</a>. In NodeJS, a division by zero yields <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity">Infinity</a>, but for C++, a division by zero yields <a href="http://www.cplusplus.com/reference/cmath/nan-function/">nan</a> of the appropriate type (yes, a nan for a double is not the same type as a nan for a float, just like 0 and 0.0 are different types). I didn’t know this would happen, and I’d forgotten to account for vectors of 0 magnitude.</p>
<blockquote>
<p>So, what happened?</p>
</blockquote>
<p>Nothing. Literally nothing happened. As in no computation would work because I as computing with nan all over the place and nan computed with any number gives you <em>nan</em>. Took me some printing of the values to figure this one out, though in hindsight this should have been laughably obvious.</p>
<h1 id="the-drag">The Drag</h1>
<p>Another idiotic thing I did was in writing the drag function. Here’s the first version of that.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">scionofbytes</span><span class="o">::</span><span class="n">apply_drag</span><span class="p">(</span><span class="n">scionofbytes</span><span class="o">::</span><span class="n">Mover</span><span class="o">&amp;</span> <span class="n">mover</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">scionofbytes</span><span class="o">::</span><span class="n">Vec2D</span> <span class="n">drag</span> <span class="o">=</span> <span class="n">mover</span><span class="p">.</span><span class="n">velocity</span><span class="p">.</span><span class="n">scale</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">opposite_velocity</span><span class="p">.</span><span class="n">normalize</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">opposite_velocity</span><span class="p">.</span><span class="n">scale</span><span class="p">(</span><span class="n">scionofbytes</span><span class="o">::</span><span class="n">DRAG_COEFFICIENT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">mover</span><span class="p">.</span><span class="n">velocity</span><span class="p">.</span><span class="n">subtract</span><span class="p">(</span><span class="n">drag</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Anyone see what I did wrong here? Anyone?</p>
<p>I’ll wait.</p>
<p>…</p>
<p>…</p>
<p>…</p>
<p>Okay, if you haven’t figured it out already, I subtracted the drag vector when I should have been adding it.</p>
<p>See, I’d already computed the drag force as a vector in the opposite direction to the velocity vector. And by subtracting this vector instead of adding it to velocity, I’d just increased the velocity.</p>
<p>It’s like subtracting a negative number: you actually end up adding move value. See?</p>
<p>Anyway, those were just some of the things I struggled with in my early delving of C++ and SFML. And all of them weren’t even related to programming. I’ll keep posting my adventures as they take place.</p>
<p>Cheers!</p>
]]></description></item><item><title>About</title><link>https://badgerbadgerbadgerbadger.dev/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://badgerbadgerbadgerbadger.dev/about/</guid><description><![CDATA[<p>Hi! Thanks for visiting my little corner of the internet. I&rsquo;m Shaun, a software engineer, improv player and father of one based out of Belgium. I dabble in a myriad of things, but I&rsquo;m most passionate about building software toys, improv and writing.</p>
]]></description></item></channel></rss>