<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: jeikabu</title>
    <description>The latest articles on DEV Community by jeikabu (@jeikabu).</description>
    <link>https://dev.to/jeikabu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F92569%2Fa8ccdbad-5048-4415-a740-4f7b5d893435.png</url>
      <title>DEV Community: jeikabu</title>
      <link>https://dev.to/jeikabu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeikabu"/>
    <language>en</language>
    <item>
      <title>Cross-posting to Tumblr with AWS Lambda</title>
      <dc:creator>jeikabu</dc:creator>
      <pubDate>Mon, 05 Jul 2021 09:21:23 +0000</pubDate>
      <link>https://dev.to/jeikabu/cross-posting-to-tumblr-166</link>
      <guid>https://dev.to/jeikabu/cross-posting-to-tumblr-166</guid>
      <description>&lt;p&gt;I really enjoyed &lt;a href="https://rendered-obsolete.github.io/2021/06/19/bullhorn.html"&gt;making a CLI tool to automate cross-posting blog posts to different places&lt;/a&gt;.  So, I started looking for another platform to add and Tumblr seemed like a good candidate.  Again, rough code is &lt;a href="https://github.com/jeikabu/cargo_bullhorn/"&gt;on Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tumblr API
&lt;/h2&gt;

&lt;p&gt;The Tumblr API is &lt;a href="https://www.tumblr.com/docs/en/api/v2"&gt;well documented&lt;/a&gt;.  But before you get started you need to &lt;a href="https://www.tumblr.com/oauth/apps"&gt;register an "application"&lt;/a&gt;.  For "Default callback URL" put a valid URL, you can always edit this later (via &lt;strong&gt;Account &amp;gt; Settings &amp;gt; Apps &amp;gt; ✎&lt;/strong&gt;).  This will get you an OAuth "consumer" key and secret (sometimes referred to as "client" credentials).&lt;/p&gt;

&lt;p&gt;Some parts of the Tumblr API can be called with just the consumer key.  For example, to &lt;a href="https://www.tumblr.com/docs/en/api/v2#posts--retrieve-published-posts"&gt;retrieve published posts&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-G&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://api.tumblr.com/v2/blog/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;blog-identifier&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="n"&gt;/posts&lt;/span&gt;&lt;span class="nf"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;oauth_consumer_key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# PowerShell Example:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://api.tumblr.com/v2/blog/rendered-obsolete/posts&lt;/span&gt;&lt;span class="nf"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;xxx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But most of the interesting ones, like &lt;a href="https://www.tumblr.com/docs/en/api/v2#posts---createreblog-a-post-neue-post-format"&gt;creating a new post&lt;/a&gt;, require OAuth.&lt;/p&gt;

&lt;h2&gt;
  
  
  OAuth
&lt;/h2&gt;

&lt;p&gt;Tumblr's &lt;a href="https://www.tumblr.com/docs/en/api/v2#authentication"&gt;docs&lt;/a&gt; are a bit light on OAuth 1.0a details, but their implementation closely resembles &lt;a href="https://developer.twitter.com/en/docs/authentication/oauth-1-0a/obtaining-user-access-tokens"&gt;Twitter's&lt;/a&gt; and there's also an &lt;a href="https://datatracker.ietf.org/doc/html/rfc5849"&gt;RFC&lt;/a&gt;.  Twitter calls it "3-legged" OAuth authentication, the RFC calls it "redirection-based" authentication.  Whatever you call it, the gist is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;POST https://www.tumblr.com/oauth/request_token&lt;/code&gt; with client credentials (i.e. consumer key/secret) -&amp;gt; receive "temporary credentials"&lt;/li&gt;
&lt;li&gt;Direct user to &lt;a href="https://www.tumblr.com/oauth/authorize"&gt;https://www.tumblr.com/oauth/authorize&lt;/a&gt; website for "resource owner" approval&lt;/li&gt;
&lt;li&gt;Once they approve access Tumblr will redirect them to our callback URL -&amp;gt; callback receives "verifier"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET https://www.tumblr.com/oauth/access_token&lt;/code&gt; with temporary credentials -&amp;gt; receive user credentials (i.e. token and secret)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First, I'll go over the CLI client changes, then we'll deal with the callback since it's an excuse to write an &lt;a href="https://rendered-obsolete.github.io/2019/03/19/rust_lambda.html"&gt;AWS Lambda in Rust again&lt;/a&gt;.  The final resulting user token/secret can be re-used, so this only needs to be done once per account, thankfully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Client
&lt;/h2&gt;

&lt;p&gt;I initially implemented the gory details of OAuth 1.0a using &lt;a href="https://crates.io/crates/percent-encoding"&gt;percent-encoding&lt;/a&gt; for string encoding along with &lt;a href="https://crates.io/crates/hmac"&gt;hmac&lt;/a&gt; and &lt;a href="https://crates.io/crates/sha-1"&gt;sha-1&lt;/a&gt; for the required signing.  But in the end settled on &lt;a href="https://crates.io/crates/oauth1-request"&gt;oauth1-request&lt;/a&gt; since it made the client much simpler.&lt;/p&gt;

&lt;p&gt;First, use client/consumer credentials to obtain temporary credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}/oauth/request_token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WWW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client_credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nn"&gt;oauth1_request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.consumer_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.consumer_secret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Sign request using only client/consumer credentials&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;auth_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nn"&gt;oauth1_request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;oauth1_request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HmacSha1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;// Can optionally specify callback URL here, otherwise Tumblr will use the application default&lt;/span&gt;
        &lt;span class="c1"&gt;//.callback("https://callback_url")&lt;/span&gt;
        &lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
    &lt;span class="py"&gt;.client&lt;/span&gt;
    &lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;header&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;AUTHORIZATION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth_header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resp_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="nf"&gt;.text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Parse `key0=value0&amp;amp;key1=value1&amp;amp;...` in response body for temporary credentials&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;resp_body_pairs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp_body&lt;/span&gt;
    &lt;span class="nf"&gt;.split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'&amp;amp;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="nf"&gt;.split_once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'='&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;.flatten&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;temp_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;resp_body_pairs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"oauth_token"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nf"&gt;.to_owned&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;temp_token_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;resp_body_pairs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"oauth_token_secret"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nf"&gt;.to_owned&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have the "temporary credentials".  We still need the &lt;code&gt;oauth_verifier&lt;/code&gt; that will be sent to our callback URL.  To receive this from our callback we'll use &lt;a href="https://aws.amazon.com/sqs/"&gt;AWS Simple Queue Service (SQS)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To interact with AWS in Rust there's the &lt;a href="https://aws.amazon.com/blogs/developer/a-new-aws-sdk-for-rust-alpha-launch/"&gt;recently announced&lt;/a&gt; official &lt;a href="https://github.com/awslabs/aws-sdk-rust"&gt;AWS SDK for Rust&lt;/a&gt; which requires AWS access credentials.  To obtain AWS access credentials, in the AWS console click &lt;strong&gt;$YOUR_NAME (upper-right) &amp;gt; My Security Credentials &amp;gt; Access keys &amp;gt; Create New Access Key&lt;/strong&gt; (keep these safe).  And then set the following &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html"&gt;environment variables&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"xxx"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"yyy"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, continuing with the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create temporary SQS queue `bullhorn-{temporary token}` to receive oauth_verifier from lambda&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;queue_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bullhorn-{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;aws_sqs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_env&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="nf"&gt;.create_queue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.queue_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;queue_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="py"&gt;.queue_url&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Show "resource owner" approval website in system default web browser&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}/oauth/authorize?oauth_token={}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WWW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;exit_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;open&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;that&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://crates.io/crates/open"&gt;"open" crate&lt;/a&gt; makes it easy to show the Tumblr resource owner approval page in a browser.  While the user is approving our Tumblr application, we wait for our callback to send the oauth verifier via SQS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Receive oauth_verifier from lambda via SQS&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
        &lt;span class="nf"&gt;.receive_message&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.queue_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;queue_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="py"&gt;.messages&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="c1"&gt;// Delete the temporary SQS queue&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="nf"&gt;.delete_queue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.queue_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;verifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="py"&gt;.body&lt;/span&gt;&lt;span class="nf"&gt;.as_ref&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Exchange client/consumer and temporary credentials for user credentials&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}/oauth/access_token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WWW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;temp_credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;oauth1_request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;temp_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;temp_token_secret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Must authenticate with both client and temporary credentials&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;oauth1_request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp_credentials&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;auth_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nn"&gt;oauth1_request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;oauth1_request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HmacSha1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;// Must include `oauth_verifier`&lt;/span&gt;
        &lt;span class="nf"&gt;.verifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verifier&lt;/span&gt;&lt;span class="nf"&gt;.as_ref&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client&lt;/span&gt;
    &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;header&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;AUTHORIZATION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth_header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user token and secret we receive is valid until revoked (via user's &lt;strong&gt;Settings &amp;gt; Apps&lt;/strong&gt;), so we can store them somewhere safe to avoid having to do this again.  We can now use Tumblr on the user's behalf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Callback
&lt;/h2&gt;

&lt;p&gt;Above covered the important parts of the client.  The part of OAuth authentication remaining is our callback that Tumblr's resource owner approval webpage redirects to.  We could leave an HTTP server running all the time, but that's a bit silly for something we only need occasionally.  This looks like a job for "serverless" and AWS Lambda.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Lambda
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rendered-obsolete.github.io/2019/03/19/rust_lambda.html"&gt;As before&lt;/a&gt;, we'll use the &lt;a href="https://github.com/awslabs/aws-lambda-rust-runtime"&gt;AWS Lambda Rust runtime&lt;/a&gt; to implement our lambda function.  &lt;a href="https://musl.libc.org/"&gt;MUSL&lt;/a&gt; is used to create a &lt;a href="https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/musl-support-for-fully-static-binaries.html"&gt;fully static Rust binary&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;In &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# Rename binary for AWS Lambda custom runtime:&lt;/span&gt;
&lt;span class="c"&gt;# https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html&lt;/span&gt;
&lt;span class="nn"&gt;[[bin]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"bootstrap"&lt;/span&gt;
&lt;span class="py"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src/main.rs"&lt;/span&gt;

&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;anyhow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;
&lt;span class="nn"&gt;aws_sqs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;git&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/awslabs/aws-sdk-rust"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;tag&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"v0.0.10-alpha"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;package&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"aws-sdk-sqs"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;futures&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.3"&lt;/span&gt;
&lt;span class="py"&gt;lambda_runtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.3"&lt;/span&gt;
&lt;span class="nn"&gt;serde&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;["derive"]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde_json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;
&lt;span class="nn"&gt;tokio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.5.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;["full"]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;tracing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1"&lt;/span&gt;
&lt;span class="py"&gt;tracing-subscriber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;.cargo/config&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[build]&lt;/span&gt;
&lt;span class="c"&gt;# Override default build target&lt;/span&gt;
&lt;span class="py"&gt;target&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"x86_64-unknown-linux-musl"&lt;/span&gt;

&lt;span class="c"&gt;# Needed on Mac otherwise build fails with `ld: unknown option: --eh-frame-hdr`&lt;/span&gt;
&lt;span class="nn"&gt;[target.x86_64-unknown-linux-musl]&lt;/span&gt;
&lt;span class="py"&gt;linker&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"x86_64-linux-musl-gcc"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For starters, put something like the &lt;a href="https://github.com/awslabs/aws-lambda-rust-runtime/blob/master/lambda-runtime/examples/basic.rs"&gt;basic example&lt;/a&gt; in &lt;code&gt;src/main.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;oauth_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;oauth_verifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Serialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nn"&gt;lambda_runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;lambda_runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;handler_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nn"&gt;lambda_runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;lambda_runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"ok"&lt;/span&gt;&lt;span class="nf"&gt;.to_owned&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to compile this lambda, we also need to build OpenSSL with musl (based on &lt;a href="https://qiita.com/liubin/items/6c94f0b61f746c08b74c"&gt;this post&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;### Install pre-requisites&lt;/span&gt;
&lt;span class="c"&gt;# For Mac/OSX: install command line tools and cross-compiler&lt;/span&gt;
xcode-select &lt;span class="nt"&gt;--install&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;FiloSottile/musl-cross/musl-cross

rustup target add x86_64-unknown-linux-musl

&lt;span class="c"&gt;### Build OpenSSL with musl&lt;/span&gt;
wget https://github.com/openssl/openssl/archive/OpenSSL_1_1_1f.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf OpenSSL_1_1_1f.tar.gz
&lt;span class="nb"&gt;cd &lt;/span&gt;openssl-OpenSSL_1_1_1f
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CROSS_COMPILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;x86_64-linux-musl-
&lt;span class="c"&gt;# `-DOPENSSL_NO_SECURE_MEMORY` is to avoid `define OPENSSL_SECURE_MEMORY` which needs `#include &amp;lt;linux/mman.h&amp;gt;` (which OSX doesn't have).&lt;/span&gt;
./Configure &lt;span class="nv"&gt;CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-DOPENSSL_NO_SECURE_MEMORY -fpie -pie"&lt;/span&gt; no-shared no-async &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;output_abs_path/musl &lt;span class="nt"&gt;--openssldir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;output_abs_path/musl/ssl linux-x86_64
make depend
&lt;span class="c"&gt;# Use `sysctl -n hw.physicalcpu` or `hw.logicalcpu` with `-j` if so inclined&lt;/span&gt;
make
&lt;span class="c"&gt;# Install required stuff to `output_abs_path/musl/` (exclude man-pages, etc.)&lt;/span&gt;
make install_sw
&lt;span class="c"&gt;# Set value from `--prefix` above&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENSSL_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;output_abs_path/musl

&lt;span class="c"&gt;### Build Rust lambda function&lt;/span&gt;
cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could now create our lambda with the AWS console, but the aws-lambda-rust-runtime docs show how to do it via the &lt;a href="https://aws.amazon.com/cli/"&gt;AWS CLI&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set AWS credentials: https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html&lt;/span&gt;
aws configure

&lt;span class="c"&gt;# Create package with layout required for custom lambda runtime:&lt;/span&gt;
&lt;span class="c"&gt;# https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html&lt;/span&gt;
&lt;span class="c"&gt;# -j/--junk-paths avoids directory structure&lt;/span&gt;
zip &lt;span class="nt"&gt;--junk-paths&lt;/span&gt; lambda.zip ../target/x86_64-unknown-linux-musl/release/bootstrap

&lt;span class="c"&gt;# Get ARN for desired existing role&lt;/span&gt;
aws iam list-roles

&lt;span class="c"&gt;# Create new lambda function&lt;/span&gt;
aws lambda create-function &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--function-name&lt;/span&gt; bullhorn &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--handler&lt;/span&gt; doesnt.matter &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--zip-file&lt;/span&gt; fileb://./lambda.zip &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--runtime&lt;/span&gt; provided &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role&lt;/span&gt; arn:aws:iam::01234:role/service-role/bullhorn &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--tracing-config&lt;/span&gt; &lt;span class="nv"&gt;Mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Active &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="c"&gt;# Needed with CLI V2 &lt;/span&gt;
    &lt;span class="nt"&gt;--cli-binary-format&lt;/span&gt; raw-in-base64-out
    &lt;span class="nt"&gt;--cli-connect-timeout&lt;/span&gt; 10000

&lt;span class="c"&gt;# Run the lambda&lt;/span&gt;
aws lambda invoke &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--function-name&lt;/span&gt; bullhorn &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--payload&lt;/span&gt; &lt;span class="s1"&gt;'{"oauth_token": "xxx", "oauth_verifier": "yyy"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="c"&gt;# Needed with CLI V2&lt;/span&gt;
    &lt;span class="nt"&gt;--cli-binary-format&lt;/span&gt; raw-in-base64-out &lt;span class="se"&gt;\&lt;/span&gt;
    response.json

&lt;span class="c"&gt;# To update lambda binary&lt;/span&gt;
aws lambda update-function-code &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--function-name&lt;/span&gt; bullhorn &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--zip-file&lt;/span&gt; fileb://./lambda.zip &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--cli-connect-timeout&lt;/span&gt; 10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--cli-connect-timeout&lt;/code&gt; deals with commands failing with &lt;code&gt;Error: Connection was closed before we received a valid response&lt;/code&gt; (see &lt;a href="https://github.com/aws/aws-cli/issues/3842"&gt;issue&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;--region &amp;lt;region&amp;gt;&lt;/code&gt; for a region other than the default set with &lt;code&gt;aws configure&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  API Gateway
&lt;/h3&gt;

&lt;p&gt;To get Tumblr to call our lambda via the callback URL we use &lt;a href="https://aws.amazon.com/api-gateway/"&gt;AWS API Gateway&lt;/a&gt; to trigger our lambda via an HTTP endpoint.&lt;/p&gt;

&lt;p&gt;Open the lambda in AWS console then &lt;strong&gt;+ Add trigger &amp;gt; API Gateway &amp;gt; Create an API&lt;/strong&gt;, for "API type" &lt;strong&gt;REST API&lt;/strong&gt; ("HTTP" likely also works) and for "Security" &lt;strong&gt;Open&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;From the list of triggers expand &lt;strong&gt;Details&lt;/strong&gt; and note "API endpoint".  This can be set as "Default callback URL" of the Tumblr application as well as the &lt;code&gt;oauth_callback&lt;/code&gt; parameter in the client.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;http://tumblr/authorize&lt;/code&gt; redirects to the callback you get &lt;code&gt;https://your_callback_url?oauth_token=xxx&amp;amp;oauth_verifier=yyy&lt;/code&gt;.  Apparently lambdas don't accept query strings (the &lt;code&gt;?xxx=yyy&lt;/code&gt; bits), so we need to &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/pass-api-gateway-rest-api-parameters/"&gt;move the query into the body&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In AWS console open API Gateway and &lt;strong&gt;/bullhorn ANY &amp;gt; Integration Request&lt;/strong&gt;, deselect &lt;strong&gt;Use Lambda Proxy integration&lt;/strong&gt; and expand &lt;strong&gt;Mapping Templates&lt;/strong&gt;.  Select &lt;strong&gt;When there are no templates defined&lt;/strong&gt;, &lt;strong&gt;Add mapping template&lt;/strong&gt; enter &lt;code&gt;application/json&lt;/code&gt; and click ☑.  Now there's two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;From "Generate template" pick &lt;strong&gt;Method request passthrough&lt;/strong&gt;.  This will result in the input to your lambda containing the entire request:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;transformations:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"body-json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"querystring"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"oauth_verifier"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yyy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"oauth_token"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxx"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"stage-variables"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a simple mapping to move the query parameters into the request body:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"oauth_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$input.params('oauth_token')"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"oauth_verifier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$input.params('oauth_verifier')"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;The latter is easier.  See the &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html"&gt;docs for template syntax&lt;/a&gt; details.&lt;/p&gt;

&lt;p&gt;Don't forget to &lt;strong&gt;Save&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;In API Gateway you can verify everything works, for "Method" &lt;strong&gt;GET&lt;/strong&gt; and "Query Strings" &lt;code&gt;oauth_token=xxx&amp;amp;oauth_verifier=yyy&lt;/code&gt; then &lt;strong&gt;Test&lt;/strong&gt;.  In the log output you'll see: the original query, the mapped endpoint request body, and the output from the lambda.&lt;/p&gt;

&lt;p&gt;Once this is working you must select &lt;strong&gt;Actions &amp;gt; Deploy API&lt;/strong&gt;, set "Deployment stage" to &lt;strong&gt;default&lt;/strong&gt; and then &lt;strong&gt;Deploy&lt;/strong&gt;.  Failure to do this will result in your lambda receiving the query string from the initial API Gateway configuration.&lt;/p&gt;

&lt;p&gt;CloudWatch Logs isn't enabled by default for API Gateway.  &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/api-gateway-cloudwatch-logs/"&gt;To enable logging&lt;/a&gt; you'll need to create a role and enable them in &lt;strong&gt;API Gateway &amp;gt; {select API} &amp;gt; Stages &amp;gt; default &amp;gt; Logs/Tracing&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Lambda
&lt;/h3&gt;

&lt;p&gt;In order to work with SQS queues our lambda needs some additional permissions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;From AWS console open IAM&lt;/li&gt;
&lt;li&gt;Select the role used by the lambda function and expand the policy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit policy &amp;gt; Add additional permissions&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;"Service" = &lt;strong&gt;SQS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;"Actions" = &lt;strong&gt;Read / GetQueueUrl&lt;/strong&gt; and &lt;strong&gt;Write / SendMessage&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;"Resources" = &lt;code&gt;arn:aws:sqs:us-east-1:01234:bullhorn-*&lt;/code&gt; (the naming convention used for our SQS queues)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A working version of the lambda is just the send portion of SQS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;lambda_runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;aws_sqs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_env&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Get SQS queue based on name set by client&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
        &lt;span class="nf"&gt;.get_queue_url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.queue_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bullhorn-{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="py"&gt;.oauth_token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Send oauth_verifier to client so it can retrieve user token/secret&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
        &lt;span class="nf"&gt;.send_message&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.message_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="py"&gt;.oauth_verifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.set_queue_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="py"&gt;.queue_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;message_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="py"&gt;.message_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sequence_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="py"&gt;.sequence_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our client will receive the verifier and complete OAuth authentication as described earlier.&lt;/p&gt;

&lt;p&gt;Since managed services like SQS generally exhibit &lt;a href="https://en.wikipedia.org/wiki/Eventual_consistency"&gt;eventual consistency&lt;/a&gt;, I wonder if &lt;code&gt;get_queue_url()&lt;/code&gt; can temporarily fail and needs to be retried.  It's worked fine the way it is (so far), so maybe that's accounted for when the client creates the queue?&lt;/p&gt;

&lt;h2&gt;
  
  
  Posting to Tumblr
&lt;/h2&gt;

&lt;p&gt;Thus far everything was related to OAuth and obtaining user credentials.  Once we have them we can interact with Tumblr.&lt;/p&gt;

&lt;p&gt;The Tumblr API has both a "legacy" and "neue" forms.  Legacy supports markdown, but not canonical URLs.  Neue has canonical URLs but not markdown; posts must be composed from &lt;a href="https://www.tumblr.com/docs/npf"&gt;Neue Post Format (NPF) blocks&lt;/a&gt;.  It might work to build the markdown as HTML and embed in a block, but I'll look into that later.  Until there's a better solution, we'll create a "link" post that's just a hyper-link to the original article.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Check if the article already exists&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
    &lt;span class="py"&gt;.client&lt;/span&gt;
    &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}/blog/{}/posts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.blog_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;// Only requires api_key authentication, get response in "Neue Post Format"&lt;/span&gt;
    &lt;span class="nf"&gt;.query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s"&gt;"api_key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.consumer_key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"npf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nf"&gt;.to_owned&lt;/span&gt;&lt;span class="p"&gt;())])&lt;/span&gt;
    &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
    &lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="py"&gt;.response.posts&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.find_map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.content&lt;/span&gt;
        &lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;// Find block that is a "link" and contains canonical URL, and return its ID&lt;/span&gt;
        &lt;span class="nf"&gt;.find&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;ContentBlock&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Link&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;display_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;display_url&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;canonical_url&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.id_string&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// There's a series of structs for serde to parse the JSON response.&lt;/span&gt;
