use arrow::datatypes::{DataType, TimeUnit};
pub mod value;
pub use value::{encode_record, encode_records};
pub fn wp_type_to_arrow(dt: &wp_model_core::model::DataType) -> DataType {
use wp_model_core::model::DataType as WpDt;
match dt {
WpDt::Bool => DataType::Boolean,
WpDt::Int => DataType::Int64,
WpDt::BigInt => DataType::Utf8,
WpDt::Float => DataType::Float64,
WpDt::Port => DataType::Int32,
WpDt::Time
| WpDt::TimeISO
| WpDt::TimeRFC3339
| WpDt::TimeRFC2822
| WpDt::TimeTIMESTAMP
| WpDt::TimeCLF => DataType::Timestamp(TimeUnit::Nanosecond, None),
WpDt::Hex => DataType::Utf8,
WpDt::Base64 => DataType::Binary,
WpDt::Chars
| WpDt::Symbol
| WpDt::PeekSymbol
| WpDt::IP
| WpDt::IpNet
| WpDt::Domain
| WpDt::Email
| WpDt::Url
| WpDt::SN
| WpDt::IdCard
| WpDt::MobilePhone
| WpDt::KV
| WpDt::KvArr
| WpDt::Json
| WpDt::ExactJson
| WpDt::HttpRequest
| WpDt::HttpStatus
| WpDt::HttpAgent
| WpDt::HttpMethod
| WpDt::Auto
| WpDt::ProtoText
| WpDt::Obj
| WpDt::Ignore => DataType::Utf8,
WpDt::Array(_) => DataType::Utf8,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wire_contract_full_mapping_is_pinned() {
use wp_model_core::model::{ArraySubtype, DataType as WpDt};
let ts = DataType::Timestamp(TimeUnit::Nanosecond, None);
let cases: Vec<(WpDt, DataType)> = vec![
(WpDt::Bool, DataType::Boolean),
(WpDt::Chars, DataType::Utf8),
(WpDt::Symbol, DataType::Utf8),
(WpDt::PeekSymbol, DataType::Utf8),
(WpDt::Int, DataType::Int64),
(WpDt::BigInt, DataType::Utf8),
(WpDt::Float, DataType::Float64),
(WpDt::Ignore, DataType::Utf8),
(WpDt::Time, ts.clone()),
(WpDt::TimeISO, ts.clone()),
(WpDt::TimeRFC3339, ts.clone()),
(WpDt::TimeRFC2822, ts.clone()),
(WpDt::TimeTIMESTAMP, ts.clone()),
(WpDt::TimeCLF, ts.clone()),
(WpDt::IP, DataType::Utf8),
(WpDt::IpNet, DataType::Utf8),
(WpDt::Domain, DataType::Utf8),
(WpDt::Email, DataType::Utf8),
(WpDt::Port, DataType::Int32),
(WpDt::SN, DataType::Utf8),
(WpDt::Hex, DataType::Utf8),
(WpDt::Base64, DataType::Binary),
(WpDt::KV, DataType::Utf8),
(WpDt::KvArr, DataType::Utf8),
(WpDt::Json, DataType::Utf8),
(WpDt::ExactJson, DataType::Utf8),
(WpDt::HttpRequest, DataType::Utf8),
(WpDt::HttpStatus, DataType::Utf8),
(WpDt::HttpAgent, DataType::Utf8),
(WpDt::HttpMethod, DataType::Utf8),
(WpDt::Url, DataType::Utf8),
(WpDt::Auto, DataType::Utf8),
(WpDt::ProtoText, DataType::Utf8),
(WpDt::Obj, DataType::Utf8),
(WpDt::Array(ArraySubtype::new("int")), DataType::Utf8),
(WpDt::IdCard, DataType::Utf8),
(WpDt::MobilePhone, DataType::Utf8),
];
assert_eq!(
cases.len(),
37,
"wp-model-core DataType 变体数变化 → 同步更新规格表 §3"
);
for (dt, expected) in cases {
assert_eq!(wp_type_to_arrow(&dt), expected, "DataType::{dt:?}");
}
}
#[test]
fn hex_must_be_utf8() {
assert_eq!(
wp_type_to_arrow(&wp_model_core::model::DataType::Hex),
DataType::Utf8
);
}
#[test]
fn wire_contract_differs_from_the_typed_frontend_on_two_rows() {
use crate::schema::{BIGINT_DECIMAL_PRECISION, WpDataType, to_arrow_type};
use wp_model_core::model::{ArraySubtype, DataType as WpDt};
assert_eq!(wp_type_to_arrow(&WpDt::BigInt), DataType::Utf8);
assert_eq!(
to_arrow_type(&WpDataType::BigInt),
DataType::Decimal256(BIGINT_DECIMAL_PRECISION, 0)
);
assert_eq!(
wp_type_to_arrow(&WpDt::Array(ArraySubtype::new("int"))),
DataType::Utf8
);
assert!(
matches!(
to_arrow_type(&WpDataType::Array(Box::new(WpDataType::Digit))),
DataType::List(_)
),
"前端对结构化数组给 List(inner)(它自己的口径)"
);
}
#[test]
fn wire_contract_and_frontend_agree_on_the_overlap() {
use crate::schema::{WpDataType, to_arrow_type};
use wp_model_core::model::DataType as WpDt;
let rows = [
(WpDt::Bool, WpDataType::Bool),
(WpDt::Int, WpDataType::Digit),
(WpDt::Float, WpDataType::Float),
(WpDt::Time, WpDataType::Time),
(WpDt::Chars, WpDataType::Chars),
(WpDt::IP, WpDataType::Ip),
(WpDt::Hex, WpDataType::Hex),
];
for (model, frontend) in rows {
assert_eq!(
wp_type_to_arrow(&model),
to_arrow_type(&frontend),
"重叠行应一致:{model:?} vs {frontend:?}"
);
}
}
}