diff --git a/sdk/messaging/azeventgrid/CHANGELOG.md b/sdk/messaging/azeventgrid/CHANGELOG.md new file mode 100644 index 000000000000..bd474b266681 --- /dev/null +++ b/sdk/messaging/azeventgrid/CHANGELOG.md @@ -0,0 +1,5 @@ +# Release History + +## 0.1.0 (TBD) + +* Event Grid package for Event Grid Namespaces diff --git a/sdk/messaging/azeventgrid/LICENSE.txt b/sdk/messaging/azeventgrid/LICENSE.txt new file mode 100644 index 000000000000..22aed37e650b --- /dev/null +++ b/sdk/messaging/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/azeventgrid/NOTICE.txt b/sdk/messaging/azeventgrid/NOTICE.txt new file mode 100644 index 000000000000..895fd036e8df --- /dev/null +++ b/sdk/messaging/azeventgrid/NOTICE.txt @@ -0,0 +1,32 @@ +aztemplate + +NOTICES AND INFORMATION +Do Not Translate or Localize + +This software incorporates material from third parties. Microsoft makes certain +open source code available at https://3rdpartysource.microsoft.com, or you may +send a check or money order for US $5.00, including the product name, the open +source component name, and version number, to: + +Source Code Compliance Team +Microsoft Corporation +One Microsoft Way +Redmond, WA 98052 +USA + +Notwithstanding any other terms, you may reverse engineer this software to the +extent required to debug changes to any libraries licensed under the GNU Lesser +General Public License. + +------------------------------------------------------------------------------ + +Azure SDK for Go uses third-party libraries or other resources that may be +distributed under licenses different than the Azure SDK for Go software. + +In the event that we accidentally failed to list a required notice, please +bring it to our attention. Post an issue or email us: + + @microsoft.com + +The attached notices are provided for information only. + diff --git a/sdk/messaging/azeventgrid/README.md b/sdk/messaging/azeventgrid/README.md new file mode 100644 index 000000000000..ba7eaea401b5 --- /dev/null +++ b/sdk/messaging/azeventgrid/README.md @@ -0,0 +1,69 @@ +# Azure Template Package client library for Go + +Azure Template Package client library for Go (`aztemplate`) matches necessary patterns that the development team has established to create a unified SDK written in the Go programming language. These libraries follow the Azure SDK Design Guidelines for Go. + +The library allows client libraries to expose common functionality in a consistent fashion. Once you learn how to use these APIs in one client library, you will know how to use them in other client libraries. + +## Getting started + +For a rich example of a well formatted readme, please check [here.](https://github.com/Azure/azure-sdk/blob/main/docs/policies/README-TEMPLATE.md) In addition, this is an [example readme](https://github.com/Azure/azure-sdk/blob/main/docs/policies/README-EXAMPLE.md) that should be emulated. Note that the top-level sections in this template align with that of the [template.](https://github.com/Azure/azure-sdk/blob/main/docs/policies/README-TEMPLATE.md) + +# Key concepts + +Bullet point list of your library's main concepts. + +# Examples + +Examples of some of the key concepts for your library. + +# Troubleshooting + +Running into issues? This section should contain details as to what to do there. + +# 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 diff --git a/sdk/messaging/azeventgrid/assets.json b/sdk/messaging/azeventgrid/assets.json new file mode 100644 index 000000000000..0189b4352a79 --- /dev/null +++ b/sdk/messaging/azeventgrid/assets.json @@ -0,0 +1,6 @@ +{ + "AssetsRepo": "Azure/azure-sdk-assets", + "AssetsRepoPrefixPath": "go", + "TagPrefix": "go/messaging/azeventgrid", + "Tag": "go/messaging/azeventgrid_601fd733f2" +} diff --git a/sdk/messaging/azeventgrid/autorest.md b/sdk/messaging/azeventgrid/autorest.md new file mode 100644 index 000000000000..4ee2fddc2a89 --- /dev/null +++ b/sdk/messaging/azeventgrid/autorest.md @@ -0,0 +1,64 @@ +## Go + +``` yaml +title: EventGridClient +description: Azure Event Grid client +generated-metadata: false +clear-output-folder: false +go: true +input-file: + # This was the commit that everyone used to generate their first official betas. + - https://raw.githubusercontent.com/Azure/azure-rest-api-specs/947c9ce9b20900c6cbc8e95bc083e723d09a9c2c/specification/eventgrid/data-plane/Microsoft.EventGrid/preview/2023-06-01-preview/EventGrid.json + # when we start using the .tsp file directly we can start referring to the compiled output. + # ./tsp-output\@azure-tools\typespec-autorest\2023-06-01-preview\openapi.json +license-header: MICROSOFT_MIT_NO_VERSION +module: github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid +openapi-type: "data-plane" +output-folder: ../azeventgrid +override-client-name: Client +security: "AADToken" +use: "@autorest/go@4.0.0-preview.49" +version: "^3.0.0" +directive: + # we have to write a little wrapper code for this so we'll hide the public function + # for now. + - from: client.go + where: $ + transform: return $.replace(/PublishCloudEvents\(/g, "internalPublishCloudEvents("); + # make sure the casing of the properties is what compliant. + # - from: swagger-document + # where: $.definitions.CloudEvent.properties.data + # transform: > + # $["type"] = "array" + # - from: swagger-document + # where: $.definitions.CloudEvent.properties.data + # transform: > + # $["items"] = {"type": "byte"} + - from: swagger-document + where: $.definitions.CloudEvent.properties.specversion + transform: $["x-ms-client-name"] = "SpecVersion" + - from: swagger-document + where: $.definitions.CloudEvent.properties.datacontenttype + transform: $["x-ms-client-name"] = "DataContentType" + - from: swagger-document + where: $.definitions.CloudEvent.properties.dataschema + transform: $["x-ms-client-name"] = "DataSchema" + # 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" + # delete client name prefix from method options and response types + - from: + - client.go + - models.go + - response_types.go + where: $ + transform: return $.replace(/Client(\w+)((?:Options|Response))/g, "$1$2"); + - from: + - client.go + - models.go + - models_serde.go + - response_types.go + where: $ + transform: return $.replace(/AzureCoreFoundations/g, ""); +``` diff --git a/sdk/messaging/azeventgrid/build.go b/sdk/messaging/azeventgrid/build.go new file mode 100644 index 000000000000..98ce5cd2b813 --- /dev/null +++ b/sdk/messaging/azeventgrid/build.go @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +//go:build go1.18 +// +build go1.18 + +//go:generate autorest ./autorest.md +//go:generate gofmt -w . + +package azeventgrid diff --git a/sdk/messaging/azeventgrid/ci.yml b/sdk/messaging/azeventgrid/ci.yml new file mode 100644 index 000000000000..093eb520154e --- /dev/null +++ b/sdk/messaging/azeventgrid/ci.yml @@ -0,0 +1,29 @@ +# 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/azeventgrid + - eng/ + +pr: + branches: + include: + - main + - feature/* + - hotfix/* + - release/* + paths: + include: + - sdk/messaging/azeventgrid + + +stages: +- template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml + parameters: + ServiceDirectory: 'messaging/azeventgrid' diff --git a/sdk/messaging/azeventgrid/client.go b/sdk/messaging/azeventgrid/client.go new file mode 100644 index 000000000000..da5c52aa33bd --- /dev/null +++ b/sdk/messaging/azeventgrid/client.go @@ -0,0 +1,322 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +//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. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. +// DO NOT EDIT. + +package azeventgrid + +import ( + "context" + "errors" + "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/azcore/runtime" + "net/http" + "net/url" + "strconv" + "strings" +) + +// 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 +} + +// AcknowledgeCloudEvents - Acknowledge batch of Cloud Events. The server responds with an HTTP 200 status code if at least +// one event is successfully acknowledged. The response body will include the set of successfully +// acknowledged lockTokens, along with other failed lockTokens with their corresponding error information. Successfully acknowledged +// events will no longer be available to any consumer. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-06-01-preview +// - topicName - Topic Name. +// - eventSubscriptionName - Event Subscription Name. +// - lockTokens - AcknowledgeOptions. +// - options - AcknowledgeCloudEventsOptions contains the optional parameters for the Client.AcknowledgeCloudEvents method. +func (client *Client) AcknowledgeCloudEvents(ctx context.Context, topicName string, eventSubscriptionName string, lockTokens AcknowledgeOptions, options *AcknowledgeCloudEventsOptions) (AcknowledgeCloudEventsResponse, error) { + req, err := client.acknowledgeCloudEventsCreateRequest(ctx, topicName, eventSubscriptionName, lockTokens, options) + if err != nil { + return AcknowledgeCloudEventsResponse{}, err + } + resp, err := client.internal.Pipeline().Do(req) + if err != nil { + return AcknowledgeCloudEventsResponse{}, err + } + if !runtime.HasStatusCode(resp, http.StatusOK) { + return AcknowledgeCloudEventsResponse{}, runtime.NewResponseError(resp) + } + return client.acknowledgeCloudEventsHandleResponse(resp) +} + +// acknowledgeCloudEventsCreateRequest creates the AcknowledgeCloudEvents request. +func (client *Client) acknowledgeCloudEventsCreateRequest(ctx context.Context, topicName string, eventSubscriptionName string, lockTokens AcknowledgeOptions, options *AcknowledgeCloudEventsOptions) (*policy.Request, error) { + urlPath := "/topics/{topicName}/eventsubscriptions/{eventSubscriptionName}:acknowledge" + if topicName == "" { + return nil, errors.New("parameter topicName cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{topicName}", url.PathEscape(topicName)) + if eventSubscriptionName == "" { + return nil, errors.New("parameter eventSubscriptionName cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{eventSubscriptionName}", url.PathEscape(eventSubscriptionName)) + req, err := runtime.NewRequest(ctx, http.MethodPost, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-06-01-preview") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + if err := runtime.MarshalAsJSON(req, lockTokens); err != nil { + return nil, err + } + return req, nil +} + +// acknowledgeCloudEventsHandleResponse handles the AcknowledgeCloudEvents response. +func (client *Client) acknowledgeCloudEventsHandleResponse(resp *http.Response) (AcknowledgeCloudEventsResponse, error) { + result := AcknowledgeCloudEventsResponse{} + if err := runtime.UnmarshalAsJSON(resp, &result.AcknowledgeResult); err != nil { + return AcknowledgeCloudEventsResponse{}, err + } + return result, nil +} + +// PublishCloudEvents - Publish Batch Cloud Event to namespace topic. In case of success, the server responds with an HTTP +// 200 status code with an empty JSON object in response. Otherwise, the server can return various error +// codes. For example, 401: which indicates authorization failure, 403: which indicates quota exceeded or message is too large, +// 410: which indicates that specific topic is not found, 400: for bad +// request, and 500: for internal server error. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-06-01-preview +// - topicName - Topic Name. +// - events - Array of Cloud Events being published. +// - options - PublishCloudEventsOptions contains the optional parameters for the Client.PublishCloudEvents method. +func (client *Client) internalPublishCloudEvents(ctx context.Context, topicName string, events []*CloudEvent, options *PublishCloudEventsOptions) (PublishCloudEventsResponse, error) { + req, err := client.publishCloudEventsCreateRequest(ctx, topicName, events, options) + if err != nil { + return PublishCloudEventsResponse{}, err + } + resp, err := client.internal.Pipeline().Do(req) + if err != nil { + return PublishCloudEventsResponse{}, err + } + if !runtime.HasStatusCode(resp, http.StatusOK) { + return PublishCloudEventsResponse{}, runtime.NewResponseError(resp) + } + return client.publishCloudEventsHandleResponse(resp) +} + +// publishCloudEventsCreateRequest creates the PublishCloudEvents request. +func (client *Client) publishCloudEventsCreateRequest(ctx context.Context, topicName string, events []*CloudEvent, options *PublishCloudEventsOptions) (*policy.Request, error) { + urlPath := "/topics/{topicName}:publish" + if topicName == "" { + return nil, errors.New("parameter topicName cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{topicName}", url.PathEscape(topicName)) + req, err := runtime.NewRequest(ctx, http.MethodPost, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-06-01-preview") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + if err := runtime.MarshalAsJSON(req, events); err != nil { + return nil, err + } + return req, nil +} + +// publishCloudEventsHandleResponse handles the PublishCloudEvents response. +func (client *Client) publishCloudEventsHandleResponse(resp *http.Response) (PublishCloudEventsResponse, error) { + result := PublishCloudEventsResponse{} + if err := runtime.UnmarshalAsJSON(resp, &result.Interface); err != nil { + return PublishCloudEventsResponse{}, err + } + return result, nil +} + +// ReceiveCloudEvents - Receive Batch of Cloud Events from the Event Subscription. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-06-01-preview +// - topicName - Topic Name. +// - eventSubscriptionName - Event Subscription Name. +// - options - ReceiveCloudEventsOptions contains the optional parameters for the Client.ReceiveCloudEvents method. +func (client *Client) ReceiveCloudEvents(ctx context.Context, topicName string, eventSubscriptionName string, options *ReceiveCloudEventsOptions) (ReceiveCloudEventsResponse, error) { + req, err := client.receiveCloudEventsCreateRequest(ctx, topicName, eventSubscriptionName, options) + if err != nil { + return ReceiveCloudEventsResponse{}, err + } + resp, err := client.internal.Pipeline().Do(req) + if err != nil { + return ReceiveCloudEventsResponse{}, err + } + if !runtime.HasStatusCode(resp, http.StatusOK) { + return ReceiveCloudEventsResponse{}, runtime.NewResponseError(resp) + } + return client.receiveCloudEventsHandleResponse(resp) +} + +// receiveCloudEventsCreateRequest creates the ReceiveCloudEvents request. +func (client *Client) receiveCloudEventsCreateRequest(ctx context.Context, topicName string, eventSubscriptionName string, options *ReceiveCloudEventsOptions) (*policy.Request, error) { + urlPath := "/topics/{topicName}/eventsubscriptions/{eventSubscriptionName}:receive" + if topicName == "" { + return nil, errors.New("parameter topicName cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{topicName}", url.PathEscape(topicName)) + if eventSubscriptionName == "" { + return nil, errors.New("parameter eventSubscriptionName cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{eventSubscriptionName}", url.PathEscape(eventSubscriptionName)) + req, err := runtime.NewRequest(ctx, http.MethodPost, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-06-01-preview") + if options != nil && options.MaxEvents != nil { + reqQP.Set("maxEvents", strconv.FormatInt(int64(*options.MaxEvents), 10)) + } + if options != nil && options.MaxWaitTime != nil { + reqQP.Set("maxWaitTime", strconv.FormatInt(int64(*options.MaxWaitTime), 10)) + } + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// receiveCloudEventsHandleResponse handles the ReceiveCloudEvents response. +func (client *Client) receiveCloudEventsHandleResponse(resp *http.Response) (ReceiveCloudEventsResponse, error) { + result := ReceiveCloudEventsResponse{} + if err := runtime.UnmarshalAsJSON(resp, &result.ReceiveResult); err != nil { + return ReceiveCloudEventsResponse{}, err + } + return result, nil +} + +// RejectCloudEvents - Reject batch of Cloud Events. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-06-01-preview +// - topicName - Topic Name. +// - eventSubscriptionName - Event Subscription Name. +// - lockTokens - RejectOptions +// - options - RejectCloudEventsOptions contains the optional parameters for the Client.RejectCloudEvents method. +func (client *Client) RejectCloudEvents(ctx context.Context, topicName string, eventSubscriptionName string, lockTokens RejectOptions, options *RejectCloudEventsOptions) (RejectCloudEventsResponse, error) { + req, err := client.rejectCloudEventsCreateRequest(ctx, topicName, eventSubscriptionName, lockTokens, options) + if err != nil { + return RejectCloudEventsResponse{}, err + } + resp, err := client.internal.Pipeline().Do(req) + if err != nil { + return RejectCloudEventsResponse{}, err + } + if !runtime.HasStatusCode(resp, http.StatusOK) { + return RejectCloudEventsResponse{}, runtime.NewResponseError(resp) + } + return client.rejectCloudEventsHandleResponse(resp) +} + +// rejectCloudEventsCreateRequest creates the RejectCloudEvents request. +func (client *Client) rejectCloudEventsCreateRequest(ctx context.Context, topicName string, eventSubscriptionName string, lockTokens RejectOptions, options *RejectCloudEventsOptions) (*policy.Request, error) { + urlPath := "/topics/{topicName}/eventsubscriptions/{eventSubscriptionName}:reject" + if topicName == "" { + return nil, errors.New("parameter topicName cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{topicName}", url.PathEscape(topicName)) + if eventSubscriptionName == "" { + return nil, errors.New("parameter eventSubscriptionName cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{eventSubscriptionName}", url.PathEscape(eventSubscriptionName)) + req, err := runtime.NewRequest(ctx, http.MethodPost, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-06-01-preview") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + if err := runtime.MarshalAsJSON(req, lockTokens); err != nil { + return nil, err + } + return req, nil +} + +// rejectCloudEventsHandleResponse handles the RejectCloudEvents response. +func (client *Client) rejectCloudEventsHandleResponse(resp *http.Response) (RejectCloudEventsResponse, error) { + result := RejectCloudEventsResponse{} + if err := runtime.UnmarshalAsJSON(resp, &result.RejectResult); err != nil { + return RejectCloudEventsResponse{}, err + } + return result, nil +} + +// ReleaseCloudEvents - Release batch of Cloud Events. The server responds with an HTTP 200 status code if at least one event +// is successfully released. The response body will include the set of successfully released +// lockTokens, along with other failed lockTokens with their corresponding error information. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-06-01-preview +// - topicName - Topic Name. +// - eventSubscriptionName - Event Subscription Name. +// - lockTokens - ReleaseOptions +// - options - ReleaseCloudEventsOptions contains the optional parameters for the Client.ReleaseCloudEvents method. +func (client *Client) ReleaseCloudEvents(ctx context.Context, topicName string, eventSubscriptionName string, lockTokens ReleaseOptions, options *ReleaseCloudEventsOptions) (ReleaseCloudEventsResponse, error) { + req, err := client.releaseCloudEventsCreateRequest(ctx, topicName, eventSubscriptionName, lockTokens, options) + if err != nil { + return ReleaseCloudEventsResponse{}, err + } + resp, err := client.internal.Pipeline().Do(req) + if err != nil { + return ReleaseCloudEventsResponse{}, err + } + if !runtime.HasStatusCode(resp, http.StatusOK) { + return ReleaseCloudEventsResponse{}, runtime.NewResponseError(resp) + } + return client.releaseCloudEventsHandleResponse(resp) +} + +// releaseCloudEventsCreateRequest creates the ReleaseCloudEvents request. +func (client *Client) releaseCloudEventsCreateRequest(ctx context.Context, topicName string, eventSubscriptionName string, lockTokens ReleaseOptions, options *ReleaseCloudEventsOptions) (*policy.Request, error) { + urlPath := "/topics/{topicName}/eventsubscriptions/{eventSubscriptionName}:release" + if topicName == "" { + return nil, errors.New("parameter topicName cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{topicName}", url.PathEscape(topicName)) + if eventSubscriptionName == "" { + return nil, errors.New("parameter eventSubscriptionName cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{eventSubscriptionName}", url.PathEscape(eventSubscriptionName)) + req, err := runtime.NewRequest(ctx, http.MethodPost, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-06-01-preview") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + if err := runtime.MarshalAsJSON(req, lockTokens); err != nil { + return nil, err + } + return req, nil +} + +// releaseCloudEventsHandleResponse handles the ReleaseCloudEvents response. +func (client *Client) releaseCloudEventsHandleResponse(resp *http.Response) (ReleaseCloudEventsResponse, error) { + result := ReleaseCloudEventsResponse{} + if err := runtime.UnmarshalAsJSON(resp, &result.ReleaseResult); err != nil { + return ReleaseCloudEventsResponse{}, err + } + return result, nil +} diff --git a/sdk/messaging/azeventgrid/client_custom.go b/sdk/messaging/azeventgrid/client_custom.go new file mode 100644 index 000000000000..6dcee91c22a3 --- /dev/null +++ b/sdk/messaging/azeventgrid/client_custom.go @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azeventgrid + +import ( + "context" + "net/http" + + "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/azcore/runtime" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/internal/uuid" +) + +// ClientOptions contains optional settings for [Client] +type ClientOptions struct { + azcore.ClientOptions +} + +// NewClientWithSharedKeyCredential creates a [Client] using a shared key. +func NewClientWithSharedKeyCredential(endpoint string, key string, options *ClientOptions) (*Client, error) { + if options == nil { + options = &ClientOptions{} + } + + // TODO: I believe we're supposed to allow for dynamically updating the key at any time as well. + azc, err := azcore.NewClient(moduleName+".Client", moduleVersion, runtime.PipelineOptions{ + PerRetry: []policy.Policy{ + &skpolicy{Key: key}, + }, + }, &options.ClientOptions) + + if err != nil { + return nil, err + } + + return &Client{ + internal: azc, + endpoint: endpoint, + }, nil +} + +// PublishCloudEvents - Publish Batch Cloud Event to namespace topic. In case of success, the server responds with an HTTP +// 200 status code with an empty JSON object in response. Otherwise, the server can return various error +// codes. For example, 401: which indicates authorization failure, 403: which indicates quota exceeded or message is too large, +// 410: which indicates that specific topic is not found, 400: for bad +// request, and 500: for internal server error. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-06-01-preview +// - endpoint - The host name of the namespace, e.g. namespaceName1.westus-1.eventgrid.azure.net +// - topicName - Topic Name. +// - events - Array of Cloud Events being published. +// - options - ClientPublishCloudEventsOptions contains the optional parameters for the Client.PublishCloudEvents method. +func (client *Client) PublishCloudEvents(ctx context.Context, topicName string, events []*CloudEvent, options *PublishCloudEventsOptions) (PublishCloudEventsResponse, error) { + ctx = runtime.WithHTTPHeader(ctx, http.Header{ + "Content-type": []string{"application/cloudevents-batch+json; charset=utf-8"}, + }) + + for _, evt := range events { + if evt.ID == nil { + id, err := uuid.New() + + if err != nil { + return PublishCloudEventsResponse{}, err + } + + evt.ID = to.Ptr(id.String()) + } + + if evt.SpecVersion == nil || *evt.SpecVersion == "" { + evt.SpecVersion = &defaultSpecVersion + } + } + + return client.internalPublishCloudEvents(ctx, topicName, events, options) +} + +// TODO: remove in favor of a common policy instead? +type skpolicy struct { + Key string +} + +func (p *skpolicy) Do(req *policy.Request) (*http.Response, error) { + req.Raw().Header.Add("Authorization", "SharedAccessKey "+p.Key) + return req.Next() +} + +var defaultSpecVersion = "1.0" diff --git a/sdk/messaging/azeventgrid/client_test.go b/sdk/messaging/azeventgrid/client_test.go new file mode 100644 index 000000000000..bf78fe73b604 --- /dev/null +++ b/sdk/messaging/azeventgrid/client_test.go @@ -0,0 +1,253 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azeventgrid_test + +import ( + "context" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid" + "github.com/stretchr/testify/require" +) + +func TestFailedAck(t *testing.T) { + c := newClientWrapper(t, nil) + + pubResp, err := c.PublishCloudEvents(context.Background(), c.TestVars.Topic, []*azeventgrid.CloudEvent{ + { + Data: []byte("ack this one"), + Source: to.Ptr("hello-source"), + Type: to.Ptr("world"), + }, + }, nil) + require.NoError(t, err) + + // just documenting this, I don't think the return value is useful. + require.Equal(t, map[string]interface{}{}, pubResp.Interface) + + recvResp, err := c.ReceiveCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, &azeventgrid.ReceiveCloudEventsOptions{ + MaxEvents: to.Ptr[int32](1), + MaxWaitTime: to.Ptr[int32](10), + }) + require.NoError(t, err) + + ackResp, err := c.AcknowledgeCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, azeventgrid.AcknowledgeOptions{ + LockTokens: []*string{recvResp.Value[0].BrokerProperties.LockToken}, + }, nil) + require.NoError(t, err) + require.Empty(t, ackResp.FailedLockTokens) + require.Equal(t, []*string{recvResp.Value[0].BrokerProperties.LockToken}, ackResp.SucceededLockTokens) + + // now let's try to do stuff with an "out of date" token + t.Run("AcknowledgeCloudEvents", func(t *testing.T) { + resp, err := c.AcknowledgeCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, azeventgrid.AcknowledgeOptions{ + LockTokens: []*string{recvResp.Value[0].BrokerProperties.LockToken}, + }, nil) + require.NoError(t, err) + require.Empty(t, resp.SucceededLockTokens) + // TODO: these two fields are not symmetrical - FailedLockTokens carries a reason. + require.Equal(t, []*azeventgrid.FailedLockToken{ + { + LockToken: recvResp.Value[0].BrokerProperties.LockToken, + ErrorCode: to.Ptr("TokenLost"), + ErrorDescription: to.Ptr("Token has expired."), + }, + }, resp.FailedLockTokens) + }) + + t.Run("RejectCloudEvents", func(t *testing.T) { + resp, err := c.RejectCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, azeventgrid.RejectOptions{ + LockTokens: []*string{recvResp.Value[0].BrokerProperties.LockToken}, + }, nil) + require.NoError(t, err) + require.Empty(t, resp.SucceededLockTokens) + // TODO: these two fields are not symmetrical - FailedLockTokens carries a reason. + require.Equal(t, []*azeventgrid.FailedLockToken{ + { + LockToken: recvResp.Value[0].BrokerProperties.LockToken, + ErrorCode: to.Ptr("TokenLost"), + ErrorDescription: to.Ptr("Token has expired."), + }, + }, resp.FailedLockTokens) + }) + + t.Run("AcknowledgeCloudEvents", func(t *testing.T) { + resp, err := c.ReleaseCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, azeventgrid.ReleaseOptions{ + LockTokens: []*string{recvResp.Value[0].BrokerProperties.LockToken}, + }, nil) + require.NoError(t, err) + require.Empty(t, resp.SucceededLockTokens) + // TODO: these two fields are not symmetrical - FailedLockTokens carries a reason. + require.Equal(t, []*azeventgrid.FailedLockToken{ + { + LockToken: recvResp.Value[0].BrokerProperties.LockToken, + ErrorCode: to.Ptr("TokenLost"), + ErrorDescription: to.Ptr("Token has expired."), + }, + }, resp.FailedLockTokens) + }) +} + +func TestPartialAckFailure(t *testing.T) { + c := newClientWrapper(t, nil) + + _, err := c.PublishCloudEvents(context.Background(), c.TestVars.Topic, []*azeventgrid.CloudEvent{ + { + Data: []byte("event one"), + Source: to.Ptr("hello-source"), + Type: to.Ptr("world"), + }, + { + Data: []byte("event two"), + Source: to.Ptr("hello-source"), + Type: to.Ptr("world"), + }, + }, nil) + require.NoError(t, err) + + events, err := c.ReceiveCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, &azeventgrid.ReceiveCloudEventsOptions{ + MaxEvents: to.Ptr[int32](2), + }) + require.NoError(t, err) + + // we'll ack one now so we can force a failure to happen. + ackResp, err := c.AcknowledgeCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, azeventgrid.AcknowledgeOptions{ + LockTokens: []*string{events.Value[0].BrokerProperties.LockToken}, + }, nil) + require.NoError(t, err) + require.Empty(t, ackResp.FailedLockTokens) + + // this will result in a partial failure. + ackResp, err = c.AcknowledgeCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, azeventgrid.AcknowledgeOptions{ + LockTokens: []*string{ + events.Value[0].BrokerProperties.LockToken, + events.Value[1].BrokerProperties.LockToken, + }, + }, nil) + require.NoError(t, err) + require.Equal(t, []*azeventgrid.FailedLockToken{ + { + LockToken: events.Value[0].BrokerProperties.LockToken, + ErrorCode: to.Ptr("TokenLost"), + ErrorDescription: to.Ptr("Token has expired."), + }, + }, ackResp.FailedLockTokens) + require.Equal(t, []*string{events.Value[1].BrokerProperties.LockToken}, ackResp.SucceededLockTokens) +} + +func TestReject(t *testing.T) { + c := newClientWrapper(t, nil) + + _, err := c.PublishCloudEvents(context.Background(), c.TestVars.Topic, []*azeventgrid.CloudEvent{ + { + Data: "event one", + Source: to.Ptr("TestAbandon"), + Type: to.Ptr("world"), + }, + }, nil) + require.NoError(t, err) + + events, err := c.ReceiveCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, nil) + require.NoError(t, err) + + requireEqualCloudEvent(t, &azeventgrid.CloudEvent{ + Data: "event one", + Source: to.Ptr("TestAbandon"), + Type: to.Ptr("world"), + }, events.Value[0].Event) + + require.Equal(t, int32(1), *events.Value[0].BrokerProperties.DeliveryCount, "DeliveryCount starts at 1") + + rejectResp, err := c.RejectCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, azeventgrid.RejectOptions{ + LockTokens: []*string{events.Value[0].BrokerProperties.LockToken}, + }, nil) + require.NoError(t, err) + require.Empty(t, rejectResp.FailedLockTokens) + + events, err = c.ReceiveCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, &azeventgrid.ReceiveCloudEventsOptions{ + MaxEvents: to.Ptr[int32](1), + MaxWaitTime: to.Ptr[int32](10), + }) + require.NoError(t, err) + require.Empty(t, events.Value) +} + +func TestRelease(t *testing.T) { + c := newClientWrapper(t, nil) + _, err := c.PublishCloudEvents(context.Background(), c.TestVars.Topic, []*azeventgrid.CloudEvent{ + { + Data: "event one", + Source: to.Ptr("TestAbandon"), + Type: to.Ptr("world"), + }, + }, nil) + require.NoError(t, err) + + events, err := c.ReceiveCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, nil) + require.NoError(t, err) + + requireEqualCloudEvent(t, &azeventgrid.CloudEvent{ + Data: "event one", + Source: to.Ptr("TestAbandon"), + Type: to.Ptr("world"), + }, events.Value[0].Event) + + require.Equal(t, int32(1), *events.Value[0].BrokerProperties.DeliveryCount, "DeliveryCount starts at 1") + + rejectResp, err := c.ReleaseCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, azeventgrid.ReleaseOptions{ + LockTokens: []*string{events.Value[0].BrokerProperties.LockToken}, + }, nil) + require.NoError(t, err) + require.Empty(t, rejectResp.FailedLockTokens) + + events, err = c.ReceiveCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, nil) + require.NoError(t, err) + + require.Equal(t, int32(2), *events.Value[0].BrokerProperties.DeliveryCount, "DeliveryCount is incremented") + ackResp, err := c.AcknowledgeCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, azeventgrid.AcknowledgeOptions{ + LockTokens: []*string{events.Value[0].BrokerProperties.LockToken}, + }, nil) + require.NoError(t, err) + require.Empty(t, ackResp.FailedLockTokens) +} + +func TestPublishingAndReceivingCloudEvents(t *testing.T) { + c := newClientWrapper(t, nil) + _, err := c.PublishCloudEvents(context.Background(), c.TestVars.Topic, []*azeventgrid.CloudEvent{ + { + Data: "hello world", + Source: to.Ptr("hello-source"), + Type: to.Ptr("world"), + }, + }, nil) + require.NoError(t, err) + + resp, err := c.ReceiveCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, nil) + require.NoError(t, err) + require.NotEmpty(t, resp.Value) + + // this doesn't work - it comes back as a base64 encoded string. + require.Equal(t, "hello world", resp.Value[0].Event.Data) + require.Equal(t, "hello-source", *resp.Value[0].Event.Source) + require.Equal(t, "world", *resp.Value[0].Event.Type) + + ackArgs := azeventgrid.AcknowledgeOptions{} + + for _, e := range resp.Value { + require.NotNil(t, e.BrokerProperties.LockToken) + ackArgs.LockTokens = append(ackArgs.LockTokens, e.BrokerProperties.LockToken) + } + + ackResp, err := c.AcknowledgeCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, ackArgs, nil) + require.NoError(t, err) + + require.Empty(t, ackResp.FailedLockTokens) + require.NotEmpty(t, ackResp.SucceededLockTokens) +} + +// https://github.com/cloudevents/spec/blob/v1.0/json-format.md#31-handling-of-data diff --git a/sdk/messaging/azeventgrid/go.mod b/sdk/messaging/azeventgrid/go.mod new file mode 100644 index 000000000000..21970fdfe483 --- /dev/null +++ b/sdk/messaging/azeventgrid/go.mod @@ -0,0 +1,20 @@ +module github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid + +go 1.18 + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 + github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 + github.com/joho/godotenv v1.5.1 + github.com/stretchr/testify v1.7.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dnaeon/go-vcr v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/text v0.8.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/sdk/messaging/azeventgrid/go.sum b/sdk/messaging/azeventgrid/go.sum new file mode 100644 index 000000000000..b83312d72c1f --- /dev/null +++ b/sdk/messaging/azeventgrid/go.sum @@ -0,0 +1,29 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 h1:SEy2xmstIphdPwNBUi7uhvjyjhVKISfwjfOJmuy7kg4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +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.1/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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sdk/messaging/azeventgrid/main_test.go b/sdk/messaging/azeventgrid/main_test.go new file mode 100644 index 000000000000..4d9abe295190 --- /dev/null +++ b/sdk/messaging/azeventgrid/main_test.go @@ -0,0 +1,28 @@ +//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" +) + +func TestMain(m *testing.M) { + if err := recording.ResetProxy(nil); err != nil { + panic(err) + } + + if err := godotenv.Load(); err != nil { + log.Printf("Failed to load .env file, no integration tests will run: %s", err) + } + + os.Exit(m.Run()) +} diff --git a/sdk/messaging/azeventgrid/models.go b/sdk/messaging/azeventgrid/models.go new file mode 100644 index 000000000000..a31c9bf57e04 --- /dev/null +++ b/sdk/messaging/azeventgrid/models.go @@ -0,0 +1,202 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +//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. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. +// DO NOT EDIT. + +package azeventgrid + +import "time" + +// AcknowledgeOptions - Array of lock token strings for the corresponding received Cloud Events to be acknowledged. +type AcknowledgeOptions struct { + // REQUIRED; String array of lock tokens. + LockTokens []*string +} + +// AcknowledgeResult - The result of the Acknowledge operation. +type AcknowledgeResult struct { + // REQUIRED; Array of LockToken values for failed cloud events. Each LockToken includes the lock token value along with the + // related error information (namely, the error code and description). + FailedLockTokens []*FailedLockToken + + // REQUIRED; Array of lock tokens values for the successfully acknowledged cloud events. + SucceededLockTokens []*string +} + +// Error - The error object. +type Error struct { + // REQUIRED; One of a server-defined set of error codes. + Code *string + + // REQUIRED; An array of details about specific errors that led to this reported error. + Details []*Error + + // REQUIRED; A human-readable representation of the error. + Message *string + + // An object containing more specific information than the current object about the error. + Innererror *InnerError + + // The target of the error. + Target *string +} + +// ErrorResponse - A response containing error details. +type ErrorResponse struct { + // REQUIRED; The error object. + Error *Error +} + +// InnerError - An object containing more specific information about the error. As per Microsoft One API +// guidelines - +// https://github.com/Microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses. +type InnerError struct { + // REQUIRED; One of a server-defined set of error codes. + Code *string + + // Inner error. + Innererror *InnerError +} + +// BrokerProperties - Properties of the Event Broker operation. +type BrokerProperties struct { + // REQUIRED; The attempt count for delivering the event. + DeliveryCount *int32 + + // REQUIRED; The token used to lock the event. + LockToken *string +} + +// AcknowledgeCloudEventsOptions contains the optional parameters for the Client.AcknowledgeCloudEvents method. +type AcknowledgeCloudEventsOptions struct { + // placeholder for future optional parameters +} + +// PublishCloudEventsOptions contains the optional parameters for the Client.PublishCloudEvents method. +type PublishCloudEventsOptions struct { + // placeholder for future optional parameters +} + +// ReceiveCloudEventsOptions contains the optional parameters for the Client.ReceiveCloudEvents method. +type ReceiveCloudEventsOptions struct { + // Max Events count to be received. Minimum value is 1, while maximum value is 100 events. If not specified, the default value + // is 1. + MaxEvents *int32 + // Max wait time value for receive operation in Seconds. It is the time in seconds that the server approximately waits for + // the availability of an event and responds to the request. If an event is + // available, the broker responds immediately to the client. Minimum value is 10 seconds, while maximum value is 120 seconds. + // If not specified, the default value is 60 seconds. + MaxWaitTime *int32 +} + +// RejectCloudEventsOptions contains the optional parameters for the Client.RejectCloudEvents method. +type RejectCloudEventsOptions struct { + // placeholder for future optional parameters +} + +// ReleaseCloudEventsOptions contains the optional parameters for the Client.ReleaseCloudEvents method. +type ReleaseCloudEventsOptions struct { + // placeholder for future optional parameters +} + +// CloudEvent - Properties of an event published to an Azure Messaging EventGrid Namespace topic using the CloudEvent 1.0 +// Schema. +type CloudEvent struct { + // REQUIRED; An identifier for the event. The combination of id and source must be unique for each distinct event. + ID *string + + // REQUIRED; Identifies the context in which an event happened. The combination of id and source must be unique for each distinct + // event. + Source *string + + // REQUIRED; The version of the CloudEvents specification which the event uses. + SpecVersion *string + + // REQUIRED; Type of event related to the originating occurrence. + Type *string + + // Event data specific to the event type. + Data any + + // Event data specific to the event type, encoded as a base64 string. + DataBase64 []byte + + // Content type of data value. + DataContentType *string + + // Identifies the schema that data adheres to. + DataSchema *string + + // This describes the subject of the event in the context of the event producer (identified by source). + Subject *string + + // The time (in UTC) the event was generated, in RFC3339 format. + Time *time.Time +} + +// FailedLockToken - Failed LockToken information. +type FailedLockToken struct { + // REQUIRED; Error code related to the token. Example of such error codes are BadToken: which indicates the Token is not formatted + // correctly, TokenLost: which indicates that token is not found, and + // InternalServerError: For any internal server errors. + ErrorCode *string + + // REQUIRED; Description of the token error. + ErrorDescription *string + + // REQUIRED; LockToken value + LockToken *string +} + +// ReceiveDetails - Receive operation details per Cloud Event. +type ReceiveDetails struct { + // REQUIRED; The Event Broker details. + BrokerProperties *BrokerProperties + + // REQUIRED; Cloud Event details. + Event *CloudEvent +} + +// ReceiveResult - Details of the Receive operation response. +type ReceiveResult struct { + // REQUIRED; Array of receive responses, one per cloud event. + Value []*ReceiveDetails +} + +// RejectOptions - Array of lock token strings for the corresponding received Cloud Events to be rejected. +type RejectOptions struct { + // REQUIRED; String array of lock tokens. + LockTokens []*string +} + +// RejectResult - The result of the Reject operation. +type RejectResult struct { + // REQUIRED; Array of LockToken values for failed cloud events. Each LockToken includes the lock token value along with the + // related error information (namely, the error code and description). + FailedLockTokens []*FailedLockToken + + // REQUIRED; Array of lock tokens values for the successfully rejected cloud events. + SucceededLockTokens []*string +} + +// ReleaseOptions - Array of lock token strings for the corresponding received Cloud Events to be released. +type ReleaseOptions struct { + // REQUIRED; String array of lock tokens. + LockTokens []*string +} + +// ReleaseResult - The result of the Release operation. +type ReleaseResult struct { + // REQUIRED; Array of LockToken values for failed cloud events. Each LockToken includes the lock token value along with the + // related error information (namely, the error code and description). + FailedLockTokens []*FailedLockToken + + // REQUIRED; Array of lock tokens values for the successfully released cloud events. + SucceededLockTokens []*string +} diff --git a/sdk/messaging/azeventgrid/models_serde.go b/sdk/messaging/azeventgrid/models_serde.go new file mode 100644 index 000000000000..7faf3a798eef --- /dev/null +++ b/sdk/messaging/azeventgrid/models_serde.go @@ -0,0 +1,522 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +//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. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. +// DO NOT EDIT. + +package azeventgrid + +import ( + "encoding/json" + "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "reflect" +) + +// MarshalJSON implements the json.Marshaller interface for type AcknowledgeOptions. +func (a AcknowledgeOptions) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "lockTokens", a.LockTokens) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type AcknowledgeOptions. +func (a *AcknowledgeOptions) 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", a, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "lockTokens": + err = unpopulate(val, "LockTokens", &a.LockTokens) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type AcknowledgeResult. +func (a AcknowledgeResult) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "failedLockTokens", a.FailedLockTokens) + populate(objectMap, "succeededLockTokens", a.SucceededLockTokens) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type AcknowledgeResult. +func (a *AcknowledgeResult) 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", a, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "failedLockTokens": + err = unpopulate(val, "FailedLockTokens", &a.FailedLockTokens) + delete(rawMsg, key) + case "succeededLockTokens": + err = unpopulate(val, "SucceededLockTokens", &a.SucceededLockTokens) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type Error. +func (a Error) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "code", a.Code) + populate(objectMap, "details", a.Details) + populate(objectMap, "innererror", a.Innererror) + populate(objectMap, "message", a.Message) + populate(objectMap, "target", a.Target) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type Error. +func (a *Error) 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", a, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "code": + err = unpopulate(val, "Code", &a.Code) + delete(rawMsg, key) + case "details": + err = unpopulate(val, "Details", &a.Details) + delete(rawMsg, key) + case "innererror": + err = unpopulate(val, "Innererror", &a.Innererror) + delete(rawMsg, key) + case "message": + err = unpopulate(val, "Message", &a.Message) + delete(rawMsg, key) + case "target": + err = unpopulate(val, "Target", &a.Target) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ErrorResponse. +func (a ErrorResponse) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "error", a.Error) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ErrorResponse. +func (a *ErrorResponse) 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", a, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "error": + err = unpopulate(val, "Error", &a.Error) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type InnerError. +func (a InnerError) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "code", a.Code) + populate(objectMap, "innererror", a.Innererror) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type InnerError. +func (a *InnerError) 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", a, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "code": + err = unpopulate(val, "Code", &a.Code) + delete(rawMsg, key) + case "innererror": + err = unpopulate(val, "Innererror", &a.Innererror) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type BrokerProperties. +func (b BrokerProperties) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "deliveryCount", b.DeliveryCount) + populate(objectMap, "lockToken", b.LockToken) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type BrokerProperties. +func (b *BrokerProperties) 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", b, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "deliveryCount": + err = unpopulate(val, "DeliveryCount", &b.DeliveryCount) + delete(rawMsg, key) + case "lockToken": + err = unpopulate(val, "LockToken", &b.LockToken) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", b, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type CloudEvent. +func (c CloudEvent) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populateAny(objectMap, "data", c.Data) + populateByteArray(objectMap, "data_base64", c.DataBase64, runtime.Base64StdFormat) + populate(objectMap, "datacontenttype", c.DataContentType) + populate(objectMap, "dataschema", c.DataSchema) + populate(objectMap, "id", c.ID) + populate(objectMap, "source", c.Source) + populate(objectMap, "specversion", c.SpecVersion) + populate(objectMap, "subject", c.Subject) + populateTimeRFC3339(objectMap, "time", c.Time) + populate(objectMap, "type", c.Type) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type CloudEvent. +func (c *CloudEvent) 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", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "data": + err = unpopulate(val, "Data", &c.Data) + delete(rawMsg, key) + case "data_base64": + err = runtime.DecodeByteArray(string(val), &c.DataBase64, runtime.Base64StdFormat) + delete(rawMsg, key) + case "datacontenttype": + err = unpopulate(val, "DataContentType", &c.DataContentType) + delete(rawMsg, key) + case "dataschema": + err = unpopulate(val, "DataSchema", &c.DataSchema) + delete(rawMsg, key) + case "id": + err = unpopulate(val, "ID", &c.ID) + delete(rawMsg, key) + case "source": + err = unpopulate(val, "Source", &c.Source) + delete(rawMsg, key) + case "specversion": + err = unpopulate(val, "SpecVersion", &c.SpecVersion) + delete(rawMsg, key) + case "subject": + err = unpopulate(val, "Subject", &c.Subject) + delete(rawMsg, key) + case "time": + err = unpopulateTimeRFC3339(val, "Time", &c.Time) + delete(rawMsg, key) + case "type": + err = unpopulate(val, "Type", &c.Type) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type FailedLockToken. +func (f FailedLockToken) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "errorCode", f.ErrorCode) + populate(objectMap, "errorDescription", f.ErrorDescription) + populate(objectMap, "lockToken", f.LockToken) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type FailedLockToken. +func (f *FailedLockToken) 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", f, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "errorCode": + err = unpopulate(val, "ErrorCode", &f.ErrorCode) + delete(rawMsg, key) + case "errorDescription": + err = unpopulate(val, "ErrorDescription", &f.ErrorDescription) + delete(rawMsg, key) + case "lockToken": + err = unpopulate(val, "LockToken", &f.LockToken) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", f, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ReceiveDetails. +func (r ReceiveDetails) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "brokerProperties", r.BrokerProperties) + populate(objectMap, "event", r.Event) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ReceiveDetails. +func (r *ReceiveDetails) 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", r, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "brokerProperties": + err = unpopulate(val, "BrokerProperties", &r.BrokerProperties) + delete(rawMsg, key) + case "event": + err = unpopulate(val, "Event", &r.Event) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", r, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ReceiveResult. +func (r ReceiveResult) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "value", r.Value) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ReceiveResult. +func (r *ReceiveResult) 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", r, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "value": + err = unpopulate(val, "Value", &r.Value) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", r, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type RejectOptions. +func (r RejectOptions) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "lockTokens", r.LockTokens) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type RejectOptions. +func (r *RejectOptions) 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", r, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "lockTokens": + err = unpopulate(val, "LockTokens", &r.LockTokens) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", r, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type RejectResult. +func (r RejectResult) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "failedLockTokens", r.FailedLockTokens) + populate(objectMap, "succeededLockTokens", r.SucceededLockTokens) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type RejectResult. +func (r *RejectResult) 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", r, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "failedLockTokens": + err = unpopulate(val, "FailedLockTokens", &r.FailedLockTokens) + delete(rawMsg, key) + case "succeededLockTokens": + err = unpopulate(val, "SucceededLockTokens", &r.SucceededLockTokens) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", r, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ReleaseOptions. +func (r ReleaseOptions) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "lockTokens", r.LockTokens) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ReleaseOptions. +func (r *ReleaseOptions) 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", r, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "lockTokens": + err = unpopulate(val, "LockTokens", &r.LockTokens) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", r, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ReleaseResult. +func (r ReleaseResult) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "failedLockTokens", r.FailedLockTokens) + populate(objectMap, "succeededLockTokens", r.SucceededLockTokens) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ReleaseResult. +func (r *ReleaseResult) 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", r, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "failedLockTokens": + err = unpopulate(val, "FailedLockTokens", &r.FailedLockTokens) + delete(rawMsg, key) + case "succeededLockTokens": + err = unpopulate(val, "SucceededLockTokens", &r.SucceededLockTokens) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", r, 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 populateByteArray(m map[string]any, k string, b []byte, f runtime.Base64Encoding) { + if azcore.IsNullValue(b) { + m[k] = nil + } else if len(b) == 0 { + return + } else { + m[k] = runtime.EncodeByteArray(b, f) + } +} + +func unpopulate(data json.RawMessage, fn string, v any) error { + if data == nil { + 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/azeventgrid/response_types.go b/sdk/messaging/azeventgrid/response_types.go new file mode 100644 index 000000000000..723f70baba03 --- /dev/null +++ b/sdk/messaging/azeventgrid/response_types.go @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +//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. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. +// DO NOT EDIT. + +package azeventgrid + +// AcknowledgeCloudEventsResponse contains the response from method Client.AcknowledgeCloudEvents. +type AcknowledgeCloudEventsResponse struct { + AcknowledgeResult +} + +// PublishCloudEventsResponse contains the response from method Client.PublishCloudEvents. +type PublishCloudEventsResponse struct { + // Anything + Interface any +} + +// ReceiveCloudEventsResponse contains the response from method Client.ReceiveCloudEvents. +type ReceiveCloudEventsResponse struct { + ReceiveResult +} + +// RejectCloudEventsResponse contains the response from method Client.RejectCloudEvents. +type RejectCloudEventsResponse struct { + RejectResult +} + +// ReleaseCloudEventsResponse contains the response from method Client.ReleaseCloudEvents. +type ReleaseCloudEventsResponse struct { + ReleaseResult +} diff --git a/sdk/messaging/azeventgrid/shared_test.go b/sdk/messaging/azeventgrid/shared_test.go new file mode 100644 index 000000000000..1e50ff2cc986 --- /dev/null +++ b/sdk/messaging/azeventgrid/shared_test.go @@ -0,0 +1,231 @@ +//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/tls" + "fmt" + "net/http" + "os" + "strings" + "sync" + "testing" + + "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/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid" + "github.com/stretchr/testify/require" +) + +var fakeTestVars = testVars{ + Key: "key", + Endpoint: "https://fake.eastus-1.eventgrid.azure.net", + Topic: "topic", + Subscription: "subscription", +} + +type testVars struct { + Key string + Endpoint string + Topic string + Subscription string + + KeyLogPath string +} + +func loadEnv() (testVars, error) { + var missing []string + + get := func(n string) string { + if v := os.Getenv(n); v == "" { + missing = append(missing, n) + } + + return os.Getenv(n) + } + + tv := testVars{ + Key: get("EVENTGRID_KEY"), + Endpoint: get("EVENTGRID_ENDPOINT"), + Topic: get("EVENTGRID_TOPIC"), + Subscription: get("EVENTGRID_SUBSCRIPTION"), + } + + if len(missing) > 0 { + return testVars{}, fmt.Errorf("Missing env variables: %s", strings.Join(missing, ",")) + } + + // Setting this variable will cause the test clients to dump out the pre-master-key + // for your HTTP connection. This allows you decrypt a packet capture from wireshark. + // + // If you want to do this just set SSLKEYLOGFILE_TEST env var to a path on disk and + // Go will write out the key. + tv.KeyLogPath = os.Getenv("SSLKEYLOGFILE_TEST") + return tv, nil +} + +type clientWrapper struct { + *azeventgrid.Client + TestVars testVars +} + +type clientWrapperOptions struct { + DontPurgeEvents bool +} + +func newClientWrapper(t *testing.T, opts *clientWrapperOptions) clientWrapper { + var client *azeventgrid.Client + var tv testVars + + if recording.GetRecordMode() != recording.PlaybackMode { + tmpTestVars, err := loadEnv() + require.NoError(t, err) + tv = tmpTestVars + } else { + tv = fakeTestVars + } + + if recording.GetRecordMode() == recording.LiveMode { + keyLogWriter, err := os.OpenFile(tv.KeyLogPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777) + require.NoError(t, err) + + t.Cleanup(func() { keyLogWriter.Close() }) + + tp := http.DefaultTransport.(*http.Transport).Clone() + tp.TLSClientConfig = &tls.Config{ + KeyLogWriter: keyLogWriter, + } + + httpClient := &http.Client{Transport: tp} + + tmpClient, err := azeventgrid.NewClientWithSharedKeyCredential(tv.Endpoint, tv.Key, &azeventgrid.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Transport: httpClient, + }, + }) + require.NoError(t, err) + client = tmpClient + + purgePreviousEvents(t, client, tv) + } else { + tmpClient, err := azeventgrid.NewClientWithSharedKeyCredential(tv.Endpoint, tv.Key, &azeventgrid.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Transport: newRecordingTransporter(t, tv), + }, + }) + require.NoError(t, err) + client = tmpClient + } + + return clientWrapper{ + Client: client, + TestVars: tv, + } +} + +func newRecordingTransporter(t *testing.T, testVars testVars) policy.Transporter { + transport, err := recording.NewRecordingHTTPClient(t, nil) + require.NoError(t, err) + + err = recording.Start(t, "sdk/messaging/azeventgrid/testdata", nil) + require.NoError(t, err) + + // err = recording.ResetProxy(nil) + // require.NoError(t, err) + + err = recording.AddURISanitizer(fakeTestVars.Endpoint, testVars.Endpoint, nil) + require.NoError(t, err) + + err = recording.AddURISanitizer(fakeTestVars.Topic, testVars.Topic, nil) + require.NoError(t, err) + + err = recording.AddURISanitizer(fakeTestVars.Subscription, testVars.Subscription, nil) + require.NoError(t, err) + + err = recording.AddGeneralRegexSanitizer( + `"id":"00000000-0000-0000-0000-000000000000"`, + `"id":"[^"]+"`, nil) + require.NoError(t, err) + + err = recording.AddGeneralRegexSanitizer( + `"lockToken":"fake-lock-token"`, + `"lockToken":\s*"[^"]+"`, nil) + require.NoError(t, err) + + err = recording.AddGeneralRegexSanitizer( + `"lockTokens": ["fake-lock-token"]`, + `"lockTokens":\s*\[\s*"[^"]+"\s*\]`, nil) + require.NoError(t, err) + + err = recording.AddGeneralRegexSanitizer( + `"succeededLockTokens": ["fake-lock-token"]`, + `"succeededLockTokens":\s*\[\s*"[^"]+"\s*\]`, nil) + require.NoError(t, err) + + err = recording.AddGeneralRegexSanitizer( + `"lockTokens": ["fake-lock-token", "fake-lock-token"]`, + `"lockTokens":\s*\[\s*"[^"]+"\s*\,\s*"[^"]+"\s*\]`, nil) + require.NoError(t, err) + + t.Cleanup(func() { + err := recording.Stop(t, nil) + require.NoError(t, err) + }) + + return transport +} + +func requireEqualCloudEvent(t *testing.T, expected *azeventgrid.CloudEvent, actual *azeventgrid.CloudEvent) { + t.Helper() + + require.NotEmpty(t, actual.ID, "ID is not empty") + require.NotEmpty(t, actual.SpecVersion, "SpecVersion is not empty") + + expected.ID = actual.ID + + if expected.SpecVersion == nil { + expected.SpecVersion = actual.SpecVersion + } + + require.Equal(t, actual, expected) +} + +var purge sync.Once + +func purgePreviousEvents(t *testing.T, c *azeventgrid.Client, testVars testVars) { + purge.Do(func() { + if recording.GetRecordMode() != recording.LiveMode { + return + } + + // we'll purge all the events in the queue just to ensure tests + // run clean. + events, err := c.ReceiveCloudEvents(context.Background(), testVars.Topic, testVars.Subscription, &azeventgrid.ReceiveCloudEventsOptions{ + MaxEvents: to.Ptr[int32](100), + MaxWaitTime: to.Ptr[int32](10), + }) + require.NoError(t, err) + + var lockTokens []*string + + for _, e := range events.Value { + lockTokens = append(lockTokens, e.BrokerProperties.LockToken) + } + + if len(lockTokens) > 0 { + resp, err := c.AcknowledgeCloudEvents(context.Background(), testVars.Topic, testVars.Subscription, azeventgrid.AcknowledgeOptions{ + LockTokens: lockTokens, + }, nil) + require.NoError(t, err) + require.Empty(t, resp.FailedLockTokens) + } + }) + +} diff --git a/sdk/messaging/azeventgrid/test-resources.bicep b/sdk/messaging/azeventgrid/test-resources.bicep new file mode 100644 index 000000000000..3f20c0681e00 --- /dev/null +++ b/sdk/messaging/azeventgrid/test-resources.bicep @@ -0,0 +1,61 @@ +@description('The base resource name.') +param baseName string = resourceGroup().name + +@description('The resource location') +param location string = resourceGroup().location + +var namespaceName = '${baseName}-2' +var topicName = 'testtopic1' +var subscriptionName = 'testsubscription1' + +resource ns_resource 'Microsoft.EventGrid/namespaces@2023-06-01-preview' = { + name: namespaceName + location: location + sku: { + name: 'Standard' + capacity: 1 + } + properties: { + isZoneRedundant: true + publicNetworkAccess: 'Enabled' + } +} + +resource ns_testtopic1 'Microsoft.EventGrid/namespaces/topics@2023-06-01-preview' = { + parent: ns_resource + name: topicName + properties: { + publisherType: 'Custom' + inputSchema: 'CloudEventSchemaV1_0' + eventRetentionInDays: 1 + } +} + +resource ns_testtopic1_testsubscription1 'Microsoft.EventGrid/namespaces/topics/eventSubscriptions@2023-06-01-preview' = { + parent: ns_testtopic1 + name: subscriptionName + properties: { + deliveryConfiguration: { + deliveryMode: 'Queue' + queue: { + receiveLockDurationInSeconds: 60 + maxDeliveryCount: 10 + eventTimeToLive: 'P1D' + } + } + eventDeliverySchema: 'CloudEventSchemaV1_0' + filtersConfiguration: { + includedEventTypes: [] + } + } +} + +// https://learn.microsoft.com/en-us/rest/api/eventgrid/controlplane-version2023-06-01-preview/namespaces/list-shared-access-keys?tabs=HTTP +output EVENTGRID_KEY string = listKeys(resourceId('Microsoft.EventGrid/namespaces', namespaceName), '2023-06-01-preview').key1 +// TODO: get this formatted properly +output EVENTGRID_ENDPOINT string= 'https://${ns_resource.properties.topicsConfiguration.hostname}' + +output EVENTGRID_TOPIC string = topicName +output EVENTGRID_SUBSCRIPTION string = subscriptionName +output RESOURCE_GROUP string = resourceGroup().name +output AZURE_SUBSCRIPTION_ID string = subscription().subscriptionId diff --git a/sdk/messaging/azeventgrid/time_rfc3339.go b/sdk/messaging/azeventgrid/time_rfc3339.go new file mode 100644 index 000000000000..dd6f013810cb --- /dev/null +++ b/sdk/messaging/azeventgrid/time_rfc3339.go @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +//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. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. +// DO NOT EDIT. + +package azeventgrid + +import ( + "encoding/json" + "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "reflect" + "regexp" + "strings" + "time" +) + +const ( + utcLayoutJSON = `"2006-01-02T15:04:05.999999999"` + utcLayout = "2006-01-02T15:04:05.999999999" + rfc3339JSON = `"` + time.RFC3339Nano + `"` +) + +// 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+)*"*$`) + +type timeRFC3339 time.Time + +func (t timeRFC3339) MarshalJSON() (json []byte, err error) { + tt := time.Time(t) + return tt.MarshalJSON() +} + +func (t timeRFC3339) MarshalText() (text []byte, err error) { + tt := time.Time(t) + return tt.MarshalText() +} + +func (t *timeRFC3339) UnmarshalJSON(data []byte) error { + layout := utcLayoutJSON + if tzOffsetRegex.Match(data) { + layout = rfc3339JSON + } + return t.Parse(layout, string(data)) +} + +func (t *timeRFC3339) UnmarshalText(data []byte) (err error) { + layout := utcLayout + if tzOffsetRegex.Match(data) { + layout = time.RFC3339Nano + } + return t.Parse(layout, string(data)) +} + +func (t *timeRFC3339) Parse(layout, value string) error { + p, err := time.Parse(layout, strings.ToUpper(value)) + *t = timeRFC3339(p) + return err +} + +func populateTimeRFC3339(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] = (*timeRFC3339)(t) +} + +func unpopulateTimeRFC3339(data json.RawMessage, fn string, t **time.Time) error { + if data == nil || strings.EqualFold(string(data), "null") { + return nil + } + var aux timeRFC3339 + if err := json.Unmarshal(data, &aux); err != nil { + return fmt.Errorf("struct field %s: %v", fn, err) + } + *t = (*time.Time)(&aux) + return nil +} diff --git a/sdk/messaging/azeventgrid/version.go b/sdk/messaging/azeventgrid/version.go new file mode 100644 index 000000000000..8cdb500a1f41 --- /dev/null +++ b/sdk/messaging/azeventgrid/version.go @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azeventgrid + +// 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" +)