Skip to content

Commit 7a3d93b

Browse files
GAUNSDclaudenickmazzi
authored
chore(autorag+automl): RESTful refactor of S3 BFF endpoints (opendatahub-io#7358)
* chore(autorag+automl): RESTful refactor of S3 BFF endpoints * chore: Update BFF and UI code Propagate S3 endpoint RESTful refactor from OpenAPI specs to BFF Go handlers and frontend TypeScript callers in both automl and autorag. - BFF handlers (s3_handler.go): extract key from URL path param (ps.ByName) instead of query string; update signatures and doc comments. - BFF routing (app.go): update path constants to use :key path segment. - Frontend API callers (api/s3.ts): embed key in URL path via encodeURIComponent instead of query param. - Frontend hooks (queries.ts, mutations.ts): rewrite fetch/XHR URLs to use /s3/files/<key> path. Generated-by: Claude Opus 4.6 noreply@anthropic.com Co-authored-by: Claude Opus 4.6 noreply@anthropic.com * chore: Fix golang tests - Update all S3 test URLs from query-param key (`/s3/file?key=X`) to path-param key (`/s3/files/X?...`) in both automl and autorag - Add `preserveRawPath` middleware to support percent-encoded slashes (`%2F`) in S3 key path parameters, scoped to `/s3/files/` endpoints only to minimize regression risk - Add `url.PathUnescape` in S3 handlers to decode path params after raw-path routing - Fix DSPA direct-handler tests to pass `httprouter.Params` instead of nil for the `:key` path parameter - Update MissingKey tests to expect 404 (route no longer exists without a key segment) - Update WhitespaceOnlyKey tests to use `url.PathEscape` instead of `url.QueryEscape` - Update schema endpoint tests from `/s3/file/schema?key=X` to `/s3/files/X/schema` Generated-by: Claude Opus 4.6 <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: Fix frontend and contract-tests Update tests to match the RESTful S3 endpoint refactoring where `key` moved from a query parameter to a URL path parameter. - automl frontend queries.spec.tsx: update URL assertions to verify key in path (/s3/files/KEY/schema) instead of query params - automl contract tests: transform all S3 file and schema endpoint URLs, update OpenAPI $ref paths (~1s3~1files~1{key}), remove 5 tests for missing/empty key (no longer applicable with path params), and use encodeURIComponent() for keys containing slashes - autorag contract tests: same URL and $ref transformations for GET and POST S3 file endpoints, remove 4 missing-key tests, leave S3 files list endpoint (/api/v1/s3/files) unchanged Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback Commit-generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback - Tighten preserveRawPath in automl and autorag BFFs to use strings.HasPrefix against known API path prefixes instead of strings.Contains, preventing false matches on unrelated paths - Add POST /api/v1/s3/files/:key upload contract tests to automl for parity with autorag (missing params, no file part, Content-Length 413, secret 404, and valid 201 upload) Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: Fix tests Fix automl S3 upload contract test failing with 400 Bad Request. - Update buildFormDataWithFile() to use text/csv content type and .csv filename instead of application/octet-stream with .pdf filename, matching the automl BFF's CSV-only upload restriction (resolveCsvMultipartContentType) - Update all POST test URL paths from file.pdf to file.csv for consistency with the uploaded file type Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback Remove dead code in preserveRawPath middleware (autorag & automl): - Removed s3FilesPrefixedPrefix variable and its HasPrefix check, which was unreachable because http.StripPrefix removes PathPrefix from both Path and RawPath before preserveRawPath runs. - Added unit tests for preserveRawPath in both packages, including a case that asserts the prefixed path is not matched. Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: make fmt * chore: PR feedback - Updated TestPreserveRawPath in automl and autorag to reproduce the real runtime chain - Added useStripPrefix field to table-driven tests; when true, wraps the handler with http.StripPrefix(PathPrefix, ...) before calling ServeHTTP - Changed prefixed-path test case expectedPath to /api/v1/s3/files/docs%2Ffile.csv reflecting that StripPrefix + preserveRawPath preserves the percent-encoded key - Renamed test case to reflect that the prefixed path IS matched after StripPrefix removes the prefix Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback - Add empty-key validation to fetchS3File in both automl and autorag - Add empty-key guard to autorag XHR upload path (useUploadToStorageMutation) - Improve XHR network error message with actionable context - Align missing-key BFF test (TestGetS3FileHandler_MissingKey) in automl - Add unit tests for uploadFileToS3 empty-key guard in both packages - Add unit tests for fetchS3File empty-key guard in autorag Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback - Strengthen automl useS3GetFileSchemaQuery test to assert full encoded key segment and query params instead of weak toContain check - Add autorag fetchS3File test for special characters (slashes/spaces) in key encoding - Add double-encoded key (%252F) test cases to preserveRawPath middleware unit tests in both automl and autorag - Add double-encoded key integration tests in s3_handler_test.go for both packages, documenting Go URL parser limitation Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: Refactor /schema endpoint - Remove dedicated /api/v1/s3/files/:key/schema route and S3FileSchemaPath constant from app.go - Merge schema logic into GetS3FileHandler via ?view=schema query parameter in s3_handler.go - Extract schema handling into private handleS3FileSchemaView method, delete public GetS3FileSchemaHandler - Add validation for unknown view parameter values (returns 400) - Update OpenAPI spec: remove /schema path, add view query param and S3FileSchemaResponse component schema - Move type inference priority documentation to S3FileSchemaResponse component description - Update Go tests: rename to TestGetS3FileHandler_ViewSchema_*, add edge case tests for file named schema and unknown view - Update frontend queries.ts to use params.set(view, schema) instead of /schema path segment - Update frontend test assertions to verify view=schema in query string - Update contract test URLs and dollar-ref pointers to match new schema location Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback - Add required constraints to S3FileSchemaResponse OpenAPI schema - Mark top-level data property as required - Mark columns and parse_warnings as required inside data object - Aligns OpenAPI contract with BFF handler which always returns these fields Assisted-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback - Remove strings.TrimSpace from S3 key parameter in GetS3FileHandler and PostS3FileHandler to preserve legitimate S3 object keys with leading/trailing whitespace - Apply fix in both automl and autorag packages Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback * chore: PR feedback - Assert restCREATE and handleRestFailures are not called when key validation rejects early in autorag and automl s3 upload tests - Add beforeEach with jest.clearAllMocks() for proper test isolation - Import and type-safe mock restCREATE and handleRestFailures with jest.mocked() Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback - Remove WhitespaceOnlyKey tests from autorag and automl S3 handlers - Whitespace-only keys are valid S3 object keys after TrimSpace removal Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback - Clarify OpenAPI spec for GET /api/v1/s3/files/{key} application/json media type - Add description noting application/json only returns S3FileSchemaResponse when view=schema is set - Use allOf wrapper to preserve description alongside (OpenAPI 3.0 sibling keyword limitation) Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback - Fix S3 key %252F collision in preserveRawPath middleware by using r.URL.EscapedPath() instead of conditionally checking r.URL.RawPath, ensuring double-encoded percent sequences are re-encoded correctly - Add middleware test for RawPath-empty scenario (Go url.Parse round-trip) - Fix integration test to assert correct decoded key for %252F input Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback - Port S3 key %252F collision fix to automl: use r.URL.EscapedPath() in preserveRawPath middleware instead of conditional r.URL.RawPath check - Add middleware test for RawPath-empty scenario - Fix integration test to assert correct decoded key for %252F input Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * chore: PR feedback - Remove separate application/json media type from S3 file GET 200 response to fix OpenAPI validator ambiguity - Add S3FileResponse schema using oneOf to document both response variants (raw binary and CSV schema) - Reference S3FileResponse from the */* media type so a single entry covers all cases Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Nick Mazzitelli <nmazzite@redhat.com>
1 parent 482ac10 commit 7a3d93b

26 files changed

Lines changed: 1240 additions & 644 deletions

File tree

packages/automl/api/openapi/automl.yaml

Lines changed: 136 additions & 187 deletions
Large diffs are not rendered by default.

packages/automl/bff/internal/api/app.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ const (
3636
UserPath = ApiPathPrefix + "/user"
3737
NamespacePath = ApiPathPrefix + "/namespaces"
3838
SecretsPath = ApiPathPrefix + "/secrets"
39-
S3FilePath = ApiPathPrefix + "/s3/file"
40-
S3FileSchemaPath = ApiPathPrefix + "/s3/file/schema"
39+
S3FilePath = ApiPathPrefix + "/s3/files/:key"
4140
S3FilesPath = ApiPathPrefix + "/s3/files"
4241
PipelineRunsPath = ApiPathPrefix + "/pipeline-runs"
4342
ModelRegistriesPath = ApiPathPrefix + "/model-registries"
@@ -259,11 +258,10 @@ func (app *App) Routes() http.Handler {
259258

260259
// S3 operations — DSPA discovery is skipped when the caller supplies an explicit
261260
// secretName (the handler resolves credentials directly in that case).
262-
apiRouter.GET(S3FileSchemaPath, app.AttachNamespace(app.RequireAccessToPipelineServers(app.attachPipelineClientIfNeeded(app.GetS3FileSchemaHandler))))
263261
apiRouter.GET(S3FilePath, app.AttachNamespace(app.RequireAccessToPipelineServers(app.attachPipelineClientIfNeeded(app.GetS3FileHandler))))
264262
apiRouter.GET(S3FilesPath, app.AttachNamespace(app.RequireAccessToPipelineServers(app.attachPipelineClientIfNeeded(app.GetS3FilesHandler))))
265-
// POST /s3/file deliberately omits attachPipelineClientIfNeeded: secretName is required; there is
266-
// no DSPA fallback (creation flow uses an explicitly chosen input/target data secret).
263+
// POST /s3/files/:key deliberately omits attachPipelineClientIfNeeded: secretName is required;
264+
// there is no DSPA fallback (creation flow uses an explicitly chosen input/target data secret).
267265
apiRouter.POST(S3FilePath, app.AttachNamespace(app.rejectDeclaredOversizedS3Post(app.RequireAccessToPipelineServers(app.PostS3FileHandler))))
268266

269267
// Model Registry - register model binary (target registry via path param + discovered ServerURL)
@@ -276,9 +274,12 @@ func (app *App) Routes() http.Handler {
276274
// App Router
277275
appMux := http.NewServeMux()
278276

279-
// handler for api calls
280-
appMux.Handle(ApiPathPrefix+"/", apiRouter)
281-
appMux.Handle(PathPrefix+ApiPathPrefix+"/", http.StripPrefix(PathPrefix, apiRouter))
277+
// handler for api calls — preserveRawPath ensures percent-encoded path parameters
278+
// (e.g., S3 keys containing %2F) are forwarded to the router without decoding,
279+
// so :key matches the full encoded segment rather than splitting on /.
280+
rawPathRouter := preserveRawPath(apiRouter)
281+
appMux.Handle(ApiPathPrefix+"/", rawPathRouter)
282+
appMux.Handle(PathPrefix+ApiPathPrefix+"/", http.StripPrefix(PathPrefix, rawPathRouter))
282283

283284
// file server for the frontend file and SPA routes
284285
staticDir := http.Dir(app.config.StaticAssetsDir)

packages/automl/bff/internal/api/middleware.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,3 +1168,20 @@ func (app *App) AttachDiscoveredPipeline(next func(http.ResponseWriter, *http.Re
11681168
next(w, r, psParams)
11691169
}
11701170
}
1171+
1172+
// preserveRawPath wraps an http.Handler so that percent-encoded path segments
1173+
// (e.g. %2F inside an S3 key) survive exactly one level of decoding in the
1174+
// handler. For S3 file endpoints it replaces Path with EscapedPath(), which
1175+
// re-encodes any percent-literal characters that Go's url.Parse already
1176+
// decoded (e.g. %252F → Path has %2F → EscapedPath re-encodes to %252F).
1177+
// The handler then calls url.PathUnescape once to recover the real key.
1178+
func preserveRawPath(next http.Handler) http.Handler {
1179+
s3FilesPrefix := ApiPathPrefix + "/s3/files/"
1180+
1181+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1182+
if escaped := r.URL.EscapedPath(); strings.HasPrefix(escaped, s3FilesPrefix) {
1183+
r.URL.Path = escaped
1184+
}
1185+
next.ServeHTTP(w, r)
1186+
})
1187+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package api
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"net/url"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestPreserveRawPath(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
path string
16+
rawPath string
17+
useStripPrefix bool
18+
expectedPath string
19+
}{
20+
{
21+
name: "s3 files path with percent-encoded key swaps Path for RawPath",
22+
path: "/api/v1/s3/files/docs/file.csv",
23+
rawPath: "/api/v1/s3/files/docs%2Ffile.csv",
24+
expectedPath: "/api/v1/s3/files/docs%2Ffile.csv",
25+
},
26+
{
27+
name: "s3 files path without encoding is unchanged",
28+
path: "/api/v1/s3/files/simple.csv",
29+
rawPath: "",
30+
expectedPath: "/api/v1/s3/files/simple.csv",
31+
},
32+
{
33+
name: "double-encoded key preserves %25 literal via RawPath",
34+
path: "/api/v1/s3/files/docs%2Ffile.csv",
35+
rawPath: "/api/v1/s3/files/docs%252Ffile.csv",
36+
expectedPath: "/api/v1/s3/files/docs%252Ffile.csv",
37+
},
38+
{
39+
name: "double-encoded key re-encodes when RawPath is empty",
40+
path: "/api/v1/s3/files/docs%2Ffile.csv",
41+
rawPath: "",
42+
expectedPath: "/api/v1/s3/files/docs%252Ffile.csv",
43+
},
44+
{
45+
name: "non-s3 path with RawPath is unchanged",
46+
path: "/api/v1/models",
47+
rawPath: "/api/v1/models",
48+
expectedPath: "/api/v1/models",
49+
},
50+
{
51+
name: "prefixed path is matched after StripPrefix removes prefix",
52+
path: "/automl/api/v1/s3/files/docs/file.csv",
53+
rawPath: "/automl/api/v1/s3/files/docs%2Ffile.csv",
54+
useStripPrefix: true,
55+
expectedPath: "/api/v1/s3/files/docs%2Ffile.csv",
56+
},
57+
}
58+
59+
for _, tt := range tests {
60+
t.Run(tt.name, func(t *testing.T) {
61+
var capturedPath string
62+
inner := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
63+
capturedPath = r.URL.Path
64+
})
65+
66+
handler := preserveRawPath(inner)
67+
if tt.useStripPrefix {
68+
handler = http.StripPrefix(PathPrefix, handler)
69+
}
70+
71+
req := httptest.NewRequest(http.MethodGet, tt.path, nil)
72+
req.URL = &url.URL{Path: tt.path, RawPath: tt.rawPath}
73+
rr := httptest.NewRecorder()
74+
75+
handler.ServeHTTP(rr, req)
76+
77+
assert.Equal(t, tt.expectedPath, capturedPath)
78+
})
79+
}
80+
}

