diff --git a/fieldx/README.md b/fieldx/README.md new file mode 100644 index 0000000..2d4f787 --- /dev/null +++ b/fieldx/README.md @@ -0,0 +1,292 @@ +# FieldX - Schema-based Object Generator + +FieldX 提供了基于预定义 Schema 生成 `map[string]any` 对象的功能。 + +## 功能特性 + +- **固定值字段 (string)**: 在生成的对象中设置固定的字符串值 +- **字段引用 (field)**: 从源对象中获取指定字段的值,支持嵌套路径访问 +- **嵌套对象 (object)**: 支持生成多层嵌套的复杂对象结构 + +## 快速开始 + +```go +import "git.fsdpf.net/go/reflux/fieldx" + +// 1. 定义 JSON Schema +jsonSchema := `{ + "name": {"type": "field", "value": "userName"}, + "status": {"type": "string", "value": "active"} +}` + +// 2. 从 JSON 创建 Schema +schema, _ := fieldx.SchemaFromJSON(jsonSchema) + +// 3. 直接生成对象 (一行代码!) +source := map[string]any{"userName": "Alice"} +result, _ := schema.Generate(source) + +// result: map[name:Alice status:active] +``` + +## 使用示例 + +### 推荐方式: 直接使用 Schema + +最简单直接的方式: + +```go +import "git.fsdpf.net/go/reflux/fieldx" + +// 1. 从 JSON 创建 Schema +schema, _ := fieldx.SchemaFromJSON(`{ + "name": {"type": "field", "value": "userName"}, + "status": {"type": "string", "value": "active"} +}`) + +// 2. 直接生成对象 +source := map[string]any{"userName": "Alice"} +result, _ := schema.Generate(source) +// result: map[name:Alice status:active] +``` + +### 创建 Schema 的方式 + +#### 方式一: SchemaFromJSON (推荐) +```go +schema, _ := fieldx.SchemaFromJSON(jsonStr) +``` + +#### 方式二: json.Unmarshal +```go +var schema fieldx.Schema +json.Unmarshal([]byte(jsonStr), &schema) +``` + +#### 方式三: SchemaFromMap +```go +var data map[string]any +json.Unmarshal([]byte(jsonStr), &data) +schema, _ := fieldx.SchemaFromMap(data) +``` + +#### 方式四: 编程方式 +```go +schema := fieldx.Schema{ + "field": fieldx.Field{ + Type: fieldx.FieldTypeString, + Value: "value", + }, +} +``` + +### 嵌套对象示例 + +```go +schema := fieldx.Schema{ + "type": fieldx.Field{ + Type: fieldx.FieldTypeString, + Value: "user_profile", + }, + "user": fieldx.Field{ + Type: fieldx.FieldTypeObject, + Fields: fieldx.Schema{ + "id": fieldx.Field{ + Type: fieldx.FieldTypeField, + Value: "userId", + }, + "name": fieldx.Field{ + Type: fieldx.FieldTypeField, + Value: "userName", + }, + }, + }, +} + +source := map[string]any{ + "userId": 123, + "userName": "Alice", +} + +result, _ := schema.Generate(source) +// result: {"type": "user_profile", "user": {"id": 123, "name": "Alice"}} +``` + +### 嵌套路径访问 + +支持使用点号分隔的路径从嵌套的源对象中获取值: + +```go +schema := fieldx.Schema{ + "userId": fieldx.Field{ + Type: fieldx.FieldTypeField, + Value: "user.id", // 使用点号访问嵌套字段 + }, + "userEmail": fieldx.Field{ + Type: fieldx.FieldTypeField, + Value: "user.contact.email", // 支持多层嵌套 + }, +} + +source := map[string]any{ + "user": map[string]any{ + "id": 456, + "contact": map[string]any{ + "email": "alice@example.com", + }, + }, +} + +result, _ := schema.Generate(source) +// result: {"userId": 456, "userEmail": "alice@example.com"} +``` + +## 字段类型说明 + +### FieldTypeString +固定字符串值类型。 + +```go +fieldx.Field{ + Type: fieldx.FieldTypeString, + Value: "固定的字符串值", +} +``` + +### FieldTypeField +从源对象获取字段值,支持点号分隔的嵌套路径。 + +```go +fieldx.Field{ + Type: fieldx.FieldTypeField, + Value: "user.profile.name", // 支持嵌套路径 +} +``` + +### FieldTypeObject +嵌套对象类型,使用 `Fields` 字段定义子结构。 + +```go +fieldx.Field{ + Type: fieldx.FieldTypeObject, + Fields: fieldx.Schema{ + // 嵌套的字段定义 + }, +} +``` + +## JSON Schema 格式 + +Schema 可以从 JSON 反序列化,这是最方便的使用方式: + +### 基本格式 + +```json +{ + "字段名": { + "type": "string|field|object", + "value": "根据type不同而不同", + "fields": {/* 当type为object时使用 */} + } +} +``` + +### 类型说明 + +**1. string** - 固定字符串值 +```json +{"type": "string", "value": "固定值"} +``` + +**2. field** - 从源对象获取字段(支持点号分隔的嵌套路径) +```json +{"type": "field", "value": "user.name"} +``` + +**3. object** - 嵌套对象 +```json +{ + "type": "object", + "fields": { + "子字段名": {"type": "...", "value": "..."} + } +} +``` + +### 完整 JSON Schema 示例 + +```json +{ + "type": { + "type": "string", + "value": "user_profile" + }, + "userId": { + "type": "field", + "value": "user.id" + }, + "userName": { + "type": "field", + "value": "user.name" + }, + "contact": { + "type": "object", + "fields": { + "email": { + "type": "field", + "value": "user.email" + }, + "phone": { + "type": "string", + "value": "N/A" + } + } + } +} +``` + +使用此 Schema: + +```go +// 1. 从 JSON 创建 Schema +schema, _ := fieldx.SchemaFromJSON(jsonSchema) + +// 2. 准备源数据 +source := map[string]any{ + "user": map[string]any{ + "id": 123, + "name": "Alice", + "email": "alice@example.com", + }, +} + +// 3. 生成对象 +result, _ := schema.Generate(source) + +// 结果: +// { +// "type": "user_profile", +// "userId": 123, +// "userName": "Alice", +// "contact": { +// "email": "alice@example.com", +// "phone": "N/A" +// } +// } +``` + +## 错误处理 + +当引用的字段不存在时,`Generate()` 方法会返回错误: + +```go +result, err := schema.Generate(source) +if err != nil { + // 处理错误,例如: + // "failed to generate field xxx: field not found: yyy" +} +``` + +## 完整示例 + +查看 [example_test.go](example_test.go) 获取更多使用示例。 diff --git a/fieldx/example_test.go b/fieldx/example_test.go new file mode 100644 index 0000000..0f75241 --- /dev/null +++ b/fieldx/example_test.go @@ -0,0 +1,296 @@ +package fieldx_test + +import ( + "encoding/json" + "fmt" + + "git.fsdpf.net/go/reflux/fieldx" +) + +// ExampleSchemaFromJSON 演示从 JSON 字符串创建 Schema 并生成对象 +func ExampleSchemaFromJSON() { + // JSON Schema 定义 + jsonSchema := `{ + "name": { + "type": "field", + "value": "userName" + }, + "status": { + "type": "string", + "value": "active" + }, + "profile": { + "type": "object", + "fields": { + "email": { + "type": "field", + "value": "email" + }, + "role": { + "type": "string", + "value": "user" + } + } + } + }` + + // 从 JSON 创建 Schema + schema, _ := fieldx.SchemaFromJSON(jsonSchema) + + // 源数据 + source := map[string]any{ + "userName": "Alice", + "email": "alice@example.com", + } + + // 生成对象 + result, _ := schema.Generate(source) + + // 输出结果 + fmt.Printf("name: %s\n", result["name"]) + fmt.Printf("status: %s\n", result["status"]) + profile := result["profile"].(map[string]any) + fmt.Printf("profile.email: %s\n", profile["email"]) + fmt.Printf("profile.role: %s\n", profile["role"]) + + // Output: + // name: Alice + // status: active + // profile.email: alice@example.com + // profile.role: user +} + +// ExampleSchema_Generate_nestedPath 演示使用嵌套路径访问源数据 +func ExampleSchema_Generate_nestedPath() { + // 定义 Schema(使用点号分隔的路径) + schema := fieldx.Schema{ + "userId": fieldx.Field{ + Type: fieldx.FieldTypeField, + Value: "user.id", + }, + "userName": fieldx.Field{ + Type: fieldx.FieldTypeField, + Value: "user.name", + }, + "userEmail": fieldx.Field{ + Type: fieldx.FieldTypeField, + Value: "user.contact.email", + }, + } + + // 嵌套的源数据 + source := map[string]any{ + "user": map[string]any{ + "id": 123, + "name": "Alice", + "contact": map[string]any{ + "email": "alice@example.com", + }, + }, + } + + // 生成对象 + result, _ := schema.Generate(source) + + // 输出结果 + fmt.Printf("userId: %v\n", result["userId"]) + fmt.Printf("userName: %s\n", result["userName"]) + fmt.Printf("userEmail: %s\n", result["userEmail"]) + + // Output: + // userId: 123 + // userName: Alice + // userEmail: alice@example.com +} + +// ExampleSchema_Generate_complexNested 演示复杂的嵌套对象生成 +func ExampleSchema_Generate_complexNested() { + // 定义复杂的嵌套 Schema + schema := fieldx.Schema{ + "type": fieldx.Field{ + Type: fieldx.FieldTypeString, + Value: "user_profile", + }, + "user": fieldx.Field{ + Type: fieldx.FieldTypeObject, + Fields: fieldx.Schema{ + "id": fieldx.Field{ + Type: fieldx.FieldTypeField, + Value: "userId", + }, + "name": fieldx.Field{ + Type: fieldx.FieldTypeField, + Value: "userName", + }, + "contact": fieldx.Field{ + Type: fieldx.FieldTypeObject, + Fields: fieldx.Schema{ + "email": fieldx.Field{ + Type: fieldx.FieldTypeField, + Value: "email", + }, + "phone": fieldx.Field{ + Type: fieldx.FieldTypeString, + Value: "N/A", + }, + }, + }, + }, + }, + } + + // 源数据 + source := map[string]any{ + "userId": 456, + "userName": "Bob", + "email": "bob@example.com", + } + + // 生成对象 + result, _ := schema.Generate(source) + + // 输出结果 + fmt.Printf("type: %s\n", result["type"]) + user := result["user"].(map[string]any) + fmt.Printf("user.id: %v\n", user["id"]) + fmt.Printf("user.name: %s\n", user["name"]) + contact := user["contact"].(map[string]any) + fmt.Printf("user.contact.email: %s\n", contact["email"]) + fmt.Printf("user.contact.phone: %s\n", contact["phone"]) + + // Output: + // type: user_profile + // user.id: 456 + // user.name: Bob + // user.contact.email: bob@example.com + // user.contact.phone: N/A +} + +// ExampleSchema_Unmarshal 演示直接使用 json.Unmarshal 创建 Schema +func ExampleSchema_Unmarshal() { + // JSON Schema 字符串 + jsonStr := `{ + "name": { + "type": "field", + "value": "userName" + }, + "status": { + "type": "string", + "value": "active" + } + }` + + // 直接反序列化为 Schema + var schema fieldx.Schema + json.Unmarshal([]byte(jsonStr), &schema) + + // 源数据 + source := map[string]any{ + "userName": "Alice", + } + + // 生成对象 + result, _ := schema.Generate(source) + + // 输出结果 + fmt.Printf("name: %s\n", result["name"]) + fmt.Printf("status: %s\n", result["status"]) + + // Output: + // name: Alice + // status: active +} + +// ExampleSchemaFromMap 演示从 map[string]any 创建 Schema +func ExampleSchemaFromMap() { + // 先解析 JSON 到 map + jsonStr := `{ + "userId": { + "type": "field", + "value": "user.id" + }, + "userName": { + "type": "field", + "value": "user.name" + } + }` + + var data map[string]any + json.Unmarshal([]byte(jsonStr), &data) + + // 从 map 创建 Schema + schema, _ := fieldx.SchemaFromMap(data) + + // 源数据 + source := map[string]any{ + "user": map[string]any{ + "id": 789, + "name": "Charlie", + }, + } + + // 生成对象 + result, _ := schema.Generate(source) + + // 输出结果 + fmt.Printf("userId: %v\n", result["userId"]) + fmt.Printf("userName: %s\n", result["userName"]) + + // Output: + // userId: 789 + // userName: Charlie +} + +// ExampleSchema_allFieldTypes 演示所有字段类型的使用 +func ExampleSchema_allFieldTypes() { + // 定义包含所有字段类型的 Schema + schema := fieldx.Schema{ + // string 类型:固定字符串值 + "version": fieldx.Field{ + Type: fieldx.FieldTypeString, + Value: "1.0.0", + }, + // field 类型:从源数据获取值 + "title": fieldx.Field{ + Type: fieldx.FieldTypeField, + Value: "documentTitle", + }, + // object 类型:嵌套对象 + "metadata": fieldx.Field{ + Type: fieldx.FieldTypeObject, + Fields: fieldx.Schema{ + "created": fieldx.Field{ + Type: fieldx.FieldTypeField, + Value: "createdAt", + }, + "author": fieldx.Field{ + Type: fieldx.FieldTypeField, + Value: "author", + }, + }, + }, + } + + // 源数据 + source := map[string]any{ + "documentTitle": "API Documentation", + "createdAt": "2024-01-01", + "author": "Alice", + } + + // 生成对象 + result, _ := schema.Generate(source) + + // 输出结果 + fmt.Printf("version: %s\n", result["version"]) + fmt.Printf("title: %s\n", result["title"]) + metadata := result["metadata"].(map[string]any) + fmt.Printf("metadata.created: %s\n", metadata["created"]) + fmt.Printf("metadata.author: %s\n", metadata["author"]) + + // Output: + // version: 1.0.0 + // title: API Documentation + // metadata.created: 2024-01-01 + // metadata.author: Alice +} diff --git a/fieldx/schema.go b/fieldx/schema.go new file mode 100644 index 0000000..4351fee --- /dev/null +++ b/fieldx/schema.go @@ -0,0 +1,162 @@ +package fieldx + +import ( + "encoding/json" + "fmt" + "strings" + + "git.fsdpf.net/go/reflux" +) + +// FieldType 定义字段类型 +type FieldType string + +const ( + // FieldTypeString 固定字符串值 + FieldTypeString FieldType = "string" + // FieldTypeField 从传入对象中获取字段值 + FieldTypeField FieldType = "field" + // FieldTypeObject 嵌套对象类型 + FieldTypeObject FieldType = "object" +) + +// Field 定义单个字段的 Schema +type Field struct { + // Type 字段类型: string, field, object + Type FieldType `json:"type"` + // Value 字段值,根据 Type 不同含义不同: + // - string: 固定字符串值 + // - field: 要获取的字段路径 + // - object: 忽略,使用 Fields + Value string `json:"value,omitempty"` + // Fields 当 Type 为 object 时,包含嵌套的字段定义 + Fields Schema `json:"fields,omitempty"` +} + +// Schema 定义字段映射表 +type Schema map[string]Field + +// Generate 根据 Schema 生成对象 +// source: 源数据对象,用于 FieldTypeField 类型获取字段值 +// 返回生成的 map[string]any 对象 +func (s Schema) Generate(source map[string]any) (map[string]any, error) { + // 使用 reflux 包装源数据,提供统一的访问接口 + rfx := reflux.New(source) + + result := make(map[string]any) + err := s.generateFields(rfx, result) + if err != nil { + return nil, err + } + return result, nil +} + +// generateFields 递归生成字段 +func (s Schema) generateFields(source reflux.R, target map[string]any) error { + for fieldName, field := range s { + value, err := field.generateValue(source) + if err != nil { + return fmt.Errorf("failed to generate field %s: %w", fieldName, err) + } + target[fieldName] = value + } + return nil +} + +// generateValue 生成字段值 +func (f Field) generateValue(source reflux.R) (any, error) { + switch f.Type { + case FieldTypeString: + return f.Value, nil + case FieldTypeField: + return getFieldValue(f.Value, source) + case FieldTypeObject: + // 嵌套对象 + nestedResult := make(map[string]any) + err := f.Fields.generateFields(source, nestedResult) + if err != nil { + return nil, err + } + return nestedResult, nil + default: + return nil, fmt.Errorf("unsupported field type: %s", f.Type) + } +} + +// getFieldValue 从源对象获取字段值 +// 支持点号分隔的路径,如 "user.name" +func getFieldValue(path string, source reflux.R) (any, error) { + // 分割路径 + parts := strings.Split(path, ".") + + // 检查字段是否存在 + if !source.Exists(parts...) { + return nil, fmt.Errorf("field not found: %s", path) + } + + // 获取值 + accessor := source.Get(parts...) + return accessor.Any(), nil +} + +// SchemaFromJSON 从 JSON 字符串创建 Schema +// 这是最简单直接的方式 +func SchemaFromJSON(jsonStr string) (Schema, error) { + var schema Schema + err := json.Unmarshal([]byte(jsonStr), &schema) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal JSON: %w", err) + } + return schema, nil +} + +// SchemaFromMap 从 map[string]any 创建 Schema +// 支持从 JSON 反序列化后的 map 转换 +func SchemaFromMap(data map[string]any) (Schema, error) { + schema := make(Schema) + for key, value := range data { + field, err := fieldFromMap(value) + if err != nil { + return nil, err + } + schema[key] = field + } + return schema, nil +} + +// fieldFromMap 从 map[string]any 创建 Field +func fieldFromMap(data any) (Field, error) { + m, ok := data.(map[string]any) + if !ok { + return Field{}, fmt.Errorf("invalid field data: expected map[string]any, got %T", data) + } + + field := Field{} + + // 解析 type + if typeVal, ok := m["type"]; ok { + if typeStr, ok := typeVal.(string); ok { + field.Type = FieldType(typeStr) + } + } + + // 解析 value + if value, ok := m["value"]; ok { + if valueStr, ok := value.(string); ok { + field.Value = valueStr + } + } + + // 解析 fields (嵌套) + if fields, ok := m["fields"]; ok { + if fieldsMap, ok := fields.(map[string]any); ok { + nestedSchema, err := SchemaFromMap(fieldsMap) + if err != nil { + return Field{}, err + } + field.Fields = nestedSchema + } + } + + return field, nil +} diff --git a/fieldx/schema_test.go b/fieldx/schema_test.go new file mode 100644 index 0000000..151cfed --- /dev/null +++ b/fieldx/schema_test.go @@ -0,0 +1,499 @@ +package fieldx + +import ( + "encoding/json" + "testing" +) + +// TestSchemaFromJSON_Simple 测试从 JSON 创建简单 Schema +func TestSchemaFromJSON_Simple(t *testing.T) { + jsonStr := `{ + "name": { + "type": "field", + "value": "userName" + }, + "status": { + "type": "string", + "value": "active" + } + }` + + schema, err := SchemaFromJSON(jsonStr) + if err != nil { + t.Fatalf("SchemaFromJSON() error = %v", err) + } + + if schema["name"].Type != FieldTypeField { + t.Errorf("name.Type = %v, want %v", schema["name"].Type, FieldTypeField) + } + + if schema["status"].Type != FieldTypeString { + t.Errorf("status.Type = %v, want %v", schema["status"].Type, FieldTypeString) + } + + // 测试生成 + source := map[string]any{ + "userName": "Alice", + } + + result, err := schema.Generate(source) + if err != nil { + t.Fatalf("Generate() error = %v", err) + } + + if result["name"] != "Alice" { + t.Errorf("result[name] = %v, want Alice", result["name"]) + } + + if result["status"] != "active" { + t.Errorf("result[status] = %v, want active", result["status"]) + } +} + +// TestSchemaFromJSON_Nested 测试嵌套对象 +func TestSchemaFromJSON_Nested(t *testing.T) { + jsonStr := `{ + "type": { + "type": "string", + "value": "user_profile" + }, + "user": { + "type": "object", + "fields": { + "id": { + "type": "field", + "value": "userId" + }, + "name": { + "type": "field", + "value": "userName" + } + } + } + }` + + schema, err := SchemaFromJSON(jsonStr) + if err != nil { + t.Fatalf("SchemaFromJSON() error = %v", err) + } + + if schema["user"].Type != FieldTypeObject { + t.Errorf("user.Type = %v, want %v", schema["user"].Type, FieldTypeObject) + } + + // 测试生成 + source := map[string]any{ + "userId": 123, + "userName": "Alice", + } + + result, err := schema.Generate(source) + if err != nil { + t.Fatalf("Generate() error = %v", err) + } + + if result["type"] != "user_profile" { + t.Errorf("type = %v, want user_profile", result["type"]) + } + + user := result["user"].(map[string]any) + if user["id"] != 123 { + t.Errorf("user.id = %v, want 123", user["id"]) + } + + if user["name"] != "Alice" { + t.Errorf("user.name = %v, want Alice", user["name"]) + } +} + +// TestSchemaFromJSON_NestedPath 测试嵌套路径访问 +func TestSchemaFromJSON_NestedPath(t *testing.T) { + jsonStr := `{ + "userId": { + "type": "field", + "value": "user.id" + }, + "userEmail": { + "type": "field", + "value": "user.contact.email" + } + }` + + schema, err := SchemaFromJSON(jsonStr) + if err != nil { + t.Fatalf("SchemaFromJSON() error = %v", err) + } + + // 测试嵌套源数据 + source := map[string]any{ + "user": map[string]any{ + "id": 456, + "contact": map[string]any{ + "email": "alice@example.com", + }, + }, + } + + result, err := schema.Generate(source) + if err != nil { + t.Fatalf("Generate() error = %v", err) + } + + if result["userId"] != 456 { + t.Errorf("userId = %v, want 456", result["userId"]) + } + + if result["userEmail"] != "alice@example.com" { + t.Errorf("userEmail = %v, want alice@example.com", result["userEmail"]) + } +} + +// TestSchemaFromJSON_ComplexNested 测试复杂嵌套结构 +func TestSchemaFromJSON_ComplexNested(t *testing.T) { + jsonStr := `{ + "type": { + "type": "string", + "value": "user_profile" + }, + "user": { + "type": "object", + "fields": { + "id": { + "type": "field", + "value": "userId" + }, + "contact": { + "type": "object", + "fields": { + "email": { + "type": "field", + "value": "email" + }, + "phone": { + "type": "string", + "value": "N/A" + } + } + } + } + } + }` + + schema, err := SchemaFromJSON(jsonStr) + if err != nil { + t.Fatalf("SchemaFromJSON() error = %v", err) + } + + source := map[string]any{ + "userId": 123, + "email": "test@example.com", + } + + result, err := schema.Generate(source) + if err != nil { + t.Fatalf("Generate() error = %v", err) + } + + if result["type"] != "user_profile" { + t.Errorf("type = %v, want user_profile", result["type"]) + } + + user := result["user"].(map[string]any) + if user["id"] != 123 { + t.Errorf("user.id = %v, want 123", user["id"]) + } + + contact := user["contact"].(map[string]any) + if contact["email"] != "test@example.com" { + t.Errorf("contact.email = %v, want test@example.com", contact["email"]) + } + + if contact["phone"] != "N/A" { + t.Errorf("contact.phone = %v, want N/A", contact["phone"]) + } +} + +// TestSchemaFromJSON_InvalidJSON 测试无效 JSON +func TestSchemaFromJSON_InvalidJSON(t *testing.T) { + jsonStr := `{invalid json}` + + _, err := SchemaFromJSON(jsonStr) + if err == nil { + t.Error("SchemaFromJSON() expected error for invalid JSON, got nil") + } +} + +// TestSchemaFromJSON_EmptyJSON 测试空 JSON +func TestSchemaFromJSON_EmptyJSON(t *testing.T) { + jsonStr := `{}` + + schema, err := SchemaFromJSON(jsonStr) + if err != nil { + t.Fatalf("SchemaFromJSON() error = %v", err) + } + + if len(schema) != 0 { + t.Errorf("schema length = %v, want 0", len(schema)) + } +} + +// TestSchemaFromMap_StringType 测试从 Map 创建字符串类型 +func TestSchemaFromMap_StringType(t *testing.T) { + data := map[string]any{ + "status": map[string]any{ + "type": "string", + "value": "active", + }, + } + + schema, err := SchemaFromMap(data) + if err != nil { + t.Fatalf("SchemaFromMap() error = %v", err) + } + + if schema["status"].Type != FieldTypeString { + t.Errorf("status.Type = %v, want %v", schema["status"].Type, FieldTypeString) + } + + if schema["status"].Value != "active" { + t.Errorf("status.Value = %v, want active", schema["status"].Value) + } +} + +// TestSchemaFromMap_FieldType 测试从 Map 创建字段类型 +func TestSchemaFromMap_FieldType(t *testing.T) { + data := map[string]any{ + "name": map[string]any{ + "type": "field", + "value": "userName", + }, + } + + schema, err := SchemaFromMap(data) + if err != nil { + t.Fatalf("SchemaFromMap() error = %v", err) + } + + if schema["name"].Type != FieldTypeField { + t.Errorf("name.Type = %v, want %v", schema["name"].Type, FieldTypeField) + } + + if schema["name"].Value != "userName" { + t.Errorf("name.Value = %v, want userName", schema["name"].Value) + } +} + +// TestSchemaFromMap_ObjectType 测试从 Map 创建对象类型 +func TestSchemaFromMap_ObjectType(t *testing.T) { + data := map[string]any{ + "user": map[string]any{ + "type": "object", + "fields": map[string]any{ + "id": map[string]any{ + "type": "field", + "value": "userId", + }, + "name": map[string]any{ + "type": "string", + "value": "default_name", + }, + }, + }, + } + + schema, err := SchemaFromMap(data) + if err != nil { + t.Fatalf("SchemaFromMap() error = %v", err) + } + + if schema["user"].Type != FieldTypeObject { + t.Errorf("user.Type = %v, want %v", schema["user"].Type, FieldTypeObject) + } + + if schema["user"].Fields["id"].Type != FieldTypeField { + t.Errorf("user.fields.id.Type = %v, want %v", schema["user"].Fields["id"].Type, FieldTypeField) + } + + if schema["user"].Fields["name"].Value != "default_name" { + t.Errorf("user.fields.name.Value = %v, want default_name", schema["user"].Fields["name"].Value) + } +} + +// TestSchemaFromMap_InvalidData 测试无效数据 +func TestSchemaFromMap_InvalidData(t *testing.T) { + data := map[string]any{ + "field1": "invalid", + } + + _, err := SchemaFromMap(data) + if err == nil { + t.Error("SchemaFromMap() expected error for invalid data, got nil") + } +} + +// TestSchema_Unmarshal 测试直接使用 json.Unmarshal +func TestSchema_Unmarshal(t *testing.T) { + jsonStr := `{ + "name": { + "type": "field", + "value": "userName" + }, + "status": { + "type": "string", + "value": "active" + } + }` + + var schema Schema + err := json.Unmarshal([]byte(jsonStr), &schema) + if err != nil { + t.Fatalf("json.Unmarshal() error = %v", err) + } + + if schema["name"].Type != FieldTypeField { + t.Errorf("name.Type = %v, want %v", schema["name"].Type, FieldTypeField) + } + + // 测试生成 + source := map[string]any{ + "userName": "Alice", + } + + result, err := schema.Generate(source) + if err != nil { + t.Fatalf("Generate() error = %v", err) + } + + if result["name"] != "Alice" { + t.Errorf("result[name] = %v, want Alice", result["name"]) + } +} + +// TestSchema_UnmarshalInStruct 测试在结构体中使用 Schema +func TestSchema_UnmarshalInStruct(t *testing.T) { + type Config struct { + Name string `json:"name"` + Schema Schema `json:"schema"` + } + + jsonStr := `{ + "name": "test_config", + "schema": { + "userId": { + "type": "field", + "value": "user.id" + }, + "userName": { + "type": "field", + "value": "user.name" + } + } + }` + + var config Config + err := json.Unmarshal([]byte(jsonStr), &config) + if err != nil { + t.Fatalf("json.Unmarshal() error = %v", err) + } + + if config.Name != "test_config" { + t.Errorf("config.Name = %v, want test_config", config.Name) + } + + if config.Schema["userId"].Type != FieldTypeField { + t.Errorf("userId.Type = %v, want %v", config.Schema["userId"].Type, FieldTypeField) + } + + // 测试生成 + source := map[string]any{ + "user": map[string]any{ + "id": 123, + "name": "Alice", + }, + } + + result, err := config.Schema.Generate(source) + if err != nil { + t.Fatalf("Generate() error = %v", err) + } + + if result["userId"] != 123 { + t.Errorf("userId = %v, want 123", result["userId"]) + } + + if result["userName"] != "Alice" { + t.Errorf("userName = %v, want Alice", result["userName"]) + } +} + +// TestSchema_Generate_FieldNotFound 测试字段不存在的情况 +func TestSchema_Generate_FieldNotFound(t *testing.T) { + schema := Schema{ + "name": Field{ + Type: FieldTypeField, + Value: "nonExistentField", + }, + } + + source := map[string]any{ + "otherField": "value", + } + + _, err := schema.Generate(source) + if err == nil { + t.Error("Generate() expected error for non-existent field, got nil") + } +} + +// TestSchema_Generate_AllTypes 测试所有字段类型组合 +func TestSchema_Generate_AllTypes(t *testing.T) { + schema := Schema{ + "fixedValue": Field{ + Type: FieldTypeString, + Value: "constant", + }, + "dynamicValue": Field{ + Type: FieldTypeField, + Value: "inputValue", + }, + "nestedObject": Field{ + Type: FieldTypeObject, + Fields: Schema{ + "innerFixed": Field{ + Type: FieldTypeString, + Value: "inner_constant", + }, + "innerDynamic": Field{ + Type: FieldTypeField, + Value: "inputValue", + }, + }, + }, + } + + source := map[string]any{ + "inputValue": "test_value", + } + + result, err := schema.Generate(source) + if err != nil { + t.Fatalf("Generate() error = %v", err) + } + + if result["fixedValue"] != "constant" { + t.Errorf("fixedValue = %v, want constant", result["fixedValue"]) + } + + if result["dynamicValue"] != "test_value" { + t.Errorf("dynamicValue = %v, want test_value", result["dynamicValue"]) + } + + nested := result["nestedObject"].(map[string]any) + if nested["innerFixed"] != "inner_constant" { + t.Errorf("innerFixed = %v, want inner_constant", nested["innerFixed"]) + } + + if nested["innerDynamic"] != "test_value" { + t.Errorf("innerDynamic = %v, want test_value", nested["innerDynamic"]) + } +}