use indexmap::IndexMap;
use crate::genai_types::Schema;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpMethod {
Get,
Post,
Put,
Patch,
Delete,
Head,
Options,
Trace,
}
impl HttpMethod {
pub fn as_str(self) -> &'static str {
match self {
Self::Get => "GET",
Self::Post => "POST",
Self::Put => "PUT",
Self::Patch => "PATCH",
Self::Delete => "DELETE",
Self::Head => "HEAD",
Self::Options => "OPTIONS",
Self::Trace => "TRACE",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ParamLocation {
Path,
Query,
Header,
Cookie,
Body,
}
#[derive(Debug, Clone)]
pub struct ApiParameter {
pub name: String,
pub py_name: String,
pub location: ParamLocation,
pub schema: Schema,
pub required: bool,
pub description: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ParsedOperation {
pub name: String,
pub description: String,
pub base_url: String,
pub path: String,
pub method: HttpMethod,
pub parameters: Vec<ApiParameter>,
pub security_schemes: Vec<String>,
}
impl ParsedOperation {
#[must_use]
pub fn build_args_schema(&self) -> Schema {
let mut props: IndexMap<String, Schema> = IndexMap::new();
let mut required: Vec<String> = Vec::new();
for p in &self.parameters {
let mut s = p.schema.clone();
if let Some(desc) = &p.description {
s = s.with_description(desc);
}
props.insert(p.py_name.clone(), s);
if p.required {
required.push(p.py_name.clone());
}
}
let mut schema = Schema::object();
for (k, v) in props {
schema = schema.property(k, v);
}
for r in required {
schema = schema.require(r);
}
schema
}
}
#[must_use]
pub fn to_snake_case(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut prev_lower = false;
for c in s.chars() {
if c.is_alphanumeric() || c == '_' {
if c.is_ascii_uppercase() {
if prev_lower {
out.push('_');
}
out.push(c.to_ascii_lowercase());
prev_lower = false;
} else {
out.push(c);
prev_lower = c.is_ascii_lowercase() || c.is_ascii_digit();
}
} else {
out.push('_');
prev_lower = false;
}
}
let mut cleaned = String::with_capacity(out.len());
let mut prev_us = false;
for c in out.chars() {
if c == '_' {
if !prev_us {
cleaned.push(c);
}
prev_us = true;
} else {
cleaned.push(c);
prev_us = false;
}
}
cleaned.trim_matches('_').to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snake_case_smoke() {
assert_eq!(to_snake_case("getPetById"), "get_pet_by_id");
assert_eq!(to_snake_case("List-Pets"), "list_pets");
assert_eq!(to_snake_case("HTTPError"), "httperror");
}
}