<?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: Pal Kerecsenyi</title>
    <description>The latest articles on DEV Community by Pal Kerecsenyi (@palkerecs).</description>
    <link>https://dev.to/palkerecs</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%2F2026470%2Fb8380bee-c378-47d0-b2ce-3fc1b63d2ccb.png</url>
      <title>DEV Community: Pal Kerecsenyi</title>
      <link>https://dev.to/palkerecs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/palkerecs"/>
    <language>en</language>
    <item>
      <title>Open source, end-to-end encrypted, private forms</title>
      <dc:creator>Pal Kerecsenyi</dc:creator>
      <pubDate>Fri, 06 Sep 2024 10:00:04 +0000</pubDate>
      <link>https://dev.to/palkerecs/open-source-end-to-end-encrypted-private-forms-3jo0</link>
      <guid>https://dev.to/palkerecs/open-source-end-to-end-encrypted-private-forms-3jo0</guid>
      <description>&lt;p&gt;Hi Dev.to! I'm Pal, founder of &lt;a href="https://palform.app" rel="noopener noreferrer"&gt;Palform&lt;/a&gt;, an end-to-end encrypted form builder.&lt;/p&gt;

&lt;p&gt;Unlike other solutions out there (looking at you, Google), it's focussed on privacy and security. But that doesn't mean a compromise on aesthetics and usability: the design is fully customisable to meet your brand, and loads of analysis features help you generate reports.&lt;/p&gt;

&lt;p&gt;It's written in &lt;strong&gt;Rust and Svelte&lt;/strong&gt; with large components of WebAssembly handling the encryption on the frontend. The code is &lt;strong&gt;open source&lt;/strong&gt; under the AGPL license, so you can &lt;a href="https://github.com/palform/palform" rel="noopener noreferrer"&gt;view it on GitHub&lt;/a&gt; for yourself and audit its security freely.&lt;/p&gt;

&lt;p&gt;The website has hosted plans, including a &lt;strong&gt;free plan with unlimited responses&lt;/strong&gt;. I'm working on building a smooth self-hosting experience, too!&lt;/p&gt;

&lt;p&gt;The forms space is something that's needed better privacy for a long time, and today is a better day than any to stop relying on Google.&lt;/p&gt;

&lt;p&gt;Let me know what you think :)&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>privacy</category>
      <category>rust</category>
    </item>
    <item>
      <title>Type-safe scalable database IDs for Rust</title>
      <dc:creator>Pal Kerecsenyi</dc:creator>
      <pubDate>Wed, 04 Sep 2024 12:45:18 +0000</pubDate>
      <link>https://dev.to/palkerecs/type-safe-scalable-database-ids-for-rust-24k3</link>
      <guid>https://dev.to/palkerecs/type-safe-scalable-database-ids-for-rust-24k3</guid>
      <description>&lt;p&gt;Read the original blog post here: &lt;a href="https://docs.palform.app/blog/2024/09/04/tsids/" rel="noopener noreferrer"&gt;https://docs.palform.app/blog/2024/09/04/tsids/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Database IDs are one of those things you really don't want to think about. It feels like they should just "work". Why are there different types of IDs?? Why can't I just use an auto-incrementing number? The real world is just so annoying.&lt;/p&gt;

&lt;p&gt;In this article, I'll explain why UUIDs look like the cool solution to all our problems (but actually aren't), and demonstrate a simple but powerful system I used at Palform to vastly improve the database experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problems with UUIDs
&lt;/h2&gt;

&lt;p&gt;Nowadays, UUIDs are often the go-to for the majority of developers. Essentially all major databases, ORMs, clients, and SDKs have built-in or high-quality 3rd-party support for them.&lt;/p&gt;

&lt;p&gt;Yes they're slightly more difficult to generate, but you don't have to write code for that anymore!&lt;/p&gt;

&lt;p&gt;With 128 bits of data, it's practically impossible to ever get a collision so you're safe from that too.&lt;/p&gt;

