lychee 0.24.0

A fast, async link checker
use anyhow::{Context, Result};

use super::StatsFormatter;
use crate::formatters::stats::OutputStats;

pub(crate) struct Json;

impl Json {
    pub(crate) const fn new() -> Self {
        Self {}
    }
}

impl StatsFormatter for Json {
    /// Format stats as JSON object
    fn format(&self, stats: OutputStats) -> Result<String> {
        serde_json::to_string_pretty(&stats).context("Cannot format stats as JSON")
    }
}

#[cfg(test)]
mod tests {
    use crate::formatters::stats::{Json, StatsFormatter, get_dummy_stats};
    use pretty_assertions::assert_eq;

    const EXPECTED_JSON: &str = r#"{
  "total": 2,
  "unique": 2,
  "successful": 0,
  "unknown": 0,
  "unsupported": 0,
  "timeouts": 1,
  "redirects": 1,
  "remaps": 0,
  "excludes": 0,
  "errors": 1,
  "cached": 0,
  "success_map": {},
  "error_map": {
    "https://example.com/": [
      {
        "url": "https://github.com/mre/idiomatic-rust-doesnt-exist-man",
        "status": {
          "text": "404 Not Found",
          "code": 404
        },
        "span": {
          "line": 1,
          "column": 1
        },
        "duration": {
          "secs": 1,
          "nanos": 0
        }
      }
    ]
  },
  "timeout_map": {
    "https://example.com/": [
      {
        "url": "https://httpbin.org/delay/2",
        "status": {
          "text": "Timeout",
          "details": "Request timed out"
        },
        "span": {
          "line": 1,
          "column": 1
        },
        "duration": {
          "secs": 1,
          "nanos": 0
        }
      }
    ]
  },
  "suggestion_map": {
    "https://example.com/": [
      {
        "original": "https://original.dev/",
        "suggestion": "https://suggestion.dev/"
      }
    ]
  },
  "redirect_map": {
    "https://example.com/": [
      {
        "origin": "https://1.dev/",
        "redirects": [
          {
            "url": "https://2.dev/",
            "code": 308
          },
          {
            "url": "http://redirected.dev/",
            "code": 308
          }
        ]
      }
    ]
  },
  "excluded_map": {},
  "duration": {
    "secs": 0,
    "nanos": 0
  },
  "detailed_stats": true,
  "host_stats": {
    "example.com": {
      "total_requests": 5,
      "successful_requests": 3,
      "success_rate": 0.6,
      "rate_limited": 1,
      "client_errors": 0,
      "server_errors": 1,
      "median_request_time_ms": null,
      "cache_hits": 1,
      "cache_misses": 4,
      "cache_hit_rate": 0.2,
      "status_codes": {}
    }
  }
}"#;

    #[test]
    fn test_json_formatter() {
        let formatter = Json::new();
        let result = formatter.format(get_dummy_stats()).unwrap();

        assert_eq!(result, EXPECTED_JSON);
    }
}