Congratulations!

[Valid RSS] This is a valid RSS feed.

Recommendations

This feed is valid, but interoperability with the widest range of feed readers could be improved by implementing the following recommendations.

Source: http://akrabat.com/feed/

  1. <?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
  2. xmlns:content="http://purl.org/rss/1.0/modules/content/"
  3. xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  4. xmlns:dc="http://purl.org/dc/elements/1.1/"
  5. xmlns:atom="http://www.w3.org/2005/Atom"
  6. xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
  7. xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
  8. >
  9.  
  10. <channel>
  11. <title>Rob Allen</title>
  12. <atom:link href="https://akrabat.com/feed/" rel="self" type="application/rss+xml" />
  13. <link>https://akrabat.com</link>
  14. <description>Pragmatism in the real world</description>
  15. <lastBuildDate>Thu, 19 Jun 2025 10:43:01 +0000</lastBuildDate>
  16. <language>en-US</language>
  17. <sy:updatePeriod>
  18. hourly </sy:updatePeriod>
  19. <sy:updateFrequency>
  20. 1 </sy:updateFrequency>
  21. <generator>https://wordpress.org/?v=6.8.1</generator>
  22. <item>
  23. <title>FIxing Linux Tailscale exit node routing</title>
  24. <link>https://akrabat.com/fixing-linux-tailscale-exit-node-routing/</link>
  25. <comments>https://akrabat.com/fixing-linux-tailscale-exit-node-routing/#respond</comments>
  26. <dc:creator><![CDATA[Rob]]></dc:creator>
  27. <pubDate>Tue, 24 Jun 2025 09:00:00 +0000</pubDate>
  28. <category><![CDATA[Computing]]></category>
  29. <guid isPermaLink="false">https://akrabat.com/?p=7401</guid>
  30.  
  31. <description><![CDATA[I run a Tailscale network so that remote computers can access local services. I also have a Linux box at home on that network that advertises itself as an exit node and recently noticed that it wasn't working. I had some time recently to sit down and work out what was going on. My initial suspicion was that it was DNS related as a cursory search brought up lots of results related to DNS. However,… <a href="https://akrabat.com/fixing-linux-tailscale-exit-node-routing/">continue reading</a>.]]></description>
  32. <content:encoded><![CDATA[<p>I run a <a href="https://tailscale.com/">Tailscale</a> network so that remote computers can access local services. I also have a Linux box at home on that network that advertises itself as an <a href="https://tailscale.com/kb/1103/exit-nodes">exit node</a> and recently noticed that it wasn't working.</p>
  33. <p>I had some time recently to sit down and work out what was going on. My initial suspicion was that it was DNS related as a cursory search brought up lots of results related to DNS. However, some quick tests with <tt>nslookup</tt> and <tt>dig</tt> showed that DNS was correctly resolving, so it seemed to be a routing issue.</p>
  34. <p>Further searching led me to realise that my Linux box needs to masquerade the traffic. This can be done using:</p>
  35. <pre>
  36. sudo iptables -t nat -A POSTROUTING -o {network interface} -j MASQUERADE
  37. </pre>
  38. <p>I used <tt>ifconfig</tt> to look up my network interface which was <tt>enp3s0</tt> and then all was well. </p>
  39. <p>I connected my Mac to the exit node from a remote location and could browse the web with my remote IP address correctly set to my home's IP address.</p>
  40. <p>Given that this was working, I'm unclear what has changed such that this setting needed configuring. I have found <a href="https://github.com/tailscale/tailscale/issues/15708">issue 15708</a> which may be related so potentially a future Tailscale update will solve this. I don't rebook this box often, so maybe I set this flag before and forgot?</p>
  41. <p>I've written it up here now though, so I can find it again if I need it!</p>
  42. ]]></content:encoded>
  43. <wfw:commentRss>https://akrabat.com/fixing-linux-tailscale-exit-node-routing/feed/</wfw:commentRss>
  44. <slash:comments>0</slash:comments>
  45. </item>
  46. <item>
  47. <title>Renaming files with Hazel</title>
  48. <link>https://akrabat.com/renaming-files-with-hazel/</link>
  49. <comments>https://akrabat.com/renaming-files-with-hazel/#respond</comments>
  50. <dc:creator><![CDATA[Rob]]></dc:creator>
  51. <pubDate>Tue, 17 Jun 2025 10:00:00 +0000</pubDate>
  52. <category><![CDATA[Mac]]></category>
  53. <guid isPermaLink="false">https://akrabat.com/?p=7390</guid>
  54.  
  55. <description><![CDATA[I'm a member of a number of groups that publish a magazine, either paper-based or PDF. I prefer the PDF version, so download from the website and then move to the relevant directory. Recently, I realised that I could use Hazel to do this for me. To take one example, the filename of the PDF that I download is of the format PE-{issue number}-web{some random characters}.pdf, for example: PE-123-web9j45s3gd.pdf. There's sometimes a hyphen after web,… <a href="https://akrabat.com/renaming-files-with-hazel/">continue reading</a>.]]></description>
  56. <content:encoded><![CDATA[<p>I'm a member of a number of groups that publish a magazine, either paper-based or PDF. I prefer the PDF version, so download from the website and then move to the relevant directory.</p>
  57. <p>Recently, I realised that I could use Hazel to do this for me.</p>
  58. <p>To take one example, the filename of the PDF that I download is of the format <tt>PE-{issue number}-web{some random characters}.pdf</tt>, for example: <tt>PE-123-web9j45s3gd.pdf</tt>. There's sometimes a hyphen after <tt>web</tt>, but not always.</p>
  59. <p>I want the filename to <tt>PE Magazine {issue number}</tt></p>
  60. <p>The rule in Hazel looks like this:</p>
  61. <picture><source  
  62. srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-rename-pe-mag-dark.png"
  63.        media="(prefers-color-scheme: dark)"
  64.    /><source
  65. srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-rename-pe-mag-light.png"
  66.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  67.    /><br />
  68.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-rename-pe-mag-light.png" loading="lazy" alt="Screenshot of Hazel rename rule" width="500"/>
  69. </picture>
  70. <h2>Matching the name</h2>
  71. <p>Breaking it down, we detect that this is a file of interest with a <em>Name matches</em> rule. We can start with the literal string <tt>PE-</tt> as that's constant, but then we need to match the number and also give it a name so that we can refer to it later.</p>
  72. <p>This is done using the "Custom Text" element. You can then click on it to edit its attributes where I set its name to "issue" and the pattern to "Number" which matches sequential digits.</p>
  73. <picture><source  
  74. srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-match-attributes-dark.png"
  75.        media="(prefers-color-scheme: dark)"
  76.    /><source
  77. srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-match-attributes-light.png"
  78.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  79.    /><br />
  80.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-match-attributes-light.png" loading="lazy" alt="Screenshot of Hazel match attributes screen" class="border" width="300"/>
  81. </picture>
  82. <p>The rest of the name match is a hyphen followed by an "Anything" element as we don't care about any other characters.</p>
  83. <p><tt>Renaming</tt></p>
  84. <p>Now that we have we can use the <em>Rename with pattern</em> action with the literal "<tt>PE Magazine - </tt>" followed by our "issue" custom element that we created in the match rule, followed by the extension.</p>
  85. <h2>Finishing it off</h2>
  86. <p>Once the file has been renamed, there's a "Move to folder" action to move it the right place and then the folder is opened, so that I can grab the file if I need to.</p>
  87. <p>That's it. Automating the rename and move means that I don't have to think about it again.</p>
  88. ]]></content:encoded>
  89. <wfw:commentRss>https://akrabat.com/renaming-files-with-hazel/feed/</wfw:commentRss>
  90. <slash:comments>0</slash:comments>
  91. </item>
  92. <item>
  93. <title>Updating SMTP relay in postfix</title>
  94. <link>https://akrabat.com/updating-smtp-relay-in-postfix/</link>
  95. <comments>https://akrabat.com/updating-smtp-relay-in-postfix/#respond</comments>
  96. <dc:creator><![CDATA[Rob]]></dc:creator>
  97. <pubDate>Tue, 10 Jun 2025 10:00:00 +0000</pubDate>
  98. <category><![CDATA[Computing]]></category>
  99. <category><![CDATA[Sysadmin]]></category>
  100. <guid isPermaLink="false">https://akrabat.com/?p=7383</guid>
  101.  
  102. <description><![CDATA[On a server that I help to maintain, it has postfix installed for emailing results of cron jobs and other status updates. This was set up to relay through SendGrid as they had a 100 email per month plan and we send out significantly fewer than that. Unfortunately, SendGrid are retiring their free plan, so I had to move to a new service and picked SMTP2GO and so had to update the postfix configuration. As… <a href="https://akrabat.com/updating-smtp-relay-in-postfix/">continue reading</a>.]]></description>
  103. <content:encoded><![CDATA[<p>On a server that I help to maintain, it has <a href="https://www.postfix.org">postfix</a> installed for emailing results of cron jobs and other status updates. This was set up to relay through SendGrid as they had a 100 email per month plan and we send out significantly fewer than that.</p>
  104. <p>Unfortunately, SendGrid are retiring their free plan, so I had to move to a new service and picked <a href="https://www.smtp2go.com">SMTP2GO</a> and so had to update the postfix configuration. As I didn't have this written down, I'm noting in here.</p>
  105. <h2>Get SMTP details</h2>
  106. <p>You need the <tt>SMTP hostname</tt>, <tt>SMTP username</tt> and <tt>SMTP password</tt>. SMTP2GO allows you to have multiple usernames and usefully allows you to rate limit each one individually, should you need that.</p>
  107. <h2>Configure postfix</h2>
  108. <h3>Firstly, update <tt>/etc/postfix/main.cf</tt></h3>
  109. <p>Find <tt>relayhost</tt> and change to <tt>[mail.smtp2go.com]:587</tt>. I'm just updating, so the rest of the settings I left alone. </p>
  110. <p>However, the relevant settings are:</p>
  111. <pre>
  112. relayhost = [mail.smtp2go.com]:587
  113. smtp_sasl_auth_enable = yes
  114. smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
  115. smtp_sasl_security_options = noanonymous
  116. smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
  117. smtp_use_tls = yes
  118. </pre>
  119. <h3>Secondly, add the new SMTP user's credentials</h3>
  120. <p>Edit <tt>/etc/postfix/sasl_passwd</tt>. This needs to be done as root as the permissions on this file are <tt>600</tt> and it's owned by <tt>root:root</tt>.</p>
  121. <p>Add a new line with this format:</p>
  122. <pre>
  123. [mail.smtp2go.com]:587 {username}:{password}
  124. </pre>
  125. <p>Where <tt>{username}</tt> is the SMTP username and <tt>{password}</tt> is the SMTP password.</p>
  126. <p>Save the file and then create the hash database:</p>
  127. <pre>
  128. sudo postmap /etc/postfix/sasl_passwd
  129. </pre>
  130. <h3>Restart postfix</h3>
  131. <p>That's it, so just restart postfix with <tt>sudo service postfix restart</tt></p>
  132. ]]></content:encoded>
  133. <wfw:commentRss>https://akrabat.com/updating-smtp-relay-in-postfix/feed/</wfw:commentRss>
  134. <slash:comments>0</slash:comments>
  135. </item>
  136. <item>
  137. <title>Global word count on Mac using Shortcuts</title>
  138. <link>https://akrabat.com/global-word-count-on-mac-using-shortcuts/</link>
  139. <comments>https://akrabat.com/global-word-count-on-mac-using-shortcuts/#respond</comments>
  140. <dc:creator><![CDATA[Rob]]></dc:creator>
  141. <pubDate>Tue, 03 Jun 2025 10:00:00 +0000</pubDate>
  142. <category><![CDATA[Computing]]></category>
  143. <category><![CDATA[Mac]]></category>
  144. <guid isPermaLink="false">https://akrabat.com/?p=7361</guid>
  145.  
  146. <description><![CDATA[I've had a few cases recently when I wanted to know the number of words that I had written. To do this, I've copied the text to BBEdit which displays the word count in its status bar, but this is a bit of a faff. I finally sat down and created a Shortcut for it that took 10 mins. This is the shortcut: The idea is that I want to select the text and run… <a href="https://akrabat.com/global-word-count-on-mac-using-shortcuts/">continue reading</a>.]]></description>
  147. <content:encoded><![CDATA[<p>I've had a few cases recently when I wanted to know the number of words that I had written. To do this, I've copied the text to BBEdit which displays the word count in its status bar, but this is a bit of a faff.</p>
  148. <p>I finally sat down and created a Shortcut for it that took 10 mins.</p>
  149. <p>This is the shortcut:</p>
  150. <picture><source  
  151. srcset="https://akrabat.com/wp-content/uploads/2025/05/2025-05-word-count-shortcut-light.png"
  152.        media="(prefers-color-scheme: dark)"
  153.    /><source
  154. srcset="https://akrabat.com/wp-content/uploads/2025/05/2025-05-word-count-shortcut-light.png"
  155.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  156.    /><br />
  157.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/05/2025-05-word-count-shortcut-light.png" loading="lazy" alt="Screenshort of Word Count shortcut" width="600"/>
  158. </picture>
  159. <p>The idea is that I want to select the text and run the shortcut without having to copy to the clipboard, so to do this, I enable "Use as Quick Action" on the Services menu. I also assigned a global shortcut of <tt>⌃⌥⇧⌘W</tt> for convenience, though going to the Services menu isn't hard.</p>
  160. <p>A Quick Action shortcut automatically gets a "Receive" input block, which I set to be just accept text input as it makes no sense to count words in other types of text. I also set it to look in the clipboard if there's no selection. Once we have input, we run it through the "Count" action and then pipe to a "Show Alert" so that the value can be seen. It's all quite easy.</p>
  161. <p>That's it. I can now select text and press </p>
  162. <pre>⌃⌥⇧⌘W</pre>
  163. <p> and the number of words is displayed!</p>
  164. <picture><source  
  165. srcset="https://akrabat.com/wp-content/uploads/2025/05/2025-05-wordcount-screenshot-dark.png"
  166.        media="(prefers-color-scheme: dark)"
  167.    /><source
  168. srcset="https://akrabat.com/wp-content/uploads/2025/05/2025-05-wordcount-screenshot-light.png"
  169.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  170.    /><br />
  171.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/05/2025-05-wordcount-screenshot-light.png" loading="lazy" alt="Screenshort of output of Word Count shortcut" width="300"/>
  172. </picture>
  173. ]]></content:encoded>
  174. <wfw:commentRss>https://akrabat.com/global-word-count-on-mac-using-shortcuts/feed/</wfw:commentRss>
  175. <slash:comments>0</slash:comments>
  176. </item>
  177. <item>
  178. <title>Moving a Perforce P4 P4ROOT to a different drive</title>
  179. <link>https://akrabat.com/moving-a-perforce-p4-p4root-to-a-different-drive/</link>
  180. <comments>https://akrabat.com/moving-a-perforce-p4-p4root-to-a-different-drive/#respond</comments>
  181. <dc:creator><![CDATA[Rob]]></dc:creator>
  182. <pubDate>Tue, 27 May 2025 10:00:00 +0000</pubDate>
  183. <category><![CDATA[Software]]></category>
  184. <guid isPermaLink="false">https://akrabat.com/?p=7349</guid>
  185.  
  186. <description><![CDATA[On one of my servers here, I run a local Perforce P4 server for my son. He's a game developer and as they use P4 at work, he wanted to learn it in a sandbox and to have somewhere familiar to put his own work. Installation onto Ubuntu was easy enough and I provided access outside of our local network via Tailscale and all was well. Recently, I was doing some admin-stuff on the server… <a href="https://akrabat.com/moving-a-perforce-p4-p4root-to-a-different-drive/">continue reading</a>.]]></description>
  187. <content:encoded><![CDATA[<p>On one of my servers here, I run a local <a href="https://www.perforce.com/products/helix-core">Perforce P4</a> server for my son. He's a game developer and as they use P4 at work, he wanted to learn it in a sandbox and to have somewhere familiar to put his own work.</p>
  188. <p>Installation onto Ubuntu was easy enough and I provided access outside of our local network via <a href="https://tailscale.com">Tailscale</a> and all was well.</p>
  189. <p>Recently, I was doing some admin-stuff on the server and realised that the drive that P4 was using was the local 256GB drive rather than one of the big 8TB drives in the box, so I needed to move it. The directory that the instance was using was <tt>/opt/perforce/servers/p4d-holland1</tt>. This needed moving to <tt>/media/ssd1/data/perforce/servers/p4d-holland1</tt>.</p>
  190. <p><strong>Note</strong> I run a single instance, very default, not-very-important  P4 server. Don't just copy these steps blindly if your setup is more complicated or mission critical! Make sure that you <a href="https://help.perforce.com/helix-core/server-apps/p4sag/2024.1/Content/P4SAG/moving-same-machine.html">read the docs</a>.</p>
  191. <p>These are the steps I followed:</p>
  192. <ul>
  193. <li>Stop the instance: <tt>p4dctl stop p4d-holland1</tt></li>
  194. <li>Move the data by copying it and renaming the old directory (just in case):
  195. <pre>
  196. mkdir -p /media/ssd1/data/perforce/servers/p4d-holland1
  197. cp -r /opt/perforce/servers/p4d-holland1/* /media/ssd1/data/perforce/servers/p4d-holland1/
  198. mv /opt/perforce/servers/p4d-holland1 /opt/perforce/servers/p4d-holland1-old
  199. </pre>
  200. </li>
  201. <li>Edit <tt>/etc/perforce/p4dctl.conf.d/p4d-holland1.conf</tt> and update the <tt>P4ROOT</tt> to the new directory</li>
  202. <li>Start the instance again: <tt>p4dctl start p4d-holland1</tt></li>
  203. </ul>
  204. <p>I didn't see any errors, so I got my son to check it. After he said it was all good, I removed the old <tt>/opt/perforce/servers/p4d-holland1-old</tt> directory.</p>
  205. <p>My son now has more than enough space to continue his side-projects.</p>
  206. ]]></content:encoded>
  207. <wfw:commentRss>https://akrabat.com/moving-a-perforce-p4-p4root-to-a-different-drive/feed/</wfw:commentRss>
  208. <slash:comments>0</slash:comments>
  209. </item>
  210. <item>
  211. <title>Updated rst2pdf website</title>
  212. <link>https://akrabat.com/updated-rst2pdf-website/</link>
  213. <comments>https://akrabat.com/updated-rst2pdf-website/#respond</comments>
  214. <dc:creator><![CDATA[Rob]]></dc:creator>
  215. <pubDate>Tue, 20 May 2025 10:00:00 +0000</pubDate>
  216. <category><![CDATA[rst2pdf]]></category>
  217. <guid isPermaLink="false">https://akrabat.com/?p=7355</guid>
  218.  
  219. <description><![CDATA[Earlier this year, Lorna spent some time updating rst2pdf's website to use Sphinx. The nice thing about Sphinx is that it uses restructuredText, the same as rst2pdf does, so we now stay in the same ecosystem. While, we could have continued using Jekyll, it makes much more sense for us to use the same markup language as we use for the main tool, and our manual is written in it too. With the Jekyll system,… <a href="https://akrabat.com/updated-rst2pdf-website/">continue reading</a>.]]></description>
  220. <content:encoded><![CDATA[<p>Earlier this year, <a href="https://lornajane.net/about">Lorna</a> spent some time updating <a href="https://rst2pdf.org">rst2pdf</a>'s website to use <a href="https://www.sphinx-doc.org/en/master/">Sphinx</a>. The nice thing about Sphinx is that it uses <a href="https://docutils.sourceforge.io/rst.html">restructuredText</a>, the same as rst2pdf does, so we now stay in the same ecosystem.</p>
  221. <p>While, we could have continued using Jekyll, it makes much more sense for us to use the same markup language as we use for the main tool, and our manual is written in it too. With the Jekyll system, we used docutils with a generic and simple CSS file to create the HTML version of the manual, but now as Sphinx is rST native, it builds the HTML version of the manual and it as it is now styled the same as the website, it looks much better. Of course, we use rst2pdf to generate the PDF version ;)</p>
  222. <p>Rather handily, we now have search on the site and also have a dark mode which you can see in this screenshot if your OS is in dark mode as you view this page. I also like that you can click on the "eye" icon and view the rST source for the page.</p>
  223. <picture><source  
  224. srcset="https://akrabat.com/wp-content/uploads/2025/05/2025-05-rst2pdf.org-dark.png"
  225.        media="(prefers-color-scheme: dark)"
  226.    /><source
  227. srcset="https://akrabat.com/wp-content/uploads/2025/05/2025-05-rst2pdf.org-light.png"
  228.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  229.    /><br />
  230.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/05/2025-05-rst2pdf.org-light.png" loading="lazy" alt="2025 05 rst2pdf.org light." width="600"/>
  231. </picture>
  232. <p>This required a fair amount of work. Thanks Lorna for taking the time to do it!</p>
  233. ]]></content:encoded>
  234. <wfw:commentRss>https://akrabat.com/updated-rst2pdf-website/feed/</wfw:commentRss>
  235. <slash:comments>0</slash:comments>
  236. </item>
  237. <item>
  238. <title>Using OneOf for a property in an OpenAPI spec</title>
  239. <link>https://akrabat.com/using-oneof-for-a-property-in-an-openapi-spec/</link>
  240. <comments>https://akrabat.com/using-oneof-for-a-property-in-an-openapi-spec/#respond</comments>
  241. <dc:creator><![CDATA[Rob]]></dc:creator>
  242. <pubDate>Tue, 13 May 2025 10:00:00 +0000</pubDate>
  243. <category><![CDATA[API]]></category>
  244. <category><![CDATA[OpenAPI]]></category>
  245. <guid isPermaLink="false">https://akrabat.com/?p=7330</guid>
  246.  
  247. <description><![CDATA[When writing an OpenAPI specification, I came across the need for a particular property in an error response to be either a string or an object. This situation came about when validating a POST request that takes an items property that is a list of objects As a contrived example of a pet with accessories, consider this example request in curl: curl -X "POST" "http://api.example.com/pets" \ -H 'Content-Type: application/json' \ -d $'{ "name": "Rover", "items": [… <a href="https://akrabat.com/using-oneof-for-a-property-in-an-openapi-spec/">continue reading</a>.]]></description>
  248. <content:encoded><![CDATA[<p>When writing an <a href="https://www.openapis.org">OpenAPI specification</a>, I came across the need for a particular property in an error response to be either a string or an object.</p>
  249. <p>This situation came about when validating a POST request that takes an <tt>items</tt> property that is a list of objects</p>
  250. <p>As a contrived example of a pet with accessories, consider this example request in <tt>curl</tt>:</p>
  251. <pre>
  252. curl -X "POST" "http://api.example.com/pets" \
  253.     -H 'Content-Type: application/json' \
  254.     -d $'{
  255.  "name": "Rover",
  256.  "items": [
  257.    {
  258.      "name": "collar"
  259.      "count": 1
  260.    },
  261.    {
  262.      "name": "bowl"
  263.      "count": 2
  264.    }
  265.  ],
  266. }'
  267. </pre>
  268. <p>For the <tt>items</tt> property, there are two error responses if validation fails:</p>
  269. <ol>
  270. <li>Property missing or wrong type.<br />For example:
  271. <pre>
  272. {
  273.  "items": "Items property is required"
  274. }
  275. </pre>
  276. </li>
  277. <li>Missing or invalid sub property<br />For example on the <tt>count</tt> property of the second item:
  278. <pre>
  279. {
  280.  "items": [
  281.    {
  282.    },
  283.    {
  284.        "count": "Item count must be a positive integer"
  285.    }
  286.  ]
  287. }
  288. </pre>
  289. </li>
  290. </ol>
  291. <p>To document this in OpenAPI, I wrote the following for my <tt>'400'</tt> response (simplified):</p>
  292. <pre>
  293. '400':
  294.  description: Validation failed
  295.  content:
  296.      application/json:
  297.        schema:
  298.          type: object
  299.          properties:
  300.            errors:
  301.              type: object
  302.              properties:
  303.                name:
  304.                  type: string
  305.                  example: "Pet name must not be empty"
  306.                items:
  307.                  oneOf:
  308.                    - $ref: '#/components/schemas/ValidationErrorPetItemsObject'
  309.                    - $ref: '#/components/schemas/ValidationErrorPetItemsString'
  310.  
  311. </pre>
  312. <p>Within the <tt>components</tt> -&gt; <tt>schemas</tt> section, we define both schemas:</p>
  313. <pre>
  314. ValidationErrorPetItemsString:
  315.  type: string
  316.  example: "Items property is required"
  317.  
  318.  
  319. ValidationErrorPetItemsPropertyObject:
  320.  type: array
  321.  items:
  322.    type: object
  323.    properties:
  324.      id:
  325.        type: string
  326.        example: "Item name is required"
  327.      count:
  328.        type: string
  329.        example: "Item count must be a positive integer"
  330. </pre>
  331. <p>I don't know how to specify that an empty array will be included so that the client can work out which item object has the problem, so for now it is documented in the description.</p>
  332. <p>There's probably other ways to solve this, but this is the one I came up with. If there's a better way, let me know.</p>
  333. ]]></content:encoded>
  334. <wfw:commentRss>https://akrabat.com/using-oneof-for-a-property-in-an-openapi-spec/feed/</wfw:commentRss>
  335. <slash:comments>0</slash:comments>
  336. </item>
  337. <item>
  338. <title>Additional parameters on a PHP interface method</title>
  339. <link>https://akrabat.com/additional-parameters-on-a-php-interface-method/</link>
  340. <comments>https://akrabat.com/additional-parameters-on-a-php-interface-method/#comments</comments>
  341. <dc:creator><![CDATA[Rob]]></dc:creator>
  342. <pubDate>Tue, 06 May 2025 10:00:00 +0000</pubDate>
  343. <category><![CDATA[PHP]]></category>
  344. <guid isPermaLink="false">https://akrabat.com/?p=7333</guid>
  345.  
  346. <description><![CDATA[On the Roave Discord recently, there was a discussion about not breaking BC in interfaces inspired by this post by Jérôme Tamarelle: It's clearly true that if you add a new parameter to a method on an interface, then that's a BC break as every concrete implementation of the interface needs to change their signature. However, Gina commented that you don't need to use func_get_arg() as concrete implementations can add additional optional arguments. WHAT?!!! I… <a href="https://akrabat.com/additional-parameters-on-a-php-interface-method/">continue reading</a>.]]></description>
  347. <content:encoded><![CDATA[<p>On the <a href="http://discord.gg/roave">Roave Discord</a> recently, there was a discussion about not breaking BC in interfaces inspired by <a href="https://phpc.social/@gromnan/114347134096322334">this post by Jérôme Tamarelle</a>:</p>
  348. <p><img fetchpriority="high" decoding="async" src="https://akrabat.com/wp-content/uploads/2025/04/2025gromnan-post.jpeg" alt="Gromnan post." title="gromnan-post.jpeg" border="0" width="500" height="379" /></p>
  349. <p>It's clearly true that if you add a new parameter to a method on an interface, then that's a BC break as every concrete implementation of the interface needs to change their signature.</p>
  350. <p>However, Gina commented that you don't need to use <tt>func_get_arg()</tt> as concrete implementations can <em>add additional optional arguments</em>.</p>
  351. <p><strong>WHAT?!!!</strong></p>
  352. <p>I didn't know this and it had never occurred to me to try, so I had to go <a href="https://3v4l.org/#live">3v4l.org</a> and check</p>
  353. <pre>&lt;?php
  354.  
  355. interface Foo
  356. {
  357.    public function bar(int $a): void;
  358. }
  359.  
  360. class Baz implements Foo
  361. {
  362.    public function bar(int $a, int $b=2): void
  363.    {
  364.        echo "$a: $b";
  365.    }
  366. }
  367.  
  368. (new Baz)->bar(1,3);
  369. </pre>
  370. <p>Sure enough, this code <a href="https://3v4l.org/judRV">works</a>!</p>
  371. <p>I don't have much of a need for this, but it's useful to know.</p>
  372. ]]></content:encoded>
  373. <wfw:commentRss>https://akrabat.com/additional-parameters-on-a-php-interface-method/feed/</wfw:commentRss>
  374. <slash:comments>2</slash:comments>
  375. </item>
  376. <item>
  377. <title>Some thoughts on LLM usage in my work</title>
  378. <link>https://akrabat.com/some-thoughts-on-llm-usage-in-my-work/</link>
  379. <comments>https://akrabat.com/some-thoughts-on-llm-usage-in-my-work/#respond</comments>
  380. <dc:creator><![CDATA[Rob]]></dc:creator>
  381. <pubDate>Tue, 29 Apr 2025 10:00:00 +0000</pubDate>
  382. <category><![CDATA[AI]]></category>
  383. <guid isPermaLink="false">https://akrabat.com/?p=7339</guid>
  384.  
  385. <description><![CDATA[While it would be nice to put the genie back in the bottle, that hasn't happened often in human history, so for the foreseeable future, AI in the form of LLMs are here to stay. I imagine that what we use them for will change over time as we collectively internalise their limitations. Personally, I'm now using them for my work as much as I reasonably can. This is involving many different areas, such as… <a href="https://akrabat.com/some-thoughts-on-llm-usage-in-my-work/">continue reading</a>.]]></description>
  386. <content:encoded><![CDATA[<p>While it would be nice to put the genie back in the bottle, that hasn't happened often in human history, so for the foreseeable future, AI in the form of LLMs are here to stay. I imagine that what we use them for will change over time as we collectively internalise their limitations. </p>
  387. <p>Personally, I'm now using them for my work as much as I reasonably can. This is involving many different areas, such as asking questions that I would previously have just googled. While the LLM does make things up, the first 40 answers on Google can be decidedly less than helpful nowadays too. The LLM will read many more webpage results than I can be bothered to, provide a summary and cite its sources so that I can click a few to get a feel for how much I trust it.</p>
  388. <p>When developing, I'm using the LLM for multiple tasks. It can help me find bugs when I paste in snippets of code and say "this code is getting X wrong. Tell me why and propose a fix". I generally find that this level of focussed question works quite well, though sometimes, it will go off on a tangent and never come back.</p>
  389. <p>In an IDE, I've found LLM-based smart-autocomplete from something like Copilot works quite well. Again, it's a focussed small task and so tends to work reasonably well for me. I've also found that writing a comment as I would normally do helps the LLM get it right.</p>
  390. <p>New to me is Claude Code. This command line tool reads the files in the current directory and can operate on them. I've found that it is able to do bigger chunks of work as a result and I use <tt>git diff</tt> to assess its work and tweak as necessary before I personally commit. </p>
  391. <p>One thing that is obvious in hindsight, but if I don't have a clear idea in my mind of what I need, then the tools will do worse. This can be more clearly seen with the larger blocks of work, so now, if I'm exploring how and what I want to do, I will talk with the LLM more conversationally more like a brainstorming session to get to the point where I know what I want to do. Then I can instruct it to do the work. This works no differently from trying to delegate a job to a junior and sounds so obvious when written down. However, in the excitement of seeing the magic of the LLM doing the right thing, it's easy to forget and then be surprised when it goes so so wrong.</p>
  392. <p>Not every tool is useful for everyone or useful in every situation. There are also ethical and environmental considerations that affect how people view any given technology and I do not want to suggest that LLM tools must be used; they don't. </p>
  393. <p>I realise that I'm not using these tools are much as others, however, I'm finding them useful.</p>
  394. ]]></content:encoded>
  395. <wfw:commentRss>https://akrabat.com/some-thoughts-on-llm-usage-in-my-work/feed/</wfw:commentRss>
  396. <slash:comments>0</slash:comments>
  397. </item>
  398. <item>
  399. <title>Previewing OpenAPI specs using redocly&#039;s Docker container</title>
  400. <link>https://akrabat.com/previewing-openapi-specs-using-redoclys-docker-container/</link>
  401. <comments>https://akrabat.com/previewing-openapi-specs-using-redoclys-docker-container/#respond</comments>
  402. <dc:creator><![CDATA[Rob]]></dc:creator>
  403. <pubDate>Tue, 22 Apr 2025 10:00:00 +0000</pubDate>
  404. <category><![CDATA[API]]></category>
  405. <category><![CDATA[Docker]]></category>
  406. <category><![CDATA[OpenAPI]]></category>
  407. <guid isPermaLink="false">https://akrabat.com/?p=7325</guid>
  408.  
  409. <description><![CDATA[To provide consistency between the environments of our developers, I'm a strong proponent of using containers so that every developer is using the same versions of our tools. This is really important for command line tooling that depends on a separately installed language such as NodeJS or PHP as a simple npm i -g can install wildly different versions if a dev is running an older (or newer!) version of Node. For OpenAPI specs, listing… <a href="https://akrabat.com/previewing-openapi-specs-using-redoclys-docker-container/">continue reading</a>.]]></description>
  410. <content:encoded><![CDATA[<p>To provide consistency between the environments of our developers, I'm a strong proponent of using containers so that every developer is using the same versions of our tools. This is really important for command line tooling that depends on a separately installed language such as NodeJS or PHP as a simple <tt>npm i -g</tt> can install wildly different versions if a dev is running an older (or newer!) version of Node.</p>
  411. <p>For OpenAPI specs, listing is a must and I currently use <a href="https://stoplight.io/open-source/spectral">Spectral</a> for that, but it can be handy to have rendered documentation visible when writing specs. I don't know about you, but I find it easier to proofread text when in a different context to the editor that I'm currently writing it in. </p>
  412. <p>To render OpenAPI docs in real-time as I write, I picked <a href="https://redocly.com/docs/cli">Redocly</a> which has a <tt>preview-docs</tt> option.</p>
  413. <p>The local command is: <tt>redocly/cli preview-docs openapi.yaml</tt> which will render the OpenAPI spec in <tt>openapi.yaml</tt> as an HTML page at http://localhost:8080. You can then edit the spec file and it will hot reload the browser which is super-convenient.</p>
  414. <h2>Using preview-docs in the Docker container</h2>
  415. <p>To preview the docs using the <a href="https://hub.docker.com/r/redocly/cli">redocly/cli Docker image</a>, you need this command:</p>
  416. <pre>
  417. docker run --init --rm \
  418. -p 8080:8080 -p 32201:32201 \
  419. -v $(PWD):/spec \
  420. redocly/cli preview-docs --use-community-edition openapi.yaml --host 0.0.0.0
  421. </pre>
  422. <p>There's quite a lot going on here, so let's look at the key options:</p>
  423. <ul>
  424. <li><tt>--init</tt> is required so that ctrl+c will exit cleanly.</li>
  425. <li><tt>--rm</tt> deletes the container on exit, so we tidy up after ourselves.</li>
  426. <li><tt>-p 8080:8080 -p 32201:32201</tt> maps ports 8080 and 32201 from the container to our local computer. We need 32201 so that hot reloading works.</li>
  427. <li><tt>-v $(PWD):/spec</tt> maps our current directory to the container's <tt>/spec</tt> directory which is its working directory.</li>
  428. <li><tt>redocly/cli preview-docs openapi.yaml</tt> runs Redocly's <tt>preview-docs</tt> command against our <tt>openapi.yaml</tt> OpenAPI spec file.</li>
  429. <li><tt>--host 0.0.0.0</tt> to make the app accessible from all network interfaces in the container so that we can view the preview from our local computer.</li>
  430. </ul>
  431. <p>We can now open <a href="http://localhost:8080/">http://localhost:8080/</a> and view our nicely rendered OpenAPI spec!</p>
  432. <p><img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/04/2025redocli-cli-rps-openapi.png" alt="Redocli cli rps openapi." title="redocli-cli-rps-openapi.png" border="0" width="700" height="462" /></p>
  433. ]]></content:encoded>
  434. <wfw:commentRss>https://akrabat.com/previewing-openapi-specs-using-redoclys-docker-container/feed/</wfw:commentRss>
  435. <slash:comments>0</slash:comments>
  436. </item>
  437. </channel>
  438. </rss>
  439.  

If you would like to create a banner that links to this page (i.e. this validation result), do the following:

  1. Download the "valid RSS" banner.

  2. Upload the image to your own server. (This step is important. Please do not link directly to the image on this server.)

  3. Add this HTML to your page (change the image src attribute if necessary):

If you would like to create a text link instead, here is the URL you can use:

http://www.feedvalidator.org/check.cgi?url=http%3A//akrabat.com/feed/

Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda