use core::fmt;
use core::ops::Deref;
#[cfg(not(feature = "std"))]
use alloc::string::String;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub enum ProtocolVersion {
V2025_06_18,
#[default]
V2025_11_25,
Draft,
Unknown(String),
}
impl ProtocolVersion {
pub const LATEST: Self = Self::V2025_11_25;
pub const STABLE: &[Self] = &[Self::V2025_06_18, Self::V2025_11_25];
#[must_use]
pub fn as_str(&self) -> &str {
match self {
Self::V2025_06_18 => "2025-06-18",
Self::V2025_11_25 => "2025-11-25",
Self::Draft => "DRAFT-2026-v1",
Self::Unknown(s) => s.as_str(),
}
}
#[must_use]
pub fn is_known(&self) -> bool {
!matches!(self, Self::Unknown(_))
}
#[must_use]
pub fn is_stable(&self) -> bool {
matches!(self, Self::V2025_06_18 | Self::V2025_11_25)
}
#[must_use]
pub fn is_draft(&self) -> bool {
matches!(self, Self::Draft)
}
fn ordinal(&self) -> u32 {
match self {
Self::V2025_06_18 => 1,
Self::V2025_11_25 => 2,
Self::Draft => 3,
Self::Unknown(_) => u32::MAX,
}
}
}
impl fmt::Display for ProtocolVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl PartialOrd for ProtocolVersion {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ProtocolVersion {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match (self, other) {
(Self::Unknown(a), Self::Unknown(b)) => a.cmp(b),
_ => self.ordinal().cmp(&other.ordinal()),
}
}
}
impl From<&str> for ProtocolVersion {
fn from(s: &str) -> Self {
match s {
"2025-06-18" => Self::V2025_06_18,
"2025-11-25" => Self::V2025_11_25,
"DRAFT-2026-v1" => Self::Draft,
other => Self::Unknown(other.into()),
}
}
}
impl From<String> for ProtocolVersion {
fn from(s: String) -> Self {
match s.as_str() {
"2025-06-18" => Self::V2025_06_18,
"2025-11-25" => Self::V2025_11_25,
"DRAFT-2026-v1" => Self::Draft,
_ => Self::Unknown(s),
}
}
}
impl From<ProtocolVersion> for String {
fn from(v: ProtocolVersion) -> Self {
match v {
ProtocolVersion::Unknown(s) => s,
other => other.as_str().into(),
}
}
}
impl Serialize for ProtocolVersion {
fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for ProtocolVersion {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> core::result::Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(ProtocolVersion::from(s))
}
}
impl PartialEq<&str> for ProtocolVersion {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<ProtocolVersion> for &str {
fn eq(&self, other: &ProtocolVersion) -> bool {
*self == other.as_str()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Uri(String);
impl Uri {
#[must_use]
pub fn new(uri: impl Into<String>) -> Self {
Self(uri.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_inner(self) -> String {
self.0
}
}
impl fmt::Display for Uri {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Deref for Uri {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<str> for Uri {
fn as_ref(&self) -> &str {
&self.0
}
}
impl From<String> for Uri {
fn from(value: String) -> Self {
Self(value)
}
}
impl From<&str> for Uri {
fn from(value: &str) -> Self {
Self(value.into())
}
}
impl From<Uri> for String {
fn from(value: Uri) -> Self {
value.0
}
}
impl PartialEq<&str> for Uri {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<Uri> for &str {
fn eq(&self, other: &Uri) -> bool {
*self == other.as_str()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct MimeType(String);
impl MimeType {
#[must_use]
pub fn new(mime_type: impl Into<String>) -> Self {
Self(mime_type.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for MimeType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Deref for MimeType {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<str> for MimeType {
fn as_ref(&self) -> &str {
&self.0
}
}
impl From<String> for MimeType {
fn from(value: String) -> Self {
Self(value)
}
}
impl From<&str> for MimeType {
fn from(value: &str) -> Self {
Self(value.into())
}
}
impl From<MimeType> for String {
fn from(value: MimeType) -> Self {
value.0
}
}
impl PartialEq<&str> for MimeType {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<MimeType> for &str {
fn eq(&self, other: &MimeType) -> bool {
*self == other.as_str()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Base64String(String);
impl Base64String {
#[must_use]
pub fn new(data: impl Into<String>) -> Self {
Self(data.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for Base64String {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Deref for Base64String {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<str> for Base64String {
fn as_ref(&self) -> &str {
&self.0
}
}
impl From<String> for Base64String {
fn from(value: String) -> Self {
Self(value)
}
}
impl From<&str> for Base64String {
fn from(value: &str) -> Self {
Self(value.into())
}
}
impl From<Base64String> for String {
fn from(value: Base64String) -> Self {
value.0
}
}
impl PartialEq<&str> for Base64String {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<Base64String> for &str {
fn eq(&self, other: &Base64String) -> bool {
*self == other.as_str()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BaseMetadata {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}
impl BaseMetadata {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
title: None,
}
}
#[must_use]
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
}
pub type Cursor = String;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn protocol_version_serde_roundtrip() {
let v = ProtocolVersion::V2025_11_25;
let s = serde_json::to_string(&v).unwrap();
assert_eq!(s, "\"2025-11-25\"");
let back: ProtocolVersion = serde_json::from_str(&s).unwrap();
assert_eq!(back, v);
}
#[test]
fn protocol_version_unknown_preserves_string() {
let v: ProtocolVersion = "future-version".into();
assert_eq!(v, ProtocolVersion::Unknown("future-version".into()));
assert!(!v.is_known());
assert!(!v.is_stable());
}
#[test]
fn protocol_version_ordering() {
assert!(ProtocolVersion::V2025_06_18 < ProtocolVersion::V2025_11_25);
assert!(ProtocolVersion::V2025_11_25 < ProtocolVersion::Draft);
assert!(ProtocolVersion::Draft < ProtocolVersion::Unknown("x".into()));
}
#[test]
fn uri_transparent_serde() {
let uri = Uri::new("file:///path/to/file.txt");
let s = serde_json::to_string(&uri).unwrap();
assert_eq!(s, "\"file:///path/to/file.txt\"");
let back: Uri = serde_json::from_str(&s).unwrap();
assert_eq!(back, uri);
}
#[test]
fn mime_type_and_base64_roundtrips() {
let m = MimeType::new("application/json");
assert_eq!(m.as_str(), "application/json");
let b = Base64String::new("aGVsbG8=");
assert_eq!(b.as_str(), "aGVsbG8=");
}
#[test]
fn base_metadata_builder() {
let meta = BaseMetadata::new("my_tool").with_title("My Tool");
assert_eq!(meta.name, "my_tool");
assert_eq!(meta.title.as_deref(), Some("My Tool"));
let json = serde_json::to_string(&meta).unwrap();
assert!(json.contains("\"name\":\"my_tool\""));
assert!(json.contains("\"title\":\"My Tool\""));
}
#[test]
fn base_metadata_omits_absent_title() {
let meta = BaseMetadata::new("x");
let json = serde_json::to_string(&meta).unwrap();
assert!(!json.contains("title"));
}
}