&lt;p&gt;Around the internet, there are &lt;a href="https://blog.boot.dev/clean-code/what-are-uuids-and-should-you-use-them/" rel="noopener noreferrer"&gt;several&lt;/a&gt; &lt;a href="https://www.howtogeek.com/devops/what-are-uuids-and-why-are-they-useful/" rel="noopener noreferrer"&gt;blog&lt;/a&gt; &lt;a href="https://www.uniqueids.org/en/what-is-uuid" rel="noopener noreferrer"&gt;posts&lt;/a&gt; advocating for their use over sequential IDs. And it's true: it's probably nearly always the better choice between the two. It hides the number of records you have and prevents automated enumeration, while not impacting performance &lt;em&gt;too&lt;/em&gt; much in smaller databases.&lt;/p&gt;

&lt;p&gt;But with &lt;a href="https://palform.app" rel="noopener noreferrer"&gt;Palform&lt;/a&gt;, my aim was to handle hundreds of thousands of form responses smoothly and lag-free. "Good enough" won't do, so we need a better solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter TSIDs
&lt;/h2&gt;

&lt;p&gt;Already gaining traction between developers, TSIDs &lt;a href="https://www.foxhound.systems/blog/time-sorted-unique-identifiers/" rel="noopener noreferrer"&gt;strike a balance&lt;/a&gt; between incremental IDs and UUIDs. They're more space-efficient than the latter, while having the same time-sortable property of the former (although it's not fully guaranteed).&lt;/p&gt;

&lt;p&gt;I won't write about the details of how they work here, as other posts have already done a much better job than I could.&lt;/p&gt;

&lt;p&gt;They can be a bit of a pain with distributed systems as they require a node ID, which is not always easy to obtain in a definitely unique way. However, Palform is not really a distributed system.&lt;/p&gt;

&lt;p&gt;Support is also much weaker than UUIDs, but that's also fine: luckily, Rust has an &lt;a href="https://crates.io/crates/tsid" rel="noopener noreferrer"&gt;excellent crate for it&lt;/a&gt; which is really all I needed.&lt;/p&gt;

&lt;p&gt;UUIDs are serialised in a &lt;a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-format" rel="noopener noreferrer"&gt;highly standardised way&lt;/a&gt;. They're so widespread that most developers can glance at the random-looking data and immediately tell it's a UUID. Just look at it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;de6fc6ac-2c80-48ce-9b48-f9793a0b8c71
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mmmm beautiful.&lt;/p&gt;

&lt;p&gt;TSIDs are 64 bit integers. They're usually serialised into a 13 character base-32 URL-safe string. That's much shorter and arguably more readable than a UUID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0GYDWW6VM4C0E
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even nicer.&lt;/p&gt;

&lt;p&gt;So I made the decision: Palform would use TSIDs. But how to implement them?&lt;/p&gt;

&lt;h2&gt;
  
  
  SeaORM support
&lt;/h2&gt;

&lt;p&gt;Palform uses the excellent &lt;a href="https://www.sea-ql.org/SeaORM/" rel="noopener noreferrer"&gt;SeaORM&lt;/a&gt; to manage database records on the backend. It has support for Postgres' built-in &lt;code&gt;uuid&lt;/code&gt; type, but understandably no support for the relatively new TSID.&lt;/p&gt;

&lt;p&gt;SeaORM requires declaring Rust entities (basically a struct) for each of your tables. This then makes it easy to write very Rust-idiomatic and satisfying database queries. Here's what an entity looks like when using UUIDs:&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(Clone,&lt;/span&gt; &lt;span class="nd"&gt;Debug,&lt;/span&gt; &lt;span class="nd"&gt;PartialEq,&lt;/span&gt; &lt;span class="nd"&gt;DeriveEntityModel,&lt;/span&gt; &lt;span class="nd"&gt;Eq,&lt;/span&gt; &lt;span class="nd"&gt;Serialize,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize)]&lt;/span&gt;
&lt;span class="nd"&gt;#[sea_orm(table_name&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"auth_token"&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;Model&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;hash&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;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DateTime&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;expires_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[sea_orm(primary_key,&lt;/span&gt; &lt;span class="nd"&gt;auto_increment&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;Uuid&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;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uuid&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;This uses the very popular &lt;a href="https://crates.io/crates/uuid" rel="noopener noreferrer"&gt;&lt;code&gt;uuid&lt;/code&gt;&lt;/a&gt; crate, and SeaORM takes care of converting to/from the PostgreSQL type.&lt;/p&gt;

