Skip to content

Commit 4d4ef08

Browse files
committed
feat(api): Add validation for DatabaseConfigRequest
Add Validate() method to DatabaseConfigRequest that validates: - Database type (mysql, postgres, mariadb, mongodb, redis) - Mode (shared, create, existing, external) - Required fields based on mode: - existing mode requires existing_container - external mode requires external_host and positive external_port Validation is called in createDeployment handler before processing databases array. Signed-off-by: nfebe <fenn25.fn@gmail.com>
1 parent 96e298b commit 4d4ef08

2 files changed

Lines changed: 160 additions & 9 deletions

File tree

internal/api/multi_database_test.go

Lines changed: 118 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,12 @@ func TestGenerateDatabaseEnvVars(t *testing.T) {
111111
}
112112
}
113113

114-
func TestDatabaseConfigRequest_Validation(t *testing.T) {
114+
func TestDatabaseConfigRequest_Validate(t *testing.T) {
115115
tests := []struct {
116116
name string
117117
req DatabaseConfigRequest
118118
wantErr bool
119+
errMsg string
119120
}{
120121
{
121122
name: "valid shared database config",
@@ -150,23 +151,131 @@ func TestDatabaseConfigRequest_Validation(t *testing.T) {
150151
},
151152
wantErr: false,
152153
},
154+
{
155+
name: "valid create mode config",
156+
req: DatabaseConfigRequest{
157+
Alias: "newdb",
158+
Type: "mariadb",
159+
Mode: "create",
160+
},
161+
wantErr: false,
162+
},
163+
{
164+
name: "invalid database type",
165+
req: DatabaseConfigRequest{
166+
Alias: "test",
167+
Type: "oracle",
168+
Mode: "shared",
169+
},
170+
wantErr: true,
171+
errMsg: "invalid database type",
172+
},
173+
{
174+
name: "invalid mode",
175+
req: DatabaseConfigRequest{
176+
Alias: "test",
177+
Type: "mysql",
178+
Mode: "invalid",
179+
},
180+
wantErr: true,
181+
errMsg: "invalid database mode",
182+
},
183+
{
184+
name: "existing mode missing container",
185+
req: DatabaseConfigRequest{
186+
Alias: "test",
187+
Type: "mysql",
188+
Mode: "existing",
189+
},
190+
wantErr: true,
191+
errMsg: "existing_container is required",
192+
},
193+
{
194+
name: "external mode missing host",
195+
req: DatabaseConfigRequest{
196+
Alias: "test",
197+
Type: "postgres",
198+
Mode: "external",
199+
ExternalPort: 5432,
200+
},
201+
wantErr: true,
202+
errMsg: "external_host is required",
203+
},
204+
{
205+
name: "external mode missing port",
206+
req: DatabaseConfigRequest{
207+
Alias: "test",
208+
Type: "postgres",
209+
Mode: "external",
210+
ExternalHost: "db.example.com",
211+
},
212+
wantErr: true,
213+
errMsg: "external_port must be a positive integer",
214+
},
215+
{
216+
name: "external mode with zero port",
217+
req: DatabaseConfigRequest{
218+
Alias: "test",
219+
Type: "postgres",
220+
Mode: "external",
221+
ExternalHost: "db.example.com",
222+
ExternalPort: 0,
223+
},
224+
wantErr: true,
225+
errMsg: "external_port must be a positive integer",
226+
},
227+
{
228+
name: "external mode with negative port",
229+
req: DatabaseConfigRequest{
230+
Alias: "test",
231+
Type: "postgres",
232+
Mode: "external",
233+
ExternalHost: "db.example.com",
234+
ExternalPort: -1,
235+
},
236+
wantErr: true,
237+
errMsg: "external_port must be a positive integer",
238+
},
239+
{
240+
name: "valid mongodb config",
241+
req: DatabaseConfigRequest{
242+
Alias: "mongo",
243+
Type: "mongodb",
244+
Mode: "shared",
245+
},
246+
wantErr: false,
247+
},
153248
}
154249

155250
for _, tt := range tests {
156251
t.Run(tt.name, func(t *testing.T) {
157-
if tt.req.Alias == "" && !tt.wantErr {
158-
t.Error("alias should not be empty for valid config")
159-
}
160-
if tt.req.Type == "" && !tt.wantErr {
161-
t.Error("type should not be empty for valid config")
162-
}
163-
if tt.req.Mode == "" && !tt.wantErr {
164-
t.Error("mode should not be empty for valid config")
252+
err := tt.req.Validate()
253+
if tt.wantErr {
254+
if err == nil {
255+
t.Errorf("Validate() expected error containing %q, got nil", tt.errMsg)
256+
return
257+
}
258+
if tt.errMsg != "" && !containsSubstring(err.Error(), tt.errMsg) {
259+
t.Errorf("Validate() error = %q, want error containing %q", err.Error(), tt.errMsg)
260+
}
261+
} else {
262+
if err != nil {
263+
t.Errorf("Validate() unexpected error: %v", err)
264+
}
165265
}
166266
})
167267
}
168268
}
169269

270+
func containsSubstring(s, substr string) bool {
271+
for i := 0; i <= len(s)-len(substr); i++ {
272+
if s[i:i+len(substr)] == substr {
273+
return true
274+
}
275+
}
276+
return false
277+
}
278+
170279
func TestMultipleDatabaseConfigs(t *testing.T) {
171280
metadata := &models.ServiceMetadata{
172281
Name: "myapp",

internal/api/server.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,39 @@ type DatabaseConfigRequest struct {
591591
EnvPrefix string `json:"env_prefix,omitempty"`
592592
}
593593

594+
func (d *DatabaseConfigRequest) Validate() error {
595+
validTypes := map[string]bool{
596+
"mysql": true, "postgres": true, "mariadb": true,
597+
"mongodb": true, "redis": true,
598+
}
599+
if !validTypes[d.Type] {
600+
return fmt.Errorf("invalid database type: %s (must be mysql, postgres, mariadb, mongodb, or redis)", d.Type)
601+
}
602+
603+
validModes := map[string]bool{
604+
"shared": true, "create": true, "existing": true, "external": true,
605+
}
606+
if !validModes[d.Mode] {
607+
return fmt.Errorf("invalid database mode: %s (must be shared, create, existing, or external)", d.Mode)
608+
}
609+
610+
switch d.Mode {
611+
case "existing":
612+
if d.ExistingContainer == "" {
613+
return fmt.Errorf("existing_container is required for mode 'existing'")
614+
}
615+
case "external":
616+
if d.ExternalHost == "" {
617+
return fmt.Errorf("external_host is required for mode 'external'")
618+
}
619+
if d.ExternalPort <= 0 {
620+
return fmt.Errorf("external_port must be a positive integer for mode 'external'")
621+
}
622+
}
623+
624+
return nil
625+
}
626+
594627
func (s *Server) createDeployment(c *gin.Context) {
595628
var req struct {
596629
Name string `json:"name" binding:"required"`
@@ -694,6 +727,15 @@ func (s *Server) createDeployment(c *gin.Context) {
694727
var databaseConfigs []models.DatabaseConfig
695728

696729
if len(req.Databases) > 0 {
730+
for i, dbReq := range req.Databases {
731+
if err := dbReq.Validate(); err != nil {
732+
c.JSON(http.StatusBadRequest, gin.H{
733+
"error": fmt.Sprintf("invalid database configuration at index %d: %s", i, err.Error()),
734+
})
735+
return
736+
}
737+
}
738+
697739
envVars, configs, err := s.createDatabasesForDeployment(req.Name, req.Databases)
698740
if err != nil {
699741
c.JSON(http.StatusInternalServerError, gin.H{

0 commit comments

Comments
 (0)