Skip to content

Commit

Permalink
Merge branch 'main' into feat/add-playground-example-google-3d-tiles
Browse files Browse the repository at this point in the history
  • Loading branch information
ShogoHirasawa authored Jan 23, 2025
2 parents 322b03e + 1141a0c commit 7387525
Show file tree
Hide file tree
Showing 46 changed files with 1,633 additions and 114 deletions.
2 changes: 1 addition & 1 deletion server/.air.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ send_interrupt = false
kill_delay = 1000 # ms

# include file extensions
include_ext = ["go"]
include_ext = ["go", "json"]

# exclude directories
exclude_dir = ["e2e", "tmp"]
Expand Down
2 changes: 1 addition & 1 deletion server/e2e/gql_storytelling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@ func TestStoryPageCRUD(t *testing.T) {
res.Object().
Value("errors").Array().
Element(0).Object().
ValueEqual("message", "input: updateStoryPage page not found")
ValueEqual("message", "page not found")

_, _, pageID2 := createPage(e, sID, storyID, "test 2", true)
_, _, pageID3 := createPage(e, sID, storyID, "test 3", false)
Expand Down
11 changes: 5 additions & 6 deletions server/e2e/gql_workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestDeleteTeam(t *testing.T) {
}
o = Request(e, uId1.String(), request).Object()

o.Value("errors").Array().First().Object().Value("message").Equal("input: deleteTeam operation denied")
o.Value("errors").Array().First().Object().Value("message").Equal("operation denied")
}

func TestUpdateTeam(t *testing.T) {
Expand All @@ -67,7 +67,7 @@ func TestUpdateTeam(t *testing.T) {
Query: query,
}
o = Request(e, uId1.String(), request).Object()
o.Value("errors").Array().First().Object().Value("message").Equal("input: updateTeam not found")
o.Value("errors").Array().First().Object().Value("message").Equal("not found")
}

func TestAddMemberToTeam(t *testing.T) {
Expand All @@ -93,7 +93,7 @@ func TestAddMemberToTeam(t *testing.T) {
Query: query,
}
Request(e, uId1.String(), request).Object().
Value("errors").Array().First().Object().Value("message").Equal("input: addMemberToTeam user already joined")
Value("errors").Array().First().Object().Value("message").Equal("user already joined")
}

func TestRemoveMemberFromTeam(t *testing.T) {
Expand All @@ -114,7 +114,7 @@ func TestRemoveMemberFromTeam(t *testing.T) {
assert.False(t, w.Members().HasUser(uId3))

o := Request(e, uId1.String(), request).Object()
o.Value("errors").Array().First().Object().Value("message").Equal("input: removeMemberFromTeam target user does not exist in the workspace")
o.Value("errors").Array().First().Object().Value("message").Equal("target user does not exist in the workspace")
}

func TestUpdateMemberOfTeam(t *testing.T) {
Expand All @@ -128,7 +128,6 @@ func TestUpdateMemberOfTeam(t *testing.T) {
Query: query,
}
Request(e, uId1.String(), request)

w, err = r.Workspace.FindByID(context.Background(), wId2)
assert.Nil(t, err)
assert.Equal(t, w.Members().User(uId3).Role, workspace.RoleWriter)
Expand All @@ -138,5 +137,5 @@ func TestUpdateMemberOfTeam(t *testing.T) {
Query: query,
}
o := Request(e, uId1.String(), request).Object()
o.Value("errors").Array().First().Object().Value("message").Equal("input: updateMemberOfTeam operation denied")
o.Value("errors").Array().First().Object().Value("message").Equal("operation denied")
}
22 changes: 13 additions & 9 deletions server/internal/adapter/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
contextUsecases ContextKey = "usecases"
contextMockAuth ContextKey = "mockauth"
contextCurrentHost ContextKey = "currenthost"
contextLang ContextKey = "lang"
)

var defaultLang = language.English
Expand All @@ -33,6 +34,10 @@ type AuthInfo struct {
EmailVerified *bool
}

func AttachLang(ctx context.Context, lang language.Tag) context.Context {
return context.WithValue(ctx, contextLang, lang)
}

