diff --git a/go.mod b/go.mod index af6d842..426549c 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/go-playground/validator/v10 v10.11.1 + github.com/pkg/errors v0.9.1 github.com/senseyeio/duration v0.0.0-20180430131211-7c2a214ada46 github.com/stretchr/testify v1.7.0 k8s.io/apimachinery v0.25.1 diff --git a/go.sum b/go.sum index a82bf18..9657239 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= diff --git a/hack/conv/main.go b/hack/conv/main.go new file mode 100644 index 0000000..e70e738 --- /dev/null +++ b/hack/conv/main.go @@ -0,0 +1,128 @@ +// Copyright 2022 The Serverless Workflow Specification Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "errors" + "log" + "os" + "path" + "path/filepath" + "strings" + + "gopkg.in/yaml.v3" + + "github.com/serverlessworkflow/sdk-go/v2/test" +) + +func convert(i interface{}) interface{} { + switch x := i.(type) { + case map[interface{}]interface{}: + m2 := map[string]interface{}{} + for k, v := range x { + m2[k.(string)] = convert(v) + } + return m2 + case []interface{}: + for i, v := range x { + x[i] = convert(v) + } + } + return i +} + +func transform( + files []string, + srcFormat string, + destFormat string, + unmarshal func(data []byte, out interface{}) error, + marshal func(in interface{}) ([]byte, error), +) { + for _, srcFile := range files { + if !strings.HasSuffix(srcFile, srcFormat) { + log.Printf("%s is not %s format, skip it", srcFile, srcFormat) + continue + } + + destFile := srcFile[0:len(srcFile)-len(srcFormat)] + destFormat + if _, err := os.Stat(destFile); err == nil { + log.Printf("ERR: the target file %v exists, skip it", destFile) + continue + } else if !errors.Is(err, os.ErrNotExist) { + log.Printf("ERR: stat target file %v, %v, skip it", destFile, err) + continue + } + + srcData, err := os.ReadFile(filepath.Clean(srcFile)) + if err != nil { + log.Printf("ERR: cannot read file %v, %v, skip it", srcFile, err) + continue + } + + var srcObj interface{} + err = unmarshal(srcData, &srcObj) + if err != nil { + log.Printf("ERR: cannot unmarshal file %v to %s, %v, skip it", srcFile, srcFormat, err) + continue + } + + destObj := convert(srcObj) + destData, err := marshal(destObj) + if err != nil { + log.Printf("ERR: cannot marshal fild %v data to %v, %v, skip it", srcFile, destFormat, err) + continue + } + + err = os.WriteFile(destFile, destData, 0600) + if err != nil { + log.Printf("ERR: cannot write to file %v, %v, skip it", destFile, err) + continue + } + + log.Printf("convert %v to %v done", srcFile, destFile) + } +} + +func main() { + // TODO: make this as argument + dir := path.Join(test.CurrentProjectPath(), "parser", "testdata", "workflows", "urifiles") + dirEntries, err := os.ReadDir(dir) + if err != nil { + panic(err) + } + + files := make([]string, 0, len(dirEntries)) + for _, entry := range dirEntries { + if entry.IsDir() { + log.Printf("%s is directory, skip it", entry.Name()) + continue + } + + files = append(files, path.Join(dir, entry.Name())) + } + + log.Printf("found %v files", len(files)) + + // First, convert all json format files to yaml + log.Printf("start to convert all json format files to yaml format") + transform(files, ".json", ".yaml", json.Unmarshal, yaml.Marshal) + + // Second, convert all yaml format files to json + log.Printf("start to convert all yaml format files to json format") + transform(files, ".yaml", ".json", yaml.Unmarshal, func(in interface{}) ([]byte, error) { + return json.MarshalIndent(in, "", " ") + }) +} diff --git a/model/util.go b/model/util.go index 1cfd08b..31f711b 100644 --- a/model/util.go +++ b/model/util.go @@ -21,6 +21,8 @@ import ( "os" "path/filepath" "strings" + + "sigs.k8s.io/yaml" ) const prefix = "file:/" @@ -49,6 +51,17 @@ func getBytesFromFile(s string) (b []byte, err error) { if b, err = os.ReadFile(filepath.Clean(s)); err != nil { return nil, err } + + // TODO: optimize this + // NOTE: In specification, we can declared independently definitions with another file format, so + // we must convert independently yaml source to json format data before unmarshal. + if strings.HasSuffix(s, ".yaml") || strings.HasSuffix(s, ".yml") { + b, err = yaml.YAMLToJSON(b) + if err != nil { + return nil, err + } + } + return b, nil } diff --git a/parser/testdata/workflows/applicationrequest-issue103.json b/parser/testdata/workflows/applicationrequest-issue103.json new file mode 100644 index 0000000..9b8c0a2 --- /dev/null +++ b/parser/testdata/workflows/applicationrequest-issue103.json @@ -0,0 +1,79 @@ +{ + "id": "applicantrequest", + "version": "1.0", + "name": "Applicant Request Decision Workflow", + "description": "Determine if applicant request is valid", + "start": "CheckApplication", + "specVersion": "0.7", + "auth": "./testdata/workflows/urifiles/auth.yaml", + "functions": [ + { + "name": "sendRejectionEmailFunction", + "operation": "http://myapis.org/applicationapi.json#emailRejection" + } + ], + "retries": [ + { + "name": "TimeoutRetryStrategy", + "delay": "PT1M", + "maxAttempts": "5" + } + ], + "states": [ + { + "name": "CheckApplication", + "type": "switch", + "dataConditions": [ + { + "condition": "${ .applicants | .age >= 18 }", + "transition": { + "nextState": "StartApplication" + } + }, + { + "condition": "${ .applicants | .age < 18 }", + "transition": { + "nextState": "RejectApplication" + } + } + ], + "default": { + "transition": { + "nextState": "RejectApplication" + } + } + }, + { + "name": "StartApplication", + "type": "operation", + "actions": [ + { + "subFlowRef": { + "workflowId": "startApplicationWorkflowId" + } + } + ], + "end": { + "terminate": true + } + }, + { + "name": "RejectApplication", + "type": "operation", + "actionMode": "sequential", + "actions": [ + { + "functionRef": { + "refName": "sendRejectionEmailFunction", + "parameters": { + "applicant": "${ .applicant }" + } + } + } + ], + "end": { + "terminate": true + } + } + ] +} \ No newline at end of file diff --git a/parser/testdata/workflows/urifiles/auth.yaml b/parser/testdata/workflows/urifiles/auth.yaml new file mode 100644 index 0000000..14ba4e2 --- /dev/null +++ b/parser/testdata/workflows/urifiles/auth.yaml @@ -0,0 +1,23 @@ +# Copyright 2022 The Serverless Workflow Specification Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- name: testAuth + properties: + token: test_token + scheme: bearer +- name: testAuth2 + properties: + password: test_pwd + username: test_user + scheme: basic diff --git a/test/path.go b/test/path.go new file mode 100644 index 0000000..e9ff5e4 --- /dev/null +++ b/test/path.go @@ -0,0 +1,53 @@ +// Copyright 2022 The Serverless Workflow Specification Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "os" + "path/filepath" + "runtime" + + "github.com/pkg/errors" +) + +// CurrentProjectPath get the project root path +func CurrentProjectPath() string { + path := currentFilePath() + + ppath, err := filepath.Abs(filepath.Join(filepath.Dir(path), "../")) + if err != nil { + panic(errors.Wrapf(err, "Get current project path with %s failed", path)) + } + + f, err := os.Stat(ppath) + if err != nil { + panic(errors.Wrapf(err, "Stat project path %v failed", ppath)) + } + + if f.Mode()&os.ModeSymlink != 0 { + fpath, err := os.Readlink(ppath) + if err != nil { + panic(errors.Wrapf(err, "Readlink from path %v failed", fpath)) + } + ppath = fpath + } + + return ppath +} + +func currentFilePath() string { + _, file, _, _ := runtime.Caller(1) + return file +} diff --git a/test/path_test.go b/test/path_test.go new file mode 100644 index 0000000..4ccb672 --- /dev/null +++ b/test/path_test.go @@ -0,0 +1,31 @@ +// Copyright 2022 The Serverless Workflow Specification Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCurrentProjectPath(t *testing.T) { + t.Run("normal test", func(t *testing.T) { + path := CurrentProjectPath() + + // NOTE: the '/code' path is used with code pipeline. + // When code running in the pipeline, the codebase will copy to /home/code directory. + assert.Regexp(t, "(/sdk-go$)|(/code$)", path) + }) +}