├── .clog.toml ├── .github └── workflows │ └── workflow.yaml ├── .gitignore ├── Cargo.toml ├── Changelog.md ├── LICENSE ├── README.md ├── examples └── printer.rs ├── src ├── lib.rs ├── v1_2 │ └── mod.rs └── v1_3 │ └── mod.rs └── tests └── fixtures ├── someapi123.har └── someapi13.har /.clog.toml: -------------------------------------------------------------------------------- 1 | [clog] 2 | 3 | [sections] 4 | "Bug Fixes" = ["fx", "fix"] 5 | Builds = ["build"] 6 | Chores = ["chore"] 7 | "Continuous Integrations" = ["ci"] 8 | Documentation = ["docs"] 9 | Features = ["ft", "feat", "feature"] 10 | Refactorings = ["ref", "refactor"] 11 | Reverts = ["rev", "revert"] 12 | Styles = ["style"] 13 | Tests = ["test"] 14 | "Performance Improvements" = ["perf", "performance"] 15 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | check: 13 | name: Check 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | rust: 18 | - stable 19 | 20 | steps: 21 | - name: Checkout Repo 22 | uses: actions/checkout@v2 23 | - uses: actions/cache@v2 24 | with: 25 | path: | 26 | ~/.cargo/registry 27 | ~/.cargo/git 28 | ~/.cargo/bin 29 | target 30 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 31 | restore-keys: | 32 | ${{ runner.os }}-cargo- 33 | - name: Setup toolchain 34 | uses: actions-rs/toolchain@v1 35 | with: 36 | profile: minimal 37 | toolchain: ${{ matrix.rust }} 38 | components: rustfmt, clippy 39 | override: true 40 | - name: Check 41 | uses: actions-rs/cargo@v1 42 | with: 43 | command: check 44 | - name: Rustfmt 45 | uses: actions-rs/cargo@v1 46 | with: 47 | command: fmt 48 | args: --all -- --check 49 | - name: Clippy 50 | uses: actions-rs/cargo@v1 51 | with: 52 | command: clippy 53 | args: -- -D warnings 54 | - name: Test 55 | uses: actions-rs/cargo@v1 56 | with: 57 | command: test 58 | 59 | # release: 60 | # name: Release 61 | # needs: check 62 | # runs-on: ubuntu-latest 63 | # if: github.event_name == 'push' && github.ref == 'refs/heads/master' 64 | # steps: 65 | # - name: Checkout Repo 66 | # uses: actions/checkout@v2 67 | # - uses: actions/cache@v2 68 | # with: 69 | # path: | 70 | # ~/.cargo/registry 71 | # ~/.cargo/git 72 | # ~/.cargo/bin 73 | # target 74 | # key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 75 | # - name: Install semantic-rs 76 | # run: | 77 | # cargo install --git https://github.com/mandrean/semantic-rs 78 | # - name: Semantic Release 79 | # run: semantic-rs -r yes -w yes 80 | # env: 81 | # CI: true 82 | # RUST_LOG: debug 83 | # GH_TOKEN: ${{ secrets.GH_TOKEN }} 84 | # CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} 85 | # GIT_COMMITTER_NAME: semantic-rs 86 | # GIT_COMMITTER_EMAIL: semantic@rs 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | **/*.parsed 5 | **/*.pretty 6 | .idea/ 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "har" 3 | version = "0.8.1" 4 | authors = ["Sebastian Mandrean "] 5 | edition = "2021" 6 | description = "A HTTP Archive format (HAR) serialization & deserialization library." 7 | license = "MIT" 8 | repository = "https://github.com/mandrean/har-rs" 9 | documentation = "https://docs.rs/har" 10 | readme = "README.md" 11 | keywords = ["har", "serialization", "deserialization", "json", "yaml"] 12 | 13 | [dependencies] 14 | serde = { version = "1.0", features = ["derive"] } 15 | serde_json = "1.0" 16 | serde_with = "3.11" 17 | serde_yaml = "0.9" 18 | thiserror = "1.0" 19 | 20 | [dev-dependencies] 21 | glob = "0.3" 22 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | 2 | ## v0.8.1 (2024-10-15) 3 | 4 | 5 | 6 | 7 | 8 | ## v0.8.0 (2023-01-08) 9 | 10 | 11 | 12 | 13 | 14 | ## v0.7.1 (2021-10-18) 15 | 16 | 17 | #### Chores 18 | 19 | * Remove unused dep ([fd10256a](fd10256a)) 20 | * Manually patch the changelog ([d9dd610e](d9dd610e)) 21 | 22 | #### Bug Fixes 23 | 24 | * Make some fields optional (#22) ([ccdcfca4](ccdcfca4)) 25 | 26 | #### Documentation 27 | 28 | * Clarify library usage/documentation ([21f6a917](21f6a917)) 29 | 30 | 31 | 32 | 33 | ## v0.7.0 (2021-03-02) 34 | 35 | 36 | #### Documentation 37 | 38 | * Bump version in README ([3516ab8d](3516ab8d)) 39 | 40 | #### Refactorings 41 | 42 | * Switch to using thiserror ([d162c8ba](d162c8ba)) 43 | 44 | #### Breaking Changes 45 | 46 | * Switch to using thiserror ([d162c8ba](d162c8ba)) 47 | 48 | #### Chores 49 | 50 | * Bump deps ([a76b71be](a76b71be)) 51 | 52 | #### Continuous Integrations 53 | 54 | * Add cache restore keys ([dfabe51b](dfabe51b)) 55 | * Disable continuous delivery for now ([043dbd72](043dbd72)) 56 | 57 | 58 | 59 | 60 | ## v0.6.1 (2020-10-13) 61 | 62 | 63 | #### Bug Fixes 64 | 65 | * Revert "Check in lockfile" (#16) ([0c5ed643](0c5ed643)) 66 | 67 | #### Continuous Integrations 68 | 69 | * Explicitly set semantic-rs modes ([10cc3d2d](10cc3d2d)) 70 | 71 | 72 | 73 | 74 | ## v0.6.0 (2020-10-13) 75 | 76 | 77 | #### Continuous Integrations 78 | 79 | * Set up GitHub Actions V2 (#15) ([da5dd0be](da5dd0be)) 80 | * Switch to mandrean/semantic-rs fork ([c16c53aa](c16c53aa)) 81 | 82 | #### Refactorings 83 | 84 | * Remove url_serde ([7b28586c](7b28586c)) 85 | * Use serde_with to remove repetitive annotations ([e18e6bb8](e18e6bb8)) 86 | * Make postData.text optional ([c877f81b](c877f81b)) 87 | * Remove _charlesStatus residues ([b36733f4](b36733f4)) 88 | 89 | #### Documentation 90 | 91 | * Bump version in README ([d02f4cfc](d02f4cfc)) 92 | 93 | #### Breaking Changes 94 | 95 | * Make postData.text optional ([c877f81b](c877f81b)) 96 | * Remove _charlesStatus residues ([b36733f4](b36733f4)) 97 | 98 | #### Tests 99 | 100 | * Fix regression in fixtures ([6e884adc](6e884adc)) 101 | 102 | #### Chores 103 | 104 | * Remove deprecated badges section ([004dfc6b](004dfc6b)) 105 | * Check in lockfile ([945247b4](945247b4)) 106 | * Bump dependencies ([46f1fb00](46f1fb00)) 107 | 108 | 109 | 110 | 111 | ## v0.5.0 (2020-05-04) 112 | 113 | 114 | #### Bug Fixes 115 | 116 | * Port HAR v1.2 fp metrics fixes to HAR v1.3 (#12) ([5c60c183](5c60c183)) 117 | 118 | #### Breaking Changes 119 | 120 | * Port HAR v1.2 fp metrics fixes to HAR v1.3 (#12) ([5c60c183](5c60c183)) 121 | 122 | 123 | 124 | 125 | ## v0.4.0 (2020-05-04) 126 | 127 | 128 | #### Breaking Changes 129 | 130 | * Allow floating point number in timing metrics (#8) ([4773e5ea](4773e5ea)) 131 | 132 | #### Documentation 133 | 134 | * Generate new changelog for v0.3.0 using custom Clog config ([60feb71a](60feb71a)) 135 | 136 | #### Refactorings 137 | 138 | * Allow floating point number in timing metrics (#8) ([4773e5ea](4773e5ea)) 139 | 140 | #### Builds 141 | 142 | * Add Clog config ([b1ac66a3](b1ac66a3)) 143 | 144 | 145 | 146 | 147 | ## v0.3.0 (2019-05-09) 148 | 149 | 150 | #### Builds 151 | 152 | * Add Clog config ([b1ac66a3](b1ac66a3)) 153 | 154 | #### Continuous Integrations 155 | 156 | * Set CI envvar to true ([392bce6b](392bce6b)) 157 | 158 | #### Chores 159 | 160 | * Allow deprecated error_chain ([c52b3c9d](c52b3c9d)) 161 | * Bump glob dev dependency ([af20707f](af20707f)) 162 | 163 | #### Documentation 164 | 165 | * Bump version in README ([b7423f91](b7423f91)) 166 | 167 | 168 | 169 | 170 | ## v0.2.0 (2019-02-14) 171 | 172 | 173 | #### Features 174 | 175 | * Support HAR v1.3 proposal (#5) ([3d7c529b](3d7c529b)) 176 | 177 | 178 | 179 | 180 | ## v0.1.0 (2018-11-16) 181 | 182 | 183 | #### Features 184 | 185 | * Create library (#1) ([216c5f5e](216c5f5e)) 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2020 Sebastian Mandrean 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | har-rs 2 | ====== 3 | [HTTP Archive format (HAR)][har] serialization & deserialization library, written in Rust. 4 | 5 | 6 | [![Latest version](https://img.shields.io/crates/v/har.svg)](https://crates.io/crates/har) 7 | [![Documentation](https://docs.rs/har/badge.svg)](https://docs.rs/har) 8 | ![License](https://img.shields.io/crates/l/har.svg) 9 | 10 | Install 11 | ------- 12 | Add the following to your `Cargo.toml` file: 13 | 14 | ```toml 15 | [dependencies] 16 | har = "0.8" 17 | ``` 18 | 19 | Use 20 | --- 21 | Simplest possible example: 22 | ```rust 23 | use har::from_path; 24 | 25 | fn main() { 26 | match har::from_path("path/to/file.har") { 27 | Ok(spec) => println!("spec: {:?}", spec), 28 | Err(err) => println!("error: {}", err) 29 | } 30 | } 31 | ``` 32 | 33 | See [docs.rs/har] for the full library API documentation. 34 | 35 | Contribute 36 | ---------- 37 | This project follows [semver], [conventional commits] and semantic releasing using [mandrean/semantic-rs]. 38 | 39 | Note 40 | ---- 41 | Inspired by [softprops/openapi](https://github.com/softprops/openapi). 42 | 43 | [conventional commits]: https://www.conventionalcommits.org 44 | [docs.rs/har]: https://docs.rs/har 45 | [har]: https://en.wikipedia.org/wiki/.har 46 | [mandrean/semantic-rs]: https://github.com/mandrean/semantic-rs 47 | [semver]: https://semver.org/ 48 | -------------------------------------------------------------------------------- /examples/printer.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code, unused_imports)] 2 | use har::{from_path, to_json, Error}; 3 | use std::io::Write; 4 | 5 | fn main() { 6 | if let Some(path) = std::env::args().nth(1) { 7 | match har::from_path(path) { 8 | Ok(spec) => { 9 | /*for (path, op) in spec.paths { 10 | println!("{}", path); 11 | println!("{:#?}", op); 12 | } 13 | for (name, definition) in spec.definitions { 14 | println!("{}", name); 15 | println!("{:#?}", definition); 16 | }*/ 17 | println!("{}", har::to_json(&spec).unwrap()); 18 | } 19 | Err(e) => { 20 | match e { 21 | Error::Io(e) => eprintln!("{}", e), 22 | Error::Yaml(e) => eprintln!("{}", e), 23 | Error::Json(e) => eprintln!("{}", e), 24 | } 25 | 26 | ::std::process::exit(1); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use std::fs::File; 4 | use std::io::Read; 5 | use std::path::Path; 6 | 7 | pub mod v1_2; 8 | pub mod v1_3; 9 | 10 | /// Errors that HAR functions may return 11 | #[derive(thiserror::Error, Debug)] 12 | pub enum Error { 13 | #[error("error reading file")] 14 | Io(#[from] ::std::io::Error), 15 | #[error("error serializing YAML")] 16 | Yaml(#[from] ::serde_yaml::Error), 17 | #[error("error serializing JSON")] 18 | Json(#[from] ::serde_json::Error), 19 | } 20 | 21 | /// Supported versions of HAR. 22 | /// 23 | /// Note that point releases require adding here (as they must other wise they wouldn't need a new version) 24 | /// Using untagged can avoid that but the errors on incompatible documents become super hard to debug. 25 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] 26 | #[serde(tag = "version")] 27 | pub enum Spec { 28 | /// Version 1.2 of the HAR specification. 29 | /// 30 | /// Refer to the official 31 | /// [specification](https://w3c.github.io/web-performance/specs/HAR/Overview.html) 32 | /// for more information. 33 | #[allow(non_camel_case_types)] 34 | #[serde(rename = "1.2")] 35 | V1_2(v1_2::Log), 36 | 37 | // Version 1.3 of the HAR specification. 38 | // 39 | // Refer to the draft 40 | // [specification](https://github.com/ahmadnassri/har-spec/blob/master/versions/1.3.md) 41 | // for more information. 42 | #[allow(non_camel_case_types)] 43 | #[serde(rename = "1.3")] 44 | V1_3(v1_3::Log), 45 | } 46 | 47 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] 48 | pub struct Har { 49 | pub log: Spec, 50 | } 51 | 52 | /// Deserialize a HAR from a path 53 | pub fn from_path

