Skip to content
Open

#3 #4

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a6162e2
remove error pkg
SyntaxErrorLineNULL Dec 10, 2024
17b9922
moved the errors into one package with interfaces
SyntaxErrorLineNULL Dec 10, 2024
1acb350
init Subscriber
SyntaxErrorLineNULL Dec 10, 2024
c660adc
message init
SyntaxErrorLineNULL Dec 10, 2024
9d89b23
message struct
SyntaxErrorLineNULL Dec 10, 2024
24022f2
add descriptions
SyntaxErrorLineNULL Dec 10, 2024
c38dea9
GetContainer method
SyntaxErrorLineNULL Dec 10, 2024
61d6266
GetHeader method
SyntaxErrorLineNULL Dec 10, 2024
b5d215c
Ack method
SyntaxErrorLineNULL Dec 10, 2024
3b8dbb7
Nak method
SyntaxErrorLineNULL Dec 10, 2024
04ec190
Respond method
SyntaxErrorLineNULL Dec 10, 2024
b41d0db
fix import
SyntaxErrorLineNULL Dec 10, 2024
4f04fc8
receive message
SyntaxErrorLineNULL Dec 10, 2024
9e3765a
unsubscribe
SyntaxErrorLineNULL Dec 10, 2024
9b59b59
check if subscriber is close
SyntaxErrorLineNULL Dec 10, 2024
d20a649
check subject and queue is empty
SyntaxErrorLineNULL Dec 10, 2024
bc9d1ba
init encode (message serialization)
SyntaxErrorLineNULL Dec 10, 2024
030b870
encode interface
SyntaxErrorLineNULL Dec 10, 2024
7823984
add json serialization lib
SyntaxErrorLineNULL Dec 10, 2024
669edb5
add request time, and add json fields
SyntaxErrorLineNULL Dec 10, 2024
d6a1c41
Subject and Header fields
SyntaxErrorLineNULL Dec 10, 2024
5dbcce9
encode test init
SyntaxErrorLineNULL Dec 10, 2024
b008b46
encode test init
SyntaxErrorLineNULL Dec 10, 2024
44046eb
validate message method
SyntaxErrorLineNULL Dec 10, 2024
08b1af3
check if message is empty and use valid message method
SyntaxErrorLineNULL Dec 10, 2024
9d282d5
get pkg for segmentio
SyntaxErrorLineNULL Dec 10, 2024
872228f
encode test cases(test decode)
SyntaxErrorLineNULL Dec 10, 2024
1ee37f8
update constructor
SyntaxErrorLineNULL Dec 10, 2024
2898670
encode interface
SyntaxErrorLineNULL Dec 10, 2024
06d7756
implement encode interface
SyntaxErrorLineNULL Dec 10, 2024
55322cb
add description
SyntaxErrorLineNULL Dec 10, 2024
73c783c
decode test case
SyntaxErrorLineNULL Dec 11, 2024
7a3efd0
empty message test case
SyntaxErrorLineNULL Dec 11, 2024
1fd002a
invalid json in nats message test
SyntaxErrorLineNULL Dec 11, 2024
bc0a99b
change method name
SyntaxErrorLineNULL Dec 11, 2024
fd42535
fix test
SyntaxErrorLineNULL Dec 11, 2024
e851731
refactoring message constructor
SyntaxErrorLineNULL Dec 12, 2024
ad048f9
fix encoder (constructor isn't use)
SyntaxErrorLineNULL Dec 12, 2024
43aad74
update encoder test
SyntaxErrorLineNULL Dec 12, 2024
857effd
add nats message in message object
SyntaxErrorLineNULL Dec 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions error.go

This file was deleted.

2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/nats-io/gnatsd v1.4.1
github.com/nats-io/nats-server v1.4.1
github.com/nats-io/nats.go v1.37.0
github.com/segmentio/encoding v0.4.1
github.com/stretchr/testify v1.9.0
)

