diff --git a/examples/log2har.rs b/examples/log2har.rs index ecd7867..897606b 100644 --- a/examples/log2har.rs +++ b/examples/log2har.rs @@ -110,6 +110,7 @@ fn parse_log(input: &str) -> Result { // the iterator must succeed given the definition of file - otherwise parse fails. let log = LogParser::parse(Rule::file, &input)?.next().unwrap(); let mut result = String::new(); + let mut entries: Vec = Vec::new(); for record in log.into_inner() { match record.as_rule() { Rule::log_entry => { @@ -216,12 +217,26 @@ fn parse_log(input: &str) -> Result { e => Err(format_err!("Unexpected parse rule 2 encountered {:?}", e))?, } } - result.push_str(&format!("{}\n", serde_json::to_string_pretty(&entry)?)); + entries.push(entry); } Rule::EOI => (), e => Err(format_err!("Unexpected parse rule 1 encountered {:?}", e))?, } } + let har_log = har::Har { + log: har::Spec::V1_3(v1_3::Log { + browser: None, + creator: v1_3::Creator { + name: "kubernetes-rs".to_string(), + version: "0".to_string(), + comment: None, + }, + pages: None, + entries: entries, + comment: None, + }), + }; + result.push_str(&format!("{}\n", serde_json::to_string_pretty(&har_log)?)); Ok(result) } @@ -300,62 +315,75 @@ I0225 20:16:03.147299 14148 request.go:942] Response Body: { } "#; let expected = r#"{ - "pageref": null, - "startedDateTime": "", - "time": 0, - "request": { - "method": "GET", - "url": "https://localhost:6445/version?timeout=32s", - "httpVersion": "unknown", - "cookies": [], - "headers": [ - { - "name": "Accept", - "value": "application/json, */*" - }, - { - "name": "User-Agent", - "value": "kubectl/v1.13.0 (windows/amd64) kubernetes/ddf47ac" - } - ], - "queryString": [], - "headersSize": -1, - "bodySize": -1 - }, - "response": { - "status": -1, - "statusText": "", - "httpVersion": "unknown", - "cookies": [], - "headers": [ - { - "name": "Content-Type", - "value": "application/json" - }, - { - "name": "Content-Length", - "value": "263" - }, + "log": { + "version": "1.3", + "creator": { + "name": "kubernetes-rs", + "version": "0" + }, + "browser": null, + "pages": null, + "entries": [ { - "name": "Date", - "value": "Thu, 21 Feb 2019 21:04:01 GMT" + "pageref": null, + "startedDateTime": "", + "time": 0, + "request": { + "method": "GET", + "url": "https://localhost:6445/version?timeout=32s", + "httpVersion": "unknown", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "application/json, */*" + }, + { + "name": "User-Agent", + "value": "kubectl/v1.13.0 (windows/amd64) kubernetes/ddf47ac" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": -1, + "statusText": "", + "httpVersion": "unknown", + "cookies": [], + "headers": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Content-Length", + "value": "263" + }, + { + "name": "Date", + "value": "Thu, 21 Feb 2019 21:04:01 GMT" + } + ], + "content": { + "size": -1, + "mimeType": "", + "text": "ewogICJtYWpvciI6ICIxIiwKICAibWlub3IiOiAiMTMiLAogICJnaXRWZXJzaW9uIjogInYxLjEzLjAiLAogICJnaXRDb21taXQiOiAiZGRmNDdhYzEzYzFhOTQ4M2VhMDM1YTc5Y2Q3YzEwMDA1ZmYyMWE2ZCIsCiAgImdpdFRyZWVTdGF0ZSI6ICJjbGVhbiIsCiAgImJ1aWxkRGF0ZSI6ICIyMDE4LTEyLTAzVDIwOjU2OjEyWiIsCiAgImdvVmVyc2lvbiI6ICJnbzEuMTEuMiIsCiAgImNvbXBpbGVyIjogImdjIiwKICAicGxhdGZvcm0iOiAibGludXgvYW1kNjQiCn0K", + "encoding": "base64" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": -1 + } } - ], - "content": { - "size": -1, - "mimeType": "", - "text": "ewogICJtYWpvciI6ICIxIiwKICAibWlub3IiOiAiMTMiLAogICJnaXRWZXJzaW9uIjogInYxLjEzLjAiLAogICJnaXRDb21taXQiOiAiZGRmNDdhYzEzYzFhOTQ4M2VhMDM1YTc5Y2Q3YzEwMDA1ZmYyMWE2ZCIsCiAgImdpdFRyZWVTdGF0ZSI6ICJjbGVhbiIsCiAgImJ1aWxkRGF0ZSI6ICIyMDE4LTEyLTAzVDIwOjU2OjEyWiIsCiAgImdvVmVyc2lvbiI6ICJnbzEuMTEuMiIsCiAgImNvbXBpbGVyIjogImdjIiwKICAicGxhdGZvcm0iOiAibGludXgvYW1kNjQiCn0K", - "encoding": "base64" - }, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 - }, - "cache": {}, - "timings": { - "send": -1, - "wait": -1, - "receive": -1 + ] } } "#; @@ -384,62 +412,75 @@ I0219 14:38:39.153335 13292 request.go:942] Response Body: { } "#; let expected = r#"{ - "pageref": null, - "startedDateTime": "", - "time": 0, - "request": { - "method": "GET", - "url": "https://localhost:6445/api/v1/namespaces?limit=500", - "httpVersion": "unknown", - "cookies": [], - "headers": [ - { - "name": "User-Agent", - "value": "kubectl/v1.13.0 (windows/amd64) kubernetes/ddf47ac" - }, - { - "name": "Accept", - "value": "application/json;as=Table;v=v1beta1;g=meta.k8s.io, application/json" - } - ], - "queryString": [], - "headersSize": -1, - "bodySize": -1 - }, - "response": { - "status": -1, - "statusText": "", - "httpVersion": "unknown", - "cookies": [], - "headers": [ - { - "name": "Content-Type", - "value": "application/json" - }, - { - "name": "Content-Length", - "value": "263" - }, + "log": { + "version": "1.3", + "creator": { + "name": "kubernetes-rs", + "version": "0" + }, + "browser": null, + "pages": null, + "entries": [ { - "name": "Date", - "value": "Sun, 17 Feb 2019 15:21:36 GMT" + "pageref": null, + "startedDateTime": "", + "time": 0, + "request": { + "method": "GET", + "url": "https://localhost:6445/api/v1/namespaces?limit=500", + "httpVersion": "unknown", + "cookies": [], + "headers": [ + { + "name": "User-Agent", + "value": "kubectl/v1.13.0 (windows/amd64) kubernetes/ddf47ac" + }, + { + "name": "Accept", + "value": "application/json;as=Table;v=v1beta1;g=meta.k8s.io, application/json" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": -1, + "statusText": "", + "httpVersion": "unknown", + "cookies": [], + "headers": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Content-Length", + "value": "263" + }, + { + "name": "Date", + "value": "Sun, 17 Feb 2019 15:21:36 GMT" + } + ], + "content": { + "size": -1, + "mimeType": "", + "text": "ewogICJtYWpvciI6ICIxIiwKICAibWlub3IiOiAiMTMiLAogICJnaXRWZXJzaW9uIjogInYxLjEzLjAiLAogICJnaXRDb21taXQiOiAiZGRmNDdhYzEzYzFhOTQ4M2VhMDM1YTc5Y2Q3YzEwMDA1ZmYyMWE2ZCIsCiAgImdpdFRyZWVTdGF0ZSI6ICJjbGVhbiIsCiAgImJ1aWxkRGF0ZSI6ICIyMDE4LTEyLTAzVDIwOjU2OjEyWiIsCiAgImdvVmVyc2lvbiI6ICJnbzEuMTEuMiIsCiAgImNvbXBpbGVyIjogImdjIiwKICAicGxhdGZvcm0iOiAibGludXgvYW1kNjQiCn0K", + "encoding": "base64" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": -1 + } } - ], - "content": { - "size": -1, - "mimeType": "", - "text": "ewogICJtYWpvciI6ICIxIiwKICAibWlub3IiOiAiMTMiLAogICJnaXRWZXJzaW9uIjogInYxLjEzLjAiLAogICJnaXRDb21taXQiOiAiZGRmNDdhYzEzYzFhOTQ4M2VhMDM1YTc5Y2Q3YzEwMDA1ZmYyMWE2ZCIsCiAgImdpdFRyZWVTdGF0ZSI6ICJjbGVhbiIsCiAgImJ1aWxkRGF0ZSI6ICIyMDE4LTEyLTAzVDIwOjU2OjEyWiIsCiAgImdvVmVyc2lvbiI6ICJnbzEuMTEuMiIsCiAgImNvbXBpbGVyIjogImdjIiwKICAicGxhdGZvcm0iOiAibGludXgvYW1kNjQiCn0K", - "encoding": "base64" - }, - "redirectURL": "", - "headersSize": -1, - "bodySize": -1 - }, - "cache": {}, - "timings": { - "send": -1, - "wait": -1, - "receive": -1 + ] } } "#; diff --git a/src/lib.rs b/src/lib.rs index 8b13789..445d029 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,5 @@ +#[cfg(test)] +extern crate failure; +#[cfg(test)] +mod tests; diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..3bf34c2 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,74 @@ +use failure::{err_msg, Error}; +use har; +use serde_json::Value; + +use ::kubernetes_api::core::v1::NamespaceList; + +fn entries(ahar: &har::Har) -> Result<&Vec, Error> { + match &ahar.log { + har::Spec::V1_3(log) => Ok(&log.entries), + _ => Err(err_msg("unexpected har version")), + } +} + +fn response(entry: &har::v1_3::Entries) -> Result { + // https://github.com/rust-lang/rust/issues/46871 + // let body = &entry.response.content.text?; + let body = entry + .response + .content + .text + .as_ref() + .ok_or(err_msg("no response body"))?; + Ok(match &entry.response.content.encoding { + None => body.clone(), + Some(enc) if enc == "base64" => String::from_utf8(base64::decode(&body)?)?, + Some(enc) => Err(err_msg(format!("unknown encoding {}", enc)))?, + }) +} + +#[test] +fn test_namespace_har() -> Result<(), Error> { + // This is not generalised. We should aim to make this model driven and fully generalised, but EFUTURE. + // Load the HAR + // HAR errors are awkwardly incompatibly... feel free to whinge, + // but even map_err was throwing from::From failures - I think due to har's Result wrapper. + let har = match har::from_path("testdata/traces/json/get-namespaces.har") { + Ok(something) => Ok(something), + Err(e) => Err(err_msg(format!("{}", e))), + }?; + for entry in entries(&har)? { + println!("url {:?}", entry.request.url); + // XXX todos: + // Check that the GVK url route is correct - route that URL and match back to the GVK or vice versa. + // cross check the self link? + // its a get, no payload upload verification + // verify the GET options from the URL are understood - do we have a parser for those ? + + // parse the response by its expected type + let response_str = response(entry)?; + println!("response {}", response_str); + let typed: NamespaceList = serde_json::from_str(&response_str)?; + println!("list {:?}", typed); + println!("list-to-json {}", serde_json::to_string_pretty(&typed)?); + // reserialise the response to JSON, bounce it back through Value to eliminate the *ordering* in the output imposed by the use of structured types. + let typed_json = serde_json::to_string_pretty(&serde_json::from_str::( + &serde_json::to_string_pretty(&typed)?, + )?)?; + // let typed_interim: Value = serde_json::from_str(&typed_j) + // compare the resulting JSON + // canonicalise the reference JSON to avoid over-time library output variations. + let interim: Value = serde_json::from_str(&response_str)?; + let canonical = serde_json::to_string_pretty(&interim)?; + println!("{}", canonical); + println!("{}", typed_json); + // XXX: should be eq, but need to: + // - fix Pods kind in output + // - remove extra apiVersion and kind etc + // - implement omitEmpty + // XXX or alternatively have a different round trip verification approach + assert_ne!(canonical, typed_json); + // compare equal. + } + Ok(()) +} diff --git a/testdata/traces/json/get-namespaces.har b/testdata/traces/json/get-namespaces.har index 2d247fb..b0e0af4 100644 --- a/testdata/traces/json/get-namespaces.har +++ b/testdata/traces/json/get-namespaces.har @@ -1 +1,72 @@ -{"pageref":null,"startedDateTime":"","time":0,"request":{"method":"GET","url":"https://localhost:6445/api/v1/namespaces?limit=500","httpVersion":"unknown","cookies":[],"headers":[{"name":"Accept","value":"application/json"},{"name":"User-Agent","value":"kubectl/v1.13.0 (windows/amd64) kubernetes/ddf47ac"}],"queryString":[],"headersSize":-1,"bodySize":-1},"response":{"status":-1,"statusText":"","httpVersion":"unknown","cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"1132"},{"name":"Date","value":"Thu, 21 Feb 2019 20:15:36 GMT"}],"content":{"size":-1,"mimeType":"","text":"eyJraW5kIjoiTmFtZXNwYWNlTGlzdCIsImFwaVZlcnNpb24iOiJ2MSIsIm1ldGFkYXRhIjp7InNlbGZMaW5rIjoiL2FwaS92MS9uYW1lc3BhY2VzIiwicmVzb3VyY2VWZXJzaW9uIjoiMTI2MDU4In0sIml0ZW1zIjpbeyJtZXRhZGF0YSI6eyJuYW1lIjoiZGVmYXVsdCIsInNlbGZMaW5rIjoiL2FwaS92MS9uYW1lc3BhY2VzL2RlZmF1bHQiLCJ1aWQiOiJiMjk0YTg5Zi0yYjQzLTExZTktYWYyOC0wMDE1NWQwMTFmMzciLCJyZXNvdXJjZVZlcnNpb24iOiI2IiwiY3JlYXRpb25UaW1lc3RhbXAiOiIyMDE5LTAyLTA4VDAxOjQ4OjU3WiJ9LCJzcGVjIjp7ImZpbmFsaXplcnMiOlsia3ViZXJuZXRlcyJdfSwic3RhdHVzIjp7InBoYXNlIjoiQWN0aXZlIn19LHsibWV0YWRhdGEiOnsibmFtZSI6ImRvY2tlciIsInNlbGZMaW5rIjoiL2FwaS92MS9uYW1lc3BhY2VzL2RvY2tlciIsInVpZCI6IjcwMGMwNTg5LTJiNDUtMTFlOS1hZjI4LTAwMTU1ZDAxMWYzNyIsInJlc291cmNlVmVyc2lvbiI6IjEyMzQiLCJjcmVhdGlvblRpbWVzdGFtcCI6IjIwMTktMDItMDhUMDI6MDE6MjRaIn0sInNwZWMiOnsiZmluYWxpemVycyI6WyJrdWJlcm5ldGVzIl19LCJzdGF0dXMiOnsicGhhc2UiOiJBY3RpdmUifX0seyJtZXRhZGF0YSI6eyJuYW1lIjoia3ViZS1wdWJsaWMiLCJzZWxmTGluayI6Ii9hcGkvdjEvbmFtZXNwYWNlcy9rdWJlLXB1YmxpYyIsInVpZCI6ImI1MGNjZmVlLTJiNDMtMTFlOS1hZjI4LTAwMTU1ZDAxMWYzNyIsInJlc291cmNlVmVyc2lvbiI6IjQ2IiwiY3JlYXRpb25UaW1lc3RhbXAiOiIyMDE5LTAyLTA4VDAxOjQ5OjAxWiJ9LCJzcGVjIjp7ImZpbmFsaXplcnMiOlsia3ViZXJuZXRlcyJdfSwic3RhdHVzIjp7InBoYXNlIjoiQWN0aXZlIn19LHsibWV0YWRhdGEiOnsibmFtZSI6Imt1YmUtc3lzdGVtIiwic2VsZkxpbmsiOiIvYXBpL3YxL25hbWVzcGFjZXMva3ViZS1zeXN0ZW0iLCJ1aWQiOiJiMzBlNGJlMS0yYjQzLTExZTktYWYyOC0wMDE1NWQwMTFmMzciLCJyZXNvdXJjZVZlcnNpb24iOiIxMiIsImNyZWF0aW9uVGltZXN0YW1wIjoiMjAxOS0wMi0wOFQwMTo0ODo1N1oifSwic3BlYyI6eyJmaW5hbGl6ZXJzIjpbImt1YmVybmV0ZXMiXX0sInN0YXR1cyI6eyJwaGFzZSI6IkFjdGl2ZSJ9fV19Cg==","encoding":"base64"},"redirectURL":"","headersSize":-1,"bodySize":-1},"cache":{},"timings":{"send":-1,"wait":-1,"receive":-1}} +{ + "log": { + "version": "1.3", + "creator": { + "name": "kubernetes-rs", + "version": "0" + }, + "browser": null, + "pages": null, + "entries": [ + { + "pageref": null, + "startedDateTime": "", + "time": 0, + "request": { + "method": "GET", + "url": "https://localhost:6445/api/v1/namespaces?limit=500", + "httpVersion": "unknown", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "User-Agent", + "value": "kubectl/v1.13.0 (windows/amd64) kubernetes/ddf47ac" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": -1, + "statusText": "", + "httpVersion": "unknown", + "cookies": [], + "headers": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Content-Length", + "value": "1132" + }, + { + "name": "Date", + "value": "Thu, 21 Feb 2019 20:15:36 GMT" + } + ], + "content": { + "size": -1, + "mimeType": "", + "text": "eyJraW5kIjoiTmFtZXNwYWNlTGlzdCIsImFwaVZlcnNpb24iOiJ2MSIsIm1ldGFkYXRhIjp7InNlbGZMaW5rIjoiL2FwaS92MS9uYW1lc3BhY2VzIiwicmVzb3VyY2VWZXJzaW9uIjoiMTI2MDU4In0sIml0ZW1zIjpbeyJtZXRhZGF0YSI6eyJuYW1lIjoiZGVmYXVsdCIsInNlbGZMaW5rIjoiL2FwaS92MS9uYW1lc3BhY2VzL2RlZmF1bHQiLCJ1aWQiOiJiMjk0YTg5Zi0yYjQzLTExZTktYWYyOC0wMDE1NWQwMTFmMzciLCJyZXNvdXJjZVZlcnNpb24iOiI2IiwiY3JlYXRpb25UaW1lc3RhbXAiOiIyMDE5LTAyLTA4VDAxOjQ4OjU3WiJ9LCJzcGVjIjp7ImZpbmFsaXplcnMiOlsia3ViZXJuZXRlcyJdfSwic3RhdHVzIjp7InBoYXNlIjoiQWN0aXZlIn19LHsibWV0YWRhdGEiOnsibmFtZSI6ImRvY2tlciIsInNlbGZMaW5rIjoiL2FwaS92MS9uYW1lc3BhY2VzL2RvY2tlciIsInVpZCI6IjcwMGMwNTg5LTJiNDUtMTFlOS1hZjI4LTAwMTU1ZDAxMWYzNyIsInJlc291cmNlVmVyc2lvbiI6IjEyMzQiLCJjcmVhdGlvblRpbWVzdGFtcCI6IjIwMTktMDItMDhUMDI6MDE6MjRaIn0sInNwZWMiOnsiZmluYWxpemVycyI6WyJrdWJlcm5ldGVzIl19LCJzdGF0dXMiOnsicGhhc2UiOiJBY3RpdmUifX0seyJtZXRhZGF0YSI6eyJuYW1lIjoia3ViZS1wdWJsaWMiLCJzZWxmTGluayI6Ii9hcGkvdjEvbmFtZXNwYWNlcy9rdWJlLXB1YmxpYyIsInVpZCI6ImI1MGNjZmVlLTJiNDMtMTFlOS1hZjI4LTAwMTU1ZDAxMWYzNyIsInJlc291cmNlVmVyc2lvbiI6IjQ2IiwiY3JlYXRpb25UaW1lc3RhbXAiOiIyMDE5LTAyLTA4VDAxOjQ5OjAxWiJ9LCJzcGVjIjp7ImZpbmFsaXplcnMiOlsia3ViZXJuZXRlcyJdfSwic3RhdHVzIjp7InBoYXNlIjoiQWN0aXZlIn19LHsibWV0YWRhdGEiOnsibmFtZSI6Imt1YmUtc3lzdGVtIiwic2VsZkxpbmsiOiIvYXBpL3YxL25hbWVzcGFjZXMva3ViZS1zeXN0ZW0iLCJ1aWQiOiJiMzBlNGJlMS0yYjQzLTExZTktYWYyOC0wMDE1NWQwMTFmMzciLCJyZXNvdXJjZVZlcnNpb24iOiIxMiIsImNyZWF0aW9uVGltZXN0YW1wIjoiMjAxOS0wMi0wOFQwMTo0ODo1N1oifSwic3BlYyI6eyJmaW5hbGl6ZXJzIjpbImt1YmVybmV0ZXMiXX0sInN0YXR1cyI6eyJwaGFzZSI6IkFjdGl2ZSJ9fV19Cg==", + "encoding": "base64" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1 + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": -1 + } + } + ] + } +}