(path: P) -> Result 54 | where 55 | P: AsRef, 56 | { 57 | from_reader(File::open(path)?) 58 | } 59 | 60 | /// Deserialize a HAR from type which implements Read 61 | pub fn from_reader(read: R) -> Result 62 | where 63 | R: Read, 64 | { 65 | Ok(serde_json::from_reader::(read)?) 66 | } 67 | 68 | /// Serialize HAR spec to a YAML string 69 | pub fn to_yaml(spec: &Har) -> Result { 70 | Ok(serde_yaml::to_string(spec)?) 71 | } 72 | 73 | /// Serialize HAR spec to JSON string 74 | pub fn to_json(spec: &Har) -> Result { 75 | Ok(serde_json::to_string_pretty(spec)?) 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use super::*; 81 | use glob::glob; 82 | use std::fs::File; 83 | use std::io::Write; 84 | 85 | const FIXTURES_GLOB: &str = "tests/fixtures/*.har"; 86 | 87 | /// Helper function for reading a file to string. 88 | fn read_file

(path: P) -> String 89 | where 90 | P: AsRef, 91 | { 92 | let mut f = File::open(path).unwrap(); 93 | let mut content = String::new(); 94 | f.read_to_string(&mut content).unwrap(); 95 | content 96 | } 97 | 98 | /// Helper function to write string to file. 99 | fn write_to_file

