use rmcp::ErrorData;
use crate::globals;
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Deserialize)]
#[serde(transparent)]
pub struct Registry {
#[serde(deserialize_with = "deserialize_string")]
value: Option<String>,
}
impl Registry {
pub fn value(&self) -> Option<&str> {
self.value
.as_deref()
.or_else(|| globals::get_default_registry())
}
}
impl schemars::JsonSchema for Registry {
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("string")
}
fn schema_id() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("string")
}
fn inline_schema() -> bool {
true
}
fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({ "type": "string", "default": null })
}
}
pub fn deserialize_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
let opt = Option::<String>::deserialize(deserializer)?;
match opt.as_deref() {
Some("null") | Some("") => Ok(None),
_ => Ok(opt),
}
}
pub fn deserialize_string_vec<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
use serde_json::Value;
let value = Value::deserialize(deserializer)?;
match value {
Value::Null => Ok(None),
Value::String(s) if s.to_lowercase() == "null" || s.is_empty() => Ok(None),
Value::String(s) => Ok(Some(vec![s])),
Value::Array(arr) => {
let strings: Result<Vec<String>, _> = arr
.into_iter()
.map(|v| match v {
Value::String(s) => Ok(s),
_ => Err(serde::de::Error::custom("Expected string in array")),
})
.collect();
Ok(Some(strings?))
}
_ => Err(serde::de::Error::custom("Expected array or null")),
}
}
pub fn locking_mode_to_cli_flags(
mode: Option<&str>,
preferred: &str,
) -> Result<Vec<&'static str>, ErrorData> {
Ok(match mode.unwrap_or(preferred) {
"locked" => vec!["--locked"],
"unlocked" => vec![], "offline" => vec!["--offline"],
"frozen" => vec!["--frozen"],
unknown => {
return Err(ErrorData::invalid_params(
format!(
"Unknown locking mode: {unknown}. Valid options are: locked, unlocked, offline, frozen"
),
None,
));
}
})
}
pub fn output_verbosity_to_cli_flags(mode: Option<&str>) -> Result<Vec<&'static str>, ErrorData> {
Ok(match mode.unwrap_or("quiet") {
"quiet" => vec!["--quiet"],
"normal" => vec![], "verbose" => vec!["--verbose"],
unknown => {
return Err(ErrorData::invalid_params(
format!(
"Unknown output verbosity: {unknown}. Valid options are: quiet, normal, verbose"
),
None,
));
}
})
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Deserialize, schemars::JsonSchema)]
pub struct PackageWithVersion {
pub package: String,
#[serde(default, deserialize_with = "deserialize_string")]
pub version: Option<String>,
}
impl PackageWithVersion {
#[cfg(test)]
pub fn new(package: String) -> Self {
Self {
package,
version: None,
}
}
#[cfg(test)]
pub fn with_version(package: String, version: String) -> Self {
Self {
package,
version: Some(version),
}
}
pub fn to_spec(&self) -> String {
match &self.version {
Some(version) => format!("{}@{}", self.package, version),
None => self.package.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, serde::Deserialize, PartialEq)]
struct TestString {
#[serde(deserialize_with = "deserialize_string")]
value: Option<String>,
}
#[derive(Debug, serde::Deserialize, PartialEq)]
struct TestStringVec {
#[serde(deserialize_with = "deserialize_string_vec")]
value: Option<Vec<String>>,
}
#[test]
fn test_deserialize_string_some() {
let json = r#"{ "value": "hello" }"#;
let result: TestString = serde_json::from_str(json).unwrap();
assert_eq!(result.value, Some("hello".to_string()));
}
#[test]
fn test_deserialize_string_null_string() {
let json = r#"{ "value": "null" }"#;
let result: TestString = serde_json::from_str(json).unwrap();
assert_eq!(result.value, None);
}
#[test]
fn test_deserialize_string_null_value() {
let json = r#"{ "value": null }"#;
let result: TestString = serde_json::from_str(json).unwrap();
assert_eq!(result.value, None);
}
#[test]
fn test_deserialize_string_empty_string() {
let json = r#"{ "value": "" }"#;
let result: TestString = serde_json::from_str(json).unwrap();
assert_eq!(result.value, None);
}
#[test]
fn test_deserialize_string_vec_some() {
let json = r#"{ "value": ["a", "b", "c"] }"#;
let result: TestStringVec = serde_json::from_str(json).unwrap();
assert_eq!(
result.value,
Some(vec!["a".to_string(), "b".to_string(), "c".to_string()])
);
}
#[test]
fn test_deserialize_string_vec_null_string() {
let json = r#"{ "value": "null" }"#;
let result: TestStringVec = serde_json::from_str(json).unwrap();
assert_eq!(result.value, None);
}
#[test]
fn test_deserialize_string_vec_null_value() {
let json = r#"{ "value": null }"#;
let result: TestStringVec = serde_json::from_str(json).unwrap();
assert_eq!(result.value, None);
}
#[test]
fn test_deserialize_string_vec_empty_string() {
let json = r#"{ "value": "" }"#;
let result: TestStringVec = serde_json::from_str(json).unwrap();
assert_eq!(result.value, None);
}
#[test]
fn test_deserialize_string_vec_empty_array() {
let json = r#"{ "value": [] }"#;
let result: TestStringVec = serde_json::from_str(json).unwrap();
assert_eq!(result.value, Some(vec![]));
}
#[test]
fn test_deserialize_string_vec_invalid_element() {
let json = r#"{ "value": [1, 2, 3] }"#;
let result: Result<TestStringVec, _> = serde_json::from_str(json);
assert!(result.is_err());
}
#[test]
fn test_package_with_version_new() {
let pkg = PackageWithVersion::new("serde".to_string());
assert_eq!(pkg.package, "serde");
assert_eq!(pkg.version, None);
assert_eq!(pkg.to_spec(), "serde");
}
#[test]
fn test_package_with_version_with_version() {
let pkg = PackageWithVersion::with_version("serde".to_string(), "1.0.0".to_string());
assert_eq!(pkg.package, "serde");
assert_eq!(pkg.version, Some("1.0.0".to_string()));
assert_eq!(pkg.to_spec(), "serde@1.0.0");
}
#[test]
fn test_package_with_version_deserialize_package_only() {
let json = r#"{"package":"serde"}"#;
let result: PackageWithVersion = serde_json::from_str(json).unwrap();
assert_eq!(result.package, "serde");
assert_eq!(result.version, None);
}
#[test]
fn test_package_with_version_deserialize_package_with_version() {
let json = r#"{"package":"serde","version":"1.0.0"}"#;
let result: PackageWithVersion = serde_json::from_str(json).unwrap();
assert_eq!(result.package, "serde");
assert_eq!(result.version, Some("1.0.0".to_string()));
}
#[test]
fn test_package_with_version_deserialize_null_version() {
let json = r#"{"package":"serde","version":null}"#;
let result: PackageWithVersion = serde_json::from_str(json).unwrap();
assert_eq!(result.package, "serde");
assert_eq!(result.version, None);
}
#[test]
fn test_package_with_version_deserialize_version_null_string() {
let json = r#"{"package":"serde","version":"null"}"#;
let result: PackageWithVersion = serde_json::from_str(json).unwrap();
assert_eq!(result.package, "serde");
assert_eq!(result.version, None); }
#[test]
fn test_package_with_version_to_spec() {
let pkg1 = PackageWithVersion::new("serde".to_string());
assert_eq!(pkg1.to_spec(), "serde");
let pkg2 = PackageWithVersion::with_version("tokio".to_string(), "1.0.0".to_string());
assert_eq!(pkg2.to_spec(), "tokio@1.0.0");
let pkg3 = PackageWithVersion::with_version("clap".to_string(), "4.0.0-beta.1".to_string());
assert_eq!(pkg3.to_spec(), "clap@4.0.0-beta.1");
}
#[test]
fn test_locking_mode_cli_flags() {
assert_eq!(
locking_mode_to_cli_flags(None, "locked").unwrap(),
vec!["--locked"]
);
assert_eq!(
locking_mode_to_cli_flags(Some("locked"), "locked").unwrap(),
vec!["--locked"]
);
assert_eq!(
locking_mode_to_cli_flags(Some("unlocked"), "locked").unwrap(),
Vec::<&str>::new()
);
assert_eq!(
locking_mode_to_cli_flags(Some("offline"), "locked").unwrap(),
vec!["--offline"]
);
assert_eq!(
locking_mode_to_cli_flags(Some("frozen"), "locked").unwrap(),
vec!["--frozen"]
);
assert!(locking_mode_to_cli_flags(Some("invalid"), "locked").is_err());
let error = locking_mode_to_cli_flags(Some("invalid"), "locked").unwrap_err();
assert!(error.to_string().contains("Unknown locking mode: invalid"));
assert_eq!(
locking_mode_to_cli_flags(None, "unlocked").unwrap(),
Vec::<&str>::new()
);
assert_eq!(
locking_mode_to_cli_flags(Some("locked"), "unlocked").unwrap(),
vec!["--locked"]
);
}
#[test]
fn test_output_verbosity_to_cli_flags() {
assert_eq!(
output_verbosity_to_cli_flags(None).unwrap(),
vec!["--quiet"]
);
assert_eq!(
output_verbosity_to_cli_flags(Some("quiet")).unwrap(),
vec!["--quiet"]
);
assert_eq!(
output_verbosity_to_cli_flags(Some("normal")).unwrap(),
Vec::<&str>::new()
);
assert_eq!(
output_verbosity_to_cli_flags(Some("verbose")).unwrap(),
vec!["--verbose"]
);
assert!(output_verbosity_to_cli_flags(Some("invalid")).is_err());
let error = output_verbosity_to_cli_flags(Some("invalid")).unwrap_err();
assert!(
error
.to_string()
.contains("Unknown output verbosity: invalid")
);
}
#[test]
fn test_output_verbosity_cli_flags() {
assert_eq!(
output_verbosity_to_cli_flags(None).unwrap(),
vec!["--quiet"]
);
assert_eq!(
output_verbosity_to_cli_flags(Some("quiet")).unwrap(),
vec!["--quiet"]
);
assert_eq!(
output_verbosity_to_cli_flags(Some("normal")).unwrap(),
Vec::<&'static str>::new()
);
assert_eq!(
output_verbosity_to_cli_flags(Some("verbose")).unwrap(),
vec!["--verbose"]
);
assert!(output_verbosity_to_cli_flags(Some("invalid")).is_err());
}
}