{"id":28434937,"url":"https://github.com/cosmicmind/entid","last_synced_at":"2026-02-17T23:01:56.851Z","repository":{"id":280551046,"uuid":"942367345","full_name":"CosmicMind/entid","owner":"CosmicMind","description":"An entity id generator and validator for Rust models.","archived":false,"fork":false,"pushed_at":"2025-08-04T18:36:26.000Z","size":85,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"release","last_synced_at":"2025-10-20T12:44:49.422Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CosmicMind.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-03-04T02:10:06.000Z","updated_at":"2025-09-22T22:44:04.000Z","dependencies_parsed_at":"2025-03-04T03:33:29.945Z","dependency_job_id":"e5c24339-17e0-4aee-9be2-ee94596367de","html_url":"https://github.com/CosmicMind/entid","commit_stats":null,"previous_names":["cosmicmind/entid","cosmicmind/entity-id"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/CosmicMind/entid","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CosmicMind%2Fentid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CosmicMind%2Fentid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CosmicMind%2Fentid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CosmicMind%2Fentid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CosmicMind","download_url":"https://codeload.github.com/CosmicMind/entid/tar.gz/refs/heads/release","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CosmicMind%2Fentid/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29561783,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-17T21:50:49.831Z","status":"ssl_error","status_checked_at":"2026-02-17T21:46:15.313Z","response_time":100,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-05T19:41:51.147Z","updated_at":"2026-02-17T23:01:56.837Z","avatar_url":"https://github.com/CosmicMind.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# entid\n\nA Rust library for generating and validating type-safe, prefixed entity identifiers based on UUIDs and ULIDs.\n\n[![Crates.io](https://img.shields.io/crates/v/entid.svg)](https://crates.io/crates/entid)\n[![Documentation](https://docs.rs/entid/badge.svg)](https://docs.rs/entid)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n## Features\n\n- **Type-safe entity IDs**: Create distinct ID types for different entities\n- **Multiple identifier formats**: Support for both UUID and ULID\n- **Prefix support**: Automatically add entity-specific prefixes to IDs\n- **Performance optimized**: Thread-safe caching of string representations\n- **Serde compatible**: Seamless serialization and deserialization\n- **Comprehensive error handling**: Clear error types for all operations\n- **Zero-cost abstractions**: Minimal runtime overhead\n- **Derive macro for implementing the `Prefix` trait**: Optional\n\n## Installation\n\nAdd this to your `Cargo.toml`:\n\n```toml\n[dependencies]\nentid = \"0.4.3\"\n```\n\nTo use the derive macro for implementing the `Prefix` trait, enable the `derive` feature:\n\n```toml\n[dependencies]\nentid = { version = \"0.4.3\", features = [\"derive\"] }\n```\n\n### API Overview\n\nThe `EntityId` type provides several methods for working with entity IDs:\n\n```rust\ntype UserId = UuidEntityId::\u003cUser\u003e;\n\n// Create a new EntityId\nlet user_id = UserId::generate();\n\n// Get the full ID string with prefix (e.g., \"user_123e4567-e89b-12d3-a456-426614174000\")\nlet full_id = user_id.as_str();\n\n// Get just the identifier part without the prefix (e.g., \"123e4567-e89b-12d3-a456-426614174000\")\nlet raw_id = user_id.id_str();\n\n// Get a reference to the underlying identifier object\nlet identifier = user_id.identifier();\n\n// Get the identifier string directly from the identifier\nlet id_str = user_id.identifier().as_str();\n\n// Get the prefix for this entity type\nlet prefix = UserId::prefix(); // \"user\"\n\n// Get the delimiter for this entity type\nlet delimiter = UserId::delimiter(); // \"_\"\n\n// For ULID-based IDs, get the timestamp\nif let Some(timestamp_ms) = ulid_id.timestamp_ms() {\n    println!(\"ID created at: {} ms since epoch\", timestamp_ms);\n}\n```\n\n### Flexible Creation Methods\n\nThe library provides multiple ways to create entity IDs:\n\n```rust\nuse entid::{EntityId, Identifier, Prefix, UuidEntityId, UlidEntityId, Uuid, Ulid};\n\ntype UserId = UuidEntityId::\u003cUser\u003e;\n\n// Using the generate method\nlet user_id1 = UserId::generate();\n\n// Using the new method with flexible string types (with prefix)\nlet id_str = \"user_123e4567-e89b-12d3-a456-426614174000\";\nlet user_id2 = UserId::new(id_str).unwrap();\nlet user_id3 = UserId::new(id_str.to_string()).unwrap();\n\n// Using from_raw_str to parse a raw identifier string (without prefix)\nlet raw_uuid = \"123e4567-e89b-12d3-a456-426614174000\";\nlet user_id4 = UserId::from_raw_str(raw_uuid).unwrap();\n\n// Using parse_raw_str with custom error handling\nlet user_id5 = UserId::parse_raw_str(raw_uuid, |e| format!(\"Invalid UUID: {}\", e)).unwrap();\n\n// Using TryFrom trait\nlet user_id6 = UserId::try_from(id_str).unwrap();\nlet user_id7 = UserId::try_from(id_str.to_string()).unwrap();\n\n// Using FromStr trait\nlet user_id8 = id_str.parse::\u003cUserId\u003e().unwrap();\n\n// Using convenience methods\nlet uuid = Uuid::new_v4();\nlet user_id9 = UserId::with_uuid(uuid);\nlet user_id10 = UserId::new_v4();\nlet user_id11 = UserId::new_v5(\u0026Uuid::NAMESPACE_DNS, \"example.com\");\n\n// Using the builder pattern\nlet user_id12 = UserId::builder().build();\nlet user_id13 = UserId::builder().with_uuid(uuid).build();\nlet user_id14 = UserId::builder().with_uuid_v4().build();\nlet user_id15 = UserId::builder().with_uuid_v5(\u0026Uuid::NAMESPACE_DNS, \"example.com\").build();\n\n// For ULID-based IDs\ntype PostId = UlidEntityId::\u003cPost\u003e;\n\nlet ulid = Ulid::new();\nlet post_id1 = PostId::with_ulid(ulid);\nlet post_id2 = PostId::with_timestamp(1625097600000); // July 1, 2021\nlet post_id3 = PostId::monotonic_from(Some(\u0026post_id2));\n\n// Using the builder pattern for ULID\nlet post_id4 = PostId::builder().with_ulid(ulid).build();\nlet post_id5 = PostId::builder().with_timestamp(1625097600000).build();\nlet post_id6 = PostId::builder().with_monotonic_from(Some(\u0026post_id5)).build();\n```\n\n### Using EntityId in Collections\n\nThe `EntityId` type implements `Borrow\u003cstr\u003e` and `AsRef\u003cstr\u003e`, making it easy to use in collections:\n\n```rust\nuse std::collections::{HashMap, HashSet};\n\n// Use EntityId as a key in a HashMap\nlet mut user_map = HashMap::new();\nuser_map.insert(user_id1, \"John Doe\");\n\n// Look up by string\nlet user = user_map.get(id_str);\n\n// Use EntityId in a HashSet\nlet mut user_set = HashSet::new();\nuser_set.insert(user_id1);\n\n// Check if a string is in the set\nlet contains = user_set.contains(id_str);\n```\n\n## Usage\n\n### Basic Example with UUID\n\n```rust\nuse entid::{EntityId, Prefix, UuidIdentifier, UuidEntityId};\n\ntype UserId = UuidEntityId::\u003cUser\u003e;\n\n// Define your entity types with custom prefixes\nstruct User;\nimpl Prefix for User {\n    fn prefix() -\u003e \u0026'static str {\n        \"user\"\n    }\n\n    fn delimiter() -\u003e \u0026'static str {\n        \"_\"\n    }\n}\n\ntype PostId = EntityId::\u003cPost, UuidIdentifier\u003e;\n\nstruct Post;\nimpl Prefix for Post {\n    fn prefix() -\u003e \u0026'static str {\n        \"post\"\n    }\n    \n    // Optional: Override the default delimiter\n    fn delimiter() -\u003e \u0026'static str {\n        \"-\"\n    }\n}\n\nfn main() {\n    // Generate random IDs with UUID\n    let user_id = UserId::generate();\n    let post_id = PostId::generate();\n    \n    // Print the IDs\n    println!(\"User ID: {}\", user_id); // e.g., \"user_6ba7b810-9dad-11d1-80b4-00c04fd430c8\"\n    println!(\"Post ID: {}\", post_id); // e.g., \"post-123e4567-e89b-12d3-a456-426614174000\"\n    \n    // Parse existing IDs\n    let parsed_user_id = UserId::new(\"user_6ba7b810-9dad-11d1-80b4-00c04fd430c8\").unwrap();\n    \n    // Type safety prevents mixing different entity IDs\n    // This won't compile:\n    // let wrong: UuidEntityId\u003cPost\u003e = user_id;\n}\n```\n\n### Using the Derive Macro\n\nWith the `derive` feature enabled, you can use the derive macro to implement the `Prefix` trait:\n\n```rust\nuse entid::{Prefix, UuidEntityId, UlidEntityId};\n\ntype UserId = UuidEntityId::\u003cUser\u003e;\n\n#[derive(Prefix)]\n#[entid(prefix = \"user\", delimiter = \"_\")]\nstruct User;\n\ntype PostId = UlidEntityId::\u003cPost\u003e;\n\n#[derive(Prefix)]\n#[entid(prefix = \"post\", delimiter = \"-\")]\nstruct Post;\n\ntype CommentId = UuidEntityId::\u003cComment\u003e;\n\n// The delimiter is optional and defaults to \"_\"\n#[derive(Prefix)]\n#[entid(prefix = \"comment\")]\nstruct Comment;\n\nfn main() {\n    let user_id = UserId::generate();\n    println!(\"User ID: {}\", user_id); // e.g., \"user_6ba7b810-9dad-11d1-80b4-00c04fd430c8\"\n    \n    let post_id = PostId::generate();\n    println!(\"Post ID: {}\", post_id); // e.g., \"post-01H1VECZJYJ1QV2V0D0000JJDX\"\n    \n    let comment_id = CommentId::generate();\n    println!(\"Comment ID: {}\", comment_id); // e.g., \"comment_6ba7b810-9dad-11d1-80b4-00c04fd430c8\"\n}\n```\n\n### Using ULID Instead of UUID\n\n```rust\nuse entid::{EntityId, Prefix, UlidIdentifier, UlidEntityId};\n\ntype ProductId = UlidEntityId::\u003cProduct\u003e;\n\nstruct Product;\nimpl Prefix for Product {\n    fn prefix() -\u003e \u0026'static str {\n        \"prod\"\n    }\n}\n\nfn main() {\n    // Generate a ULID-based ID\n    let product_id = ProductId::generate();\n    \n    // ULIDs are lexicographically sortable by creation time\n    let product_ids: Vec\u003cUProductId\u003e = (0..10)\n        .map(|_| ProductId::generate())\n        .collect();\n    \n    // Sorting will order by creation time\n    let mut sorted_ids = product_ids.clone();\n    sorted_ids.sort();\n    \n    // Get the timestamp from a ULID (not available with UUID)\n    if let Some(timestamp_ms) = product_id.timestamp_ms() {\n        println!(\"Product ID created at: {} ms since epoch\", timestamp_ms);\n    }\n}\n```\n\n### Using Deterministic UUIDs (v5)\n\n```rust\nuse entid::{EntityId, Prefix, UuidIdentifier, Uuid};\n\ntype ApiKeyToken = EntityId::\u003cApiKey, UuidIdentifier\u003e;\n\nstruct ApiKey;\nimpl Prefix for ApiKey {\n    fn prefix() -\u003e \u0026'static str {\n        \"key\"\n    }\n}\n\nfn main() {\n    // Create a namespace for your application\n    let namespace = Uuid::parse_str(\"6ba7b810-9dad-11d1-80b4-00c04fd430c8\").unwrap();\n    \n    // Create a UUID v5 identifier\n    let uuid_id = UuidIdentifier::new_v5(\u0026namespace, \"user@example.com\");\n    \n    // Create an entity ID from the identifier\n    let api_key = ApiKeyToken::from_identifier(uuid_id);\n    \n    // Same input produces the same ID\n    let uuid_id2 = UuidIdentifier::new_v5(\u0026namespace, \"user@example.com\");\n    let api_key2 = ApiKeyToken::from_identifier(uuid_id2);\n    \n    assert_eq!(api_key, api_key2);\n}\n```\n\n### Enhanced Error Handling\n\nThe library provides detailed error information and convenient methods for error handling:\n\n```rust\nuse entid::{EntityId, EntityIdError, IdentifierError, Prefix, UuidEntityId};\nuse std::error::Error;\n\n#[derive(Prefix)]\n#[entid(prefix = \"user\")]\nstruct User;\n\ntype UserId = UuidEntityId\u003cUser\u003e;\n\n// Convert errors to strings\nfn parse_user_id(input: \u0026str) -\u003e Result\u003cUserId, String\u003e {\n    UserId::new(input).map_err(|e| e.to_string()) // Use Display trait\n}\n\n// Get the specific error type\nfn handle_id_error(input: \u0026str) -\u003e Result\u003cUserId, String\u003e {\n    match UserId::from_raw_str(input) {\n        Ok(id) =\u003e Ok(id),\n        Err(EntityIdError::InvalidIdentifier) =\u003e {\n            // Try to parse as UUID to get more specific error\n            match uuid::Uuid::parse_str(input) {\n                Err(uuid_err) =\u003e Err(format!(\"Invalid UUID: {}\", uuid_err)),\n                _ =\u003e Err(\"Unknown identifier error\".to_string()),\n            }\n        },\n        Err(e) =\u003e Err(e.to_string()),\n    }\n}\n\n// Access the underlying error directly\nfn process_with_detailed_errors\u003cS: AsRef\u003cstr\u003e\u003e(input: S) -\u003e Result\u003cUserId, String\u003e {\n    UserId::from_raw_str(input.as_ref()).map_err(|e| {\n        match e {\n            EntityIdError::InvalidIdentifier =\u003e {\n                // Try to parse directly to get the specific error\n                match uuid::Uuid::parse_str(input.as_ref()) {\n                    Err(uuid_err) =\u003e {\n                        let id_err = IdentifierError::Uuid(uuid_err);\n                        \n                        // Get the underlying UUID error\n                        if let Some(uuid_err) = id_err.uuid_error() {\n                            format!(\"UUID parsing failed: {}\", uuid_err)\n                        } else {\n                            // Get the error message directly\n                            format!(\"UUID parsing failed: {}\", id_err.error_message())\n                        }\n                    },\n                    _ =\u003e \"Unknown identifier error\".to_string(),\n                }\n            },\n            _ =\u003e e.to_string(),\n        }\n    })\n}\n\n// Use the standard Error trait methods\nfn log_error_details(err: \u0026EntityIdError) {\n    println!(\"Error: {}\", err);\n    \n    if let Some(source) = err.source() {\n        println!(\"Caused by: {}\", source);\n    }\n}\n```\n\n### Error Handling\n\n```rust\nuse entid::{EntityId, EntityIdError, IdentifierError, Prefix, UuidIdentifier};\n\ntype UserId = EntityId\u003cUser, UuidIdentifier\u003e;\n\nstruct User;\nimpl Prefix for User {\n    fn prefix() -\u003e \u0026'static str {\n        \"user\"\n    }\n}\n\nfn parse_id(input: \u0026str) -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    // Parse an entity ID string\n    match UserId::new(input) {\n        Ok(id) =\u003e {\n            println!(\"Successfully parsed ID: {}\", id);\n            Ok(())\n        },\n        Err(EntityIdError::InvalidFormat) =\u003e {\n            // Handle invalid format (missing prefix or delimiter)\n            println!(\"Invalid ID format: {}\", input);\n            Err(Box::new(EntityIdError::InvalidFormat))\n        },\n        Err(EntityIdError::InvalidIdentifier) =\u003e {\n            // Handle invalid identifier (not a valid UUID/ULID)\n            println!(\"Invalid identifier part in ID: {}\", input);\n            Err(Box::new(EntityIdError::InvalidIdentifier))\n        }\n    }\n}\n\n// Parse a raw identifier string\nfn parse_raw_identifier(input: \u0026str) -\u003e Result\u003cUuidIdentifier, IdentifierError\u003e {\n    UuidIdentifier::parse(input)\n}\n```\n\n### Using with Serde\n\n```rust\nuse entid::{EntityId, Prefix, UlidIdentifier};\nuse serde::{Serialize, Deserialize};\n\nstruct Order;\nimpl Prefix for Order {\n    fn prefix() -\u003e \u0026'static str {\n        \"order\"\n    }\n}\n\ntype OrderId = EntityId\u003cOrder, UlidIdentifier\u003e;\n\n#[derive(Serialize, Deserialize)]\nstruct OrderRecord {\n    id: OrderId,\n    customer_name: String,\n    amount: f64,\n}\n\nfn main() {\n    let order = OrderRecord {\n        id: OrderId::generate(),\n        customer_name: \"John Doe\".to_string(),\n        amount: 123.45,\n    };\n    \n    // Serialize to JSON\n    let json = serde_json::to_string(\u0026order).unwrap();\n    println!(\"JSON: {}\", json);\n    \n    // Deserialize from JSON\n    let deserialized: OrderRecord = serde_json::from_str(\u0026json).unwrap();\n    assert_eq!(order.id, deserialized.id);\n}\n```\n\n### Enhanced Error Handling with String Conversions\n\nThe library provides convenient error handling with string conversions through `AsRef\u003cstr\u003e` and `Into\u003cString\u003e` implementations:\n\n```rust\nuse entid::{EntityId, Prefix, UlidIdentifier};\n\ntype TaskId = EntityId::\u003cTask, UlidIdentifier\u003e;\n\nstruct Task;\nimpl Prefix for Task {\n    fn prefix() -\u003e \u0026'static str {\n        \"task\"\n    }\n}\n\nfn main() {\n    // Create a ULID-based entity ID\n    let task1 = TaskId::generate();\n    \n    // Create a monotonic ULID (ensures ordering even within the same millisecond)\n    let ulid2 = UlidIdentifier::monotonic_from(Some(task1.identifier()));\n    let task2 = TaskId::from_identifier(ulid2);\n    \n    // task2 is guaranteed to sort after task1\n    assert!(task2 \u003e task1);\n}\n```\n\n### Custom Validation\n\n```rust\nuse entid::{EntityId, Prefix, UuidIdentifier};\n\ntype ApiKeyToken = EntityId\u003cApiKey, UuidIdentifier\u003e;\n\nstruct ApiKey;\nimpl Prefix for ApiKey {\n    fn prefix() -\u003e \u0026'static str {\n        \"token\"\n    }\n}\n\n// Extend EntityId with custom validation logic\nimpl ApiKeyToken {\n    pub fn is_valid_for_environment(\u0026self, env: \u0026str) -\u003e bool {\n        // Custom validation logic based on the UUID version\n        match env {\n            \"production\" =\u003e self.identifier().version() == Some(uuid::Version::Sha1),\n            _ =\u003e true,\n        }\n    }\n}\n```\n\n### Additional Conversion Methods\n\nThe library provides additional methods for converting between different representations:\n\n```rust\nuse entid::{Prefix, UuidEntityId, UuidIdentifier};\n\n#[derive(Prefix)]\n#[entid(prefix = \"user\")]\nstruct User;\n\ntype UserId = UuidEntityId\u003cUser\u003e;\n\n// Generate a new ID\nlet user_id = UserId::generate();\n\n// Convert to raw identifier string (without prefix)\nlet raw_string = user_id.to_raw_string();\nassert_eq!(raw_string, user_id.id_str().to_string());\n\n// Convert to the underlying identifier type\nlet uuid_identifier: UuidIdentifier = user_id.to_identifier();\nassert_eq!(uuid_identifier, *user_id.identifier());\n\n// Use with functions that accept string types\nfn process_string(s: impl AsRef\u003cstr\u003e) {\n    println!(\"Processing: {}\", s.as_ref());\n}\n\n// Works directly with EntityId thanks to AsRef\u003cstr\u003e\nprocess_string(user_id);\n```\n\n## Choosing Between UUID and ULID\n\n### UUID Advantages\n- Industry standard with wide adoption\n- Multiple versions for different use cases (v1, v3, v4, v5)\n- Well-supported in databases and other systems\n\n### ULID Advantages\n- Lexicographically sortable (sorts by creation time)\n- URL-safe (no special characters)\n- Shorter string representation (26 characters vs 36 for UUID)\n- Built-in timestamp component\n\n## Performance Considerations\n\n- String representations are cached using `OnceLock` for thread-safe lazy initialization\n- The `EntityId` type implements `Hash`, `PartialEq`, and `Eq` for efficient use in collections\n- Memory usage is optimized by using `PhantomData` for type parameters\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcosmicmind%2Fentid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcosmicmind%2Fentid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcosmicmind%2Fentid/lists"}