diff --git a/API.md b/API.md index 8285f71..4dc7592 100644 --- a/API.md +++ b/API.md @@ -11,6 +11,10 @@ List of all known Tesla APIs, and if this crate supports it, and which of the Te Currently only the Owner API is partially supported by this crate. -| API | Owner API | Fleet API | Command Mode | -|-----------| --- | --- | --- | -| honk_horn | ✅ | | | + + +| API | Owner API | Fleet API | Command Mode | +| --- | --- | --- | --- | +| honk_horn | ✅ | | | + + diff --git a/tesla_api_coverage/README.md b/tesla_api_coverage/README.md index 101b2fb..f82a9a8 100644 --- a/tesla_api_coverage/README.md +++ b/tesla_api_coverage/README.md @@ -14,6 +14,11 @@ This project does (or will do) the following: * Has a configuration on how to merge the endpoints, e.g. if an endpoint name is different, how to resolve it. * Output a table of endpoints that are implemented or not, maybe in Markdown. +### API.md output + +* Update API.md in the root. +* Look for `` and `` and generate the table in between. +* Use timdorr's API as the source of truth for the list of endpoints because it is the oldest and longest! ### Brainstorm diff --git a/tesla_api_coverage/src/api_md.rs b/tesla_api_coverage/src/api_md.rs new file mode 100644 index 0000000..d30f171 --- /dev/null +++ b/tesla_api_coverage/src/api_md.rs @@ -0,0 +1,14 @@ +use crate::fleet::FleetEndpoint; +use crate::teslatte::TeslatteEndpoint; +use crate::timdorr::TimdorrEndpoint; +use crate::vehicle_command::VehicleCommandEndpoint; +use std::collections::HashMap; + +pub fn generate_api_md( + teslatte: Vec, + fleet: HashMap, + vehicle_command: HashMap, + timdorr: HashMap, +) -> anyhow::Result<()> { + todo!() +} diff --git a/tesla_api_coverage/src/fleet.rs b/tesla_api_coverage/src/fleet.rs index 44e9da8..cbee496 100644 --- a/tesla_api_coverage/src/fleet.rs +++ b/tesla_api_coverage/src/fleet.rs @@ -66,11 +66,11 @@ pub struct Parameter { description: String, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct FleetEndpoint { - name: String, - method: reqwest::Method, - url_definition: String, + pub name: String, + pub method: reqwest::Method, + pub uri: String, // description: String, // category: Category, // scopes: Vec, @@ -109,10 +109,19 @@ pub fn parse(html: &str) -> HashMap { } let Some(next_element) = element.next_sibling_element() else { - return map; + break; }; element = next_element; } + + // Replace all vehicles/{id}/ to use {vehicle_id} + for (name, endpoint) in &mut map { + endpoint.uri = endpoint + .uri + .replace("vehicles/{id}/", "vehicles/{vehicle_id}/"); + } + + return map; } /// Return None if this is not an endpoint. @@ -166,7 +175,7 @@ fn parse_call(element: ElementRef) -> Option { Some(FleetEndpoint { name: name.to_string(), method: reqwest::Method::from_bytes(method.as_bytes()).unwrap(), - url_definition: url.to_string(), + uri: url.to_string(), }) } diff --git a/tesla_api_coverage/src/main.rs b/tesla_api_coverage/src/main.rs index fd2c769..5e451ce 100644 --- a/tesla_api_coverage/src/main.rs +++ b/tesla_api_coverage/src/main.rs @@ -1,14 +1,19 @@ +mod api_md; mod fleet; mod nom_help; mod teslatte; mod timdorr; mod vehicle_command; -use clap::{Parser, Subcommand}; -use scraper::Element; +use crate::fleet::FleetEndpoint; +use crate::teslatte::TeslatteEndpoint; +use crate::timdorr::TimdorrEndpoint; +use crate::vehicle_command::VehicleCommandEndpoint; +use clap::Parser; +use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use std::str::FromStr; -use tracing::info; +use tracing::{error, info}; const TIMDORR_URL: &str = "https://raw.githubusercontent.com/timdorr/tesla-api/master/ownerapi_endpoints.json"; @@ -40,6 +45,10 @@ async fn main() { let mut teslatte_project_path = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap(); teslatte_project_path.push(".."); let teslatte_endpoints = teslatte::parse(&teslatte_project_path).unwrap(); + let teslatte_endpoints: HashMap = teslatte_endpoints + .into_iter() + .map(|e| (e.name.clone(), e)) + .collect(); let fleet_endpoints = fleet::parse(&fleet_html); let command_endpoints = vehicle_command::parse(&command_golang); @@ -49,6 +58,98 @@ async fn main() { info!("{} endpoints in fleet", fleet_endpoints.len()); info!("{} endpoints in command", command_endpoints.len()); info!("{} endpoints in timdorr", timdorr_endpoints.len()); + + let mut merged = merge( + teslatte_endpoints, + fleet_endpoints, + command_endpoints, + timdorr_endpoints, + ) + .unwrap(); + + // Let's do a check to see if the fleet api matches the timdorr api. + // If it doesn't, let's panic! + let mut perfect = true; + for (k, v) in &merged { + if let Some(fleet) = &v.fleet { + if let Some(timdorr) = &v.timdorr { + if fleet.uri != timdorr.uri { + error!("{}: fleet: {}, timdorr: {}", k, fleet.uri, timdorr.uri); + perfect = false; + } + } + } + } + + if !perfect { + panic!("Fleet and Timdorr don't match. See errors above."); + } + + // filter_interesting_endpoints(&mut merged); + todo!(); + + dbg!(&merged); +} + +/// Remove endpoints that we're not interested in (yet) in place. +// pub fn filter_interesting_endpoints(mut endpoints: &mut HashMap) { +// endpoints.retain(|_, e| { +// !e. starts_with("/api/1/directives") +// && !e.starts_with("/api/1/subscriptions") +// && !e.starts_with("/api/1/dx/") +// && !e.starts_with("/bff/v2/mobile-app") +// }); +// } + +#[derive(Debug)] +pub struct Endpoint { + pub name: String, + pub teslatte: Option, + pub fleet: Option, + pub vehicle_command: Option, + pub timdorr: Option, +} + +pub fn merge( + teslatte: HashMap, + fleet: HashMap, + vehicle_command: HashMap, + timdorr: HashMap, +) -> anyhow::Result> { + // Collate all the keys into a single set + let mut keys = HashSet::with_capacity(100); + keys.extend(teslatte.iter().map(|(k, _)| k.clone())); + keys.extend(fleet.keys().map(|k| k.clone())); + keys.extend(vehicle_command.keys().map(|k| k.clone())); + keys.extend(timdorr.keys().map(|k| k.clone())); + + // Put the keys into a Vec and sort. + let mut keys: Vec = keys.into_iter().collect(); + keys.sort(); + + let mut endpoints = Vec::with_capacity(keys.len()); + for name in keys { + // for each of these maps, if the key exists, then we have an endpoint. + let teslatte = teslatte.get(&name).cloned(); + let fleet = fleet.get(&name).cloned(); + let vehicle_command = vehicle_command.get(&name).cloned(); + let timdorr = timdorr.get(&name).cloned(); + + let endpoint = Endpoint { + name, + teslatte, + fleet, + vehicle_command, + timdorr, + }; + + endpoints.push(endpoint); + } + + Ok(endpoints + .into_iter() + .map(|e| (e.name.clone(), e)) + .collect::>()) } async fn cache_fetch(url: &str, filename: &str, cache: bool) -> String { diff --git a/tesla_api_coverage/src/teslatte.rs b/tesla_api_coverage/src/teslatte.rs index b8cdfec..1d6ff64 100644 --- a/tesla_api_coverage/src/teslatte.rs +++ b/tesla_api_coverage/src/teslatte.rs @@ -1,21 +1,20 @@ //! Parse the whole teslatte project and find any get*! post*! macros. use crate::nom_help::{ignore_whitespace, quoted_string, short_trace}; +use heck::ToKebabCase; use nom::branch::alt; -use nom::bytes::complete::{tag, take_till1, take_until1, take_while1}; -use nom::character::complete::alpha1; -use nom::character::is_alphabetic; +use nom::bytes::complete::{tag, take_while1}; use nom::combinator::opt; -use nom::{IResult, Needed}; +use nom::IResult; use reqwest::Method; use std::fs::read_to_string; use std::path::{Path, PathBuf}; -use tracing::{debug, info, trace}; +use tracing::{debug, trace}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TeslatteEndpoint { pub method: Method, - pub endpoint: String, + pub name: String, pub uri: String, // pub args: Vec, // pub post_struct: Option, @@ -24,7 +23,8 @@ pub struct TeslatteEndpoint { pub fn parse(path: &Path) -> anyhow::Result> { let mut path = PathBuf::from(path); path.push("src"); - path.push("**/*.rs"); + path.push("**"); + path.push("*.rs"); debug!("Globbing {path:?}"); @@ -37,6 +37,11 @@ pub fn parse(path: &Path) -> anyhow::Result> { endpoints.extend(append_endpoints); } + // Change the name to be kebab-case. + for endpoint in &mut endpoints { + endpoint.name = endpoint.name.to_kebab_case(); + } + Ok(endpoints) } @@ -129,7 +134,7 @@ fn get(s: &str) -> IResult<&str, TeslatteEndpoint> { let endpoint = TeslatteEndpoint { method: Method::GET, - endpoint: fn_name.to_string(), + name: fn_name.to_string(), uri: uri.to_string(), }; @@ -151,7 +156,7 @@ fn get_arg(s: &str) -> IResult<&str, TeslatteEndpoint> { let endpoint = TeslatteEndpoint { method: Method::GET, - endpoint: fn_name.to_string(), + name: fn_name.to_string(), uri: uri.to_string(), }; @@ -173,7 +178,7 @@ fn get_args(s: &str) -> IResult<&str, TeslatteEndpoint> { let endpoint = TeslatteEndpoint { method: Method::GET, - endpoint: fn_name.to_string(), + name: fn_name.to_string(), uri: uri.to_string(), }; @@ -195,7 +200,7 @@ fn post_arg(s: &str) -> IResult<&str, TeslatteEndpoint> { let endpoint = TeslatteEndpoint { method: Method::POST, - endpoint: fn_name.to_string(), + name: fn_name.to_string(), uri: uri.to_string(), }; @@ -216,7 +221,7 @@ fn post_arg_empty(s: &str) -> IResult<&str, TeslatteEndpoint> { let endpoint = TeslatteEndpoint { method: Method::POST, - endpoint: fn_name.to_string(), + name: fn_name.to_string(), uri: uri.to_string(), }; diff --git a/tesla_api_coverage/src/timdorr.rs b/tesla_api_coverage/src/timdorr.rs index 614985f..697f1d4 100644 --- a/tesla_api_coverage/src/timdorr.rs +++ b/tesla_api_coverage/src/timdorr.rs @@ -1,20 +1,29 @@ use heck::ToKebabCase; use serde::Deserialize; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] #[serde(rename_all = "UPPERCASE")] pub struct TimdorrEndpoint { #[serde(rename = "TYPE")] - method: String, - uri: String, - auth: bool, + pub method: String, + pub uri: String, + pub auth: bool, } pub fn parse(json_str: &str) -> HashMap { let map: HashMap = serde_json::from_str(json_str).unwrap(); - // rename all the keys to kebab-case + // Massage all URLs to have a / before "api". + let map = map + .into_iter() + .map(|(k, mut v)| { + v.uri = format!("/{}", v.uri); + (k, v) + }) + .collect::>(); + + // Rename all the keys to kebab-case map.into_iter() .map(|(k, v)| (k.to_kebab_case(), v)) .collect::>() diff --git a/tesla_api_coverage/src/vehicle_command.rs b/tesla_api_coverage/src/vehicle_command.rs index 596e28c..7fd1342 100644 --- a/tesla_api_coverage/src/vehicle_command.rs +++ b/tesla_api_coverage/src/vehicle_command.rs @@ -30,7 +30,7 @@ pub fn seek_to_map(s: &str) -> IResult<&str, ()> { Ok((s, ())) } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct VehicleCommandEndpoint { pub endpoint: String, pub help: String,