use std::borrow::Borrow;
use std::fmt;
use std::ops::Deref;
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ModelId(String);
impl fmt::Debug for ModelId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, formatter)
}
}
impl ModelId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
}
impl fmt::Display for ModelId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.0)
}
}
impl Deref for ModelId {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}
impl AsRef<str> for ModelId {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Borrow<str> for ModelId {
fn borrow(&self) -> &str {
&self.0
}
}
impl From<String> for ModelId {
fn from(id: String) -> Self {
Self(id)
}
}
impl From<&str> for ModelId {
fn from(id: &str) -> Self {
Self(id.to_string())
}
}
impl From<&ModelId> for ModelId {
fn from(id: &ModelId) -> Self {
id.clone()
}
}
impl From<ModelId> for String {
fn from(id: ModelId) -> Self {
id.0
}
}
impl From<&ModelId> for String {
fn from(id: &ModelId) -> Self {
id.0.clone()
}
}
impl PartialEq<str> for ModelId {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl PartialEq<&str> for ModelId {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl PartialEq<String> for ModelId {
fn eq(&self, other: &String) -> bool {
&self.0 == other
}
}
impl PartialEq<ModelId> for str {
fn eq(&self, other: &ModelId) -> bool {
self == other.0
}
}
impl PartialEq<ModelId> for &str {
fn eq(&self, other: &ModelId) -> bool {
*self == other.0
}
}
impl PartialEq<ModelId> for String {
fn eq(&self, other: &ModelId) -> bool {
self == &other.0
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
#[test]
fn it_behaves_like_the_string_it_wraps() {
let id = ModelId::new("openai/gpt-oss-20b");
assert_eq!(id.to_string(), "openai/gpt-oss-20b");
assert_eq!(id, "openai/gpt-oss-20b");
assert_eq!("openai/gpt-oss-20b", id);
assert!(id.starts_with("openai/"));
assert_eq!(takes_str(&id), "openai/gpt-oss-20b");
}
fn takes_str(model: &str) -> &str {
model
}
#[test]
fn a_map_of_ids_is_queryable_by_str() {
let by_model = HashMap::from([(ModelId::new("aws/anthropic/claude-opus-4-5"), 1)]);
assert_eq!(by_model.get("aws/anthropic/claude-opus-4-5"), Some(&1));
}
#[test]
fn it_serializes_as_a_bare_string() -> serde_json::Result<()> {
let id = ModelId::new("openai/gpt-oss-20b");
let json = serde_json::to_string(&id)?;
assert_eq!(json, "\"openai/gpt-oss-20b\"");
assert_eq!(serde_json::from_str::<ModelId>(&json)?, id);
Ok(())
}
}