mirror of
https://github.com/aykhans/sarin.git
synced 2026-04-14 20:19:37 +00:00
feat: add json_Object and json_Encode template funcs
This commit is contained in:
@@ -12,6 +12,7 @@ Sarin supports Go templates in URL paths, methods, bodies, headers, params, cook
|
|||||||
- [General Functions](#general-functions)
|
- [General Functions](#general-functions)
|
||||||
- [String Functions](#string-functions)
|
- [String Functions](#string-functions)
|
||||||
- [Collection Functions](#collection-functions)
|
- [Collection Functions](#collection-functions)
|
||||||
|
- [JSON Functions](#json-functions)
|
||||||
- [Time Functions](#time-functions)
|
- [Time Functions](#time-functions)
|
||||||
- [Crypto Functions](#crypto-functions)
|
- [Crypto Functions](#crypto-functions)
|
||||||
- [Body Functions](#body-functions)
|
- [Body Functions](#body-functions)
|
||||||
@@ -117,6 +118,33 @@ sarin -U http://example.com/users \
|
|||||||
| `slice_Int(values ...int)` | Create int slice | `{{ slice_Int 1 2 3 }}` |
|
| `slice_Int(values ...int)` | Create int slice | `{{ slice_Int 1 2 3 }}` |
|
||||||
| `slice_Uint(values ...uint)` | Create uint slice | `{{ slice_Uint 1 2 3 }}` |
|
| `slice_Uint(values ...uint)` | Create uint slice | `{{ slice_Uint 1 2 3 }}` |
|
||||||
|
|
||||||
|
### JSON Functions
|
||||||
|
|
||||||
|
Build JSON payloads programmatically without manual quoting or escaping. `json_Object` is the ergonomic shortcut for flat objects; `json_Encode` marshals any value (slice, map, etc.) to a JSON string.
|
||||||
|
|
||||||
|
| Function | Description | Example |
|
||||||
|
| --------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------- |
|
||||||
|
| `json_Object(pairs ...any)` | Build an object from interleaved key-value pairs and return it as a JSON string. Keys must be strings. | `{{ json_Object "name" "Alice" "age" 30 }}` |
|
||||||
|
| `json_Encode(v any)` | Marshal any value (slice, map, etc.) to a JSON string. | `{{ json_Encode (slice_Str "a" "b") }}` → `["a","b"]` |
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Flat object with fake data
|
||||||
|
body: '{{ json_Object "name" (fakeit_FirstName) "email" (fakeit_Email) }}'
|
||||||
|
|
||||||
|
# Embed a solved captcha token
|
||||||
|
body: '{{ json_Object "g-recaptcha-response" (twocaptcha_RecaptchaV2 "API_KEY" "SITE_KEY" "https://example.com") }}'
|
||||||
|
|
||||||
|
# Encode a slice as a JSON array
|
||||||
|
body: '{{ json_Encode (slice_Str "a" "b" "c") }}'
|
||||||
|
|
||||||
|
# Encode a string dictionary (map[string]string)
|
||||||
|
body: '{{ json_Encode (dict_Str "key1" "value1" "key2" "value2") }}'
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** Object keys are serialized in alphabetical order (Go's `encoding/json` default), not insertion order. For API payloads this is almost always fine because JSON key order is semantically irrelevant.
|
||||||
|
|
||||||
### Time Functions
|
### Time Functions
|
||||||
|
|
||||||
| Function | Description | Example |
|
| Function | Description | Example |
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -85,6 +86,38 @@ func NewDefaultTemplateFuncMap(randSource rand.Source, fileCache *FileCache) tem
|
|||||||
"slice_Uint": func(values ...uint) []uint { return values },
|
"slice_Uint": func(values ...uint) []uint { return values },
|
||||||
"slice_Join": strings.Join,
|
"slice_Join": strings.Join,
|
||||||
|
|
||||||
|
// JSON
|
||||||
|
// json_Encode marshals any value to a JSON string.
|
||||||
|
// Usage: {{ json_Encode (dict_Str "key" "value") }}
|
||||||
|
"json_Encode": func(v any) (string, error) {
|
||||||
|
data, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return "", types.NewJSONEncodeError(err)
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
},
|
||||||
|
// json_Object builds a JSON object from interleaved key-value pairs and returns it
|
||||||
|
// as a JSON string. Keys must be strings; values may be any JSON-encodable type.
|
||||||
|
// Usage: {{ json_Object "name" "Alice" "age" 30 }}
|
||||||
|
"json_Object": func(pairs ...any) (string, error) {
|
||||||
|
if len(pairs)%2 != 0 {
|
||||||
|
return "", types.ErrJSONObjectOddArgs
|
||||||
|
}
|
||||||
|
obj := make(map[string]any, len(pairs)/2)
|
||||||
|
for i := 0; i < len(pairs); i += 2 {
|
||||||
|
key, ok := pairs[i].(string)
|
||||||
|
if !ok {
|
||||||
|
return "", types.NewJSONObjectKeyError(i, pairs[i])
|
||||||
|
}
|
||||||
|
obj[key] = pairs[i+1]
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return "", types.NewJSONEncodeError(err)
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
},
|
||||||
|
|
||||||
// Time
|
// Time
|
||||||
"time_NowUnix": func() int64 { return time.Now().Unix() },
|
"time_NowUnix": func() int64 { return time.Now().Unix() },
|
||||||
"time_NowUnixMilli": func() int64 { return time.Now().UnixMilli() },
|
"time_NowUnixMilli": func() int64 { return time.Now().UnixMilli() },
|
||||||
|
|||||||
@@ -208,8 +208,41 @@ func (e URLParseError) Unwrap() error {
|
|||||||
var (
|
var (
|
||||||
ErrFileCacheNotInitialized = errors.New("file cache is not initialized")
|
ErrFileCacheNotInitialized = errors.New("file cache is not initialized")
|
||||||
ErrFormDataOddArgs = errors.New("body_FormData requires an even number of arguments (key-value pairs)")
|
ErrFormDataOddArgs = errors.New("body_FormData requires an even number of arguments (key-value pairs)")
|
||||||
|
ErrJSONObjectOddArgs = errors.New("json_Object requires an even number of arguments (key-value pairs)")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type JSONObjectKeyError struct {
|
||||||
|
Index int
|
||||||
|
Value any
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJSONObjectKeyError(index int, value any) JSONObjectKeyError {
|
||||||
|
return JSONObjectKeyError{Index: index, Value: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e JSONObjectKeyError) Error() string {
|
||||||
|
return fmt.Sprintf("json_Object key at index %d must be a string, got %T", e.Index, e.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSONEncodeError struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJSONEncodeError(err error) JSONEncodeError {
|
||||||
|
if err == nil {
|
||||||
|
err = errNoError
|
||||||
|
}
|
||||||
|
return JSONEncodeError{Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e JSONEncodeError) Error() string {
|
||||||
|
return "json_Encode failed: " + e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e JSONEncodeError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
type TemplateParseError struct {
|
type TemplateParseError struct {
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user