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 }