Repository: nuveo/prest
Pull request #152 (2017-05-24 13:45:56)

implement funcs to help scripts
related issue #114 

codecov[bot]: # [Codecov](https://codecov.io/gh/nuveo/prest/pull/152?src=pr&el=h1) Report
> Merging [#152](https://codecov.io/gh/nuveo/prest/pull/152?src=pr&el=desc) into [master](https://codecov.io/gh/nuveo/prest/commit/29eda67483c099e80bc36bfe994bd621d08c4c47?src=pr&el=desc) will **decrease** coverage by `0.65%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/nuveo/prest/pull/152/graphs/tree.svg?height=150&token=eVD9urwIEv&width=650&src=pr)](https://codecov.io/gh/nuveo/prest/pull/152?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##           master    #152      +/-   ##
=========================================
- Coverage   85.76%   85.1%   -0.66%     
=========================================
  Files          10      10              
  Lines         864     866       +2     
=========================================
- Hits          741     737       -4     
- Misses         81      85       +4     
- Partials       42      44       +2
```


| [Impacted Files](https://codecov.io/gh/nuveo/prest/pull/152?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [adapters/postgres/queries.go](https://codecov.io/gh/nuveo/prest/pull/152?src=pr&el=tree#diff-YWRhcHRlcnMvcG9zdGdyZXMvcXVlcmllcy5nbw==) | `81.81% <100%> (-3.52%)` | :arrow_down: |
| [controllers/sql.go](https://codecov.io/gh/nuveo/prest/pull/152?src=pr&el=tree#diff-Y29udHJvbGxlcnMvc3FsLmdv) | `86.95% <0%> (-13.05%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/nuveo/prest/pull/152?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/nuveo/prest/pull/152?src=pr&el=footer). Last update [29eda67...de7a9b4](https://codecov.io/gh/nuveo/prest/pull/152?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index 189a0be..a0c96e0 100644
--- a/README.md
+++ b/README.md
@@ -353,6 +353,23 @@ PUT    /_QUERIES/bar/some_update?field1=foo
 PATCH  /_QUERIES/bar/some_update?field1=foo
 DELETE /_QUERIES/bar/some_delete?field1=foo
 ```
+### Template functions
+
+- *isSet* return true if param is set
+
+```sql
+SELECT * FROM table 
+{{if isSet "field1"}}
+WHERE name = "{{.field1}}"
+{{end}} 
+;
+```
+
+- *defaultOrValue* return param value or default value
+
+```sql
+SELECT * FROM table WHERE name = '{{defaultOrValue "field1" "gopher"}}';
+```
 
 ## Permissions
 
diff --git a/adapters/postgres/queries.go b/adapters/postgres/queries.go
index 3535fb1..68ec7f6 100644
--- a/adapters/postgres/queries.go
+++ b/adapters/postgres/queries.go
@@ -8,10 +8,13 @@ import (
 	"net/url"
 	"os"
 	"path/filepath"
-	"text/template"
+	gotemplate "text/template"
+
+	"path"
 
 	"github.com/nuveo/prest/adapters/postgres/connection"
 	"github.com/nuveo/prest/config"
+	"github.com/nuveo/prest/template"
 )
 
 // GetScript get SQL template file
@@ -42,22 +45,25 @@ func GetScript(verb, folder, scriptName string) (script string, err error) {
 
 // ParseScript use values sent by users and add on script
 func ParseScript(scriptPath string, queryURL url.Values) (sqlQuery string, values []interface{}, err error) {
-	tpl, err := template.ParseFiles(scriptPath)
-	if err != nil {
-		err = fmt.Errorf("could not parse file %s: %+v", scriptPath, err)
-		return
-	}
-	tpl = tpl.Option("missingkey=error")
-
+	_, tplName := path.Split(scriptPath)
 	q := make(map[string]string)
 	pid := 1
 	for key := range queryURL {
-		q[key] = fmt.Sprintf("%s", queryURL.Get(key))
+		q[key] = queryURL.Get(key)
 		pid++
 	}
 
+	funcs := &template.FuncRegistry{TemplateData: q}
+	tpl := gotemplate.New(tplName).Funcs(funcs.RegistryAllFuncs())
+
+	tpl, err = tpl.ParseFiles(scriptPath)
+	if err != nil {
+		err = fmt.Errorf("could not parse file %s: %+v", scriptPath, err)
+		return
+	}
+
 	var buff bytes.Buffer
-	err = tpl.Execute(&buff, q)
+	err = tpl.Execute(&buff, funcs.TemplateData)
 	if err != nil {
 		err = fmt.Errorf("could not execute template %v", err)
 		return
diff --git a/template/funcregistry.go b/template/funcregistry.go
new file mode 100644
index 0000000..48a4f08
--- /dev/null
+++ b/template/funcregistry.go
@@ -0,0 +1,32 @@
+package template
+
+import (
+	"text/template"
+)
+
+// FuncRegistry registry func for templates
+type FuncRegistry struct {
+	TemplateData map[string]string
+}
+
+// RegistryAllFuncs for template
+func (fr *FuncRegistry) RegistryAllFuncs() (funcs template.FuncMap) {
+	funcs = template.FuncMap{
+		"isSet":          fr.isSet,
+		"defaultOrValue": fr.defaultOrValue,
+	}
+	return
+}
+
+func (fr *FuncRegistry) isSet(key string) (ok bool) {
+	_, ok = fr.TemplateData[key]
+	return
+}
+
+func (fr *FuncRegistry) defaultOrValue(key, defaultValue string) (value string) {
+	if ok := fr.isSet(key); !ok {
+		fr.TemplateData[key] = defaultValue
+	}
+	value = fr.TemplateData[key]
+	return
+}


Test patch:
diff --git a/adapters/postgres/queries_test.go b/adapters/postgres/queries_test.go
index e848419..350a030 100644
--- a/adapters/postgres/queries_test.go
+++ b/adapters/postgres/queries_test.go
@@ -91,16 +91,6 @@ func TestParseScript(t *testing.T) {
 	queryURL.Del("field1")
 	queryURL.Set("notable", "123")
 
-	t.Log("Try Parse Script with invalid params")
-	sql, _, err = ParseScript(fmt.Sprintf(scriptPath, "get_all.read.sql"), queryURL)
-	if err == nil {
-		t.Errorf("expected error, but got: %v", err)
-	}
-
-	if sql != "" {
-		t.Errorf("expected empty string, got: %s", sql)
-	}
-
 	t.Log("Try Parse Script with noexistent script")
 	sql, _, err = ParseScript(fmt.Sprintf(scriptPath, "gt_all.read.sql"), queryURL)
 	if err == nil {
diff --git a/controllers/sql_test.go b/controllers/sql_test.go
index 2e38699..936ecfa 100644
--- a/controllers/sql_test.go
+++ b/controllers/sql_test.go
@@ -55,9 +55,6 @@ func TestExecuteScriptQuery(t *testing.T) {
 	}{
 		{"Execute script GET method", "/testing/script-get/?field1=gopher", "GET", http.StatusOK},
 		{"Execute script POST method", "/testing/script-post/?field1=gopherzin&field2=pereira", "POST", http.StatusOK},
-		// errors
-		{"Execute script GET method invalid", "/testing/script-get/?nonexistent=gopher", "GET", http.StatusBadRequest},
-		{"Execute script POST method invalid", "/testing/script-post/?nonexistent=gopher", "POST", http.StatusBadRequest},
 	}
 
 	apiReq := api.Request{}
@@ -81,6 +78,7 @@ func TestExecuteFromScripts(t *testing.T) {
 		method      string
 		status      int
 	}{
+		{"Get results using scripts and funcs by GET method", "/_QUERIES/fulltable/funcs", "GET", http.StatusOK},
 		{"Get results using scripts by GET method", "/_QUERIES/fulltable/get_all?field1=gopher", "GET", http.StatusOK},
 		{"Get results using scripts by POST method", "/_QUERIES/fulltable/write_all?field1=gopherzin&field2=pereira", "POST", http.StatusOK},
 		{"Get results using scripts by PUT method", "/_QUERIES/fulltable/put_all?field1=trump&field2=pereira", "PUT", http.StatusOK},
@@ -89,7 +87,6 @@ func TestExecuteFromScripts(t *testing.T) {
 		// errors
 		{"Get errors using nonexistent folder", "/_QUERIES/fullnon/delete_all?field1=trump", "DELETE", http.StatusBadRequest},
 		{"Get errors using nonexistent script", "/_QUERIES/fulltable/some_com_all?field1=trump", "DELETE", http.StatusBadRequest},
-		{"Get errors with invalid params in script", "/_QUERIES/fulltable/get_all?column1=gopher", "GET", http.StatusBadRequest},
 		{"Get errors with invalid execution of sql", "/_QUERIES/fulltable/create_table?field1=test7", "POST", http.StatusBadRequest},
 	}
 
diff --git a/controllers/utils_test.go b/controllers/utils_test.go
index e57d320..ce85559 100644
--- a/controllers/utils_test.go
+++ b/controllers/utils_test.go
@@ -206,6 +206,12 @@ func createMockScripts(base string) {
 	if err != nil {
 		log.Println(err)
 	}
+
+	_, err = os.Create(fmt.Sprint(base, "/fulltable/funcs.read.sql"))
+	if err != nil {
+		log.Println(err)
+	}
+
 	_, err = os.Create(fmt.Sprint(base, "/fulltable/write_all.write.sql"))
 	if err != nil {
 		log.Println(err)
@@ -251,6 +257,7 @@ func writeMockScripts(base string) {
 		}
 	}
 
+	write("SELECT * FROM test7 WHERE name = '{{defaultOrValue \"field1\" \"gopher\"}}'", "funcs.read.sql")
 	write("SELECT * FROM test7 WHERE name = '{{.field1}}'", "get_all.read.sql")
 	write("INSERT INTO test7 (name, surname) VALUES ('{{.field1}}', '{{.field2}}')", "write_all.write.sql")
 	write("CREATE TABLE {{.field1}};", "create_table.write.sql")
diff --git a/template/funcregistry_test.go b/template/funcregistry_test.go
new file mode 100644
index 0000000..197ce27
--- /dev/null
+++ b/template/funcregistry_test.go
@@ -0,0 +1,49 @@
+package template
+
+import (
+	"testing"
+)
+
+func TestIsSet(t *testing.T) {
+	data := make(map[string]string)
+	data["test"] = "testValue"
+	funcs := &FuncRegistry{TemplateData: data}
+	ok := funcs.isSet("test")
+	if !ok {
+		t.Error("expected true but got false")
+	}
+	ok = funcs.isSet("testFalse")
+	if ok {
+		t.Error("expected false but got true")
+	}
+}
+
+func TestDefaultOrValue(t *testing.T) {
+	data := make(map[string]string)
+	data["test"] = "testValue"
+	funcs := &FuncRegistry{TemplateData: data}
+	value := funcs.defaultOrValue("test", "testDefault")
+	if value != "testValue" {
+		t.Errorf("expected 'testValue' but got %s", value)
+	}
+	value = funcs.defaultOrValue("testDefaultValue", "testDefault")
+	if value != "testDefault" {
+		t.Errorf("expected 'testDefault' but got %s", value)
+	}
+}
+
+func TestRegistryAllFuncs(t *testing.T) {
+	data := make(map[string]string)
+	data["test"] = "testValue"
+	funcs := &FuncRegistry{TemplateData: data}
+
+	fmap := funcs.RegistryAllFuncs()
+	_, ok := fmap["isSet"]
+	if !ok {
+		t.Error("func isSet is not registred")
+	}
+	_, ok = fmap["defaultOrValue"]
+	if !ok {
+		t.Error("func defaultOrValue is not registred")
+	}
+}