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

     1  package prompting
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/mutagen-io/mutagen/pkg/identifier"
     9  	"github.com/mutagen-io/mutagen/pkg/prompting"
    10  )
    11  
    12  // Server provides an implementation of the Prompting service.
    13  type Server struct {
    14  	// UnimplementedPromptingServer is the required base implementation.
    15  	UnimplementedPromptingServer
    16  }
    17  
    18  // NewServer creates a new prompt server.
    19  func NewServer() *Server {
    20  	return &Server{}
    21  }
    22  
    23  // Host performs prompt hosting.
    24  func (s *Server) Host(stream Prompting_HostServer) error {
    25  	// Receive and validate the initial request.
    26  	request, err := stream.Recv()
    27  	if err != nil {
    28  		return fmt.Errorf("unable to receive initial request: %w", err)
    29  	} else if err = request.ensureValid(hostRequestModeInitial); err != nil {
    30  		return fmt.Errorf("received invalid initial request: %w", err)
    31  	}
    32  
    33  	// Create a unique identifier for the prompter.
    34  	identifier, err := identifier.New(identifier.PrefixPrompter)
    35  	if err != nil {
    36  		return fmt.Errorf("unable to generate prompter identifier: %w", err)
    37  	}
    38  
    39  	// Send the initial response.
    40  	if err := stream.Send(&HostResponse{Identifier: identifier}); err != nil {
    41  		return fmt.Errorf("unable to send initial response: %w", err)
    42  	}
    43  
    44  	// Extract the request context.
    45  	ctx := stream.Context()
    46  
    47  	// Wrap the stream to create a prompter.
    48  	prompter := &streamPrompter{
    49  		allowPrompts: request.AllowPrompts,
    50  		stream:       stream,
    51  	}
    52  
    53  	// Register the prompter.
    54  	if err := prompting.RegisterPrompterWithIdentifier(identifier, prompter); err != nil {
    55  		return fmt.Errorf("unable to register prompter: %w", err)
    56  	}
    57  
    58  	// Wait for the request or connection to be terminated.
    59  	<-ctx.Done()
    60  
    61  	// Unregister the promper.
    62  	prompting.UnregisterPrompter(identifier)
    63  
    64  	// Success.
    65  	return nil
    66  }
    67  
    68  // asyncPromptResponse provides a structure for returning prompt results
    69  // asynchronously, allowing prompting to be cancelled.
    70  type asyncPromptResponse struct {
    71  	// response is the response returned by the prompter.
    72  	response string
    73  	// error is the error returned by the prompter.
    74  	error error
    75  }
    76  
    77  // Prompt performs prompting against registered prompters.
    78  func (s *Server) Prompt(ctx context.Context, request *PromptRequest) (*PromptResponse, error) {
    79  	// Validate the request.
    80  	if err := request.ensureValid(); err != nil {
    81  		return nil, fmt.Errorf("invalid prompt request: %w", err)
    82  	}
    83  
    84  	// Perform prompting from the global registry asynchronously.
    85  	// TODO: Should we build cancellation into the Prompter interface itself?
    86  	asyncResponse := make(chan asyncPromptResponse, 1)
    87  	go func() {
    88  		response, err := prompting.Prompt(request.Prompter, request.Prompt)
    89  		asyncResponse <- asyncPromptResponse{response, err}
    90  	}()
    91  
    92  	// Wait for a response or cancellation.
    93  	select {
    94  	case <-ctx.Done():
    95  		return nil, errors.New("prompting cancelled while waiting for response")
    96  	case r := <-asyncResponse:
    97  		if r.error != nil {
    98  			return nil, fmt.Errorf("unable to prompt: %w", r.error)
    99  		} else {
   100  			return &PromptResponse{Response: r.response}, nil
   101  		}
   102  	}
   103  }