nodeinfo-upub 0.0.3

de-/serialize nodeinfo json structs (upub fork)
Documentation
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};

/// Node metadata for version detection only used for deserialization.
#[derive(Debug, Deserialize)]
pub(crate) struct NodeVersion<'a> {
  pub(crate) version: &'a str,
}

pub type NodeInfo<'a> = NodeInfoInternal<&'a str>;
pub type NodeInfoOwned = NodeInfoInternal<String>;

/// Node metadata about a server running in the fediverse.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct NodeInfoInternal<T> {
  pub version: T,
  pub software: Software<T>,
  #[serde(default)]
  pub protocols: Vec<T>,
  #[serde(default)]
  pub services: Services<T>,
  #[serde(rename = "openRegistrations", default)]
  pub open_registrations: bool,
  #[serde(default)]
  pub usage: Usage,
  #[serde(default)]
  pub metadata: Map<String, Value>,
}

/// Node legacy metadata about a server running in the federation, only used for deserialization.
#[derive(Debug, PartialEq, Deserialize)]
pub struct LegacyNodeInfo<'a> {
  version: &'a str,
  software: Software<&'a str>,
  #[serde(default)]
  services: Services<&'a str>,
  #[serde(default)]
  protocols: Services<&'a str>,
  #[serde(rename = "openRegistrations", default)]
  open_registrations: bool,
  #[serde(default)]
  usage: Usage,
  #[serde(default)]
  metadata: Map<String, Value>,
}

/// Software contains infos about software running on the node.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Software<T> {
  pub name: T,
  pub version: Option<T>,
  pub repository: Option<T>,
  pub homepage: Option<T>,
}

/// Services tell about third party sites the node can connect to or interact with.
#[derive(Debug, PartialEq, Default, Serialize, Deserialize)]
pub struct Services<T> {
  pub inbound: Vec<T>,
  pub outbound: Vec<T>,
}

/// Usage statistics for the node
#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Usage {
  pub users: Option<Users>,
  pub local_posts: Option<i64>,
  pub local_comments: Option<i64>,
}

// UsersUsage are statistics about the users of the node.
#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Users {
  pub total: Option<i64>,
  pub active_halfyear: Option<i64>,
  pub active_month: Option<i64>,
}

impl<'a> Software<&'a str> {
  fn to_owned(&self) -> Software<String> {
    Software {
      name: self.name.to_string(),
      version: self.version.map(ToString::to_string),
      repository: self.repository.map(ToString::to_string),
      homepage: self.homepage.map(ToString::to_string),
    }
  }
}

impl<'a> Services<&'a str> {
  fn to_owned(&self) -> Services<String> {
    Services {
      inbound: self.inbound.iter().map(ToString::to_string).collect(),
      outbound: self.outbound.iter().map(ToString::to_string).collect(),
    }
  }
}

impl NodeInfo<'_> {
  /// Converts all internal `&str`s into `String`s.
  pub fn to_owned(&self) -> NodeInfoOwned {
    NodeInfoOwned {
      version: self.version.to_string(),
      software: self.software.to_owned(),
      protocols: self.protocols.iter().map(ToString::to_string).collect(),
      services: self.services.to_owned(),
      open_registrations: self.open_registrations,
      usage: self.usage.clone(),
      metadata: self.metadata.clone(),
    }
  }
}

impl<'a> From<LegacyNodeInfo<'a>> for NodeInfo<'a> {
  fn from(other: LegacyNodeInfo<'a>) -> Self {
    let mut combined_protocols: Vec<&'a str> = other
      .protocols
      .inbound
      .into_iter()
      .chain(other.protocols.outbound)
      .collect();
    combined_protocols.sort();
    combined_protocols.dedup();

    NodeInfo {
      version: other.version,
      software: other.software,
      services: other.services,
      protocols: combined_protocols,
      open_registrations: other.open_registrations,
      usage: other.usage,
      metadata: other.metadata,
    }
  }
}

impl<'a> From<LegacyNodeInfo<'a>> for NodeInfoOwned {
  fn from(value: LegacyNodeInfo<'a>) -> Self {
    NodeInfo::from(value).to_owned()
  }
}