Repository: fsouza/fake-gcs-server
Pull request #676 (2022-02-05 10:17:51)

Honor ListObjects IncludeTrailingDelimiter
This allows gcsfuse to list directory objects which end with / and
allows pjdfstest rmdir/06.t to pass.  References
GoogleCloudPlatform/gcsfuse#590.

gaul: > Is it possible to add a test? Thanks!

Done but blocked on googleapis/google-cloud-go#5617.

Patch:
diff --git a/fakestorage/object.go b/fakestorage/object.go
index a86a903..d5a2718 100644
--- a/fakestorage/object.go
+++ b/fakestorage/object.go
@@ -266,11 +266,12 @@ func (s *Server) createObject(obj Object) (Object, error) {
 }
 
 type ListOptions struct {
-	Prefix      string
-	Delimiter   string
-	Versions    bool
-	StartOffset string
-	EndOffset   string
+	Prefix                   string
+	Delimiter                string
+	Versions                 bool
+	StartOffset              string
+	EndOffset                string
+	IncludeTrailingDelimiter bool
 }
 
 // ListObjects returns a sorted list of objects that match the given criteria,
@@ -306,6 +307,9 @@ func (s *Server) ListObjectsWithOptions(bucketName string, options ListOptions)
 			if isInOffset(prefix, options.StartOffset, options.EndOffset) {
 				prefixes[prefix] = true
 			}
+			if options.IncludeTrailingDelimiter && obj.Name == prefix {
+				respObjects = append(respObjects, obj)
+			}
 		} else {
 			if isInOffset(obj.Name, options.StartOffset, options.EndOffset) {
 				respObjects = append(respObjects, obj)
@@ -453,11 +457,12 @@ func (s *Server) objectWithGenerationOnValidGeneration(bucketName, objectName, g
 func (s *Server) listObjects(r *http.Request) jsonResponse {
 	bucketName := mux.Vars(r)["bucketName"]
 	objs, prefixes, err := s.ListObjectsWithOptions(bucketName, ListOptions{
-		Prefix:      r.URL.Query().Get("prefix"),
-		Delimiter:   r.URL.Query().Get("delimiter"),
-		Versions:    r.URL.Query().Get("versions") == "true",
-		StartOffset: r.URL.Query().Get("startOffset"),
-		EndOffset:   r.URL.Query().Get("endOffset"),
+		Prefix:                   r.URL.Query().Get("prefix"),
+		Delimiter:                r.URL.Query().Get("delimiter"),
+		Versions:                 r.URL.Query().Get("versions") == "true",
+		StartOffset:              r.URL.Query().Get("startOffset"),
+		EndOffset:                r.URL.Query().Get("endOffset"),
+		IncludeTrailingDelimiter: r.URL.Query().Get("includeTrailingDelimiter") == "true",
 	})
 	if err != nil {
 		return jsonResponse{status: http.StatusNotFound}
diff --git a/go.mod b/go.mod
index 4eb6750..5e0cc5e 100644
--- a/go.mod
+++ b/go.mod
@@ -2,7 +2,7 @@ module github.com/fsouza/fake-gcs-server
 
 require (
 	cloud.google.com/go/pubsub v1.18.0
-	cloud.google.com/go/storage v1.21.0
+	cloud.google.com/go/storage v1.21.1-0.20220301044418-a34503bc0f0b
 	github.com/google/go-cmp v0.5.7
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/mux v1.8.0
@@ -30,7 +30,7 @@ require (
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c // indirect
+	google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf // indirect
 	google.golang.org/grpc v1.44.0 // indirect
 	google.golang.org/protobuf v1.27.1 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
diff --git a/go.sum b/go.sum
index 960adf0..5770c70 100644
--- a/go.sum
+++ b/go.sum
@@ -36,7 +36,6 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
 cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
-cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw=
 cloud.google.com/go/compute v1.3.0 h1:mPL/MzDDYHsh5tHRS9mhmhWlcgClCrCa6ApQCU6wnHI=
 cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
@@ -57,8 +56,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-cloud.google.com/go/storage v1.21.0 h1:HwnT2u2D309SFDHQII6m18HlrCi3jAXhUMTLOWXYH14=
-cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA=
+cloud.google.com/go/storage v1.21.1-0.20220301044418-a34503bc0f0b h1:a2zSO6f6aoCv9Eu/wskVXnceAdJmIy1wap0aqzFYf0k=
+cloud.google.com/go/storage v1.21.1-0.20220301044418-a34503bc0f0b/go.mod h1:LV51ZfTtzeoeXb0mKg4HgywiJ4PFR11jFNtQwUzhg8U=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -381,7 +380,6 @@ golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -490,9 +488,7 @@ google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3l
 google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
 google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
 google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM=
-google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M=
 google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
-google.golang.org/api v0.69.0/go.mod h1:boanBiw+h5c3s+tBPgEzLDRHfFLWV0qXxRHz3ws7C80=
 google.golang.org/api v0.70.0 h1:67zQnAE0T2rB0A3CwLSas0K+SbVzSxP+zTLkQLexeiw=
 google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@@ -567,14 +563,11 @@ google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ6
 google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220216160803-4663080d8bc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c h1:TU4rFa5APdKTq0s6B7WTsH6Xmx0Knj86s6Biz56mErE=
 google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf h1:SVYXkUz2yZS9FWb2Gm8ivSlbNQzL2Z/NpPKE3RG2jWk=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=


Test patch:
diff --git a/fakestorage/object_test.go b/fakestorage/object_test.go
index b52872a..5be64a6 100644
--- a/fakestorage/object_test.go
+++ b/fakestorage/object_test.go
@@ -615,6 +615,7 @@ func getObjectsForListTests() []Object {
 		{ObjectAttrs: ObjectAttrs{BucketName: "some-bucket", Name: "img/brand.jpg"}},
 		{ObjectAttrs: ObjectAttrs{BucketName: "some-bucket", Name: "video/hi-res/some_video_1080p.mp4"}},
 		{ObjectAttrs: ObjectAttrs{BucketName: "other-bucket", Name: "static/css/style.css"}},
+		{ObjectAttrs: ObjectAttrs{BucketName: "trailing-delimiter-bucket", Name: "foo/"}},
 	}
 }
 
@@ -731,6 +732,20 @@ func getTestCasesForListTests(versioningEnabled, withOverwrites bool) []listTest
 			[]string{"img/brand.jpg"},
 			[]string{"img/hi-res/"},
 		},
+		{
+			"delimiter without IncludeTrailingDelimiter",
+			"trailing-delimiter-bucket",
+			&storage.Query{Delimiter: "/", IncludeTrailingDelimiter: false},
+			[]string{},
+			[]string{"foo/"},
+		},
+		{
+			"delimiter with IncludeTrailingDelimiter",
+			"trailing-delimiter-bucket",
+			&storage.Query{Delimiter: "/", IncludeTrailingDelimiter: true},
+			[]string{"foo/"},
+			[]string{"foo/"},
+		},
 	}
 }