Repository: oras-project/oras (Go)

Issue:
Refactor `OnCompleted()` of metadata handlers
`OnCompleted` should have no parameters as it only renders metadata output. We also need to make it an independent interface and make metadata handlers contain it.

The `OnCompleted()` function will be renamed as `Render()`.

Affected command:
- oras attach (refactor done)
- oras pull (done)
- oras manifest push (done)
- oras manifest index
- oras discover (done)


Discussion:

Patch:
diff --git a/cmd/oras/internal/display/metadata/interface.go b/cmd/oras/internal/display/metadata/interface.go
index 145a65d0a..71c26de5f 100644
--- a/cmd/oras/internal/display/metadata/interface.go
+++ b/cmd/oras/internal/display/metadata/interface.go
@@ -20,17 +20,17 @@ import (
 	"oras.land/oras/cmd/oras/internal/option"
 )
 
+// Renderer renders metadata information when an operation is complete.
+type Renderer interface {
+	Render() error
+}
+
 // PushHandler handles metadata output for push events.
 type PushHandler interface {
 	TaggedHandler
+	Renderer
 
-	OnCopied(opts *option.Target) error
-	OnCompleted(root ocispec.Descriptor) error
-}
-
-// Renderer renders metadata information when an operation is complete.
-type Renderer interface {
-	Render() error
+	OnCopied(opts *option.Target, root ocispec.Descriptor) error
 }
 
 // AttachHandler handles metadata output for attach events.
diff --git a/cmd/oras/internal/display/metadata/json/push.go b/cmd/oras/internal/display/metadata/json/push.go
index 6c3a03ebe..9433829e6 100644
--- a/cmd/oras/internal/display/metadata/json/push.go
+++ b/cmd/oras/internal/display/metadata/json/push.go
@@ -31,6 +31,7 @@ type PushHandler struct {
 	path   string
 	out    io.Writer
 	tagged model.Tagged
+	root   ocispec.Descriptor
 }
 
 // NewPushHandler creates a new handler for push events.
@@ -47,15 +48,16 @@ func (ph *PushHandler) OnTagged(desc ocispec.Descriptor, tag string) error {
 }
 
 // OnCopied is called after files are copied.
