#![allow(dead_code)]
pub mod payload;
pub mod rust_source;
use std::collections::BTreeMap;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Spec {
pub snapshot_version: String,
pub bot_api_version: String,
pub bot_api_date: String,
pub source_url: String,
pub types: BTreeMap<String, SpecType>,
pub methods: BTreeMap<String, SpecMethod>,
pub discriminants: BTreeMap<String, String>,
pub discriminant_fields: BTreeMap<String, String>,
pub enum_values: BTreeMap<String, Vec<String>>,
pub unions: BTreeMap<String, Vec<String>>,
}
#[derive(Debug, Deserialize)]
pub struct SpecField(String, u8);
impl SpecField {
pub fn kind(&self) -> &str {
&self.0
}
pub fn optional(&self) -> bool {
self.1 == 1
}
pub fn base_type(&self) -> &str {
self.0.strip_prefix("Array of ").unwrap_or(&self.0)
}
}
pub type SpecType = BTreeMap<String, SpecField>;
pub type SpecMethod = BTreeMap<String, SpecField>;
const SNAPSHOT: &str = include_str!("../spec/bot-api-10.2.json");
pub fn load() -> Spec {
let spec: Spec = serde_json::from_str(SNAPSHOT).unwrap_or_else(|e| {
panic!(
"the committed spec snapshot failed to parse: {e}\n\
Regenerate it with make_spec_snapshot.py — do not hand-edit it."
)
});
assert!(
!spec.types.is_empty() && !spec.methods.is_empty(),
"the spec snapshot parsed but is empty; every conformance test would \
vacuously pass. Regenerate it."
);
spec
}
use std::path::{Path, PathBuf};
pub fn workspace_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
.canonicalize()
.expect("workspace root resolves")
}
pub fn library_sources() -> Vec<(PathBuf, String)> {
fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
walk(&path, out);
} else if path.extension().is_some_and(|e| e == "rs") {
out.push(path);
}
}
}
let crates = workspace_root().join("crates");
let mut files = Vec::new();
for entry in std::fs::read_dir(&crates)
.expect("crates/ exists")
.flatten()
{
walk(&entry.path().join("src"), &mut files);
}
assert!(
files.len() > 20,
"expected to find the workspace sources, found {} files — the layout may \
have changed and this test would silently check nothing",
files.len()
);
files
.into_iter()
.map(|p| {
let text = std::fs::read_to_string(&p).expect("source file is readable");
(p, text)
})
.collect()
}
pub fn count_occurrences(haystack: &str, needle: &str) -> usize {
let bytes = haystack.as_bytes();
let mut count = 0;
let mut from = 0;
while let Some(found) = haystack[from..].find(needle) {
let start = from + found;
let end = start + needle.len();
let before_ok = start == 0 || !is_ident_byte(bytes[start - 1]);
let after_ok = end == bytes.len() || !is_ident_byte(bytes[end]);
if before_ok && after_ok {
count += 1;
}
from = end;
}
count
}
pub fn is_ident_byte(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_'
}