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
18#[derive(thiserror::Error, Debug)]
20pub enum Error {
21 #[error("failed to read HAR input")]
22 Read {
23 #[source]
24 source: std::io::Error,
25 },
26 #[error("failed to decode HAR JSON")]
27 DecodeJson {
28 #[source]
29 source: serde_json::Error,
30 },
31 #[cfg(feature = "yaml")]
32 #[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
33 #[error("failed to encode HAR as YAML")]
34 EncodeYaml {
35 #[source]
36 source: yaml_serde::Error,
37 },
38 #[error("failed to encode HAR as JSON")]
39 EncodeJson {
40 #[source]
41 source: serde_json::Error,
42 },
43 #[error("HAR document must contain a top-level `log` object")]
44 MissingLog,
45 #[error("HAR document is missing `log.version`")]
46 MissingVersion,
47 #[error("unsupported HAR version `{0}`")]
48 UnsupportedVersion(String),
49}
50
51impl Error {
52 fn read(source: std::io::Error) -> Self {
53 Self::Read { source }
54 }
55
56 fn decode_json(source: serde_json::Error) -> Self {
57 Self::DecodeJson { source }
58 }
59
60 #[cfg(feature = "yaml")]
61 fn encode_yaml(source: yaml_serde::Error) -> Self {
62 Self::EncodeYaml { source }
63 }
64
65 fn encode_json(source: serde_json::Error) -> Self {
66 Self::EncodeJson { source }
67 }
68}
69
70#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72pub enum HarVersion {
73 V1_2,
74 V1_3,
75}
76
77impl HarVersion {
78 pub const fn as_str(self) -> &'static str {
79 match self {
80 Self::V1_2 => "1.2",
81 Self::V1_3 => "1.3",
82 }
83 }
84}
85
86impl fmt::Display for HarVersion {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 f.write_str(self.as_str())
89 }
90}
91
92#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
98#[serde(tag = "version")]
99pub enum Spec {
100 #[allow(non_camel_case_types)]
106 #[serde(rename = "1.2")]
107 V1_2(v1_2::Log),
108
109 #[allow(non_camel_case_types)]
115 #[serde(rename = "1.3")]
116 V1_3(v1_3::Log),
117}
118
119#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
120pub struct Har {
121 pub log: Spec,
122}
123
124impl Har {
125 pub fn version(&self) -> HarVersion {
126 match &self.log {
127 Spec::V1_2(_) => HarVersion::V1_2,
128 Spec::V1_3(_) => HarVersion::V1_3,
129 }
130 }
131}
132
133pub fn from_path<P>(path: P) -> Result<Har, Error>
135where
136 P: AsRef<Path>,
137{
138 from_reader(File::open(path).map_err(Error::read)?)
139}
140
141pub fn from_slice(input: &[u8]) -> Result<Har, Error> {
143 let value = serde_json::from_slice::<serde_json::Value>(input).map_err(Error::decode_json)?;
144 parse_har_value(value)
145}
146
147pub fn from_str(input: &str) -> Result<Har, Error> {
166 from_slice(input.as_bytes())
167}
168
169pub fn from_reader<R>(mut reader: R) -> Result<Har, Error>
171where
172 R: Read,
173{
174 let mut bytes = Vec::new();
175 reader.read_to_end(&mut bytes).map_err(Error::read)?;
176 from_slice(&bytes)
177}
178
179#[cfg(feature = "yaml")]
200#[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
201pub fn to_yaml(spec: &Har) -> Result<String, Error> {
202 yaml_serde::to_string(spec).map_err(Error::encode_yaml)
203}
204
205pub fn to_json(spec: &Har) -> Result<String, Error> {
207 serde_json::to_string_pretty(spec).map_err(Error::encode_json)
208}
209
210fn parse_har_value(value: serde_json::Value) -> Result<Har, Error> {
211 let root = value.as_object().ok_or(Error::MissingLog)?;
212 let log_value = root.get("log").cloned().ok_or(Error::MissingLog)?;
213
214 let Some(log_object) = log_value.as_object() else {
215 return Err(Error::MissingLog);
216 };
217
218 let version = match log_object
219 .get("version")
220 .and_then(serde_json::Value::as_str)
221 {
222 Some("1.2") => HarVersion::V1_2,
223 Some("1.3") => HarVersion::V1_3,
224 Some(other) => return Err(Error::UnsupportedVersion(other.to_owned())),
225 None => return Err(Error::MissingVersion),
226 };
227
228 let log = match version {
229 HarVersion::V1_2 => {
230 let log = serde_json::from_value(log_value).map_err(Error::decode_json)?;
231 Spec::V1_2(log)
232 }
233 HarVersion::V1_3 => {
234 let log = serde_json::from_value(log_value).map_err(Error::decode_json)?;
235 Spec::V1_3(log)
236 }
237 };
238
239 Ok(Har { log })
240}