-func (ph *PushHandler) OnCopied(opts *option.Target) error {
+func (ph *PushHandler) OnCopied(opts *option.Target, root ocispec.Descriptor) error {
 	if opts.RawReference != "" && !contentutil.IsDigest(opts.Reference) {
 		ph.tagged.AddTag(opts.Reference)
 	}
 	ph.path = opts.Path
+	ph.root = root
 	return nil
 }
 
-// OnCompleted is called after the push is completed.
-func (ph *PushHandler) OnCompleted(root ocispec.Descriptor) error {
-	return output.PrintPrettyJSON(ph.out, model.NewPush(root, ph.path, ph.tagged.Tags()))
+// Render implements PushHandler.
+func (ph *PushHandler) Render() error {
+	return output.PrintPrettyJSON(ph.out, model.NewPush(ph.root, ph.path, ph.tagged.Tags()))
 }
diff --git a/cmd/oras/internal/display/metadata/template/push.go b/cmd/oras/internal/display/metadata/template/push.go
index 22de93876..bc40f0afa 100644
--- a/cmd/oras/internal/display/metadata/template/push.go
+++ b/cmd/oras/internal/display/metadata/template/push.go
@@ -32,6 +32,7 @@ type PushHandler struct {
 	path     string
 	tagged   model.Tagged
 	out      io.Writer
+	root     ocispec.Descriptor
 }
 
 // NewPushHandler returns a new handler for push events.
@@ -49,15 +50,16 @@ func (ph *PushHandler) OnTagged(desc ocispec.Descriptor, tag string) error {
 }
 
 // OnCopied is called after files are copied.
-func (ph *PushHandler) OnCopied(opts *option.Target) error {
+func (ph *PushHandler) OnCopied(opts *option.Target, root ocispec.Descriptor) error {
 	if opts.RawReference != "" && !contentutil.IsDigest(opts.Reference) {
 		ph.tagged.AddTag(opts.Reference)
 	}
 	ph.path = opts.Path
+	ph.root = root
 	return nil
 }
 
-// OnCompleted is called after the push is completed.
-func (ph *PushHandler) OnCompleted(root ocispec.Descriptor) error {
-	return output.ParseAndWrite(ph.out, model.NewPush(root, ph.path, ph.tagged.Tags()), ph.template)
+// Render implements PushHandler.
+func (ph *PushHandler) Render() error {
+	return output.ParseAndWrite(ph.out, model.NewPush(ph.root, ph.path, ph.tagged.Tags()), ph.template)
 }
diff --git a/cmd/oras/internal/display/metadata/text/push.go b/cmd/oras/internal/display/metadata/text/push.go
index b84b510a3..317345a63 100644
--- a/cmd/oras/internal/display/metadata/text/push.go
+++ b/cmd/oras/internal/display/metadata/text/push.go
@@ -28,6 +28,7 @@ import (
 type PushHandler struct {
 	printer *output.Printer
 	tagLock sync.Mutex
+	root    ocispec.Descriptor
 }
 
 // NewPushHandler returns a new handler for push events.
@@ -45,15 +46,16 @@ func (h *PushHandler) OnTagged(_ ocispec.Descriptor, tag string) error {
 }
 
 // OnCopied is called after files are copied.
-func (h *PushHandler) OnCopied(opts *option.Target) error {
+func (h *PushHandler) OnCopied(opts *option.Target, root ocispec.Descriptor) error {
+	h.root = root
 	return h.printer.Println("Pushed", opts.AnnotatedReference())
 }
 
-// OnCompleted is called after the push is completed.
-func (h *PushHandler) OnCompleted(root ocispec.Descriptor) error {
-	err := h.printer.Println("ArtifactType:", root.ArtifactType)
+// Render implements PushHandler.
+func (h *PushHandler) Render() error {
+	err := h.printer.Println("ArtifactType:", h.root.ArtifactType)
 	if err != nil {
 		return err
 	}
-	return h.printer.Println("Digest:", root.Digest)
+	return h.printer.Println("Digest:", h.root.Digest)
 }
diff --git a/cmd/oras/root/push.go b/cmd/oras/root/push.go
index bc47c4fd4..1b7396aa2 100644
--- a/cmd/oras/root/push.go
+++ b/cmd/oras/root/push.go
@@ -218,11 +218,11 @@ func runPush(cmd *cobra.Command, opts *pushOptions) error {
 	}
 	memoryStore := memory.New()
 	union := contentutil.MultiReadOnlyTarget(memoryStore, store)
-	displayStatus, displayMetadata, err := display.NewPushHandler(opts.Printer, opts.Format, opts.TTY, union)
+	statusHandler, metadataHandler, err := display.NewPushHandler(opts.Printer, opts.Format, opts.TTY, union)
 	if err != nil {
 		return err
 	}
-	descs, err := loadFiles(ctx, store, opts.Annotations, opts.FileRefs, displayStatus)
+	descs, err := loadFiles(ctx, store, opts.Annotations, opts.FileRefs, statusHandler)
 	if err != nil {
 		return err
 	}
@@ -243,15 +243,15 @@ func runPush(cmd *cobra.Command, opts *pushOptions) error {
 	if err != nil {
 		return err
 	}
-	dst, stopTrack, err := displayStatus.TrackTarget(originalDst)
+	dst, stopTrack, err := statusHandler.TrackTarget(originalDst)
 	if err != nil {
 		return err
 	}
 	copyOptions := oras.DefaultCopyOptions
 	copyOptions.Concurrency = opts.concurrency
-	copyOptions.CopyGraphOptions.OnCopySkipped = displayStatus.OnCopySkipped
-	copyOptions.CopyGraphOptions.PreCopy = displayStatus.PreCopy
-	copyOptions.CopyGraphOptions.PostCopy = displayStatus.PostCopy
+	copyOptions.CopyGraphOptions.OnCopySkipped = statusHandler.OnCopySkipped
+	copyOptions.CopyGraphOptions.PreCopy = statusHandler.PreCopy
+	copyOptions.CopyGraphOptions.PostCopy = statusHandler.PostCopy
 	copyWithScopeHint := func(root ocispec.Descriptor) error {
 		// add both pull and push scope hints for dst repository
 		// to save potential push-scope token requests during copy
@@ -270,7 +270,7 @@ func runPush(cmd *cobra.Command, opts *pushOptions) error {
 	if err != nil {
 		return err
 	}
-	err = displayMetadata.OnCopied(&opts.Target)
+	err = metadataHandler.OnCopied(&opts.Target, root)
 	if err != nil {
 		return err
 	}
@@ -282,13 +282,13 @@ func runPush(cmd *cobra.Command, opts *pushOptions) error {
 		}
 		tagBytesNOpts := oras.DefaultTagBytesNOptions
 		tagBytesNOpts.Concurrency = opts.concurrency
-		dst := listener.NewTagListener(originalDst, nil, displayMetadata.OnTagged)
+		dst := listener.NewTagListener(originalDst, nil, metadataHandler.OnTagged)
 		if _, err = oras.TagBytesN(ctx, dst, root.MediaType, contentBytes, opts.extraRefs, tagBytesNOpts); err != nil {
 			return err
 		}
 	}
 
-	err = displayMetadata.OnCompleted(root)
+	err = metadataHandler.Render()
 	if err != nil {
 		return err
 	}


Test patch:
diff --git a/cmd/oras/internal/display/metadata/text/push_test.go b/cmd/oras/internal/display/metadata/text/push_test.go
index 960de4134..8871b8e35 100644
--- a/cmd/oras/internal/display/metadata/text/push_test.go
+++ b/cmd/oras/internal/display/metadata/text/push_test.go
@@ -68,8 +68,9 @@ func TestPushHandler_OnCompleted(t *testing.T) {
 			printer := output.NewPrinter(tt.out, os.Stderr)
 			p := &PushHandler{
 				printer: printer,
+				root:    tt.root,
 			}
-			if err := p.OnCompleted(tt.root); (err != nil) != tt.wantErr {
+			if err := p.Render(); (err != nil) != tt.wantErr {
 				t.Errorf("PushHandler.OnCompleted() error = %v, wantErr %v", err, tt.wantErr)
 			}
 		})