func AttachUser(ctx context.Context, u *user.User) context.Context {
return context.WithValue(ctx, contextUser, u)
}
Expand Down Expand Up @@ -68,17 +73,16 @@ func Lang(ctx context.Context, lang *language.Tag) string {
return lang.String()
}

u := User(ctx)
if u == nil {
return defaultLang.String()
}

l := u.Lang()
if l.IsRoot() {
return defaultLang.String()
if v := ctx.Value(contextLang); v != nil {
if lang, ok := v.(language.Tag); ok {
if lang.IsRoot() {
return defaultLang.String()
}
return lang.String()
}
}

return l.String()
return defaultLang.String()
}

func Operator(ctx context.Context) *usecase.Operator {
Expand Down
96 changes: 96 additions & 0 deletions server/internal/adapter/context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package adapter

import (
"context"
"testing"

"github.com/reearth/reearthx/log"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
)

func TestAttachLang(t *testing.T) {
t.Run("Valid language tag", func(t *testing.T) {
ctx := context.Background()

lang := language.Japanese
newCtx := AttachLang(ctx, lang)

storedLang := newCtx.Value(contextLang)

assert.NotNil(t, storedLang, "Language should be stored in context")
assert.Equal(t, lang, storedLang, "Stored language should match the input")
})

t.Run("Default language (Und)", func(t *testing.T) {
ctx := context.Background()

lang := language.Und
newCtx := AttachLang(ctx, lang)

storedLang := newCtx.Value(contextLang)

assert.NotNil(t, storedLang, "Language should be stored in context")
assert.Equal(t, lang, storedLang, "Stored language should match the input")
})

t.Run("Context chaining", func(t *testing.T) {
ctx := context.Background()

lang1 := language.English
ctx1 := AttachLang(ctx, lang1)

lang2 := language.French
ctx2 := AttachLang(ctx1, lang2)

// confirm that the latest language is stored in the context
assert.Equal(t, lang2, ctx2.Value(contextLang), "Latest language should be stored in context")

// old context is not affected
assert.Equal(t, lang1, ctx1.Value(contextLang), "Old context should retain its value")
})
}

func TestLang(t *testing.T) {

// Default language for testing
defaultLang := language.English // or set it to whatever your defaultLang is

t.Run("Lang is provided and valid", func(t *testing.T) {
lang := language.Japanese
result := Lang(context.Background(), &lang)
assert.Equal(t, "ja", result)
})

t.Run("Lang is nil, context has valid lang", func(t *testing.T) {
lang := language.French
ctx := context.WithValue(context.Background(), contextLang, lang)
result := Lang(ctx, nil)
log.Infofc(ctx, "result: %s", result)
assert.Equal(t, "fr", result)
})

t.Run("Lang is nil, context lang is empty", func(t *testing.T) {
ctx := context.WithValue(context.Background(), contextLang, language.Make(""))
result := Lang(ctx, nil)
assert.Equal(t, defaultLang.String(), result)
})

t.Run("Lang is nil, context has no lang", func(t *testing.T) {
result := Lang(context.Background(), nil)
assert.Equal(t, defaultLang.String(), result)
})

t.Run("Lang is root, context has no lang", func(t *testing.T) {
rootLang := language.Und
result := Lang(context.Background(), &rootLang)
assert.Equal(t, defaultLang.String(), result)
})

t.Run("Lang is french, context has no lang", func(t *testing.T) {
lang := language.French
result := Lang(context.Background(), &lang)
assert.Equal(t, lang.String(), result)
})

}
4 changes: 4 additions & 0 deletions server/internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/labstack/echo/v4/middleware"
"github.com/reearth/reearth/server/internal/adapter"
http2 "github.com/reearth/reearth/server/internal/adapter/http"

"github.com/reearth/reearth/server/internal/usecase/interactor"
"github.com/reearth/reearthx/appx"
"github.com/reearth/reearthx/log"
Expand Down Expand Up @@ -65,6 +66,7 @@ func initEcho(ctx context.Context, cfg *ServerConfig) *echo.Echo {
log.Infof("Using mock auth for local development")
wrapHandler = func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

ctx := r.Context()
ctx = adapter.AttachMockAuth(ctx, true)
next.ServeHTTP(w, r.WithContext(ctx))
Expand Down Expand Up @@ -115,6 +117,8 @@ func initEcho(ctx context.Context, cfg *ServerConfig) *echo.Echo {
AuthSrvUIDomain: cfg.Config.Host_Web,
}))

