django-cfg 1.4.62__py3-none-any.whl → 1.4.64__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/services/otp_service.py +3 -14
- django_cfg/apps/centrifugo/__init__.py +57 -0
- django_cfg/apps/centrifugo/admin/__init__.py +13 -0
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
- django_cfg/apps/centrifugo/admin/config.py +82 -0
- django_cfg/apps/centrifugo/apps.py +31 -0
- django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
- django_cfg/apps/centrifugo/codegen/README.md +242 -0
- django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
- django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
- django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
- django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
- django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
- django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
- django_cfg/apps/centrifugo/decorators.py +137 -0
- django_cfg/apps/centrifugo/management/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
- django_cfg/apps/centrifugo/managers/__init__.py +12 -0
- django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
- django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
- django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
- django_cfg/apps/centrifugo/models/__init__.py +11 -0
- django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
- django_cfg/apps/centrifugo/registry.py +106 -0
- django_cfg/apps/centrifugo/router.py +125 -0
- django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
- django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
- django_cfg/apps/centrifugo/serializers/channels.py +26 -0
- django_cfg/apps/centrifugo/serializers/health.py +17 -0
- django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
- django_cfg/apps/centrifugo/serializers/stats.py +21 -0
- django_cfg/apps/centrifugo/services/__init__.py +12 -0
- django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
- django_cfg/apps/centrifugo/services/client/client.py +582 -0
- django_cfg/apps/centrifugo/services/client/config.py +236 -0
- django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
- django_cfg/apps/centrifugo/services/config_helper.py +63 -0
- django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
- django_cfg/apps/centrifugo/services/logging.py +677 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
- django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
- django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
- django_cfg/apps/centrifugo/urls.py +31 -0
- django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
- django_cfg/apps/centrifugo/views/__init__.py +15 -0
- django_cfg/apps/centrifugo/views/admin_api.py +380 -0
- django_cfg/apps/centrifugo/views/dashboard.py +15 -0
- django_cfg/apps/centrifugo/views/monitoring.py +286 -0
- django_cfg/apps/centrifugo/views/testing_api.py +422 -0
- django_cfg/apps/support/utils/support_email_service.py +5 -18
- django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
- django_cfg/apps/urls.py +5 -5
- django_cfg/core/base/config_model.py +4 -44
- django_cfg/core/builders/apps_builder.py +2 -2
- django_cfg/core/generation/integration_generators/third_party.py +8 -8
- django_cfg/core/utils/__init__.py +5 -0
- django_cfg/core/utils/url_helpers.py +73 -0
- django_cfg/modules/base.py +7 -7
- django_cfg/modules/django_client/core/__init__.py +2 -1
- django_cfg/modules/django_client/core/config/config.py +8 -0
- django_cfg/modules/django_client/core/generator/__init__.py +42 -2
- django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
- django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
- django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
- django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
- django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
- django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
- django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
- django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
- django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
- django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
- django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
- django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
- django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
- django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
- django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
- django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
- django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
- django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
- django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
- django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
- django_cfg/modules/django_client/system/schema_parser.py +5 -1
- django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
- django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
- django_cfg/modules/django_unfold/dashboard.py +25 -19
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/core.py +2 -0
- django_cfg/registry/modules.py +2 -2
- django_cfg/static/js/api/centrifugo/client.mjs +164 -0
- django_cfg/static/js/api/centrifugo/index.mjs +13 -0
- django_cfg/static/js/api/index.mjs +5 -5
- django_cfg/static/js/api/types.mjs +89 -26
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/METADATA +1 -1
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/RECORD +142 -70
- django_cfg/apps/ipc/README.md +0 -346
- django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
- django_cfg/apps/ipc/TESTING.md +0 -539
- django_cfg/apps/ipc/__init__.py +0 -60
- django_cfg/apps/ipc/admin.py +0 -232
- django_cfg/apps/ipc/apps.py +0 -98
- django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
- django_cfg/apps/ipc/migrations/0002_rpclog_is_event.py +0 -23
- django_cfg/apps/ipc/migrations/__init__.py +0 -0
- django_cfg/apps/ipc/models.py +0 -229
- django_cfg/apps/ipc/serializers/__init__.py +0 -29
- django_cfg/apps/ipc/serializers/serializers.py +0 -343
- django_cfg/apps/ipc/services/__init__.py +0 -7
- django_cfg/apps/ipc/services/client/__init__.py +0 -23
- django_cfg/apps/ipc/services/client/client.py +0 -621
- django_cfg/apps/ipc/services/client/config.py +0 -214
- django_cfg/apps/ipc/services/client/exceptions.py +0 -201
- django_cfg/apps/ipc/services/logging.py +0 -239
- django_cfg/apps/ipc/services/monitor.py +0 -466
- django_cfg/apps/ipc/services/rpc_log_consumer.py +0 -330
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
- django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
- django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
- django_cfg/apps/ipc/urls.py +0 -23
- django_cfg/apps/ipc/views/__init__.py +0 -13
- django_cfg/apps/ipc/views/dashboard.py +0 -15
- django_cfg/apps/ipc/views/monitoring.py +0 -251
- django_cfg/apps/ipc/views/testing.py +0 -285
- django_cfg/static/js/api/ipc/client.mjs +0 -114
- django_cfg/static/js/api/ipc/index.mjs +0 -13
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.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
|
+
}
|