&lt;p&gt;Creating a new record is very easy:&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;new_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;admin_user&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ActiveModel&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="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_v4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&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="n"&gt;new_user&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&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;Now how to do this with TSIDs? The &lt;code&gt;tsid&lt;/code&gt; create contains a &lt;code&gt;create_tsid&lt;/code&gt; function, which returns a &lt;code&gt;TSID&lt;/code&gt; struct. We can turn that into a &lt;code&gt;u64&lt;/code&gt;, too:&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="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="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;create_tsid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.number&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need a way to convert back from &lt;code&gt;u64&lt;/code&gt; to the &lt;code&gt;TSID&lt;/code&gt; struct. It's all a bit awkward, and the typing is weird: what's a &lt;code&gt;u64&lt;/code&gt;? It could be any number, how do we guarantee it's a TSID?&lt;/p&gt;

&lt;h2&gt;
  
  
  Building my own adapter
&lt;/h2&gt;

&lt;p&gt;It felt like an upward battle at this point, and I kept thinking how much easier it would be to just settle with the beautifully supported UUIDs. But this was getting interesting now, and so I decided to spend nearly an entire week designing a custom adapter. I had a plan.&lt;/p&gt;

&lt;p&gt;I wanted my IDs to be &lt;em&gt;type-safe&lt;/em&gt;. That is, each table in my database should somehow have its own type of ID, and Rust should complain violently if, for example, I try to use a User ID in a place where a Question ID is needed.&lt;/p&gt;

&lt;p&gt;First, I'd need a struct that I can base everything off. This would still involve the &lt;code&gt;tsid&lt;/code&gt; crate, just with a lot of wrapping code. Here's how I started:&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;Clone,&lt;/span&gt; &lt;span class="nd"&gt;Copy,&lt;/span&gt; &lt;span class="nd"&gt;PartialEq,&lt;/span&gt; &lt;span class="nd"&gt;Eq,&lt;/span&gt; &lt;span class="nd"&gt;Hash)]&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;PalformDatabaseID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&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="nn"&gt;tsid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TSID&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;Next came the typing. Stripe, famous for its much-loved developer API, does something similar by &lt;a href="https://dev.to/stripe/designing-apis-for-humans-object-ids-3o5a"&gt;prefixing IDs&lt;/a&gt; with a short code that identifies the resource. For example, customer IDs are prefixed with &lt;code&gt;cus_&lt;/code&gt;. Not only does it avoid confusion in code, it's also super readable, as you can tell what an ID is referring to without any additional information.&lt;/p&gt;

&lt;p&gt;Inspired by this very satisfying system, I wanted to create something similar.&lt;/p&gt;

&lt;p&gt;I created a trait:&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;trait&lt;/span&gt; &lt;span class="n"&gt;PalformIDResource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Eq&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;PartialEq&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Clone&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Copy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Hash&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Send&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;prefix&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prefix is an &lt;code&gt;Option&lt;/code&gt; to support certain edge cases where I need to be able to refer to &lt;em&gt;any&lt;/em&gt; type of ID in a single field (e.g. audit logs). Where the prefix is unknown, I use &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, I simply created a type implementing this trait for each of my tables:&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(Eq,&lt;/span&gt; &lt;span class="nd"&gt;PartialEq,&lt;/span&gt; &lt;span class="nd"&gt;Clone,&lt;/span&gt; &lt;span class="nd"&gt;Copy,&lt;/span&gt; &lt;span class="nd"&gt;Debug,&lt;/span&gt; &lt;span class="nd"&gt;Hash)]&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;IDOrganisation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;PalformIDResource&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;IDOrganisation&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;prefix&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;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="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org"&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;With some macros added in for better readability, you can see &lt;a href="https://github.com/palform/palform/blob/main/packages/tsid/src/resources.rs" rel="noopener noreferrer"&gt;all the resource declarations on GitHub&lt;/a&gt; (give me a star while you're there ❤️).&lt;/p&gt;

&lt;p&gt;Then, I modified the &lt;code&gt;PalformDatabaseID&lt;/code&gt; struct to include the prefix.&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;Clone,&lt;/span&gt; &lt;span class="nd"&gt;Copy,&lt;/span&gt; &lt;span class="nd"&gt;PartialEq,&lt;/span&gt; &lt;span class="nd"&gt;Eq,&lt;/span&gt; &lt;span class="nd"&gt;Hash)]&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;PalformDatabaseID&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PalformIDResource&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;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&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="n"&gt;TSID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PhantomData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Resource&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then came the laborious part: adding all the integrations needed for this to work with Serde, SeaORM, Rocket, the OpenAPI generator, etc. You can see &lt;a href="https://github.com/palform/palform/tree/main/packages/tsid/src" rel="noopener noreferrer"&gt;everything on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rewriting the entities
&lt;/h2&gt;