&lt;span class="c1"&gt;// The following are the important ones:&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id_string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ContentBlock&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// &amp;lt;SNIP&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Serde will serialize these from JSON like, e.g.:&lt;/span&gt;
&lt;span class="c1"&gt;// {type="link", display_url="xxx", ...}&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize)]&lt;/span&gt;
&lt;span class="nd"&gt;#[serde(tag&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;rename_all&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"snake_case"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ContentBlock&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Link&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;display_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create/update an article:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Must authenticate using both client/consumer and user tokens/secrets&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;oauth1_request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_parts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.consumer_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.consumer_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.token_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="py"&gt;.front_matter.tags&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;RequestTags&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LinkRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// If we found existing article this will be Some and we'll update.  Otherwise this is None and we create.&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="py"&gt;.front_matter.title&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;canonical_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="nn"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// To create an article: POST {blog_id}/post&lt;/span&gt;
&lt;span class="c1"&gt;// To update: POST {blog_id}/post/edit&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"{}/blog/{}/post{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.blog_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="nf"&gt;.is_some&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"/edit"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Sign the request and create `Authorization` HTTP header&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;auth_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nn"&gt;oauth1_request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;oauth1_request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HmacSha1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// For POST, request body contains `application/x-www-form-urlencoded`&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;oauth1_request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to_form_urlencoded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
    &lt;span class="py"&gt;.client&lt;/span&gt;
    &lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;header&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;AUTHORIZATION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth_header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;header&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CONTENT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"application/x-www-form-urlencoded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// HTTP request to create/update "link" type post&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(oauth1_request::Request)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;LinkRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/// Must be `Some` when updating an existing article, `None` when creating a new one&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[oauth1(rename&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RequestTags&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// &amp;lt;SNIP&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Helper to serialize Vec&amp;lt;_&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;RequestTags&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Need to impl Display so oauth1_request knows how to serialize a Vec&amp;lt;_&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Display&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;RequestTags&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Formatter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'_&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tag_param&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.tags&lt;/span&gt;&lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nd"&gt;write!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag_param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting post looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JzVwIU8y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rendered-obsolete.github.io/assets/tumblr_link_post.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JzVwIU8y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rendered-obsolete.github.io/assets/tumblr_link_post.png" alt="" width="852" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>serverless</category>
      <category>productivity</category>
      <category>cli</category>
    </item>
    <item>
      <title>Yet Another Snazzy Rust CLI</title>
      <dc:creator>jeikabu</dc:creator>
      <pubDate>Sat, 19 Jun 2021 16:05:32 +0000</pubDate>
      <link>https://dev.to/jeikabu/yet-another-snazzy-rust-cli-k4i</link>
      <guid>https://dev.to/jeikabu/yet-another-snazzy-rust-cli-k4i</guid>
      <description>&lt;p&gt;I recently looked at using &lt;a href="https://github.com/pjeziorowski/rollout"&gt;pjeziorowski's rollout&lt;/a&gt; tool to &lt;a href="https://cyberwritings.com/publishing-articles"&gt;cross-publish to hashnode and devto&lt;/a&gt;.  Started making a few minor changes.  Then considered a few contentious changes.  And in the end decided I should just make my own in Rust.  Because... Rust in all the things.&lt;/p&gt;

&lt;p&gt;I settled on the following requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Post multiple places&lt;/p&gt;

&lt;p&gt;I've been using GitHub Pages and cross-posting to devto via RSS feed.  But this tends to munge articles a bit; strips syntax highlighting, adds a linefeed to end of code blocks, mangles links to other articles, etc.  All this requires manual fixing before publishing, so I've never been happy with it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Keep articles up to date&lt;/p&gt;

&lt;p&gt;&lt;code&gt;POST&lt;/code&gt;ing is one thing, but I regularly make updates/corrections that I'd like to propogate.  Pretty much all my devto articles are out of date, for this I need &lt;code&gt;PUT&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Manage &lt;code&gt;canonical_url&lt;/code&gt; (and front matter, in general)&lt;/p&gt;

&lt;p&gt;Based on where/when the original is posted set the canonical URL for the cross-posts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Async&lt;/p&gt;

&lt;p&gt;I'm impatient.  But I also anticipate needing to do a few requests to publish and update articles.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Markdown&lt;/p&gt;

&lt;p&gt;Articles are written in markdown and may contain &lt;a href="https://jekyllrb.com/docs/front-matter/"&gt;Jekyll front-matter&lt;/a&gt; as used by Github Pages.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To avoid confusion with the original (or &lt;a href="https://github.com/fetlife/rollout"&gt;some Ruby feature flag thingie&lt;/a&gt;), I dub thee... &lt;strong&gt;BULLHORN&lt;/strong&gt; (stylized in all-caps, for obvious reasons).&lt;/p&gt;

