mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-03 18:47:41 +01:00
* s3api/iceberg: report the reason a table schema was rejected newTableMetadata swallowed the iceberg-go error and returned nil, so every schema the metadata builder refused came back as a bare 500 "Failed to build table metadata". A v3-only column type is the common case: creating a table with a variant field but no format-version 3 property leaves the client with nothing, while "variant is not supported until v3" sits in the server log. Return the error instead and classify it. Schema, spec and argument failures are the caller's input, so they answer 400 with the underlying reason; the rest stay 500. Paths that build placeholder metadata with no schema keep their existing 500 via newEmptyTableMetadata. * s3api/iceberg: fail LoadTable when placeholder metadata cannot be built buildLoadTableResult dropped a nil from the placeholder path straight into the response. That serializes as "metadata":null under HTTP 200, which no Iceberg client can parse -- a worse outcome than the 500 the nil was meant to signal. Return an error instead and let the five callers answer 500. The nil-return convention goes away with it, so the commit and transaction paths check an error rather than a sentinel. * s3api/iceberg: route rejected schemas through writeManagerError The two helpers added here duplicated work the package already does. writeManagerError is the canonical error-to-response mapper -- it already downgrades client-input failures to 400 and defaults the rest to 500 -- so teach it the iceberg-go schema and spec sentinels instead of standing up a parallel classifier. The placeholder wrapper was a pure alias for newTableMetadata with nil arguments; call that directly. No behavior change beyond the 500 message, which now reads err.Error() like every other manager error rather than carrying its own prefix.
70 lines
2.5 KiB
Go
70 lines
2.5 KiB
Go
package iceberg
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/apache/iceberg-go"
|
|
)
|
|
|
|
func TestNameValidationError(t *testing.T) {
|
|
cases := []struct {
|
|
err error
|
|
want bool
|
|
}{
|
|
{nil, false},
|
|
// Wrapped exactly as the s3tables manager surfaces it.
|
|
{fmt.Errorf("all filers failed, last error: invalid namespace name: only 'a-z', '0-9', and '_' are allowed"), true},
|
|
{fmt.Errorf("invalid table name: only 'a-z', '0-9', and '_' are allowed"), true},
|
|
{fmt.Errorf("namespace name must start with a letter or digit"), true},
|
|
{fmt.Errorf("namespace name cannot start with reserved prefix 'aws'"), true},
|
|
{fmt.Errorf("table name must be between 1 and 255 characters"), true},
|
|
{fmt.Errorf("namespace not found"), false},
|
|
{fmt.Errorf("all filers failed, last error: rpc timeout"), false},
|
|
// Unrelated faults that merely mention a name must stay 500.
|
|
{fmt.Errorf("failed to resolve table name from index"), false},
|
|
{fmt.Errorf("error fetching namespace name mapping"), false},
|
|
}
|
|
for _, c := range cases {
|
|
if got := nameValidationError(c.err); got != c.want {
|
|
t.Errorf("nameValidationError(%v) = %v, want %v", c.err, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWriteManagerError(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
err error
|
|
wantCode int
|
|
wantType string
|
|
}{
|
|
{"invalid name is a client error", fmt.Errorf("invalid namespace name: only 'a-z', '0-9', and '_' are allowed"), http.StatusBadRequest, "BadRequestException"},
|
|
{"rejected schema is a client error", fmt.Errorf("%w: for v2: variant is not supported until v3", iceberg.ErrInvalidSchema), http.StatusBadRequest, "BadRequestException"},
|
|
{"bad format version is a client error", fmt.Errorf("%w: 4", iceberg.ErrInvalidFormatVersion), http.StatusBadRequest, "BadRequestException"},
|
|
{"everything else is a server fault", fmt.Errorf("all filers failed, last error: connection refused"), http.StatusInternalServerError, "InternalServerError"},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
rec := httptest.NewRecorder()
|
|
writeManagerError(rec, c.err)
|
|
if rec.Code != c.wantCode {
|
|
t.Fatalf("status = %d, want %d", rec.Code, c.wantCode)
|
|
}
|
|
var resp ErrorResponse
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if resp.Error.Type != c.wantType {
|
|
t.Fatalf("error type = %q, want %q", resp.Error.Type, c.wantType)
|
|
}
|
|
if resp.Error.Code != c.wantCode {
|
|
t.Fatalf("error code = %d, want %d", resp.Error.Code, c.wantCode)
|
|
}
|
|
})
|
|
}
|
|
}
|