diff --git a/README.md b/README.md
index 40be526..74287ad 100644
--- a/README.md
+++ b/README.md
@@ -74,18 +74,6 @@ Send 10,000 GET requests with 50 concurrent connections and a random User-Agent
sarin -U http://example.com -r 10_000 -c 50 -H "User-Agent: {{ fakeit_UserAgent }}"
```
-Example output:
-
-```
-┌──────────┬───────┬──────────┬───────────┬─────────┬───────────┬──────────┬───────────┐
-│ Response │ Count │ Min │ Max │ Average │ P90 │ P95 │ P99 │
-├──────────┼───────┼──────────┼───────────┼─────────┼───────────┼──────────┼───────────┤
-│ 200 │ 10000 │ 78.038ms │ 288.153ms │ 94.71ms │ 103.078ms │ 131.08ms │ 269.218ms │
-├──────────┼───────┼──────────┼───────────┼─────────┼───────────┼──────────┼───────────┤
-│ Total │ 10000 │ 78.038ms │ 288.153ms │ 94.71ms │ 103.078ms │ 131.08ms │ 269.218ms │
-└──────────┴───────┴──────────┴───────────┴─────────┴───────────┴──────────┴───────────┘
-```
-
Run a 5-minute duration-based test:
```sh
@@ -112,15 +100,15 @@ For detailed documentation on all configuration options (URL, method, timeout, c
## Templating
-Sarin supports Go templates in methods, bodies, headers, params, cookies, and values. Use the 320+ built-in functions to generate dynamic data for each request.
+Sarin supports Go templates in URL paths, methods, bodies, headers, params, cookies, and values. Use the 320+ built-in functions to generate dynamic data for each request.
**Example:**
```sh
-sarin -U http://example.com/users \
- -V "ID={{ fakeit_UUID }}" \
- -H "X-Request-ID: {{ .Values.ID }}" \
- -B '{"id": "{{ .Values.ID }}"}'
+sarin -U "http://example.com/users/{{ fakeit_UUID }}" -r 1000 -c 10 \
+ -V "RequestID={{ fakeit_UUID }}" \
+ -H "X-Request-ID: {{ .Values.RequestID }}" \
+ -B '{"request_id": "{{ .Values.RequestID }}"}'
```
For the complete templating guide and functions reference, see the **[Templating Guide](docs/templating.md)**.
diff --git a/docs/configuration.md b/docs/configuration.md
index ab0c7f2..69de6d0 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -76,7 +76,21 @@ config3.yaml > config2.yaml > config4.yaml > config1.yaml
## URL
-Target URL. Must be HTTP or HTTPS.
+Target URL. Must be HTTP or HTTPS. The URL path supports [templating](templating.md), allowing dynamic path generation per request.
+
+> **Note:** Templating is only supported in the URL path. Host and scheme must be static.
+
+**Example with dynamic path:**
+
+```yaml
+url: http://example.com/users/{{ fakeit_UUID }}/profile
+```
+
+**CLI example with dynamic path:**
+
+```sh
+sarin -U "http://example.com/users/{{ fakeit_UUID }}" -r 1000 -c 10
+```
## Method
diff --git a/docs/examples.md b/docs/examples.md
index e5d245f..05b3222 100644
--- a/docs/examples.md
+++ b/docs/examples.md
@@ -6,8 +6,8 @@ This guide provides practical examples for common Sarin use cases.
- [Basic Usage](#basic-usage)
- [Request-Based vs Duration-Based Tests](#request-based-vs-duration-based-tests)
-- [Dynamic Requests with Templating](#dynamic-requests-with-templating)
- [Headers, Cookies, and Parameters](#headers-cookies-and-parameters)
+- [Dynamic Requests with Templating](#dynamic-requests-with-templating)
- [Request Bodies](#request-bodies)
- [Using Proxies](#using-proxies)
- [Output Formats](#output-formats)
@@ -108,104 +108,6 @@ concurrency: 100
-## Dynamic Requests with Templating
-
-Generate a random User-Agent for each request:
-
-```sh
-sarin -U http://example.com -r 1000 -c 10 \
- -H "User-Agent: {{ fakeit_UserAgent }}"
-```
-
-
-YAML equivalent
-
-```yaml
-url: http://example.com
-requests: 1000
-concurrency: 10
-headers:
- User-Agent: "{{ fakeit_UserAgent }}"
-```
-
-
-
-Send requests with random user data:
-
-```sh
-sarin -U http://example.com/api/users -r 1000 -c 10 \
- -M POST \
- -H "Content-Type: application/json" \
- -B '{"name": "{{ fakeit_Name }}", "email": "{{ fakeit_Email }}"}'
-```
-
-
-YAML equivalent
-
-```yaml
-url: http://example.com/api/users
-requests: 1000
-concurrency: 10
-method: POST
-headers:
- Content-Type: application/json
-body: '{"name": "{{ fakeit_Name }}", "email": "{{ fakeit_Email }}"}'
-```
-
-
-
-Use values to share generated data across headers and body:
-
-```sh
-sarin -U http://example.com/api/users -r 1000 -c 10 \
- -M POST \
- -V "ID={{ fakeit_UUID }}" \
- -H "X-Request-ID: {{ .Values.ID }}" \
- -B '{"id": "{{ .Values.ID }}", "name": "{{ fakeit_Name }}"}'
-```
-
-
-YAML equivalent
-
-```yaml
-url: http://example.com/api/users
-requests: 1000
-concurrency: 10
-method: POST
-values: "ID={{ fakeit_UUID }}"
-headers:
- X-Request-ID: "{{ .Values.ID }}"
-body: '{"id": "{{ .Values.ID }}", "name": "{{ fakeit_Name }}"}'
-```
-
-
-
-Generate random IPs and timestamps:
-
-```sh
-sarin -U http://example.com/api/logs -r 500 -c 20 \
- -M POST \
- -H "Content-Type: application/json" \
- -B '{"ip": "{{ fakeit_IPv4Address }}", "timestamp": "{{ fakeit_Date }}", "action": "{{ fakeit_HackerVerb }}"}'
-```
-
-
-YAML equivalent
-
-```yaml
-url: http://example.com/api/logs
-requests: 500
-concurrency: 20
-method: POST
-headers:
- Content-Type: application/json
-body: '{"ip": "{{ fakeit_IPv4Address }}", "timestamp": "{{ fakeit_Date }}", "action": "{{ fakeit_HackerVerb }}"}'
-```
-
-
-
-> For the complete list of 320+ template functions, see the **[Templating Guide](templating.md)**.
-
## Headers, Cookies, and Parameters
**Custom headers:**
@@ -323,6 +225,140 @@ cookies:
+## Dynamic Requests with Templating
+
+**Dynamic URL paths:**
+
+Test different resource endpoints with random IDs:
+
+```sh
+sarin -U "http://example.com/users/{{ fakeit_UUID }}/profile" -r 1000 -c 10
+```
+
+
+YAML equivalent
+
+```yaml
+url: http://example.com/users/{{ fakeit_UUID }}/profile
+requests: 1000
+concurrency: 10
+```
+
+
+
+Test with random numeric IDs:
+
+```sh
+sarin -U "http://example.com/products/{{ fakeit_Number 1 10000 }}" -r 1000 -c 10
+```
+
+
+YAML equivalent
+
+```yaml
+url: http://example.com/products/{{ fakeit_Number 1 10000 }}
+requests: 1000
+concurrency: 10
+```
+
+
+
+**Generate a random User-Agent for each request:**
+
+```sh
+sarin -U http://example.com -r 1000 -c 10 \
+ -H "User-Agent: {{ fakeit_UserAgent }}"
+```
+
+
+YAML equivalent
+
+```yaml
+url: http://example.com
+requests: 1000
+concurrency: 10
+headers:
+ User-Agent: "{{ fakeit_UserAgent }}"
+```
+
+
+
+Send requests with random user data:
+
+```sh
+sarin -U http://example.com/api/users -r 1000 -c 10 \
+ -M POST \
+ -H "Content-Type: application/json" \
+ -B '{"name": "{{ fakeit_Name }}", "email": "{{ fakeit_Email }}"}'
+```
+
+
+YAML equivalent
+
+```yaml
+url: http://example.com/api/users
+requests: 1000
+concurrency: 10
+method: POST
+headers:
+ Content-Type: application/json
+body: '{"name": "{{ fakeit_Name }}", "email": "{{ fakeit_Email }}"}'
+```
+
+
+
+Use values to share generated data across headers and body:
+
+```sh
+sarin -U http://example.com/api/users -r 1000 -c 10 \
+ -M POST \
+ -V "ID={{ fakeit_UUID }}" \
+ -H "X-Request-ID: {{ .Values.ID }}" \
+ -B '{"id": "{{ .Values.ID }}", "name": "{{ fakeit_Name }}"}'
+```
+
+
+YAML equivalent
+
+```yaml
+url: http://example.com/api/users
+requests: 1000
+concurrency: 10
+method: POST
+values: "ID={{ fakeit_UUID }}"
+headers:
+ X-Request-ID: "{{ .Values.ID }}"
+body: '{"id": "{{ .Values.ID }}", "name": "{{ fakeit_Name }}"}'
+```
+
+
+
+Generate random IPs and timestamps:
+
+```sh
+sarin -U http://example.com/api/logs -r 500 -c 20 \
+ -M POST \
+ -H "Content-Type: application/json" \
+ -B '{"ip": "{{ fakeit_IPv4Address }}", "timestamp": "{{ fakeit_Date }}", "action": "{{ fakeit_HackerVerb }}"}'
+```
+
+
+YAML equivalent
+
+```yaml
+url: http://example.com/api/logs
+requests: 500
+concurrency: 20
+method: POST
+headers:
+ Content-Type: application/json
+body: '{"ip": "{{ fakeit_IPv4Address }}", "timestamp": "{{ fakeit_Date }}", "action": "{{ fakeit_HackerVerb }}"}'
+```
+
+
+
+> For the complete list of 320+ template functions, see the **[Templating Guide](templating.md)**.
+
## Request Bodies
**Simple JSON body:**
diff --git a/docs/templating.md b/docs/templating.md
index 5a4bf62..05ae26f 100644
--- a/docs/templating.md
+++ b/docs/templating.md
@@ -1,6 +1,8 @@
# Templating
-Sarin supports Go templates in methods, bodies, headers, params, cookies, and values.
+Sarin supports Go templates in URL paths, methods, bodies, headers, params, cookies, and values.
+
+> **Note:** Templating in URL host and scheme is not supported. Only the path portion of the URL can contain templates.
## Table of Contents
diff --git a/internal/config/template_validator.go b/internal/config/template_validator.go
index 72de180..6618209 100644
--- a/internal/config/template_validator.go
+++ b/internal/config/template_validator.go
@@ -180,6 +180,15 @@ func validateTemplateValues(values []string, funcMap template.FuncMap) []types.F
return validationErrors
}
+func validateTemplateURLPath(urlPath string, funcMap template.FuncMap) []types.FieldValidationError {
+ if err := validateTemplateString(urlPath, funcMap); err != nil {
+ return []types.FieldValidationError{
+ types.NewFieldValidationError("URL.Path", urlPath, err),
+ }
+ }
+ return nil
+}
+
func ValidateTemplates(config *Config) []types.FieldValidationError {
// Create template function map using the same functions as sarin package
randSource := sarin.NewDefaultRandSource()
@@ -190,6 +199,11 @@ func ValidateTemplates(config *Config) []types.FieldValidationError {
var allErrors []types.FieldValidationError
+ // Validate URL path
+ if config.URL != nil {
+ allErrors = append(allErrors, validateTemplateURLPath(config.URL.Path, funcMap)...)
+ }
+
// Validate methods
allErrors = append(allErrors, validateTemplateMethods(config.Methods, funcMap)...)
diff --git a/internal/sarin/request.go b/internal/sarin/request.go
index 96768c9..9b4604e 100644
--- a/internal/sarin/request.go
+++ b/internal/sarin/request.go
@@ -40,6 +40,7 @@ func NewRequestGenerator(
localRand := rand.New(randSource)
templateFuncMap := NewDefaultTemplateFuncMap(randSource)
+ pathGenerator, isPathGeneratorDynamic := createTemplateFunc(requestURL.Path, templateFuncMap)
methodGenerator, isMethodGeneratorDynamic := NewMethodGeneratorFunc(localRand, methods, templateFuncMap)
paramsGenerator, isParamsGeneratorDynamic := NewParamsGeneratorFunc(localRand, params, templateFuncMap)
headersGenerator, isHeadersGeneratorDynamic := NewHeadersGeneratorFunc(localRand, headers, templateFuncMap)
@@ -51,35 +52,45 @@ func NewRequestGenerator(
valuesGenerator := NewValuesGeneratorFunc(values, templateFuncMap)
+ var (
+ data valuesData
+ path string
+ err error
+ )
return func(req *fasthttp.Request) error {
- req.SetRequestURI(requestURL.Path)
req.Header.SetHost(requestURL.Host)
- data, err := valuesGenerator()
+ data, err = valuesGenerator()
if err != nil {
return err
}
- if err := methodGenerator(req, data); err != nil {
+ path, err = pathGenerator(data)
+ if err != nil {
+ return err
+ }
+ req.SetRequestURI(path)
+
+ if err = methodGenerator(req, data); err != nil {
return err
}
bodyTemplateFuncMapData.ClearFormDataContenType()
- if err := bodyGenerator(req, data); err != nil {
+ if err = bodyGenerator(req, data); err != nil {
return err
}
- if err := headersGenerator(req, data); err != nil {
+ if err = headersGenerator(req, data); err != nil {
return err
}
if bodyTemplateFuncMapData.GetFormDataContenType() != "" {
req.Header.Add("Content-Type", bodyTemplateFuncMapData.GetFormDataContenType())
}
- if err := paramsGenerator(req, data); err != nil {
+ if err = paramsGenerator(req, data); err != nil {
return err
}
- if err := cookiesGenerator(req, data); err != nil {
+ if err = cookiesGenerator(req, data); err != nil {
return err
}
@@ -87,7 +98,8 @@ func NewRequestGenerator(
req.URI().SetScheme("https")
}
return nil
- }, isMethodGeneratorDynamic ||
+ }, isPathGeneratorDynamic ||
+ isMethodGeneratorDynamic ||
isParamsGeneratorDynamic ||
isHeadersGeneratorDynamic ||
isCookiesGeneratorDynamic ||