use inquire::{Confirm, MultiSelect, Select, Text};
use serde_json::{Map, Value, json};
use strum::VariantNames;
use crate::ResponseFormat;
use crate::error::FlarerError;
#[derive(Debug, Clone, Copy, strum::Display, strum::EnumString, strum::VariantNames)]
#[strum(serialize_all = "lowercase")]
pub enum JsonType {
Object,
Array,
String,
Number,
Boolean,
Null,
}
#[derive(Debug, Clone)]
struct Item {
name: String,
type_: String,
}
fn cfg<E: std::fmt::Display>(e: E) -> FlarerError {
FlarerError::Config(e.to_string())
}
fn properties_builder() -> crate::Result<Vec<String>> {
let mut names: Vec<String> = Vec::new();
println!(
"Define one or more top-level properties. e.g. for an e-commerce page \
you might want a `products` array."
);
let first = Text::new("First property name:")
.with_help_message("e.g. products")
.prompt()
.map_err(cfg)?;
names.push(first);
loop {
let more = Confirm::new("Add another top-level property?")
.with_default(false)
.prompt()
.map_err(cfg)?;
if !more {
break;
}
let next = Text::new("Property name:").prompt().map_err(cfg)?;
names.push(next);
}
Ok(names)
}
type PropertyDef = (String, Vec<Item>, Vec<String>);
fn items_builder(names: Vec<String>) -> crate::Result<Vec<PropertyDef>> {
let mut results = Vec::new();
for name in names {
loop {
let mut items: Vec<Item> = Vec::new();
println!(
"Defining fields for `{name}`. After each field choose whether to add another."
);
loop {
let item_name = Text::new("Field name:").prompt().map_err(cfg)?;
let item_type = Select::new("Field type:", JsonType::VARIANTS.to_vec())
.prompt()
.map_err(cfg)?;
items.push(Item {
name: item_name,
type_: item_type.to_string(),
});
let more = Confirm::new("Add another field?")
.with_default(false)
.prompt()
.map_err(cfg)?;
if !more {
break;
}
}
let required = MultiSelect::new(
"Select required fields:",
items.iter().map(|i| i.name.clone()).collect(),
)
.prompt()
.map_err(cfg)?;
let confirm = Confirm::new(&format!(
"Confirm fields for `{name}`: {:?} (required: {:?})",
items, required
))
.with_default(true)
.prompt()
.map_err(cfg)?;
if confirm {
results.push((name.clone(), items, required));
break;
}
}
}
Ok(results)
}
fn assemble(values: Vec<PropertyDef>) -> Value {
let mut root = Map::new();
for (name, items, required) in values {
let mut props = Map::new();
for it in items {
props.insert(it.name, json!({ "type": it.type_ }));
}
root.insert(
name,
json!({
"type": "array",
"items": {
"type": "object",
"properties": Value::Object(props),
"required": required,
}
}),
);
}
json!({
"type": "object",
"properties": Value::Object(root),
})
}
pub async fn build_response_format_interactive() -> crate::Result<ResponseFormat> {
let names = properties_builder()?;
let items = items_builder(names)?;
Ok(ResponseFormat::json_schema(assemble(items)))
}