// facade 出口层 —— 移植自 rust jeeflow-facade/src/lib.rs L20-402(C1/C5/C7/C22 强制层)
// ⚠️ builtin Json Number 是 Double:>2^53 的雪花 id 在构造期就丢精度,
// 故行 VO 构造时 id 即以十进制串落键(stringify_ids 留作递归安全网,覆盖嵌套/复数数组)。
///|
pub const CODE_SUCCESS : Int64 = 0L
///|
pub const CODE_ERROR : Int64 = 99999999L
///|
pub fn ok(data : Json?) -> Json {
let obj = @json.empty_object()
@json.obj_set(obj, "code", @json.number_of(CODE_SUCCESS))
@json.obj_set(obj, "msg", @json.string_of("成功"))
@json.obj_set(obj, "data", match data {
Some(d) => d
None => @json.null_json()
})
obj
}
///|
pub fn ok_data(data : Json) -> Json {
ok(Some(data))
}
///|
pub fn err(msg : String) -> Json {
let obj = @json.empty_object()
@json.obj_set(obj, "code", @json.number_of(CODE_ERROR))
@json.obj_set(obj, "msg", @json.string_of(msg))
obj
}
///|
pub fn is_ok(env : Json) -> Bool {
match @json.get_i64(env, "code") {
Some(c) => c == 0L
None => false
}
}
///|
pub fn envelope_data(env : Json) -> Json? {
@json.get(env, "data")
}
///|
fn to_camel(s : String) -> String {
let b = StringBuilder::new()
let mut upper_next = false
for ch in s {
if ch == '_' {
upper_next = true
} else if upper_next {
let code = ch.to_int()
if code >= 97 && code <= 122 {
b.write_char(Char::from_int(code - 32))
} else {
b.write_char(ch)
}
upper_next = false
} else {
b.write_char(ch)
}
}
b.to_string()
}
///|
/// 自由形态嵌套 map 键(VO 顶层 camelCase,内部保留原键)
fn is_opaque_value_key(k : String) -> Bool {
k == "jsonObject" ||
k == "json_object" ||
k == "ext" ||
k == "instanceExt" ||
k == "instance_ext" ||
k == "variable" ||
k == "taskFormData" ||
k == "task_form_data" ||
k == "formData" ||
k == "form_data" ||
k == "nodeProgress" ||
k == "node_progress"
}
///|
fn to_camel_json_inner(val : Json, convert_keys : Bool) -> Json {
match val {
Json::Object(map) => {
let out = @json.empty_object()
for k, v in map {
let new_key = if convert_keys { to_camel(k) } else { k }
let child_convert = convert_keys && !is_opaque_value_key(k) && !is_opaque_value_key(new_key)
@json.obj_set(out, new_key, to_camel_json_inner(v, child_convert))
}
out
}
Json::Array(arr) => {
let out = @json.empty_array()
for v in arr {
@json.arr_push(out, to_camel_json_inner(v, convert_keys))
}
out
}
other => other
}
}
///|
pub fn to_camel_json(val : Json) -> Json {
to_camel_json_inner(val, true)
}
///|
/// C1:id/*Id/*_id 单数键值字符串化(递归);ids/*Ids/*_ids 复数键逐元素
pub fn stringify_ids(val : Json) -> Json {
match val {
Json::Object(map) => {
let out = @json.empty_object()
for k, v in map {
if k == "id" || k.ends_with("Id") || k.ends_with("_id") {
@json.obj_set(out, k, stringify_id_value(v))
} else if k == "ids" || k.ends_with("Ids") || k.ends_with("_ids") {
@json.obj_set(out, k, stringify_id_array(v))
} else {
@json.obj_set(out, k, stringify_ids(v))
}
}
out
}
Json::Array(arr) => {
let out = @json.empty_array()
for v in arr {
@json.arr_push(out, stringify_ids(v))
}
out
}
other => other
}
}
///|
fn stringify_id_value(v : Json) -> Json {
match v {
// id 字符串化走 repr 原文优先(>2^53 防 double 取整,D-M5-3)
Json::Number(n, repr~) =>
match repr {
Some(s) => @json.string_of(s)
None => @json.string_of(n.to_int64().to_string())
}
other => other
}
}
///|
fn stringify_id_array(v : Json) -> Json {
match v {
Json::Array(arr) => {
let out = @json.empty_array()
for item in arr {
@json.arr_push(out, stringify_id_value(item))
}
out
}
other => stringify_id_value(other)
}
}
///|
/// C7:时间键格式化(NOW() 占位 / ISO T → 空格)
pub fn format_time_fields(val : Json) -> Json {
match val {
Json::Object(map) => {
let out = @json.empty_object()
for k, v in map {
if k.ends_with("Time") || k.ends_with("_time") {
@json.obj_set(out, k, format_time_value(v))
} else {
@json.obj_set(out, k, format_time_fields(v))
}
}
out
}
Json::Array(arr) => {
let out = @json.empty_array()
for v in arr {
@json.arr_push(out, format_time_fields(v))
}
out
}
other => other
}
}
///|
fn format_time_value(v : Json) -> Json {
match v {
Json::String(s) => {
if s == "NOW()" || s == "NOW" {
return @json.string_of(@model.current_time_str())
}
// ISO 'T' → 空格(19 位近似可直接换)
if s.length() >= 19 {
let bytes = s.to_array()
if bytes[10] == 'T' {
let approx = s.substring(start=0, end=10).to_string() + " " + s.substring(start=11, end=19).to_string()
return @json.string_of(approx)
}
// 毫秒尾巴 ".fff" 截断
if s.length() > 19 && s.to_array()[10] == ' ' {
return @json.string_of(s.substring(start=0, end=19).to_string())
}
}
@json.string_of(s)
}
other => other
}
}
///|
/// 出口统一变换:camelCase → id 字符串化 → 时间格式化
pub fn transform_output(val : Json) -> Json {
format_time_fields(stringify_ids(to_camel_json(val)))
}
///|
/// 分页五键(C22)
pub fn page_to_json[T](page : @model.PageResult[T], row_json : (T) -> Json) -> Json {
let obj = @json.empty_object()
@json.obj_set(obj, "pageNum", @json.number_of(page.page_num()))
@json.obj_set(obj, "pageSize", @json.number_of(page.page_size()))
@json.obj_set(obj, "recordCount", @json.number_of(page.record_count()))
@json.obj_set(obj, "totalPage", @json.number_of(page.total_page()))
let rows = @json.empty_array()
for r in page.rows() {
@json.arr_push(rows, row_json(r))
}
@json.obj_set(obj, "rows", rows)
obj
}
///|
pub fn parse_graph(content : String) -> Json? {
let t = content.trim(chars=" \t\r\n").to_string()
if t.is_empty() {
return None
}
match (try? @json.parse_json(t)) {
Ok(v) => Some(v)
Err(_) => None
}
}
///|
pub fn parse_json_map(s : String?) -> Map[String, Json] {
let out : Map[String, Json] = Map::new()
match s {
Some(raw) => {
let t = raw.trim(chars=" \t\r\n").to_string()
if !t.is_empty() {
match (try? @json.parse_json(t)) {
Ok(Json::Object(m)) =>
for k, v in m {
out[k] = v
}
_ => ()
}
}
}
None => ()
}
out
}
///|
/// 表单数据派生(issues/15):prefix 键带前缀 + 去前缀双份
pub fn form_data_of_map(vars : Map[String, Json], prefix : String) -> Json {
let out = @json.empty_object()
for k, v in vars {
if k.starts_with(prefix) {
@json.obj_set(out, k, v)
@json.obj_set(out, k.substring(start=prefix.length()).to_string(), v)
}
}
out
}
///|
pub fn form_data_of_flow(vars : @json.FlowData, prefix : String) -> Json {
form_data_of_map(vars.inner(), prefix)
}
///|
pub fn flow_data_to_object(vars : @json.FlowData) -> Json {
vars.to_object_json()
}
///|
pub fn first_task_node_id(json_object : Json?) -> String? {
match json_object {
Some(obj) =>
match obj.value("nodes") {
Some(Json::Array(nodes)) => {
for n in nodes {
match n.value("type") {
Some(Json::String(t)) =>
if t == "snaker:task" {
return @json.as_str(n.value("id").unwrap_or(@json.null_json()))
}
_ => ()
}
}
None
}
_ => None
}
None => None
}
}
///|
fn opt_json(s : String?) -> Json {
match s {
Some(v) => @json.string_of(v)
None => @json.null_json()
}
}
///|
fn num_json(n : Int) -> Json {
@json.number_of(n.to_int64())
}
///|
fn opt_num_json(n : Int?) -> Json {
match n {
Some(v) => num_json(v)
None => @json.null_json()
}
}
///|
fn i64_json(n : Int64) -> Json {
@json.string_of(n.to_string())
}
///|
fn opt_i64_json(n : Int64?) -> Json {
match n {
Some(v) => i64_json(v)
None => @json.null_json()
}
}