#![cfg_attr(docsrs, feature(doc_cfg))]
use serde::{Deserialize, Serialize};
use std::fmt;
use std::fs::File;
use std::io::Read;
use std::path::Path;
pub mod v1_2;
pub mod v1_3;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("failed to read HAR input")]
Read {
#[source]
source: std::io::Error,
},
#[error("failed to decode HAR JSON")]
DecodeJson {
#[source]
source: serde_json::Error,
},
#[cfg(feature = "yaml")]
#[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
#[error("failed to encode HAR as YAML")]
EncodeYaml {
#[source]
source: yaml_serde::Error,
},
#[error("failed to encode HAR as JSON")]
EncodeJson {
#[source]
source: serde_json::Error,
},
#[error("HAR document must contain a top-level `log` object")]
MissingLog,
#[error("HAR document is missing `log.version`")]
MissingVersion,
#[error("unsupported HAR version `{0}`")]
UnsupportedVersion(String),
}
impl Error {
fn read(source: std::io::Error) -> Self {
Self::Read { source }
}
fn decode_json(source: serde_json::Error) -> Self {
Self::DecodeJson { source }
}
#[cfg(feature = "yaml")]
fn encode_yaml(source: yaml_serde::Error) -> Self {
Self::EncodeYaml { source }
}
fn encode_json(source: serde_json::Error) -> Self {
Self::EncodeJson { source }
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum HarVersion {
V1_2,
V1_3,
}
impl HarVersion {
pub const fn as_str(self) -> &'static str {
match self {
Self::V1_2 => "1.2",
Self::V1_3 => "1.3",
}
}
}
impl fmt::Display for HarVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(tag = "version")]
pub enum Spec {
#[allow(non_camel_case_types)]
#[serde(rename = "1.2")]
V1_2(v1_2::Log),
#[allow(non_camel_case_types)]
#[serde(rename = "1.3")]
V1_3(v1_3::Log),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Har {
pub log: Spec,
}
impl Har {
pub fn version(&self) -> HarVersion {
match &self.log {
Spec::V1_2(_) => HarVersion::V1_2,
Spec::V1_3(_) => HarVersion::V1_3,
}
}
}
pub fn from_path<P>(path: P) -> Result<Har, Error>
where
P: AsRef<Path>,
{
from_reader(File::open(path).map_err(Error::read)?)
}
pub fn from_slice(input: &[u8]) -> Result<Har, Error> {
let value = serde_json::from_slice::<serde_json::Value>(input).map_err(Error::decode_json)?;
parse_har_value(value)
}
pub fn from_str(input: &str) -> Result<Har, Error> {
from_slice(input.as_bytes())
}
pub fn from_reader<R>(mut reader: R) -> Result<Har, Error>
where
R: Read,
{
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).map_err(Error::read)?;
from_slice(&bytes)
}
#[cfg(feature = "yaml")]
#[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
pub fn to_yaml(spec: &Har) -> Result<String, Error> {
yaml_serde::to_string(spec).map_err(Error::encode_yaml)
}
pub fn to_json(spec: &Har) -> Result<String, Error> {
serde_json::to_string_pretty(spec).map_err(Error::encode_json)
}
fn parse_har_value(value: serde_json::Value) -> Result<Har, Error> {
let root = value.as_object().ok_or(Error::MissingLog)?;
let log_value = root.get("log").cloned().ok_or(Error::MissingLog)?;
let Some(log_object) = log_value.as_object() else {
return Err(Error::MissingLog);
};
let version = match log_object
.get("version")
.and_then(serde_json::Value::as_str)
{
Some("1.2") => HarVersion::V1_2,
Some("1.3") => HarVersion::V1_3,
Some(other) => return Err(Error::UnsupportedVersion(other.to_owned())),
None => return Err(Error::MissingVersion),
};
let log = match version {
HarVersion::V1_2 => {
let log = serde_json::from_value(log_value).map_err(Error::decode_json)?;
Spec::V1_2(log)
}
HarVersion::V1_3 => {
let log = serde_json::from_value(log_value).map_err(Error::decode_json)?;
Spec::V1_3(log)
}
};
Ok(Har { log })
}