1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use 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#[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#[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#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
122#[serde(tag = "version")]
123pub enum Spec {
124 #[allow(non_camel_case_types)]
130 #[serde(rename = "1.2")]
131 V1_2(v1_2::Log),
132
133 #[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
157pub 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
165pub 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
171pub fn from_str(input: &str) -> Result<Har, Error> {
190 from_slice(input.as_bytes())
191}
192
193pub 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#[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
229pub 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}