Repository: fsouza/fake-gcs-server
Pull request #1254 (2023-07-17 20:12:51)

Disallow composing more than 32 objects at once
The Google Cloud Storage API does not allow it either (see [documentation][1]). Trying to compose more than 32 objects at once qualifies as a 400 BadRequest. This is the body that GCS returns in such a case:

```json
{
  "error": {
    "code": 400,
    "message": "The number of source components provided (33) exceeds the maximum (32)",
    "errors": [
      {
        "message": "The number of source components provided (33) exceeds the maximum (32)",
        "domain": "global",
        "reason": "invalid"
      }
    ]
  }
}
```

Note that GCS supports composing more than 32 objects into one. But it requires multiple `compose` calls, each one composing at most 32 objects in one go.

[1]: https://cloud.google.com/storage/docs/json_api/v1/objects/compose


Patch:
diff --git a/fakestorage/object.go b/fakestorage/object.go
index 47994f2..047629d 100644
--- a/fakestorage/object.go
+++ b/fakestorage/object.go
@@ -1121,6 +1121,14 @@ func (s *Server) composeObject(r *http.Request) jsonResponse {
 		}
 	}
 
+	const maxComposeObjects = 32
+	if len(composeRequest.SourceObjects) > maxComposeObjects {
+		return jsonResponse{
+			status:       http.StatusBadRequest,
+			errorMessage: fmt.Sprintf("The number of source components provided (%d) exceeds the maximum (%d)", len(composeRequest.SourceObjects), maxComposeObjects),
+		}
+	}
+
 	sourceNames := make([]string, 0, len(composeRequest.SourceObjects))
 	for _, n := range composeRequest.SourceObjects {
 		sourceNames = append(sourceNames, n.Name)


Test patch:
diff --git a/fakestorage/object_test.go b/fakestorage/object_test.go
index 38c8adf..26dc27c 100644
--- a/fakestorage/object_test.go
+++ b/fakestorage/object_test.go
@@ -1992,6 +1992,7 @@ func TestServiceClientComposeObject(t *testing.T) {
 			destObjectName    string
 			sourceObjectNames []string
 			expectedContent   string
+			expectedError     string
 		}{
 			{
 				"destination file doesn't exist",
@@ -1999,6 +2000,7 @@ func TestServiceClientComposeObject(t *testing.T) {
 				"files/some-file.txt",
 				[]string{"files/source1.txt", "files/source2.txt"},
 				source1Content + source2Content,
+				"",
 			},
 			{
 				"destination file already exists",
@@ -2006,6 +2008,7 @@ func TestServiceClientComposeObject(t *testing.T) {
 				"files/destination.txt",
 				[]string{"files/source1.txt", "files/source2.txt"},
 				source1Content + source2Content,
+				"",
 			},
 			{
 				"destination is a source",
@@ -2013,6 +2016,15 @@ func TestServiceClientComposeObject(t *testing.T) {
 				"files/source3.txt",
 				[]string{"files/source2.txt", "files/source3.txt"},
 				source2Content + source3Content,
+				"",
+			},
+			{
+				"too many objects at once",
+				"first-bucket",
+				"files/destination.txt",
+				[]string{"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33"},
+				"",
+				"googleapi: Error 400: The number of source components provided (33) exceeds the maximum (32)",
 			},
 		}
 		for _, test := range tests {
@@ -2033,6 +2045,9 @@ func TestServiceClientComposeObject(t *testing.T) {
 				composer.Metadata = map[string]string{"baz": "qux"}
 				attrs, err := composer.Run(context.TODO())
 				if err != nil {
+					if err.Error() == test.expectedError {
+						return
+					}
 					t.Fatal(err)
 				}