Merge pull request #218 from trheyi/main

[change] componet setting
This commit is contained in:
Max 2022-10-21 12:01:14 +08:00 committed by GitHub
commit 6d92d1346a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 452 additions and 24 deletions

View file

@ -191,7 +191,7 @@ func exportAPI() error {
Path: "/service/:name",
Guard: "bearer-jwt",
Method: "POST",
Process: "yao.app.Serivce",
Process: "yao.app.Service",
In: []string{"$param.name", ":payload"},
Out: gou.Out{Status: 200, Type: "application/json"},
}

View file

@ -1,5 +1,9 @@
package component
import (
jsoniter "github.com/json-iterator/go"
)
// process
// yao.component.TagView
// yao.component.TagEdit
@ -12,19 +16,27 @@ func Export() error {
return nil
}
// MarshalJSON Custom JSON parse
func (dsl DSL) MarshalJSON() ([]byte, error) {
return jsoniter.Marshal(dsl.Map())
}
// Map cast to map[string]interface{}
func (dsl DSL) Map() map[string]interface{} {
return map[string]interface{}{
res := map[string]interface{}{
"type": dsl.Type,
"in": dsl.In,
"out": dsl.Out,
"props": map[string]interface{}(dsl.Props),
}
if dsl.Bind != "" {
res["bind"] = dsl.Bind
}
return res
}
// Clone Component
func (dsl *DSL) Clone() *DSL {
new := DSL{
Bind: dsl.Bind,
Type: dsl.Type,
In: dsl.In,
Out: dsl.Out,

View file

@ -0,0 +1,152 @@
package component
import (
"fmt"
"reflect"
"strings"
jsoniter "github.com/json-iterator/go"
"github.com/yaoapp/gou"
"github.com/yaoapp/kun/maps"
)
// "$C(value)", "$C(props)", "$C(type)"}
var defaults = []CArg{
{IsExp: true, key: "value", value: nil},
{IsExp: true, key: "props", value: nil},
{IsExp: true, key: "type", value: nil},
}
// Value compute value
func (compute *Compute) Value(data maps.MapStr, sid string, global map[string]interface{}) (interface{}, error) {
if compute.Process == "" {
return nil, fmt.Errorf("compute process is required")
}
// Build-In handlers
args := compute.GetArgs(data)
if handler, has := hanlders[compute.Process]; has {
return handler(args...)
}
if !strings.Contains(compute.Process, ".") {
return nil, fmt.Errorf("compute %s does not found", compute.Process)
}
// Run process
process, err := gou.ProcessOf(compute.Process, args...)
if err != nil {
return nil, err
}
res, err := process.WithSID(sid).WithGlobal(global).Exec()
if err != nil {
return nil, err
}
return res, nil
}
// GetArgs return args
func (compute *Compute) GetArgs(data maps.MapStr) []interface{} {
args := []interface{}{}
for _, arg := range compute.Args {
args = append(args, arg.Value(data))
}
return args
}
// Value compute arg value
func (arg CArg) Value(data maps.MapStr) interface{} {
if !arg.IsExp {
return arg.value
}
return data.Get(arg.key)
}
// MarshalJSON Custom JSON parse
func (compute Compute) MarshalJSON() ([]byte, error) {
if compute.Args == nil || len(compute.Args) == 0 || reflect.DeepEqual(compute.Args, defaults) {
return jsoniter.Marshal(compute.Process)
}
return jsoniter.Marshal(computeAlias(compute))
}
// UnmarshalJSON Custom JSON parse
func (compute *Compute) UnmarshalJSON(data []byte) error {
// allow null
if data == nil || len(data) < 1 || (len(data) == 2 && data[0] == '"' && data[1] == '"') {
*compute = Compute{Args: []CArg{}}
return fmt.Errorf("Compute should be {} or string")
}
switch data[0] {
case '[':
*compute = Compute{Args: []CArg{}}
return fmt.Errorf("Compute should be {} or string")
case '{': // json
var new computeAlias
err := jsoniter.Unmarshal(data, &new)
if err != nil {
return err
}
new.Process = strings.TrimSpace(new.Process)
*compute = Compute(new)
return nil
default:
compute.Process = strings.TrimSpace((strings.Trim(string(data), `"`)))
compute.Args = defaults
return nil
}
}
// MarshalJSON for JSON parse
func (arg CArg) MarshalJSON() ([]byte, error) {
if arg.IsExp {
return []byte(fmt.Sprintf(`"$C(%s)"`, arg.key)), nil
}
if v, ok := arg.value.(string); ok && strings.HasPrefix(v, "::") {
return jsoniter.Marshal(fmt.Sprintf("\\%s", v))
}
return jsoniter.Marshal(arg.value)
}
// UnmarshalJSON for JSON parse
func (arg *CArg) UnmarshalJSON(data []byte) error {
if data == nil || len(data) < 1 {
*arg = CArg{value: nil, IsExp: false}
return nil
}
// "$C(value)", "$C(props)", "$C(type)"}
if len(data) > 3 && data[0] == '"' && data[1] == '$' && data[2] == 'C' && data[3] == '(' {
key := strings.TrimSpace(strings.TrimRight(strings.TrimLeft(string(data), `"$C(`), `)"`))
*arg = CArg{key: key, IsExp: true}
return nil
} else if len(data) > 4 && data[0] == '"' && data[1] == '\\' && data[2] == '\\' && data[3] == ':' && data[4] == ':' {
// ["$C(row.type)", "\\::", "$C(value)", "-", "$C(row.status)"]
value := string(data[3 : len(data)-1])
*arg = CArg{value: value, IsExp: false}
return nil
}
var v interface{}
err := jsoniter.Unmarshal(data, &v)
if err != nil {
return err
}
*arg = CArg{value: v, IsExp: false}
return nil
}

View file

@ -0,0 +1,190 @@
package component
import (
"testing"
jsoniter "github.com/json-iterator/go"
"github.com/stretchr/testify/assert"
"github.com/yaoapp/gou/session"
"github.com/yaoapp/kun/maps"
)
func TestComputeUnmarshalJSON(t *testing.T) {
tests := testComputeData()
var compute Compute
err := jsoniter.Unmarshal(tests["Trim"], &compute)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "Trim", compute.Process)
assert.Equal(t, true, compute.Args[0].IsExp)
assert.Equal(t, "value", compute.Args[0].key)
assert.Equal(t, nil, compute.Args[0].value)
assert.Equal(t, true, compute.Args[1].IsExp)
assert.Equal(t, "props", compute.Args[1].key)
assert.Equal(t, nil, compute.Args[1].value)
assert.Equal(t, true, compute.Args[2].IsExp)
assert.Equal(t, "type", compute.Args[2].key)
assert.Equal(t, nil, compute.Args[2].value)
err = jsoniter.Unmarshal(tests["Concat"], &compute)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "Concat", compute.Process)
assert.Equal(t, false, compute.Args[1].IsExp)
assert.Equal(t, "::", compute.Args[1].value)
assert.Equal(t, "", compute.Args[1].key)
err = jsoniter.Unmarshal(tests["Mapping"], &compute)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "Mapping", compute.Process)
assert.Equal(t, false, compute.Args[1].IsExp)
assert.Equal(t, "checked", compute.Args[1].value.(map[string]interface{})["0"])
assert.Equal(t, "", compute.Args[1].key)
err = jsoniter.Unmarshal(tests["MappingOnline"], &compute)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "scripts.compute.MappingOnline", compute.Process)
err = jsoniter.Unmarshal(tests["Empty"], &compute)
assert.NotNil(t, err)
assert.Equal(t, "", compute.Process)
err = jsoniter.Unmarshal(tests["Error"], &compute)
assert.NotNil(t, err)
assert.Equal(t, "", compute.Process)
}
func TestComputeMarshalJSON(t *testing.T) {
tests := testComputeData()
var compute Compute
err := jsoniter.Unmarshal(tests["Trim"], &compute)
if err != nil {
t.Fatal(err)
}
bytes, err := jsoniter.Marshal(compute)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, tests["Trim"], bytes)
err = jsoniter.Unmarshal(tests["Concat"], &compute)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "Concat", compute.Process)
bytes, err = jsoniter.Marshal(compute)
if err != nil {
t.Fatal(err)
}
assert.Contains(t, string(bytes), `Concat`)
assert.Contains(t, string(bytes), `$C(value)`)
assert.Contains(t, string(bytes), `\\::`)
err = jsoniter.Unmarshal(tests["Mapping"], &compute)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "Mapping", compute.Process)
bytes, err = jsoniter.Marshal(compute)
if err != nil {
t.Fatal(err)
}
assert.Contains(t, string(bytes), `Mapping`)
assert.Contains(t, string(bytes), `curing`)
err = jsoniter.Unmarshal(tests["Empty"], &compute)
assert.NotNil(t, err)
assert.Equal(t, "", compute.Process)
bytes, err = jsoniter.Marshal(compute)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, `""`, string(bytes))
err = jsoniter.Unmarshal(tests["Error"], &compute)
assert.NotNil(t, err)
assert.Equal(t, "", compute.Process)
bytes, err = jsoniter.Marshal(compute)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, `""`, string(bytes))
}
func TestComputeValue(t *testing.T) {
tests := testComputeData()
data := maps.MapStr{
"value": " Concat-Test ",
"row.type": "UnitTest",
"row.status": "enabled",
}
var compute Compute
err := jsoniter.Unmarshal(tests["Concat"], &compute)
if err != nil {
t.Fatal(err)
}
id := session.ID()
res, err := compute.Value(data, id, nil)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "UnitTest:: Concat-Test -enabled", res)
err = jsoniter.Unmarshal(tests["Trim"], &compute)
if err != nil {
t.Fatal(err)
}
res, err = compute.Value(data, id, nil)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "Concat-Test", res)
compute = Compute{Process: "NotFound", Args: []CArg{}}
res, err = compute.Value(data, id, nil)
assert.Contains(t, err.Error(), "does not found")
assert.Nil(t, res)
}
func testComputeData() map[string][]byte {
return map[string][]byte{
"Trim": []byte(`"Trim"`),
"Concat": []byte(`{
"process": "Concat",
"args": ["$C(row.type)", "\\::", "$C(value)", "-", "$C(row.status)"]
}`),
"Mapping": []byte(` {
"process": "Mapping",
"args": [
"$C(value)",
{ "0": "checked", "1": "curing", "2": "cured" }
]
}`),
"MappingOnline": []byte(`{
"process": "scripts.compute.MappingOnline",
"args": ["$C(value)", "$C(props.mapping)"]
}`),
"Empty": []byte(`""`),
"Error": []byte("[]"),
}
}

