From 19d28913dbfe20e049f4fc4a29ae12f0bd7c11cf Mon Sep 17 00:00:00 2001 From: Gabriel Cataldo Date: Fri, 15 Dec 2023 10:26:35 -0300 Subject: [PATCH] Finish documentation and README --- README.md | 400 +++++++++++++++++++++++++++++++++++++- _example/consumer/main.go | 108 ++++++++++ _example/producer/main.go | 150 ++++++++++++++ go.mod | 4 +- gopher-sqs.png | Bin 0 -> 30641 bytes sqs/consumer.go | 77 ++++++-- sqs/log.go | 4 +- sqs/main_test.go | 152 ++++++++------- sqs/message.go | 6 +- sqs/message_test.go | 2 +- sqs/option/consumer.go | 6 + sqs/option/producer.go | 9 +- sqs/producer.go | 22 +-- sqs/queue.go | 4 +- sqs/queue_test.go | 4 +- 15 files changed, 834 insertions(+), 114 deletions(-) create mode 100644 _example/consumer/main.go create mode 100644 _example/producer/main.go create mode 100644 gopher-sqs.png diff --git a/README.md b/README.md index 0d8d30b..25b0a34 100644 --- a/README.md +++ b/README.md @@ -1 +1,399 @@ -# go-aws-sqs \ No newline at end of file +AWS SQS Template +================= + + + +[![Project status](https://img.shields.io/badge/version-v1.0.0-vividgreen.svg)](https://github.com/GabrielHCataldo/go-aws-sqs/releases/tag/v1.0.5) +[![Go Report Card](https://goreportcard.com/badge/github.com/GabrielHCataldo/go-aws-sqs)](https://goreportcard.com/report/github.com/GabrielHCataldo/go-aws-sqs) +[![Coverage Status](https://coveralls.io/repos/GabrielHCataldo/go-aws-sqs/badge.svg?branch=main&service=github)](https://coveralls.io/github/GabrielHCataldo/go-aws-sqs?branch=main) +[![Open Source Helpers](https://www.codetriage.com/gabrielhcataldo/go-aws-sqs/badges/users.svg)](https://www.codetriage.com/gabrielhcataldo/go-aws-sqs) +[![GoDoc](https://godoc.org/github/GabrielHCataldo/go-aws-sqs?status.svg)](https://pkg.go.dev/github.com/GabrielHCataldo/go-aws-sqs/sqs) +![License](https://img.shields.io/dub/l/vibe-d.svg) + +[//]: # ([![build workflow](https://github.com/GabrielHCataldo/go-aws-sqs/actions/workflows/go.yml/badge.svg)](https://github.com/GabrielHCataldo/go-aws-sqs/actions)) + +[//]: # ([![Source graph](https://sourcegraph.com/github.com/go-aws-sqs/sqs/-/badge.svg)](https://sourcegraph.com/github.com/go-aws-sqs/sqs?badge)) + +[//]: # ([![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/GabrielHCataldo/go-aws-sqs/sqs)](https://www.tickgit.com/browse?repo=github.com/GabrielHCataldo/go-aws-sqs)) + +The go-aws-sqs project came to facilitate the use of aws sqs in your go project with incredible flexibility in sending +messages to any type of body, and a fantastic and simple to use consumer. Below we list some implemented features: + +- Simplicity in message production, with auto conversion of body and message attributes +- Powerful customizable consumer, with auto conversion of body and message attributes. +- Automatic removal of the successfully consumed message (Optional). +- More simplistic function calls. +- Log visibility for the consumer. +- Asynchronous message production. +- Asynchronous message consumption. +- Don't worry about unwanted type conversions anymore. + +Installation +------------ + +Use go get. + + go get github.com/GabrielHCataldo/go-aws-sqs + +Then import the go-aws-sqs package into your own code. + +```go +import "github.com/GabrielHCataldo/go-aws-sqs/sqs" +``` + +Usability and documentation +------------ +**IMPORTANT**: Always check the documentation in the structures and functions fields. +For more details on the examples, visit [All examples link](https://github/GabrielHCataldo/go-aws-sqs/blob/main/_example) + +### Producer +To produce a message, it's simple, in the example below we will send a normal body text: + +```go +import ( + "context" + "github.com/GabrielHCataldo/go-aws-sqs/sqs" + "github.com/GabrielHCataldo/go-logger/logger" + "os" +) + +func main() { + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + body := "body test" + message, err := sqs.SendMessage(ctx, os.Getenv("SQS_QUEUE_TEST_URL"), body) + if err != nil { + logger.Error("error send message:", err) + } else { + logger.Info("message sent successfully:", message) + } +} +``` + +Output: + + [INFO 2023/12/15 07:22:34] main.go:33: message sent successfully: {"MD5OfMessageAttributes":null,"MD5OfMessageBody":"2e9cc74f6f6aca12eaa2d252df457910","MD5OfMessageSystemAttributes":null,"MessageId":"48276fb7-022d-4114-8282-b407d8fd4dd3","SequenceNumber":null,"ResultMetadata":{}} + +You can also pass any type of value, below we will pass the body as a structure: + +```go +import ( + "context" + "github.com/GabrielHCataldo/go-aws-sqs/sqs" + "github.com/GabrielHCataldo/go-logger/logger" + "os" + "time" +) + +type test struct { + Name string `json:"name,omitempty"` + BirthDate time.Time `json:"birthDate,omitempty"` + Emails []string `json:"emails,omitempty"` + Bank bank `json:"bank,omitempty"` + Map map[string]any +} + +type bank struct { + Account string `json:"account,omitempty"` + Digits string `json:"digits,omitempty"` + Balance float64 `json:"balance,omitempty"` +} + +func main() { + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + body := initTestStruct() + message, err := sqs.SendMessage(ctx, os.Getenv("SQS_QUEUE_TEST_URL"), body) + if err != nil { + logger.Error("error send message:", err) + } else { + logger.Info("message sent successfully:", message) + } +} +``` + +Output: + + [INFO 2023/12/15 07:30:42] main.go:45: message sent successfully: {"MD5OfMessageAttributes":null,"MD5OfMessageBody":"52e95a6a12e47e7ef6f63ff0ccfb77b6","MD5OfMessageSystemAttributes":null,"MessageId":"b43e19ca-48d9-47e8-8457-183242b86e1e","SequenceNumber":null,"ResultMetadata":{}} + +As an optional parameter, we have **DelaySeconds**, **MessageAttributes**, **MessageSystemAttributes** +among others, see below: + +```go +import ( + "context" + "github.com/GabrielHCataldo/go-aws-sqs/sqs" + "github.com/GabrielHCataldo/go-logger/logger" + "os" + "time" +) + +func main() { + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + body := initTestStruct() + opt := option.NewProducer(). + // HTTP communication customization options with AWS SQS + SetHttpClient(option.HttpClient{}). + // print logs (default: false) + SetDebugMode(true). + // delay to delay the availability of message processing (default: 0) + SetDelaySeconds(5 * time.Second). + // Message attributes, must be of type Map or Struct, other types are not acceptable. + SetMessageAttributes(initTestMap()). + // The message system attribute to send + SetMessageSystemAttributes(option.MessageSystemAttributes{}). + // This parameter applies only to FIFO (first-in-first-out) queues. The token used for deduplication of sent messages. + SetMessageDeduplicationId(""). + // This parameter applies only to FIFO (first-in-first-out) queues. The tag that specifies that a message belongs to a specific message group. + SetMessageGroupId("") + + message, err := sqs.SendMessage(ctx, os.Getenv("SQS_QUEUE_TEST_URL"), body, opt) + if err != nil { + logger.Error("error send message:", err) + } else { + logger.Info("message sent successfully:", message) + } +} +``` + +Output: + + [INFO 2023/12/15 08:00:54] producer.go:40: getting client sqs.. + [INFO 2023/12/15 08:00:54] producer.go:42: preparing message input.. + [INFO 2023/12/15 08:00:54] producer.go:48: sending message.. + [INFO 2023/12/15 08:00:54] producer.go:53: message sent successfully: {"MD5OfMessageAttributes":"2a0b53405b3678d246c0a76b321e1cea","MD5OfMessageBody":"19554d258904ecb0a27b3cf238c2ecf2","MD5OfMessageSystemAttributes":null,"MessageId":"e08368e5-08b5-456b-a4f3-b6fb7862b893","SequenceNumber":null,"ResultMetadata":{}} + [INFO 2023/12/15 08:00:54] main.go:85: message sent successfully: {"MD5OfMessageAttributes":"2a0b53405b3678d246c0a76b321e1cea","MD5OfMessageBody":"19554d258904ecb0a27b3cf238c2ecf2","MD5OfMessageSystemAttributes":null,"MessageId":"e08368e5-08b5-456b-a4f3-b6fb7862b893","SequenceNumber":null,"ResultMetadata":{}} + +We can use all these examples mentioned above asynchronously, calling the function +**SendMessageAsync**, see: + +```go +import ( + "context" + "github.com/GabrielHCataldo/go-aws-sqs/sqs" + "github.com/GabrielHCataldo/go-logger/logger" + "os" +) + +func main() { + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + body := "body test" + sqs.SendMessageAsync(ctx, os.Getenv("SQS_QUEUE_TEST_URL"), body) +} +``` + +For more producer examples visit: [All examples produce](https://github/GabrielHCataldo/go-aws-sqs/blob/main/_example/producer/main.go) + +### Consumer + +To consume messages from the queue, it is also very simple, you don't need to worry about writing loop +lines, conversion lines, log lines, etc., see a simple example below: + +```go +import ( + "context" + "github.com/GabrielHCataldo/go-aws-sqs/sqs" + "github.com/GabrielHCataldo/go-logger/logger" + "os" +) + +func main() { + sqs.SimpleReceiveMessage(os.Getenv("SQS_QUEUE_TEST_STRING_URL"), handler) +} + +func handler(ctx *sqs.SimpleContext[string]) error { + logger.Debug("ctx simple to process message:", ctx) + return nil +} +``` + +Output: + + [DEBUG 2023/12/15 08:27:49] main.go:43: ctx simple to process message: {"QueueUrl":"https://sqs.sa-east-1.amazonaws.com/430896945629/go-aws-sqs-test-string","Message":{"Attributes":{"ApproximateFirstReceiveTimestamp":"0001-01-01T00:00:00Z","ApproximateReceiveCount":0,"MessageDeduplicationId":"","MessageGroupId":"","SenderId":"","SentTimestamp":"0001-01-01T00:00:00Z","SequenceNumber":0},"Body":"body test","Id":"d2b78dfe-8b68-4b7d-af59-15826c52515a","MD5OfBody":"2e9cc74f6f6aca12eaa2d252df457910","MD5OfMessageAttributes":null,"MessageAttributes":null,"ReceiptHandle":"AQEBG2Ek7PrGMDpnvCbTh3pX8U3I2AjkRyQW1e3QZmgZBmn/x4gB5l1Wd6S/c6NTlsuiIZ6qp3230ZrN2ogjWlTbX65jS3zTZfIsDBCrqf3+Zra31XthuZClEzSMf2qP2tpPFtX0Dm5De4YkoOaXm2mk3z0e201DSF5lZK3HK2ACA+ftc3UhlO7HMaGsTanAq+6QvWVLi49xVn5lUEfh+nEeOpBMywCLneIsBzUH9H/Jt62g3p10tYMFG+TAFiTfdwK2yWhwrVcFCOK8oDd+GA90v38vz0bIK8JcqvkRejMe3TUS5HocP+Adt5MW/ona/eLHIPVUqpuFULS7A1FIVIWSwnC4WSBpDOy8eNg/lESgawY9MmThb53h0jJiMp3IXgGTSTiF7qRZFimNIj8IjdFBNg=="}} + [DEBUG 2023/12/15 08:27:49] main.go:43: ctx simple to process message: {"QueueUrl":"https://sqs.sa-east-1.amazonaws.com/430896945629/go-aws-sqs-test-string","Message":{"Attributes":{"ApproximateFirstReceiveTimestamp":"0001-01-01T00:00:00Z","ApproximateReceiveCount":0,"MessageDeduplicationId":"","MessageGroupId":"","SenderId":"","SentTimestamp":"0001-01-01T00:00:00Z","SequenceNumber":0},"Body":"body test","Id":"d2b78dfe-8b68-4b7d-af59-15826c52515a","MD5OfBody":"2e9cc74f6f6aca12eaa2d252df457910","MD5OfMessageAttributes":null,"MessageAttributes":null,"ReceiptHandle":"AQEBfQYdy5nmL+BVJFslmID7vW8WyDnL83OGY8i4YfpfqaAM4xZ2+cwMIPsKoyOVZYII565b6I8XvoJmsZBK7adaGZtUk9r4+w9EZVfe3DCxB4Bt5gYbdb15Kjes8iQAQowuwCZ8e8FSnj4wXEBacPbaOLN0qo1afU3hU3kZ7ZION+0jfiup2AE3yK5/xR7vQtNqTZulNpzda5o1XICsdz/67iLQExhEJqbO3iSrclbzGVaggjPXYOlzx7iFeQrS2hcbQe5boZHCl/W8gC12TRPf/WX2OxNiU9UddY/nBPpy6FJG6I1HPmQla6OMznllE/f4yhjk1a5DzbSEDUjXaaalDFzUBYRbhpEFmb5NbNrsto/myP+QOhoU+DtgdL9iUQen83+MKJDpbCglmdpBYvoIKA=="}} + + +If you want to obtain the message attributes serialized in Map or Struct, you can call the function +**ReceiveMessage** passing in the second parameter of the handler context indicating the type you want to serialize +message attributes, see: + +```go +import ( + "context" + "github.com/GabrielHCataldo/go-aws-sqs/sqs" + "github.com/GabrielHCataldo/go-logger/logger" + "os" +) + +type messageAttTest struct { + Name string `json:"account,omitempty"` + Text string `json:"text,omitempty"` + Balance float64 `json:"balance,omitempty"` + Bool bool `json:"bool"` + Int int `json:"int"` + SubStruct test `json:"subStruct,omitempty"` + PointerTest *test `json:"pointerBank,omitempty"` + Map map[string]any + Any any + EmptyString string `json:"emptyString,omitempty"` + HideString string `json:"-"` +} + +func main() { + sqs.ReceiveMessage(os.Getenv("SQS_QUEUE_TEST_STRING_URL"), handler) +} + +func handler(ctx *sqs.Context[test, messageAttTest]) error { + logger.Debug("ctx to process message:", ctx) + return nil +} +``` + +Output: + + [DEBUG 2023/12/15 08:19:08] main.go:43: ctx to process message: {"QueueUrl":"https://sqs.sa-east-1.amazonaws.com/430896945629/go-aws-sqs-test-string","Message":{"Attributes":{"ApproximateFirstReceiveTimestamp":"0001-01-01T00:00:00Z","ApproximateReceiveCount":0,"MessageDeduplicationId":"","MessageGroupId":"","SenderId":"","SentTimestamp":"0001-01-01T00:00:00Z","SequenceNumber":0},"Body":"body test","Id":"0fe8cca6-92ac-4a6d-98de-ec373f433266","MD5OfBody":"2e9cc74f6f6aca12eaa2d252df457910","MD5OfMessageAttributes":null,"MessageAttributes":{"Any":null,"Map":null,"account":"","balance":0,"bool":false,"emptyString":"","int":0,"subStruct":{"Map":null,"bank":{"account":"","balance":0,"digits":""},"birthDate":"0001-01-01T00:00:00Z","name":""},"text":""},"ReceiptHandle":"AQEBFaWjTuCHZcWH9JNlAJt0/cyJBwnK+K5yWVYpCvT+/EBsnSRkEuOp1G6YqSA5szJKc7EXULmDlUxWzQ4pYvCT9BhP6nk82blgcg3Kw5zQy/fym6vg46CdoqLwT0d9vAlnKOsvtkN/dGC6ze0lcSsGjSbUIujo3NC4uXzmflbuanaUIfPo7eju2LGYtw/eAgeVHIdMeH1kkrYfNfTl5iRQejSuSoaozq0V/E3+hfyNhBpmm+J2Bsays+AHvXEZpMNwouycwTpiBWpQzoybUJbXVwHSiQj539hAOGsrmJ2vG+ZQfYb19AClh0gdvoyKsuR6F4Qny0mGqOaGOqD2k/CiuTHkz+W/6nclTKTmaUlUogc+EHCC5qirnKq3i/pCd8UFWGV8PZ7vy48Deh/KYRvLJw=="}} + +We can consume messages with body of all types in all message receiving functions, +See below an example consuming a body with structure: + +```go +import ( + "context" + "github.com/GabrielHCataldo/go-aws-sqs/sqs" + "github.com/GabrielHCataldo/go-logger/logger" + "os" +) + +type test struct { + Name string `json:"name,omitempty"` + BirthDate time.Time `json:"birthDate,omitempty"` + Emails []string `json:"emails,omitempty"` + Bank bank `json:"bank,omitempty"` + Map map[string]any +} + +type bank struct { + Account string `json:"account,omitempty"` + Digits string `json:"digits,omitempty"` + Balance float64 `json:"balance,omitempty"` +} + +func main() { + sqs.SimpleReceiveMessage(os.Getenv("SQS_QUEUE_TEST_URL"), handler) +} + +func handler(ctx *sqs.SimpleContext[test]) error { + logger.Debug("ctx simple body struct to process message:", ctx) + return nil +} +``` + +Output: + + [DEBUG 2023/12/15 09:08:39] main.go:47: ctx simple body struct to process message: {"QueueUrl":"https://sqs.sa-east-1.amazonaws.com/430896945629/go-aws-sqs-test","Message":{"Attributes":{"ApproximateFirstReceiveTimestamp":"0001-01-01T00:00:00Z","ApproximateReceiveCount":0,"MessageDeduplicationId":"","MessageGroupId":"","SenderId":"","SentTimestamp":"0001-01-01T00:00:00Z","SequenceNumber":0},"Body":{"Map":{"bool":true,"float":1.23,"int":1,"string":"text test"},"bank":{"account":"123456","balance":200.12,"digits":"2"},"birthDate":"2023-12-15T09:08:25-03:00","emails":["test@gmail.com","gabriel@gmail.com","gabriel.test@gmail.com"],"name":"Test Name"},"Id":"ac141711-fcf0-4073-a206-78a8a8914756","MD5OfBody":"9d70de56f2ce5bb257a0e88a4ae5bb64","MD5OfMessageAttributes":null,"MessageAttributes":null,"ReceiptHandle":"AQEBOSU25xkDe6sED3CxRGKFrgwTcLpaHpwgsBUSUkG2VXzVmcKLTnXp1JtnbB9fS1S/C1s3R5XWyFfgAasNQyxuFpwYJ07O0ZrTzixElJxUPCrcOxt0glM7Oa7wHeiU4yzT4xfVy7nv4r+oOPcaLUgiWvk0pp6q72Uu/Y51zqIRbhlpRnG189FPbhExfjqvTWjqzHNUzWop5cCMVhqF7F8BKp1LXVADOfbtaBTaJX17lv5x+tHOjjMg8hecJNWAo7k9PZg3+b23Fxe3/k2scjayKFXdsjkpgWrow+VvzJP5FrZ6b/jTgFTTkU4hz6zLmrntRCSqZpwmdU1rb+tCDGH3cHLoZ61vl0KOcgmCLnrkrG3pU0jfP1zOeHzYm+EZV+75LAnbPtjqdN/iZGLqseotvg=="}} + +If you want to customize consumer roles, see example below: + +```go +import ( + "context" + "github.com/GabrielHCataldo/go-aws-sqs/sqs" + "github.com/GabrielHCataldo/go-logger/logger" + "os" +) + +func main() { + opt := option.NewConsumer(). + // HTTP communication customization options with AWS SQS + SetHttpClient(option.HttpClient{}). + // print logs (default: false) + SetDebugMode(true). + // If true remove the message from the queue after successfully processed (handler error return is null) (default: false) + SetDeleteMessageProcessedSuccess(true). + // Duration time to process the message, timeout applied in the past context. (default: 5 seconds) + SetConsumerMessageTimeout(5 * time.Second). + // Delay to run the next search for messages in the queue (default: 0) + SetDelayQueryLoop(5 * time.Second). + // The maximum number of messages to return. 1 a 10 (default: 10) + SetMaxNumberOfMessages(10). + // The maximum number of messages to return. 1 a 10 (default: 0) + SetVisibilityTimeout(5 * time.Second). + // The duration that the received messages are hidden from subsequent + // retrieve requests after being retrieved by a ReceiveMessage request. + SetReceiveRequestAttemptId(""). + // The duration for which the call waits for a message to arrive in + // the queue before returning (default: 0) + SetWaitTimeSeconds(1 * time.Second) + + sqs.SimpleReceiveMessage(os.Getenv("SQS_QUEUE_TEST_URL"), handler, opt) +} + +func handler(ctx *sqs.SimpleContext[test]) error { + logger.Debug("ctx simple body struct to process message:", ctx) + return nil +} +``` + +Output: + + [INFO 2023/12/15 10:08:44] consumer.go:265: Run start find messages with options: {"Default":{"debugMode":true,"httpClient":{"APIOptions":null,"AppID":"","AuthSchemeResolver":null,"AuthSchemes":null,"BaseEndpoint":null,"ClientLogMode":0,"Credentials":null,"DefaultsMode":"","DisableMessageChecksumValidation":false,"EndpointOptions":{"DisableHTTPS":false,"LogDeprecated":false,"Logger":null,"ResolvedRegion":"","UseDualStackEndpoint":0,"UseFIPSEndpoint":0},"EndpointResolverV2":null,"HTTPClient":null,"HTTPSignerV4":null,"Logger":null,"Region":"","RetryMaxAttempts":0,"RetryMode":"","Retryer":null,"RuntimeEnvironment":{"EC2InstanceMetadataRegion":"","EnvironmentIdentifier":"","Region":""}}},"DeleteMessageProcessedSuccess":true,"ConsumerMessageTimeout":5000000000,"DelayQueryLoop":5000000000,"MaxNumberOfMessages":10,"VisibilityTimeout":5000000000,"ReceiveRequestAttemptId":null,"WaitTimeSeconds":1000000000} + [INFO 2023/12/15 10:08:44] consumer.go:244: Start process received messages size: 3 + [DEBUG 2023/12/15 10:08:44] main.go:77: ctx simple body struct to process message: {"QueueUrl":"https://sqs.sa-east-1.amazonaws.com/430896945629/go-aws-sqs-test","Message":{"Attributes":{"ApproximateFirstReceiveTimestamp":"0001-01-01T00:00:00Z","ApproximateReceiveCount":0,"MessageDeduplicationId":"","MessageGroupId":"","SenderId":"","SentTimestamp":"0001-01-01T00:00:00Z","SequenceNumber":0},"Body":{"Map":{"balance":10.23,"bool":true,"emptyString":"","int":3,"name":"Name test producer","nil":null,"text":"Text field"},"bank":{"account":"123456","balance":200.12,"digits":"2"},"birthDate":"2023-12-15T09:51:48-03:00","emails":["test@gmail.com","gabriel@gmail.com","gabriel.test@gmail.com"],"name":"Test Name"},"Id":"8a41c561-173e-4c8d-b1a9-645e61cd1e85","MD5OfBody":"f2deace7b685238892eab686ce6d4932","MD5OfMessageAttributes":"7aea2eb88bd4799daf004990a6397c00","MessageAttributes":{"Map":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"{\"balance\":10.23,\"bool\":true,\"emptyString\":\"\",\"int\":3,\"name\":\"Name test producer\",\"nil\":null,\"text\":\"Text field\"}"},"account":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"Name test producer"},"balance":{"BinaryListValues":null,"BinaryValue":null,"DataType":"Number","StringListValues":null,"StringValue":"10.23"},"bool":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"true"},"int":{"BinaryListValues":null,"BinaryValue":null,"DataType":"Number","StringListValues":null,"StringValue":"3"},"pointerBank":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"{\"name\":\"Test Name\",\"birthDate\":\"2023-12-15T09:51:48.015621-03:00\",\"emails\":[\"test@gmail.com\",\"gabriel@gmail.com\",\"gabriel.test@gmail.com\"],\"bank\":{\"account\":\"123456\",\"digits\":\"2\",\"balance\":200.12},\"Map\":{\"balance\":10.23,\"bool\":true,\"emptyString\":\"\",\"int\":3,\"name\":\"Name test producer\",\"nil\":null,\"text\":\"Text field\"},\"emptyString\":\"\",\"pointerBank\":{\"account\":\"123456\",\"digits\":\"2\",\"balance\":200.12},\"Any\":null}"},"subStruct":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"{\"name\":\"Test Name\",\"birthDate\":\"2023-12-15T09:51:48.015621-03:00\",\"emails\":[\"test@gmail.com\",\"gabriel@gmail.com\",\"gabriel.test@gmail.com\"],\"bank\":{\"account\":\"123456\",\"digits\":\"2\",\"balance\":200.12},\"Map\":{\"balance\":10.23,\"bool\":true,\"emptyString\":\"\",\"int\":3,\"name\":\"Name test producer\",\"nil\":null,\"text\":\"Text field\"},\"emptyString\":\"\",\"pointerBank\":{\"account\":\"123456\",\"digits\":\"2\",\"balance\":200.12},\"Any\":null}"},"text":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"Text field"}},"ReceiptHandle":"AQEB8Cs8DmT2np6msZsgZq0BrL7HR5Q5imP4sK89UnwfvIKPLTKn5Ca+j0Cy1xDdSmFONpKtTvyhMDTIX5hIJYMXJmf45rZlf0msHYZ6GmBQoKRJbbDseAIFTmqhJTtsD8oXweBvjSAeVh5hw7kuZCW+bMQf2x6cQyk0WPw7oklHbGos+yVuyzn/OWeVx+7wgkF9E9ozfJfuf3lxTrygXOne7bB3q2unnM4luNIVgcD/Hxni6lIoe0RdOK8rpo7XrxdHWgUh64JcR2bQNZbnY6dPU0Tch6TEkb+sB7Yenvdkr2ZTdMClVhDvFqYjeReBawuLXamtVpiOp+dfTvCy8tEVvkZj0cIV+nCrj3nv2Nsz/jqkv/bM9FF4+O8lMYuHvskTz49SYxDmlwxp6vyMZTDROg=="}} + [DEBUG 2023/12/15 10:08:44] main.go:77: ctx simple body struct to process message: {"QueueUrl":"https://sqs.sa-east-1.amazonaws.com/430896945629/go-aws-sqs-test","Message":{"Attributes":{"ApproximateFirstReceiveTimestamp":"0001-01-01T00:00:00Z","ApproximateReceiveCount":0,"MessageDeduplicationId":"","MessageGroupId":"","SenderId":"","SentTimestamp":"0001-01-01T00:00:00Z","SequenceNumber":0},"Body":{"Map":{"balance":10.23,"bool":true,"emptyString":"","int":3,"name":"Name test producer","nil":null,"text":"Text field"},"bank":{"account":"123456","balance":200.12,"digits":"2"},"birthDate":"2023-12-15T09:54:27-03:00","emails":["test@gmail.com","gabriel@gmail.com","gabriel.test@gmail.com"],"name":"Test Name"},"Id":"c3e1360c-2eb2-4326-b80e-85d69c8f32f3","MD5OfBody":"a599c012497c800aed1679812981d6da","MD5OfMessageAttributes":"e1fc8c890c4720384ffca66c432cc0ac","MessageAttributes":{"Map":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"{\"balance\":10.23,\"bool\":true,\"emptyString\":\"\",\"int\":3,\"name\":\"Name test producer\",\"nil\":null,\"text\":\"Text field\"}"},"account":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"Name test producer"},"balance":{"BinaryListValues":null,"BinaryValue":null,"DataType":"Number","StringListValues":null,"StringValue":"10.23"},"bool":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"true"},"int":{"BinaryListValues":null,"BinaryValue":null,"DataType":"Number","StringListValues":null,"StringValue":"3"},"pointerBank":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"{\"name\":\"Test Name\",\"birthDate\":\"2023-12-15T09:54:27.558714-03:00\",\"emails\":[\"test@gmail.com\",\"gabriel@gmail.com\",\"gabriel.test@gmail.com\"],\"bank\":{\"account\":\"123456\",\"digits\":\"2\",\"balance\":200.12},\"Map\":{\"balance\":10.23,\"bool\":true,\"emptyString\":\"\",\"int\":3,\"name\":\"Name test producer\",\"nil\":null,\"text\":\"Text field\"},\"emptyString\":\"\",\"pointerBank\":{\"account\":\"123456\",\"digits\":\"2\",\"balance\":200.12},\"Any\":null}"},"subStruct":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"{\"name\":\"Test Name\",\"birthDate\":\"2023-12-15T09:54:27.558714-03:00\",\"emails\":[\"test@gmail.com\",\"gabriel@gmail.com\",\"gabriel.test@gmail.com\"],\"bank\":{\"account\":\"123456\",\"digits\":\"2\",\"balance\":200.12},\"Map\":{\"balance\":10.23,\"bool\":true,\"emptyString\":\"\",\"int\":3,\"name\":\"Name test producer\",\"nil\":null,\"text\":\"Text field\"},\"emptyString\":\"\",\"pointerBank\":{\"account\":\"123456\",\"digits\":\"2\",\"balance\":200.12},\"Any\":null}"},"text":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"Text field"}},"ReceiptHandle":"AQEBiTWyaLxTUXk+sQbCogksDaphcYmOrNhlaUEk6DuREqbRL6/5Lu8tq6nMXAYCWDytUAH7CnNtuh8bXeeB89S4kl24pUSbKS3/OmOADichHPhgNteUANGxDvutZqonUjQW9DqwoqnEXJAapFSSW3vRBmcSOTOQBroDhunIz0BQzbr1ylkw6J11FU0iIzHMWA90vWQj5hIIhK5tZGTbhTIys+DIJLYqdvY4VQR2QRScVbKj4rSIrz7j0RE94+vkP1h8dB+XR1Guq7+E4N68batQq1oAA2SjDa/q3mYkYTtputb1sw55TqvW9boLetwqb98dQlcE6OoMN3vV3Rudl3ixLESNHXn7Od+mDKmHRKPm0irWhP05m0dHfTlVnPGU5dreFO/9DCJ4vEGZjTONbNpxTg=="}} + [DEBUG 2023/12/15 10:08:44] main.go:77: ctx simple body struct to process message: {"QueueUrl":"https://sqs.sa-east-1.amazonaws.com/430896945629/go-aws-sqs-test","Message":{"Attributes":{"ApproximateFirstReceiveTimestamp":"0001-01-01T00:00:00Z","ApproximateReceiveCount":0,"MessageDeduplicationId":"","MessageGroupId":"","SenderId":"","SentTimestamp":"0001-01-01T00:00:00Z","SequenceNumber":0},"Body":{"Map":{"balance":10.23,"bool":true,"emptyString":"","int":3,"name":"Name test producer","nil":null,"text":"Text field"},"bank":{"account":"123456","balance":200.12,"digits":"2"},"birthDate":"2023-12-15T09:55:12-03:00","emails":["test@gmail.com","gabriel@gmail.com","gabriel.test@gmail.com"],"name":"Test Name"},"Id":"807ccb3f-7bfd-4e8a-83fd-18f461ce7f6b","MD5OfBody":"edcf363ca1c42c81c34fbb3586c4fbb6","MD5OfMessageAttributes":"e7b35866b4dbfa2075553540b18433b5","MessageAttributes":{"Map":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"{\"balance\":10.23,\"bool\":true,\"emptyString\":\"\",\"int\":3,\"name\":\"Name test producer\",\"nil\":null,\"text\":\"Text field\"}"},"account":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"Name test producer"},"balance":{"BinaryListValues":null,"BinaryValue":null,"DataType":"Number","StringListValues":null,"StringValue":"10.23"},"bool":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"true"},"int":{"BinaryListValues":null,"BinaryValue":null,"DataType":"Number","StringListValues":null,"StringValue":"3"},"pointerBank":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"{\"name\":\"Test Name\",\"birthDate\":\"2023-12-15T09:55:12.111056-03:00\",\"emails\":[\"test@gmail.com\",\"gabriel@gmail.com\",\"gabriel.test@gmail.com\"],\"bank\":{\"account\":\"123456\",\"digits\":\"2\",\"balance\":200.12},\"Map\":{\"balance\":10.23,\"bool\":true,\"emptyString\":\"\",\"int\":3,\"name\":\"Name test producer\",\"nil\":null,\"text\":\"Text field\"},\"emptyString\":\"\",\"pointerBank\":{\"account\":\"123456\",\"digits\":\"2\",\"balance\":200.12},\"Any\":null}"},"subStruct":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"{\"name\":\"Test Name\",\"birthDate\":\"2023-12-15T09:55:12.111056-03:00\",\"emails\":[\"test@gmail.com\",\"gabriel@gmail.com\",\"gabriel.test@gmail.com\"],\"bank\":{\"account\":\"123456\",\"digits\":\"2\",\"balance\":200.12},\"Map\":{\"balance\":10.23,\"bool\":true,\"emptyString\":\"\",\"int\":3,\"name\":\"Name test producer\",\"nil\":null,\"text\":\"Text field\"},\"emptyString\":\"\",\"pointerBank\":{\"account\":\"123456\",\"digits\":\"2\",\"balance\":200.12},\"Any\":null}"},"text":{"BinaryListValues":null,"BinaryValue":null,"DataType":"String","StringListValues":null,"StringValue":"Text field"}},"ReceiptHandle":"AQEBw7GpJmzuQjetJgl7VLNVy7ESboXzdTcprQoRxH/xV1aQewH4P5cXmgB8sY/Zoa/QoGSbzq42TbRaBOr3y+htzzv399ysn7vW9FlImpsXWQHgf/TOHr3L4Uv6hwr3KcWZzfSe0nRIzf+YQQzDBf0MayU+NWSfkbcyM1g2FCwByho+SFvLIi5RkYIGpGVh2IpUEP2Bfym5QKHBddV9bXR7Uf32xTQVhHDb+7LxJmhgCyBFawGiRh9ZVVMBP0maZMyHh1keOk7N5Z8v9zVvvzsOMagciMB2ysT9wmbO9kinuPHE683a+mKDdibmnu/Bo0tGTXp2Iv6ksT31nUdM05JwYdCtkoyH7ZU556sY2D/XChc0a+aOJ1PvfHAxe756MmglldU5fdmn30VVsfAzj8/VPQ=="}} + [INFO 2023/12/15 10:08:44] consumer.go:290: Finish process messages! processed: 3 success: ["807ccb3f-7bfd-4e8a-83fd-18f461ce7f6b"] failed: null + + +You can also consume messages asynchronously, see: + +```go +import ( + "context" + "github.com/GabrielHCataldo/go-aws-sqs/sqs" + "github.com/GabrielHCataldo/go-logger/logger" + "os" +) + +func main() { + sqs.SimpleReceiveMessageAsync(os.Getenv("SQS_QUEUE_TEST_URL"), handler, option.NewConsumer().SetDebugMode(true)) + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + select { + case <-c: + logger.Info("Stopped application!") + } +} + +func handler(ctx *sqs.SimpleContext[test]) error { + logger.Debug("ctx simple body struct to process message:", ctx) + return nil +} +``` + +Output: + + [INFO 2023/12/15 10:12:52] consumer.go:265: Run start find messages with options: {"Default":{"debugMode":true},"DeleteMessageProcessedSuccess":false,"ConsumerMessageTimeout":5000000000,"DelayQueryLoop":5000000000,"MaxNumberOfMessages":10,"VisibilityTimeout":0,"ReceiveRequestAttemptId":null,"WaitTimeSeconds":0} + [INFO 2023/12/15 10:12:52] consumer.go:240: No msg available to be processed, searching again in 5s + [INFO 2023/12/15 10:12:57] consumer.go:244: Start process received messages size: 1 + [DEBUG 2023/12/15 10:12:57] main.go:88: ctx simple body struct to process message: {"QueueUrl":"https://sqs.sa-east-1.amazonaws.com/430896945629/go-aws-sqs-test","Message":{"Attributes":{"ApproximateFirstReceiveTimestamp":"0001-01-01T00:00:00Z","ApproximateReceiveCount":0,"MessageDeduplicationId":"","MessageGroupId":"","SenderId":"","SentTimestamp":"0001-01-01T00:00:00Z","SequenceNumber":0},"Body":{"Map":{"bool":true,"float":1.23,"int":1,"string":"text test"},"bank":{"account":"123456","balance":200.12,"digits":"2"},"birthDate":"2023-12-15T10:13:28-03:00","emails":["test@gmail.com","gabriel@gmail.com","gabriel.test@gmail.com"],"name":"Test Name"},"Id":"4ce4ed91-9b4b-4820-897b-9da5a8c6c630","MD5OfBody":"ad4c4de0c67e5eb5fb373b308fd38f53","MD5OfMessageAttributes":null,"MessageAttributes":null,"ReceiptHandle":"AQEB96WZYV/s91wxR1n8MUr39NllE4h+oVRZUN2WdCRXQoZEJ4DrV09kqyRRxnRxkyh9J/jTCX6sbyjKj6WzJ7YYkhrwr3ruQBW4BR/S2zz8b94waclJpfTaoq2fAa3SBl2/5nQhrCfsuFGfdDhIANlQNv9fyRMv4Vxsil9cjWauMXM2ilgsrcSnaDX2mRFzxDPzGhTnLJEIXOsJ+5nWb4ex5IVsYc81V8TEfs1c4dYmGc4PNs7s2MXtlXtDy2mOg+YnfgCT5evflCy3oN8qEXNglKqCDEuqqeQU2JXslN76zsObKG8ReZyB9PZIimfnhbM6AhIif5YbSfMX5ZylcyKZGF/9K8qwhAh51Lq3TBMxnHT+tOhslnov8MlECcWPjfQW2HdaXGnBGd/phV83MwlhVw=="}} + [INFO 2023/12/15 10:14:18] consumer.go:290: Finish process messages! processed: 1 success: ["4ce4ed91-9b4b-4820-897b-9da5a8c6c630"] failed: null + +For more consumer examples visit: [All examples consumer](https://github/GabrielHCataldo/go-aws-sqs/blob/main/_example/consumer/main.go) + +### For more examples + +- [Producer](https://github/GabrielHCataldo/go-aws-sqs/blob/main/_example/consumer/main.go) +- [Consumer](https://github/GabrielHCataldo/go-aws-sqs/blob/main/_example/consumer/main.go) +- [Queue](https://github/GabrielHCataldo/go-aws-sqs/blob/main/_example/queue/main.go) +- [Message](https://github/GabrielHCataldo/go-aws-sqs/blob/main/_example/message/main.go) + +How to contribute +------ +Make a pull request, or if you find a bug, open it +an Issues. + +License +------- +Distributed under MIT license, see the license file within the code for more details. \ No newline at end of file diff --git a/_example/consumer/main.go b/_example/consumer/main.go new file mode 100644 index 0000000..381f7e2 --- /dev/null +++ b/_example/consumer/main.go @@ -0,0 +1,108 @@ +package main + +import ( + "github.com/GabrielHCataldo/go-aws-sqs/sqs" + "github.com/GabrielHCataldo/go-aws-sqs/sqs/option" + "github.com/GabrielHCataldo/go-logger/logger" + "os" + "os/signal" + "time" +) + +type test struct { + Name string `json:"name,omitempty"` + BirthDate time.Time `json:"birthDate,omitempty"` + Emails []string `json:"emails,omitempty"` + Bank bank `json:"bank,omitempty"` + Map map[string]any +} + +type bank struct { + Account string `json:"account,omitempty"` + Digits string `json:"digits,omitempty"` + Balance float64 `json:"balance,omitempty"` +} + +type messageAttTest struct { + Name string `json:"account,omitempty"` + Text string `json:"text,omitempty"` + Balance float64 `json:"balance,omitempty"` + Bool bool `json:"bool"` + Int int `json:"int"` + SubStruct test `json:"subStruct,omitempty"` + PointerTest *test `json:"pointerBank,omitempty"` + Map map[string]any + Any any + EmptyString string `json:"emptyString,omitempty"` + HideString string `json:"-"` +} + +func main() { + simpleReceiveMessage() + simpleReceiveMessageStruct() + receiveMessage() + completeOptions() + simpleReceiveMessageAsync() +} + +func simpleReceiveMessage() { + sqs.SimpleReceiveMessage(os.Getenv("SQS_QUEUE_TEST_STRING_URL"), handlerSimple) +} + +func simpleReceiveMessageStruct() { + sqs.SimpleReceiveMessage(os.Getenv("SQS_QUEUE_TEST_URL"), handler) +} + +func receiveMessage() { + sqs.ReceiveMessage(os.Getenv("SQS_QUEUE_TEST_STRING_URL"), handlerReceiveMessage) +} + +func completeOptions() { + opt := option.NewConsumer(). + // HTTP communication customization options with AWS SQS + SetHttpClient(option.HttpClient{}). + // print logs (default: false) + SetDebugMode(true). + // If true remove the message from the queue after successfully processed (handler error return is null) (default: false) + SetDeleteMessageProcessedSuccess(true). + // Duration time to process the message, timeout applied in the past context. (default: 5 seconds) + SetConsumerMessageTimeout(5 * time.Second). + // Delay to run the next search for messages in the queue (default: 0) + SetDelayQueryLoop(5 * time.Second). + // The maximum number of messages to return. 1 a 10 (default: 10) + SetMaxNumberOfMessages(10). + // The maximum number of messages to return. 1 a 10 (default: 0) + SetVisibilityTimeout(5 * time.Second). + // The duration that the received messages are hidden from subsequent + // retrieve requests after being retrieved by a ReceiveMessage request. + SetReceiveRequestAttemptId(""). + // The duration for which the call waits for a message to arrive in + // the queue before returning (default: 0) + SetWaitTimeSeconds(1 * time.Second) + sqs.SimpleReceiveMessage(os.Getenv("SQS_QUEUE_TEST_URL"), handler, opt) +} + +func simpleReceiveMessageAsync() { + sqs.SimpleReceiveMessageAsync(os.Getenv("SQS_QUEUE_TEST_URL"), handler, option.NewConsumer().SetDebugMode(true)) + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + select { + case <-c: + logger.Info("Stopped application!") + } +} + +func handler(ctx *sqs.SimpleContext[test]) error { + logger.Debug("ctx simple body struct to process message:", ctx) + return nil +} + +func handlerSimple(ctx *sqs.SimpleContext[string]) error { + logger.Debug("ctx simple to process message:", ctx) + return nil +} + +func handlerReceiveMessage(ctx *sqs.Context[string, messageAttTest]) error { + logger.Debug("ctx to process message:", ctx) + return nil +} diff --git a/_example/producer/main.go b/_example/producer/main.go new file mode 100644 index 0000000..1d1ae1c --- /dev/null +++ b/_example/producer/main.go @@ -0,0 +1,150 @@ +package main + +import ( + "context" + "github.com/GabrielHCataldo/go-aws-sqs/sqs" + "github.com/GabrielHCataldo/go-aws-sqs/sqs/option" + "github.com/GabrielHCataldo/go-logger/logger" + "os" + "time" +) + +type test struct { + Name string `json:"name,omitempty"` + BirthDate time.Time `json:"birthDate,omitempty"` + Emails []string `json:"emails,omitempty"` + Bank bank `json:"bank,omitempty"` + Map map[string]any +} + +type bank struct { + Account string `json:"account,omitempty"` + Digits string `json:"digits,omitempty"` + Balance float64 `json:"balance,omitempty"` +} + +type messageAttTest struct { + Name string `json:"account,omitempty"` + Text string `json:"text,omitempty"` + Balance float64 `json:"balance,omitempty"` + Bool bool `json:"bool"` + Int int `json:"int"` + SubStruct test `json:"subStruct,omitempty"` + PointerTest *test `json:"pointerBank,omitempty"` + Map map[string]any + Any any + EmptyString string `json:"emptyString,omitempty"` + HideString string `json:"-"` +} + +func main() { + simple() + simpleAsync() + structBody() + mapBody() + completeOptions() +} + +func simple() { + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + body := "body test" + message, err := sqs.SendMessage(ctx, os.Getenv("SQS_QUEUE_TEST_STRING_URL"), body) + if err != nil { + logger.Error("error send message:", err) + } else { + logger.Info("message sent successfully:", message) + } +} + +func simpleAsync() { + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + body := "body test" + sqs.SendMessageAsync(ctx, os.Getenv("SQS_QUEUE_TEST_STRING_URL"), body) +} + +func structBody() { + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + body := initTestStruct() + message, err := sqs.SendMessage(ctx, os.Getenv("SQS_QUEUE_TEST_URL"), body) + if err != nil { + logger.Error("error send message:", err) + } else { + logger.Info("message sent successfully:", message) + } +} + +func mapBody() { + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + body := initTestMap() + message, err := sqs.SendMessage(ctx, os.Getenv("SQS_QUEUE_TEST_STRING_URL"), body) + if err != nil { + logger.Error("error send message:", err) + } else { + logger.Info("message sent successfully:", message) + } +} + +func completeOptions() { + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + body := initTestStruct() + opt := option.NewProducer(). + // HTTP communication customization options with AWS SQS + SetHttpClient(option.HttpClient{}). + // print logs (default: false) + SetDebugMode(true). + // delay to delay the availability of message processing (default: 0) + SetDelaySeconds(5 * time.Second). + // Message attributes, must be of type Map or Struct, other types are not acceptable. + SetMessageAttributes(initMessageAttTest()). + // The message system attribute to send + SetMessageSystemAttributes(option.MessageSystemAttributes{}). + // This parameter applies only to FIFO (first-in-first-out) queues. The token used for deduplication of sent messages. + SetMessageDeduplicationId(""). + // This parameter applies only to FIFO (first-in-first-out) queues. The tag that specifies that a message belongs to a specific message group. + SetMessageGroupId("") + message, err := sqs.SendMessage(ctx, os.Getenv("SQS_QUEUE_TEST_URL"), body, opt) + if err != nil { + logger.Error("error send message:", err) + } else { + logger.Info("message sent successfully:", message) + } +} + +func initTestStruct() test { + b := bank{ + Account: "123456", + Digits: "2", + Balance: 200.12, + } + return test{ + Name: "Test Name", + BirthDate: time.Now(), + Emails: []string{"test@gmail.com", "gabriel@gmail.com", "gabriel.test@gmail.com"}, + Bank: b, + Map: map[string]any{"int": 1, "bool": true, "float": 1.23, "string": "text test"}, + } +} + +func initTestMap() map[string]any { + return map[string]any{"int": 1, "bool": true, "float": 1.23, "string": "text test"} +} + +func initMessageAttTest() messageAttTest { + t := initTestStruct() + return messageAttTest{ + Name: "Name test producer", + Text: "Text field", + Balance: 10.23, + Bool: true, + Int: 3, + SubStruct: t, + PointerTest: &t, + Map: initTestMap(), + HideString: "hide test", + } +} diff --git a/go.mod b/go.mod index 66c6a24..3b7463e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ -module go-aws-sqs +module github.com/GabrielHCataldo/go-aws-sqs -go 1.21 +go 1.21.3 require ( github.com/GabrielHCataldo/go-logger v1.0.8 diff --git a/gopher-sqs.png b/gopher-sqs.png new file mode 100644 index 0000000000000000000000000000000000000000..dbe73c24bbc7ce7ea7c8e57364f21803992cff33 GIT binary patch literal 30641 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