packages/automl/bff/internal/api/s3_declared_limit_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func TestPostS3FileHandler_ContentLengthTooLarge(t *testing.T) {
5858
smallBody := []byte("--b\r\nContent-Disposition: form-data; name=\"file\"; filename=\"x\"\r\n\r\nx\r\n--b--\r\n")
5959
req, err := http.NewRequest(
6060
http.MethodPost,
61-
"/api/v1/s3/file?namespace=ns&secretName=sec&bucket=bkt&key=k",
61+
"/api/v1/s3/files/k?namespace=ns&secretName=sec&bucket=bkt",
6262
bytes.NewReader(smallBody),
6363
)
6464
require.NoError(t, err)
@@ -88,7 +88,7 @@ func TestPostS3FileHandler_UnknownContentLength_PassesDeclaredSizeMiddleware(t *
8888
smallBody := []byte("--b\r\nContent-Disposition: form-data; name=\"file\"; filename=\"data.csv\"\r\nContent-Type: text/csv\r\n\r\nx\r\n--b--\r\n")
8989
req, err := http.NewRequest(
9090
http.MethodPost,
91-
"/api/v1/s3/file?namespace=test-namespace&secretName=non-existent&bucket=my-bucket&key=file.csv",
91+
"/api/v1/s3/files/file.csv?namespace=test-namespace&secretName=non-existent&bucket=my-bucket",
9292
bytes.NewReader(smallBody),
9393
)
9494
require.NoError(t, err)

packages/automl/bff/internal/api/s3_handler.go

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"mime/multipart"
1010
"net"
1111
"net/http"
12+
"net/url"
1213
"path"
1314
"regexp"
1415
"strconv"
@@ -218,12 +219,14 @@ func s3ConnectivityErrorMessage(bucket string) string {
218219
}
219220

220221
// GetS3FileHandler retrieves a file from S3 storage.
222+
// Path parameters:
223+
// - key (required): S3 object key to retrieve.
224+
//
221225
// Query parameters:
222226
// - secretName (optional): Kubernetes secret with S3 credentials.
223227
// If omitted, credentials are taken from the DSPA associated with the namespace.
224228
// - bucket (optional): S3 bucket; ignored on the DSPA path.
225-
// - key (required): S3 object key to retrieve.
226-
func (app *App) GetS3FileHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
229+
func (app *App) GetS3FileHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
227230
queryParams := r.URL.Query()
228231

229232
secretName := queryParams.Get("secretName")
@@ -232,9 +235,23 @@ func (app *App) GetS3FileHandler(w http.ResponseWriter, r *http.Request, _ httpr
232235
return
233236
}
234237

235-
key := strings.TrimSpace(queryParams.Get("key"))
238+
key, err := url.PathUnescape(ps.ByName("key"))
239+
if err != nil {
240+
app.badRequestResponse(w, r, fmt.Errorf("invalid URL encoding in path parameter 'key': %w", err))
241+
return
242+
}
236243
if key == "" {
237-
app.badRequestResponse(w, r, errors.New("query parameter 'key' is required and cannot be empty"))
244+
app.badRequestResponse(w, r, errors.New("path parameter 'key' is required and cannot be empty"))
245+
return
246+
}
247+
248+
if v := queryParams.Get("view"); v != "" && v != "schema" {
249+
app.badRequestResponse(w, r, fmt.Errorf("unsupported view: %q (supported: schema)", v))
250+
return
251+
}
252+
253+
if queryParams.Get("view") == "schema" {
254+
app.handleS3FileSchemaView(w, r, key, queryParams)
238255
return
239256
}
240257

@@ -300,14 +317,15 @@ func (app *App) effectivePostS3CollisionAttempts() int {
300317
}
301318

302319
// PostS3FileHandler uploads a CSV file to S3 storage using credentials from a Kubernetes secret.
303-
// Query parameters: namespace, secretName, key (required); bucket (optional, uses AWS_S3_BUCKET from secret if not provided).
320+
// Path parameters: key (required).
321+
// Query parameters: namespace, secretName (required); bucket (optional, uses AWS_S3_BUCKET from secret if not provided).
304322
// Request body: multipart/form-data with a file part named "file". Only CSV uploads are allowed: Content-Type
305323
// text/csv, or application/octet-stream with a .csv filename (or empty Content-Type with a .csv filename).
306324
// Candidate keys are chosen via HeadObject; the file is streamed to S3 once with If-None-Match (no full-file buffer).
307325
// If HeadObject and PUT disagree (concurrent writer), the handler returns 409 Conflict without retrying.
308326
//
309327
// Note: namespace is provided via the AttachNamespace middleware
310-
func (app *App) PostS3FileHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
328+
func (app *App) PostS3FileHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
311329
queryParams := r.URL.Query()
312330

313331
secretName := queryParams.Get("secretName")
@@ -320,9 +338,13 @@ func (app *App) PostS3FileHandler(w http.ResponseWriter, r *http.Request, _ http
320338
return
321339
}
322340

323-
key := strings.TrimSpace(queryParams.Get("key"))
341+
key, err := url.PathUnescape(ps.ByName("key"))
342+
if err != nil {
343+
app.badRequestResponse(w, r, fmt.Errorf("invalid URL encoding in path parameter 'key': %w", err))
344+
return
345+
}
324346
if key == "" {
325-
app.badRequestResponse(w, r, errors.New("query parameter 'key' is required and cannot be empty"))
347+
app.badRequestResponse(w, r, errors.New("path parameter 'key' is required and cannot be empty"))
326348
return
327349
}
328350

@@ -595,26 +617,8 @@ func s3GetResponseTypeAllowsInlineViewing(sanitizedContentType string) bool {
595617
}
596618
}
597619

598-
// GetS3FileSchemaHandler retrieves the schema (column names and types) from a CSV file in S3.
599-
// Query parameters:
600-
// - secretName (optional): Kubernetes secret with S3 credentials.
601-
// If omitted, credentials are taken from the DSPA associated with the namespace.
602-
// - bucket (optional): S3 bucket; ignored on the DSPA path.
603-
// - key (required): S3 object key (must be a .csv file).
604-
func (app *App) GetS3FileSchemaHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
605-
queryParams := r.URL.Query()
606-
607-
secretName := queryParams.Get("secretName")
608-
if secretName != "" && !isValidDNS1123Subdomain(secretName) {
609-
app.badRequestResponse(w, r, errors.New("invalid secretName: must be a valid DNS-1123 subdomain (lowercase alphanumeric, '-', or '.', start/end with alphanumeric, max 253 chars)"))
610-
return
611-
}
612-
613-
key := strings.TrimSpace(queryParams.Get("key"))
614-
if key == "" {
615-
app.badRequestResponse(w, r, errors.New("query parameter 'key' is required and cannot be empty"))
616-
return
617-
}
620+
func (app *App) handleS3FileSchemaView(w http.ResponseWriter, r *http.Request, key string, queryParams url.Values) {
621+
secretName := strings.TrimSpace(queryParams.Get("secretName"))
618622

619623
s3, ok := app.resolveS3Client(w, r, secretName, queryParams.Get("bucket"))
620624
if !ok {
@@ -679,7 +683,7 @@ func (app *App) GetS3FileSchemaHandler(w http.ResponseWriter, r *http.Request, _
679683
}
680684
}
681685

682-
// S3FilesEnvelope is the response envelope for GET /s3/files.
686+
// S3FilesEnvelope is the response envelope for GET /api/v1/s3/files.
683687
type S3FilesEnvelope Envelope[models.S3ListObjectsResponse, None]
684688

685689
// GetS3FilesHandler lists objects in an S3 bucket.

0 commit comments

Comments
 (0)