package_json_parser 0.0.17

A parser for package.json
Documentation
use jsonc_parser::ast::ObjectProp;
use serde::de::{self, IgnoredAny, MapAccess, Visitor, value::MapAccessDeserializer};
use serde::{Deserialize, Deserializer, Serialize};
use std::fmt;

use crate::ext::Validator;

#[derive(Debug, Serialize, Clone)]
pub enum Readme {
  String(String),
  Object(ReadmeContent),
}

#[derive(Debug, Serialize, Clone)]
pub struct ReadmeContent {
  pub r#type: String,
  pub value: String,
}

impl<'de> Deserialize<'de> for ReadmeContent {
  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  where
    D: Deserializer<'de>,
  {
    const FIELDS: &[&str] = &["type", "value"];

    enum Field {
      Type,
      Value,
      Ignore,
    }

    impl<'de> Deserialize<'de> for Field {
      fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
      where
        D: Deserializer<'de>,
      {
        struct FieldVisitor;

        impl<'de> Visitor<'de> for FieldVisitor {
          type Value = Field;

          fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
            formatter.write_str("`type` or `value`")
          }

          fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
          where
            E: de::Error,
          {
            Ok(match value {
              "type" => Field::Type,
              "value" => Field::Value,
              _ => Field::Ignore,
            })
          }
        }

        deserializer.deserialize_identifier(FieldVisitor)
      }
    }

    struct ReadmeContentVisitor;

    impl<'de> Visitor<'de> for ReadmeContentVisitor {
      type Value = ReadmeContent;

      fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("an object with `type` and `value`")
      }

      fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
      where
        M: MapAccess<'de>,
      {
        let mut r#type = None;
        let mut value = None;

        while let Some(key) = map.next_key::<Field>()? {
          match key {
            Field::Type => {
              if r#type.is_some() {
                return Err(de::Error::duplicate_field("type"));
              }
              r#type = Some(map.next_value()?);
            }
            Field::Value => {
              if value.is_some() {
                return Err(de::Error::duplicate_field("value"));
              }
              value = Some(map.next_value()?);
            }
            Field::Ignore => {
              let _: IgnoredAny = map.next_value()?;
            }
          }
        }

        let r#type = r#type.ok_or_else(|| de::Error::missing_field("type"))?;
        let value = value.ok_or_else(|| de::Error::missing_field("value"))?;

        Ok(ReadmeContent { r#type, value })
      }
    }

    deserializer.deserialize_struct("ReadmeContent", FIELDS, ReadmeContentVisitor)
  }
}

impl<'de> Deserialize<'de> for Readme {
  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  where
    D: Deserializer<'de>,
  {
    struct ReadmeVisitor;

    impl<'de> Visitor<'de> for ReadmeVisitor {
      type Value = Readme;

      fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("a string or an object for readme")
      }

      fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
      where
        E: serde::de::Error,
      {
        Ok(Readme::String(value.to_string()))
      }

      fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
      where
        E: serde::de::Error,
      {
        Ok(Readme::String(value))
      }

      fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
      where
        M: MapAccess<'de>,
      {
        let content = ReadmeContent::deserialize(MapAccessDeserializer::new(map))?;
        Ok(Readme::Object(content))
      }
    }

    deserializer.deserialize_any(ReadmeVisitor)
  }
}

impl Validator for Readme {
  fn validate(&self, _prop: Option<&ObjectProp>) -> miette::Result<()> {
    Ok(())
  }
}

#[cfg(test)]
mod tests {
  use crate::PackageJsonParser;

  #[test]
  fn should_deserialize_readme_successfully() {
    let parsed =
      PackageJsonParser::parse_str(r#"{"readme":{ "type": "text", "value": "README" }}"#);
    assert!(parsed.is_ok());
  }

  #[test]
  fn should_fail_deserialize_readme_when_field_type_is_invalid() {
    let parsed = PackageJsonParser::parse_str(r#"{"readme":{ "type": true, "value": "README" }}"#);
    assert!(parsed.is_ok());
    let parsed = parsed.unwrap();
    assert!(parsed.readme().is_err());
  }

  #[test]
  fn should_fail_deserialize_readme_content_when_required_field_is_missing() {
    let parsed = PackageJsonParser::parse_str(r#"{"readme":{ "type": "text" }}"#);
    assert!(parsed.is_ok());
    let parsed = parsed.unwrap();
    assert!(parsed.readme().is_err());
  }

  #[test]
  fn should_fail_deserialize_readme_when_json_is_invalid() {
    let parsed = PackageJsonParser::parse_str("{");
    assert!(parsed.is_err());
  }
}