Implement a Pinning API in Rust
This challenge involves creating a simplified in-memory pinning API for managing digital assets. You will build a service that allows users to "pin" (register) assets and then later retrieve information about them. This is a foundational concept in distributed systems and data management, where pinning ensures data is readily available and not subject to garbage collection.
Problem Description
Your task is to implement a Rust application that simulates a pinning service. This service should allow clients to:
- Pin an Asset: Register a new asset with the service. Each asset will be identified by a unique identifier (e.g., a hash or ID).
- Retrieve Pin Information: Query the service to check if an asset is pinned and retrieve its associated information.
- Unpin an Asset: Remove an asset from the pinned list.
The service will operate entirely in memory. You will need to design the data structures and methods to support these operations efficiently.
Key Requirements:
- Asset Identification: Assets must be uniquely identifiable. For this challenge, we will use
Stringas the asset identifier. - Asset Data: For simplicity, when an asset is pinned, we'll associate a small piece of arbitrary data with it. We can represent this data as a
Stringfor this exercise. - Concurrency (Optional but Recommended): Consider how your API would handle multiple requests concurrently, even if the initial implementation doesn't strictly require it.
- Error Handling: Implement appropriate error handling for invalid operations (e.g., trying to retrieve a non-existent asset).
Expected Behavior:
- When an asset is pinned, it should be stored in the service's internal state.
- When attempting to pin an asset that already exists, the service should either update its information or return an appropriate error/indicator. For this challenge, let's assume pinning an existing asset updates its associated data.
- Retrieving an asset that is pinned should return its associated data.
- Retrieving an asset that is not pinned should result in a specific error.
- Unpinning an asset should remove it from the pinned state.
- Attempting to unpin a non-existent asset should not cause a panic and might return an indicator that it wasn't found.
Edge Cases to Consider:
- Pinning an asset with an empty identifier or data.
- Unpinning an asset that has already been unpinned.
- Concurrent access to the pinning store (if implementing concurrency).
Examples
Example 1: Basic Pinning and Retrieval
// Assume PinAPI is a struct implementing the required methods
let mut api = PinAPI::new();
// Pin asset "hash123" with data "This is asset 1"
api.pin("hash123", "This is asset 1").unwrap();
// Retrieve information for "hash123"
let retrieved_data = api.get_pin_info("hash123").unwrap();
assert_eq!(retrieved_data, "This is asset 1");
Explanation: The asset with ID "hash123" and data "This is asset 1" is successfully pinned. A subsequent retrieval returns the associated data.
Example 2: Updating Pinned Data and Unpinning
let mut api = PinAPI::new();
// Pin asset "hash456"
api.pin("hash456", "Initial data for asset 456").unwrap();
// Pin it again with new data (should update)
api.pin("hash456", "Updated data for asset 456").unwrap();
// Retrieve and verify the updated data
let retrieved_data = api.get_pin_info("hash456").unwrap();
assert_eq!(retrieved_data, "Updated data for asset 456");
// Unpin the asset
api.unpin("hash456").unwrap();
// Attempt to retrieve after unpinning
let result = api.get_pin_info("hash456");
assert!(result.is_err()); // Expect an error because it's no longer pinned
Explanation: The asset "hash456" is initially pinned. Pinning it again with new data updates the associated information. After unpinning, a retrieval attempt results in an error.
Example 3: Handling Non-existent Assets
let mut api = PinAPI::new();
// Attempt to retrieve a non-existent asset
let result_get = api.get_pin_info("nonexistent_hash");
assert!(result_get.is_err()); // Expect an error
// Attempt to unpin a non-existent asset
let result_unpin = api.unpin("nonexistent_hash");
assert!(result_unpin.is_ok()); // No error should occur, but it just does nothing effectively
Explanation: Trying to get information for an asset that has never been pinned results in an error. Attempting to unpin an asset that isn't pinned is a no-op and should not produce an error.
Constraints
- The asset identifier (
String) can be up to 255 characters long. - The asset data (
String) can be up to 1024 characters long. - The
PinAPIshould be able to store at least 1,000,000 pinned assets in memory. get_pin_infooperations should ideally complete in O(1) average time complexity.pinandunpinoperations should ideally complete in O(1) average time complexity.
Notes
- You can use Rust's standard library extensively. Consider using
std::collections::HashMapfor efficient key-value storage. - Define an
enumfor potential errors that your API can return. - For simplicity, focus on the core logic first. If you have time, explore thread-safety using
Arc<Mutex<...>>or similar constructs to make yourPinAPIsafe for concurrent access. - The goal is to demonstrate a clear, well-structured API design in Rust.