use json::{object, JsonValue};
#[derive(Clone, Debug)]
pub enum Fields {
String,
StringPass,
StringKey,
StringTel,
StringIdent,
StringEmail,
StringColor,
StringCode,
StringBarcode,
StringQrcode,
Radio,
Select,
TableList,
TableTree,
Int,
Switch,
Dict,
Text,
TextEditor,
TextUrl,
Json,
Array,
Object,
Float,
DateTime,
Year,
}
impl Fields {
pub fn name(&mut self, name: &str) -> Field {
Field::new(self.clone(), name)
}
pub fn str(self) -> &'static str {
match self {
Fields::String
| Fields::StringPass
| Fields::StringKey
| Fields::StringTel
| Fields::StringIdent
| Fields::StringEmail
| Fields::StringColor
| Fields::StringCode
| Fields::StringBarcode
| Fields::StringQrcode => "string",
Fields::Radio => "radio",
Fields::Select => "select",
Fields::TableList => "table",
Fields::TableTree => "tree",
Fields::Int => "int",
Fields::Switch => "bool",
Fields::Dict => "string",
Fields::Text | Fields::TextEditor | Fields::TextUrl => "text",
Fields::Json => "json",
Fields::Array => "array",
Fields::Object => "object",
Fields::Float => "float",
Fields::DateTime => "datetime",
Fields::Year => "year",
}
}
}
#[derive(Debug)]
pub struct Field {
mode: Fields,
require: bool,
field: String,
title: String,
def: JsonValue,
length: usize,
dec: usize,
option: Vec<&'static str>,
show: bool,
describe: String,
example: JsonValue,
api: String,
table_name: String,
multiple: bool,
}
impl Field {
pub fn new(mode: Fields, name: &str) -> Field {
Self {
mode,
require: true,
field: name.to_string(),
title: "".to_string(),
def: JsonValue::Null,
length: 0,
dec: 0,
option: vec![],
show: true,
describe: "".to_string(),
example: JsonValue::Null,
api: "".to_string(),
table_name: "".to_string(),
multiple: false,
}
}
pub fn title(mut self, name: &str) -> Field {
self.title = name.to_string();
self
}
pub fn def(mut self, name: JsonValue) -> Field {
self.def = name;
self
}
pub fn require(mut self, require: bool) -> Field {
self.require = require;
self
}
pub fn show(mut self, show: bool) -> Field {
self.show = show;
self
}
pub fn example(mut self, example: JsonValue) -> Field {
self.example = example;
self
}
pub fn length(mut self, length: usize) -> Field {
self.length = length;
self
}
pub fn describe(mut self, describe: &str) -> Field {
self.describe = describe.to_string();
self
}
pub fn option(mut self, option: Vec<&'static str>) -> Field {
self.option = option;
self
}
pub fn table(mut self, table: &str, api: &str, fields: Vec<&'static str>) -> Field {
self.api = api.to_string();
self.table_name = table.to_string();
self.option = fields;
self
}
pub fn multiple(mut self, multiple: bool, count: usize) -> Field {
self.multiple = multiple;
if count == 0 {
self.length = 1
} else {
self.length = count;
}
self
}
pub fn dec(mut self, dec: usize) -> Field {
self.dec = dec;
self
}
pub fn sql(self) -> JsonValue {
object! {}
}
pub fn swagger(self) -> JsonValue {
object! {
"type": self.mode.str(),
"example": self.example,
}
}
pub fn field(self) -> JsonValue {
let example = if self.example.is_empty() {
self.def.clone()
} else {
self.example
};
let mut field = object! {
"require"=> self.require,
"field"=> self.field,
"title"=> self.title,
"show"=> self.show,
"describe"=> self.describe,
"example"=> example
};
match self.mode {
Fields::String
| Fields::StringPass
| Fields::StringKey
| Fields::StringTel
| Fields::StringIdent
| Fields::StringEmail
| Fields::StringColor
| Fields::StringCode
| Fields::StringBarcode
| Fields::StringQrcode => {
field["length"] = self.length.into();
field["def"] = self.def;
}
Fields::Radio => {
field["option"] = self.option.into();
field["def"] = self.def;
}
Fields::Select => {
field["option"] = self.option.into();
field["def"] = self.def;
}
Fields::TableList | Fields::TableTree | Fields::Dict => {
field["option"] = self.option.into();
field["def"] = self.def;
field["api"] = self.api.into();
field["table"] = self.table_name.into();
field["multiple"] = self.multiple.into();
field["length"] = self.length.into();
}
Fields::Int => {
field["length"] = self.length.into();
field["def"] = self.def;
}
Fields::Switch => {
field["def"] = self.def;
}
Fields::Text
| Fields::TextEditor
| Fields::TextUrl
| Fields::Json
| Fields::Array
| Fields::Object => {
field["length"] = self.length.into();
field["def"] = self.def;
}
Fields::Float => {
field["length"] = self.length.into();
field["dec"] = self.dec.into();
field["def"] = self.def;
}
Fields::DateTime => {
field["def"] = self.def;
}
Fields::Year => {
field["def"] = self.def;
}
};
field
}
}
#[cfg(test)]
mod tests {
use super::*;
use json::JsonValue;
#[test]
fn fields_str_string_variants() {
assert_eq!(Fields::String.str(), "string");
assert_eq!(Fields::StringPass.str(), "string");
assert_eq!(Fields::StringKey.str(), "string");
assert_eq!(Fields::StringTel.str(), "string");
assert_eq!(Fields::StringIdent.str(), "string");
assert_eq!(Fields::StringEmail.str(), "string");
assert_eq!(Fields::StringColor.str(), "string");
assert_eq!(Fields::StringCode.str(), "string");
assert_eq!(Fields::StringBarcode.str(), "string");
assert_eq!(Fields::StringQrcode.str(), "string");
}
#[test]
fn fields_str_other_variants() {
assert_eq!(Fields::Radio.str(), "radio");
assert_eq!(Fields::Select.str(), "select");
assert_eq!(Fields::TableList.str(), "table");
assert_eq!(Fields::TableTree.str(), "tree");
assert_eq!(Fields::Int.str(), "int");
assert_eq!(Fields::Switch.str(), "bool");
assert_eq!(Fields::Dict.str(), "string");
assert_eq!(Fields::Text.str(), "text");
assert_eq!(Fields::TextEditor.str(), "text");
assert_eq!(Fields::TextUrl.str(), "text");
assert_eq!(Fields::Json.str(), "json");
assert_eq!(Fields::Array.str(), "array");
assert_eq!(Fields::Object.str(), "object");
assert_eq!(Fields::Float.str(), "float");
assert_eq!(Fields::DateTime.str(), "datetime");
assert_eq!(Fields::Year.str(), "year");
}
#[test]
fn fields_name_creates_field() {
let f = Fields::Int.name("age");
assert_eq!(f.field, "age");
assert!(matches!(f.mode, Fields::Int));
}
#[test]
fn field_new_defaults() {
let f = Field::new(Fields::String, "name");
assert!(matches!(f.mode, Fields::String));
assert!(f.require);
assert!(f.show);
assert_eq!(f.field, "name");
assert_eq!(f.title, "");
assert!(f.def.is_null());
assert_eq!(f.length, 0);
assert_eq!(f.dec, 0);
assert!(f.option.is_empty());
assert_eq!(f.describe, "");
assert!(f.example.is_null());
assert_eq!(f.api, "");
assert_eq!(f.table_name, "");
assert!(!f.multiple);
}
#[test]
fn field_title() {
let res = Fields::String.name("x").title("My Title").field();
assert_eq!(res["title"], "My Title");
}
#[test]
fn field_def() {
let res = Fields::String.name("x").def("default_val".into()).field();
assert_eq!(res["def"], "default_val");
}
#[test]
fn field_require_false() {
let res = Fields::String.name("x").require(false).field();
assert_eq!(res["require"], false);
}
#[test]
fn field_show_false() {
let res = Fields::String.name("x").show(false).field();
assert_eq!(res["show"], false);
}
#[test]
fn field_example_set() {
let res = Fields::String.name("x").example("ex".into()).field();
assert_eq!(res["example"], "ex");
}
#[test]
fn field_length() {
let res = Fields::String.name("x").length(50).field();
assert_eq!(res["length"], 50);
}
#[test]
fn field_describe() {
let res = Fields::String.name("x").describe("desc text").field();
assert_eq!(res["describe"], "desc text");
}
#[test]
fn field_option() {
let res = Fields::Radio.name("x").option(vec!["a", "b"]).field();
assert_eq!(res["option"][0], "a");
assert_eq!(res["option"][1], "b");
}
#[test]
fn field_table() {
let res = Fields::TableList
.name("x")
.table("my_table", "/api/list", vec!["col1", "col2"])
.field();
assert_eq!(res["table"], "my_table");
assert_eq!(res["api"], "/api/list");
assert_eq!(res["option"][0], "col1");
assert_eq!(res["option"][1], "col2");
}
#[test]
fn field_multiple_count_zero() {
let f = Field::new(Fields::TableList, "x").multiple(true, 0);
assert!(f.multiple);
assert_eq!(f.length, 1);
}
#[test]
fn field_multiple_count_nonzero() {
let f = Field::new(Fields::TableList, "x").multiple(true, 5);
assert!(f.multiple);
assert_eq!(f.length, 5);
}
#[test]
fn field_dec() {
let res = Fields::Float.name("x").dec(4).field();
assert_eq!(res["dec"], 4);
}
#[test]
fn field_sql_returns_empty_object() {
let res = Fields::String.name("x").sql();
assert!(res.is_object());
assert_eq!(res.len(), 0);
}
#[test]
fn field_swagger_returns_type_and_example() {
let res = Fields::Int.name("x").example(42.into()).swagger();
assert_eq!(res["type"], "int");
assert_eq!(res["example"], 42);
}
#[test]
fn field_output_string_variants() {
let variants = [
Fields::String,
Fields::StringPass,
Fields::StringKey,
Fields::StringTel,
Fields::StringIdent,
Fields::StringEmail,
Fields::StringColor,
Fields::StringCode,
Fields::StringBarcode,
Fields::StringQrcode,
];
for mut v in variants {
let res = v.name("s").length(100).def("d".into()).field();
assert_eq!(res["length"], 100);
assert_eq!(res["def"], "d");
}
}
#[test]
fn field_output_radio() {
let res = Fields::Radio
.name("r")
.option(vec!["yes", "no"])
.def("yes".into())
.field();
assert_eq!(res["option"][0], "yes");
assert_eq!(res["option"][1], "no");
assert_eq!(res["def"], "yes");
assert!(res["length"].is_null());
}
#[test]
fn field_output_select() {
let res = Fields::Select
.name("s")
.option(vec!["a", "b", "c"])
.def("a".into())
.field();
assert_eq!(res["option"].len(), 3);
assert_eq!(res["def"], "a");
assert!(res["length"].is_null());
}
#[test]
fn field_output_table_list() {
let res = Fields::TableList
.name("t")
.table("tbl", "/api", vec!["f1"])
.multiple(true, 3)
.def("x".into())
.field();
assert_eq!(res["option"][0], "f1");
assert_eq!(res["def"], "x");
assert_eq!(res["api"], "/api");
assert_eq!(res["table"], "tbl");
assert_eq!(res["multiple"], true);
assert_eq!(res["length"], 3);
}
#[test]
fn field_output_table_tree() {
let res = Fields::TableTree
.name("t")
.table("tree_tbl", "/tree", vec!["c1", "c2"])
.multiple(false, 0)
.def(JsonValue::Null)
.field();
assert_eq!(res["table"], "tree_tbl");
assert_eq!(res["api"], "/tree");
assert_eq!(res["multiple"], false);
assert_eq!(res["length"], 1);
}
#[test]
fn field_output_dict() {
let res = Fields::Dict
.name("d")
.table("dict_tbl", "/dict", vec!["k"])
.def("v".into())
.field();
assert_eq!(res["table"], "dict_tbl");
assert_eq!(res["api"], "/dict");
assert_eq!(res["def"], "v");
assert!(!res["multiple"].is_null());
}
#[test]
fn field_output_int() {
let res = Fields::Int.name("i").length(11).def(0.into()).field();
assert_eq!(res["length"], 11);
assert_eq!(res["def"], 0);
}
#[test]
fn field_output_switch() {
let res = Fields::Switch.name("sw").def(true.into()).field();
assert_eq!(res["def"], true);
assert!(res["length"].is_null());
}
#[test]
fn field_output_text_variants() {
let variants = [
Fields::Text,
Fields::TextEditor,
Fields::TextUrl,
Fields::Json,
Fields::Array,
Fields::Object,
];
for mut v in variants {
let res = v.name("t").length(5000).def("body".into()).field();
assert_eq!(res["length"], 5000);
assert_eq!(res["def"], "body");
}
}
#[test]
fn field_output_float() {
let res = Fields::Float
.name("f")
.length(10)
.dec(2)
.def(1.5.into())
.field();
assert_eq!(res["length"], 10);
assert_eq!(res["dec"], 2);
assert_eq!(res["def"], 1.5);
}
#[test]
fn field_output_datetime() {
let res = Fields::DateTime.name("dt").def("2026-01-01".into()).field();
assert_eq!(res["def"], "2026-01-01");
assert!(res["length"].is_null());
}
#[test]
fn field_output_year() {
let res = Fields::Year.name("y").def(2026.into()).field();
assert_eq!(res["def"], 2026);
assert!(res["length"].is_null());
}
#[test]
fn field_example_fallback_to_def() {
let res = Fields::String.name("x").def("fallback".into()).field();
assert_eq!(res["example"], "fallback");
}
#[test]
fn field_example_explicit_overrides_def() {
let res = Fields::String
.name("x")
.def("default".into())
.example("explicit".into())
.field();
assert_eq!(res["example"], "explicit");
}
}