e.Use(AttachLanguageMiddleware)

// auth srv
authServer(ctx, e, &cfg.Config.AuthSrv, cfg.Repos)

Expand Down
77 changes: 69 additions & 8 deletions server/internal/app/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package app

import (
"context"
"errors"
"time"

"github.com/99designs/gqlgen/graphql"
Expand All @@ -14,7 +15,12 @@ import (
"github.com/reearth/reearth/server/internal/adapter"
"github.com/reearth/reearth/server/internal/adapter/gql"
"github.com/reearth/reearth/server/internal/app/config"
"github.com/reearth/reearth/server/pkg/i18n/message"
"github.com/reearth/reearth/server/pkg/verror"
"github.com/reearth/reearthx/log"
"github.com/vektah/gqlparser/v2/ast"
"github.com/vektah/gqlparser/v2/gqlerror"
"golang.org/x/text/language"
)

const (
Expand All @@ -24,6 +30,7 @@ const (
)

func GraphqlAPI(conf config.GraphQLConfig, dev bool) echo.HandlerFunc {

schema := gql.NewExecutableSchema(gql.Config{
Resolvers: gql.NewResolver(),
})
Expand Down Expand Up @@ -55,15 +62,15 @@ func GraphqlAPI(conf config.GraphQLConfig, dev bool) echo.HandlerFunc {
// tracing
srv.Use(otelgqlgen.Middleware())

srv.SetErrorPresenter(
// show more detailed error messgage in debug mode
func(ctx context.Context, e error) *gqlerror.Error {
if dev {
return gqlerror.ErrorPathf(graphql.GetFieldContext(ctx).Path(), "%s", e.Error())
srv.SetErrorPresenter(func(ctx context.Context, e error) *gqlerror.Error {
defer func() {
if r := recover(); r != nil {
log.Errorfc(ctx, "panic recovered in error presenter: %v", r)
return
}
return graphql.DefaultErrorPresenter(ctx, e)
},
)
}()
return customErrorPresenter(ctx, e, dev)
})

// only enable middlewares in dev mode
if dev {
Expand All @@ -82,3 +89,57 @@ func GraphqlAPI(conf config.GraphQLConfig, dev bool) echo.HandlerFunc {
return nil
}
}

// customErrorPresenter handles custom GraphQL error presentation by converting various error types
// into localized GraphQL errors.
func customErrorPresenter(ctx context.Context, e error, devMode bool) *gqlerror.Error {
var graphqlErr *gqlerror.Error
var vError *verror.VError
lang := adapter.Lang(ctx, nil)

systemError := ""
if errors.As(e, &vError) {
if errMsg, ok := vError.ErrMsg[language.Make(lang)]; ok {
messageText := message.ApplyTemplate(ctx, errMsg.Message, vError.TemplateData, language.Make(lang))
graphqlErr = &gqlerror.Error{
Err: vError,
Message: messageText,
Extensions: map[string]interface{}{
"code": vError.GetErrCode(),
"message": messageText,
"description": message.ApplyTemplate(ctx, errMsg.Description, vError.TemplateData, language.Make(lang)),
},
}
}
if vError.Err != nil {
systemError = vError.Err.Error()
}
}

if graphqlErr == nil {
graphqlErr = graphql.DefaultErrorPresenter(ctx, e)
systemError = e.Error()
}

if graphqlErr.Extensions == nil {
graphqlErr.Extensions = make(map[string]interface{})
}

if devMode {
if fieldCtx := graphql.GetFieldContext(ctx); fieldCtx != nil {
graphqlErr.Path = fieldCtx.Path()
} else {
graphqlErr.Path = ast.Path{}
}

graphqlErr.Extensions["system_error"] = systemError
}

if systemError != "" {
log.Errorfc(ctx, "system error: %+v", e)
}

log.Warnfc(ctx, "graphqlErr: %+v", graphqlErr)

return graphqlErr
}
Loading

0 comments on commit 7387525

Please sign in to comment.