From 5ccec14189aba1abd7f1f319935a53da5f017df7 Mon Sep 17 00:00:00 2001 From: Igor Lazarev Date: Sun, 24 Nov 2024 19:55:45 +0300 Subject: [PATCH] handling panics in public getters --- examples/basic/di/container.go | 42 +++++++---- examples/basic/di/internal/bitset.go | 2 +- examples/basic/di/internal/container.go | 40 ++++------- .../basic/di/internal/lookup/container.go | 2 +- internal/app/command_generate.go | 6 ++ internal/config/parameters.go | 15 +++- internal/di/definitions.go | 30 ++++++-- internal/di/definitions_parser.go | 25 +++++++ internal/di/definitions_parser_test.go | 8 +-- internal/di/factories_generator.go | 5 +- internal/di/factories_generator_test.go | 2 +- internal/di/factories_parser.go | 67 ++++++++++++++---- internal/di/file_generation.go | 36 +++++++++- internal/di/file_generation_test.go | 5 +- internal/di/generator.go | 26 +++++-- internal/di/internal_container_generator.go | 42 +++++++++-- internal/di/public_container_generator.go | 61 ++++++++++++++-- .../factories_container_with_services.txt | 2 +- .../import_alias_internal_container.txt | 7 +- .../import_alias_public_container.txt | 22 +++++- .../separate_container_internal_container.txt | 19 ++++- .../separate_container_public_file.txt | 22 +++++- .../single_container_with_basic_types.txt | 49 ++++++++++--- ...iner_with_basic_types_public_container.txt | 70 +++++++++++++++---- ...ntainer_with_closer_internal_container.txt | 7 +- ...r_with_getters_only_internal_container.txt | 7 +- ...ontainer_with_getters_only_public_file.txt | 22 +++++- ...with_service_setter_internal_container.txt | 7 +- ...es_from_one_package_internal_container.txt | 13 +++- 29 files changed, 530 insertions(+), 131 deletions(-) diff --git a/examples/basic/di/container.go b/examples/basic/di/container.go index f0ecd03..dc36277 100644 --- a/examples/basic/di/container.go +++ b/examples/basic/di/container.go @@ -1,5 +1,5 @@ // Code generated by DIGEN; DO NOT EDIT. -// This file was generated by Dependency Injection Container Generator rev-230ce04-dirty. +// This file was generated by Dependency Injection Container Generator rev-4ca64d5. // See docs at https://github.com/strider2038/digen package di @@ -10,6 +10,8 @@ import ( httphandler "basic/app/httphandler" internal "basic/di/internal" "context" + "errors" + "fmt" "net/http" "sync" ) @@ -39,28 +41,34 @@ func NewContainer(config config.Params, injectors ...Injector) (*Container, erro return c, nil } -func (c *Container) Server(ctx context.Context) (*http.Server, error) { +func (c *Container) Server(ctx context.Context) (s *http.Server, err error) { c.mu.Lock() defer c.mu.Unlock() - s := c.c.Server(ctx) - err := c.c.Error() - if err != nil { - return nil, err - } + defer func() { + if recovered := recover(); recovered != nil { + err = newRecoveredError(recovered, c.c.Error()) + } + }() + + s = c.c.Server(ctx) + err = c.c.Error() return s, err } -func (c *Container) FindEntityHandler(ctx context.Context) (*httphandler.FindEntity, error) { +func (c *Container) FindEntityHandler(ctx context.Context) (s *httphandler.FindEntity, err error) { c.mu.Lock() defer c.mu.Unlock() - s := c.c.API().(*internal.APIContainer).FindEntityHandler(ctx) - err := c.c.Error() - if err != nil { - return nil, err - } + defer func() { + if recovered := recover(); recovered != nil { + err = newRecoveredError(recovered, c.c.Error()) + } + }() + + s = c.c.API().(*internal.APIContainer).FindEntityHandler(ctx) + err = c.c.Error() return s, err } @@ -79,3 +87,11 @@ func (c *Container) Close() { c.c.Close() } + +func newRecoveredError(recovered any, err error) error { + r := fmt.Errorf("panic: %v", recovered) + if err != nil { + return errors.Join(r, fmt.Errorf("previous error: %w", err)) + } + return r +} diff --git a/examples/basic/di/internal/bitset.go b/examples/basic/di/internal/bitset.go index 89dac7c..fb6eeed 100644 --- a/examples/basic/di/internal/bitset.go +++ b/examples/basic/di/internal/bitset.go @@ -1,5 +1,5 @@ // Code generated by DIGEN; DO NOT EDIT. -// This file was generated by Dependency Injection Container Generator rev-230ce04-dirty. +// This file was generated by Dependency Injection Container Generator rev-4ca64d5. // See docs at https://github.com/strider2038/digen package internal diff --git a/examples/basic/di/internal/container.go b/examples/basic/di/internal/container.go index 89c8705..5cd4d0f 100644 --- a/examples/basic/di/internal/container.go +++ b/examples/basic/di/internal/container.go @@ -1,5 +1,5 @@ // Code generated by DIGEN; DO NOT EDIT. -// This file was generated by Dependency Injection Container Generator rev-230ce04-dirty. +// This file was generated by Dependency Injection Container Generator rev-4ca64d5. // See docs at https://github.com/strider2038/digen package internal @@ -89,9 +89,7 @@ func (c *Container) Config(ctx context.Context) config.Params { func (c *Container) Logger(ctx context.Context) *log.Logger { if !c.init.IsSet(1) && c.err == nil { c.logger = factories.CreateLogger(ctx, c) - if c.err == nil { - c.init.Set(1) - } + c.init.Set(1) } return c.logger } @@ -99,9 +97,7 @@ func (c *Container) Logger(ctx context.Context) *log.Logger { func (c *Container) DB(ctx context.Context) *sql.DB { if !c.init.IsSet(2) && c.err == nil { c.db = factories.CreateDB(ctx, c) - if c.err == nil { - c.init.Set(2) - } + c.init.Set(2) } return c.db } @@ -109,9 +105,7 @@ func (c *Container) DB(ctx context.Context) *sql.DB { func (c *Container) Server(ctx context.Context) *http.Server { if !c.init.IsSet(3) && c.err == nil { c.server = factories.CreateServer(ctx, c) - if c.err == nil { - c.init.Set(3) - } + c.init.Set(3) } return c.server } @@ -123,9 +117,7 @@ func (c *Container) Params() lookup.ParamsContainer { func (c *ParamsContainer) ServerPort(ctx context.Context) int { if !c.init.IsSet(4) && c.err == nil { c.serverPort = factories.CreateParamsServerPort(ctx, c) - if c.err == nil { - c.init.Set(4) - } + c.init.Set(4) } return c.serverPort } @@ -133,9 +125,7 @@ func (c *ParamsContainer) ServerPort(ctx context.Context) int { func (c *ParamsContainer) ServerHost(ctx context.Context) string { if !c.init.IsSet(5) && c.err == nil { c.serverHost = factories.CreateParamsServerHost(ctx, c) - if c.err == nil { - c.init.Set(5) - } + c.init.Set(5) } return c.serverHost } @@ -143,9 +133,7 @@ func (c *ParamsContainer) ServerHost(ctx context.Context) string { func (c *ParamsContainer) RequestTimeout(ctx context.Context) time.Duration { if !c.init.IsSet(6) && c.err == nil { c.requestTimeout = factories.CreateParamsRequestTimeout(ctx, c) - if c.err == nil { - c.init.Set(6) - } + c.init.Set(6) } return c.requestTimeout } @@ -157,9 +145,7 @@ func (c *Container) API() lookup.APIContainer { func (c *APIContainer) FindEntityHandler(ctx context.Context) *httphandler.FindEntity { if !c.init.IsSet(7) && c.err == nil { c.findEntityHandler = factories.CreateAPIFindEntityHandler(ctx, c) - if c.err == nil { - c.init.Set(7) - } + c.init.Set(7) } return c.findEntityHandler } @@ -171,9 +157,7 @@ func (c *Container) UseCases() lookup.UseCaseContainer { func (c *UseCaseContainer) FindEntity(ctx context.Context) *usecase.FindEntity { if !c.init.IsSet(8) && c.err == nil { c.findEntity = factories.CreateUseCasesFindEntity(ctx, c) - if c.err == nil { - c.init.Set(8) - } + c.init.Set(8) } return c.findEntity } @@ -185,19 +169,19 @@ func (c *Container) Repositories() lookup.RepositoryContainer { func (c *RepositoryContainer) EntityRepository(ctx context.Context) domain.EntityRepository { if !c.init.IsSet(9) && c.err == nil { c.entityRepository = factories.CreateRepositoriesEntityRepository(ctx, c) - if c.err == nil { - c.init.Set(9) - } + c.init.Set(9) } return c.entityRepository } func (c *Container) SetConfig(s config.Params) { c.config = s + c.init.Set(0) } func (c *RepositoryContainer) SetEntityRepository(s domain.EntityRepository) { c.entityRepository = s + c.init.Set(9) } func (c *Container) Close() { diff --git a/examples/basic/di/internal/lookup/container.go b/examples/basic/di/internal/lookup/container.go index a58798d..1cc8230 100644 --- a/examples/basic/di/internal/lookup/container.go +++ b/examples/basic/di/internal/lookup/container.go @@ -1,5 +1,5 @@ // Code generated by DIGEN; DO NOT EDIT. -// This file was generated by Dependency Injection Container Generator rev-230ce04-dirty. +// This file was generated by Dependency Injection Container Generator rev-4ca64d5. // See docs at https://github.com/strider2038/digen package lookup diff --git a/internal/app/command_generate.go b/internal/app/command_generate.go index 4a045f1..2b992d7 100644 --- a/internal/app/command_generate.go +++ b/internal/app/command_generate.go @@ -22,6 +22,12 @@ func newGenerator(options *Options, params *config.Parameters) *di.Generator { Version: options.Version, BuildTime: options.BuildTime, Logger: terminalLogger{}, + ErrorWrapping: di.ErrorHandling{ + Package: params.ErrorHandling.Package, + WrapPackage: params.ErrorHandling.WrapPackage, + WrapFunction: params.ErrorHandling.WrapFunction, + Verb: params.ErrorHandling.Verb, + }, } } diff --git a/internal/config/parameters.go b/internal/config/parameters.go index 64fb2b3..2460def 100644 --- a/internal/config/parameters.go +++ b/internal/config/parameters.go @@ -13,14 +13,22 @@ import ( var errInvalidPath = errors.New("invalid path") type Parameters struct { - Version string `json:"version" yaml:"version"` - Container Container `json:"container" yaml:"container"` + Version string `json:"version" yaml:"version"` + Container Container `json:"container" yaml:"container"` + ErrorHandling ErrorHandling `json:"errorHandling" yaml:"errorHandling"` } type Container struct { Dir string `json:"dir" yaml:"dir"` } +type ErrorHandling struct { + Package string `json:"package" yaml:"package"` + WrapPackage string `json:"wrapPackage" yaml:"wrapPackage"` + WrapFunction string `json:"function" yaml:"function"` + Verb string `json:"verb" yaml:"verb"` +} + func Load() (*Parameters, error) { config := newConfig() err := config.ReadInConfig() @@ -93,6 +101,9 @@ func initConfig(config *viper.Viper) error { config.Set("version", Version) config.Set("container.dir", dir) + config.Set("errorHandling.package", "errors") + config.Set("errorHandling.wrapPackage", "fmt") + config.Set("errorHandling.wrapFunction", "Errorf") err = config.SafeWriteConfig() if err != nil { return errors.Errorf("write config: %w", err) diff --git a/internal/di/definitions.go b/internal/di/definitions.go index a22c8cb..f726f73 100644 --- a/internal/di/definitions.go +++ b/internal/di/definitions.go @@ -13,6 +13,7 @@ type RootContainerDefinition struct { Imports map[string]*ImportDefinition Services []*ServiceDefinition Containers []*ContainerDefinition + Factories map[string]*FactoryDefinition } func (c RootContainerDefinition) Type(definition TypeDefinition) func(statement *jen.Statement) { @@ -150,13 +151,28 @@ func (d TypeDefinition) String() string { return s.String() } -type FactoryFile struct { - Imports map[string]*ImportDefinition - Services []string +type FactoryDefinitions struct { + Imports map[string]*ImportDefinition + Factories map[string]*FactoryDefinition } -type Tags struct { - Options []string - FactoryFilename string - PublicName string +func NewFactoryDefinitions() *FactoryDefinitions { + return &FactoryDefinitions{ + Imports: map[string]*ImportDefinition{}, + Factories: map[string]*FactoryDefinition{}, + } +} + +func (d *FactoryDefinitions) merge(df *FactoryDefinitions) { + for k, v := range df.Factories { + d.Factories[k] = v + } + for k, v := range df.Imports { + d.Imports[k] = v + } +} + +type FactoryDefinition struct { + Name string + ReturnsError bool } diff --git a/internal/di/definitions_parser.go b/internal/di/definitions_parser.go index 0060c52..b5819dc 100644 --- a/internal/di/definitions_parser.go +++ b/internal/di/definitions_parser.go @@ -68,6 +68,7 @@ func (p *DefinitionsParser) parseContainerAST(file *ast.File) (*RootContainerDef Imports: imports, Services: services, Containers: containers, + Factories: make(map[string]*FactoryDefinition, 0), } return definition, nil @@ -314,6 +315,12 @@ func parseTypeDefinition(expr ast.Expr) (TypeDefinition, error) { return TypeDefinition{}, errors.Errorf("%w: %s", ErrUnexpectedType, "parse type") } +type Tags struct { + Options []string + FactoryFilename string + PublicName string +} + func parseFieldTags(field *ast.Field) Tags { if field.Tag == nil || len(field.Tag.Value) == 0 { return Tags{} @@ -335,3 +342,21 @@ func validateInternalContainer(container *ast.StructType) error { return nil } + +type FuncDeclaration struct { + ReturnsErr bool +} + +func parseFuncDeclaration(decl *ast.FuncDecl) (FuncDeclaration, error) { + declaration := FuncDeclaration{} + + if decl.Type.Results != nil { + for _, field := range decl.Type.Results.List { + if id, ok := field.Type.(*ast.Ident); ok && id.Name == "error" { + declaration.ReturnsErr = true + } + } + } + + return declaration, nil +} diff --git a/internal/di/definitions_parser_test.go b/internal/di/definitions_parser_test.go index a9ab1b3..119007b 100644 --- a/internal/di/definitions_parser_test.go +++ b/internal/di/definitions_parser_test.go @@ -58,7 +58,7 @@ func TestParseContainerFromSource(t *testing.T) { } func TestParseFactoryFromSource(t *testing.T) { - factory, err := di.ParseFactoryFromSource(testFactorySource) + factory, err := di.ParseFactoriesFromSource(testFactorySource) require.NoError(t, err) require.NotNil(t, factory) @@ -66,9 +66,9 @@ func TestParseFactoryFromSource(t *testing.T) { assert.NotNil(t, factory.Imports["domain"]) assert.NotNil(t, factory.Imports["httpadapter"]) assert.NotNil(t, factory.Imports["inmemory"]) - assert.Contains(t, factory.Services, "EntityRepository") - assert.Contains(t, factory.Services, "UseCase") - assert.Contains(t, factory.Services, "Handler") + assert.Contains(t, factory.Factories, "EntityRepository") + assert.Contains(t, factory.Factories, "UseCase") + assert.Contains(t, factory.Factories, "Handler") } func assertExpectedContainerImports(t *testing.T, imports map[string]*di.ImportDefinition) { diff --git a/internal/di/factories_generator.go b/internal/di/factories_generator.go index 4d4cd29..0b2c4db 100644 --- a/internal/di/factories_generator.go +++ b/internal/di/factories_generator.go @@ -47,7 +47,10 @@ func (g *FactoriesGenerator) Generate() ([]*File, error) { jen.Id("ctx").Qual("context", "Context"), jen.Id("c").Qual(g.params.packageName(LookupPackage), "Container"), ). - Do(g.container.Type(service.Type)). + Params( + jen.Do(g.container.Type(service.Type)), + jen.Error(), + ). Block(jen.Panic(jen.Lit("not implemented"))), ) } diff --git a/internal/di/factories_generator_test.go b/internal/di/factories_generator_test.go index 8eb164b..740d976 100644 --- a/internal/di/factories_generator_test.go +++ b/internal/di/factories_generator_test.go @@ -21,7 +21,7 @@ func TestFactoriesGenerator_Generate(t *testing.T) { Name: "Container", Package: "testpkg", Imports: map[string]*di.ImportDefinition{ - "domain": {Path: `"example.com/test/domain"`}, + "domain": {Path: "example.com/test/domain"}, }, Services: []*di.ServiceDefinition{ { diff --git a/internal/di/factories_parser.go b/internal/di/factories_parser.go index 2f586de..bdd55e6 100644 --- a/internal/di/factories_parser.go +++ b/internal/di/factories_parser.go @@ -2,38 +2,81 @@ package di import ( "go/ast" + "io/fs" + "path/filepath" "strings" "github.com/muonsoft/errors" ) -func ParseFactoryFromSource(source string) (*FactoryFile, error) { +func ParseFactoriesFromDir(dir string) (*FactoryDefinitions, error) { + definitions := NewFactoryDefinitions() + if !isFileExist(dir) { + return definitions, nil + } + + err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if !strings.HasSuffix(path, ".go") { + return nil + } + file, err := parseFile(path) + if err != nil { + return err + } + df, err := parseFactoriesAST(file) + if err != nil { + return err + } + definitions.merge(df) + + return nil + }) + if err != nil { + return nil, errors.Errorf("walk dir %q: %w", dir, err) + } + + return definitions, nil +} + +func ParseFactoriesFromSource(source string) (*FactoryDefinitions, error) { file, err := parseSource(source) if err != nil { return nil, err } - return parseFactoryAST(file) + return parseFactoriesAST(file) } -func parseFactoryAST(file *ast.File) (*FactoryFile, error) { +func parseFactoriesAST(file *ast.File) (*FactoryDefinitions, error) { imports, err := parseImports(file) if err != nil { return nil, errors.Errorf("parse imports: %w", err) } - var services []string + factories := make(map[string]*FactoryDefinition, len(file.Scope.Objects)) for name, object := range file.Scope.Objects { - if object.Kind == ast.Fun && strings.HasPrefix(name, "Create") { - services = append(services, strings.TrimPrefix(name, "Create")) + if funcDecl, ok := object.Decl.(*ast.FuncDecl); ok && object.Kind == ast.Fun && strings.HasPrefix(name, "Create") { + f, err := parseFuncDeclaration(funcDecl) + if err != nil { + return nil, errors.Errorf("parse func declaration: %w", err) + } + factoryName := strings.TrimPrefix(name, "Create") + factories[factoryName] = &FactoryDefinition{ + Name: factoryName, + ReturnsError: f.ReturnsErr, + } } } - factory := &FactoryFile{ - Imports: imports, - Services: services, - } - - return factory, nil + return &FactoryDefinitions{ + Imports: imports, + Factories: factories, + }, nil } diff --git a/internal/di/file_generation.go b/internal/di/file_generation.go index 03aa943..be09f32 100644 --- a/internal/di/file_generation.go +++ b/internal/di/file_generation.go @@ -4,6 +4,7 @@ import ( "strconv" "strings" + "github.com/dave/jennifer/jen" "github.com/muonsoft/errors" ) @@ -16,7 +17,32 @@ func GenerateDefinitionsContainerFile() *File { } type GenerationParameters struct { - RootPackage string + RootPackage string + ErrorHandling ErrorHandling +} + +type ErrorHandling struct { + Package string + WrapPackage string + WrapFunction string + Verb string +} + +func (w ErrorHandling) Defaults() ErrorHandling { + if w.Package == "" { + w.Package = "errors" + } + if w.WrapPackage == "" { + w.WrapPackage = "fmt" + } + if w.WrapFunction == "" { + w.WrapFunction = "Errorf" + } + if w.Verb == "" { + w.Verb = "%w" + } + + return w } func (params *GenerationParameters) rootPackageName() string { @@ -31,6 +57,14 @@ func (params *GenerationParameters) packageName(packageType PackageType) string return strings.Trim(strconv.Quote(params.RootPackage+"/"+packageDirs[packageType]), `"`) } +func (params *GenerationParameters) wrapError(message string, errorIdentifier jen.Code) *jen.Statement { + path := params.ErrorHandling.WrapPackage + funcName := params.ErrorHandling.WrapFunction + verb := params.ErrorHandling.Verb + + return jen.Qual(path, funcName).Call(jen.Lit(message+": "+verb), errorIdentifier) +} + func GenerateFiles(container *RootContainerDefinition, params GenerationParameters) ([]*File, error) { return NewFileGenerator(container, params).GenerateFiles() } diff --git a/internal/di/file_generation_test.go b/internal/di/file_generation_test.go index 748a1ae..d6d17ed 100644 --- a/internal/di/file_generation_test.go +++ b/internal/di/file_generation_test.go @@ -408,7 +408,7 @@ func TestGenerate(t *testing.T) { require.GreaterOrEqual(t, len(files), 3) assert.Contains( t, string(files[2].Content), - `func (c *Container) APIRouter(ctx context.Context) (http.Handler, error) {`, + `func (c *Container) APIRouter(ctx context.Context) (s http.Handler, err error) {`, ) }, }, @@ -428,7 +428,8 @@ func TestGenerate(t *testing.T) { } files, err := di.GenerateFiles(test.container, di.GenerationParameters{ - RootPackage: "example.com/test/di", + RootPackage: "example.com/test/di", + ErrorHandling: di.ErrorHandling{}.Defaults(), }) require.NoError(t, err) diff --git a/internal/di/generator.go b/internal/di/generator.go index 84a985f..a45a0c1 100644 --- a/internal/di/generator.go +++ b/internal/di/generator.go @@ -11,12 +11,14 @@ import ( const ( definitionsFile = "internal/definitions/container.go" + factoriesDir = "internal/factories" ) type Generator struct { - BaseDir string - ModulePath string - Logger Logger + BaseDir string + ModulePath string + Logger Logger + ErrorWrapping ErrorHandling Version string BuildTime string @@ -56,11 +58,19 @@ func (g *Generator) Generate() error { container, err := ParseDefinitionsFromFile(g.BaseDir + "/" + definitionsFile) if err != nil { - return err + return errors.Errorf("parse definitions file: %w", err) } - g.Logger.Info("definitions container", definitionsFile, "successfully parsed") + factories, err := ParseFactoriesFromDir(g.BaseDir + "/" + factoriesDir) + if err != nil { + return errors.Errorf("parse factories: %w", err) + } + if len(factories.Factories) > 0 { + container.Factories = factories.Factories + g.Logger.Info("factories", factoriesDir, "successfully parsed") + } + if err := g.generateContainerFiles(container); err != nil { return err } @@ -104,7 +114,8 @@ func (g *Generator) init() error { func (g *Generator) generateContainerFiles(container *RootContainerDefinition) error { params := GenerationParameters{ - RootPackage: g.RootPackage(), + RootPackage: g.RootPackage(), + ErrorHandling: g.ErrorWrapping.Defaults(), } files, err := GenerateFiles(container, params) @@ -132,7 +143,8 @@ func (g *Generator) generateContainerFiles(container *RootContainerDefinition) e func (g *Generator) generateFactoriesFiles(container *RootContainerDefinition) error { params := GenerationParameters{ - RootPackage: g.RootPackage(), + RootPackage: g.RootPackage(), + ErrorHandling: g.ErrorWrapping.Defaults(), } manager := NewFactoriesGenerator(container, g.BaseDir, params) files, err := manager.Generate() diff --git a/internal/di/internal_container_generator.go b/internal/di/internal_container_generator.go index c106da4..89aacc5 100644 --- a/internal/di/internal_container_generator.go +++ b/internal/di/internal_container_generator.go @@ -168,14 +168,46 @@ func (g *InternalContainerGenerator) generateInitBlock(service *ServiceDefinitio ) } - return jen.If(jen.Op("!").Id("c").Dot("init").Dot("IsSet").Call(jen.Lit(service.ID)). - Op("&&").Op("c").Dot("err").Op("==").Nil()). - Block( + factoryName := strings.Title(service.Prefix) + service.Title() + + withError := true + if factory, exists := g.container.Factories[factoryName]; exists { + withError = factory.ReturnsError + } + + block := make([]jen.Code, 0, 2) + if withError { + block = append(block, + jen.Var().Id("err").Error(), + jen. + List( + jen.Id("c").Dot(strcase.ToLowerCamel(service.Name)), + jen.Id("err"), + ). + Op("="). + Qual(g.params.packageName(FactoriesPackage), "Create"+factoryName). + Call(jen.Id("ctx"), jen.Id("c")), + jen.If(jen.Id("err").Op("!=").Nil()).Block( + jen.Id("c").Dot("SetError").Call( + g.params.wrapError("create "+factoryName, jen.Id("err")), + ), + ), + ) + } else { + block = append(block, jen.Id("c").Dot(strcase.ToLowerCamel(service.Name)).Op("="). - Qual(g.params.packageName(FactoriesPackage), "Create"+strings.Title(service.Prefix)+service.Title()). + Qual(g.params.packageName(FactoriesPackage), "Create"+factoryName). Call(jen.Id("ctx"), jen.Id("c")), - jen.Id("c").Dot("init").Dot("Set").Call(jen.Lit(service.ID)), ) + } + + block = append(block, + jen.Id("c").Dot("init").Dot("Set").Call(jen.Lit(service.ID)), + ) + + return jen.If(jen.Op("!").Id("c").Dot("init").Dot("IsSet").Call(jen.Lit(service.ID)). + Op("&&").Op("c").Dot("err").Op("==").Nil()). + Block(block...) } func (g *InternalContainerGenerator) generateSetters() { diff --git a/internal/di/public_container_generator.go b/internal/di/public_container_generator.go index 71216f7..1ee1df5 100644 --- a/internal/di/public_container_generator.go +++ b/internal/di/public_container_generator.go @@ -49,10 +49,12 @@ func (g *PublicContainerGenerator) Generate() (*File, error) { methods := make([]jen.Code, 0, 2*len(g.container.Services)) arguments := make([]jen.Code, 0, 1) argumentSetters := make([]jen.Code, 0) + gettersCount := 0 for _, service := range g.container.Services { if service.IsPublic { methods = append(methods, jen.Line(), jen.Line(), g.generateGetter(service, nil)) + gettersCount++ } if service.HasSetter { methods = append(methods, jen.Line(), jen.Line(), g.generateSetter(service, nil)) @@ -67,6 +69,7 @@ func (g *PublicContainerGenerator) Generate() (*File, error) { for _, service := range attachedContainer.Services { if service.IsPublic { methods = append(methods, jen.Line(), jen.Line(), g.generateGetter(service, attachedContainer)) + gettersCount++ } if service.HasSetter { methods = append(methods, jen.Line(), jen.Line(), g.generateSetter(service, attachedContainer)) @@ -88,6 +91,9 @@ func (g *PublicContainerGenerator) Generate() (*File, error) { g.file.Add(g.generateConstructor(arguments, argumentSetters)) g.file.Add(methods...) g.file.Add(jen.Line(), g.generateCloser()) + if gettersCount > 0 { + g.file.Add(g.generateErrorHandler()...) + } return g.file.GetFile() } @@ -133,15 +139,27 @@ func (g *PublicContainerGenerator) generateGetter(service *ServiceDefinition, co jen.Id("ctx").Qual("context", "Context"), ). Params( - jen.Do(g.container.Type(service.Type)), - jen.Error(), + jen.Id("s").Do(g.container.Type(service.Type)), + jen.Err().Error(), ). Block( jen.Id("c").Dot("mu").Dot("Lock").Call(), jen.Defer().Id("c").Dot("mu").Dot("Unlock").Call(), jen.Line(), - jen.Id("s").Op(":=").Id("c").Dot("c").Do(g.containerPath(container)).Dot(service.Title()).Call(jen.Id("ctx")), - jen.Id("err").Op(":=").Id("c").Dot("c").Dot("Error").Call(), + jen.Defer().Func().Call().Block( + jen.If( + jen.Id("recovered").Op(":=").Recover(), + jen.Id("recovered").Op("!=").Nil(), + ).Block( + jen.Err().Op("=").Id("newRecoveredError").Call( + jen.Id("recovered"), + jen.Id("c").Dot("c").Dot("Error").Call(), + ), + ), + ).Call(), + jen.Line(), + jen.Id("s").Op("=").Id("c").Dot("c").Do(g.containerPath(container)).Dot(service.Title()).Call(jen.Id("ctx")), + jen.Id("err").Op("=").Id("c").Dot("c").Dot("Error").Call(), jen.Line(), jen.Return(jen.Id("s"), jen.Err()), ) @@ -206,3 +224,38 @@ func (g *PublicContainerGenerator) containerPath(container *ContainerDefinition) ) } } + +func (g *PublicContainerGenerator) generateErrorHandler() []jen.Code { + errPackage := g.params.ErrorHandling.Package + wrapPackage := g.params.ErrorHandling.WrapPackage + errFunc := g.params.ErrorHandling.WrapFunction + errVerb := g.params.ErrorHandling.Verb + + return []jen.Code{ + jen.Line(), + jen.Func().Id("newRecoveredError"). + Params( + jen.Id("recovered").Any(), + jen.Id("err").Error(), + ). + Error(). + Block( + jen.Id("r").Op(":=").Qual(wrapPackage, errFunc).Call( + jen.Lit("panic: %v"), + jen.Id("recovered"), + ), + jen.If(jen.Err().Op("!=").Nil()).Block( + jen.Return( + jen.Qual(errPackage, "Join").Call( + jen.Id("r"), + jen.Qual(wrapPackage, errFunc).Call( + jen.Lit("previous error: "+errVerb), + jen.Err(), + ), + ), + ), + ), + jen.Return(jen.Id("r")), + ), + } +} diff --git a/internal/di/testdata/generation/factories_container_with_services.txt b/internal/di/testdata/generation/factories_container_with_services.txt index 5c4d827..75b14a5 100644 --- a/internal/di/testdata/generation/factories_container_with_services.txt +++ b/internal/di/testdata/generation/factories_container_with_services.txt @@ -6,6 +6,6 @@ import ( domain "example.com/test/domain" ) -func CreateServiceName(ctx context.Context, c lookup.Container) *domain.Service { +func CreateServiceName(ctx context.Context, c lookup.Container) (*domain.Service, error) { panic("not implemented") } diff --git a/internal/di/testdata/generation/import_alias_internal_container.txt b/internal/di/testdata/generation/import_alias_internal_container.txt index 792f244..ecc33f4 100644 --- a/internal/di/testdata/generation/import_alias_internal_container.txt +++ b/internal/di/testdata/generation/import_alias_internal_container.txt @@ -4,6 +4,7 @@ import ( "context" factories "example.com/test/di/internal/factories" httpadapter "example.com/test/infrastructure/api/http" + "fmt" ) type Container struct { @@ -34,7 +35,11 @@ func (c *Container) SetError(err error) { func (c *Container) ServiceName(ctx context.Context) *httpadapter.ServiceHandler { if !c.init.IsSet(0) && c.err == nil { - c.serviceName = factories.CreateServiceName(ctx, c) + var err error + c.serviceName, err = factories.CreateServiceName(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create ServiceName: %w", err)) + } c.init.Set(0) } return c.serviceName diff --git a/internal/di/testdata/generation/import_alias_public_container.txt b/internal/di/testdata/generation/import_alias_public_container.txt index cfe6215..1ad1486 100644 --- a/internal/di/testdata/generation/import_alias_public_container.txt +++ b/internal/di/testdata/generation/import_alias_public_container.txt @@ -2,8 +2,10 @@ package di import ( "context" + "errors" internal "example.com/test/di/internal" httpadapter "example.com/test/infrastructure/api/http" + "fmt" "sync" ) @@ -30,12 +32,18 @@ func NewContainer(injectors ...Injector) (*Container, error) { return c, nil } -func (c *Container) ServiceName(ctx context.Context) (*httpadapter.ServiceHandler, error) { +func (c *Container) ServiceName(ctx context.Context) (s *httpadapter.ServiceHandler, err error) { c.mu.Lock() defer c.mu.Unlock() - s := c.c.ServiceName(ctx) - err := c.c.Error() + defer func() { + if recovered := recover(); recovered != nil { + err = newRecoveredError(recovered, c.c.Error()) + } + }() + + s = c.c.ServiceName(ctx) + err = c.c.Error() return s, err } @@ -46,3 +54,11 @@ func (c *Container) Close() { c.c.Close() } + +func newRecoveredError(recovered any, err error) error { + r := fmt.Errorf("panic: %v", recovered) + if err != nil { + return errors.Join(r, fmt.Errorf("previous error: %w", err)) + } + return r +} diff --git a/internal/di/testdata/generation/separate_container_internal_container.txt b/internal/di/testdata/generation/separate_container_internal_container.txt index 84e44d7..0f7b13f 100644 --- a/internal/di/testdata/generation/separate_container_internal_container.txt +++ b/internal/di/testdata/generation/separate_container_internal_container.txt @@ -5,6 +5,7 @@ import ( factories "example.com/test/di/internal/factories" lookup "example.com/test/di/internal/lookup" domain "example.com/test/domain" + "fmt" ) type Container struct { @@ -46,7 +47,11 @@ type InternalContainerType struct { func (c *Container) TopService(ctx context.Context) *domain.Service { if !c.init.IsSet(0) && c.err == nil { - c.topService = factories.CreateTopService(ctx, c) + var err error + c.topService, err = factories.CreateTopService(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create TopService: %w", err)) + } c.init.Set(0) } return c.topService @@ -58,7 +63,11 @@ func (c *Container) InternalContainerName() lookup.InternalContainerType { func (c *InternalContainerType) FirstService(ctx context.Context) *domain.Service { if !c.init.IsSet(1) && c.err == nil { - c.firstService = factories.CreateInternalContainerTypeFirstService(ctx, c) + var err error + c.firstService, err = factories.CreateInternalContainerTypeFirstService(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create InternalContainerTypeFirstService: %w", err)) + } c.init.Set(1) } return c.firstService @@ -66,7 +75,11 @@ func (c *InternalContainerType) FirstService(ctx context.Context) *domain.Servic func (c *InternalContainerType) SecondService(ctx context.Context) *domain.Service { if !c.init.IsSet(2) && c.err == nil { - c.secondService = factories.CreateInternalContainerTypeSecondService(ctx, c) + var err error + c.secondService, err = factories.CreateInternalContainerTypeSecondService(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create InternalContainerTypeSecondService: %w", err)) + } c.init.Set(2) } return c.secondService diff --git a/internal/di/testdata/generation/separate_container_public_file.txt b/internal/di/testdata/generation/separate_container_public_file.txt index 5246966..491098a 100644 --- a/internal/di/testdata/generation/separate_container_public_file.txt +++ b/internal/di/testdata/generation/separate_container_public_file.txt @@ -2,8 +2,10 @@ package di import ( "context" + "errors" internal "example.com/test/di/internal" domain "example.com/test/domain" + "fmt" "sync" ) @@ -32,12 +34,18 @@ func NewContainer(requiredService *domain.Service, injectors ...Injector) (*Cont return c, nil } -func (c *Container) FirstService(ctx context.Context) (*domain.Service, error) { +func (c *Container) FirstService(ctx context.Context) (s *domain.Service, err error) { c.mu.Lock() defer c.mu.Unlock() - s := c.c.InternalContainerName().(*internal.InternalContainerType).FirstService(ctx) - err := c.c.Error() + defer func() { + if recovered := recover(); recovered != nil { + err = newRecoveredError(recovered, c.c.Error()) + } + }() + + s = c.c.InternalContainerName().(*internal.InternalContainerType).FirstService(ctx) + err = c.c.Error() return s, err } @@ -56,3 +64,11 @@ func (c *Container) Close() { c.c.Close() } + +func newRecoveredError(recovered any, err error) error { + r := fmt.Errorf("panic: %v", recovered) + if err != nil { + return errors.Join(r, fmt.Errorf("previous error: %w", err)) + } + return r +} diff --git a/internal/di/testdata/generation/single_container_with_basic_types.txt b/internal/di/testdata/generation/single_container_with_basic_types.txt index 7e5c80a..b5ebbfe 100644 --- a/internal/di/testdata/generation/single_container_with_basic_types.txt +++ b/internal/di/testdata/generation/single_container_with_basic_types.txt @@ -3,6 +3,7 @@ package internal import ( "context" factories "example.com/test/di/internal/factories" + "fmt" "net/url" "time" ) @@ -42,7 +43,11 @@ func (c *Container) SetError(err error) { func (c *Container) StringOption(ctx context.Context) string { if !c.init.IsSet(0) && c.err == nil { - c.stringOption = factories.CreateStringOption(ctx, c) + var err error + c.stringOption, err = factories.CreateStringOption(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create StringOption: %w", err)) + } c.init.Set(0) } return c.stringOption @@ -50,7 +55,11 @@ func (c *Container) StringOption(ctx context.Context) string { func (c *Container) StringPointer(ctx context.Context) *string { if !c.init.IsSet(1) && c.err == nil { - c.stringPointer = factories.CreateStringPointer(ctx, c) + var err error + c.stringPointer, err = factories.CreateStringPointer(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create StringPointer: %w", err)) + } c.init.Set(1) } return c.stringPointer @@ -58,7 +67,11 @@ func (c *Container) StringPointer(ctx context.Context) *string { func (c *Container) IntOption(ctx context.Context) int { if !c.init.IsSet(2) && c.err == nil { - c.intOption = factories.CreateIntOption(ctx, c) + var err error + c.intOption, err = factories.CreateIntOption(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create IntOption: %w", err)) + } c.init.Set(2) } return c.intOption @@ -66,7 +79,11 @@ func (c *Container) IntOption(ctx context.Context) int { func (c *Container) TimeOption(ctx context.Context) time.Time { if !c.init.IsSet(3) && c.err == nil { - c.timeOption = factories.CreateTimeOption(ctx, c) + var err error + c.timeOption, err = factories.CreateTimeOption(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create TimeOption: %w", err)) + } c.init.Set(3) } return c.timeOption @@ -74,7 +91,11 @@ func (c *Container) TimeOption(ctx context.Context) time.Time { func (c *Container) DurationOption(ctx context.Context) time.Duration { if !c.init.IsSet(4) && c.err == nil { - c.durationOption = factories.CreateDurationOption(ctx, c) + var err error + c.durationOption, err = factories.CreateDurationOption(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create DurationOption: %w", err)) + } c.init.Set(4) } return c.durationOption @@ -82,7 +103,11 @@ func (c *Container) DurationOption(ctx context.Context) time.Duration { func (c *Container) URLOption(ctx context.Context) url.URL { if !c.init.IsSet(5) && c.err == nil { - c.urloption = factories.CreateURLOption(ctx, c) + var err error + c.urloption, err = factories.CreateURLOption(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create URLOption: %w", err)) + } c.init.Set(5) } return c.urloption @@ -90,7 +115,11 @@ func (c *Container) URLOption(ctx context.Context) url.URL { func (c *Container) IntSlice(ctx context.Context) []int { if !c.init.IsSet(6) && c.err == nil { - c.intSlice = factories.CreateIntSlice(ctx, c) + var err error + c.intSlice, err = factories.CreateIntSlice(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create IntSlice: %w", err)) + } c.init.Set(6) } return c.intSlice @@ -98,7 +127,11 @@ func (c *Container) IntSlice(ctx context.Context) []int { func (c *Container) StringMap(ctx context.Context) map[string]string { if !c.init.IsSet(7) && c.err == nil { - c.stringMap = factories.CreateStringMap(ctx, c) + var err error + c.stringMap, err = factories.CreateStringMap(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create StringMap: %w", err)) + } c.init.Set(7) } return c.stringMap diff --git a/internal/di/testdata/generation/single_container_with_basic_types_public_container.txt b/internal/di/testdata/generation/single_container_with_basic_types_public_container.txt index a8d7278..8421468 100644 --- a/internal/di/testdata/generation/single_container_with_basic_types_public_container.txt +++ b/internal/di/testdata/generation/single_container_with_basic_types_public_container.txt @@ -2,7 +2,9 @@ package di import ( "context" + "errors" internal "example.com/test/di/internal" + "fmt" "sync" "time" ) @@ -30,52 +32,82 @@ func NewContainer(injectors ...Injector) (*Container, error) { return c, nil } -func (c *Container) StringOption(ctx context.Context) (string, error) { +func (c *Container) StringOption(ctx context.Context) (s string, err error) { c.mu.Lock() defer c.mu.Unlock() - s := c.c.StringOption(ctx) - err := c.c.Error() + defer func() { + if recovered := recover(); recovered != nil { + err = newRecoveredError(recovered, c.c.Error()) + } + }() + + s = c.c.StringOption(ctx) + err = c.c.Error() return s, err } -func (c *Container) StringPointer(ctx context.Context) (*string, error) { +func (c *Container) StringPointer(ctx context.Context) (s *string, err error) { c.mu.Lock() defer c.mu.Unlock() - s := c.c.StringPointer(ctx) - err := c.c.Error() + defer func() { + if recovered := recover(); recovered != nil { + err = newRecoveredError(recovered, c.c.Error()) + } + }() + + s = c.c.StringPointer(ctx) + err = c.c.Error() return s, err } -func (c *Container) IntOption(ctx context.Context) (int, error) { +func (c *Container) IntOption(ctx context.Context) (s int, err error) { c.mu.Lock() defer c.mu.Unlock() - s := c.c.IntOption(ctx) - err := c.c.Error() + defer func() { + if recovered := recover(); recovered != nil { + err = newRecoveredError(recovered, c.c.Error()) + } + }() + + s = c.c.IntOption(ctx) + err = c.c.Error() return s, err } -func (c *Container) TimeOption(ctx context.Context) (time.Time, error) { +func (c *Container) TimeOption(ctx context.Context) (s time.Time, err error) { c.mu.Lock() defer c.mu.Unlock() - s := c.c.TimeOption(ctx) - err := c.c.Error() + defer func() { + if recovered := recover(); recovered != nil { + err = newRecoveredError(recovered, c.c.Error()) + } + }() + + s = c.c.TimeOption(ctx) + err = c.c.Error() return s, err } -func (c *Container) DurationOption(ctx context.Context) (time.Duration, error) { +func (c *Container) DurationOption(ctx context.Context) (s time.Duration, err error) { c.mu.Lock() defer c.mu.Unlock() - s := c.c.DurationOption(ctx) - err := c.c.Error() + defer func() { + if recovered := recover(); recovered != nil { + err = newRecoveredError(recovered, c.c.Error()) + } + }() + + s = c.c.DurationOption(ctx) + err = c.c.Error() return s, err } @@ -86,3 +118,11 @@ func (c *Container) Close() { c.c.Close() } + +func newRecoveredError(recovered any, err error) error { + r := fmt.Errorf("panic: %v", recovered) + if err != nil { + return errors.Join(r, fmt.Errorf("previous error: %w", err)) + } + return r +} diff --git a/internal/di/testdata/generation/single_container_with_closer_internal_container.txt b/internal/di/testdata/generation/single_container_with_closer_internal_container.txt index 9fc7b63..0e55c27 100644 --- a/internal/di/testdata/generation/single_container_with_closer_internal_container.txt +++ b/internal/di/testdata/generation/single_container_with_closer_internal_container.txt @@ -4,6 +4,7 @@ import ( "context" factories "example.com/test/di/internal/factories" sql "example.com/test/sql" + "fmt" ) type Container struct { @@ -34,7 +35,11 @@ func (c *Container) SetError(err error) { func (c *Container) Connection(ctx context.Context) sql.Connection { if !c.init.IsSet(0) && c.err == nil { - c.connection = factories.CreateConnection(ctx, c) + var err error + c.connection, err = factories.CreateConnection(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create Connection: %w", err)) + } c.init.Set(0) } return c.connection diff --git a/internal/di/testdata/generation/single_container_with_getters_only_internal_container.txt b/internal/di/testdata/generation/single_container_with_getters_only_internal_container.txt index 3393f13..e2d1426 100644 --- a/internal/di/testdata/generation/single_container_with_getters_only_internal_container.txt +++ b/internal/di/testdata/generation/single_container_with_getters_only_internal_container.txt @@ -4,6 +4,7 @@ import ( "context" factories "example.com/test/di/internal/factories" domain "example.com/test/domain" + "fmt" ) type Container struct { @@ -34,7 +35,11 @@ func (c *Container) SetError(err error) { func (c *Container) ServiceName(ctx context.Context) *domain.Service { if !c.init.IsSet(0) && c.err == nil { - c.serviceName = factories.CreateServiceName(ctx, c) + var err error + c.serviceName, err = factories.CreateServiceName(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create ServiceName: %w", err)) + } c.init.Set(0) } return c.serviceName diff --git a/internal/di/testdata/generation/single_container_with_getters_only_public_file.txt b/internal/di/testdata/generation/single_container_with_getters_only_public_file.txt index 062262b..855f8a4 100644 --- a/internal/di/testdata/generation/single_container_with_getters_only_public_file.txt +++ b/internal/di/testdata/generation/single_container_with_getters_only_public_file.txt @@ -2,8 +2,10 @@ package di import ( "context" + "errors" internal "example.com/test/di/internal" domain "example.com/test/domain" + "fmt" "sync" ) @@ -30,12 +32,18 @@ func NewContainer(injectors ...Injector) (*Container, error) { return c, nil } -func (c *Container) ServiceName(ctx context.Context) (*domain.Service, error) { +func (c *Container) ServiceName(ctx context.Context) (s *domain.Service, err error) { c.mu.Lock() defer c.mu.Unlock() - s := c.c.ServiceName(ctx) - err := c.c.Error() + defer func() { + if recovered := recover(); recovered != nil { + err = newRecoveredError(recovered, c.c.Error()) + } + }() + + s = c.c.ServiceName(ctx) + err = c.c.Error() return s, err } @@ -46,3 +54,11 @@ func (c *Container) Close() { c.c.Close() } + +func newRecoveredError(recovered any, err error) error { + r := fmt.Errorf("panic: %v", recovered) + if err != nil { + return errors.Join(r, fmt.Errorf("previous error: %w", err)) + } + return r +} diff --git a/internal/di/testdata/generation/single_container_with_service_setter_internal_container.txt b/internal/di/testdata/generation/single_container_with_service_setter_internal_container.txt index bffa611..05ddd7d 100644 --- a/internal/di/testdata/generation/single_container_with_service_setter_internal_container.txt +++ b/internal/di/testdata/generation/single_container_with_service_setter_internal_container.txt @@ -4,6 +4,7 @@ import ( "context" factories "example.com/test/di/internal/factories" domain "example.com/test/domain" + "fmt" ) type Container struct { @@ -34,7 +35,11 @@ func (c *Container) SetError(err error) { func (c *Container) ServiceName(ctx context.Context) *domain.Service { if !c.init.IsSet(0) && c.err == nil { - c.serviceName = factories.CreateServiceName(ctx, c) + var err error + c.serviceName, err = factories.CreateServiceName(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create ServiceName: %w", err)) + } c.init.Set(0) } return c.serviceName diff --git a/internal/di/testdata/generation/two_services_from_one_package_internal_container.txt b/internal/di/testdata/generation/two_services_from_one_package_internal_container.txt index a7f9e5a..e28e384 100644 --- a/internal/di/testdata/generation/two_services_from_one_package_internal_container.txt +++ b/internal/di/testdata/generation/two_services_from_one_package_internal_container.txt @@ -4,6 +4,7 @@ import ( "context" factories "example.com/test/di/internal/factories" domain "example.com/test/domain" + "fmt" ) type Container struct { @@ -35,7 +36,11 @@ func (c *Container) SetError(err error) { func (c *Container) FirstService(ctx context.Context) *domain.Service { if !c.init.IsSet(0) && c.err == nil { - c.firstService = factories.CreateFirstService(ctx, c) + var err error + c.firstService, err = factories.CreateFirstService(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create FirstService: %w", err)) + } c.init.Set(0) } return c.firstService @@ -43,7 +48,11 @@ func (c *Container) FirstService(ctx context.Context) *domain.Service { func (c *Container) SecondService(ctx context.Context) *domain.Service { if !c.init.IsSet(1) && c.err == nil { - c.secondService = factories.CreateSecondService(ctx, c) + var err error + c.secondService, err = factories.CreateSecondService(ctx, c) + if err != nil { + c.SetError(fmt.Errorf("create SecondService: %w", err)) + } c.init.Set(1) } return c.secondService