package server
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"runtime/debug"
"strings"
"github.com/99designs/gqlgen/graphql"
"github.com/emersion/go-message/mail"
"git.sr.ht/~sircmpwn/core-go/auth"
"git.sr.ht/~sircmpwn/core-go/config"
"git.sr.ht/~sircmpwn/core-go/email"
)
// Provides a graphql.RecoverFunc which will print the stack trace, and if
// debug mode is not enabled, email it to the administrator.
func EmailRecover(ctx context.Context, _origErr interface{}) error {
origErr, ok := _origErr.(error)
if !ok {
log.Printf("Unexpected error in recover: %v\n", _origErr)
return fmt.Errorf("internal system error")
}
log.Println(origErr)
if errors.Is(origErr, context.Canceled) {
return origErr
}
if errors.Is(origErr, context.DeadlineExceeded) {
return origErr
}
if origErr.Error() == "pq: canceling statement due to user request" {
return origErr
}
stack := string(debug.Stack())
log.Println(stack)
if config.Debug {
return fmt.Errorf("internal system error")
}
var header mail.Header
header.SetSubject(fmt.Sprintf("[%s] GraphQL query error: %v",
config.ServiceName(ctx), origErr))
conf := config.ForContext(ctx)
to, _ := conf.Get("mail", "error-to")
if to == "" {
log.Println("Warning: mail::error-to is not set")
return fmt.Errorf("internal system error")
}
rcpt, err := mail.ParseAddress(to)
if err != nil {
log.Printf("Warning: mail::error-to is invalid: %v", err)
return fmt.Errorf("internal system error")
}
header.SetAddressList("To", []*mail.Address{rcpt})
var reader io.Reader
func() {
defer func() {
if err := recover(); err != nil {
reader = strings.NewReader(fmt.Sprintf(`An error occured outside of the GraphQL context:
%s`, stack))
}
}()
quser := auth.ForContext(ctx)
octx := graphql.GetOperationContext(ctx)
vars, _ := json.Marshal(octx.Variables)
reader = strings.NewReader(
fmt.Sprintf(`Error occured processing GraphQL request:
%v
When running the following query on behalf of %s <%s>:
%s
With these variables:
%s
The following stack trace was produced:
%s`, origErr, quser.Username, quser.Email, octx.RawQuery,
string(vars), stack))
}()
email.EnqueueStd(ctx, header, reader, nil)
return fmt.Errorf("internal system error")
}