| Crates.io | kindest |
| lib.rs | kindest |
| version | 1.0.1 |
| created_at | 2025-03-21 09:36:04.43766+00 |
| updated_at | 2025-07-10 13:49:47.362083+00 |
| description | Costless typed identifiers backed by UUID, with kind readable in serialized versions |
| homepage | |
| repository | https://github.com/Canop/kindest |
| max_upload_size | |
| id | 1600338 |
| size | 106,709 |
This is a fork of kind
It mostly updates json-schema to the (still not stable) version 1.0.
Prefer to use the original if you can.
With kind, you
See also the introduction to Kind motives and operation.
Serialize and Deserialize implementations for Id, Ided, and the id_enum! enumsId (with uuid columns) and for Ided (with tables having an uuid identifier)IdIn the current version, the sqlx feature is only complete for postgresql.
You could implement the Identifiable trait, but the easiest solution is to just add attributes to your structs:
use kind::*;
#[derive(Identifiable)]
#[kind(class="Cust")]
pub struct Customer {
// many fields
}
#[derive(Identifiable)]
#[kind(class="Cont")]
pub struct Contract {
// many fields
}
A kind::Id is strongly typed to avoid misuse of Rust APIs, especially when functions ask for several ids of different types.
The kind::Id also prevents the misuse of any string based API, such as Rest or GraphQL, by prefixing the internally used ids with a class prefix.
It's costless: the kind is handled by the type system and doesn't clutter the compiled binary
assert_eq!(
std::mem::size_of::<Id<Customer>>(),
std::mem::size_of::<uuid::Uuid>(),
);
You can parse the id from eg JSON, or just a string
let id: Id<Customer> = "Cust_371c35ec-34d9-4315-ab31-7ea8889a419a"
.parse().unwrap();
The type is checked, so this customer id can't be misused as a contract id
assert!(
"Cust_371c35ec-34d9-4315-ab31-7ea8889a419a"
.parse::<Id<Contract>>()
.is_err()
);
Note: the public id is parsed and checked in a case insensitive way
assert_eq!(id, "cust_371c35ec-34d9-4315-ab31-7ea8889a419a".parse());
assert_eq!(id, "CUST_371C35EC-34D9-4315-AB31-7EA8889A419A".parse());
Ided is short for "identified".
Sometimes, you have to deal with raw objects without id, because that's what you receive from your REST api for creation, or because you give it an id only when inserting the row in database.
That's why our raw Customer type has no id.
Most API don't deal with just the raw Customer type but with an Ided<Customer>, which is guaranteed to have an id.
An ided can be created from an id and an "entity":
let new_customer = Customer { name: "John".to_string() };
let customer = Ided::new(id, new_customer);
assert_eq!(
customer.id().to_string(),
"Cust_371c35ec-34d9-4315-ab31-7ea8889a419a"
);
assert_eq!(customer.entity().name, "John");
An ided automatically derefs into the entity type, so this is valid too:
assert_eq!(customer.name, "John");
An Ided object is serialized with the id next to the other fields, without unnecessary nesting.
#[derive(Identifiable, serde::Serialize, serde::Deserialize)]
#[kind(class="Cust")]
pub struct Customer {
pub name: String,
}
let json = r#"{
"id": "Cust_371c35ec-34d9-4315-ab31-7ea8889a419a",
"name": "John"
}"#;
let customer: Ided<Customer> = serde_json::from_str(&json).unwrap();
assert_eq!(
customer.id().to_string(),
"Cust_371c35ec-34d9-4315-ab31-7ea8889a419a"
);
assert_eq!(customer.name, "John");
The id kind is checked, the deserialization below fails because the prefix of the id is wrong:
let json = r#"{
"id": "Con_371c35ec-34d9-4315-ab31-7ea8889a419a",
"name": "John"
}"#;
assert!(serde_json::from_str::<Ided<Customer>>(&json).is_err());
In database, the id is just an uuid. The kind of the id in the database is implicitly given by the query and your DB structure, there's no additional check on reading/writing from rust to the DB and you don't have to change the DB structure when starting to use Kind.
The Id type implements Encode and Decode, so that it can be used transparently in sqlx queries just like any other primary type (arrays of Id are supported too, which is convenient with PostgreSQL).
As for serde, FromRow implementation on Ided is automatically deduced from the implementation on the raw struct.
So you will usually just declare your struct like this to have the Ided loaded from an sqlx::Row containing both the id column and the ones of the raw struct:
#[derive(Identifiable, sqlx::FromRow)]
#[kind(class="Cust")]
pub struct Customer {
pub name: String,
}
If you are generating JSON schema for your objects using schemars crate, you can enable jsonschema feature, and we will generate definition for the Id object and any Ided object:
#[derive(JsonSchema, Identifiable)]
#[kind(class="Cust")]
pub struct Customer {
pub name: String
}
fn main() {
let schema = schema_for!(Ided<Customer>);
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
}
will print out
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Customer_ided",
"description": "Identified version of Customer",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "string"
},
"name": {
"type": "string"
}
}
}
Open APi support (gated behind openapi feature flag) is currently extremely rudimentary. So far the only supported feature is defining the schema-level Id object that can be referenced by other schemas.
Example for including the Id into generated schema:
pub struct ApiDoc;
impl utoipa::OpenApi for ApiDoc {
fn openapi() -> utoipa::openapi::OpenApi {
let mut components = utoipa::openapi::ComponentsBuilder::new();
let (kind_name, kind_schema) = kind::openapi_schema();
components = components.schema(kind_name, kind_schema);
//extra components and paths
let mut openapi = utoipa::openapi::OpenApiBuilder::new()
.components(Some(components.build()))
.build();
openapi
}
}