&lt;h2&gt;
  
  
  Devto and REST
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.forem.com/api/"&gt;devto API&lt;/a&gt; is pretty straight-forward.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.logrocket.com/the-state-of-rust-http-clients/"&gt;"How to choose the right Rust HTTP client"&lt;/a&gt; gives a good overview of Rust HTTP crates.  In this case, I settled on &lt;a href="https://github.com/seanmonstar/reqwest"&gt;reqwest&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Helper state&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;DevtoCrossPublish&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'s&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'s&lt;/span&gt; &lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Serialized to JSON and used as body of HTTP request&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(serde::Serialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;body_markdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;published&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;canonical_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[serde(skip_serializing_if&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Vec::is_empty"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// API is expecting:&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//  article: {&lt;/span&gt;
&lt;span class="c1"&gt;//      title: ...&lt;/span&gt;
&lt;span class="c1"&gt;//      body_markdown: ...&lt;/span&gt;
&lt;span class="c1"&gt;//  }&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(serde::Serialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Posting a new article is pretty simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client&lt;/span&gt;&lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://dev.to/api/articles"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// Authenticate&lt;/span&gt;
    &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.api_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Updating an article takes a bit more.  Need to find the devto article corresponding to the original.  I decided the canonical URL was the best way to do this because once published it shouldn't change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get page of most recent articles&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;me_articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://dev.to/api/articles/me"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.api_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Find article with matching canonical URL, if it exists&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;compare_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="py"&gt;.front_matter.canonical_url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;existing_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;me_articles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;me_articles&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;me_articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;me_articles&lt;/span&gt;&lt;span class="py"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ArticleResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;me_articles&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.find&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;compare_val&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.and_then&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;None&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Note `put` and URL for existing article&lt;/span&gt;
&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client&lt;/span&gt;&lt;span class="nf"&gt;.put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://dev.to/api/articles/{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;existing_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.api_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Medium and REST
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/Medium/medium-api-docs"&gt;medium API&lt;/a&gt; is pretty limited.  You can post new articles, but you can't update (i.e. PUT) existing articles.  In fact, you can't even GET your articles.  Regardless, I'd still like to check for an article using the canonical URL to avoid POSTing a duplicate.  Unfortunately, it seems the only viable option to obtain your articles is to get your RSS feed, get individual posts, and check the HTML &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element for the canonical link.&lt;/p&gt;

&lt;p&gt;Based on &lt;a href="https://simplabs.com/blog/2020/12/31/xml-and-rust/"&gt;this article&lt;/a&gt;, I decided to try quick-xml.  In &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="nn"&gt;quick-xml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.22"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;optional&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;rss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;optional&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get RSS feed&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://medium.com/feed/@{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="py"&gt;.username&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
    &lt;span class="nf"&gt;.bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Parse RSS feed&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;rss&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Iterate over links to posts&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="py"&gt;.items&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="py"&gt;.link&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Get article&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
            &lt;span class="nf"&gt;.text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Extract canonical `&amp;lt;link&amp;gt;` from `&amp;lt;head&amp;gt;`&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you get an article, the response body contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- SNIP --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- SNIP --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;data-rh=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"canonical"&lt;/span&gt;
            &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://rendered-obsolete.github.io/2021/05/03/dotnet_calli.html"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;data-rh=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt; &lt;span class="na"&gt;data-rh=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preload"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"script"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt; &lt;span class="na"&gt;data-fela-rehydration=&lt;/span&gt;&lt;span class="s"&gt;"456"&lt;/span&gt; &lt;span class="na"&gt;data-fela-type=&lt;/span&gt;&lt;span class="s"&gt;"STATIC"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- SNIP --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- SNIP --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see the canonical URL with &lt;code&gt;rel="canonical"&lt;/code&gt;.  In theory we should be able to enable the &lt;code&gt;"serialize"&lt;/code&gt; feature of quick-xml and follow &lt;a href="https://docs.rs/quick-xml/0.22.0/quick_xml/de/index.html"&gt;the example&lt;/a&gt; to decode with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;PartialEq)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;PartialEq)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Head&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Workaround&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;PartialEq)]&lt;/span&gt;
&lt;span class="nd"&gt;#[serde(rename_all&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"lowercase"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;Workaround&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;PartialEq)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Link&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;story&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;quick_xml&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;de&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;story&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;enum Workaround&lt;/code&gt; is needed because serde doesn't like the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; mixed in between &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt;s (see &lt;a href="https://github.com/tafia/quick-xml/issues/177"&gt;this issue&lt;/a&gt;).  However, it fails with &lt;code&gt;EndEventMismatch { expected: "link", found: "head" }&lt;/code&gt;.  If you look at the last &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; in the response it's never closed with &lt;code&gt;/&amp;gt;&lt;/code&gt;- HTML isn't necessarily valid XML.  So we'll instead need to follow the &lt;a href="https://docs.rs/quick-xml/0.22.0/quick_xml/index.html#reader"&gt;reader example&lt;/a&gt; and manually traverse the HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;quick_xml&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="nf"&gt;.trim_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;is_head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;quick_xml&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;events&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BytesEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BytesStart&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="nf"&gt;.read_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="nf"&gt;.name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;b"head"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;is_head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;End&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_head&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="nf"&gt;.name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;b"head"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;is_head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_head&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="nf"&gt;.name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;b"link"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Check `e.attributes()` key/value for `rel="canonical"`, etc.&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Eof&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="nf"&gt;.clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Posting an article is similar to devto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client&lt;/span&gt;&lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}/users/{}/posts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="py"&gt;.id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bearer {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.api_token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hashnode and GraphQL
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://engineering.hashnode.com/introducing-hashnode-graphql-api-public-beta-cjydzvp59001q2gs1b5zxaeaf"&gt;Hashnode provides a GraphQL API&lt;/a&gt;.  A GraphQL tutorial is out of scope, but I'll cover the basics.  &lt;a href="https://graphql.org/learn/"&gt;https://graphql.org/learn/&lt;/a&gt; is a good place for further information.&lt;/p&gt;

&lt;p&gt;Hashnode has a web-based &lt;a href="https://api.hashnode.com/"&gt;API sandbox&lt;/a&gt;, but the &lt;a href="https://github.com/graphql/vscode-graphql"&gt;VSCode extension&lt;/a&gt; is even comfier because you can test your queries and then use them directly in Rust:&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/vscode_graphql_execute.png" class="article-body-image-wrapper"&gt;&lt;img src="/assets/vscode_graphql_execute.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Populate &lt;code&gt;.graphqlrc.yaml&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://api.hashnode.com/&lt;/span&gt;
&lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;endpoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://api.hashnode.com/&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${HASHNODE_API_TOKEN}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The will give requests made by the VSCode extension an &lt;code&gt;Authorization&lt;/code&gt; header based on &lt;code&gt;HASHNODE_API_TOKEN&lt;/code&gt; environment variable.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;src/hashnode.graphql&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="c"&gt;# `query` keyword and the name we'll use in client code&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Tags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c"&gt;# The query we'll run&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;tagCategories&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c"&gt;# Fields to include in response&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click &lt;strong&gt;Execute Query&lt;/strong&gt; in VSCode and it should output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tagCategories"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"56744721958ef13879b94cad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JavaScript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"javascript"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the fields in the response match what we requested.&lt;/p&gt;

&lt;p&gt;The main Rust &lt;a href="https://blog.logrocket.com/code-first-vs-schema-first-development-graphql/"&gt;code-first&lt;/a&gt; GraphQL project seems to be &lt;a href="https://github.com/graphql-rust/juniper"&gt;Juniper&lt;/a&gt;.  Seeing as how I can't abide code littered with string liter-als (pun intended), the schema-first options boil down to &lt;a href="https://github.com/davidpdrsn/juniper-from-schema"&gt;juniper-from-schema&lt;/a&gt; and &lt;a href="https://github.com/graphql-rust/graphql-client"&gt;graphql-client&lt;/a&gt;.  I started with the latter mainly because it has far more stars on github and downloads on crates.io, but also because &lt;a href="https://github.com/graphql-rust/graphql-client/blob/master/examples/github/examples/github.rs"&gt;their examples&lt;/a&gt; use reqwest.&lt;/p&gt;

&lt;p&gt;To use in Rust:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Download `graphql-client` tool to `~/.cargo/bin/` (add to $PATH)&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;graphql_client_cli
&lt;span class="c"&gt;# Get hashnode API schema&lt;/span&gt;
graphql-client introspect-schema &lt;span class="nt"&gt;--output&lt;/span&gt; hashnode_schema.json https://api.hashnode.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time for macro magic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(graphql_client::GraphQLQuery)]&lt;/span&gt;
&lt;span class="nd"&gt;#[graphql(&lt;/span&gt;
    &lt;span class="nd"&gt;schema_path&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"hashnode_schema.json"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;query_path&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src/hashnode.graphql"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Added to `#[derive()]` on `ResponseData`&lt;/span&gt;
    &lt;span class="nd"&gt;response_derives&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Debug"&lt;/span&gt;
    &lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="c1"&gt;// Name matches query in hashnode.graphql&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Tags&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;build_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Variables&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Post query to hashnode server&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client&lt;/span&gt;&lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.hashnode.com/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Response is GraphQL `[Tags]` type (array itself and contained items can be null).&lt;/span&gt;
&lt;span class="c1"&gt;// But, `[Tags!]!` (nothing is null) is easier to deal with in Rust&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TagsTagCategories&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Make sure to wrap your `ResponseData` with `graphql_client::Response`!&lt;/span&gt;
    &lt;span class="c1"&gt;// If you `resp.json&amp;lt;test::ResponseData&amp;gt;()` you'll get nothing because the &lt;/span&gt;
    &lt;span class="c1"&gt;// response is `{ data: ResponseData }`&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;graphql_client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ResponseData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="py"&gt;.data&lt;/span&gt;
        &lt;span class="nf"&gt;.and_then&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="py"&gt;.tag_categories&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;// Turn `Option&amp;lt;Vec&amp;lt;Option&amp;lt;_&amp;gt;&amp;gt;&amp;gt;` into `Vec&amp;lt;_&amp;gt;`&lt;/span&gt;
        &lt;span class="nf"&gt;.unwrap_or_default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.into_iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.flatten&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That probably looks like a lot of code and types.  Rust can infer many of the types and I could have imported the namespaces, but I find it helpful to see explicit modules and types until you understand what's going on.  The last third is just collapsing &lt;code&gt;Option&amp;lt;&amp;gt;&lt;/code&gt;/&lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to see what the &lt;code&gt;#[derive()]&lt;/code&gt; and &lt;code&gt;#[graphql()]&lt;/code&gt; macros are doing behind the scenes, you can explicitly generate Rust source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;graphql-client generate src/hashnode.graphql &lt;span class="nt"&gt;--output-directory&lt;/span&gt; src &lt;span class="nt"&gt;--schema-path&lt;/span&gt; ./hashnode_schema.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;hashnode.rs&lt;/code&gt; contains the generated code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#![allow(dead_code)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;OPERATION_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Tags"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"query Tags {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  tagCategories{&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    _id&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    name&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    slug&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;}"&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nd"&gt;#[allow(dead_code)]&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Boolean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;#[allow(dead_code)]&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;#[allow(dead_code)]&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;#[allow(dead_code)]&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;#[derive(Deserialize)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;TagsTagCategories&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;#[serde(rename&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"_id"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nd"&gt;#[derive(Serialize)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Variables&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;#[derive(Deserialize)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ResponseData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;#[serde(rename&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tagCategories"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;tag_categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TagsTagCategories&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nn"&gt;graphql_client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;GraphQLQuery&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Variables&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ResponseData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ResponseData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;build_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Variables&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;graphql_client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;QueryBody&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Variables&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;graphql_client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;QueryBody&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;QUERY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;operation_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;OPERATION_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Main take-aways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You end up using both &lt;code&gt;tags::&lt;/code&gt; and &lt;code&gt;Tags::&lt;/code&gt; (note capital &lt;code&gt;T&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/serde-rs/serde"&gt;serde&lt;/a&gt; is used for serialization to/from JSON&lt;/li&gt;
&lt;li&gt;The nullable GraphQL type &lt;code&gt;TYPE&lt;/code&gt; becomes &lt;code&gt;Option&amp;lt;TYPE&amp;gt;&lt;/code&gt; in Rust, while the non-nullable GraphQL &lt;code&gt;TYPE!&lt;/code&gt; becomes &lt;code&gt;TYPE&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Posting an article can be done with a "mutation".  This example is interesting because it shows how to pass arguments to GraphQL with &lt;code&gt;Variables&lt;/code&gt;.  In &lt;code&gt;hashnode.graphql&lt;/code&gt; add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="c"&gt;# We'll provide `$input` and `$pubId` arguments in Rust&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CreatePubStory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CreateStoryInput&lt;/span&gt;&lt;span class="p"&gt;!,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pubId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$hideFromFeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c"&gt;# This is the mutation, we use arguments from above&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;createPublicationStory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;publicationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pubId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hideFromHashnodeFeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$hideFromFeed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c"&gt;# Response fields specified in fragment&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;createResponseFields&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Fragment usable on CreatePostOutput types&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;fragment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;createResponseFields&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CreatePostOutput&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the corresponding Rust is similar to before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(graphql_client::GraphQLQuery)]&lt;/span&gt;
&lt;span class="nd"&gt;#[graphql(&lt;/span&gt;
    &lt;span class="nd"&gt;schema_path&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"hashnode_schema.json"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;query_path&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src/hashnode.graphql"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;response_derives&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Debug"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Add `#[derive(Default)]` to `Variables` type&lt;/span&gt;
    &lt;span class="nd"&gt;variables_derives&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Default"&lt;/span&gt;
    &lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;CreatePubStory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Struct used as mutation input&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;create_pub_story&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CreateStoryInput&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;content_markdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="py"&gt;.body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Tags from earlier query&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="py"&gt;.front_matter.title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// etc...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="c1"&gt;// Pass `$input` and `$pub_id` arguments to `CreatePubStory` mutation in hashnode.graphql via `Variables` struct&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CreatePubStory&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;build_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;create_pub_story&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pub_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.pub_id&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="nn"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Since we added #[derive(Default)]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client&lt;/span&gt;&lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.hashnode.com/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// Required authorization header&lt;/span&gt;
    &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.api_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;graphql_client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;create_pub_story&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ResponseData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Updating a post can be accomplished with &lt;code&gt;updateStory()&lt;/code&gt; mutation.  However, hashnode's API doesn't seem to provide a way to retrieve a post's canonical URL, so we can't use that to locate an existing article like with devto.  Instead, I'll generate a slug from the filename and look for that.  That means I'll have to handle renaming a file, but I figure that's less likely to happen than changing the title, body, or something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Miscellany
&lt;/h2&gt;

&lt;p&gt;To close this out, a few smaller topics that were useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Async
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/tokio-rs/tokio"&gt;Tokio&lt;/a&gt; seems to be the de-facto solution for async Rust.  Many of the required types like &lt;code&gt;Future&lt;/code&gt; are in the standard library, but the futures crate &lt;a href="https://rendered-obsolete.github.io/2019/10/28/async_rust_beta.html"&gt;is still pretty useful&lt;/a&gt;.  In &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;futures&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.3"&lt;/span&gt;
&lt;span class="nn"&gt;tokio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.5.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;["full"]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;main.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Elsewhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;future&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;LocalBoxFuture&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;pin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;devto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;devto&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;DevtoCrossPublish&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;devto&lt;/span&gt;&lt;span class="nf"&gt;.publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="c1"&gt;// Do the same for medium and hashnode...&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;future&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;join_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;join_all&lt;/code&gt; is similar to the &lt;a href="https://rust-lang.github.io/async-book/06_multiple_futures/02_join.html"&gt;&lt;code&gt;join!&lt;/code&gt; macro&lt;/a&gt; and essentially awaits an iterator of futures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error Handling
&lt;/h3&gt;

&lt;p&gt;There's been several chapters in the Rust error handling story; &lt;code&gt;failure&lt;/code&gt;, &lt;code&gt;error-chain&lt;/code&gt;, and a few others I'm forgetting.  Apparently those are old and busted, &lt;a href="https://github.com/dtolnay/anyhow"&gt;&lt;code&gt;anyhow&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/dtolnay/thiserror"&gt;&lt;code&gt;thiserror&lt;/code&gt;&lt;/a&gt; are new hotness.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;anyhow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;
&lt;span class="py"&gt;thiserror&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;anyhow works with the &lt;code&gt;std::error::Error&lt;/code&gt; trait, propogates errors with &lt;code&gt;?&lt;/code&gt;, allows specifying additional context, and has some useful macros:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;can_fail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;this_fails&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to whatever"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;also_fails&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.with_context&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Check this: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;important_thing&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;whatever&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// anyhow! macro produces a anyhow::Error&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;anyhow!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Missing attribute: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;other_thing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// bail! macro is shorthand for above&lt;/span&gt;
        &lt;span class="nd"&gt;bail!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Missing attribute: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From &lt;a href="https://docs.rs/anyhow/1.0.40/anyhow/struct.Error.html"&gt;the docs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;(anyhow::)Error works a lot like &lt;code&gt;Box&amp;lt;dyn std::error::Error&amp;gt;&lt;/code&gt;, but with these differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Error requires that the error is Send, Sync, and 'static.&lt;/li&gt;
&lt;li&gt;Error guarantees that a backtrace is available, even if the underlying error type does not provide one.&lt;/li&gt;
&lt;li&gt;Error is represented as a narrow pointer — exactly one word in size instead of two.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;thiserror is complementary by helping create custom error types that &lt;code&gt;impl std::error::Error&lt;/code&gt; and &lt;code&gt;impl Display&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(thiserror::Error,&lt;/span&gt; &lt;span class="nd"&gt;Clone,&lt;/span&gt; &lt;span class="nd"&gt;Debug)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[error(&lt;/span&gt;&lt;span class="s"&gt;"Bad path, expected {expected}: {found}"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;BadPath&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;path&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;PathBuf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="nf"&gt;.ok_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BadPath&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nf"&gt;.to_owned&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nf"&gt;.to_owned&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It has several other features, check &lt;a href="https://docs.rs/thiserror/"&gt;the docs&lt;/a&gt; for more information.&lt;/p&gt;

&lt;p&gt;If you get this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;E0308&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;mismatched&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;
   &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;platforms&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;hashnode&lt;/span&gt;&lt;span class="py"&gt;.rs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;148&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="mi"&gt;148&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;         &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt;             &lt;span class="o"&gt;^^^^^^^^^^^^^^^^&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;impl From&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;NotFound&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Logging
&lt;/h3&gt;

&lt;p&gt;I'd wager most people are familiar with &lt;a href="https://crates.io/crates/log"&gt;log&lt;/a&gt; and a logger implementation like &lt;a href="https://docs.rs/env_logger/"&gt;env_logger&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;log&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nn"&gt;env_logger&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Information: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/tokio-rs/tracing"&gt;tracing&lt;/a&gt; is similar.  In Cargo.toml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;tracing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1"&lt;/span&gt;
&lt;span class="py"&gt;tracing-subscriber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nn"&gt;tracing_subscriber&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use &lt;a href="https://docs.rs/tracing-subscriber/0.2.18/tracing_subscriber/filter/struct.EnvFilter.html"&gt;RUST_LOG to filter output&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# PowerShell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;RUST_LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"warn,cargo_bullhorn=trace"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# bash-like&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RUST_LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;cargo_bullhorn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tracing provides additional features: logging sections of code with RAII spans, decorator for functions, and more.  Check &lt;a href="https://docs.rs/tracing/0.1.26/tracing/"&gt;the docs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>cli</category>
      <category>productivity</category>
      <category>graphql</category>
    </item>
    <item>
      <title>Please stop the forem app pop-up</title>
      <dc:creator>jeikabu</dc:creator>
      <pubDate>Sun, 30 May 2021 06:41:29 +0000</pubDate>
      <link>https://dev.to/jeikabu/please-stop-the-forem-app-pop-up-2015</link>
      <guid>https://dev.to/jeikabu/please-stop-the-forem-app-pop-up-2015</guid>
      <description>&lt;p&gt;Please stop the forem app message from constantly popping up on mobile. It's annoying. &lt;/p&gt;

</description>
      <category>meta</category>
      <category>ux</category>
    </item>
    <item>
      <title>Benchmarking in .NET or How I Learned to Stop Worrying and Love Perf Regression Testing</title>
      <dc:creator>jeikabu</dc:creator>
      <pubDate>Sat, 22 May 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/jeikabu/benchmarking-in-net-or-how-i-learned-to-stop-worrying-and-love-perf-regression-testing-55pn</link>
      <guid>https://dev.to/jeikabu/benchmarking-in-net-or-how-i-learned-to-stop-worrying-and-love-perf-regression-testing-55pn</guid>
      <description>&lt;p&gt;In &lt;a href="https://rendered-obsolete.github.io/2021/05/03/dotnet_calli.html" rel="noopener noreferrer"&gt;an earlier post&lt;/a&gt; we were looking at the performance of function pointers introduced in .NET 5/C# 9.0. To do that we used &lt;a href="https://github.com/dotnet/BenchmarkDotNet" rel="noopener noreferrer"&gt;BenchmarkDotNet&lt;/a&gt; (&lt;a href="https://benchmarkdotnet.org/articles/overview.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;), the de-facto .NET benchmarking framework. This is a shotgun dump of notes to get you writing your own benchmarks- mainly for performance regression testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basics
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install benchmark template&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BenchmarkDotNet.Templates&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Create new benchmark project `benchmarks`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;benchmark&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;benchmarks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--console-app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;0.12.1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;See &lt;a href="https://benchmarkdotnet.org/articles/guides/dotnet-new-templates.html" rel="noopener noreferrer"&gt;BenchmarkDotNet template options&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Confusingly, you’ll find references to installing &lt;code&gt;BenchmarkDotNet.Tool&lt;/code&gt; global tool which has been deprecated (see issue &lt;a href="https://github.com/dotnet/BenchmarkDotNet/issues/1670" rel="noopener noreferrer"&gt;1670&lt;/a&gt; and PR &lt;a href="https://github.com/dotnet/BenchmarkDotNet/pull/1572" rel="noopener noreferrer"&gt;1572&lt;/a&gt;). Likewise, the dotnet project template &lt;code&gt;BenchmarkDotNet.Templates&lt;/code&gt; defaults to: NET Core App 3.0 (which is &lt;a href="https://aka.ms/dotnet-core-support" rel="noopener noreferrer"&gt;out of support&lt;/a&gt;), and version 0.12.0 of BenchmarkDotNet (which doesn’t support NET 5.0 and may fail with &lt;code&gt;System.NotSupportedException: Unknown .NET Runtime&lt;/code&gt;). Open &lt;code&gt;benchmarks/benchmarks.csproj&lt;/code&gt; and replace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-&amp;lt;TargetFramework&amp;gt;netcoreapp3.0&amp;lt;/TargetFramework&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+&amp;lt;TargetFramework&amp;gt;net5.0&amp;lt;/TargetFramework&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template is pretty simple, so it’s just as easy to run &lt;code&gt;dotnet new classlib -o benchmarks&lt;/code&gt; and populate &lt;code&gt;benchmarks.csproj&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net5.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span class="nt"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PlatformTarget&amp;gt;&lt;/span&gt;AnyCPU&lt;span class="nt"&gt;&amp;lt;/PlatformTarget&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;DebugType&amp;gt;&lt;/span&gt;portable&lt;span class="nt"&gt;&amp;lt;/DebugType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;DebugSymbols&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/DebugSymbols&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AllowUnsafeBlocks&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/AllowUnsafeBlocks&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Optimize&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/Optimize&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Configuration&amp;gt;&lt;/span&gt;Release&lt;span class="nt"&gt;&amp;lt;/Configuration&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"BenchmarkDotNet"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"0.12.1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"BenchmarkDotNet.Diagnostics.Windows"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"0.12.1"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(OS)' == 'Windows_NT'"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;benchmarks/Program.cs&lt;/code&gt;, replace the main body with the following to get the same functionality as the benchmark runner CLI (from &lt;a href="https://benchmarkdotnet.org/articles/guides/console-args.html" rel="noopener noreferrer"&gt;the docs&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;BenchmarkDotNet.Configs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;BenchmarkDotNet.Running&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;benchmarks&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;BenchmarkSwitcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromAssembly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a benchmark in &lt;code&gt;Benchmarks.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;BenchmarkDotNet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;BenchmarkDotNet.Attributes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;benchmarks&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Delegate&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;CallDelegate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Do nothing for now&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try running the benchmarks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/Benchmarks/benchmarks.csproj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"benchmarks.*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you leave off &lt;code&gt;--filter&lt;/code&gt; it will wait for input from stdin.&lt;/p&gt;

&lt;p&gt;To see the list of CLI options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/Benchmarks/benchmarks.csproj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-h&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  More
&lt;/h2&gt;

&lt;p&gt;Invariably, you’re testing some other library as opposed to writing stand-alone benchmarks. And then at some point it may fail with &lt;code&gt;Assembly XXX which defines benchmarks references non-optimized YYY&lt;/code&gt;. Everything needs to be compiled for release:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add a project reference to assembly we're benchmarking&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;benchmarks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/nng.NET.Shared/nng.NET.Shared.csproj&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Note --configuration&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/Benchmarks/benchmarks.csproj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"benchmarks.*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to other testing frameworks, you can have initialization and cleanup (that is excluded from benchmark timings) with &lt;code&gt;[GlobalSetup]&lt;/code&gt;/&lt;code&gt;[GlobalCleanup]&lt;/code&gt; and &lt;code&gt;[IterationSetup]&lt;/code&gt;/&lt;code&gt;[IterationCleanup]&lt;/code&gt; for per-class and per-iteration behavior, respectively (&lt;a href="https://benchmarkdotnet.org/articles/features/setup-and-cleanup.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Delegate&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;delegate&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;nng_aio_set_output_delegate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nng_aio&lt;/span&gt; &lt;span class="n"&gt;aio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;nng_aio_set_output_delegate&lt;/span&gt; &lt;span class="n"&gt;nng_aio_set_output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;GlobalSetup&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;GlobalSetup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NativeLibrary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetExport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"nng_aio_set_output"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;nng_aio_set_output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetDelegateForFunctionPointer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;nng_aio_set_output_delegate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;CallDelegate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nng_aio_set_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nng_aio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two main approaches to defining benchmarks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://benchmarkdotnet.org/articles/configs/configs.html" rel="noopener noreferrer"&gt;Configs&lt;/a&gt; (in either fluent or object style)&lt;/li&gt;
&lt;li&gt;Class/function &lt;a href="https://benchmarkdotnet.org/api/BenchmarkDotNet.Attributes.html" rel="noopener noreferrer"&gt;attributes&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which allows you to configure all sorts of interesting things: benchmark parameters, runtime &lt;a href="https://benchmarkdotnet.org/articles/configs/jobs.html" rel="noopener noreferrer"&gt;jobs&lt;/a&gt; (e.g. &lt;a href="https://benchmarkdotnet.org/articles/configs/toolchains.html" rel="noopener noreferrer"&gt;.NET version&lt;/a&gt;, JIT/GC), etc.&lt;/p&gt;

&lt;p&gt;If you’ve got multiple classes the CLI results are spread out. To get a single, tidy table at the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ManualConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DefaultConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ConfigOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JoinSummary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BenchmarkSwitcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromAssembly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://benchmarkdotnet.org/api/BenchmarkDotNet.Configs.ManualConfig.html" rel="noopener noreferrer"&gt;ManualConfig&lt;/a&gt; has numerous more options. Everything in &lt;a href="https://benchmarkdotnet.org/articles/configs/configoptions.html" rel="noopener noreferrer"&gt;&lt;code&gt;ConfigOptions&lt;/code&gt;&lt;/a&gt; can be specified as &lt;a href="https://benchmarkdotnet.org/articles/guides/console-args.html" rel="noopener noreferrer"&gt;CLI arguments&lt;/a&gt; after &lt;code&gt;--&lt;/code&gt;. For example, &lt;code&gt;JoinSummary&lt;/code&gt; corresponds to &lt;code&gt;--join&lt;/code&gt;. Reference &lt;code&gt;-- -h&lt;/code&gt; for others.&lt;/p&gt;

&lt;p&gt;To quickly iterate at the expense of accuracy use: &lt;code&gt;--job Dry&lt;/code&gt;, &lt;code&gt;--runOncePerIteration&lt;/code&gt;, or some combination of &lt;code&gt;--warmupCount 1 --iterationCount 1 --invocationCount 1 --unrollFactor 1&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reports
&lt;/h2&gt;

&lt;p&gt;By default, csv, html, and markdown results are written to &lt;code&gt;BenchmarkDotNet.Artifacts/results/&lt;/code&gt;. Other formats are possible with &lt;code&gt;-e&lt;/code&gt;, and the base output path can be specified with &lt;code&gt;-a&lt;/code&gt;/&lt;code&gt;--artifacts&lt;/code&gt;. The markdown is suitable for posts such as this, CI web interfaces, etc.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BenchmarkDotNet=v0.12.1, OS=macOS 11.3.1 (20E241) [Darwin 20.4.0]
Intel Core i5-8279U CPU 2.40GHz (Coffee Lake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=5.0.101
[Host] : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
DefaultJob : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;StdDev&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;th&gt;RatioSD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Delegate&lt;/td&gt;
&lt;td&gt;CallDelegate&lt;/td&gt;
&lt;td&gt;16.934 ns&lt;/td&gt;
&lt;td&gt;0.1687 ns&lt;/td&gt;
&lt;td&gt;0.1578 ns&lt;/td&gt;
&lt;td&gt;16.23&lt;/td&gt;
&lt;td&gt;0.23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DllImport&lt;/td&gt;
&lt;td&gt;CallDllImport&lt;/td&gt;
&lt;td&gt;6.713 ns&lt;/td&gt;
&lt;td&gt;0.0666 ns&lt;/td&gt;
&lt;td&gt;0.0590 ns&lt;/td&gt;
&lt;td&gt;6.44&lt;/td&gt;
&lt;td&gt;0.09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interface&lt;/td&gt;
&lt;td&gt;CallInterfaceToDllImport&lt;/td&gt;
&lt;td&gt;9.884 ns&lt;/td&gt;
&lt;td&gt;0.1892 ns&lt;/td&gt;
&lt;td&gt;0.2652 ns&lt;/td&gt;
&lt;td&gt;9.55&lt;/td&gt;
&lt;td&gt;0.35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pointer&lt;/td&gt;
&lt;td&gt;CallFunctionPointer&lt;/td&gt;
&lt;td&gt;6.764 ns&lt;/td&gt;
&lt;td&gt;0.1519 ns&lt;/td&gt;
&lt;td&gt;0.1347 ns&lt;/td&gt;
&lt;td&gt;6.49&lt;/td&gt;
&lt;td&gt;0.18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DllImport&lt;/td&gt;
&lt;td&gt;CallManaged&lt;/td&gt;
&lt;td&gt;1.042 ns&lt;/td&gt;
&lt;td&gt;0.0125 ns&lt;/td&gt;
&lt;td&gt;0.0110 ns&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To get graphs, install the &lt;a href="https://www.r-project.org/" rel="noopener noreferrer"&gt;R programming language&lt;/a&gt; and use &lt;code&gt;-e rplot&lt;/code&gt;. If &lt;code&gt;rscript&lt;/code&gt; is in your &lt;code&gt;PATH&lt;/code&gt; environment variable, it will automatically output several png files, otherwise you can manually run &lt;code&gt;rscript BenchmarkDotNet.Artifacts/results/BuildPlots.R&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fbenchmarkdotnet_graph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fbenchmarkdotnet_graph.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparisons
&lt;/h2&gt;

&lt;p&gt;What’s “fast” (or “slow”)? Benchmarks are sometimes useless without something to compare them to.&lt;/p&gt;

&lt;p&gt;You can define a &lt;a href="https://benchmarkdotnet.org/articles/features/baselines.html" rel="noopener noreferrer"&gt;baseline&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Baseline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;CallManaged&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Managed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Prevent inline-ing&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;MethodImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MethodImplOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NoInlining&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;nint&lt;/span&gt; &lt;span class="nf"&gt;Managed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;nint&lt;/span&gt; &lt;span class="n"&gt;_unused0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;_unused1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;nint&lt;/span&gt; &lt;span class="n"&gt;_unused2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Having a baseline also gives you “ratio” and “ratioSD” (StdDev) columns in the output.&lt;/p&gt;

&lt;p&gt;BenchmarkDotnet &lt;a href="https://github.com/dotnet/BenchmarkDotNet/issues/973" rel="noopener noreferrer"&gt;doesn’t natively support&lt;/a&gt; comparing results from different runs (i.e. before and after code changes), but you can use &lt;a href="https://github.com/dotnet/performance/tree/main/src/tools/ResultsComparer" rel="noopener noreferrer"&gt;ResultsComparer&lt;/a&gt; from the .NET project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone repo and build ResultsComparer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://github.com/dotnet/performance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dotnet_perf&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pushd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dotnet_perf/src/tools/ResultsComparer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;release&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;popd&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# View CLI options&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/dotnet_perf/artifacts/bin/ResultsComparer/Release/net5.0/ResultsComparer.dll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-h&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Run benchmarks with full JSON reports&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/Benchmarks/benchmarks.csproj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"benchmarks.*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fulljson&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Compare with results in ./base&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/dotnet_perf/artifacts/bin/ResultsComparer/Release/net5.0/ResultsComparer.dll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--diff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/BenchmarkDotNet.Artifacts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--threshold&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'5%'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ResultsComparer requires “full” JSON reports. So, you’ll have to do one of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run with &lt;code&gt;-e fulljson&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;AddExporter(BenchmarkDotNet.Exporters.Json.JsonExporter.Full)&lt;/code&gt; on a config&lt;/li&gt;
&lt;li&gt;Apply &lt;code&gt;[BenchmarkDotNet.Attributes.JsonExporterAttribute.Full]&lt;/code&gt; attribute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;--threshold&lt;/code&gt; sets the permitted variation between results. It can be a percentage, or length of time like &lt;code&gt;1ms&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Stdout contains markdown results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Slower&lt;/th&gt;
&lt;th&gt;diff/base&lt;/th&gt;
&lt;th&gt;Base Median (ns)&lt;/th&gt;
&lt;th&gt;Diff Median (ns)&lt;/th&gt;
&lt;th&gt;Modality&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;benchmarks.Interface.CallInterfaceToDllImport&lt;/td&gt;
&lt;td&gt;112104.10&lt;/td&gt;
&lt;td&gt;11.05&lt;/td&gt;
&lt;td&gt;1239001.46&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Oops, looks like my &lt;code&gt;Thread.Sleep(1)&lt;/code&gt; had a measureable impact.&lt;/p&gt;

&lt;p&gt;ResultsComparer has some unexpected behavior. It throws an exception when there’s multiple reports in the same folder, so you may need to clear results from repeated runs. And it doesn’t work with results from “dry” jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI
&lt;/h2&gt;

&lt;p&gt;We’re looking to add this as part of our Github Action workflows, but it applies equally well to any other CI/CD platform.&lt;/p&gt;

&lt;p&gt;It’s tempting to fail a build/PR if any benchmark is slower. But, sometimes it’s unavoidable with a bug fix or “correct” implementation. &lt;em&gt;IF&lt;/em&gt; we wanted to do it anyway, check if any test is “Slower”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Output comparison as CSV `./csv`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ResultsComparer.dll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--diff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/BenchmarkDotNet.Artifacts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--threshold&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'5%'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/csv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Parse csv, give it a header (it doesn't have one), and get 3rd column&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$column&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Import-csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Delimiter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;';'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Benchmark'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Source'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Result'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Result'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Check for "Slower"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$column&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Slower"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we’d really like is a summary in the comments of a pull request- similar to our code coverage reports. There’s multiple ways to add a comment from a workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/actions/github-script#comment-on-an-issue" rel="noopener noreferrer"&gt;github-script block&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;curl/REST&lt;/li&gt;
&lt;li&gt;Community marketplace action (&lt;a href="https://github.com/marketplace/actions/comment-pull-request" rel="noopener noreferrer"&gt;option1&lt;/a&gt;, &lt;a href="https://github.com/marketplace/actions/sticky-pull-request-comment" rel="noopener noreferrer"&gt;option2&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using HTTP REST, PowerShell works on all platforms and requires no external dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run ResultsComparer and capture markdown output&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ResultsComparer.dll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/BenchmarkDotNet.Artifacts/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--diff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/diff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--threshold&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'5%'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Convert string[] output to single string with line-breaks&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-join&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'`n'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$secure_token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertTo-SecureString&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_TOKEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AsPlainText&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# POST comment to PR&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="nx"&gt;accept&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'application/vnd.github.v3+json'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://api.github.com/repos/&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="nx"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$REPO&lt;/span&gt;&lt;span class="nx"&gt;/issues/&lt;/span&gt;&lt;span class="nv"&gt;$PR_ID&lt;/span&gt;&lt;span class="nx"&gt;/comments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ConvertTo-Json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Authentication&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OAuth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$secure_token&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;$PR_ID&lt;/code&gt; can be obtained from &lt;code&gt;$&lt;/code&gt; in the workflow.&lt;/p&gt;

&lt;p&gt;Assuming everything is correct:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fgithub_action_issue_comment.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fgithub_action_issue_comment.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ve now got a bunch pieces, but haven’t really settled on how we want the process to work. &lt;a href="https://rendered-obsolete.github.io/2021/02/09/github_actions.html#native-code" rel="noopener noreferrer"&gt;Similar to before&lt;/a&gt;, we can attach any of the generated reports with &lt;a href="https://github.com/actions/upload-artifact" rel="noopener noreferrer"&gt;&lt;code&gt;upload-artifact&lt;/code&gt;&lt;/a&gt;/&lt;a href="https://github.com/actions/download-artifact" rel="noopener noreferrer"&gt;&lt;code&gt;download-artifact&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://github.com/actions/upload-release-asset" rel="noopener noreferrer"&gt;&lt;code&gt;upload-release-asset&lt;/code&gt;&lt;/a&gt; as build artifacts or release assets, respectively. And then generate comparison reports. But, it feels best to use a wide gamut of fine-grained benchmarks to catch performance regressions in PRs, and then a smaller number of broader, generalized tests to highlight changes between releases. This calls for more experimentation.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>performance</category>
      <category>devops</category>
    </item>
    <item>
      <title>Physical Security</title>
      <dc:creator>jeikabu</dc:creator>
      <pubDate>Sat, 15 May 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/jeikabu/physical-security-2nm4</link>
      <guid>https://dev.to/jeikabu/physical-security-2nm4</guid>
      <description>&lt;p&gt;I love a good caper film. When I was a wee lad my father introduced me to &lt;a href="https://www.imdb.com/title/tt0079240/"&gt;The Great Train Robbery&lt;/a&gt;. Over the years I’ve seen several memorable others &lt;a href="https://en.wikipedia.org/wiki/Rififi"&gt;Rififi&lt;/a&gt;, &lt;a href="https://www.imdb.com/title/tt0439884/"&gt;A World Without Thieves&lt;/a&gt;, several by &lt;a href="https://www.imdb.com/name/nm0000519/?ref_=tt_ov_dr"&gt;David Mamet&lt;/a&gt; like &lt;a href="https://www.imdb.com/title/tt0120176/"&gt;The Spanish Prisoner&lt;/a&gt;. Just the other day I watched &lt;a href="https://www.imdb.com/title/tt0454848/"&gt;Inside Man&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Numerous vulnerabilities like &lt;a href="https://en.wikipedia.org/wiki/Spectre_(security_vulnerability)"&gt;Spectre&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack"&gt;Man-in-the-middle&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/SQL_injection"&gt;SQL injection&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Keystroke_logging"&gt;keyloggers&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Phishing"&gt;phishing&lt;/a&gt; and a legion of others have loomed over the IT sphere for decades. Seems like every month there’s a new, “largest ever”, high profile security incident.&lt;/p&gt;

&lt;p&gt;The never ending struggle has given rise to a bevy of best-practices and technologies: &lt;a href="https://en.wikipedia.org/wiki/Public-key_cryptography"&gt;asymmetric cryptography&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Penetration_test"&gt;penetration testing&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Salt_(cryptography)"&gt;salting hashed passwords&lt;/a&gt;, numerous VM enhancements (like &lt;a href="https://developer.amd.com/sev/"&gt;SEV&lt;/a&gt;), &lt;a href="https://en.wikipedia.org/wiki/Multi-factor_authentication"&gt;two-factor authentication&lt;/a&gt; and countless others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Locks and Lock Picking
&lt;/h2&gt;

&lt;p&gt;Many people working in IT are accustomed to the idea of a (climate-controlled) server room with limited access. Anyone that’s needed to restore root/administrator access (and perhaps keeps a bootable USB drive of associated tools on their person) understands the fundamental risk.&lt;/p&gt;

&lt;p&gt;In most cases, these are protected by locks controlled via keypad, access card, or biometrics. But the pre-cursor to them, the humble pin tumbler lock, is still found in a variety of settings like doors and cabinets that likewise protect equipment.&lt;/p&gt;

&lt;p&gt;About a year ago I became curious about lock picking. I can’t remember which movie I was watching, but someone is locked in a basement and manages to remove their restraints and escape- seemed like a useful skill to have.&lt;/p&gt;

&lt;p&gt;Numerous resources exist for budding locksmiths such as &lt;a href="https://www.amazon.com/gp/product/B01LZ44ZD5/ref=kinw_myk_ro_title"&gt;The Complete Book of Locks and Locksmithing&lt;/a&gt;. It covers the design of several types of locks, how to duplicate keys and design a master-key system, and lockpicking among other topics relevant in the trade. Additionally, there are an assortment of educational tools such as practice locks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5h45ySvd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rendered-obsolete.github.io/assets/lock_practice.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5h45ySvd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rendered-obsolete.github.io/assets/lock_practice.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After which you can graduate to over-the-counter locks available in any hardware store:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JkEHJjQ8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rendered-obsolete.github.io/assets/lock_real.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JkEHJjQ8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rendered-obsolete.github.io/assets/lock_real.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most surprising to me was that even with inexpensive, entry-level picks, anything less than “high security” locks take little practice. &lt;strong&gt;NB&lt;/strong&gt; : possession of lockpicks by unlicensed individuals may &lt;a href="https://en.wikipedia.org/wiki/Lock_picking#Legal_status"&gt;not be legal where you are&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Continuing with my cinematic theme, &lt;a href="https://www.imdb.com/title/tt1458175/"&gt;The Next Three Days&lt;/a&gt; features Russel Crowe’s failed attempt to use a &lt;a href="https://en.wikipedia.org/wiki/Lock_bumping"&gt;“bump key”&lt;/a&gt;. A variety of lockpicking using a specially cut key for particular locks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does Physical Security Really Exist?
&lt;/h2&gt;

&lt;p&gt;Over the years, researchers have demonstrated borderline outlandish, largely theoretical attacks that don’t even require physical access. For example, using &lt;a href="https://arstechnica.com/information-technology/2016/08/new-air-gap-jumper-covertly-transmits-data-in-hard-drive-sounds/"&gt;hard-drive acoustics&lt;/a&gt;, &lt;a href="https://cyber.bgu.ac.il/exfiltrating-data-from-air-gapped-computers-using-screen-brightness/"&gt;screen brightness&lt;/a&gt;, and several others.&lt;/p&gt;

&lt;p&gt;And more recently, the article that inspired this post, &lt;a href="https://kottke.org/20/08/researchers-can-duplicate-keys-from-the-sounds-they-make-in-locks"&gt;Researchers Can Duplicate Keys from the Sounds They Make in Locks&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>meatspace</category>
      <category>watercooler</category>
      <category>cinema</category>
    </item>
    <item>
      <title>Native Code in .NET 5.0 and C# 9.0</title>
      <dc:creator>jeikabu</dc:creator>
      <pubDate>Mon, 03 May 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/jeikabu/native-code-in-net-5-0-and-c-9-0-39h7</link>
      <guid>https://dev.to/jeikabu/native-code-in-net-5-0-and-c-9-0-39h7</guid>
      <description>&lt;p&gt;A while back &lt;a href="https://rendered-obsolete.github.io/2018/09/09/native-assembly.html"&gt;we covered working with native assemblies&lt;/a&gt;.  It's worth re-reading to familiarize yourself with the problem and prior solutions.  There's been a few new options introduced in .NET Core and .NET 5.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  NativeLibrary
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.nativelibrary"&gt;NativeLibrary&lt;/a&gt; static class was introduced in .NET Core 3.0 and makes it easy to use &lt;code&gt;DllImport&lt;/code&gt; in a portable way.  To call a function &lt;code&gt;nng_alloc()&lt;/code&gt; in a native shared library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In `nng.NETCore` managed assembly&lt;/span&gt;
&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;nng.Native.Basic&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UnsafeNativeMethods&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;NngDll&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nng"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NngDll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CallingConvention&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CallingConvention&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cdecl&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="nf"&gt;nng_alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UIntPtr&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// In assembly that references `nng.NETCore`&lt;/span&gt;
&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;benchmarks&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;NativeLibrary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetDllImportResolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Native&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Basic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnsafeNativeMethods&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DllImportResolver&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// Calling DllImport'd method triggers resolver&lt;/span&gt;
            &lt;span class="n"&gt;nng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Native&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Basic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnsafeNativeMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nng_alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UIntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="nf"&gt;DllImportResolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;libraryName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reflection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt; &lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DllImportSearchPath&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;searchPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Load platform-specific unmanaged shared library. e.g.:&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"runtimes/osx-x64/native/libnng.dylib"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;NativeLibrary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of the mess of calling &lt;code&gt;dlopen()&lt;/code&gt; on Mac/*nix or &lt;code&gt;LoadLibrary()&lt;/code&gt; on Windows to load a *.so or *.dll, respectively.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;DYLD_PRINT_LIBRARIES&lt;/code&gt; env variable is set before running the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dyld: loaded: &amp;lt;264EA187-4189-3CE5-82C3-9746FFE68B66&amp;gt; runtimes/osx-x64/native/libnng.dylib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Function Pointers
&lt;/h2&gt;

&lt;p&gt;.NET 5.0 and C# 9.0 &lt;a href="https://devblogs.microsoft.com/dotnet/improvements-in-native-code-interop-in-net-5-0/"&gt;introduced function pointers&lt;/a&gt; as "a performant way to call native functions from C#".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Pointer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;CallFunctionPointer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Load platform-specific native library&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NativeLibrary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"xxx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Get address of exported symbol&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NativeLibrary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetExport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"nng_alloc"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Function pointers require `unsafe` context&lt;/span&gt;
        &lt;span class="k"&gt;unsafe&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;nng_alloc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;unmanaged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Cdecl&lt;/span&gt;&lt;span class="p"&gt;]&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;nuint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="c1"&gt;// Call through function pointer&lt;/span&gt;
            &lt;span class="nf"&gt;nng_alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.nativelibrary.getexport"&gt;&lt;code&gt;NativeLibrary.GetExport()&lt;/code&gt;&lt;/a&gt; returns the address of a symbol exported by a shared library.  It effectively replaces calling the native functions &lt;code&gt;GetProcAddress()&lt;/code&gt; and &lt;code&gt;dlsym()&lt;/code&gt; on Windows and Mac/*nix, respectively.&lt;/p&gt;

&lt;p&gt;Function pointers use the funky syntax &lt;code&gt;delegate* unmanaged[Cdecl]&amp;lt;args, retval&amp;gt;&lt;/code&gt; (detailed &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/function-pointers"&gt;here&lt;/a&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;delegate*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reuses &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/"&gt;&lt;code&gt;delegate&lt;/code&gt;&lt;/a&gt; keyword and &lt;code&gt;*&lt;/code&gt; denotes an unsafe pointer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unmanaged&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Either &lt;code&gt;managed&lt;/code&gt; or &lt;code&gt;unmanaged&lt;/code&gt; depending on the function&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[Cdecl]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional after &lt;code&gt;managed&lt;/code&gt;.  One or more comma-separated &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices"&gt;&lt;code&gt;System.Runtime.CompilerServices.CallConv*&lt;/code&gt;&lt;/a&gt; values.  Here &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callconvcdecl"&gt;&lt;code&gt;CallConvCdecl&lt;/code&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;args, retval&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Function signature types- arguments followed by return value (same as &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.func-2"&gt;&lt;code&gt;System.Func&lt;/code&gt;&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Comparison
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://medium.com/@jarl.gullberg/an-introduction-to-adl-or-how-to-double-your-native-net-interop-performance-c008e4da54db"&gt;post on medium&lt;/a&gt; compared the performance of different interop approaches a few years ago.  We can re-run the same comparison passing arguments by-value with .NET 5.0 using &lt;a href="https://github.com/dotnet/BenchmarkDotNet"&gt;BenchmarkDotNet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nng_aio_set_output(null, 9, null)&lt;/code&gt; should be a reasonable choice to benchmark native interop performance because &lt;a href="https://github.com/nanomsg/nng/blob/7a0de1b25287f08b73c04d4f9c2834ae265cc382/src/nng.c#L1821"&gt;for values greater than &lt;strong&gt;3&lt;/strong&gt; it immediately returns &lt;code&gt;EINVAL&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The results on a 2019 13" MacBook Pro with Quad-core i5 @ 2.4 GHz, 16 GB RAM, running macOS Big Sur (11.3), and .NET 5.0.101:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;StdDev&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;th&gt;RatioSD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CallDelegate&lt;/td&gt;
&lt;td&gt;16.989 ns&lt;/td&gt;
&lt;td&gt;0.3568 ns&lt;/td&gt;
&lt;td&gt;0.3163 ns&lt;/td&gt;
&lt;td&gt;13.01&lt;/td&gt;
&lt;td&gt;0.74&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CallDllImport&lt;/td&gt;
&lt;td&gt;7.184 ns&lt;/td&gt;
&lt;td&gt;0.1639 ns&lt;/td&gt;
&lt;td&gt;0.1279 ns&lt;/td&gt;
&lt;td&gt;5.49&lt;/td&gt;
&lt;td&gt;0.22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CallInterfaceToDllImport&lt;/td&gt;
&lt;td&gt;10.939 ns&lt;/td&gt;
&lt;td&gt;0.3972 ns&lt;/td&gt;
&lt;td&gt;1.1711 ns&lt;/td&gt;
&lt;td&gt;8.07&lt;/td&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CallFunctionPointer&lt;/td&gt;
&lt;td&gt;6.733 ns&lt;/td&gt;
&lt;td&gt;0.1613 ns&lt;/td&gt;
&lt;td&gt;0.2208 ns&lt;/td&gt;
&lt;td&gt;5.22&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CallManaged&lt;/td&gt;
&lt;td&gt;1.305 ns&lt;/td&gt;
&lt;td&gt;0.0548 ns&lt;/td&gt;
&lt;td&gt;0.0563 ns&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Looking at &lt;em&gt;CallDllImport&lt;/em&gt; and &lt;em&gt;CallFunctionPointer&lt;/em&gt;, function pointers are ~5-10% faster than &lt;code&gt;DllImport&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;CallDelegate&lt;/em&gt; uses &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.getfunctionpointerfordelegate"&gt;Marshal.GetFunctionPointerForDelegate&lt;/a&gt; to call via a managed &lt;code&gt;delegate&lt;/code&gt;.  Similar to before, it's slowest by a sizeable margin.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;CallInterfaceToDllImport&lt;/em&gt; is the way we're currently calling native functions in &lt;a href="https://github.com/jeikabu/nng.NETCore"&gt;nng.NET&lt;/a&gt;; through an &lt;code&gt;interface&lt;/code&gt; to static functions decorated with &lt;code&gt;DllImport&lt;/code&gt;.  Based on these results, leveraging &lt;code&gt;NativeLibrary&lt;/code&gt; and function pointers we can reduce interop overhead by ~33%.  Not too shabby.&lt;/p&gt;

&lt;p&gt;To see what's going on, we can use &lt;a href="https://github.com/icsharpcode/ILSpy"&gt;ILSpy&lt;/a&gt; via the &lt;a href="https://marketplace.visualstudio.com/items?itemName=icsharpcode.ilspy-vscode"&gt;VSCode plugin&lt;/a&gt; to decompile the assembly.  Open the &lt;em&gt;Command Palette&lt;/em&gt; (&lt;code&gt;CMD+SHIFT+E&lt;/code&gt;), run "ILSpy: Decompile IL Assembly (pick file)", and select the desired assembly/*.dll.  In the &lt;em&gt;Explorer View&lt;/em&gt; (&lt;code&gt;CMD+SHIFT+E&lt;/code&gt;), browse to &lt;strong&gt;ILSPY DECOMPILED MEMBERS &amp;gt; benchmarks &amp;gt; Pointer &amp;gt; CallFunctionPointer&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    IL_0000: ldarg.0
    IL_0001: ldfld method int32 *(native int, uint32, native int) benchmarks.Pointer::nng_aio_set_output /* 04000003 */
    IL_0006: stloc.1
    IL_0007: ldsfld native int [System.Runtime]System.IntPtr::Zero /* 0A000015 */
    IL_000c: ldc.i4.s 9
    IL_000e: ldsfld native int [System.Runtime]System.IntPtr::Zero /* 0A000015 */
    IL_0013: ldloc.1
    IL_0014: calli unmanaged cdecl int32(native int, uint32, native int) /* 11000004 */
    IL_0019: stloc.0
    IL_001a: ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the decompiled source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;CallFunctionPointer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;intPtr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nng_aio_set_output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cdecl&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;&lt;span class="n"&gt;intPtr&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, for whatever reason it turns our function pointer into an &lt;code&gt;IntPtr&lt;/code&gt; and then casts it to &lt;code&gt;cdecl&lt;/code&gt; (which must be an internal symbol because we can't use it directly).  But the key thing is the use of &lt;code&gt;calli&lt;/code&gt;.  Compare that with &lt;strong&gt;benchmarks &amp;gt; DllImport &amp;gt; CallDllImport&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    IL_0000: ldsfld valuetype [nng.NET.Shared]nng.Native.nng_aio [nng.NET.Shared]nng.Native.nng_aio::Null /* 0A000014 */
    IL_0005: ldc.i4.s 9
    IL_0007: ldsfld native int [System.Runtime]System.IntPtr::Zero /* 0A000015 */
    IL_000c: call int32 [nng.NET]nng.Native.Aio.UnsafeNativeMethods::nng_aio_set_output(valuetype [nng.NET.Shared]nng.Native.nng_aio, uint32, native int) /* 0A00001C */
    IL_0011: stloc.0
    IL_0012: ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DllImport&lt;/code&gt; uses &lt;code&gt;call&lt;/code&gt;.  Based on the docs for &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.call"&gt;call&lt;/a&gt; and &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.calli"&gt;calli&lt;/a&gt; opcodes, &lt;code&gt;call&lt;/code&gt; uses a "method descriptor" that is a "metadata token" (and works with virtual functions) while &lt;code&gt;calli&lt;/code&gt; is just "a pointer to an entry point"- and apparently faster.&lt;/p&gt;

&lt;p&gt;Additional reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/core/dependency-loading/understanding-assemblyloadcontext"&gt;Understanding System.Runtime.Loader.AssemblyLoadContext&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/core/dependency-loading/loading-unmanaged"&gt;Unmanaged (native) library loading algorithm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>performance</category>
    </item>
    <item>
      <title>3D Printing a Custom Device Case</title>
      <dc:creator>jeikabu</dc:creator>
      <pubDate>Fri, 30 Apr 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/jeikabu/3d-printing-a-custom-device-case-2ngi</link>
      <guid>https://dev.to/jeikabu/3d-printing-a-custom-device-case-2ngi</guid>
      <description>&lt;p&gt;I got a 3D printer around 2 years ago and other than the occasional dodad, it hasn’t been used for the intended purpose of printing custom cases for projects (like my &lt;a href="https://rendered-obsolete.github.io/2019/12/02/iot_solar.html" rel="noopener noreferrer"&gt;solar-powered USB hardware&lt;/a&gt;). I finally got around to investigating this because I need a case for my &lt;a href="https://rendered-obsolete.github.io/2021/04/13/capa13r.html" rel="noopener noreferrer"&gt;CAPA13R mini-PC&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There’s already tons of resources on the web to help you pick a 3D printer, explain the different printing filament materials, tutorials on modeling and slicing, and so on. I’m just going to cover some tips to start making things with fairly precise constraints (but no moving parts).&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating 3D Models
&lt;/h2&gt;

&lt;p&gt;Despite being in the game industry 15 years and working along-side people who use DCC tools like &lt;a href="https://www.autodesk.com/products/3ds-max/overview" rel="noopener noreferrer"&gt;3ds Max&lt;/a&gt;, &lt;a href="https://www.autodesk.com/products/maya/overview" rel="noopener noreferrer"&gt;Maya&lt;/a&gt;, and &lt;a href="http://pixologic.com/features/about-zbrush.php" rel="noopener noreferrer"&gt;ZBrush&lt;/a&gt; everyday, I possess no 3D-modeling skills (other than briefly using &lt;a href="https://en.wikipedia.org/wiki/Milkshape_3D" rel="noopener noreferrer"&gt;MilkShape 3D&lt;/a&gt; in uni). That gap needs to be closed first.&lt;/p&gt;

&lt;p&gt;I decided on the following 3D modeling software requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Precise dimensions to achieve a good fit&lt;/li&gt;
&lt;li&gt;Free (preferably FOSS)&lt;/li&gt;
&lt;li&gt;Offline (not web/cloud-based)&lt;/li&gt;
&lt;li&gt;Multi-platform (at least Mac and Windows)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The parametric modeler &lt;a href="https://www.freecadweb.org/" rel="noopener noreferrer"&gt;FreeCAD&lt;/a&gt; meets all these requirements so I’ve been using that, but there’s other options that might work just as well. FreeCAD is a non-trivial program so I sought out some tutorials. There’s an abundance of material online, &lt;a href="https://www.youtube.com/channel/UCWEX2NVlLeIQr3v-cIF9LxA" rel="noopener noreferrer"&gt;“DrVax”&lt;/a&gt; has a few videos on FreeCAD specific to 3D printing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.blender.org/" rel="noopener noreferrer"&gt;Blender&lt;/a&gt; might also be an option and better suited to general 3D modeling, but I didn’t investigate how easy it is to use for CAD design. &lt;a href="https://openscad.org/" rel="noopener noreferrer"&gt;OpenSCAD&lt;/a&gt; looks particularly interesting.&lt;/p&gt;

&lt;p&gt;The basic process to 3D print any object is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Measure the various dimensions for the 3D object&lt;/li&gt;
&lt;li&gt;Create 3D model and export as STL (or another format supported by slicer)&lt;/li&gt;
&lt;li&gt;Use slicer software to generate g-code for 3D printer&lt;/li&gt;
&lt;li&gt;Print&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’m using the slicer developed for my printer, but you can search for what’s recommended for your printer or to get more advanced features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modeling/FreeCAD
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Export a simple object from the modeler and import into your slicer to figure out the position/orientation of the print bed relative to your modeling (probably XY-plane and +Z axis). You can always move things around, but this makes it easier to think about.&lt;/li&gt;
&lt;li&gt;Start with a simple object and work on increasingly complex objects to get a handle on the modeling software, slicer, and 3D printer.&lt;/li&gt;
&lt;li&gt;Use a meaningful reference point (e.g. the corner of a real-world physical object) as the origin from which to measure and model. This keeps dimensions/orientation simple and avoids additive in-accuracy/imprecision.&lt;/li&gt;
&lt;li&gt;In FreeCAD, &lt;code&gt;Ctrl+Shift&lt;/code&gt; rotates and &lt;code&gt;Ctrl+Cmd&lt;/code&gt; pans the view with the left button (i.e. when using a trackpad).&lt;/li&gt;
&lt;li&gt;Model in a natural, forward order as if working with clay; model and validate major/important features first and leave details and flourishes (i.e. fillet and chamfer) until the end. This minimises re-work when adjustments need to be made.&lt;/li&gt;
&lt;li&gt;FreeCAD’s model view is like snapshots back in time with pointers into the past as you make changes. Don’t alter the space-time continuum lest you create dangling pointers! At least until you learn to work with it.&lt;/li&gt;
&lt;li&gt;Keep it data-driven (FreeCAD’s spreadsheet workbench and cell aliases); &lt;code&gt;=board_width + X&lt;/code&gt; is more meaningful than &lt;code&gt;15.2&lt;/code&gt; and makes it easier to tweak related dimensions.&lt;/li&gt;
&lt;li&gt;To align geometry where there’s no useful reference, create a line to snap things to and toggle construction mode: &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Ffreecad_construction_mode.png"&gt;
&lt;/li&gt;
&lt;li&gt;Rename components (i.e. sketches/pads/pockets) so as the model becomes more complex you can find things.&lt;/li&gt;
&lt;li&gt;Superfluous air-vents or similar details help reduce the amount of material, print time, and overall weight:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Ffreecad_air_vents.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Ffreecad_air_vents.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consider dimensions where tolerances are permitted, note where wires/connectors go, account for how an object would be inserted into the print and leave extra room. Similarly, pad accordingly (e.g. &lt;em&gt;away&lt;/em&gt; from measured dimensions).&lt;/li&gt;
&lt;li&gt;Model the bare-minimum (reduced pad dimension in FreeCAD) and print a prototype to test fitting.&lt;/li&gt;
&lt;li&gt;If you’re going to print multiple pieces separately, create them as multiple bodies in FreeCAD and let the sketcher make clones or use shape binder to create relationships: &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Ffreecad_shape_binder.png"&gt;
&lt;/li&gt;
&lt;li&gt;Exaggerate features that you think only need to be a few millimetres. They’re more likely to print correctly, and will be less fragile.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Slicing and Printing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Start with PLA filament as it’s generally the most forgiving and easiest to work with (and often cheapest).&lt;/li&gt;
&lt;li&gt;Prototype with a “junk” color you’re unlikely to use for finished products to conserve more useful filaments.&lt;/li&gt;
&lt;li&gt;Use slicer low-quality settings to speed up prototyping.&lt;/li&gt;
&lt;li&gt;Make sure to check what the slicer is generating- hollow walls use far less material:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fslicer_hollow_walls.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fslicer_hollow_walls.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Since models print from the bottom upwards, you may need supports for overhangs. Most slicers can add these, if that’s not workable you can build them into the model.&lt;/li&gt;
&lt;li&gt;Consider whether supports will be easy to remove without damaging the model. Print a small section if you’re not sure.&lt;/li&gt;
&lt;li&gt;I tend to &lt;em&gt;set-and-forget&lt;/em&gt; models I’ve downloaded, but you’ll want to keep a closer eye on “unproven” models to ensure the printer doesn’t waste a lot of filament making a mess.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If warping occurs, try increasing the platform temperature and/or have the slicer generate a “raft” or “brim” (also see &lt;a href="https://all3dp.com/2/3d-print-warping-what-it-is-how-to-fix-it/" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://support.ultimaker.com/hc/en-us/articles/360012113239-How-to-fix-warping" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Print near an open window or in a well-ventilated area. Even if non-toxic, the smell of molten plastic leaves a lot to be desired.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a thin, flat object with a sharp edge (like a paint scraper or knife) to help remove objects from the print bed after everything cools. There’s &lt;a href="https://3dprinterly.com/how-to-fix-3d-prints-sticking-too-well-to-print-bed/" rel="noopener noreferrer"&gt;numerous other tricks&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you need to print multiple objects with the same material, use the slicer to layout the various models (or duplicates) and do as many as possible in one printing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you’re going to print a number of different objects, plan ahead to batch the same material together to minimize the number of times you have to switch filaments (taking into account if your printer has multiple extruders/spools).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Optional (But Often Useful)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Dremel/sand-paper/file(s), thin wire-cutters/X-acto knife, needle-nose pliers, etc.: to help remove supports and weird edges, or make minor alterations to potentially salvage an object with trivial flaws. This might not have the best results, YMMV.&lt;/li&gt;
&lt;li&gt;Calipers (digital or analog): makes it easy to take precise measurements for a snug fit or that would be awkward with a ruler:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Small round objects&lt;/th&gt;
&lt;th&gt;Precise distances with obstructions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fcaliper_dc_plug.png"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fcaliper_board.png"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Check &lt;a href="https://www.thingiverse.com/" rel="noopener noreferrer"&gt;thingiverse&lt;/a&gt; for “universal” spool holders or other accessories that might help you wrangle filament.&lt;/li&gt;
&lt;li&gt;Trashcan: &lt;em&gt;Seriously&lt;/em&gt;. Little whisps of plastic, supports, rafts, prototypes, botched prints, etc. quickly litter your work area.&lt;/li&gt;
&lt;li&gt;Small hand-held vacuum cleaner and/or something like an anti-static mat to make cleanup a breeze.&lt;/li&gt;
&lt;li&gt;Mouse: most modeling software and slicers make good use of a third button.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Product
&lt;/h2&gt;

&lt;p&gt;I’m pretty happy with the end results. The CAPA13R board and power supply rest securely in the case, and it looks decent enough to sit in my living room.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Front&lt;/th&gt;
&lt;th&gt;Back&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fcapa13r_case_front.png"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fcapa13r_case_back.png"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Contemplating a second printing to correct a few minor gripes. I printed the top with a brim instead of a raft and the right side warped a tiny bit and is a little ill-fitting, so the top doesn’t snap on quite as well as I envisioned. Also, I printed the top upside down to avoid using supports, but the surface towards the platform/raft doesn’t look as nice.&lt;/p&gt;

</description>
      <category>3dprinting</category>
      <category>showdev</category>
      <category>freecad</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>Rust in Android OS and Linux</title>
      <dc:creator>jeikabu</dc:creator>
      <pubDate>Thu, 22 Apr 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/jeikabu/rust-in-android-os-and-linux-59ae</link>
      <guid>https://dev.to/jeikabu/rust-in-android-os-and-linux-59ae</guid>
      <description>&lt;p&gt;Google had two recent announcements of particular interest to Rust enthusiasts: ongoing efforts to introduce Rust into &lt;a href="https://security.googleblog.com/2021/04/rust-in-android-platform.html"&gt;Android OS&lt;/a&gt; and the &lt;a href="https://security.googleblog.com/2021/04/rust-in-linux-kernel.html"&gt;Linux kernel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While a number of memory-safe languages like Java/Kotlin/go/et al. are used in user-space, the majority of OSes are written in C/C++ where incorrect memory usage is still a source of “high severity” bugs. The idea is that using Rust would avoid such classes of bugs and provide other benefits.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://security.googleblog.com/2021/04/rust-in-linux-kernel.html"&gt;Linux article&lt;/a&gt; has a few specific examples for a simple character device, ioctl handling, and synchronisation primitives. They illustrate some of Rust’s advantages: immutability, static typing, lifetime checks, bounds checks, &lt;a href="https://en.cppreference.com/w/cpp/language/raii"&gt;RAII&lt;/a&gt;, required initialisation and error-handling, and so on. Most achieved at compile-time rather than run-time.&lt;/p&gt;

&lt;p&gt;Why not bring C++ to Linux? &lt;a href="https://itwire.com/open-source/rust-support-in-linux-may-be-possible-by-5-14-release-torvalds.html"&gt;Linus Torvalds had this to say&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;LOL. C++ solves &lt;em&gt;none&lt;/em&gt; of the C issues, and only makes things worse. It really is a crap language.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>rust</category>
      <category>news</category>
      <category>linux</category>
      <category>android</category>
    </item>
    <item>
      <title>Building an AMD Ryzen APU (mini) Game Machine</title>
      <dc:creator>jeikabu</dc:creator>
      <pubDate>Tue, 13 Apr 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/jeikabu/building-an-amd-ryzen-apu-mini-game-machine-3i1e</link>
      <guid>https://dev.to/jeikabu/building-an-amd-ryzen-apu-mini-game-machine-3i1e</guid>
      <description>&lt;p&gt;Between my love of game consoles and working on &lt;a href="https://rendered-obsolete.github.io/2018/10/15/zplus-microservices.html" rel="noopener noreferrer"&gt;Z+ with AMD&lt;/a&gt; I perhaps have an unhealthy obsession with AMD APUs. When AxiomTek announced (&lt;a href="https://www.techradar.com/news/raspberry-pi-not-powerful-enough-for-you-this-compact-board-boasts-an-amd-ryzen-apu" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://techbriefly.com/2020/05/20/the-more-powerful-raspberry-pi-alternative-carries-an-amd-ryzen-apu/" rel="noopener noreferrer"&gt;2&lt;/a&gt;) a single-board-computer with Zen CPU cores and Vega graphics I was… intrigued, and thought it might make for a nice &lt;a href="https://en.wikipedia.org/wiki/Steam_Machine_(hardware_platform)" rel="noopener noreferrer"&gt;Steam Box&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware
&lt;/h2&gt;

&lt;p&gt;Details on the CAPA13R board are available from the &lt;a href="https://www.axiomtek.com/Default.aspx?MenuId=Products&amp;amp;FunctionId=ProductView&amp;amp;ItemId=25592&amp;amp;C=CAPA13R" rel="noopener noreferrer"&gt;product page&lt;/a&gt; and more detailed &lt;a href="https://www.axiomtek.com/Download/Spec/en-US/capa13r.pdf" rel="noopener noreferrer"&gt;spec sheet&lt;/a&gt;. Common features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AMD Ryzen Embedded V1605B or V1807B APU&lt;/li&gt;
&lt;li&gt;DDR4 SO-DIMM (up to 16GB of memory)&lt;/li&gt;
&lt;li&gt;1 x M.2 Key B (for SSD), 1 x M.2 Key E&lt;/li&gt;
&lt;li&gt;2 x 10/100/1000 Mbps Ethernet&lt;/li&gt;
&lt;li&gt;2 x USB 3.2 Gen2&lt;/li&gt;
&lt;li&gt;2 x HDMI, 1 x &lt;a href="https://en.wikipedia.org/wiki/DisplayPort#DisplayPort_dual-mode_(DP++)" rel="noopener noreferrer"&gt;DisplayPort++&lt;/a&gt;, 1 x &lt;a href="https://en.wikipedia.org/wiki/Low-voltage_differential_signaling" rel="noopener noreferrer"&gt;LVDS&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notably, it lacks wifi and bluetooth. The wifi I can do without (the board requires external power so an ethernet cable is fine), but I added a USB bluetooth adapter I had laying around.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;V1605B&lt;/th&gt;
&lt;th&gt;V1807B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;DDR4-2400&lt;/td&gt;
&lt;td&gt;DDR4-3200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Power min. RMS (W)&lt;/td&gt;
&lt;td&gt;12V @ 2.5A&lt;/td&gt;
&lt;td&gt;12V @ 4.2A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Details on the AMD SoC are available from the &lt;a href="https://www.amd.com/en/products/embedded-ryzen-v1000-series" rel="noopener noreferrer"&gt;Ryzen Embedded V1000 series&lt;/a&gt; product page and the &lt;a href="https://www.amd.com/system/files/documents/v1000-family-product-brief.pdf" rel="noopener noreferrer"&gt;product brief&lt;/a&gt;. All models have 4 cores (8 threads, 4M L2 cache) &lt;a href="https://en.wikipedia.org/wiki/Zen_(first_generation_microarchitecture)" rel="noopener noreferrer"&gt;Zen micro-architecture&lt;/a&gt; CPU married with a Radeon Vega 8 GPU (&lt;a href="https://en.wikipedia.org/wiki/Graphics_Core_Next#Graphics_Core_Next_5" rel="noopener noreferrer"&gt;GCN5&lt;/a&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;V1605B&lt;/th&gt;
&lt;th&gt;V1807B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU base/max GHz&lt;/td&gt;
&lt;td&gt;2.0 / 3.6&lt;/td&gt;
&lt;td&gt;3.35 / 3.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU &lt;a href="https://en.wikipedia.org/wiki/Graphics_Core_Next#Compute_units" rel="noopener noreferrer"&gt;CU&lt;/a&gt;/GHz/TFLOPS&lt;/td&gt;
&lt;td&gt;8 / 1.1 / 1.1&lt;/td&gt;
&lt;td&gt;11 / 1.3 / 1.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://en.wikipedia.org/wiki/Thermal_design_power" rel="noopener noreferrer"&gt;TDP&lt;/a&gt; range/nominal (W)&lt;/td&gt;
&lt;td&gt;12-25 / 15&lt;/td&gt;
&lt;td&gt;35-54 / 45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DDR4 (MT/s / GB/s)&lt;/td&gt;
&lt;td&gt;2400 / 19.2&lt;/td&gt;
&lt;td&gt;3200 / 25.6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Like &lt;a href="https://rendered-obsolete.github.io/2019/04/01/jetson_nano.html" rel="noopener noreferrer"&gt;NVidia’s Jetson Nano&lt;/a&gt;, the memory bandwidth is rather low. For comparison, the &lt;a href="https://en.wikipedia.org/wiki/Xbox_One#Hardware_comparison" rel="noopener noreferrer"&gt;original Xbox One/Xbox One S&lt;/a&gt; has 8 Jaguar cores (pre-Zen, 8 threads), 12 CUs (&lt;a href="https://en.wikipedia.org/wiki/Graphics_Core_Next#Graphics_Core_Next_2" rel="noopener noreferrer"&gt;GCN2&lt;/a&gt;, 1.3/1.4 TFLOPS), and DD3 at 68.3 GB/s. It’s difficult to make an “apples to apples” comparison because Zen has 52% improved IPC, GCN 2 and 5 are fairly distant, and the Xbox has an additional 32 MB of ESRAM at 200+ GB/s (generally used for render targets). But, the V1605V and V1807B are “in the ballpark” of the original Xbox and Xbox S, respectively- memory bandwidth aside.&lt;/p&gt;

&lt;p&gt;I picked up a lower-end CAPA13R with the V1605B and added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transcend 240GB SATA III 6Gb / s MTS420S 42mm M.2 SSD&lt;/li&gt;
&lt;li&gt;HyperX Impact DDR4 16GB, 2666MHz CL15 SODIMM XMP - HX426S15IB2 / 16&lt;/li&gt;
&lt;li&gt;PicoPSU-60 12V DC-DC ATX Mini-ITX 0-60W power supply power&lt;/li&gt;
&lt;li&gt;Salcar 60 W Transformer Power Adapter (12 V 6 A, 5.5x2.5mm)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Top&lt;/th&gt;
&lt;th&gt;Bottom&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fcapa13r.jpg"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fcapa13r_bottom.jpg"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Until &lt;a href="https://en.wikipedia.org/wiki/ATX#ATX12VO" rel="noopener noreferrer"&gt;ATX12VO&lt;/a&gt; (also see &lt;a href="https://www.gamersnexus.net/guides/3568-intel-atx-12vo-spec-explained-what-manufacturers-think" rel="noopener noreferrer"&gt;this guide&lt;/a&gt;) becomes more of a thing, I’m using a 60W PicoPSU with pins 13 and 14 (OR pin 14- &lt;code&gt;PS-ON&lt;/code&gt;- to one of the other GNDs) shorted to power the system. Depending on your power supply of choice, another approach may be necessary.&lt;/p&gt;

&lt;p&gt;It’s worth mentioning &lt;strong&gt;SSW1&lt;/strong&gt; , the small switch to the far left of the external ports. With 1-2 closed (switch &lt;em&gt;away&lt;/em&gt; from the external ports) auto power on is disabled and the soft power button is needed to power on the system. With 2-3 closed (switch &lt;em&gt;towards&lt;/em&gt; external ports) power on is enabled and just plugging the system in will auto power on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarks
&lt;/h2&gt;

&lt;p&gt;Installed Windows 10 20H2/19042.631 and the &lt;a href="https://www.axiomtek.com/Default.aspx?MenuId=Products&amp;amp;FunctionId=ProductView&amp;amp;ItemId=25592&amp;amp;C=CAPA13R&amp;amp;upcat=270" rel="noopener noreferrer"&gt;drivers&lt;/a&gt; to get audio and graphics working. Otherwise stuck with the stock install and ran a few benchmarks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Settings&lt;/th&gt;
&lt;th&gt;Score Min/Avg/Max FPS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unigine: Heaven 4.0&lt;/td&gt;
&lt;td&gt;4K/OpenGL&lt;/td&gt;
&lt;td&gt;45 1.1 1.8 4.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unigine: Heaven 4.0&lt;/td&gt;
&lt;td&gt;1920x1200/OpenGL&lt;/td&gt;
&lt;td&gt;252 4.5 10.0 24.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unigine: Heaven 4.0&lt;/td&gt;
&lt;td&gt;1080p/OpenGL&lt;/td&gt;
&lt;td&gt;317 5.1 12.6 35.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unigine: Superposition v1.1&lt;/td&gt;
&lt;td&gt;1080p med/OpenGL&lt;/td&gt;
&lt;td&gt;857 5.72 6.41 7.89&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unigine: Valley 1.0&lt;/td&gt;
&lt;td&gt;1920x1200/DX11&lt;/td&gt;
&lt;td&gt;596 9.7 14.2 24.1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Settings&lt;/th&gt;
&lt;th&gt;Score Graphics CPU&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3DMark Demo Time Spy v1.2&lt;/td&gt;
&lt;td&gt;4K/64-bit&lt;/td&gt;
&lt;td&gt;595 529 2085&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3DMark Demo Time Spy v1.2&lt;/td&gt;
&lt;td&gt;1920x1200/64-bit&lt;/td&gt;
&lt;td&gt;641 571 2170&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So, not cut out for 4K gaming. However, during none of the benchmarks did the heatsink even become warm, and even the 1080p performance isn’t very good. I wondered if either the PicoPSU or power adapter just wasn’t delivering enough power, but the GPU seemed to be fully engaged:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fcapa13r_task_manager_gpu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fcapa13r_task_manager_gpu.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What stands out is the GPU has only 1 GB of dedicated memory. This was a limiting factor for us with the &lt;a href="https://github.com/subor/sdk/blob/master/docs/topics/optimization.md" rel="noopener noreferrer"&gt;Z+ console&lt;/a&gt; (which had a carve-out of 2GB for dedicated graphics memory leaving 6 GB of system memory). In our case, we had a BIOS option to specify the amount reserved for VRAM, but the CAPA13R BIOS currently has no such option.&lt;/p&gt;

&lt;p&gt;In hind-sight I probably should have gone with the V1807V since the GPU is ~60% more powerful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Software
&lt;/h2&gt;

&lt;p&gt;The default boot prompt timeout is 1s, so you’ll have to be quick to change BIOS settings. Press &lt;code&gt;esc&lt;/code&gt;, set &lt;strong&gt;Advanced &amp;gt; Smart Fan Function&lt;/strong&gt; to &lt;strong&gt;Enabled&lt;/strong&gt;. This allows the fan speed to adjust based on the SoC temperature and makes the system much quieter.&lt;/p&gt;

&lt;p&gt;Optionally, set &lt;strong&gt;Boot &amp;gt; Quiet Boot&lt;/strong&gt; to &lt;strong&gt;Enabled&lt;/strong&gt; to suppress the BIOS startup screen. However this replaces the Windows 10 logo with the American Megatrends logo- which doesn’t look great. Changing the UEFI logo seems to require a tool like “HackBGRT”, but I decided to not bother because the boot prompt timeout is short enough that my TV often isn’t even on when the BIOS startup is displayed.&lt;/p&gt;

&lt;p&gt;Have Steam auto-start in &lt;a href="https://support.steampowered.com/kb_article.php" rel="noopener noreferrer"&gt;Big Picture Mode&lt;/a&gt;, via &lt;strong&gt;Steam &amp;gt; Settings&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fsteam_settings_bpm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fsteam_settings_bpm.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Configure &lt;a href="https://superuser.com/questions/1623508" rel="noopener noreferrer"&gt;users to auto-login&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Change registry: 

&lt;ol&gt;
&lt;li&gt;Enter &lt;code&gt;Win+R&lt;/code&gt; and type &lt;code&gt;regedit&lt;/code&gt; OR hit &lt;code&gt;Win&lt;/code&gt; key and start typing &lt;code&gt;registry editor&lt;/code&gt;, then hit &lt;code&gt;enter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Browse to &lt;strong&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PasswordLess\Device&lt;/strong&gt; change &lt;strong&gt;DevicePasswordLessBuildVersion&lt;/strong&gt; to &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Enter &lt;code&gt;Win+R&lt;/code&gt; and type &lt;code&gt;netplwiz&lt;/code&gt;, then hit &lt;code&gt;enter&lt;/code&gt; OR click &lt;strong&gt;OK&lt;/strong&gt;
&lt;/li&gt;

&lt;li&gt;Uncheck &lt;strong&gt;Users must enter a user name and password to use this computer&lt;/strong&gt; and click &lt;strong&gt;OK&lt;/strong&gt;
&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;Now when you restart the system it should login and launch straight into Steam BPM:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fsteam_bpm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fsteam_bpm.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With an added USB bluetooth adapter, pair with a Dualshock 4 controller (or your preferred gamepad):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In Windows &lt;strong&gt;Settings &amp;gt; Bluetooth &amp;amp; other devices&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add Bluetooth or other device &amp;gt; Bluetooth&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;On DS4 press SHARE and PS/home button at the same time and hold until controller LED starts flashing&lt;/li&gt;
&lt;li&gt;Should appear as “Wireless Controller”, click it to pair&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To only see games with &lt;em&gt;full&lt;/em&gt; gamepad support:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Steam &lt;strong&gt;Library&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Move to the far right to find &lt;strong&gt;Filter Games&lt;/strong&gt; and check &lt;strong&gt;Controller Supported&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Optional but useful, there’s a number of wireless keyboards with integrated touchpad on the market:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fcapa13r_wireless_keyboard.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frendered-obsolete.github.io%2Fassets%2Fcapa13r_wireless_keyboard.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To invert the two-finger scroll gesture, try using registry editor to change &lt;code&gt;ScrollDirection&lt;/code&gt; in &lt;code&gt;HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\PrecisionTouchPad&lt;/code&gt;. If that doesn’t work, try setting &lt;code&gt;FlipFlopWheel&lt;/code&gt; as per &lt;a href="https://superuser.com/questions/948348" rel="noopener noreferrer"&gt;this answer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To enable/disable edge gestures, use &lt;code&gt;EnableEdgy&lt;/code&gt; in &lt;code&gt;PrecisionTouchPad&lt;/code&gt; registry, or follow &lt;a href="https://www.tenforums.com/tutorials/48507-enable-disable-edge-swipe-screen-windows-10-a.html" rel="noopener noreferrer"&gt;this forum post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Need to find some kind of case. Haven’t decided if I’ll try to buy or 3D print one.&lt;/p&gt;

&lt;p&gt;Haven’t been able to get it waking from USB. I’ve tried disabling “Allow the computer to turn off this device to save power” and enabling “Allow this device to wake the computer” for everything related to USB and HID devices in Device Manager, as well as disabling “USB selective suspend” in advanced Power Settings. In the BIOS the only options for &lt;strong&gt;Advanced &amp;gt; ACPI Settings &amp;gt; ACPI Sleep State&lt;/strong&gt; are “Suspend Disabled” and &lt;a href="https://docs.microsoft.com/en-us/windows/win32/power/system-power-states" rel="noopener noreferrer"&gt;“S3”&lt;/a&gt;, but there doesn’t seem to be anything related to USB wake.&lt;/p&gt;

&lt;p&gt;Maybe I’ll look into running some other software like &lt;a href="https://www.retroarch.com/" rel="noopener noreferrer"&gt;RetroArch&lt;/a&gt;, or &lt;a href="https://github.com/xbmc/xbmc" rel="noopener noreferrer"&gt;Kodi&lt;/a&gt; (formerly XBMC) later.&lt;/p&gt;

&lt;p&gt;Most importantly, play some games!&lt;/p&gt;

</description>
      <category>windows10</category>
      <category>gamedev</category>
      <category>showdev</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>How do YOU moderate dev.to?</title>
      <dc:creator>jeikabu</dc:creator>
      <pubDate>Thu, 08 Apr 2021 07:39:31 +0000</pubDate>
      <link>https://dev.to/jeikabu/how-do-you-moderate-dev-to-3a22</link>
      <guid>https://dev.to/jeikabu/how-do-you-moderate-dev-to-3a22</guid>
      <description>&lt;p&gt;Dev.to has &lt;a href="https://dev.to/community-moderation"&gt;"trusted users"&lt;/a&gt; that can moderate posts/comments.&lt;/p&gt;

&lt;p&gt;If you've got this, how have you used it, or when do you &lt;em&gt;not&lt;/em&gt; use it?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>meta</category>
      <category>moderator</category>
    </item>
    <item>
      <title>Early Raspberry Pi Pico and Rust</title>
      <dc:creator>jeikabu</dc:creator>
      <pubDate>Sun, 04 Apr 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/jeikabu/early-raspberry-pi-pico-and-rust-4c2a</link>
      <guid>https://dev.to/jeikabu/early-raspberry-pi-pico-and-rust-4c2a</guid>
      <description>&lt;p&gt;The &lt;a href="https://www.raspberrypi.org/products/raspberry-pi-pico/"&gt;Raspberry Pi Pico&lt;/a&gt; announced earlier this year is a $4 &lt;a href="https://en.wikipedia.org/wiki/Microcontroller"&gt;microcontroller&lt;/a&gt; board from the makers of the ever-popular Raspberry Pi. As a Rust-enthusiast one of first questions I tend to investigate is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.google.com/search?q=can+it+run+crysis"&gt;&lt;em&gt;Can it run Rust?&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; Yes, but it’s not quite ready for prime-time. &lt;em&gt;Yet&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware
&lt;/h2&gt;

&lt;p&gt;The Pico utilizes the (also new) RP2040 microcontroller. Compared to the already minimalist Pi Zero, it has even more modest &lt;a href="https://www.raspberrypi.org/documentation/rp2040/getting-started/#board-specifications"&gt;specs&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dual-core Arm Cortex M0+ processor at up to 133 MHz&lt;/li&gt;
&lt;li&gt;264KB of SRAM, and 2MB of on-board Flash memory&lt;/li&gt;
&lt;li&gt;USB 1.1 with device and host support&lt;/li&gt;
&lt;li&gt;26 × multi-function GPIO pins&lt;/li&gt;
&lt;li&gt;2 × SPI, 2 × I2C, 2 × UART, 3 × 12-bit ADC, 16 × controllable PWM channels&lt;/li&gt;
&lt;li&gt;Temperature sensor&lt;/li&gt;
&lt;li&gt;Accelerated floating-point libraries on-chip&lt;/li&gt;
&lt;li&gt;8 × Programmable I/O (PIO) state machines for custom peripheral support&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rust Demo
&lt;/h2&gt;

&lt;p&gt;First, download and install &lt;a href="https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads"&gt;ARM toolchain for the host system&lt;/a&gt;. If necessary, add it to &lt;code&gt;PATH&lt;/code&gt; environment variable. For example, if you install the &lt;code&gt;.pkg&lt;/code&gt; on a Mac you’ll need to add &lt;code&gt;/Applications/ARM/bin/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, setup rest of build environment and build a simple demo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add support for ARM Cortex M0+&lt;/span&gt;
rustup target add thumbv6m-none-eabi

&lt;span class="c"&gt;# Build pico-sdk for elf2uf2 binary&lt;/span&gt;
git clone https://github.com/raspberrypi/pico-sdk &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 &lt;span class="nt"&gt;--recursive&lt;/span&gt; &lt;span class="nt"&gt;--single-branch&lt;/span&gt;
&lt;span class="nb"&gt;pushd &lt;/span&gt;pico-sdk &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir &lt;/span&gt;build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;build
cmake .. &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make
&lt;span class="c"&gt;# Output: build/elf2uf2/elf2uf2 (executable)&lt;/span&gt;
&lt;span class="nb"&gt;popd&lt;/span&gt;

&lt;span class="c"&gt;# Build sample program and convert output from elf to uf2&lt;/span&gt;
git clone https://github.com/rp-rs/pico-blink-rs
&lt;span class="nb"&gt;cd &lt;/span&gt;pico-blink-rs
cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;
../pico-sdk/build/elf2uf2/elf2uf2 target/thumbv6m-none-eabi/release/pico-blink-rs pico-blink-rs.uf2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/rp-rs/rp2040-pac"&gt;rp2040_pac&lt;/a&gt; seems to have changed since the example was written, so &lt;code&gt;cargo build&lt;/code&gt; fails with “error[E0609]: no field &lt;code&gt;gpio25&lt;/code&gt; on type &lt;code&gt;PADS_BANK0&lt;/code&gt;”. In &lt;code&gt;main.rs&lt;/code&gt; change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;     // Configure pin 25 for GPIO
&lt;span class="gd"&gt;- p.PADS_BANK0.gpio25.write(|w| {
&lt;/span&gt;&lt;span class="gi"&gt;+ p.PADS_BANK0.gpio[25].write(|w| {
&lt;/span&gt;         // Output Disable off
         w.od().clear_bit();
         // Input Enable on
         w.ie().set_bit();
         w
     });
&lt;span class="gd"&gt;- p.IO_BANK0.gpio25_ctrl.write(|w| {
&lt;/span&gt;&lt;span class="gi"&gt;+ p.IO_BANK0.gpio[25].gpio_ctrl.write(|w| {
&lt;/span&gt;         // Map pin 25 to SIO
&lt;span class="gd"&gt;- w.funcsel().sio_25();
&lt;/span&gt;&lt;span class="gi"&gt;+ w.funcsel().sio_0();
&lt;/span&gt;         w
     });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, flash the uf2 (like with &lt;a href="https://www.raspberrypi.org/documentation/rp2040/getting-started/#getting-started-with-c"&gt;MicroPython or C/C++&lt;/a&gt;):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Push and hold the &lt;strong&gt;BOOTSEL&lt;/strong&gt; button on the Pico and plug into host USB port 

&lt;ul&gt;
&lt;li&gt;Pico should mount as Mass Storage Device “RPI-RPI2” on host&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Copy UF2 file to “RPI-RP2” volume&lt;/li&gt;
&lt;li&gt;Pico will reboot and run program&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Coming Soon
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/rp-rs/pico-blink-rs/blob/develop/src/main.rs"&gt;&lt;code&gt;main.rs&lt;/code&gt;&lt;/a&gt; is pretty… low-level. To address this, &lt;a href="https://github.com/rp-rs/rp-hal"&gt;rp-hal&lt;/a&gt; is in progress to implement the &lt;a href="https://github.com/rust-embedded/embedded-hal"&gt;Rust Embedded HAL&lt;/a&gt;. Once that is complete, the same example might be written like a &lt;a href="https://github.com/atsamd-rs/atsamd/blob/master/boards/feather_m0/examples/blinky_basic.rs"&gt;similar example&lt;/a&gt; for the &lt;a href="https://www.adafruit.com/product/3505"&gt;Adafruit METRO M0&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[entry]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;peripherals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Peripherals&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CorePeripherals&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;clocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;GenericClockController&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with_external_32kosc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;peripherals&lt;/span&gt;&lt;span class="py"&gt;.GCLK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;peripherals&lt;/span&gt;&lt;span class="py"&gt;.PM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;peripherals&lt;/span&gt;&lt;span class="py"&gt;.SYSCTRL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;peripherals&lt;/span&gt;&lt;span class="py"&gt;.NVMCTRL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;pins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;hal&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Pins&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peripherals&lt;/span&gt;&lt;span class="py"&gt;.PORT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;red_led&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pins&lt;/span&gt;&lt;span class="py"&gt;.d13&lt;/span&gt;&lt;span class="nf"&gt;.into_open_drain_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;pins&lt;/span&gt;&lt;span class="py"&gt;.port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="py"&gt;.SYST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;clocks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="nf"&gt;.delay_ms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200u8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;red_led&lt;/span&gt;&lt;span class="nf"&gt;.set_high&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="nf"&gt;.delay_ms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200u8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;red_led&lt;/span&gt;&lt;span class="nf"&gt;.set_low&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nowhere close to as svelte as the MicroPython version, but then Rust isn’t particularly well known for being terse.&lt;/p&gt;

</description>
      <category>iot</category>
      <category>embedded</category>
      <category>raspberrypi</category>
      <category>rust</category>
    </item>
    <item>
      <title>Heterogeneous Smart Home Lighting (Part 1)</title>
      <dc:creator>jeikabu</dc:creator>
      <pubDate>Sun, 07 Mar 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/jeikabu/heterogeneous-smart-home-lighting-part-1-11b4</link>
      <guid>https://dev.to/jeikabu/heterogeneous-smart-home-lighting-part-1-11b4</guid>
      <description>&lt;p&gt;I’ve been messing around with my &lt;a href="https://rendered-obsolete.github.io/2019/11/22/home_assistant.html"&gt;“smart home” setup for a while now&lt;/a&gt;. I started with &lt;a href="https://www.philips-hue.com/en-us"&gt;Philips Hue&lt;/a&gt; for no other reason than it seemed to be the leader or de-facto standard for smart lighting. But, let’s be honest- Hue isn’t cheap, even for the white (non-colored) bulbs; they’re 12-18 EUR or 12+ USD a piece depending on the bundle.&lt;/p&gt;

&lt;p&gt;The other day I came across &lt;a href="https://www.the-ambient.com/how-to/ikea-smart-bulbs-on-philips-hue-app-255"&gt;this post&lt;/a&gt; about using Ikea brand “Trådfri” smart lights (which start at 10 EUR/USD) alongside Hue. On a whim I picked up a few different models and following the guide:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hue App &amp;gt; ⋯ &amp;gt; Light Setup &amp;gt; Add light&lt;/li&gt;
&lt;li&gt;Power-cycle the light 6 times&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;They all showed up like a charm and worked like the rest of my Hue lights. Even the &lt;a href="https://www.ikea.com/us/en/p/tradfri-driver-for-wireless-control-gray-10356189/"&gt;LED Power Supply&lt;/a&gt;. I was able to add them to existing rooms, and they even went straight into Home Assistant.&lt;/p&gt;

&lt;p&gt;However, complications arose when I tried to use non-Zigbee devices (e.g. bluetooth/Wifi devices by &lt;a href="https://www.wizconnected.com/en/consumer/"&gt;Wiz&lt;/a&gt;, etc.), or non-lights like &lt;a href="https://www.ikea.com/us/en/p/tradfri-wireless-control-outlet-30356169/"&gt;Ikea power control&lt;/a&gt; or Xiaomi motion sensor.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.ikea.com/us/en/p/tradfri-wireless-motion-sensor-white-60377655/"&gt;Ikea Trådfri motion sensor&lt;/a&gt; is a bit odd because while you &lt;strong&gt;can&lt;/strong&gt; pair it with 1 or more lights that it simply turns on, that hardly qualifies as “smart”. I’d like to use them like more generalized sensors. I’m particularly excited by the Wiz motion sensor because while larger than some of the other options it can use standard AA rechargeable batteries instead of obnoxious batteries like CR2032:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2sgZLKSH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rendered-obsolete.github.io/assets/wiz_motion_sensor_eneloop.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2sgZLKSH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rendered-obsolete.github.io/assets/wiz_motion_sensor_eneloop.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Turns out a lot of these failings can be solved with software (hurray!), but I leave that for my next post.&lt;/p&gt;

</description>
      <category>smarthome</category>
      <category>iot</category>
      <category>homeautomation</category>
      <category>hass</category>
    </item>
  </channel>
</rss>
