Skip to content

Commit cc6f00e

Browse files
authored
Merge pull request #2 from somatech1/feature/script_service
feature: support for new service type - script
2 parents ba0ccfe + 3d44a95 commit cc6f00e

File tree

11 files changed

+209
-100
lines changed

11 files changed

+209
-100
lines changed

apis/services/native/native.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import (
77
// ServiceAPI corresponds to the API that a native service must implement in
88
// its main structure.
99
type ServiceAPI interface {
10-
// Run must put the service in execution. It can block and wait for some
10+
// Start must put the service in execution. It can block and wait for some
1111
// signal to finish, which should be done in the Stop call. If it needs
1212
// a loop, i.e., execute forever, it must be done at the service level.
1313
// One way to do it is to use ctx.Done() to check if the loop needs to
1414
// finish.
15-
Run(ctx context.Context) error
15+
Start(ctx context.Context) error
1616

1717
// Stop should end the Run execution.
1818
Stop(ctx context.Context) error

apis/services/script/script.go

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package script
2+
3+
import (
4+
"context"
5+
)
6+
7+
// ServiceAPI corresponds to the API that a script service must implement in
8+
// its main structure.
9+
type ServiceAPI interface {
10+
// Run must be the service function where things happen. It is executed
11+
// only once and the service terminates.
12+
//
13+
// Services should avoid blocking this function since there are other
14+
// type of services for this purpose.
15+
Run(ctx context.Context) error
16+
17+
// Cleanup must clean or finish anything that was initialized or any resource
18+
// that need to be released.
19+
Cleanup(ctx context.Context) error
20+
}

components/definition/definition.go

+7-15
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package definition
22

33
import (
44
"context"
5-
"errors"
65
"fmt"
76
"strconv"
87
"strings"
@@ -18,7 +17,7 @@ import (
1817
// features it will have when executing.
1918
type Definitions struct {
2019
Name string `toml:"name" validate:"required"`
21-
Types []string `toml:"types" validate:"required,dive,service_type"`
20+
Types []string `toml:"types" validate:"required,single_script,no_duplicated_service,dive,service_type"`
2221
Version string `toml:"version" validate:"required,version"`
2322
Language string `toml:"language" validate:"required,oneof=go rust"`
2423
Product string `toml:"product" validate:"required,oneof=SDS SWORD PIKE"`
@@ -110,11 +109,15 @@ func (d *Definitions) Validate() error {
110109
return err
111110
}
112111

113-
if err := validate.RegisterValidationCtx("collector_name", validateCollectorName); err != nil {
112+
if err := validate.RegisterValidationCtx("service_type", validateServiceType); err != nil {
114113
return err
115114
}
116115

117-
if err := validate.RegisterValidationCtx("service_type", validateServiceType); err != nil {
116+
if err := validate.RegisterValidationCtx("single_script", ensureScriptTypeIsUnique); err != nil {
117+
return err
118+
}
119+
120+
if err := validate.RegisterValidationCtx("no_duplicated_service", checkDuplicatedServices); err != nil {
118121
return err
119122
}
120123

@@ -125,17 +128,6 @@ func (d *Definitions) Validate() error {
125128
return err
126129
}
127130

128-
types := make(map[string]bool)
129-
for _, t := range d.Types {
130-
_, ok := types[t]
131-
if !ok {
132-
types[t] = true
133-
}
134-
if ok {
135-
return errors.New("cannot have duplicated service types")
136-
}
137-
}
138-
139131
for _, svc := range d.externalServices {
140132
if err := svc.Validate(); err != nil {
141133
return err

components/definition/definition_test.go

+26-35
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,31 @@ func TestDefinitionsValidation(t *testing.T) {
1717
ErrorAssertion func(err error, msgAndArgs ...interface{}) bool
1818
CustomAssertion func(defs *Definitions)
1919
}{
20+
{
21+
Title: "should succeed with script type alone",
22+
TomlDefinitions: `
23+
name = "example"
24+
types = ["script"]
25+
version = "v1.0.0"
26+
language = "go"
27+
product = "SDS"
28+
`,
29+
ErrorAssertion: a.NoError,
30+
},
31+
{
32+
Title: "should not have script service type with other types",
33+
TomlDefinitions: `
34+
name = "example"
35+
types = ["grpc", "http", "script"]
36+
version = "v1.0.0"
37+
language = "go"
38+
product = "SDS"
39+
`,
40+
ErrorAssertion: a.Error,
41+
Expected: []string{
42+
"failed on the 'single_script' tag",
43+
},
44+
},
2045
{
2146
Title: "should not have duplicated service types",
2247
TomlDefinitions: `
@@ -28,7 +53,7 @@ product = "SDS"
2853
`,
2954
ErrorAssertion: a.Error,
3055
Expected: []string{
31-
"cannot have duplicated service types",
56+
"failed on the 'no_duplicated_service' tag",
3257
},
3358
},
3459
{
@@ -113,40 +138,6 @@ emitted_events = [ "VEHICLE_CREATED" ]
113138
DefsAssertion: a.NotNil,
114139
ErrorAssertion: a.Error,
115140
},
116-
{
117-
Title: "should fail with wrong tracing names",
118-
TomlDefinitions: `
119-
name = "service_test"
120-
types = ["grpc"]
121-
version = "v0.1.0"
122-
language = "go"
123-
product = "SDS"
124-
125-
[features.database]
126-
kind = "mongo"
127-
ttl = 0
128-
129-
[[features.tracing.collectors]]
130-
name = "error01"
131-
kind = "counter"
132-
description = "just a simple error counter"
133-
134-
[[features.tracing.collectors]]
135-
name = "error02 abc"
136-
kind = "counter"
137-
description = "another simple error counter"
138-
139-
[[features.tracing.collectors]]
140-
name = "error02-abc-EFG"
141-
kind = "counter"
142-
description = "another simple error counter"
143-
`,
144-
ErrorAssertion: a.Error,
145-
Expected: []string{
146-
"Key: 'Definitions.Features.Tracing.Collectors[1].Name' Error:Field validation for 'Name' failed on the 'collector_name' tag",
147-
"Key: 'Definitions.Features.Tracing.Collectors[2].Name' Error:Field validation for 'Name' failed on the 'collector_name' tag",
148-
},
149-
},
150141
{
151142
Title: "succeed with service custom settings",
152143
TomlDefinitions: `

components/definition/types.go

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ var (
1010
ServiceType_gRPC = CreateServiceType("grpc")
1111
ServiceType_HTTP = CreateServiceType("http")
1212
ServiceType_Native = CreateServiceType("native")
13+
ServiceType_Script = CreateServiceType("script")
1314
)
1415

1516
const (
@@ -71,6 +72,7 @@ func SupportedServiceTypes() []string {
7172
ServiceType_gRPC,
7273
ServiceType_HTTP,
7374
ServiceType_Native,
75+
ServiceType_Script,
7476
}
7577

7678
for _, t := range types {

components/definition/types_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ func TestSupportedServiceTypes(t *testing.T) {
1010
t.Run("should have all supported services", func(t *testing.T) {
1111
types := SupportedServiceTypes()
1212
a := assert.New(t)
13-
a.Equal(7, len(types))
13+
a.Equal(4, len(types))
1414
})
1515
}
1616

components/definition/validators.go

+32-21
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ package definition
33
import (
44
"context"
55
"regexp"
6+
"slices"
67
"strconv"
78
"strings"
8-
"unicode"
99

1010
"github.com/go-playground/validator/v10"
1111
)
@@ -14,26 +14,6 @@ func validateVersion(_ context.Context, fl validator.FieldLevel) bool {
1414
return ValidateVersion(fl.Field().String())
1515
}
1616

17-
// validateCollectorName checks if a name corresponds to a valid tracing
18-
// collector name, i.e., low letters or digits in snake case format.
19-
func validateCollectorName(_ context.Context, fl validator.FieldLevel) bool {
20-
if name := fl.Field().String(); name != "" {
21-
for _, c := range name {
22-
isChar := unicode.IsLetter(c) && unicode.IsLower(c)
23-
24-
if !isChar && !unicode.IsNumber(c) && c != '_' {
25-
return false
26-
}
27-
}
28-
29-
return true
30-
}
31-
32-
// We don't accept empty names.
33-
return false
34-
35-
}
36-
3717
// ValidateVersion is a helper function to validate the version format used by
3818
// services.
3919
func ValidateVersion(input string) bool {
@@ -76,3 +56,34 @@ func validatePort(port string) bool {
7656
_, err := strconv.ParseInt(port, 10, 32)
7757
return err == nil
7858
}
59+
60+
// ensureScriptTypeIsUnique validates if the 'script' service type is alone in
61+
// tht list.
62+
func ensureScriptTypeIsUnique(_ context.Context, fl validator.FieldLevel) bool {
63+
if list, ok := fl.Field().Interface().([]string); ok {
64+
index := slices.Index(list, ServiceType_Script.String())
65+
if index != -1 && len(list) > 1 {
66+
return false
67+
}
68+
}
69+
70+
return true
71+
}
72+
73+
// checkDuplicatedServices validates if the list contains duplicated elements.
74+
func checkDuplicatedServices(_ context.Context, fl validator.FieldLevel) bool {
75+
if list, ok := fl.Field().Interface().([]string); ok {
76+
types := make(map[string]bool)
77+
for _, t := range list {
78+
_, ok := types[t]
79+
if !ok {
80+
types[t] = true
81+
}
82+
if ok {
83+
return false
84+
}
85+
}
86+
}
87+
88+
return true
89+
}

components/options/script.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package options
2+
3+
import (
4+
"github.com/somatech1/mikros/components/definition"
5+
)
6+
7+
type ScriptServiceOptions struct{}
8+
9+
func (s *ScriptServiceOptions) Kind() definition.ServiceType {
10+
return definition.ServiceType_Script
11+
}

internal/services/native/native.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (s *Server) Run(_ context.Context, srv interface{}) error {
5050
s.svc = svc
5151

5252
// And put it to run.
53-
return svc.Run(s.ctx)
53+
return svc.Start(s.ctx)
5454
}
5555

5656
func (s *Server) Stop(ctx context.Context) error {

internal/services/script/script.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package script
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
loggerApi "github.com/somatech1/mikros/apis/logger"
8+
"github.com/somatech1/mikros/apis/services/script"
9+
"github.com/somatech1/mikros/components/definition"
10+
"github.com/somatech1/mikros/components/logger"
11+
"github.com/somatech1/mikros/components/plugin"
12+
)
13+
14+
type Server struct {
15+
svc script.ServiceAPI
16+
ctx context.Context
17+
cancel context.CancelFunc
18+
}
19+
20+
func New() *Server {
21+
return &Server{}
22+
}
23+
24+
func (s *Server) Name() string {
25+
return definition.ServiceType_Script.String()
26+
}
27+
28+
func (s *Server) Initialize(ctx context.Context, _ *plugin.ServiceOptions) error {
29+
cctx, cancel := context.WithCancel(ctx)
30+
31+
s.ctx = cctx
32+
s.cancel = cancel
33+
34+
return nil
35+
}
36+
37+
func (s *Server) Info() []loggerApi.Attribute {
38+
return []loggerApi.Attribute{
39+
logger.String("service.mode", definition.ServiceType_Native.String()),
40+
}
41+
}
42+
43+
func (s *Server) Run(_ context.Context, srv interface{}) error {
44+
svc, ok := srv.(script.ServiceAPI)
45+
if !ok {
46+
return errors.New("server object does not implement the script.ServiceAPI interface")
47+
}
48+
49+
// Holds a reference to the service, so we can stop it later.
50+
s.svc = svc
51+
52+
// And put it to run.
53+
return svc.Run(s.ctx)
54+
}
55+
56+
func (s *Server) Stop(ctx context.Context) error {
57+
s.cancel()
58+
return s.svc.Cleanup(ctx)
59+
}

0 commit comments

Comments
 (0)