diff --git a/README.md b/README.md index c675318..5105a30 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,98 @@ go func() { }() ``` +## Response Management (Canned Responses) + +Responses canbe fetched, like any other resource, via the `Fetch` function: + +```go +response, err := gcloudcx.Fetch[gcloudcx.Response](context, client, responseID) +``` + +Or via the dedicated `FetchByFilter` function: + +```go +response, err = gcloudcx.ResponseManagementResponse{}.FetchByFilters( + context, + client, + gcloudcx.ResponseManagementQueryFilter{ + Name: "name", Operator: "EQUALS", Values: []string{response.Name}, + }, +) +``` +This calls the query API of Genesys Cloud, which is more efficient than fetching all the responses and then filtering them (`FetchBy` method). + +Responses can apply substitutions to provide the final text that can be sent to the user. Custom substitutions are provided as a `map[string]string` to the func and default substitutions are provided by the resource itself. + +```go +text, err := response.ApplySubstitutions(context, "text/plain", map[string]string{ + "firstName": "John", + "lastName": "Doe", +}) +``` +The second argument allows to specify the content type of the text to be returned, that content type has to be present in the resource, otherwise an error is returned. `text/plain` and `text/html` are supported by the Genesys Cloud API as of today. + +`ApplySubstitutions` supports both the Genesys Cloud substitutions (`{{substitutionId}}`) and the Go Template system, which is far more powerful. See the [Go Template documentation](https://pkg.go.dev/text/template) for more information. On top of the basic features, `ApplySubstitutions` also provides the functions from the Sprig library, which is a collection of useful functions for templates. See the [Sprig documentation](https://masterminds.github.io/sprig/) for more information. + +For example the following response will return the text `Hello John Doe`: +```go +response := gcloudcx.ResponseManagementResponse{ // This is a fake response, just to show the template + Texts: []gcloudcx.ResponseManagementContent{ + { + ContentType: "text/plain", + Content: `Hello {{firstName}} {{lastName}}`, // Genesys Cloud substitutions + } + }, +} +text, err := response.ApplySubstitutions(context, "text/plain", map[string]string{ + "firstName": "John", + "lastName": "Doe", +}) +assert.Equal(t, "Hello John Doe", text) +``` + +To get the same result with Go Templates: +```go +response := gcloudcx.ResponseManagementResponse{ // This is a fake response, just to show the template + Texts: []gcloudcx.ResponseManagementContent{ + { + ContentType: "text/plain", + Content: `Hello {{.firstName}} {{.lastName}}`, // Go Template substitutions + } + }, +} +text, err := response.ApplySubstitutions(context, "text/plain", map[string]string{ + "firstName": "John", + "lastName": "Doe", +}) +assert.Equal(t, "Hello John Doe", text) +``` + +You can mix both approaches: +```go +response := gcloudcx.ResponseManagementResponse{ // This is a fake response, just to show the template + Texts: []gcloudcx.ResponseManagementContent{ + { + ContentType: "text/plain", + Content: ` +Hello {{.firstName}} {{.lastName}}, you are on {{location}}! # Genesys Cloud and Go substitutions +{{if eq .location "Earth"}}You are on the right place!{{end}} # Go Template condition +And you are visiting {{ default "Atlantis" .country }}. # Sprig function +`, + } + }, + Substitutions: []gcloudcx.ResponseManagementSubstitution{{ + ID: "location", + Description: "The location of the person to greet", + Default: "Earth", + }}, +} +text, err := response.ApplySubstitutions(context, "text/plain", map[string]string{ + "firstName": "John", + "lastName": "Doe", +}) +``` + ## Agent Chat API ## Guest Chat API diff --git a/go.mod b/go.mod index 6d04a99..254df89 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,10 @@ module github.com/gildas/go-gcloudcx go 1.18 require ( + github.com/Masterminds/sprig/v3 v3.2.3 github.com/gildas/go-core v0.5.2 github.com/gildas/go-errors v0.3.4 - github.com/gildas/go-logger v1.6.3 + github.com/gildas/go-logger v1.6.4 github.com/gildas/go-request v0.7.14 github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 @@ -22,20 +23,29 @@ require ( cloud.google.com/go/compute/metadata v0.2.2 // indirect cloud.google.com/go/logging v1.6.1 // indirect cloud.google.com/go/longrunning v0.3.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect + github.com/huandu/xstrings v1.4.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/spf13/cast v1.5.0 // indirect go.opencensus.io v0.24.0 // indirect + golang.org/x/crypto v0.4.0 // indirect golang.org/x/net v0.4.0 // indirect golang.org/x/oauth2 v0.3.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.3.0 // indirect golang.org/x/text v0.5.0 // indirect - google.golang.org/api v0.104.0 // indirect + google.golang.org/api v0.105.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 // indirect google.golang.org/grpc v1.51.0 // indirect diff --git a/go.sum b/go.sum index cd4aadd..00dd9c5 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,12 @@ cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9 cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -21,12 +27,13 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/gildas/go-core v0.5.2 h1:J7N7BGGSeQPz9VfPg7X2T3VBH+SkUTV3wD9sqr/Azfw= github.com/gildas/go-core v0.5.2/go.mod h1:xHYI7H5K1xcOH1m9tYdkoqK8n9gch4fELldA1FRT1MA= github.com/gildas/go-errors v0.3.4 h1:kH0a1yrYFl1By6KdzOMd/pa0X1xaBCXByiitgkfI7iE= github.com/gildas/go-errors v0.3.4/go.mod h1:woYbcnE/bN4faEH+wjpQAN29zzFCYNGjt8hJiNJ7btE= -github.com/gildas/go-logger v1.6.3 h1:PvCm6Amtnshwvwe8E3AFvzLIGOiW/StqPppSfREt0QE= -github.com/gildas/go-logger v1.6.3/go.mod h1:VeGgZVZFvmn8Mpow4SrtFMm3jB2HFf8YiljQkBIjIsQ= +github.com/gildas/go-logger v1.6.4 h1:FhHZwOwnveZzqj6vqIH2QzwEUn+J/S2FnBLdrPxfzck= +github.com/gildas/go-logger v1.6.4/go.mod h1:VeGgZVZFvmn8Mpow4SrtFMm3jB2HFf8YiljQkBIjIsQ= github.com/gildas/go-request v0.7.14 h1:bNnzueAb6iAQZ6eTG/v8kwUdzwfmEoJqqwokNCFkbpE= github.com/gildas/go-request v0.7.14/go.mod h1:qFKsNwHKet2fmwP9Rj+nnmnSYsRV4E3tpT1/cIIRdOw= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -56,6 +63,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -69,39 +77,70 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0= github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= +golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -110,17 +149,28 @@ golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -128,9 +178,12 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.104.0 h1:KBfmLRqdZEbwQleFlSLnzpQJwhjpmNOk4cKQIBDZ9mg= -google.golang.org/api v0.104.0/go.mod h1:JCspTXJbBxa5ySXw4UgUqVer7DfVxbvc/CTUFqAED5U= +google.golang.org/api v0.105.0 h1:t6P9Jj+6XTn4U9I2wycQai6Q/Kz7iOT+QzjJ3G2V4x8= +google.golang.org/api v0.105.0/go.mod h1:qh7eD5FJks5+BcE+cjBIm6Gz8vioK7EHvnlniqXBnqI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= @@ -162,7 +215,10 @@ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/responsemanagement_response.go b/responsemanagement_response.go index 3a96ae7..c1fc723 100644 --- a/responsemanagement_response.go +++ b/responsemanagement_response.go @@ -7,7 +7,9 @@ import ( "time" "github.com/gildas/go-errors" + "github.com/gildas/go-logger" "github.com/google/uuid" + "github.com/Masterminds/sprig/v3" ) // ResponseManagementResponse is the interface for the Response Management Response @@ -123,7 +125,10 @@ func (response ResponseManagementResponse) FetchByFilters(context context.Contex } // ApplySubstitutions applies the substitutions to the response text that matches the given content type -func (response ResponseManagementResponse) ApplySubstitutions(contentType string, substitutions map[string]string) (string, error) { +func (response ResponseManagementResponse) ApplySubstitutions(context context.Context, contentType string, substitutions map[string]string) (string, error) { + log := logger.Must(logger.FromContext(context, logger.Create("gcloudcx", "nil"))).Child("response", "applysubstitutions", "response", response.ID) + // Logging is done at the TRACE level since the response and/or the text could contain sensitive information + var text string for _, content := range response.Texts { if strings.Compare(strings.ToLower(content.ContentType), strings.ToLower(contentType)) == 0 { @@ -135,24 +140,34 @@ func (response ResponseManagementResponse) ApplySubstitutions(contentType string return "", errors.NotFound.With("text of type ", contentType) } - if len(substitutions) == 0 { + if len(substitutions) == 0 && len(response.Substitutions) == 0 { return text, nil } + if substitutions == nil { + substitutions = make(map[string]string) + } // Apply defaults from the response to the given substitutions + log.Record("substitutions", substitutions).Tracef("Substitutions") for _, substitution := range response.Substitutions { - if s, ok := substitutions[substitution.ID]; ok && len(s) == 0 { + if s, ok := substitutions[substitution.ID]; ok { + if len(s) == 0 { + substitutions[substitution.ID] = substitution.Default + } + } else { substitutions[substitution.ID] = substitution.Default } } + log.Record("substitutions", substitutions).Tracef("Substitutions with defaults") - // change the placeholders to Go Template - for id, _ := range substitutions { + // change the Genesys Cloud placeholders to Go Template placeholders + for id := range substitutions { text = strings.ReplaceAll(text, "{{"+id+"}}", "{{."+id+"}}") } + log.Tracef("Replaced gcloud placeholders with Go Template placeholders: %s", text) // apply the substitutions - tpl, err := template.New("response").Parse(text) + tpl, err := template.New("response").Funcs(sprig.FuncMap()).Parse(text) if err != nil { return "", errors.WrapErrors(errors.ArgumentInvalid.With("text", "..."), err) } diff --git a/responsemanagement_test.go b/responsemanagement_test.go index 23eabf6..75715bc 100644 --- a/responsemanagement_test.go +++ b/responsemanagement_test.go @@ -215,3 +215,215 @@ func (suite *ResponseManagementSuite) TestShouldFailFetchingResponseWithUnknownN suite.Logger.Errorf("Expected Failure", err) suite.Assert().ErrorIs(err, errors.NotFound, "Should have failed to fetch Response Management Response") } + +func (suite *ResponseManagementSuite) TestCanApplySubstitutions() { + ctx := suite.Logger.ToContext(context.Background()) + response := gcloudcx.ResponseManagementResponse{ + Name: "Test", + Texts: []gcloudcx.ResponseManagementContent{ + { + ContentType: "text/plain", + Content: "Hello, {{name}}", + }, + }, + } + text, err := response.ApplySubstitutions(ctx, "text/plain", map[string]string{"name": "John"}) + suite.Require().NoError(err, "Failed to apply substitutions") + suite.Assert().Equal("Hello, John", text) +} + +func (suite *ResponseManagementSuite) TestCanApplySubstitutionsWithoutPlaceholder() { + ctx := suite.Logger.ToContext(context.Background()) + response := gcloudcx.ResponseManagementResponse{ + Name: "Test", + Texts: []gcloudcx.ResponseManagementContent{ + { + ContentType: "text/plain", + Content: "Hello, World!", + }, + }, + } + text, err := response.ApplySubstitutions(ctx, "text/plain", map[string]string{"name": "John"}) + suite.Require().NoError(err, "Failed to apply substitutions") + suite.Assert().Equal("Hello, World!", text) + + text, err = response.ApplySubstitutions(ctx, "text/plain", nil) + suite.Require().NoError(err, "Failed to apply substitutions") + suite.Assert().Equal("Hello, World!", text) +} + +func (suite *ResponseManagementSuite) TestCanApplySubstitutionsWithGOPlaceholders() { + ctx := suite.Logger.ToContext(context.Background()) + response := gcloudcx.ResponseManagementResponse{ + Name: "Test", + Texts: []gcloudcx.ResponseManagementContent{ + { + ContentType: "text/plain", + Content: "Hello, {{.name}}", + }, + }, + } + text, err := response.ApplySubstitutions(ctx, "text/plain", map[string]string{"name": "John"}) + suite.Require().NoError(err, "Failed to apply substitutions") + suite.Assert().Equal("Hello, John", text) +} + +func (suite *ResponseManagementSuite) TestCanApplySubstitutionsWithDefaults() { + ctx := suite.Logger.ToContext(context.Background()) + response := gcloudcx.ResponseManagementResponse{ + Name: "Test", + Texts: []gcloudcx.ResponseManagementContent{ + { + ContentType: "text/plain", + Content: `Hello, {{name}}`, + }, + }, + Substitutions: []gcloudcx.ResponseManagementSubstitution{{ + ID: "name", + Description: "The name of the person to greet", + Default: "John", + }}, + } + text, err := response.ApplySubstitutions(ctx, "text/plain", map[string]string{"lastname": "Doe"}) + suite.Require().NoError(err, "Failed to apply substitutions") + suite.Assert().Equal("Hello, John", text) +} + +func (suite *ResponseManagementSuite) TestCanApplySubstitutionsWithGODefaults() { + ctx := suite.Logger.ToContext(context.Background()) + response := gcloudcx.ResponseManagementResponse{ + Name: "Test", + Texts: []gcloudcx.ResponseManagementContent{ + { + ContentType: "text/plain", + Content: `Hello, {{default "John" .name}}`, + }, + }, + } + text, err := response.ApplySubstitutions(ctx, "text/plain", map[string]string{"lastname": "Doe"}) + suite.Require().NoError(err, "Failed to apply substitutions") + suite.Assert().Equal("Hello, John", text) +} + +func (suite *ResponseManagementSuite) TestCanApplySubstitutionsWithGOAction() { + ctx := suite.Logger.ToContext(context.Background()) + response := gcloudcx.ResponseManagementResponse{ + Name: "Test", + Texts: []gcloudcx.ResponseManagementContent{ + { + ContentType: "text/plain", + Content: `Hello, {{if .name}}{{.name}}{{else}}John{{end}}`, + }, + }, + } + text, err := response.ApplySubstitutions(ctx, "text/plain", map[string]string{"lastname": "Doe"}) + suite.Require().NoError(err, "Failed to apply substitutions") + suite.Assert().Equal("Hello, John", text) +} + +func (suite *ResponseManagementSuite) TestCanApplySubstitutionsWithComplexTemplate() { + ctx := suite.Logger.ToContext(context.Background()) + expected := ` +{ + "genesys_prompt": "Would you like to buy now?", + "genesys_quick_replies": [{ + "text": "OK","payload": "answer=OK" + },{ + "text": "Cancel","payload": "answer=Cancel" + }] +}` + response := gcloudcx.ResponseManagementResponse{ + Name: "Test", + Texts: []gcloudcx.ResponseManagementContent{ + { + ContentType: "text/plain", + Content: ` +{ + "genesys_prompt": "{{question}}", + "genesys_quick_replies": [{ +{{- if .OK_payload}} + "text": "{{OK}}","payload": "{{OK_payload}}" +{{- else}} + "text": "{{OK}}","payload": "answer={{OK}}" +{{- end}} + },{ +{{- if .Cancel_payload}} + "text": "{{Cancel}}","payload": "{{Cancel_payload}}" +{{- else}} + "text": "{{Cancel}}","payload": "answer={{Cancel}}" +{{- end}} + }] +}`, + }, + }, + Substitutions: []gcloudcx.ResponseManagementSubstitution{{ + ID: "question", Default: "Would you like to proceed?", + }, { + ID: "OK", Default: "OK", + }, { + ID: "OK_payload", Default: "", + }, { + ID: "Cancel", Default: "Cancel", + }, { + ID: "Cancel_payload", Default: "", + }}, + } + arguments := map[string]string{ + "question": "Would you like to buy now?", + } + text, err := response.ApplySubstitutions(ctx, "text/plain", arguments) + suite.Require().NoError(err, "Failed to apply substitutions") + suite.Assert().Equal(expected, text) +} + +func (suite *ResponseManagementSuite) TestCanApplySubstitutionsWithComplexTemplateAndNoArguments() { + ctx := suite.Logger.ToContext(context.Background()) + expected := ` +{ + "genesys_prompt": "Would you like to proceed?", + "genesys_quick_replies": [{ + "text": "OK","payload": "answer=OK" + },{ + "text": "Cancel","payload": "answer=Cancel" + }] +}` + response := gcloudcx.ResponseManagementResponse{ + Name: "Test", + Texts: []gcloudcx.ResponseManagementContent{ + { + ContentType: "text/plain", + Content: ` +{ + "genesys_prompt": "{{question}}", + "genesys_quick_replies": [{ +{{- if .OK_payload}} + "text": "{{OK}}","payload": "{{OK_payload}}" +{{- else}} + "text": "{{OK}}","payload": "answer={{OK}}" +{{- end}} + },{ +{{- if .Cancel_payload}} + "text": "{{Cancel}}","payload": "{{Cancel_payload}}" +{{- else}} + "text": "{{Cancel}}","payload": "answer={{Cancel}}" +{{- end}} + }] +}`, + }, + }, + Substitutions: []gcloudcx.ResponseManagementSubstitution{{ + ID: "question", Default: "Would you like to proceed?", + }, { + ID: "OK", Default: "OK", + }, { + ID: "OK_payload", Default: "", + }, { + ID: "Cancel", Default: "Cancel", + }, { + ID: "Cancel_payload", Default: "", + }}, + } + text, err := response.ApplySubstitutions(ctx, "text/plain", nil) + suite.Require().NoError(err, "Failed to apply substitutions") + suite.Assert().Equal(expected, text) +} diff --git a/version.go b/version.go index aa4c67b..c00c596 100644 --- a/version.go +++ b/version.go @@ -4,7 +4,7 @@ package gcloudcx var commit string // VERSION is the version of this application -var VERSION = "0.7.19" + commit +var VERSION = "0.7.20" + commit // APP is the name of the application const APP string = "GCloudCX Client"