(path: P, filename: &str, data: &str) 100 | where 101 | P: AsRef + std::fmt::Debug, 102 | { 103 | println!(" Saving string to {:?}...", path); 104 | std::fs::create_dir_all(&path).unwrap(); 105 | let full_filename = path.as_ref().to_path_buf().join(filename); 106 | let mut f = File::create(full_filename).unwrap(); 107 | f.write_all(data.as_bytes()).unwrap(); 108 | } 109 | 110 | /// Convert a YAML `&str` to a JSON `String`. 111 | fn convert_yaml_str_to_json(yaml_str: &str) -> String { 112 | let yaml: serde_yaml::Value = serde_yaml::from_str(yaml_str).unwrap(); 113 | let json: serde_json::Value = serde_yaml::from_value(yaml).unwrap(); 114 | serde_json::to_string_pretty(&json).unwrap() 115 | } 116 | 117 | /// Deserialize and re-serialize the input file to a JSON string through two different 118 | /// paths, comparing the result. 119 | /// 1. File -> `String` -> `serde_yaml::Value` -> `serde_json::Value` -> `String` 120 | /// 2. File -> `Spec` -> `serde_json::Value` -> `String` 121 | /// Both conversion of `serde_json::Value` -> `String` are done 122 | /// using `serde_json::to_string_pretty`. 123 | /// Since the first conversion is independent of the current crate (and only 124 | /// uses serde's json and yaml support), no information should be lost in the final 125 | /// JSON string. The second conversion goes through our `Har`, so the final JSON 126 | /// string is a representation of _our_ implementation. 127 | /// By comparing those two JSON conversions, we can validate our implementation. 128 | fn compare_spec_through_json( 129 | input_file: &Path, 130 | save_path_base: &Path, 131 | ) -> (String, String, String) { 132 | // First conversion: 133 | // File -> `String` -> `serde_yaml::Value` -> `serde_json::Value` -> `String` 134 | 135 | // Read the original file to string 136 | let spec_yaml_str = read_file(input_file); 137 | // Convert YAML string to JSON string 138 | let spec_json_str = convert_yaml_str_to_json(&spec_yaml_str); 139 | 140 | // Second conversion: 141 | // File -> `Spec` -> `serde_json::Value` -> `String` 142 | 143 | // Parse the input file 144 | let parsed_spec = from_path(input_file).unwrap(); 145 | // Convert to serde_json::Value 146 | let parsed_spec_json: serde_json::Value = serde_json::to_value(parsed_spec).unwrap(); 147 | // Convert to a JSON string 148 | let parsed_spec_json_str: String = serde_json::to_string_pretty(&parsed_spec_json).unwrap(); 149 | 150 | // Save JSON strings to file 151 | let api_filename = input_file 152 | .file_name() 153 | .unwrap() 154 | .to_str() 155 | .unwrap() 156 | .replace(".yaml", ".json"); 157 | 158 | let mut save_path = save_path_base.to_path_buf(); 159 | save_path.push("yaml_to_json"); 160 | write_to_file(&save_path, &api_filename, &spec_json_str); 161 | 162 | let mut save_path = save_path_base.to_path_buf(); 163 | save_path.push("yaml_to_spec_to_json"); 164 | write_to_file(&save_path, &api_filename, &parsed_spec_json_str); 165 | 166 | // Return the JSON filename and the two JSON strings 167 | (api_filename, parsed_spec_json_str, spec_json_str) 168 | } 169 | 170 | // Makes sure the paths to the test fixtures works on this platform 171 | #[test] 172 | fn can_find_test_fixtures() { 173 | let fixture_count = glob(FIXTURES_GLOB) 174 | .expect("Failed to read glob pattern") 175 | .filter(|e| e.is_ok()) 176 | .count(); 177 | assert_ne!(0, fixture_count); 178 | } 179 | 180 | // Just tests if the deserialization does not blow up. But does not test correctness 181 | #[test] 182 | fn can_deserialize() { 183 | for entry in glob(FIXTURES_GLOB).expect("Failed to read glob pattern") { 184 | let entry = entry.unwrap(); 185 | let path = entry.as_path(); 186 | // cargo test -- --nocapture to see this message 187 | println!("Testing if {:?} is deserializable", path); 188 | from_path(path).unwrap(); 189 | } 190 | } 191 | 192 | #[test] 193 | fn can_deserialize_and_reserialize() { 194 | let save_path_base: std::path::PathBuf = 195 | ["target", "tests", "can_deserialize_and_reserialize"] 196 | .iter() 197 | .collect(); 198 | let mut invalid_diffs = Vec::new(); 199 | 200 | for entry in glob(FIXTURES_GLOB).expect("Failed to read glob pattern") { 201 | let entry = entry.unwrap(); 202 | let path = entry.as_path(); 203 | 204 | println!("Testing if {:?} is deserializable", path); 205 | 206 | let (api_filename, parsed_spec_json_str, spec_json_str) = 207 | compare_spec_through_json(path, &save_path_base); 208 | 209 | if parsed_spec_json_str != spec_json_str { 210 | invalid_diffs.push(( 211 | api_filename, 212 | parsed_spec_json_str.clone(), 213 | spec_json_str.clone(), 214 | )); 215 | File::create(path.with_extension("parsed")) 216 | .unwrap() 217 | .write_all(parsed_spec_json_str.as_bytes()) 218 | .unwrap(); 219 | File::create(path.with_extension("pretty")) 220 | .unwrap() 221 | .write_all(spec_json_str.as_bytes()) 222 | .unwrap(); 223 | } 224 | } 225 | 226 | for invalid_diff in &invalid_diffs { 227 | println!("File {} failed JSON comparison!", invalid_diff.0); 228 | } 229 | assert_eq!(invalid_diffs.len(), 0); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/v1_2/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_with::skip_serializing_none; 3 | 4 | #[skip_serializing_none] 5 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 6 | pub struct Log { 7 | pub creator: Creator, 8 | pub browser: Option, 9 | pub pages: Option>, 10 | pub entries: Vec, 11 | pub comment: Option, 12 | } 13 | 14 | #[skip_serializing_none] 15 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 16 | pub struct Creator { 17 | pub name: String, 18 | pub version: String, 19 | pub comment: Option, 20 | } 21 | 22 | #[skip_serializing_none] 23 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 24 | pub struct Pages { 25 | #[serde(rename = "startedDateTime")] 26 | pub started_date_time: String, 27 | pub id: String, 28 | pub title: String, 29 | #[serde(rename = "pageTimings")] 30 | pub page_timings: PageTimings, 31 | pub comment: Option, 32 | } 33 | 34 | #[skip_serializing_none] 35 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 36 | pub struct PageTimings { 37 | #[serde(rename = "onContentLoad", default = "default_fsize_maybe")] 38 | pub on_content_load: Option, 39 | #[serde(rename = "onLoad", default = "default_fsize_maybe")] 40 | pub on_load: Option, 41 | pub comment: Option, 42 | } 43 | 44 | #[skip_serializing_none] 45 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 46 | pub struct Entries { 47 | pub pageref: Option, 48 | #[serde(rename = "startedDateTime")] 49 | pub started_date_time: String, 50 | pub time: f64, 51 | pub request: Request, 52 | pub response: Response, 53 | pub cache: Cache, 54 | pub timings: Timings, 55 | #[serde(rename = "serverIPAddress")] 56 | pub server_ip_address: Option, 57 | pub connection: Option, 58 | pub comment: Option, 59 | } 60 | 61 | #[skip_serializing_none] 62 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 63 | pub struct Request { 64 | pub method: String, 65 | pub url: String, 66 | #[serde(rename = "httpVersion")] 67 | pub http_version: String, 68 | pub cookies: Vec, 69 | pub headers: Vec, 70 | #[serde(rename = "queryString")] 71 | pub query_string: Vec, 72 | #[serde(rename = "postData")] 73 | pub post_data: Option, 74 | #[serde(rename = "headersSize", deserialize_with = "de_default_isize")] 75 | pub headers_size: i64, 76 | #[serde(rename = "bodySize", default = "default_isize")] 77 | pub body_size: i64, 78 | pub comment: Option, 79 | } 80 | 81 | #[skip_serializing_none] 82 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 83 | pub struct Headers { 84 | pub name: String, 85 | pub value: String, 86 | pub comment: Option, 87 | } 88 | 89 | #[skip_serializing_none] 90 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 91 | pub struct Cookies { 92 | pub name: String, 93 | pub value: String, 94 | pub path: Option, 95 | pub domain: Option, 96 | pub expires: Option, 97 | #[serde(rename = "httpOnly")] 98 | pub http_only: Option, 99 | pub secure: Option, 100 | pub comment: Option, 101 | } 102 | 103 | #[skip_serializing_none] 104 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 105 | pub struct QueryString { 106 | pub name: String, 107 | pub value: String, 108 | pub comment: Option, 109 | } 110 | 111 | #[skip_serializing_none] 112 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 113 | pub struct PostData { 114 | #[serde(rename = "mimeType")] 115 | pub mime_type: String, 116 | /// Either text or params but not both : TODO turn into an untagged enum 117 | pub text: Option, 118 | pub params: Option>, 119 | pub comment: Option, 120 | } 121 | 122 | #[skip_serializing_none] 123 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 124 | pub struct Params { 125 | pub name: String, 126 | pub value: Option, 127 | #[serde(rename = "fileName")] 128 | pub file_name: Option, 129 | #[serde(rename = "contentType")] 130 | pub content_type: Option, 131 | pub comment: Option, 132 | } 133 | 134 | #[skip_serializing_none] 135 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 136 | pub struct Response { 137 | pub status: i64, 138 | #[serde(rename = "statusText")] 139 | pub status_text: String, 140 | #[serde(rename = "httpVersion")] 141 | pub http_version: String, 142 | pub cookies: Vec, 143 | pub headers: Vec, 144 | pub content: Content, 145 | #[serde(rename = "redirectURL")] 146 | pub redirect_url: Option, 147 | #[serde(rename = "headersSize", default = "default_isize")] 148 | pub headers_size: i64, 149 | #[serde(rename = "bodySize", default = "default_isize")] 150 | pub body_size: i64, 151 | pub comment: Option, 152 | } 153 | 154 | #[skip_serializing_none] 155 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 156 | pub struct Content { 157 | #[serde(default = "default_isize")] 158 | pub size: i64, 159 | pub compression: Option, 160 | #[serde(rename = "mimeType")] 161 | pub mime_type: Option, 162 | pub text: Option, 163 | pub encoding: Option, 164 | pub comment: Option, 165 | } 166 | 167 | #[skip_serializing_none] 168 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 169 | pub struct Cache { 170 | #[serde(rename = "beforeRequest")] 171 | pub before_request: Option, 172 | #[serde(rename = "afterRequest")] 173 | pub after_request: Option, 174 | } 175 | 176 | #[skip_serializing_none] 177 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 178 | pub struct CacheEntity { 179 | pub expires: Option, 180 | #[serde(rename = "lastAccess")] 181 | pub last_access: String, 182 | #[serde(rename = "eTag")] 183 | pub e_tag: String, 184 | #[serde(rename = "hitCount")] 185 | pub hit_count: i64, 186 | pub comment: Option, 187 | } 188 | 189 | #[skip_serializing_none] 190 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 191 | pub struct Timings { 192 | #[serde(default = "default_fsize_maybe")] 193 | pub blocked: Option, 194 | #[serde(default = "default_fsize_maybe")] 195 | pub dns: Option, 196 | #[serde(default = "default_fsize_maybe")] 197 | pub connect: Option, 198 | pub send: f64, 199 | pub wait: f64, 200 | pub receive: f64, 201 | #[serde(default = "default_fsize_maybe")] 202 | pub ssl: Option, 203 | pub comment: Option, 204 | } 205 | 206 | fn de_default_isize<'de, D>(deserializer: D) -> Result 207 | where 208 | D: serde::Deserializer<'de>, 209 | { 210 | Ok(Option::::deserialize(deserializer)?.unwrap_or(-1)) 211 | } 212 | 213 | fn default_isize() -> i64 { 214 | -1 215 | } 216 | 217 | fn default_fsize_maybe() -> Option { 218 | Some(-1_f64) 219 | } 220 | -------------------------------------------------------------------------------- /src/v1_3/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_with::skip_serializing_none; 3 | 4 | #[skip_serializing_none] 5 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 6 | pub struct Log { 7 | pub creator: Creator, 8 | pub browser: Option, 9 | pub pages: Option>, 10 | pub entries: Vec, 11 | pub comment: Option, 12 | } 13 | 14 | #[skip_serializing_none] 15 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 16 | pub struct Creator { 17 | pub name: String, 18 | pub version: String, 19 | pub comment: Option, 20 | } 21 | 22 | #[skip_serializing_none] 23 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 24 | pub struct Pages { 25 | #[serde(rename = "startedDateTime")] 26 | pub started_date_time: String, 27 | pub id: String, 28 | pub title: String, 29 | #[serde(rename = "pageTimings")] 30 | pub page_timings: PageTimings, 31 | pub comment: Option, 32 | } 33 | 34 | #[skip_serializing_none] 35 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 36 | pub struct PageTimings { 37 | #[serde(rename = "onContentLoad", default = "default_fsize_maybe")] 38 | pub on_content_load: Option, 39 | #[serde(rename = "onLoad", default = "default_fsize_maybe")] 40 | pub on_load: Option, 41 | pub comment: Option, 42 | } 43 | 44 | #[skip_serializing_none] 45 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 46 | pub struct Entries { 47 | pub pageref: Option, 48 | #[serde(rename = "startedDateTime")] 49 | pub started_date_time: String, 50 | pub time: f64, 51 | pub request: Request, 52 | pub response: Response, 53 | pub cache: Cache, 54 | pub timings: Timings, 55 | #[serde(rename = "serverIPAddress")] 56 | pub server_ip_address: Option, 57 | pub connection: Option, 58 | pub comment: Option, 59 | } 60 | 61 | #[skip_serializing_none] 62 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 63 | pub struct Request { 64 | pub method: String, 65 | pub url: String, 66 | #[serde(rename = "httpVersion")] 67 | pub http_version: String, 68 | pub cookies: Vec, 69 | pub headers: Vec, 70 | #[serde(rename = "queryString")] 71 | pub query_string: Vec, 72 | #[serde(rename = "postData")] 73 | pub post_data: Option, 74 | #[serde(rename = "headersSize", deserialize_with = "de_default_isize")] 75 | pub headers_size: i64, 76 | #[serde(rename = "bodySize", default = "default_isize")] 77 | pub body_size: i64, 78 | pub comment: Option, 79 | #[serde(rename = "headersCompression")] 80 | pub headers_compression: Option, 81 | } 82 | 83 | #[skip_serializing_none] 84 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 85 | pub struct Headers { 86 | pub name: String, 87 | pub value: String, 88 | pub comment: Option, 89 | } 90 | 91 | #[skip_serializing_none] 92 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 93 | pub struct Cookies { 94 | pub name: String, 95 | pub value: String, 96 | pub path: Option, 97 | pub domain: Option, 98 | pub expires: Option, 99 | #[serde(rename = "httpOnly")] 100 | pub http_only: Option, 101 | pub secure: Option, 102 | pub comment: Option, 103 | } 104 | 105 | #[skip_serializing_none] 106 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 107 | pub struct QueryString { 108 | pub name: String, 109 | pub value: String, 110 | pub comment: Option, 111 | } 112 | 113 | #[skip_serializing_none] 114 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 115 | pub struct PostData { 116 | #[serde(rename = "mimeType")] 117 | pub mime_type: String, 118 | /// Either text or params but not both : TODO turn into an untagged enum 119 | pub text: Option, 120 | pub params: Option>, 121 | pub comment: Option, 122 | pub encoding: Option, 123 | } 124 | 125 | #[skip_serializing_none] 126 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 127 | pub struct Params { 128 | pub name: String, 129 | pub value: Option, 130 | #[serde(rename = "fileName")] 131 | pub file_name: Option, 132 | #[serde(rename = "contentType")] 133 | pub content_type: Option, 134 | pub comment: Option, 135 | pub encoding: Option, 136 | } 137 | 138 | #[skip_serializing_none] 139 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 140 | pub struct Response { 141 | pub status: i64, 142 | #[serde(rename = "statusText")] 143 | pub status_text: String, 144 | #[serde(rename = "httpVersion")] 145 | pub http_version: String, 146 | pub cookies: Vec, 147 | pub headers: Vec, 148 | pub content: Content, 149 | #[serde(rename = "redirectURL")] 150 | pub redirect_url: Option, 151 | #[serde(rename = "headersSize", default = "default_isize")] 152 | pub headers_size: i64, 153 | #[serde(rename = "bodySize", default = "default_isize")] 154 | pub body_size: i64, 155 | pub comment: Option, 156 | #[serde(rename = "headersCompression")] 157 | pub headers_compression: Option, 158 | } 159 | 160 | #[skip_serializing_none] 161 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 162 | pub struct Content { 163 | #[serde(default = "default_isize")] 164 | pub size: i64, 165 | pub compression: Option, 166 | #[serde(rename = "mimeType")] 167 | pub mime_type: Option, 168 | pub text: Option, 169 | pub encoding: Option, 170 | pub comment: Option, 171 | } 172 | 173 | #[skip_serializing_none] 174 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 175 | pub struct Cache { 176 | #[serde(rename = "beforeRequest")] 177 | pub before_request: Option, 178 | #[serde(rename = "afterRequest")] 179 | pub after_request: Option, 180 | } 181 | 182 | #[skip_serializing_none] 183 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 184 | pub struct CacheEntity { 185 | pub expires: Option, 186 | #[serde(rename = "lastAccess")] 187 | pub last_access: String, 188 | #[serde(rename = "eTag")] 189 | pub e_tag: String, 190 | #[serde(rename = "hitCount")] 191 | pub hit_count: i64, 192 | pub comment: Option, 193 | } 194 | 195 | #[skip_serializing_none] 196 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)] 197 | pub struct Timings { 198 | #[serde(default = "default_fsize_maybe")] 199 | pub blocked: Option, 200 | #[serde(default = "default_fsize_maybe")] 201 | pub dns: Option, 202 | #[serde(default = "default_fsize_maybe")] 203 | pub connect: Option, 204 | pub send: f64, 205 | pub wait: f64, 206 | pub receive: f64, 207 | #[serde(default = "default_fsize_maybe")] 208 | pub ssl: Option, 209 | pub comment: Option, 210 | } 211 | 212 | fn de_default_isize<'de, D>(deserializer: D) -> Result 213 | where 214 | D: serde::Deserializer<'de>, 215 | { 216 | Ok(Option::::deserialize(deserializer)?.unwrap_or(-1)) 217 | } 218 | 219 | fn default_isize() -> i64 { 220 | -1 221 | } 222 | 223 | fn default_fsize_maybe() -> Option { 224 | Some(-1_f64) 225 | } 226 | -------------------------------------------------------------------------------- /tests/fixtures/someapi123.har: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "version": "1.2", 4 | "creator": { 5 | "name": "Charles Proxy", 6 | "version": "4.2.7", 7 | "comment": "" 8 | }, 9 | "browser": { 10 | "name": "Firefox", 11 | "version": "3.6", 12 | "comment": "" 13 | }, 14 | "pages": [ 15 | { 16 | "startedDateTime": "2009-04-16T12:07:25.123+01:00", 17 | "id": "page_0", 18 | "title": "Test Page", 19 | "pageTimings": { 20 | "onContentLoad": 1720.0, 21 | "onLoad": 2500.0, 22 | "comment": "" 23 | }, 24 | "comment": "" 25 | } 26 | ], 27 | "comment": "", 28 | "entries": [ 29 | { 30 | "pageref": "page_0", 31 | "startedDateTime": "2018-11-06T15:11:23.305+01:00", 32 | "time": 72.0, 33 | "request": { 34 | "method": "GET", 35 | "url": "https://api.someapi123.io", 36 | "httpVersion": "HTTP/2.0", 37 | "cookies": [ 38 | { 39 | "name": "TestCookie", 40 | "value": "Cookie Value", 41 | "path": "/", 42 | "domain": "api.someapi123.io", 43 | "expires": "2009-07-24T19:20:30.123+02:00", 44 | "httpOnly": false, 45 | "secure": false, 46 | "comment": "" 47 | } 48 | ], 49 | "headers": [ 50 | { 51 | "name": ":method", 52 | "value": "GET" 53 | }, 54 | { 55 | "name": ":path", 56 | "value": "/config-reader/values/block-version" 57 | }, 58 | { 59 | "name": ":authority", 60 | "value": "api.someapi123.io" 61 | }, 62 | { 63 | "name": ":scheme", 64 | "value": "https" 65 | }, 66 | { 67 | "name": "accept-encoding", 68 | "value": "gzip" 69 | }, 70 | { 71 | "name": "user-agent", 72 | "value": "okhttp/3.9.1" 73 | } 74 | ], 75 | "queryString": [ 76 | { 77 | "name": "param1", 78 | "value": "value1", 79 | "comment": "" 80 | }, 81 | { 82 | "name": "param1", 83 | "value": "value1", 84 | "comment": "" 85 | } 86 | ], 87 | "headersSize": 7, 88 | "bodySize": 0, 89 | "comment" : "" 90 | }, 91 | "response": { 92 | "status": 200, 93 | "statusText": "OK", 94 | "httpVersion": "HTTP/2.0", 95 | "cookies": [ 96 | { 97 | "name": "TestCookie", 98 | "value": "Cookie Value", 99 | "path": "/", 100 | "domain": "api.someapi123.io", 101 | "expires": "2009-07-24T19:20:30.123+02:00", 102 | "httpOnly": false, 103 | "secure": false, 104 | "comment": "" 105 | } 106 | ], 107 | "headers": [ 108 | { 109 | "name": ":status", 110 | "value": "200" 111 | }, 112 | { 113 | "name": "content-type", 114 | "value": "application/json; charset=UTF-8" 115 | }, 116 | { 117 | "name": "date", 118 | "value": "Tue, 06 Nov 2018 14:11:24 GMT" 119 | }, 120 | { 121 | "name": "server", 122 | "value": "Apache" 123 | }, 124 | { 125 | "name": "access-control-allow-origin", 126 | "value": "null" 127 | }, 128 | { 129 | "name": "access-control-allow-methods", 130 | "value": "GET, POST, HEAD, OPTIONS" 131 | }, 132 | { 133 | "name": "access-control-max-age", 134 | "value": "3600" 135 | }, 136 | { 137 | "name": "access-control-allow-headers", 138 | "value": "x-requested-with, authorization, content-type, sessionToken, username, password, version" 139 | }, 140 | { 141 | "name": "x-content-type-options", 142 | "value": "nosniff" 143 | }, 144 | { 145 | "name": "x-xss-protection", 146 | "value": "1; mode=block" 147 | }, 148 | { 149 | "name": "cache-control", 150 | "value": "no-cache, no-store, max-age=0, must-revalidate" 151 | }, 152 | { 153 | "name": "pragma", 154 | "value": "no-cache" 155 | }, 156 | { 157 | "name": "expires", 158 | "value": "0" 159 | }, 160 | { 161 | "name": "strict-transport-security", 162 | "value": "max-age=31536000; includeSubDomains; preload" 163 | }, 164 | { 165 | "name": "x-frame-options", 166 | "value": "SAMEORIGIN" 167 | }, 168 | { 169 | "name": "artifactid", 170 | "value": "gateway" 171 | }, 172 | { 173 | "name": "user-agent", 174 | "value": "okhttp/3.9.1" 175 | }, 176 | { 177 | "name": "projectversion", 178 | "value": "gateway:1.6" 179 | }, 180 | { 181 | "name": "projectversion", 182 | "value": "config-reader:1.16" 183 | }, 184 | { 185 | "name": "artifactid", 186 | "value": "config-reader" 187 | }, 188 | { 189 | "name": "x-application-context", 190 | "value": "config-reader:native,aws,prod:8887" 191 | }, 192 | { 193 | "name": "user-agent", 194 | "value": "okhttp/3.9.1" 195 | }, 196 | { 197 | "name": "content-security-policy", 198 | "value": "upgrade-insecure-requests" 199 | }, 200 | { 201 | "name": "access-control-allow-credentials", 202 | "value": "false" 203 | }, 204 | { 205 | "name": "x-cache", 206 | "value": "Miss from cloudfront" 207 | } 208 | ], 209 | "content": { 210 | "size": 323, 211 | "mimeType": "application/json; charset=UTF-8", 212 | "text": "V2hhdCB0aGUgZnVjayBkaWQgeW91IGp1c3QgZnVja2luZyBzYXkgYWJvdXQgbWUsIHlvdSBsaXR0bGUgYml0Y2g/IEknbGwgaGF2ZSB5b3Uga25vdyBJIGdyYWR1YXRlZCB0b3Agb2YgbXkgY2xhc3MgaW4gdGhlIE5hdnkgU2VhbHMsIGFuZCBJJ3ZlIGJlZW4gaW52b2x2ZWQgaW4gbnVtZXJvdXMgc2VjcmV0IHJhaWRzIG9uIEFsLVF1YWVkYSwgYW5kIEkgaGF2ZSBvdmVyIDMwMCBjb25maXJtZWQga2lsbHMuIEkgYW0gdHJhaW5lZCBpbiBnb3JpbGxhIHdhcmZhcmUgYW5kIEknbSB0aGUgdG9wIHNuaXBlciBpbiB0aGUgZW50aXJlIFVTIGFybWVkIGZvcmNlcy4gWW91IGFyZSBub3RoaW5nIHRvIG1lIGJ1dCBqdXN0IGFub3RoZXIgdGFyZ2V0LiBJIHdpbGwgd2lwZSB5b3UgdGhlIGZ1Y2sgb3V0IHdpdGggcHJlY2lzaW9uIHRoZSBsaWtlcyBvZiB3aGljaCBoYXMgbmV2ZXIgYmVlbiBzZWVuIGJlZm9yZSBvbiB0aGlzIEVhcnRoLCBtYXJrIG15IGZ1Y2tpbmcgd29yZHMuIFlvdSB0aGluayB5b3UgY2FuIGdldCBhd2F5IHdpdGggc2F5aW5nIHRoYXQgc2hpdCB0byBtZSBvdmVyIHRoZSBJbnRlcm5ldD8gVGhpbmsgYWdhaW4sIGZ1Y2tlci4gQXMgd2Ugc3BlYWsgSSBhbSBjb250YWN0aW5nIG15IHNlY3JldCBuZXR3b3JrIG9mIHNwaWVzIGFjcm9zcyB0aGUgVVNBIGFuZCB5b3VyIElQIGlzIGJlaW5nIHRyYWNlZCByaWdodCBub3cgc28geW91IGJldHRlciBwcmVwYXJlIGZvciB0aGUgc3Rvcm0sIG1hZ2dvdC4gVGhlIHN0b3JtIHRoYXQgd2lwZXMgb3V0IHRoZSBwYXRoZXRpYyBsaXR0bGUgdGhpbmcgeW91IGNhbGwgeW91ciBsaWZlLiBZb3UncmUgZnVja2luZyBkZWFkLCBraWQuIEkgY2FuIGJlIGFueXdoZXJlLCBhbnl0aW1lLCBhbmQgSSBjYW4ga2lsbCB5b3UgaW4gb3ZlciBzZXZlbiBodW5kcmVkIHdheXMsIGFuZCB0aGF0J3MganVzdCB3aXRoIG15IGJhcmUgaGFuZHMuIE5vdCBvbmx5IGFtIEkgZXh0ZW5zaXZlbHkgdHJhaW5lZCBpbiB1bmFybWVkIGNvbWJhdCwgYnV0IEkgaGF2ZSBhY2Nlc3MgdG8gdGhlIGVudGlyZSBhcnNlbmFsIG9mIHRoZSBVbml0ZWQgU3RhdGVzIE1hcmluZSBDb3JwcyBhbmQgSSB3aWxsIHVzZSBpdCB0byBpdHMgZnVsbCBleHRlbnQgdG8gd2lwZSB5b3VyIG1pc2VyYWJsZSBhc3Mgb2ZmIHRoZSBmYWNlIG9mIHRoZSBjb250aW5lbnQsIHlvdSBsaXR0bGUgc2hpdC4gSWYgb25seSB5b3UgY291bGQgaGF2ZSBrbm93biB3aGF0IHVuaG9seSByZXRyaWJ1dGlvbiB5b3VyIGxpdHRsZSBjbGV2ZXIgY29tbWVudCB3YXMgYWJvdXQgdG8gYnJpbmcgZG93biB1cG9uIHlvdSwgbWF5YmUgeW91IHdvdWxkIGhhdmUgaGVsZCB5b3VyIGZ1Y2tpbmcgdG9uZ3VlLiBCdXQgeW91IGNvdWxkbid0LCB5b3UgZGlkbid0LCBhbmQgbm93IHlvdSdyZSBwYXlpbmcgdGhlIHByaWNlLCB5b3UgZ29kZGFtbiBpZGlvdC4gSSB3aWxsIHNoaXQgZnVyeSBhbGwgb3ZlciB5b3UgYW5kIHlvdSB3aWxsIGRyb3duIGluIGl0LiBZb3UncmUgZnVja2luZyBkZWFkLCBraWRkby4K", 213 | "encoding": "base64" 214 | }, 215 | "redirectURL": "", 216 | "headersSize": 1104, 217 | "bodySize": 323 218 | }, 219 | "cache": { 220 | "beforeRequest": { 221 | "expires": "2009-04-16T15:50:36", 222 | "lastAccess": "2009-16-02T15:50:34", 223 | "eTag": "", 224 | "hitCount": 0 225 | }, 226 | "afterRequest": { 227 | "expires": "2009-04-16T15:50:36", 228 | "lastAccess": "2009-16-02T15:50:34", 229 | "eTag": "", 230 | "hitCount": 0 231 | } 232 | }, 233 | "timings": { 234 | "dns": -1.0, 235 | "blocked": -1.0, 236 | "connect": -1.0, 237 | "ssl": -1.0, 238 | "send": 0.0, 239 | "wait": 71.0, 240 | "receive": 1.0 241 | }, 242 | "serverIPAddress": "10.0.0.1", 243 | "connection": "52492" 244 | } 245 | ] 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /tests/fixtures/someapi13.har: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "version": "1.3", 4 | "creator": { 5 | "name": "Charles Proxy", 6 | "version": "4.2.7", 7 | "comment": "" 8 | }, 9 | "browser": { 10 | "name": "Firefox", 11 | "version": "3.6", 12 | "comment": "" 13 | }, 14 | "pages": [ 15 | { 16 | "startedDateTime": "2009-04-16T12:07:25.123+01:00", 17 | "id": "page_0", 18 | "title": "Test Page", 19 | "pageTimings": { 20 | "onContentLoad": 1720.0, 21 | "onLoad": 2500.0, 22 | "comment": "" 23 | }, 24 | "comment": "" 25 | } 26 | ], 27 | "comment": "", 28 | "entries": [ 29 | { 30 | "pageref": "page_0", 31 | "startedDateTime": "2018-11-06T15:11:23.305+01:00", 32 | "time": 72.0, 33 | "request": { 34 | "method": "GET", 35 | "url": "https://api.someapi123.io", 36 | "headersCompression": 10, 37 | "httpVersion": "HTTP/2.0", 38 | "cookies": [ 39 | { 40 | "name": "TestCookie", 41 | "value": "Cookie Value", 42 | "path": "/", 43 | "domain": "api.someapi123.io", 44 | "expires": "2009-07-24T19:20:30.123+02:00", 45 | "httpOnly": false, 46 | "secure": false, 47 | "comment": "" 48 | } 49 | ], 50 | "headers": [ 51 | { 52 | "name": ":method", 53 | "value": "GET" 54 | }, 55 | { 56 | "name": ":path", 57 | "value": "/config-reader/values/block-version" 58 | }, 59 | { 60 | "name": ":authority", 61 | "value": "api.someapi123.io" 62 | }, 63 | { 64 | "name": ":scheme", 65 | "value": "https" 66 | }, 67 | { 68 | "name": "accept-encoding", 69 | "value": "gzip" 70 | }, 71 | { 72 | "name": "user-agent", 73 | "value": "okhttp/3.9.1" 74 | } 75 | ], 76 | "queryString": [ 77 | { 78 | "name": "param1", 79 | "value": "value1", 80 | "comment": "" 81 | }, 82 | { 83 | "name": "param1", 84 | "value": "value1", 85 | "comment": "" 86 | } 87 | ], 88 | "headersSize": 7, 89 | "bodySize": 0, 90 | "comment": "" 91 | }, 92 | "response": { 93 | "status": 200, 94 | "statusText": "OK", 95 | "headersCompression": 42, 96 | "httpVersion": "HTTP/2.0", 97 | "cookies": [ 98 | { 99 | "name": "TestCookie", 100 | "value": "Cookie Value", 101 | "path": "/", 102 | "domain": "api.someapi123.io", 103 | "expires": "2009-07-24T19:20:30.123+02:00", 104 | "httpOnly": false, 105 | "secure": false, 106 | "comment": "" 107 | } 108 | ], 109 | "headers": [ 110 | { 111 | "name": ":status", 112 | "value": "200" 113 | }, 114 | { 115 | "name": "content-type", 116 | "value": "application/json; charset=UTF-8" 117 | }, 118 | { 119 | "name": "date", 120 | "value": "Tue, 06 Nov 2018 14:11:24 GMT" 121 | }, 122 | { 123 | "name": "server", 124 | "value": "Apache" 125 | }, 126 | { 127 | "name": "access-control-allow-origin", 128 | "value": "null" 129 | }, 130 | { 131 | "name": "access-control-allow-methods", 132 | "value": "GET, POST, HEAD, OPTIONS" 133 | }, 134 | { 135 | "name": "access-control-max-age", 136 | "value": "3600" 137 | }, 138 | { 139 | "name": "access-control-allow-headers", 140 | "value": "x-requested-with, authorization, content-type, sessionToken, username, password, version" 141 | }, 142 | { 143 | "name": "x-content-type-options", 144 | "value": "nosniff" 145 | }, 146 | { 147 | "name": "x-xss-protection", 148 | "value": "1; mode=block" 149 | }, 150 | { 151 | "name": "cache-control", 152 | "value": "no-cache, no-store, max-age=0, must-revalidate" 153 | }, 154 | { 155 | "name": "pragma", 156 | "value": "no-cache" 157 | }, 158 | { 159 | "name": "expires", 160 | "value": "0" 161 | }, 162 | { 163 | "name": "strict-transport-security", 164 | "value": "max-age=31536000; includeSubDomains; preload" 165 | }, 166 | { 167 | "name": "x-frame-options", 168 | "value": "SAMEORIGIN" 169 | }, 170 | { 171 | "name": "artifactid", 172 | "value": "gateway" 173 | }, 174 | { 175 | "name": "user-agent", 176 | "value": "okhttp/3.9.1" 177 | }, 178 | { 179 | "name": "projectversion", 180 | "value": "gateway:1.6" 181 | }, 182 | { 183 | "name": "projectversion", 184 | "value": "config-reader:1.16" 185 | }, 186 | { 187 | "name": "artifactid", 188 | "value": "config-reader" 189 | }, 190 | { 191 | "name": "x-application-context", 192 | "value": "config-reader:native,aws,prod:8887" 193 | }, 194 | { 195 | "name": "user-agent", 196 | "value": "okhttp/3.9.1" 197 | }, 198 | { 199 | "name": "content-security-policy", 200 | "value": "upgrade-insecure-requests" 201 | }, 202 | { 203 | "name": "access-control-allow-credentials", 204 | "value": "false" 205 | }, 206 | { 207 | "name": "x-cache", 208 | "value": "Miss from cloudfront" 209 | } 210 | ], 211 | "content": { 212 | "size": 323, 213 | "mimeType": "application/json; charset=UTF-8", 214 | "text": "V2hhdCB0aGUgZnVjayBkaWQgeW91IGp1c3QgZnVja2luZyBzYXkgYWJvdXQgbWUsIHlvdSBsaXR0bGUgYml0Y2g/IEknbGwgaGF2ZSB5b3Uga25vdyBJIGdyYWR1YXRlZCB0b3Agb2YgbXkgY2xhc3MgaW4gdGhlIE5hdnkgU2VhbHMsIGFuZCBJJ3ZlIGJlZW4gaW52b2x2ZWQgaW4gbnVtZXJvdXMgc2VjcmV0IHJhaWRzIG9uIEFsLVF1YWVkYSwgYW5kIEkgaGF2ZSBvdmVyIDMwMCBjb25maXJtZWQga2lsbHMuIEkgYW0gdHJhaW5lZCBpbiBnb3JpbGxhIHdhcmZhcmUgYW5kIEknbSB0aGUgdG9wIHNuaXBlciBpbiB0aGUgZW50aXJlIFVTIGFybWVkIGZvcmNlcy4gWW91IGFyZSBub3RoaW5nIHRvIG1lIGJ1dCBqdXN0IGFub3RoZXIgdGFyZ2V0LiBJIHdpbGwgd2lwZSB5b3UgdGhlIGZ1Y2sgb3V0IHdpdGggcHJlY2lzaW9uIHRoZSBsaWtlcyBvZiB3aGljaCBoYXMgbmV2ZXIgYmVlbiBzZWVuIGJlZm9yZSBvbiB0aGlzIEVhcnRoLCBtYXJrIG15IGZ1Y2tpbmcgd29yZHMuIFlvdSB0aGluayB5b3UgY2FuIGdldCBhd2F5IHdpdGggc2F5aW5nIHRoYXQgc2hpdCB0byBtZSBvdmVyIHRoZSBJbnRlcm5ldD8gVGhpbmsgYWdhaW4sIGZ1Y2tlci4gQXMgd2Ugc3BlYWsgSSBhbSBjb250YWN0aW5nIG15IHNlY3JldCBuZXR3b3JrIG9mIHNwaWVzIGFjcm9zcyB0aGUgVVNBIGFuZCB5b3VyIElQIGlzIGJlaW5nIHRyYWNlZCByaWdodCBub3cgc28geW91IGJldHRlciBwcmVwYXJlIGZvciB0aGUgc3Rvcm0sIG1hZ2dvdC4gVGhlIHN0b3JtIHRoYXQgd2lwZXMgb3V0IHRoZSBwYXRoZXRpYyBsaXR0bGUgdGhpbmcgeW91IGNhbGwgeW91ciBsaWZlLiBZb3UncmUgZnVja2luZyBkZWFkLCBraWQuIEkgY2FuIGJlIGFueXdoZXJlLCBhbnl0aW1lLCBhbmQgSSBjYW4ga2lsbCB5b3UgaW4gb3ZlciBzZXZlbiBodW5kcmVkIHdheXMsIGFuZCB0aGF0J3MganVzdCB3aXRoIG15IGJhcmUgaGFuZHMuIE5vdCBvbmx5IGFtIEkgZXh0ZW5zaXZlbHkgdHJhaW5lZCBpbiB1bmFybWVkIGNvbWJhdCwgYnV0IEkgaGF2ZSBhY2Nlc3MgdG8gdGhlIGVudGlyZSBhcnNlbmFsIG9mIHRoZSBVbml0ZWQgU3RhdGVzIE1hcmluZSBDb3JwcyBhbmQgSSB3aWxsIHVzZSBpdCB0byBpdHMgZnVsbCBleHRlbnQgdG8gd2lwZSB5b3VyIG1pc2VyYWJsZSBhc3Mgb2ZmIHRoZSBmYWNlIG9mIHRoZSBjb250aW5lbnQsIHlvdSBsaXR0bGUgc2hpdC4gSWYgb25seSB5b3UgY291bGQgaGF2ZSBrbm93biB3aGF0IHVuaG9seSByZXRyaWJ1dGlvbiB5b3VyIGxpdHRsZSBjbGV2ZXIgY29tbWVudCB3YXMgYWJvdXQgdG8gYnJpbmcgZG93biB1cG9uIHlvdSwgbWF5YmUgeW91IHdvdWxkIGhhdmUgaGVsZCB5b3VyIGZ1Y2tpbmcgdG9uZ3VlLiBCdXQgeW91IGNvdWxkbid0LCB5b3UgZGlkbid0LCBhbmQgbm93IHlvdSdyZSBwYXlpbmcgdGhlIHByaWNlLCB5b3UgZ29kZGFtbiBpZGlvdC4gSSB3aWxsIHNoaXQgZnVyeSBhbGwgb3ZlciB5b3UgYW5kIHlvdSB3aWxsIGRyb3duIGluIGl0LiBZb3UncmUgZnVja2luZyBkZWFkLCBraWRkby4K", 215 | "encoding": "base64" 216 | }, 217 | "redirectURL": "", 218 | "headersSize": 1104, 219 | "bodySize": 323 220 | }, 221 | "cache": { 222 | "beforeRequest": { 223 | "expires": "2009-04-16T15:50:36", 224 | "lastAccess": "2009-16-02T15:50:34", 225 | "eTag": "", 226 | "hitCount": 0 227 | }, 228 | "afterRequest": { 229 | "expires": "2009-04-16T15:50:36", 230 | "lastAccess": "2009-16-02T15:50:34", 231 | "eTag": "", 232 | "hitCount": 0 233 | } 234 | }, 235 | "timings": { 236 | "blocked": -1.0, 237 | "dns": -1.0, 238 | "connect": -1.0, 239 | "ssl": -1.0, 240 | "send": 0.0, 241 | "wait": 71.0, 242 | "receive": 1.0 243 | }, 244 | "serverIPAddress": "10.0.0.1", 245 | "connection": "52492" 246 | }, 247 | { 248 | "pageref": "post1", 249 | "startedDateTime": "2018-11-06T15:11:23.305+01:00", 250 | "time": 72.0, 251 | "request": { 252 | "method": "POST", 253 | "url": "https://api.example.io", 254 | "httpVersion": "HTTP/2.0", 255 | "cookies": [], 256 | "headers": [], 257 | "queryString": [], 258 | "headersSize": 7, 259 | "bodySize": 10, 260 | "postData": { 261 | "mimeType": "multipart/form-data", 262 | "text": "aGVsbG8gd29ybGQK", 263 | "encoding": "base64" 264 | } 265 | }, 266 | "response": { 267 | "status": 200, 268 | "statusText": "OK", 269 | "httpVersion": "HTTP/2.0", 270 | "cookies": [], 271 | "headers": [], 272 | "content": { 273 | "size": 323, 274 | "mimeType": "application/json; charset=UTF-8" 275 | }, 276 | "redirectURL": "", 277 | "headersSize": 1104, 278 | "bodySize": 323 279 | }, 280 | "cache": {}, 281 | "timings": { 282 | "blocked": -1.0, 283 | "connect": -1.0, 284 | "dns": -1.0, 285 | "ssl": -1.0, 286 | "send": 0.0, 287 | "wait": 71.0, 288 | "receive": 1.0 289 | } 290 | }, 291 | { 292 | "pageref": "post2", 293 | "startedDateTime": "2018-11-06T15:11:23.305+01:00", 294 | "time": 72.0, 295 | "request": { 296 | "method": "POST", 297 | "url": "https://api.example.io", 298 | "httpVersion": "HTTP/2.0", 299 | "cookies": [], 300 | "headers": [], 301 | "queryString": [], 302 | "headersSize": 7, 303 | "bodySize": 10, 304 | "postData": { 305 | "mimeType": "multipart/form-data", 306 | "params": [ 307 | { 308 | "name": "param1", 309 | "encoding": "base64", 310 | "value": "aGVsbG8gd29ybGQK" 311 | } 312 | ] 313 | } 314 | }, 315 | "response": { 316 | "status": 200, 317 | "statusText": "OK", 318 | "httpVersion": "HTTP/2.0", 319 | "cookies": [], 320 | "headers": [], 321 | "content": { 322 | "size": 323, 323 | "mimeType": "application/json; charset=UTF-8" 324 | }, 325 | "redirectURL": "", 326 | "headersSize": 1104, 327 | "bodySize": 323 328 | }, 329 | "cache": {}, 330 | "timings": { 331 | "blocked": -1.0, 332 | "connect": -1.0, 333 | "dns": -1.0, 334 | "ssl": -1.0, 335 | "send": 0.0, 336 | "wait": 71.0, 337 | "receive": 1.0 338 | } 339 | } 340 | ] 341 | } 342 | } 343 | --------------------------------------------------------------------------------