From a6705cd8ee4d6eb9e8f9be3d638ca15eb6e691fe Mon Sep 17 00:00:00 2001 From: jerbly Date: Sat, 13 Jan 2024 13:05:40 -0500 Subject: [PATCH] auth and invalid json fixes --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/honeycomb.rs | 149 +++++++++++++++++++++++++++++++++-------------- src/main.rs | 18 +++++- 4 files changed, 122 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27c595d..be5a29a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -570,7 +570,7 @@ checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "honey-explore" -version = "0.1.0" +version = "0.1.2" dependencies = [ "anyhow", "askama", diff --git a/Cargo.toml b/Cargo.toml index d7f25d4..45c6b82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "honey-explore" -version = "0.1.1" +version = "0.1.2" edition = "2021" authors = ["Jeremy Blythe "] diff --git a/src/honeycomb.rs b/src/honeycomb.rs index 2e6f23e..fe94a83 100644 --- a/src/honeycomb.rs +++ b/src/honeycomb.rs @@ -1,8 +1,13 @@ -use std::env; +use std::{ + collections::HashMap, + env, + fmt::{Display, Formatter}, +}; use anyhow::Context; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use serde_json::Value; #[derive(Debug, Clone)] pub struct HoneyComb { @@ -14,7 +19,7 @@ const HONEYCOMB_API_KEY: &str = "HONEYCOMB_API_KEY"; #[derive(Debug, Deserialize)] pub struct Dataset { pub slug: String, - pub last_written_at: DateTime, + pub last_written_at: Option>, } #[derive(Debug, Deserialize, Serialize)] @@ -42,6 +47,41 @@ struct Query { id: String, } +#[derive(Debug, Deserialize, Serialize)] +pub struct NameAndSlug { + pub name: String, + pub slug: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Authorizations { + pub api_key_access: HashMap, + pub environment: NameAndSlug, + pub team: NameAndSlug, +} + +impl Authorizations { + pub fn has_required_access(&self, access_types: &[&str]) -> bool { + access_types + .iter() + .all(|access_type| *self.api_key_access.get(*access_type).unwrap_or(&false)) + } +} + +impl Display for Authorizations { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut api_key_access = String::new(); + for (key, value) in &self.api_key_access { + api_key_access.push_str(&format!("{}: {}\n", key, value)); + } + write!( + f, + "api_key_access:\n{}\nenvironment: {}\nteam: {}", + api_key_access, self.environment.name, self.team.name + ) + } +} + impl HoneyComb { pub fn new() -> anyhow::Result { Ok(Self { @@ -51,25 +91,56 @@ impl HoneyComb { ))?, }) } - pub async fn list_all_datasets(&self) -> anyhow::Result> { + + async fn get(&self, request: &str) -> anyhow::Result + where + T: serde::de::DeserializeOwned, + { let response = reqwest::Client::new() - .get(format!("{}datasets", URL)) + .get(format!("{}{}", URL, request)) .header("X-Honeycomb-Team", &self.api_key) .send() - .await? - .json::>() .await?; - Ok(response) + let text: String = response.text().await?; + + match serde_json::from_str::(&text) { + Ok(t) => Ok(t), + Err(e) => { + eprintln!("Invalid JSON data: {}", text); + Err(anyhow::anyhow!("Failed to parse JSON data: {}", e)) + } + } + } + + pub async fn list_authorizations(&self) -> anyhow::Result { + self.get("auth").await + } + pub async fn list_all_datasets(&self) -> anyhow::Result> { + self.get("datasets").await } pub async fn list_all_columns(&self, dataset_slug: &str) -> anyhow::Result> { + self.get(&format!("columns/{}", dataset_slug)).await + } + + async fn post(&self, request: &str, json: Value) -> anyhow::Result + where + T: serde::de::DeserializeOwned, + { let response = reqwest::Client::new() - .get(format!("{}columns/{}", URL, dataset_slug)) + .post(format!("{}{}", URL, request)) .header("X-Honeycomb-Team", &self.api_key) + .json(&json) .send() - .await? - .json::>() .await?; - Ok(response) + let text: String = response.text().await?; + + match serde_json::from_str::(&text) { + Ok(t) => Ok(t), + Err(e) => { + eprintln!("Invalid JSON data: {}", text); + Err(anyhow::anyhow!("Failed to parse JSON data: {}", e)) + } + } } pub async fn get_exists_query_url( @@ -77,44 +148,34 @@ impl HoneyComb { dataset_slug: &str, column_id: &str, ) -> anyhow::Result { - let response = reqwest::Client::new() - .post(format!("{}queries/{}", URL, dataset_slug)) - .header("X-Honeycomb-Team", &self.api_key) - .json(&serde_json::json!({ - "breakdowns": [ - column_id - ], - "calculations": [{ - "op": "COUNT" - }], - "filters": [ - { + let query: Query = self + .post( + &format!("queries/{}", dataset_slug), + serde_json::json!({ + "breakdowns": [column_id], + "calculations": [{ + "op": "COUNT" + }], + "filters": [{ "column": column_id, "op": "exists", - } - ], - "time_range": 7200 - })) - .send() - .await? - .json::() + }], + "time_range": 7200 + }), + ) .await?; - let query_id = response.id; - - let response = reqwest::Client::new() - .post(format!("{}query_results/{}", URL, dataset_slug)) - .header("X-Honeycomb-Team", &self.api_key) - .json(&serde_json::json!({ - "query_id": query_id, - "disable_series": false, - "limit": 10000 - })) - .send() - .await? - .json::() + let query_result: QueryResult = self + .post( + &format!("query_results/{}", dataset_slug), + serde_json::json!({ + "query_id": query.id, + "disable_series": false, + "limit": 10000 + }), + ) .await?; - Ok(response.links.query_url) + Ok(query_result.links.query_url) } } diff --git a/src/main.rs b/src/main.rs index 1bcf6a9..612953c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -102,9 +102,21 @@ async fn main() -> anyhow::Result<()> { // fetch all the honeycomb data and build a map of attribute name to datasets let hc = match HoneyComb::new() { - Ok(hc) => Some(hc), + Ok(hc) => { + let auth = hc.list_authorizations().await?; + let required_access = ["columns", "createDatasets", "queries"]; + if auth.has_required_access(&required_access) { + Some(hc) + } else { + eprintln!( + "continuing without honeycomb: missing required access {:?}:\n{}", + required_access, auth + ); + None + } + } Err(e) => { - println!("continuing without honeycomb: {}", e); + eprintln!("continuing without honeycomb: {}", e); None } }; @@ -116,7 +128,7 @@ async fn main() -> anyhow::Result<()> { .await? .iter() .filter_map(|d| { - if (now - d.last_written_at).num_days() < 60 { + if (now - d.last_written_at.unwrap_or(now)).num_days() < 60 { Some(d.slug.clone()) } else { None