Skip to content

Commit

Permalink
wip(tesla_api_coverage): start to compare api
Browse files Browse the repository at this point in the history
  • Loading branch information
gak committed Oct 23, 2023
1 parent d0b8f6d commit 3f7df75
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 78 deletions.
5 changes: 4 additions & 1 deletion tesla_api_coverage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ tracing-subscriber = "0.3.17"
tracing = "0.1.40"
log = "0.4.20"
nom = "7.1.3"
anyhow = "1.0.75"
anyhow = "1.0.75"
serde = { version = "1.0.189", features = ["derive"] }
serde_json = "1.0.107"
heck = "0.5.0-rc.1"
47 changes: 29 additions & 18 deletions tesla_api_coverage/src/fleet.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use heck::ToKebabCase;
use scraper::{Element, ElementRef, Html, Selector};
use std::collections::HashMap;
use std::str::FromStr;
use tracing::debug;

struct FleetApiSpec {
calls: HashMap<String, Call>,
calls: HashMap<String, FleetEndpoint>,
}

// e.g. serialize to similar: vehicle-endpoints
Expand Down Expand Up @@ -56,32 +58,35 @@ enum InRequestData {
Body,
}

struct Parameter {
pub struct Parameter {
name: String,
request: InRequestData,
var_type: String,
required: bool,
description: String,
}

struct Call {
#[derive(Debug)]
pub struct FleetEndpoint {
name: String,
method: reqwest::Method,
url_definition: String,
description: String,
category: Category,
scopes: Vec<Scope>,
parameters: Vec<Parameter>,
request_example: String,
response_example: String,
// description: String,
// category: Category,
// scopes: Vec<Scope>,
// parameters: Vec<Parameter>,
// request_example: String,
// response_example: String,
}

pub fn parse(html: &str) -> () {
pub fn parse(html: &str) -> HashMap<String, FleetEndpoint> {
let document = Html::parse_document(html);
let content_selector = selector(".content h1");
let mut element = document.select(&content_selector).next().unwrap();
let mut category = None;

let mut map = HashMap::with_capacity(100);

// Iterate over all the elements in the content section until we see a h1 or h2.
loop {
match element.value().name() {
Expand All @@ -91,17 +96,20 @@ pub fn parse(html: &str) -> () {
}
"h2" => {
if category.is_some() {
let name = element.inner_html();
println!("{category:?} {name:?}");
// let call = parse_call(element);
let name = element.inner_html().to_kebab_case();
let call = parse_call(element);

if let Some(endpoint) = call {
debug!("{category:?} {endpoint:?}");
map.insert(name, endpoint);
}
}
}
_ => {}
}

let Some(next_element) = element.next_sibling_element() else {
println!("exiting...");
break;
return map;
};
element = next_element;
}
Expand All @@ -110,7 +118,7 @@ pub fn parse(html: &str) -> () {
/// Return None if this is not an endpoint.
///
/// Will panic if it looks like an endpoint and has trouble parsing.
fn parse_call(element: ElementRef) -> Option<Call> {
fn parse_call(element: ElementRef) -> Option<FleetEndpoint> {
let name = element.value().id().unwrap();

// <p><span class="endpoint"><code>POST /api/1/vehicles/{id}/command/auto_conditioning_start</code></span></p>
Expand All @@ -122,7 +130,6 @@ fn parse_call(element: ElementRef) -> Option<Call> {
}

let (method, url) = url.split_once(' ').unwrap();
println!("{} {}", method, url);

// <p>scopes: <em>vehicle_cmds</em></p>
let (fragment, element) = next(element);
Expand Down Expand Up @@ -156,7 +163,11 @@ fn parse_call(element: ElementRef) -> Option<Call> {
panic!("No examples for {}", name);
}

None
Some(FleetEndpoint {
name: name.to_string(),
method: reqwest::Method::from_bytes(method.as_bytes()).unwrap(),
url_definition: url.to_string(),
})
}

fn next(element: ElementRef) -> (Html, ElementRef) {
Expand Down
48 changes: 15 additions & 33 deletions tesla_api_coverage/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
mod fleet;
mod timdorr;
mod vehicle_command;

use clap::Parser;
use clap::{Parser, Subcommand};
use scraper::Element;
use std::path::PathBuf;
use std::str::FromStr;
Expand All @@ -21,53 +22,34 @@ struct Cli {
/// Use the cached html if exists, to avoid making requests.
#[clap(short, long)]
cached: bool,

#[clap(short = 'v', long)]
only_vehicle_command: bool,
}

#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let args = Cli::parse();

// let timorr = cache_fetch(TIMDORR_URL, TIMDORR_FILE, args.cache).await;
//
// let fleet_html = cache_fetch(
// FLEET_API_URL,
// FLEET_API_FILE,
// args.cache,
// )
// .await;
//
// let command_golang = cache_fetch(
// VEHICLE_COMMAND_URL,
// VEHICLE_COMMAND_FILE,
// args.cache,
// ).await;

let (timorr, fleet_html, command_golang) = tokio::join!(
let (timdorr, fleet_html, command_golang) = tokio::join!(
cache_fetch(TIMDORR_URL, TIMDORR_FILE, args.cached),
cache_fetch(FLEET_API_URL, FLEET_API_FILE, args.cached),
cache_fetch(VEHICLE_COMMAND_URL, VEHICLE_COMMAND_FILE, args.cached)
);

let mut vehicle_command = true;
let mut fleet_api = true;
let mut timdorr = true;
let timdorr_endpoints = timdorr::parse(&timdorr);
let fleet_endpoints = fleet::parse(&fleet_html);
let vehicle_command_endpoints = vehicle_command::parse(&command_golang);

if args.only_vehicle_command {
fleet_api = false;
timdorr = false;
}
// Make hashsets from all the keys and see what's different between timdorr and fleet
let timdorr_keys: std::collections::HashSet<&String> = timdorr_endpoints.keys().collect();
let fleet_keys: std::collections::HashSet<&String> = fleet_endpoints.keys().collect();

if fleet_api {
fleet::parse(&fleet_html);
}
let timdorr_only = timdorr_keys.difference(&fleet_keys);
let fleet_only = fleet_keys.difference(&timdorr_keys);
let both = timdorr_keys.intersection(&fleet_keys);

if vehicle_command {
vehicle_command::parse(&command_golang);
}
info!("Timdorr only: {:?}", timdorr_only);
info!("Fleet only: {:?}", fleet_only);
info!("Both: {:?}", both);
}

async fn cache_fetch(url: &str, filename: &str, cache: bool) -> String {
Expand Down
21 changes: 21 additions & 0 deletions tesla_api_coverage/src/timdorr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use heck::ToKebabCase;
use serde::Deserialize;
use std::collections::{BTreeMap, HashMap};

#[derive(Debug, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub struct Endpoint {
#[serde(rename = "TYPE")]
method: String,
uri: String,
auth: bool,
}

pub fn parse(json_str: &str) -> HashMap<String, Endpoint> {
let map: HashMap<String, Endpoint> = serde_json::from_str(json_str).unwrap();

// rename all the keys to kebab-case
map.into_iter()
.map(|(k, v)| (k.to_kebab_case(), v))
.collect::<HashMap<String, Endpoint>>()
}
61 changes: 35 additions & 26 deletions tesla_api_coverage/src/vehicle_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,41 @@ use nom::character::complete::{char, line_ending, space0, space1, tab};
use nom::combinator::opt;
use nom::multi::{many0, many1};
use nom::IResult;
use tracing::{trace, warn};
use std::collections::HashMap;
use tracing::{debug, trace, warn};

pub fn parse(s: &str) -> () {
pub fn parse(s: &str) -> HashMap<String, VehicleCommandEndpoint> {
// Seek all the way to: var commands = map[string]*Command{\n
// Afterwards has the first map entry.
let commands_start = "var commands = map[string]*Command{\n";
let offset = s.find(commands_start).unwrap();
let s = &s[offset + commands_start.len()..];
let (s, _) = seek_to_map(s).unwrap();

let (go, entries) = many1(map_entry)(s).unwrap();
let (_, entries) = many1(map_entry)(s).unwrap();

dbg!(&entries);
entries
.into_iter()
.map(|e| (e.endpoint.clone(), e))
.collect()
}

warn!("todo: parse")
pub fn seek_to_map(s: &str) -> IResult<&str, ()> {
short_trace("seek to map", s);
let tag_str = "var commands = map[string]*Command{\n";
// There's gotta be a nom function to these two lines.
let (s, _) = take_until(tag_str)(s)?;
let (s, _) = tag(tag_str)(s)?;
short_trace("seek to map done", s);
Ok((s, ()))
}

#[derive(Debug)]
struct MapEntry {
endpoint: String,
help: String,
// requires_auth: bool,
// requires_fleet: bool,
pub struct VehicleCommandEndpoint {
pub endpoint: String,
pub help: String,
pub requires_auth: bool,
pub requires_fleet: bool,
}

fn map_entry(s: &str) -> IResult<&str, MapEntry> {
fn map_entry(s: &str) -> IResult<&str, VehicleCommandEndpoint> {
// "unlock": &Command{
// help: "Unlock vehicle",
// requiresAuth: true,
Expand Down Expand Up @@ -66,33 +76,32 @@ fn map_entry(s: &str) -> IResult<&str, MapEntry> {
short_trace("requiresFleetAPI", s);
let (s, requires_fleet) = bool_field_or_false(s, "requiresFleetAPI:")?;

// required args
// Required args
short_trace("required args", s);
let (s, required_args) = args(s, "args: []Argument{")?;

// optional args
// Optional args
short_trace("optional args", s);
let (s, optional_args) = args(s, "optional: []Argument{")?;

// check and ignore the handler, as there's not really much data we can take out of it.
// Ignore the handler, as there's not really much data we can take out of it.
let (s, _) = ignore_whitespace(s)?;

let (s, _) = take_until("},")(s)?;
let (s, _) = tag("},")(s)?;

// And the end of the record...
let (s, _) = take_until("},")(s)?;
let (s, _) = tag("},")(s)?;

dbg!(endpoint, help, requires_auth, requires_fleet);
let map_entry = VehicleCommandEndpoint {
endpoint: endpoint.to_string(),
help: help.to_string(),
requires_auth,
requires_fleet,
};
debug!(?map_entry);

Ok((
s,
MapEntry {
endpoint: endpoint.to_string(),
help: help.to_string(),
},
))
Ok((s, map_entry))
}

/// Ignore the quotes and return the inner string.
Expand Down

0 comments on commit 3f7df75

Please sign in to comment.