chore: 提交所有工作进度 — SaaS 后端增强、Admin UI、桌面端集成
包含大量 SaaS 平台改进、Admin 管理后台更新、桌面端集成完善、 文档同步、测试文件重构等内容。为 QA 测试准备干净工作树。
This commit is contained in:
11
skills/json-transform/Cargo.toml
Normal file
11
skills/json-transform/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "json-transform"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
50
skills/json-transform/SKILL.md
Normal file
50
skills/json-transform/SKILL.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
name: JSON Transform
|
||||
id: json-transform
|
||||
version: "1.0"
|
||||
mode: wasm
|
||||
description: |
|
||||
将 JSON 数据按指定规则进行转换。
|
||||
支持:字段提取、重命名、过滤、排序等常用转换操作。
|
||||
author: ZCLAW
|
||||
tags:
|
||||
- json
|
||||
- transform
|
||||
- data
|
||||
triggers:
|
||||
- json转换
|
||||
- json transform
|
||||
- 数据转换
|
||||
capabilities:
|
||||
- json-parse
|
||||
- json-transform
|
||||
---
|
||||
|
||||
# JSON Transform
|
||||
|
||||
读取 stdin 中的 JSON 输入,按转换规则处理后输出到 stdout。
|
||||
|
||||
## 输入格式
|
||||
|
||||
```json
|
||||
{
|
||||
"data": { ... },
|
||||
"transforms": [
|
||||
{ "type": "pick", "fields": ["name", "age"] },
|
||||
{ "type": "rename", "from": "name", "to": "fullName" },
|
||||
{ "type": "sort", "field": "age", "order": "desc" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 输出格式
|
||||
|
||||
转换后的 JSON 对象。
|
||||
|
||||
## 构建
|
||||
|
||||
```bash
|
||||
cd skills/json-transform
|
||||
cargo build --target wasm32-wasi --release
|
||||
cp ../../../target/wasm32-wasi/release/json_transform.wasm main.wasm
|
||||
```
|
||||
154
skills/json-transform/src/lib.rs
Normal file
154
skills/json-transform/src/lib.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
//! JSON Transform — WASM guest module for ZCLAW skill system.
|
||||
//!
|
||||
//! Reads JSON from stdin, applies transforms, writes result to stdout.
|
||||
//! Target: `wasm32-wasi`
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "lowercase")]
|
||||
enum Transform {
|
||||
Pick { fields: Vec<String> },
|
||||
Rename { from: String, to: String },
|
||||
Filter { field: String, op: String, value: Value },
|
||||
Sort { field: String, order: String },
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Input {
|
||||
data: Value,
|
||||
#[serde(default)]
|
||||
transforms: Vec<Transform>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Output {
|
||||
success: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
data: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
fn ok(data: Value) -> Self {
|
||||
Self {
|
||||
success: true,
|
||||
data: Some(data),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn err(msg: impl Into<String>) -> Self {
|
||||
Self {
|
||||
success: false,
|
||||
data: None,
|
||||
error: Some(msg.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _start() {
|
||||
let mut input_str = String::new();
|
||||
if let Err(e) = io::stdin().read_to_string(&mut input_str) {
|
||||
write_output(&Output::err(format!("Failed to read stdin: {}", e)));
|
||||
return;
|
||||
}
|
||||
|
||||
let input: Input = match serde_json::from_str(&input_str) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
write_output(&Output::err(format!("Invalid JSON input: {}", e)));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut data = input.data;
|
||||
for transform in input.transforms {
|
||||
data = match apply_transform(data, &transform) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
write_output(&Output::err(e));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write_output(&Output::ok(data));
|
||||
}
|
||||
|
||||
fn apply_transform(data: Value, transform: &Transform) -> Result<Value, String> {
|
||||
match transform {
|
||||
Transform::Pick { fields } => {
|
||||
let obj = data.as_object().ok_or("Pick requires object input")?;
|
||||
let mut result = serde_json::Map::new();
|
||||
for field in fields {
|
||||
if let Some(v) = obj.get(field) {
|
||||
result.insert(field.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
Ok(Value::Object(result))
|
||||
}
|
||||
Transform::Rename { from, to } => {
|
||||
let mut obj = data.as_object().ok_or("Rename requires object input")?.clone();
|
||||
if let Some(v) = obj.remove(from) {
|
||||
obj.insert(to.clone(), v);
|
||||
}
|
||||
Ok(Value::Object(obj))
|
||||
}
|
||||
Transform::Filter { field, op, value } => {
|
||||
let arr = data.as_array().ok_or("Filter requires array input")?;
|
||||
let filtered: Vec<Value> = arr
|
||||
.iter()
|
||||
.filter(|item| {
|
||||
let item_val = item.get(field);
|
||||
match op.as_str() {
|
||||
"eq" => item_val == Some(value),
|
||||
"ne" => item_val != Some(value),
|
||||
"gt" => compare_values(item_val, value).map(|c| c.is_gt()).unwrap_or(false),
|
||||
"lt" => compare_values(item_val, value).map(|c| c.is_lt()).unwrap_or(false),
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
Ok(Value::Array(filtered))
|
||||
}
|
||||
Transform::Sort { field, order } => {
|
||||
let mut arr = data.as_array().ok_or("Sort requires array input")?.clone();
|
||||
let field = field.clone();
|
||||
let ascending = order != "desc";
|
||||
arr.sort_by(|a, b| {
|
||||
let va = a.get(&field).unwrap_or(&Value::Null);
|
||||
let vb = b.get(&field).unwrap_or(&Value::Null);
|
||||
let cmp = compare_values(Some(va), vb).unwrap_or(std::cmp::Ordering::Equal);
|
||||
if ascending { cmp } else { cmp.reverse() }
|
||||
});
|
||||
Ok(Value::Array(arr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_values(a: Option<&Value>, b: &Value) -> Option<std::cmp::Ordering> {
|
||||
let a = a?;
|
||||
match (a, b) {
|
||||
(Value::Number(an), Value::Number(bn)) => {
|
||||
if let (Some(af), Some(bf)) = (an.as_f64(), bn.as_f64()) {
|
||||
Some(af.partial_cmp(&bf).unwrap_or(std::cmp::Ordering::Equal))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
(Value::String(as_), Value::String(bs)) => Some(as_.cmp(bs)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_output(output: &Output) {
|
||||
let json = serde_json::to_string(output).unwrap_or_else(|_| r#"{"success":false}"#.to_string());
|
||||
let _ = io::stdout().write_all(json.as_bytes());
|
||||
let _ = io::stdout().flush();
|
||||
}
|
||||
Reference in New Issue
Block a user