Skip to content

Commit

Permalink
feat: add NATs discovery provider (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tochemey authored Sep 14, 2024
1 parent 0bb4e26 commit 9693d68
Show file tree
Hide file tree
Showing 25 changed files with 2,507 additions and 201 deletions.
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ linters-settings:
ignore-words:
- cancelled
- behaviour
- initialised

goheader:
template: |-
Expand Down
35 changes: 35 additions & 0 deletions discovery/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2024 Tochemey
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package discovery

import "errors"

var (
// ErrAlreadyInitialized is used when attempting to re-initialize the discovery provider
ErrAlreadyInitialized = errors.New("provider already initialized")
// ErrNotInitialized is used when the provider is not initialized
ErrNotInitialized = errors.New("provider not initialized")
// ErrAlreadyRegistered is used when attempting to re-register the provider
ErrAlreadyRegistered = errors.New("provider already registered")
// ErrNotRegistered is used when attempting to de-register the provider
ErrNotRegistered = errors.New("provider is not registered")
)
89 changes: 89 additions & 0 deletions discovery/nats/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* MIT License
*
* Copyright (c) 2022-2024 Tochemey
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package nats

import (
"fmt"
"strings"
"time"

"github.com/tochemey/gokv/internal/validation"
)

// Config represents the nats provider discoConfig
type Config struct {
// Server defines the nats server in the format nats://host:port
Server string
// Subject defines the custom NATS subject
Subject string
// Timeout defines the nodes discovery timeout
Timeout time.Duration
// MaxJoinAttempts denotes the maximum number of attempts to connect an existing NATs server
// Default to 5
MaxJoinAttempts int
// ReconnectWait sets the time to backoff after attempting a reconnect
// to a server that we were already connected to previously.
// Defaults to 2s.
ReconnectWait time.Duration
// Host specifies the host
Host string
// DiscoveryPort specifies the node discovery port
DiscoveryPort uint16
}

// Validate checks whether the given discovery configuration is valid
func (config Config) Validate() error {
return validation.New(validation.FailFast()).
AddValidator(validation.NewEmptyStringValidator("Server", config.Server)).
AddValidator(NewServerAddrValidator(config.Server)).
AddValidator(validation.NewEmptyStringValidator("Subject", config.Subject)).
AddValidator(validation.NewEmptyStringValidator("Host", config.Host)).
AddAssertion(config.DiscoveryPort > 0, "DiscoveryPort is invalid").
Validate()
}

// ServerAddrValidator helps validates the NATs server address
type ServerAddrValidator struct {
server string
}

// NewServerAddrValidator validates the nats server address
func NewServerAddrValidator(server string) validation.Validator {
return &ServerAddrValidator{server: server}
}

// Validate execute the validation code
func (x *ServerAddrValidator) Validate() error {
// make sure that the nats prefix is set in the server address
if !strings.HasPrefix(x.server, "nats") {
return fmt.Errorf("invalid nats server address: %s", x.server)
}

hostAndPort := strings.SplitN(x.server, "nats://", 2)[1]
return validation.NewTCPAddressValidator(hostAndPort).Validate()
}

// enforce compilation error
var _ validation.Validator = (*ServerAddrValidator)(nil)
48 changes: 48 additions & 0 deletions discovery/nats/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* MIT License
*
* Copyright (c) 2024 Tochemey
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package nats

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestConfig(t *testing.T) {
t.Run("With valid configuration", func(t *testing.T) {
config := Config{
Server: fmt.Sprintf("nats://%s", "127.0.0.1:234"),
Subject: "subject",
Host: "host",
DiscoveryPort: 123,
}
assert.NoError(t, config.Validate())
})
t.Run("With invalid configuration", func(t *testing.T) {
config := Config{}
assert.Error(t, config.Validate())
})
}
Loading

0 comments on commit 9693d68

Please sign in to comment.