Skip to main content

har/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! HTTP Archive (HAR) serialization and deserialization helpers.
4//!
5//! HAR input is always parsed from JSON.
6//! JSON serialization is available by default.
7//! Enable the crate feature `yaml` to serialize parsed documents with `to_yaml`.
8
9use serde::{Deserialize, Serialize};
10use std::fmt;
11use std::fs::File;
12use std::io::Read;
13use std::path::Path;
14
15pub mod v1_2;
16pub mod v1_3;
17
18pub mod analysis;
19pub mod assemble;
20pub mod classify;
21pub mod config;
22pub mod correlate;
23pub mod errorbody;
24pub mod filter;
25pub mod fingerprint;
26pub mod glob;
27pub mod grouping;
28pub mod jsonpath;
29pub mod jwt;
30pub mod loader;
31pub mod model;
32pub mod normalize;
33pub mod opaque;
34pub mod raw;
35pub mod recommender;
36pub mod redact;
37pub mod render;
38pub mod stats;
39pub mod timing;
40pub mod vendor;
41
42/// Errors that HAR functions may return.
43#[derive(thiserror::Error, Debug)]
44pub enum Error {
45    #[error("failed to read HAR input")]
46    Read {
47        #[source]
48        source: std::io::Error,
49    },
50    #[error("failed to decode HAR JSON")]
51    DecodeJson {
52        #[source]
53        source: serde_json::Error,
54    },
55    #[cfg(feature = "yaml")]
56    #[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
57    #[error("failed to encode HAR as YAML")]
58    EncodeYaml {
59        #[source]
60        source: yaml_serde::Error,
61    },
62    #[error("failed to encode HAR as JSON")]
63    EncodeJson {
64        #[source]
65        source: serde_json::Error,
66    },
67    #[error("HAR document must contain a top-level `log` object")]
68    MissingLog,
69    #[error("HAR document is missing `log.version`")]
70    MissingVersion,
71    #[error("unsupported HAR version `{0}`")]
72    UnsupportedVersion(String),
73}
74
75impl Error {
76    fn read(source: std::io::Error) -> Self {
77        Self::Read { source }
78    }
79
80    fn decode_json(source: serde_json::Error) -> Self {
81        Self::DecodeJson { source }
82    }
83
84    #[cfg(feature = "yaml")]
85    fn encode_yaml(source: yaml_serde::Error) -> Self {
86        Self::EncodeYaml { source }
87    }
88
89    fn encode_json(source: serde_json::Error) -> Self {
90        Self::EncodeJson { source }
91    }
92}
93
94/// Supported HAR versions.
95#[derive(Clone, Copy, Debug, Eq, PartialEq)]
96pub enum HarVersion {
97    V1_2,
98    V1_3,
99}
100
101impl HarVersion {
102    pub const fn as_str(self) -> &'static str {
103        match self {
104            Self::V1_2 => "1.2",
105            Self::V1_3 => "1.3",
106        }
107    }
108}
109
110impl fmt::Display for HarVersion {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        f.write_str(self.as_str())
113    }
114}
115
116/// Supported versions of HAR.
117///
118/// Note that point releases require adding here (as they must otherwise they
119/// wouldn't need a new version). Using `untagged` can avoid that but the errors
120/// on incompatible documents become super hard to debug.
121#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
122#[serde(tag = "version")]
123pub enum Spec {
124    /// Version 1.2 of the HAR specification.
125    ///
126    /// Refer to the official
127    /// [specification](https://w3c.github.io/web-performance/specs/HAR/Overview.html)
128    /// for more information.
129    #[allow(non_camel_case_types)]
130    #[serde(rename = "1.2")]
131    V1_2(v1_2::Log),
132
133    /// Version 1.3 of the HAR specification.
134    ///
135    /// Refer to the draft
136    /// [specification](https://github.com/ahmadnassri/har-spec/blob/master/versions/1.3.md)
137    /// for more information.
138    #[allow(non_camel_case_types)]
139    #[serde(rename = "1.3")]
140    V1_3(v1_3::Log),
141}
142
143#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
144pub struct Har {
145    pub log: Spec,
146}
147
148impl Har {
149    pub fn version(&self) -> HarVersion {
150        match &self.log {
151            Spec::V1_2(_) => HarVersion::V1_2,
152            Spec::V1_3(_) => HarVersion::V1_3,
153        }
154    }
155}
156
157/// Deserialize a HAR from a path.
158pub fn from_path<P>(path: P) -> Result<Har, Error>
159where
160    P: AsRef<Path>,
161{
162    from_reader(File::open(path).map_err(Error::read)?)
163}
164
165/// Deserialize a HAR from a byte slice.
166pub fn from_slice(input: &[u8]) -> Result<Har, Error> {
167    let value = serde_json::from_slice::<serde_json::Value>(input).map_err(Error::decode_json)?;
168    parse_har_value(value)
169}
170
171/// Deserialize a HAR from a string slice.
172///
173/// ```
174/// use har::{from_str, to_json, HarVersion};
175///
176/// let input = r#"{
177///   "log": {
178///     "version": "1.2",
179///     "creator": { "name": "example", "version": "1.0" },
180///     "entries": []
181///   }
182/// }"#;
183///
184/// let har = from_str(input)?;
185/// assert_eq!(har.version(), HarVersion::V1_2);
186/// assert!(to_json(&har)?.contains("\"version\": \"1.2\""));
187/// # Ok::<(), har::Error>(())
188/// ```
189pub fn from_str(input: &str) -> Result<Har, Error> {
190    from_slice(input.as_bytes())
191}
192
193/// Deserialize a HAR from a type which implements `Read`.
194pub fn from_reader<R>(mut reader: R) -> Result<Har, Error>
195where
196    R: Read,
197{
198    let mut bytes = Vec::new();
199    reader.read_to_end(&mut bytes).map_err(Error::read)?;
200    from_slice(&bytes)
201}
202
203/// Serialize a HAR to a YAML string.
204///
205/// Available with the crate feature `yaml`.
206///
207/// ```
208/// use har::{from_str, to_yaml};
209///
210/// let input = r#"{
211///   "log": {
212///     "version": "1.2",
213///     "creator": { "name": "example", "version": "1.0" },
214///     "entries": []
215///   }
216/// }"#;
217///
218/// let har = from_str(input)?;
219/// let yaml = to_yaml(&har)?;
220/// assert!(yaml.contains("version"));
221/// # Ok::<(), har::Error>(())
222/// ```
223#[cfg(feature = "yaml")]
224#[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
225pub fn to_yaml(spec: &Har) -> Result<String, Error> {
226    yaml_serde::to_string(spec).map_err(Error::encode_yaml)
227}
228
229/// Serialize a HAR to a JSON string.
230pub fn to_json(spec: &Har) -> Result<String, Error> {
231    serde_json::to_string_pretty(spec).map_err(Error::encode_json)
232}
233
234fn parse_har_value(value: serde_json::Value) -> Result<Har, Error> {
235    let root = value.as_object().ok_or(Error::MissingLog)?;
236    let log_value = root.get("log").cloned().ok_or(Error::MissingLog)?;
237
238    let Some(log_object) = log_value.as_object() else {
239        return Err(Error::MissingLog);
240    };
241
242    let version = match log_object
243        .get("version")
244        .and_then(serde_json::Value::as_str)
245    {
246        Some("1.2") => HarVersion::V1_2,
247        Some("1.3") => HarVersion::V1_3,
248        Some(other) => return Err(Error::UnsupportedVersion(other.to_owned())),
249        None => return Err(Error::MissingVersion),
250    };
251
252    let log = match version {
253        HarVersion::V1_2 => {
254            let log = serde_json::from_value(log_value).map_err(Error::decode_json)?;
255            Spec::V1_2(log)
256        }
257        HarVersion::V1_3 => {
258            let log = serde_json::from_value(log_value).map_err(Error::decode_json)?;
259            Spec::V1_3(log)
260        }
261    };
262
263    Ok(Har { log })
264}