Expand All @@ -16,6 +17,7 @@ require (
github.com/nats-io/nkeys v0.4.7 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/segmentio/asm v1.1.3 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/sys v0.16.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
github.com/segmentio/encoding v0.4.1 h1:KLGaLSW0jrmhB58Nn4+98spfvPvmo4Ci1P/WIQ9wn7w=
github.com/segmentio/encoding v0.4.1/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
Expand Down
119 changes: 119 additions & 0 deletions pkg/encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package pkg

import (
"bytes"
"errors"

"github.com/nats-io/nats.go"
"github.com/segmentio/encoding/json"
)

// Decoder defines an interface for decoding a custom Message into a NATS message.
// It abstracts the process of converting application-level message representations
// into the NATS message format to facilitate communication within a NATS-based system.
type Decoder interface {
// Decode is responsible for transforming a custom Message into a NATS message.
// The resulting NATS message contains serialized data and metadata, formatted
// for compatibility with the NATS messaging system.
// Returns the NATS message if successful or an error if the transformation fails.
Decode(msg *Message) (*nats.Msg, error)
}

// Encoding implements the Decoder interface, providing functionality to convert
// a custom Message structure into a format compatible with the NATS messaging system.
// It uses JSON serialization to encode the Message and populate the fields of a NATS message.
type Encoding struct{}

// Decode converts a custom Message into a NATS message.
// This method serializes the Message's content, including its payload and metadata,
// into JSON format and assigns the serialized data to the NATS message fields.
func (Encoding) Decode(msg *Message) (*nats.Msg, error) {
// Checks if the provided Message object is nil.
// If it is nil, an error is returned immediately, indicating the message is empty.
if msg == nil {
return nil, errors.New("message is empty")
}

// Validates the provided Message object using its Validate method.
// If validation fails, the error is returned, indicating what went wrong.
if err := msg.Validate(); err != nil {
return nil, err
}

// Initializes a new buffer that will temporarily hold the JSON-encoded data.
// This buffer acts as the target for the encoding process.
buffer := new(bytes.Buffer)

// Creates a JSON encoder that writes to the buffer.
// The encoder is responsible for serializing the Message into JSON format.
encoder := json.NewEncoder(buffer)
// Attempts to encode the custom Message into JSON and store it in the buffer.
// If an error occurs during encoding, the function immediately returns the error.
if err := encoder.Encode(msg); err != nil {
// Returns an error indicating that the encoding process failed.
return nil, err
}

// Constructs a NATS message using the encoded data and message metadata.
// The Subject, Data, and Header fields of the NATS message are populated
// using the corresponding fields from the custom Message.
return &nats.Msg{Subject: msg.Subject, Data: buffer.Bytes(), Header: nats.Header(msg.Header)}, nil
}

// Encoder defines an interface for encoding a NATS message into a custom Message.
// It abstracts the process of converting NATS-level message representations
// into application-specific Message structures to support interoperability.
type Encoder interface {
// Encode is responsible for transforming a NATS message into a custom Message.
// The resulting custom Message contains data and metadata extracted from the NATS message,
// formatted for use within the application's messaging system.
// Returns the custom Message if successful or an error if the transformation fails.
Encode(msg *nats.Msg) (*Message, error)
}

// Encode implements the Encoder interface to convert a NATS message into a custom Message structure.
// This function processes the raw data and metadata from the NATS message and transforms it into
// a structured custom Message that is compatible with the application's requirements.
func (Encoding) Encode(msg *nats.Msg) (*Message, error) {
// Checks if the provided Message object is nil.
// If it is nil, an error is returned immediately, indicating the message is empty.
if msg == nil {
return nil, errors.New("message is empty")
}

// Initializes a new buffer that will temporarily hold the JSON-encoded data.
// This buffer acts as the target for the encoding process.
buffer := new(bytes.Buffer)

// Writes the raw data from the NATS message into the buffer.
// This step ensures the data is available for the JSON decoder.
// If writing to the buffer fails, the function returns the encountered error.
if _, err := buffer.Write(msg.Data); err != nil {
return nil, err
}

// Initializes a JSON decoder to parse the data within the buffer.
// The decoder converts the JSON-formatted data into a Go structure.
decoder := json.NewDecoder(buffer)

// Declares a variable to hold the result of decoding the JSON data.
// This variable will store the custom Message constructed from the decoded input.
var message Message
// Decodes the JSON data from the buffer into the custom Message structure.
// If the decoding process encounters an error, the function immediately returns it,
// signaling that the input data is not valid JSON or does not match the expected format.
if err := decoder.Decode(&message); err != nil {
return nil, err
}

// Associates the original NATS message with the newly decoded custom Message.
// This linkage allows the application to reference the original NATS message
// when working with the custom Message structure.
message.message = msg

// Constructs and returns a pointer to the custom Message populated with the decoded data.
// The NewMessage function ensures all fields, such as RequestID, Container, and Header,
// are initialized correctly. This completes the transformation of the NATS message into
// the custom Message format required by the application.
return &message, nil
}
178 changes: 178 additions & 0 deletions pkg/encode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package pkg

import (
"bytes"
"fmt"
"testing"
"time"

"github.com/nats-io/nats.go"
"github.com/segmentio/encoding/json"
"github.com/stretchr/testify/assert"
)

func TestEncodingDecode(t *testing.T) {
// Test the Decode function of the Encoding type for different message scenarios.
// This test case is designed to validate that the Decode function correctly handles various
// cases, including valid messages, nil messages, and messages with invalid headers.
// The tests check for correct error handling, message transformation, and matching expected output.
cases := []struct {
name string
input *Message
expectError bool
expectedMsg *nats.Msg
}{
{name: "Valid Message", input: &Message{Subject: "test.subject", Header: Header{"key": {"value1", "value2"}}, RequestID: "12345", Container: []byte("test payload")}, expectError: false, expectedMsg: &nats.Msg{Subject: "test.subject", Header: nats.Header{"key": []string{"value1", "value2"}}, Data: json.RawMessage(`{"subject":"test.subject","header":{"key":["value1","value2"]},"request_id":"12345","container":"dGVzdCBwYXlsb2Fk"}`)}},
{name: "Nil Message", input: nil, expectError: true, expectedMsg: nil},
{name: "Invalid Header", input: &Message{Subject: "test.subject", Header: Header{"key": nil}, RequestID: "12345", Container: []byte("test payload")}, expectError: true, expectedMsg: nil},
{name: "Empty Header", input: &Message{Subject: "test.subject", RequestID: "12345", Container: []byte("test payload")}, expectError: false, expectedMsg: &nats.Msg{Subject: "test.subject", Data: json.RawMessage(`{"subject":"test.subject","request_id":"12345","container":"dGVzdCBwYXlsb2Fk"}`)}},
}

// Declare a variable of type Encoding. This variable will hold the instance of the encoding/decoding functionality
// that will be tested in the test cases.
var encoder Encoding

// Iterate over the slice of test cases, 'cases', to execute the test logic for each test case individually.
// This loop allows us to run the same set of assertions on different inputs and expected outputs, ensuring
// that the function works across a variety of scenarios.
for _, tt := range cases {
// Start a subtest for the current test case, using 'tt.name' as the name of the subtest.
// This ensures that each test case is isolated, and we can easily identify which test case failed
// if any errors occur. The subtest allows for independent results and better debugging.
t.Run(tt.name, func(t *testing.T) {
// Call the 'Decode' method of the encoder, passing the 'input' from the current test case (tt.input).
// This is the core functionality being tested, where the input data is processed by the decoder.
// The result of the decoding operation is stored in the 'result' variable, and any errors that occur
// are captured in the 'err' variable.
result, err := encoder.Decode(tt.input)

// Check if the current test case expects an error. The 'expectError' field in the test case struct
// indicates whether an error is expected during the 'Decode' function call. If 'expectError' is true,
// the assertions will verify that the function behaves as expected by producing an error.
if tt.expectError {
// Assert that an error has occurred during the decoding process. This assertion ensures that the error
// returned from 'Decode' is not nil when the test case expects an error. If no error occurs, the test will fail.
assert.Error(t, err, "Expected an error but got none for test case: "+tt.name)
// Assert that the 'result' is nil when an error is expected. This checks that the decoder does not
// return a valid result when an error occurs. If a valid result is returned when an error was expected,
// the test will fail.
assert.Nil(t, result, "Expected nil result but got a valid result for test case: "+tt.name)
} else {
// Assert that no error occurred during the decoding process. This check ensures that the function behaves as expected
// when an error is not anticipated. If an error is returned when it shouldn't be, the test will fail.
assert.NoError(t, err, fmt.Sprintf("Expected no error but got one for test case: %s", tt.name))

// Assert that the 'result' is not nil when no error is expected. This checks that a valid decoded result
// is returned by the 'Decode' function. If the result is nil, the test will fail.
assert.NotNil(t, result, fmt.Sprintf("Expected a valid result but got nil for test case: %s", tt.name))

// Assert that the 'Subject' field of the result matches the expected value from the test case.
// This checks that the 'Decode' function correctly transfers the 'Subject' from the input to the output.
assert.Equal(t, tt.expectedMsg.Subject, result.Subject, fmt.Sprintf("Subject mismatch for test case: %s", tt.name))

// Assert that the 'Header' field of the result matches the expected value from the test case.
// This ensures that the 'Header' is properly decoded and matches the expected format.
assert.Equal(t, tt.expectedMsg.Header, result.Header, fmt.Sprintf("Header mismatch for test case: %s", tt.name))

// Create a buffer to hold the expected JSON-encoded data. This buffer will store the result of encoding the input
// message into a JSON format for comparison against the 'Data' field of the result.
var expectedData bytes.Buffer

// Encoder the input message into the 'expectedData' buffer. This step ensures that the expected JSON data
// is correctly generated before comparing it with the actual 'Data' field in the result.
err = json.NewEncoder(&expectedData).Encode(tt.input)

// Assert that no error occurred during the encoding process. If an error is returned, it will indicate a
// problem with the encoding step, causing the test to fail.
assert.NoError(t, err, fmt.Sprintf("Expected no encoding error but got one for test case: %s", tt.name))

// Assert that the 'Data' field of the result matches the expected JSON data generated above.
// This verifies that the 'Decode' function correctly processes the input and generates the expected data.
assert.Equal(t, expectedData.Bytes(), result.Data, fmt.Sprintf("Data mismatch for test case: %s", tt.name))
}
})
}
}

func TestEncode(t *testing.T) {
// Declare a variable of type Encoding. This variable will hold the instance of the encoding/decoding functionality
// that will be tested in the test cases.
var encoder Encoding
// Assert that the encoder variable is not nil.
// This check ensures that the Encoding instance has been properly initialized or is available for use.
// A nil value here would indicate a critical setup issue, rendering the test invalid.
assert.NotNil(t, encoder, "Expected encoder to be non-nil before running tests")

// ValidNATSMessage tests the ability of the encoder to accurately decode and re-encode
// a custom Message to and from a NATS message. This test ensures the encoder handles
// valid data without errors and produces consistent results through the round-trip conversion.
// It validates the functionality of the Encoder and Decode methods under normal conditions.
t.Run("ValidNATSMessage", func(t *testing.T) {
// Define a custom Message with valid test data for encoding and decoding.
// This Message includes a subject, request ID, container data, and a current timestamp.
message := &Message{Subject: "test.subject", Header: nil, RequestID: "6557162e-7a05-4840-a350-12a6f67e2b3b", Container: Container(`{"id": 1,"name": "Tammi Watson"}`), RequestTime: time.Now()}

// Attempt to decode the custom Message into a NATS message.
// The Decode method transforms the application-level message into NATS-compatible format.
natsMsg, err := encoder.Decode(message)
// Assert that no error occurred during the decoding process.
// This verifies that valid messages are handled without issues during decoding.
assert.NoError(t, err, "Expected no error during decode operation")
// Assert that the resulting NATS message is not nil, indicating successful decoding.
assert.NotNil(t, natsMsg, "Expected a non-nil NATS message after decoding")

// Attempt to encode the NATS message back into the custom Message format.
// This ensures the round-trip transformation is consistent and correct.
res, err := encoder.Encode(natsMsg)
// Assert that no error occurred during the encoding process.
// This confirms that valid NATS messages are handled correctly during encoding.
assert.NoError(t, err, "Expected no error during encode operation")
// Assert that the re-encoded Message is not nil, verifying successful encoding.
assert.NotNil(t, res, "Expected a non-nil custom message after encoding")
// Verify that the request ID of the re-encoded Message matches the original custom Message.
// This confirms that the request ID field is preserved correctly during the round-trip transformation.
assert.Equal(t, message.RequestID, res.RequestID, "Expected the RequestID to match the original message")
// Verify that the subject of the re-encoded Message matches the original custom Message.
// This ensures that the subject field remains consistent after the transformation.
assert.Equal(t, message.Subject, res.Subject, "Expected the Subject to match the original message")
// Verify that the container of the re-encoded Message matches the original custom Message.
// This validates that the container data is preserved during encoding and decoding.
assert.Equal(t, message.Container, res.Container, "Expected the Container to match the original message")
// Verify that the header of the re-encoded Message matches the original custom Message.
// This confirms that the header field is not altered during the encoding and decoding processes.
assert.Equal(t, message.Header, res.Header, "Expected the Header to match the original message")
})

// EmptyMessage tests the behavior of the Encoder method when it is called with a nil message.
// It verifies that the method appropriately handles invalid input by returning an error.
// This test ensures that the Encoder method is robust against edge cases and fails gracefully when given invalid data.
t.Run("EmptyMessage", func(t *testing.T) {
// Attempt to encode a nil message.
// This simulates a scenario where the Encoder method is called without a valid Message object.
// The expected behavior is for the method to return an error, indicating the invalid input.
_, err := encoder.Encode(nil)

// Assert that an error is returned during the encoding process when given a nil message.
// This ensures that the method validates input correctly and avoids processing invalid data.
assert.Error(t, err, "Expected an error when encoding a nil message")
})

// InvalidJSONInNATSMessage tests the behavior of the Encoder method when the input NATS message contains invalid JSON data.
// It verifies that the method correctly identifies the invalid JSON and returns an appropriate error.
// This test ensures the robustness of the Encoder method in handling improperly formatted input data.
t.Run("InvalidJSONInNATSMessage", func(t *testing.T) {
// Create a NATS message containing invalid JSON in the Data field.
// This simulates a scenario where the message being processed has malformed JSON data,
// which the Encoder method should be able to detect and reject.
invalidNATSMsg := &nats.Msg{Data: []byte(`invalid json`)}

// Attempt to encode the NATS message containing invalid JSON.
// The Encoder method is expected to fail and return an error in this scenario.
_, err := encoder.Encode(invalidNATSMsg)

// Assert that an error is returned during the encoding process.
// This ensures the method correctly validates the JSON format in the input message
// and fails gracefully when invalid data is provided.
assert.Error(t, err, "Expected an error when encoding a NATS message with invalid JSON data")
})
}
Loading