django-cfg 1.4.62__py3-none-any.whl → 1.4.63__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-cfg might be problematic. Click here for more details.

Files changed (181) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/services/otp_service.py +3 -14
  3. django_cfg/apps/centrifugo/__init__.py +57 -0
  4. django_cfg/apps/centrifugo/admin/__init__.py +13 -0
  5. django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
  6. django_cfg/apps/centrifugo/admin/config.py +82 -0
  7. django_cfg/apps/centrifugo/apps.py +31 -0
  8. django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
  9. django_cfg/apps/centrifugo/codegen/README.md +242 -0
  10. django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
  11. django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
  12. django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
  13. django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
  14. django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
  15. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
  16. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
  17. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
  18. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
  19. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
  20. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
  21. django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
  22. django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
  23. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
  24. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
  25. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
  26. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
  27. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
  28. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
  29. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
  30. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
  31. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
  32. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
  33. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
  34. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
  35. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
  36. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
  37. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
  38. django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
  39. django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
  40. django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
  41. django_cfg/apps/centrifugo/decorators.py +137 -0
  42. django_cfg/apps/centrifugo/management/__init__.py +1 -0
  43. django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
  44. django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
  45. django_cfg/apps/centrifugo/managers/__init__.py +12 -0
  46. django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
  47. django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
  48. django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
  49. django_cfg/apps/centrifugo/models/__init__.py +11 -0
  50. django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
  51. django_cfg/apps/centrifugo/registry.py +106 -0
  52. django_cfg/apps/centrifugo/router.py +125 -0
  53. django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
  54. django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
  55. django_cfg/apps/centrifugo/serializers/channels.py +26 -0
  56. django_cfg/apps/centrifugo/serializers/health.py +17 -0
  57. django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
  58. django_cfg/apps/centrifugo/serializers/stats.py +21 -0
  59. django_cfg/apps/centrifugo/services/__init__.py +12 -0
  60. django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
  61. django_cfg/apps/centrifugo/services/client/client.py +577 -0
  62. django_cfg/apps/centrifugo/services/client/config.py +228 -0
  63. django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
  64. django_cfg/apps/centrifugo/services/config_helper.py +63 -0
  65. django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
  66. django_cfg/apps/centrifugo/services/logging.py +677 -0
  67. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
  68. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
  69. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
  70. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
  71. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
  72. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
  73. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
  74. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
  75. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
  76. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
  77. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
  78. django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
  79. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
  80. django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
  81. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
  82. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
  83. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
  84. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
  85. django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
  86. django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
  87. django_cfg/apps/centrifugo/urls.py +31 -0
  88. django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
  89. django_cfg/apps/centrifugo/views/__init__.py +15 -0
  90. django_cfg/apps/centrifugo/views/admin_api.py +374 -0
  91. django_cfg/apps/centrifugo/views/dashboard.py +15 -0
  92. django_cfg/apps/centrifugo/views/monitoring.py +286 -0
  93. django_cfg/apps/centrifugo/views/testing_api.py +422 -0
  94. django_cfg/apps/support/utils/support_email_service.py +5 -18
  95. django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
  96. django_cfg/apps/urls.py +5 -5
  97. django_cfg/core/base/config_model.py +4 -44
  98. django_cfg/core/builders/apps_builder.py +2 -2
  99. django_cfg/core/generation/integration_generators/third_party.py +8 -8
  100. django_cfg/core/utils/__init__.py +5 -0
  101. django_cfg/core/utils/url_helpers.py +73 -0
  102. django_cfg/modules/base.py +7 -7
  103. django_cfg/modules/django_client/core/__init__.py +2 -1
  104. django_cfg/modules/django_client/core/config/config.py +8 -0
  105. django_cfg/modules/django_client/core/generator/__init__.py +42 -2
  106. django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
  107. django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
  108. django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
  109. django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
  110. django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
  111. django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
  112. django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
  113. django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
  114. django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
  115. django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
  116. django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
  117. django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
  118. django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
  119. django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
  120. django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
  121. django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
  122. django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
  123. django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
  124. django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
  125. django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
  126. django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
  127. django_cfg/modules/django_client/system/schema_parser.py +5 -1
  128. django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
  129. django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
  130. django_cfg/modules/django_unfold/dashboard.py +25 -19
  131. django_cfg/pyproject.toml +1 -1
  132. django_cfg/registry/core.py +2 -0
  133. django_cfg/registry/modules.py +2 -2
  134. django_cfg/static/js/api/centrifugo/client.mjs +164 -0
  135. django_cfg/static/js/api/centrifugo/index.mjs +13 -0
  136. django_cfg/static/js/api/index.mjs +5 -5
  137. django_cfg/static/js/api/types.mjs +89 -26
  138. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/METADATA +1 -1
  139. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/RECORD +142 -70
  140. django_cfg/apps/ipc/README.md +0 -346
  141. django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
  142. django_cfg/apps/ipc/TESTING.md +0 -539
  143. django_cfg/apps/ipc/__init__.py +0 -60
  144. django_cfg/apps/ipc/admin.py +0 -232
  145. django_cfg/apps/ipc/apps.py +0 -98
  146. django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
  147. django_cfg/apps/ipc/migrations/0002_rpclog_is_event.py +0 -23
  148. django_cfg/apps/ipc/migrations/__init__.py +0 -0
  149. django_cfg/apps/ipc/models.py +0 -229
  150. django_cfg/apps/ipc/serializers/__init__.py +0 -29
  151. django_cfg/apps/ipc/serializers/serializers.py +0 -343
  152. django_cfg/apps/ipc/services/__init__.py +0 -7
  153. django_cfg/apps/ipc/services/client/__init__.py +0 -23
  154. django_cfg/apps/ipc/services/client/client.py +0 -621
  155. django_cfg/apps/ipc/services/client/config.py +0 -214
  156. django_cfg/apps/ipc/services/client/exceptions.py +0 -201
  157. django_cfg/apps/ipc/services/logging.py +0 -239
  158. django_cfg/apps/ipc/services/monitor.py +0 -466
  159. django_cfg/apps/ipc/services/rpc_log_consumer.py +0 -330
  160. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
  161. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
  162. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
  163. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
  164. django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
  165. django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
  166. django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
  167. django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
  168. django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
  169. django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
  170. django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
  171. django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
  172. django_cfg/apps/ipc/urls.py +0 -23
  173. django_cfg/apps/ipc/views/__init__.py +0 -13
  174. django_cfg/apps/ipc/views/dashboard.py +0 -15
  175. django_cfg/apps/ipc/views/monitoring.py +0 -251
  176. django_cfg/apps/ipc/views/testing.py +0 -285
  177. django_cfg/static/js/api/ipc/client.mjs +0 -114
  178. django_cfg/static/js/api/ipc/index.mjs +0 -13
  179. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/WHEEL +0 -0
  180. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/entry_points.txt +0 -0
  181. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,142 @@