&lt;p&gt;Now, I could modify the SeaORM entities to use my shiny adapter.&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;Model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[sea_orm(primary_key,&lt;/span&gt; &lt;span class="nd"&gt;auto_increment&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;PalformDatabaseID&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDAuthToken&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;PalformDatabaseID&amp;lt;IDAuthToken&amp;gt;&lt;/code&gt; is completely distinct from a &lt;code&gt;PalformDatabaseID&amp;lt;IDOrganisation&amp;gt;&lt;/code&gt;, for example. Rust will fully block the compilation if it's somehow mismatched, which is exactly what I wanted.&lt;/p&gt;

&lt;p&gt;Generating them is super easy:&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;new_form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;form&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ActiveModel&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="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;PalformDatabaseID&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;IDForm&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;random&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the database, it's still just stored as a type-independent &lt;code&gt;u64&lt;/code&gt;. But whenever it's handed to the application, it has to choose a typed prefix, which is also conveyed in the serialised form (e.g. &lt;code&gt;org_0GYDWW6VM4C0E&lt;/code&gt;). When being deserialised, the prefix is checked to ensure it matches the requested type; if it's wrong (or there is no prefix), a scary error is returned.&lt;/p&gt;

&lt;p&gt;This actually helped catch some errors! In several places, I was using the UUID of the wrong resource without noticing; everything was just &lt;code&gt;Uuid&lt;/code&gt; before, and I had to rely on variable names to keep track of what was what.&lt;/p&gt;

&lt;p&gt;For future development, it will also inevitably reduce bugs and small but high-consequence typos. It will likely even improve the security posture of Palform, which is absolutely essential with such a security-focussed application. Developers are only humans and even the most careful ones will make mistakes; this change, however, will stop the mistakes with big scary red error messages before they can cause any damage.&lt;/p&gt;

&lt;p&gt;It's also simply more Rust-like, which I'm sure will make the Rust people very happy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing the code
&lt;/h2&gt;

&lt;p&gt;Palform is &lt;a href="https://github.com/palform/palform/" rel="noopener noreferrer"&gt;open source in its entirety&lt;/a&gt;, currently under the AGPL license. The ID management is under the &lt;code&gt;packages/tsid&lt;/code&gt; directory, but I might release it as a more liberally licensed separate module in the near future, so that everyone building web apps in Rust can benefit from these performance, security, and stability improvements.&lt;/p&gt;

&lt;p&gt;In the meantime, if you're ever in need of a form builder that actually cares about encryption, privacy, scalability, and other ethical principles, give &lt;a href="https://palform.app" rel="noopener noreferrer"&gt;Palform&lt;/a&gt; a try (it's free with unlimited responses).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Thanks for reading! This article was written by &lt;a href="https://www.linkedin.com/in/palkerecs" rel="noopener noreferrer"&gt;Pal Kerecsenyi&lt;/a&gt;, the founder of Palform.&lt;/p&gt;

&lt;p&gt;If you have any questions or found a mistake, please email me at &lt;a href="mailto:pal@palform.app"&gt;pal@palform.app&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>database</category>
      <category>rust</category>
      <category>performance</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
