github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/service/prompting/host.go (about)

     1  package prompting
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/mutagen-io/mutagen/pkg/grpcutil"
     8  	"github.com/mutagen-io/mutagen/pkg/prompting"
     9  )
    10  
    11  // Host is a utility function for hosting a prompter via the Prompting service's
    12  // Host method. Although the Host method can be used directly, it requires
    13  // complex interaction and most callers will simply want to host a prompter.
    14  // Prompting is hosted in a background Goroutine. The identifier for the
    15  // prompter is returned, as well as an error channel that will be populated with
    16  // the first error to occur during prompting. Hosting will be terminated when
    17  // either an error occurs or the provided context is cancelled. The error
    18  // channel will be closed after hosting has terminated. If an error occurs
    19  // during hosting setup, then it will be returned and hosting will not commence.
    20  func Host(
    21  	ctx context.Context, client PromptingClient,
    22  	prompter prompting.Prompter, allowPrompts bool,
    23  ) (string, <-chan error, error) {
    24  	// Create a subcontext that we can use to perform cancellation in case of a
    25  	// client-side messaging or prompting error.
    26  	ctx, cancel := context.WithCancel(ctx)
    27  
    28  	// Initiate hosting.
    29  	stream, err := client.Host(ctx)
    30  	if err != nil {
    31  		cancel()
    32  		return "", nil, fmt.Errorf("unable to initiate prompt hosting: %w", err)
    33  	}
    34  
    35  	// Send the initialization request.
    36  	request := &HostRequest{
    37  		AllowPrompts: allowPrompts,
    38  	}
    39  	if err := stream.Send(request); err != nil {
    40  		cancel()
    41  		return "", nil, fmt.Errorf("unable to send initialization request: %w", err)
    42  	}
    43  
    44  	// Receive the initialization response, validate it, and extract the
    45  	// prompter identifier.
    46  	var identifier string
    47  	if response, err := stream.Recv(); err != nil {
    48  		cancel()
    49  		return "", nil, fmt.Errorf("unable to receive initialization response: %w", err)
    50  	} else if err = response.EnsureValid(true, allowPrompts); err != nil {
    51  		cancel()
    52  		return "", nil, fmt.Errorf("invalid initialization response received: %w", err)
    53  	} else {
    54  		identifier = response.Identifier
    55  	}
    56  
    57  	// Create an error monitoring channel.
    58  	hostingErrors := make(chan error, 1)
    59  
    60  	// Start hosting in a background Goroutine.
    61  	go func() {
    62  		// Defer closure of the errors channel.
    63  		defer close(hostingErrors)
    64  
    65  		// Defer cancellation of the context to ensure context resource cleanup
    66  		// and server-side cancellation in the event of a client-side error.
    67  		defer cancel()
    68  
    69  		// Loop and handle requests indefinitely.
    70  		for {
    71  			if response, err := stream.Recv(); err != nil {
    72  				hostingErrors <- fmt.Errorf("unable to receive message/prompt response: %w",
    73  					grpcutil.PeelAwayRPCErrorLayer(err),
    74  				)
    75  				return
    76  			} else if err = response.EnsureValid(false, allowPrompts); err != nil {
    77  				hostingErrors <- fmt.Errorf("invalid message/prompt response received: %w", err)
    78  				return
    79  			} else if response.IsPrompt {
    80  				if response, err := prompter.Prompt(response.Message); err != nil {
    81  					hostingErrors <- fmt.Errorf("unable to perform prompting: %w", err)
    82  					return
    83  				} else if err = stream.Send(&HostRequest{Response: response}); err != nil {
    84  					hostingErrors <- fmt.Errorf("unable to send prompt response: %w",
    85  						grpcutil.PeelAwayRPCErrorLayer(err),
    86  					)
    87  					return
    88  				}
    89  			} else {
    90  				if err := prompter.Message(response.Message); err != nil {
    91  					hostingErrors <- fmt.Errorf("unable to perform messaging: %w", err)
    92  					return
    93  				} else if err := stream.Send(&HostRequest{}); err != nil {
    94  					hostingErrors <- fmt.Errorf("unable to send message response: %w",
    95  						grpcutil.PeelAwayRPCErrorLayer(err),
    96  					)
    97  					return
    98  				}
    99  			}
   100  		}
   101  	}()
   102  
   103  	// Success.
   104  	return identifier, hostingErrors, nil
   105  }