1
+ // Code generated by django-cfg/django_client - DO NOT EDIT.
2
+ // Generated at: {{ generated_at }}
3
+
4
+ package {{ package_name }}
5
+
6
+ import (
7
+ {% if has_request_body %}
8
+ "bytes"
9
+ {% endif %}
10
+ "context"
11
+ "encoding/json"
12
+ "fmt"
13
+ "io"
14
+ "net/http"
15
+ {% if has_query_params %}
16
+ "net/url"
17
+ {% endif %}
18
+ {% if has_path_params %}
19
+ "strings"
20
+ {% endif %}
21
+
22
+ // Import shared package for error types
23
+ "{{module_name}}/shared"
24
+ )
25
+
26
+ // Client provides access to {{ package_name }} operations.
27
+ type Client struct {
28
+ BaseURL string
29
+ HTTPClient *http.Client
30
+ Token string
31
+ }
32
+
33
+ // NewClient creates a new {{ package_name }} client.
34
+ func NewClient(baseURL string, httpClient *http.Client, token string) *Client {
35
+ return &Client{
36
+ BaseURL: baseURL,
37
+ HTTPClient: httpClient,
38
+ Token: token,
39
+ }
40
+ }
41
+
42
+ {% for operation in operations %}
43
+ {% if operation.query_params_struct %}
44
+ // {{ operation.query_params_struct.name }} contains query parameters for {{ operation.name }}.
45
+ type {{ operation.query_params_struct.name }} struct {
46
+ {% for field in operation.query_params_struct.fields %}
47
+ {{ field.name }} {{ field.type }} `json:"{{ field.json_name }}"`
48
+ {% endfor %}
49
+ }
50
+
51
+ {% endif %}
52
+ {% endfor %}
53
+
54
+ {% for operation in operations %}
55
+ // {{ operation.name }} - {{ operation.description | replace('\n', '\n// ') }}
56
+ func (c *Client) {{ operation.name }}(ctx context.Context{% for param in operation.parameters %}, {{ param.name }} {{ param.type }}{% endfor %}{% if operation.request_type %}, body {{ operation.request_type }}{% endif %}) ({{ operation.response_type }}, error) {
57
+ var result {{ operation.response_type }}
58
+
59
+ // Build URL
60
+ path := "{{ operation.path }}"
61
+ {% for param in operation.parameters %}
62
+ {% if param.location == "path" %}
63
+ path = strings.ReplaceAll(path, "{{ '{' }}{{ param.name }}{{ '}' }}", fmt.Sprintf("%v", {{ param.name }}))
64
+ {% endif %}
65
+ {% endfor %}
66
+
67
+ {% if operation.parameters|selectattr("location", "equalto", "query")|list %}
68
+ // Add query parameters
69
+ if params != nil {
70
+ query := url.Values{}
71
+ // TODO: Add query parameter marshaling based on struct fields
72
+ path = path + "?" + query.Encode()
73
+ }
74
+ {% endif %}
75
+
76
+ u := c.BaseURL + path
77
+
78
+ // Prepare request body
79
+ var reqBody io.Reader
80
+ {% if operation.request_type %}
81
+ bodyBytes, err := json.Marshal(body)
82
+ if err != nil {
83
+ return result, fmt.Errorf("failed to marshal request body: %w", err)
84
+ }
85
+ reqBody = bytes.NewReader(bodyBytes)
86
+ {% endif %}
87
+
88
+ // Create request
89
+ req, err := http.NewRequestWithContext(ctx, "{{ operation.http_method }}", u, reqBody)
90
+ if err != nil {
91
+ return result, fmt.Errorf("failed to create request: %w", err)
92
+ }
93
+
94
+ // Set headers
95
+ {% if operation.request_type %}
96
+ req.Header.Set("Content-Type", "application/json")
97
+ {% endif %}
98
+ if c.Token != "" {
99
+ req.Header.Set("Authorization", "Bearer "+c.Token)
100
+ }
101
+
102
+ // Execute request
103
+ resp, err := c.HTTPClient.Do(req)
104
+ if err != nil {
105
+ return result, &shared.NetworkError{
106
+ Err: err,
107
+ Method: "{{ operation.http_method }}",
108
+ URL: u,
109
+ }
110
+ }
111
+ defer resp.Body.Close()
112
+
113
+ // Read response body
114
+ respBody, err := io.ReadAll(resp.Body)
115
+ if err != nil {
116
+ return result, &shared.NetworkError{
117
+ Err: fmt.Errorf("failed to read response: %w", err),
118
+ Method: "{{ operation.http_method }}",
119
+ URL: u,
120
+ }
121
+ }
122
+
123
+ // Check status code
124
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
125
+ return result, shared.ParseAPIError(resp.StatusCode, resp.Status, "{{ operation.http_method }}", u, respBody)
126
+ }
127
+
128
+ {% if operation.http_method != "DELETE" %}
129
+ // Decode response
130
+ if err := json.Unmarshal(respBody, &result); err != nil {
131
+ return result, &shared.UnmarshalError{
132
+ Err: err,
133
+ Body: respBody,
134
+ Type: "{{ operation.response_type }}",
135
+ }
136
+ }
137
+ {% endif %}
138
+
139
+ return result, nil
140
+ }
141
+
142
+ {% endfor %}
@@ -0,0 +1,217 @@
1
+ // Code generated by django-cfg/django_client - DO NOT EDIT.
2
+ // Generated at: {{ generated_at }}
3
+
4
+ package {{ package_name }}
5
+
6
+ import (
7
+ "fmt"
8
+ "regexp"
9
+ "strings"
10
+ )
11
+
12
+ // ValidationError represents a validation error.
13
+ type ValidationError struct {
14
+ Field string
15
+ Message string
16
+ }
17
+
18
+ // Error implements the error interface.
19
+ func (e *ValidationError) Error() string {
20
+ return fmt.Sprintf("validation error on field '%s': %s", e.Field, e.Message)
21
+ }
22
+
23
+ // ValidationErrors represents multiple validation errors.
24
+ type ValidationErrors []ValidationError
25
+
26
+ // Error implements the error interface.
27
+ func (e ValidationErrors) Error() string {
28
+ if len(e) == 0 {
29
+ return "validation errors"
30
+ }
31
+
32
+ var messages []string
33
+ for _, err := range e {
34
+ messages = append(messages, err.Error())
35
+ }
36
+ return strings.Join(messages, "; ")
37
+ }
38
+
39
+ // Add adds a validation error.
40
+ func (e *ValidationErrors) Add(field, message string) {
41
+ *e = append(*e, ValidationError{
42
+ Field: field,
43
+ Message: message,
44
+ })
45
+ }
46
+
47
+ // HasErrors returns true if there are any errors.
48
+ func (e ValidationErrors) HasErrors() bool {
49
+ return len(e) > 0
50
+ }
51
+
52
+ // Validator provides validation utilities.
53
+ type Validator struct {
54
+ errors ValidationErrors
55
+ }
56
+
57
+ // NewValidator creates a new validator.
58
+ func NewValidator() *Validator {
59
+ return &Validator{
60
+ errors: make(ValidationErrors, 0),
61
+ }
62
+ }
63
+
64
+ // Required validates that a string field is not empty.
65
+ func (v *Validator) Required(field, value string) *Validator {
66
+ if strings.TrimSpace(value) == "" {
67
+ v.errors.Add(field, "this field is required")
68
+ }
69
+ return v
70
+ }
71
+
72
+ // RequiredInt validates that an int field is set (non-zero).
73
+ func (v *Validator) RequiredInt(field string, value int64) *Validator {
74
+ if value == 0 {
75
+ v.errors.Add(field, "this field is required")
76
+ }
77
+ return v
78
+ }
79
+
80
+ // RequiredPtr validates that a pointer field is not nil.
81
+ func (v *Validator) RequiredPtr(field string, value interface{}) *Validator {
82
+ if value == nil {
83
+ v.errors.Add(field, "this field is required")
84
+ }
85
+ return v
86
+ }
87
+
88
+ // MinLength validates minimum string length.
89
+ func (v *Validator) MinLength(field, value string, min int) *Validator {
90
+ if len(value) > 0 && len(value) < min {
91
+ v.errors.Add(field, fmt.Sprintf("must be at least %d characters", min))
92
+ }
93
+ return v
94
+ }
95
+
96
+ // MaxLength validates maximum string length.
97
+ func (v *Validator) MaxLength(field, value string, max int) *Validator {
98
+ if len(value) > max {
99
+ v.errors.Add(field, fmt.Sprintf("must be at most %d characters", max))
100
+ }
101
+ return v
102
+ }
103
+
104
+ // MinValue validates minimum numeric value.
105
+ func (v *Validator) MinValue(field string, value, min int64) *Validator {
106
+ if value < min {
107
+ v.errors.Add(field, fmt.Sprintf("must be at least %d", min))
108
+ }
109
+ return v
110
+ }
111
+
112
+ // MaxValue validates maximum numeric value.
113
+ func (v *Validator) MaxValue(field string, value, max int64) *Validator {
114
+ if value > max {
115
+ v.errors.Add(field, fmt.Sprintf("must be at most %d", max))
116
+ }
117
+ return v
118
+ }
119
+
120
+ // Pattern validates string against regex pattern.
121
+ func (v *Validator) Pattern(field, value, pattern, message string) *Validator {
122
+ if value == "" {
123
+ return v
124
+ }
125
+
126
+ matched, err := regexp.MatchString(pattern, value)
127
+ if err != nil {
128
+ v.errors.Add(field, fmt.Sprintf("invalid pattern: %v", err))
129
+ return v
130
+ }
131
+
132
+ if !matched {
133
+ if message == "" {
134
+ message = fmt.Sprintf("must match pattern: %s", pattern)
135
+ }
136
+ v.errors.Add(field, message)
137
+ }
138
+
139
+ return v
140
+ }
141
+
142
+ // Email validates email format.
143
+ func (v *Validator) Email(field, value string) *Validator {
144
+ if value == "" {
145
+ return v
146
+ }
147
+
148
+ // Simple email validation
149
+ pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
150
+ return v.Pattern(field, value, pattern, "must be a valid email address")
151
+ }
152
+
153
+ // URL validates URL format.
154
+ func (v *Validator) URL(field, value string) *Validator {
155
+ if value == "" {
156
+ return v
157
+ }
158
+
159
+ if !strings.HasPrefix(value, "http://") && !strings.HasPrefix(value, "https://") {
160
+ v.errors.Add(field, "must be a valid URL starting with http:// or https://")
161
+ }
162
+
163
+ return v
164
+ }
165
+
166
+ // OneOf validates that value is one of allowed values.
167
+ func (v *Validator) OneOf(field, value string, allowed []string) *Validator {
168
+ if value == "" {
169
+ return v
170
+ }
171
+
172
+ for _, a := range allowed {
173
+ if value == a {
174
+ return v
175
+ }
176
+ }
177
+
178
+ v.errors.Add(field, fmt.Sprintf("must be one of: %s", strings.Join(allowed, ", ")))
179
+ return v
180
+ }
181
+
182
+ // Custom adds a custom validation error.
183
+ func (v *Validator) Custom(field, message string) *Validator {
184
+ v.errors.Add(field, message)
185
+ return v
186
+ }
187
+
188
+ // CustomIf adds a custom validation error if condition is true.
189
+ func (v *Validator) CustomIf(condition bool, field, message string) *Validator {
190
+ if condition {
191
+ v.errors.Add(field, message)
192
+ }
193
+ return v
194
+ }
195
+
196
+ // Errors returns all validation errors.
197
+ func (v *Validator) Errors() ValidationErrors {
198
+ return v.errors
199
+ }
200
+
201
+ // Valid returns true if there are no validation errors.
202
+ func (v *Validator) Valid() bool {
203
+ return !v.errors.HasErrors()
204
+ }
205
+
206
+ // Error returns validation errors as an error, or nil if valid.
207
+ func (v *Validator) Error() error {
208
+ if v.Valid() {
209
+ return nil
210
+ }
211
+ return v.errors
212
+ }
213
+
214
+ // Validate is a convenience function to create and return a validator.
215
+ func Validate() *Validator {
216
+ return NewValidator()
217
+ }