use serde_json::Value;
pub fn placeholder_for(prop: &str, ty: &str) -> Option<Value> {
let normalized = normalize_key(prop);
let key = normalized.as_str();
match ty {
"string" => string_placeholder(key),
"integer" | "number" => numeric_placeholder(key),
"boolean" => bool_placeholder(key),
_ => None,
}
}
fn string_placeholder(key: &str) -> Option<Value> {
let s: &str = match key {
"firstname" | "givenname" | "fname" => "John",
"lastname" | "familyname" | "surname" | "lname" => "Smith",
"fullname" | "name" | "displayname" => "John Smith",
"middlename" => "Q",
"username" | "login" => "jsmith",
"nickname" => "jay",
"email" | "emailaddress" | "emailid" => "user@example.com",
"phone" | "phonenumber" | "mobile" | "telephone" => "555-0100",
"fax" | "faxnumber" => "555-0199",
"address" | "address1" | "streetaddress" | "street" | "line1" => "123 Main St",
"address2" | "line2" => "Suite 100",
"city" | "town" | "locality" => "San Francisco",
"state" | "region" | "province" => "CA",
"country" | "countryname" => "United States",
"countrycode" => "US",
"zipcode" | "postalcode" | "postcode" | "zip" => "94105",
"company" | "companyname" | "organization" | "orgname" | "business" | "businessname" => {
"Acme Inc"
}
"brand" | "brandname" => "Acme",
"url" | "website" | "homepage" | "link" => "https://example.com",
"domain" | "domainname" => "example.com",
"ipaddress" | "ip" => "192.168.1.1",
"useragent" => "Mozilla/5.0",
"currency" | "currencycode" => "USD",
"sku" | "productcode" | "itemcode" => "SKU-12345",
"productname" | "itemname" => "Menu Item",
"orderref" | "orderreference" | "ordercode" => "ORDER-12345",
"language" | "lang" | "languagecode" => "en",
"locale" => "en-US",
"timezone" | "tz" => "America/Los_Angeles",
"description" => "Sample description",
"comment" | "note" | "notes" | "message" | "body" | "text" => "Sample text",
"title" | "subject" | "headline" => "Sample title",
"slug" => "sample-slug",
"tag" | "label" => "sample",
"status" => "active",
"kind" | "type" | "category" => "default",
"color" | "colour" | "hexcolor" => "#4A90E2",
_ => return None,
};
Some(Value::String(s.to_string()))
}
fn numeric_placeholder(key: &str) -> Option<Value> {
if let Some(name) = id_env_var(key) {
return Some(Value::String(format!("{{{{{name}}}}}")));
}
let n: i64 = match key {
"quantity" | "qty" | "count" | "size" => 1,
"page" | "pagenumber" | "pageindex" => 1,
"pagesize" | "perpage" | "limit" | "take" => 25,
"offset" | "skip" => 0,
"price" | "amount" | "total" | "subtotal" | "cost" | "value" => {
return Some(Value::Number(
serde_json::Number::from_f64(9.99).unwrap_or(0.into()),
));
}
"rating" | "score" | "stars" => 5,
"year" => 2026,
"month" => 1,
"day" => 1,
"hour" => 12,
"minute" | "min" => 30,
"second" | "sec" => 0,
_ => return None,
};
Some(Value::Number(n.into()))
}
fn bool_placeholder(key: &str) -> Option<Value> {
let val = match key {
"enabled" | "active" | "isactive" | "isenabled" | "on" => true,
"disabled" | "inactive" | "deleted" | "archived" | "cancelled" | "off" => false,
_ => return None,
};
Some(Value::Bool(val))
}
pub fn id_env_var(name: &str) -> Option<&'static str> {
let key = normalize_key(name);
match key.as_str() {
"merchantid" | "restaurantid" | "storeid" => Some("MERCHANT_ID"),
"userid" | "accountid" | "customerid" => Some("USER_ID"),
"locationid" | "siteid" | "branchid" => Some("LOCATION_ID"),
"surveyid" => Some("SURVEY_ID"),
"orderid" | "transactionid" => Some("ORDER_ID"),
"productid" | "itemid" | "menuitemid" => Some("PRODUCT_ID"),
"brandid" => Some("BRAND_ID"),
"questionnaireid" => Some("QUESTIONNAIRE_ID"),
"campaignid" => Some("CAMPAIGN_ID"),
_ => None,
}
}
pub fn known_env_vars() -> Vec<&'static str> {
let mut out = vec![
"MERCHANT_ID",
"USER_ID",
"LOCATION_ID",
"SURVEY_ID",
"ORDER_ID",
"PRODUCT_ID",
"BRAND_ID",
"QUESTIONNAIRE_ID",
"CAMPAIGN_ID",
];
out.sort();
out
}
pub fn normalize_key(prop: &str) -> String {
let mut out = String::with_capacity(prop.len());
for c in prop.chars() {
if c.is_ascii_alphanumeric() {
out.push(c.to_ascii_lowercase());
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matches_common_string_properties() {
assert_eq!(
placeholder_for("firstName", "string"),
Some(Value::String("John".into()))
);
assert_eq!(
placeholder_for("first_name", "string"),
Some(Value::String("John".into()))
);
assert_eq!(
placeholder_for("FirstName", "string"),
Some(Value::String("John".into()))
);
assert_eq!(
placeholder_for("emailAddress", "string"),
Some(Value::String("user@example.com".into()))
);
assert_eq!(
placeholder_for("phoneNumber", "string"),
Some(Value::String("555-0100".into()))
);
}
#[test]
fn id_fields_reference_env_vars() {
assert_eq!(
placeholder_for("merchantId", "integer"),
Some(Value::String("{{MERCHANT_ID}}".into()))
);
assert_eq!(
placeholder_for("user_id", "integer"),
Some(Value::String("{{USER_ID}}".into()))
);
}
#[test]
fn numeric_defaults_pick_reasonable_values() {
assert_eq!(placeholder_for("quantity", "integer"), Some(1.into()));
assert_eq!(placeholder_for("pageSize", "integer"), Some(25.into()));
assert_eq!(placeholder_for("rating", "integer"), Some(5.into()));
}
#[test]
fn bool_flags_prefer_active_over_disabled() {
assert_eq!(placeholder_for("enabled", "boolean"), Some(true.into()));
assert_eq!(placeholder_for("isActive", "boolean"), Some(true.into()));
assert_eq!(placeholder_for("archived", "boolean"), Some(false.into()));
}
#[test]
fn unmatched_name_or_wrong_type_returns_none() {
assert_eq!(placeholder_for("frobnicator", "string"), None);
assert_eq!(placeholder_for("firstName", "integer"), None);
assert_eq!(placeholder_for("emailAddress", "boolean"), None);
}
}