diff --git a/docs/docs/35-references/10-promotion-steps.md b/docs/docs/35-references/10-promotion-steps.md index 1113aa275..e0ea5f0ba 100644 --- a/docs/docs/35-references/10-promotion-steps.md +++ b/docs/docs/35-references/10-promotion-steps.md @@ -1300,7 +1300,7 @@ with a wide variety of external services. | `queryParams` | `[]object` | N | A list of query parameters to include in the request. | | `queryParams[].name` | `string` | Y | The name of the query parameter. | | `queryParams[].value` | `string` | Y | The value of the query parameter. The provided value will automatically be URL-encoded if necessary. | -| `body` | `string` | N | The body of the request. | +| `body` | `string` | N | The body of the request. __Note:__ As this field is a `string`, take care to utilize [`quote()`](./20-expression-language.md#quote) if the body is a valid JSON `object`. Refer to the example below of posting a message to a Slack channel. | | `insecureSkipTLSVerify` | `boolean` | N | Indicates whether to bypass TLS certificate verification when making the request. Setting this to `true` is highly discouraged. | | `timeout` | `string` | N | A string representation of the maximum time interval to wait for a request to complete. _This is the timeout for an individual HTTP request. If a request is retried, each attempt is independently subject to this timeout._ See Go's [`time` package docs](https://pkg.go.dev/time#ParseDuration) for a description of the accepted format. | | `successExpression` | `string` | N | An [expr-lang] expression that can evaluate the response to determine success. If this is left undefined and `failureExpression` _is_ defined, the default success criteria will be the inverse of the specified failure criteria. If both are left undefined, success is `true` when the HTTP status code is `2xx`. If `successExpression` and `failureExpression` are both defined and both evaluate to `true`, the failure takes precedence. Note that this expression should _not_ be offset by `${{` and `}}`. See examples for more details. | @@ -1440,6 +1440,9 @@ This examples is adapted from [Slack's own documentation](https://api.slack.com/tutorials/tracks/posting-messages-with-curl): ```yaml +vars: +- name: slackChannel + value: C123456 steps: # ... - uses: http @@ -1452,8 +1455,8 @@ steps: - name: Content-Type value: application/json body: | - { - "channel": ${{ vars.slackChannel }}, + ${{ quote({ + "channel": vars.slackChannel, "blocks": [ { "type": "section", @@ -1463,7 +1466,7 @@ steps: } } ] - } + }) }} ``` diff --git a/internal/expressions/json_templates.go b/internal/expressions/json_templates.go index 97f318f7e..1728a3a94 100644 --- a/internal/expressions/json_templates.go +++ b/internal/expressions/json_templates.go @@ -105,7 +105,13 @@ func EvaluateTemplate(template string, env map[string]any, exprOpts ...expr.Opti } exprOpts = append(exprOpts, expr.Function( "quote", - func(a ...any) (any, error) { return fmt.Sprintf(`"%v"`, a[0]), nil }, + func(a ...any) (any, error) { + jsonBytes, err := json.Marshal(a[0]) + if err != nil { + return nil, fmt.Errorf("error applying quote() function: %w", err) + } + return fmt.Sprintf(`"%s"`, jsonBytes), nil + }, new(func(any) string), )) t, err := fasttemplate.NewTemplate(template, "${{", "}}") diff --git a/internal/expressions/json_templates_test.go b/internal/expressions/json_templates_test.go index 1e0e89e36..2ca48e578 100644 --- a/internal/expressions/json_templates_test.go +++ b/internal/expressions/json_templates_test.go @@ -179,13 +179,43 @@ func TestEvaluateJSONTemplate(t *testing.T) { }, }, { - name: "quote function forces string result", - jsonTemplate: `{ "AString": "${{ quote(anInt) }}" }`, + name: "quote function forces null to string", + jsonTemplate: `{ "AString": "${{ quote(null) }}" }`, assertions: func(t *testing.T, jsonOutput []byte, err error) { require.NoError(t, err) parsed := testStruct{} require.NoError(t, json.Unmarshal(jsonOutput, &parsed)) - require.Equal(t, fmt.Sprintf("%d", testEnv["anInt"]), parsed.AString) + require.Equal(t, "null", parsed.AString) + }, + }, + { + name: "quote function forces bool to string", + jsonTemplate: `{ "AString": "${{ quote(true) }}" }`, + assertions: func(t *testing.T, jsonOutput []byte, err error) { + require.NoError(t, err) + parsed := testStruct{} + require.NoError(t, json.Unmarshal(jsonOutput, &parsed)) + require.Equal(t, "true", parsed.AString) + }, + }, + { + name: "quote function forces number to string", + jsonTemplate: `{ "AString": "${{ quote(42) }}" }`, + assertions: func(t *testing.T, jsonOutput []byte, err error) { + require.NoError(t, err) + parsed := testStruct{} + require.NoError(t, json.Unmarshal(jsonOutput, &parsed)) + require.Equal(t, "42", parsed.AString) + }, + }, + { + name: "quote function forces object to string", + jsonTemplate: `{ "AString": "${{ quote({ 'foo': 'bar' }) }}" }`, + assertions: func(t *testing.T, jsonOutput []byte, err error) { + require.NoError(t, err) + parsed := testStruct{} + require.NoError(t, json.Unmarshal(jsonOutput, &parsed)) + require.Equal(t, `{"foo":"bar"}`, parsed.AString) }, }, }