View file

@ -0,0 +1,48 @@
package component
import (
"fmt"
"strings"
)
var hanlders = map[string]ComputeHanlder{
"Trim": Trim,
"Concat": Concat,
"QueryString": Trim,
"ImagesView": Trim,
"ImagesEdit": Trim,
"Duration": Trim,
"HumanDataTime": Trim,
"Mapping": Trim,
"Currency": Trim,
}
// Trim string
func Trim(args ...interface{}) (interface{}, error) {
if len(args) < 1 {
return nil, fmt.Errorf("Trim args[0] is required")
}
if args[0] == nil {
return "", nil
}
v, ok := args[0].(string)
if !ok {
return nil, fmt.Errorf("Trim args[0] is not a string value")
}
return strings.TrimSpace(v), nil
}
// Concat string
func Concat(args ...interface{}) (interface{}, error) {
res := ""
for _, arg := range args {
if arg == nil {
continue
}
res = fmt.Sprintf("%v%v", res, arg)
}
return res, nil
}

View file

@ -2,10 +2,12 @@ package component
// DSL the component DSL
type DSL struct {
Type string `json:"type,omitempty"`
In string `json:"in,omitempty"`
Out string `json:"out,omitempty"`
Props PropsDSL `json:"props,omitempty"`
Bind string `json:"bind,omitempty"`
Type string `json:"type,omitempty"`
In string `json:"in,omitempty"`
Out string `json:"out,omitempty"`
Compute *Compute `json:"compute,omitempty"`
Props PropsDSL `json:"props,omitempty"`
}
// Actions the actions
@ -44,6 +46,25 @@ type PropsDSL map[string]interface{}
// ParamsDSL action params
type ParamsDSL map[string]interface{}
// Compute process
type Compute struct {
Process string `json:"process"`
Args []CArg `json:"args,omitempty"`
}
// computeAlias for JSON UnmarshalJSON
type computeAlias Compute
// CArg compute interface{}
type CArg struct {
IsExp bool
key string
value interface{}
}
// ComputeHanlder computeHanlder
type ComputeHanlder func(args ...interface{}) (interface{}, error)
// CloudPropsDSL the cloud props
type CloudPropsDSL struct {
Xpath string `json:"xpath,omitempty"`

View file

@ -61,11 +61,13 @@ func (column *ColumnDSL) Clone() *ColumnDSL {
// Map cast to map[string]inteface{}
func (column ColumnDSL) Map() map[string]interface{} {
res := map[string]interface{}{
"key": column.Key,
"link": column.Link,
"bind": column.Bind,
}
if column.Link != "" {
res["link"] = column.Link
}
if column.View != nil {
res["view"] = column.View.Map()
}
@ -109,20 +111,20 @@ func (columns Columns) ComputeFieldsMerge(computeInFields map[string]string, com
// Compute In
if column.In != "" {
if !strings.Contains(column.In, ".") {
column.In = fmt.Sprintf("yao.component.%s", column.In)
if !strings.Contains(string(column.In), ".") {
column.In = Compute(fmt.Sprintf("yao.component.%s", column.In))
}
computeInFields[column.Bind] = column.In
computeInFields[name] = column.In
computeInFields[column.Bind] = string(column.In)
computeInFields[name] = string(column.In)
}
// Compute Out
if column.Out != "" {
if !strings.Contains(column.Out, ".") {
column.In = fmt.Sprintf("yao.component.%s", column.Out)
if !strings.Contains(string(column.Out), ".") {
column.In = Compute(fmt.Sprintf("yao.component.%s", column.Out))
}
computeOutFields[column.Bind] = column.Out
computeOutFields[name] = column.Out
computeOutFields[column.Bind] = string(column.Out)
computeOutFields[name] = string(column.Out)
}
}
}

View file

@ -43,7 +43,6 @@ func (filter *FilterDSL) Clone() *FilterDSL {
// Map cast to map[string]inteface{}
func (filter FilterDSL) Map() map[string]interface{} {
res := map[string]interface{}{
"key": filter.Key,
"bind": filter.Bind,
}

View file

@ -21,8 +21,8 @@ type ColumnDSL struct {
Key string `json:"key,omitempty"`
Bind string `json:"bind,omitempty"`
Link string `json:"link,omitempty"`
In string `json:"in,omitempty"`
Out string `json:"out,omitempty"`
In Compute `json:"in,omitempty"`
Out Compute `json:"out,omitempty"`
View *component.DSL `json:"view,omitempty"`
Edit *component.DSL `json:"edit,omitempty"`
}
@ -34,6 +34,9 @@ type FilterDSL struct {
Edit *component.DSL `json:"edit,omitempty"`
}
// Compute the compute filed
type Compute string
// Transform the field transform
type Transform struct {
Variables map[string]interface{} `json:"variables,omitempty"`

View file

@ -83,6 +83,7 @@ func (fields *FieldsDSL) Xgen(layout *LayoutDSL) (map[string]interface{}, error)
filters := map[string]interface{}{}
tables := map[string]interface{}{}
if layout.Filter != nil {
for i, f := range layout.Filter.Columns {
name := f.Name

View file

@ -18,6 +18,6 @@ func TestFiledsBindModel(t *testing.T) {
t.Fatal(err)
}
assert.Equal(t, "id", tab.Fields.Table["ID"].Bind)
assert.Equal(t, 15, len(tab.Fields.Table))
assert.Equal(t, 4, len(tab.Fields.Filter))
assert.Equal(t, 17, len(tab.Fields.Table))
assert.Equal(t, 6, len(tab.Fields.Filter))
}

View file

@ -22,7 +22,7 @@ func TestLoad(t *testing.T) {
if err != nil {
t.Fatal(err)
}
assert.Equal(t, 9, len(Tables))
assert.Equal(t, 10, len(Tables))
}
func TestLoadID(t *testing.T) {