From 00f2b8b13f56d1bc7a610dce500ade6e9df72a5e Mon Sep 17 00:00:00 2001 From: "Liangying.Wei" Date: Wed, 31 Jan 2024 22:52:48 +0800 Subject: [PATCH] Go SDK for Azure Web PubSub Data plane (#21929) * generated codespec for web pubsub * Updating readme and autorest * Adding custom client and test code * Add GenerateClientAccessUrl * Resolve comments * resolve comments * Update readme * fix some comments, remove hub parameter when constructing the client, and add HealthAPIClient constructions * Fix go vet error * Fix test failure * Fix comments * Adding recording * Use asset repo * Remove skip * Fix comment * adding main test logic to start the test-proxy * update CI settings * Fix test failure * Update link to temp URL to pass CI * Fix doc comment * Update CHANGELOG.md * resolve comments * Update sdk/messaging/azwebpubsub/ci.yml Co-authored-by: Rick Winter * Update sdk/messaging/azwebpubsub/sample.env Co-authored-by: Rick Winter * Resolving comments * resolve comments * remove healthclient * Resolve comments --------- Co-authored-by: MBSolomon <89044647+MBSolomon@users.noreply.github.com> Co-authored-by: Rick Winter --- sdk/messaging/azwebpubsub/CHANGELOG.md | 7 + sdk/messaging/azwebpubsub/LICENSE.txt | 21 + sdk/messaging/azwebpubsub/NOTICE.txt | 31 + sdk/messaging/azwebpubsub/README.md | 192 +++ sdk/messaging/azwebpubsub/assets.json | 6 + sdk/messaging/azwebpubsub/autorest.md | 75 + sdk/messaging/azwebpubsub/build.go | 10 + sdk/messaging/azwebpubsub/ci.yml | 30 + sdk/messaging/azwebpubsub/client.go | 1224 +++++++++++++++++ sdk/messaging/azwebpubsub/client_custom.go | 191 +++ .../azwebpubsub/client_shared_test.go | 205 +++ sdk/messaging/azwebpubsub/client_test.go | 170 +++ sdk/messaging/azwebpubsub/constants.go | 46 + sdk/messaging/azwebpubsub/example_test.go | 61 + sdk/messaging/azwebpubsub/go.mod | 28 + sdk/messaging/azwebpubsub/go.sum | 43 + .../internal/policy_key_credential.go | 40 + .../internal/policy_key_credential_test.go | 55 + sdk/messaging/azwebpubsub/internal/util.go | 73 + .../azwebpubsub/internal/util_test.go | 57 + sdk/messaging/azwebpubsub/internal/version.go | 18 + sdk/messaging/azwebpubsub/main_test.go | 41 + sdk/messaging/azwebpubsub/models.go | 33 + sdk/messaging/azwebpubsub/models_serde.go | 126 ++ sdk/messaging/azwebpubsub/options.go | 185 +++ sdk/messaging/azwebpubsub/response_types.go | 125 ++ sdk/messaging/azwebpubsub/sample.env | 3 + 27 files changed, 3096 insertions(+) create mode 100644 sdk/messaging/azwebpubsub/CHANGELOG.md create mode 100644 sdk/messaging/azwebpubsub/LICENSE.txt create mode 100644 sdk/messaging/azwebpubsub/NOTICE.txt create mode 100644 sdk/messaging/azwebpubsub/README.md create mode 100644 sdk/messaging/azwebpubsub/assets.json create mode 100644 sdk/messaging/azwebpubsub/autorest.md create mode 100644 sdk/messaging/azwebpubsub/build.go create mode 100644 sdk/messaging/azwebpubsub/ci.yml create mode 100644 sdk/messaging/azwebpubsub/client.go create mode 100644 sdk/messaging/azwebpubsub/client_custom.go create mode 100644 sdk/messaging/azwebpubsub/client_shared_test.go create mode 100644 sdk/messaging/azwebpubsub/client_test.go create mode 100644 sdk/messaging/azwebpubsub/constants.go create mode 100644 sdk/messaging/azwebpubsub/example_test.go create mode 100644 sdk/messaging/azwebpubsub/go.mod create mode 100644 sdk/messaging/azwebpubsub/go.sum create mode 100644 sdk/messaging/azwebpubsub/internal/policy_key_credential.go create mode 100644 sdk/messaging/azwebpubsub/internal/policy_key_credential_test.go create mode 100644 sdk/messaging/azwebpubsub/internal/util.go create mode 100644 sdk/messaging/azwebpubsub/internal/util_test.go create mode 100644 sdk/messaging/azwebpubsub/internal/version.go create mode 100644 sdk/messaging/azwebpubsub/main_test.go create mode 100644 sdk/messaging/azwebpubsub/models.go create mode 100644 sdk/messaging/azwebpubsub/models_serde.go create mode 100644 sdk/messaging/azwebpubsub/options.go create mode 100644 sdk/messaging/azwebpubsub/response_types.go create mode 100644 sdk/messaging/azwebpubsub/sample.env diff --git a/sdk/messaging/azwebpubsub/CHANGELOG.md b/sdk/messaging/azwebpubsub/CHANGELOG.md new file mode 100644 index 000000000000..37a1ea328c2c --- /dev/null +++ b/sdk/messaging/azwebpubsub/CHANGELOG.md @@ -0,0 +1,7 @@ +# Release History + +## 0.1.0 (2024-01-21) + +### Features Added + +- Initial preview for the Web PubSub Service diff --git a/sdk/messaging/azwebpubsub/LICENSE.txt b/sdk/messaging/azwebpubsub/LICENSE.txt new file mode 100644 index 000000000000..ce29e72a36df --- /dev/null +++ b/sdk/messaging/azwebpubsub/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. \ No newline at end of file diff --git a/sdk/messaging/azwebpubsub/NOTICE.txt b/sdk/messaging/azwebpubsub/NOTICE.txt new file mode 100644 index 000000000000..192a008914bf --- /dev/null +++ b/sdk/messaging/azwebpubsub/NOTICE.txt @@ -0,0 +1,31 @@ +azwebpubsub + +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. \ No newline at end of file diff --git a/sdk/messaging/azwebpubsub/README.md b/sdk/messaging/azwebpubsub/README.md new file mode 100644 index 000000000000..376c40aec973 --- /dev/null +++ b/sdk/messaging/azwebpubsub/README.md @@ -0,0 +1,192 @@ +# Azure Web PubSub service client library for Go + +[Azure Web PubSub service](https://aka.ms/awps/doc) is an Azure-managed service that helps developers easily build web applications with real-time features and publish-subscribe pattern. Any scenario that requires real-time publish-subscribe messaging between server and clients or among clients can use Azure Web PubSub service. Traditional real-time features that often require polling from server or submitting HTTP requests can also use Azure Web PubSub service. + +You can use this library in your app server side to manage the WebSocket client connections, as shown in below diagram: + +![overflow](https://user-images.githubusercontent.com/668244/140014067-25a00959-04dc-47e8-ac25-6957bd0a71ce.png). + +- Send messages to hubs and groups. +- Send messages to particular users and connections. +- Organize users and connections into groups. +- Close connections +- Grant, revoke, and check permissions for an existing connection + +Details about the terms used here are described in [Key concepts](#key-concepts) section. + +Key links: +- [Source code][source] +- [API Reference Documentation][godoc] +- [Product documentation][product] +- [Samples][godoc_examples] + +## Getting started + +### Install the package + +Install the Azure Web PubSub service client module for Go with `go get`: + +```bash +go get github.com/Azure/azure-sdk-for-go/sdk/messaging/azwebpubsub +``` + +### Prerequisites + +- Go, version 1.18 or higher +- An [Azure subscription](https://azure.microsoft.com/free/) +- An existing Azure Web PubSub service instance. + + +### Authenticate the client + +Web PubSub service clients are created using a TokenCredential from the [Azure Identity package][azure_identity_pkg], like [DefaultAzureCredential][default_azure_credential]. +You can also create a client using a connection string. + +#### Using a service principal + +Constructing the client requires your Web PubSub's endpoint URL, which you can get from the Azure Portal (`Host name` value on overview page with `https` scheme). + +```go +import ( + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azwebpubsub" + "log" +) + +func main() { + cred, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + log.Fatalf("failed to obtain a credential: %v", err) + } + + client, err := azwebpubsub.NewClient("", cred, nil) + if err != nil { + log.Fatalf("failed to create client: %v", err) + } +} +``` + +#### Using a connection string + +ConnectionString can be found in the **Keys** tab from your Web PubSub resource portal. + +```go +import ( + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azwebpubsub" + "log" +) + +func main() { + client, err := azwebpubsub.NewClientFromConnectionString("", nil) + if err != nil { + log.Fatalf("failed to create client: %v", err) + } +} +``` + +# Key concepts + +### Connection + +A connection, also known as a client or a client connection, represents an individual WebSocket connection connected to the Web PubSub service. When successfully connected, a unique connection ID is assigned to this connection by the Web PubSub service. + +### Hub + +A hub is a logical concept for a set of client connections. Usually you use one hub for one purpose, for example, a chat hub, or a notification hub. When a client connection is created, it connects to a hub, and during its lifetime, it belongs to that hub. Different applications can share one Azure Web PubSub service by using different hub names. + +### Group + +A group is a subset of connections to the hub. You can add a client connection to a group, or remove the client connection from the group, anytime you want. For example, when a client joins a chat room, or when a client leaves the chat room, this chat room can be considered to be a group. A client can join multiple groups, and a group can contain multiple clients. + +### User + +Connections to Web PubSub can belong to one user. A user might have multiple connections, for example when a single user is connected across multiple devices or multiple browser tabs. + +### Message + +When the client is connected, it can send messages to the upstream application, or receive messages from the upstream application, through the WebSocket connection. + +# 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 [azwebpubsub][source]. + +# Troubleshooting + +### Live Trace + +Use **Live Trace** from the Web PubSub service portal to view the live traffic. + +### 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 or to enable logs for `azwebpubsub` only. For example: + +```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) +}) + +// pick the set of events to log +azlog.SetEvents( + azwebpubsub +) +``` + +## 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 + +[azure_identity_pkg]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity +[default_azure_credential]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#NewDefaultAzureCredential +[source]: https://github.com/Azure/azure-sdk-for-go/tree/main/sdk +[godoc]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk +[godoc_examples]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go#pkg-examples +[product]: https://aka.ms/awps/doc diff --git a/sdk/messaging/azwebpubsub/assets.json b/sdk/messaging/azwebpubsub/assets.json new file mode 100644 index 000000000000..22cfa27c00cf --- /dev/null +++ b/sdk/messaging/azwebpubsub/assets.json @@ -0,0 +1,6 @@ +{ + "AssetsRepo": "Azure/azure-sdk-assets", + "AssetsRepoPrefixPath": "go", + "TagPrefix": "go/messaging/azwebpubsub", + "Tag": "go/messaging/azwebpubsub_94be93d99b" +} diff --git a/sdk/messaging/azwebpubsub/autorest.md b/sdk/messaging/azwebpubsub/autorest.md new file mode 100644 index 000000000000..8f81fd590e2c --- /dev/null +++ b/sdk/messaging/azwebpubsub/autorest.md @@ -0,0 +1,75 @@ +## Go + +```yaml +title: WebPubSub +description: Azure Web PubSub client +clear-output-folder: false +slice-elements-byval: true +remove-non-reference-schema: true +go: true +input-file: https://github.com/Azure/azure-rest-api-specs/blob/052a4b8d50bfd5595a8b5b506015d18f2b65998d/specification/webpubsub/data-plane/WebPubSub/stable/2023-07-01/webpubsub.json +license-header: MICROSOFT_MIT_NO_VERSION +module: github.com/Azure/azure-sdk-for-go/sdk/messaging/azwebpubsub +openapi-type: "data-plane" +output-folder: ../azwebpubsub +use: "@autorest/go@4.0.0-preview.60" +directive: + # Remove HealthAPI + - from: swagger-document + remove-operation: 'HealthApi_GetServiceStatus' + # Rename enum WebPubSubPermission to Permission since the package name already contains WebPubSub. + - from: + - constants.go + - client.go + where: $ + transform: return $.replace(/WebPubSubPermission/g, "Permission"); + # Make GenerateClientToken internal. + - from: client.go + where: $ + transform: return $.replace(/\bGenerateClientToken\b/g, "generateClientToken"); + # Make *Exists internal until SDK supports it. + - from: client.go + where: $ + transform: return $.replace(/\b(Group|Connection|User)Exists\b/g, function(match, group) { return group.toLowerCase() + "Exists";}); + # Make CheckPermission internal until SDK supports it, since it leverage 404 status code + - from: client.go + where: $ + transform: return $.replace(/\bCheckPermission\b/g, "checkPermission"); + # Add more properties to the client + - from: client.go + where: $ + transform: >- + return $.replace( + /(type Client struct[^}]+})/s, + "type Client struct {\n internal *azcore.Client\n endpoint string\n key *string\n}") + # Add comments to type Permission + - from: constants.go + where: $ + transform: >- + return $.replace( + /type Permission string/s, + "// Permission contains the allowed permissions\ntype Permission string") + # Add comments to InnerError + - from: models.go + where: $ + transform: >- + return $.replace( + /type InnerError struct/s, + "// InnerError - The inner error object\ntype InnerError struct") + # delete unused error models + - from: models.go + where: $ + transform: return $.replace(/(?:\/\/.*\s)+type (?:ErrorDetail|InnerError).+\{(?:\s.+\s)+\}\s/g, ""); + - from: models_serde.go + where: $ + transform: return $.replace(/(?:\/\/.*\s)+func \(\w \*?(?:ErrorDetail|InnerError)\).*\{\s(?:.+\s)+\}\s/g, ""); + # delete client name prefix from method options and response types + - from: + - client.go + - models.go + - models_serde.go + - options.go + - response_types.go + where: $ + transform: return $.replace(/Client(\w+)((?:Options|Response))/g, "$1$2"); +``` \ No newline at end of file diff --git a/sdk/messaging/azwebpubsub/build.go b/sdk/messaging/azwebpubsub/build.go new file mode 100644 index 000000000000..565585be5005 --- /dev/null +++ b/sdk/messaging/azwebpubsub/build.go @@ -0,0 +1,10 @@ +// 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 goimports -w . +//go:generate gofmt -w . + +package azwebpubsub diff --git a/sdk/messaging/azwebpubsub/ci.yml b/sdk/messaging/azwebpubsub/ci.yml new file mode 100644 index 000000000000..8710e949d058 --- /dev/null +++ b/sdk/messaging/azwebpubsub/ci.yml @@ -0,0 +1,30 @@ +# 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/azwebpubsub/ + +pr: + branches: + include: + - main + - feature/* + - hotfix/* + - release/* + paths: + include: + - sdk/messaging/azwebpubsub/ + + +stages: +- template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml + parameters: + ServiceDirectory: 'messaging/azwebpubsub' + UsePipelineProxy: false + \ No newline at end of file diff --git a/sdk/messaging/azwebpubsub/client.go b/sdk/messaging/azwebpubsub/client.go new file mode 100644 index 000000000000..5d3b0ec64a2e --- /dev/null +++ b/sdk/messaging/azwebpubsub/client.go @@ -0,0 +1,1224 @@ +//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 azwebpubsub + +import ( + "context" + "errors" + "io" + "net/http" + "net/url" + "strconv" + "strings" + + "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" +) + +// Client contains the methods for the WebPubSub group. +// Don't use this type directly, use a constructor function instead. +type Client struct { + internal *azcore.Client + endpoint string + key *string +} + +// AddConnectionToGroup - Add a connection to the target group. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - group - Target group name, which length should be greater than 0 and less than 1025. +// - connectionID - Target connection Id +// - options - AddConnectionToGroupOptions contains the optional parameters for the Client.AddConnectionToGroup method. +func (client *Client) AddConnectionToGroup(ctx context.Context, hub string, group string, connectionID string, options *AddConnectionToGroupOptions) (AddConnectionToGroupResponse, error) { + var err error + req, err := client.addConnectionToGroupCreateRequest(ctx, hub, group, connectionID, options) + if err != nil { + return AddConnectionToGroupResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return AddConnectionToGroupResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = runtime.NewResponseError(httpResp) + return AddConnectionToGroupResponse{}, err + } + return AddConnectionToGroupResponse{}, nil +} + +// addConnectionToGroupCreateRequest creates the AddConnectionToGroup request. +func (client *Client) addConnectionToGroupCreateRequest(ctx context.Context, hub string, group string, connectionID string, options *AddConnectionToGroupOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/groups/{group}/connections/{connectionId}" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if group == "" { + return nil, errors.New("parameter group cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{group}", url.PathEscape(group)) + if connectionID == "" { + return nil, errors.New("parameter connectionID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{connectionId}", url.PathEscape(connectionID)) + req, err := runtime.NewRequest(ctx, http.MethodPut, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// AddConnectionsToGroups - Add filtered connections to multiple groups. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - groupsToAdd - Target groups and connection filter. +// - options - AddConnectionsToGroupsOptions contains the optional parameters for the Client.AddConnectionsToGroups method. +func (client *Client) AddConnectionsToGroups(ctx context.Context, hub string, groupsToAdd AddToGroupsRequest, options *AddConnectionsToGroupsOptions) (AddConnectionsToGroupsResponse, error) { + var err error + req, err := client.addConnectionsToGroupsCreateRequest(ctx, hub, groupsToAdd, options) + if err != nil { + return AddConnectionsToGroupsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return AddConnectionsToGroupsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = runtime.NewResponseError(httpResp) + return AddConnectionsToGroupsResponse{}, err + } + return AddConnectionsToGroupsResponse{}, nil +} + +// addConnectionsToGroupsCreateRequest creates the AddConnectionsToGroups request. +func (client *Client) addConnectionsToGroupsCreateRequest(ctx context.Context, hub string, groupsToAdd AddToGroupsRequest, options *AddConnectionsToGroupsOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/:addToGroups" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + 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-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + if err := runtime.MarshalAsJSON(req, groupsToAdd); err != nil { + return nil, err + } + return req, nil +} + +// AddUserToGroup - Add a user to the target group. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - group - Target group name, which length should be greater than 0 and less than 1025. +// - userID - Target user Id. +// - options - AddUserToGroupOptions contains the optional parameters for the Client.AddUserToGroup method. +func (client *Client) AddUserToGroup(ctx context.Context, hub string, group string, userID string, options *AddUserToGroupOptions) (AddUserToGroupResponse, error) { + var err error + req, err := client.addUserToGroupCreateRequest(ctx, hub, group, userID, options) + if err != nil { + return AddUserToGroupResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return AddUserToGroupResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = runtime.NewResponseError(httpResp) + return AddUserToGroupResponse{}, err + } + return AddUserToGroupResponse{}, nil +} + +// addUserToGroupCreateRequest creates the AddUserToGroup request. +func (client *Client) addUserToGroupCreateRequest(ctx context.Context, hub string, group string, userID string, options *AddUserToGroupOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/users/{userId}/groups/{group}" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if group == "" { + return nil, errors.New("parameter group cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{group}", url.PathEscape(group)) + if userID == "" { + return nil, errors.New("parameter userID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{userId}", url.PathEscape(userID)) + req, err := runtime.NewRequest(ctx, http.MethodPut, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// checkPermission - Check if a connection has permission to the specified action. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - permission - The permission: current supported actions are joinLeaveGroup and sendToGroup. +// - connectionID - Target connection Id. +// - options - CheckPermissionOptions contains the optional parameters for the Client.checkPermission method. +func (client *Client) checkPermission(ctx context.Context, hub string, permission Permission, connectionID string, options *CheckPermissionOptions) (CheckPermissionResponse, error) { + var err error + req, err := client.checkPermissionCreateRequest(ctx, hub, permission, connectionID, options) + if err != nil { + return CheckPermissionResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return CheckPermissionResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK, http.StatusNotFound) { + err = runtime.NewResponseError(httpResp) + return CheckPermissionResponse{}, err + } + return CheckPermissionResponse{}, nil +} + +// checkPermissionCreateRequest creates the checkPermission request. +func (client *Client) checkPermissionCreateRequest(ctx context.Context, hub string, permission Permission, connectionID string, options *CheckPermissionOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/permissions/{permission}/connections/{connectionId}" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if permission == "" { + return nil, errors.New("parameter permission cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{permission}", url.PathEscape(string(permission))) + if connectionID == "" { + return nil, errors.New("parameter connectionID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{connectionId}", url.PathEscape(connectionID)) + req, err := runtime.NewRequest(ctx, http.MethodHead, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + if options != nil && options.TargetName != nil { + reqQP.Set("targetName", *options.TargetName) + } + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + return req, nil +} + +// CloseAllConnections - Close the connections in the hub. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - options - CloseAllConnectionsOptions contains the optional parameters for the Client.CloseAllConnections method. +func (client *Client) CloseAllConnections(ctx context.Context, hub string, options *CloseAllConnectionsOptions) (CloseAllConnectionsResponse, error) { + var err error + req, err := client.closeAllConnectionsCreateRequest(ctx, hub, options) + if err != nil { + return CloseAllConnectionsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return CloseAllConnectionsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusNoContent) { + err = runtime.NewResponseError(httpResp) + return CloseAllConnectionsResponse{}, err + } + return CloseAllConnectionsResponse{}, nil +} + +// closeAllConnectionsCreateRequest creates the CloseAllConnections request. +func (client *Client) closeAllConnectionsCreateRequest(ctx context.Context, hub string, options *CloseAllConnectionsOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/:closeConnections" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + req, err := runtime.NewRequest(ctx, http.MethodPost, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + if options != nil && options.Excluded != nil { + for _, qv := range options.Excluded { + reqQP.Add("excluded", qv) + } + } + if options != nil && options.Reason != nil { + reqQP.Set("reason", *options.Reason) + } + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// CloseConnection - Close the client connection. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - connectionID - Target connection Id. +// - options - CloseConnectionOptions contains the optional parameters for the Client.CloseConnection method. +func (client *Client) CloseConnection(ctx context.Context, hub string, connectionID string, options *CloseConnectionOptions) (CloseConnectionResponse, error) { + var err error + req, err := client.closeConnectionCreateRequest(ctx, hub, connectionID, options) + if err != nil { + return CloseConnectionResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return CloseConnectionResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusNoContent) { + err = runtime.NewResponseError(httpResp) + return CloseConnectionResponse{}, err + } + return CloseConnectionResponse{}, nil +} + +// closeConnectionCreateRequest creates the CloseConnection request. +func (client *Client) closeConnectionCreateRequest(ctx context.Context, hub string, connectionID string, options *CloseConnectionOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/connections/{connectionId}" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if connectionID == "" { + return nil, errors.New("parameter connectionID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{connectionId}", url.PathEscape(connectionID)) + req, err := runtime.NewRequest(ctx, http.MethodDelete, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + if options != nil && options.Reason != nil { + reqQP.Set("reason", *options.Reason) + } + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// CloseGroupConnections - Close connections in the specific group. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - group - Target group name, which length should be greater than 0 and less than 1025. +// - options - CloseGroupConnectionsOptions contains the optional parameters for the Client.CloseGroupConnections method. +func (client *Client) CloseGroupConnections(ctx context.Context, hub string, group string, options *CloseGroupConnectionsOptions) (CloseGroupConnectionsResponse, error) { + var err error + req, err := client.closeGroupConnectionsCreateRequest(ctx, hub, group, options) + if err != nil { + return CloseGroupConnectionsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return CloseGroupConnectionsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusNoContent) { + err = runtime.NewResponseError(httpResp) + return CloseGroupConnectionsResponse{}, err + } + return CloseGroupConnectionsResponse{}, nil +} + +// closeGroupConnectionsCreateRequest creates the CloseGroupConnections request. +func (client *Client) closeGroupConnectionsCreateRequest(ctx context.Context, hub string, group string, options *CloseGroupConnectionsOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/groups/{group}/:closeConnections" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if group == "" { + return nil, errors.New("parameter group cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{group}", url.PathEscape(group)) + req, err := runtime.NewRequest(ctx, http.MethodPost, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + if options != nil && options.Excluded != nil { + for _, qv := range options.Excluded { + reqQP.Add("excluded", qv) + } + } + if options != nil && options.Reason != nil { + reqQP.Set("reason", *options.Reason) + } + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// CloseUserConnections - Close connections for the specific user. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - userID - The user Id. +// - options - CloseUserConnectionsOptions contains the optional parameters for the Client.CloseUserConnections method. +func (client *Client) CloseUserConnections(ctx context.Context, hub string, userID string, options *CloseUserConnectionsOptions) (CloseUserConnectionsResponse, error) { + var err error + req, err := client.closeUserConnectionsCreateRequest(ctx, hub, userID, options) + if err != nil { + return CloseUserConnectionsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return CloseUserConnectionsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusNoContent) { + err = runtime.NewResponseError(httpResp) + return CloseUserConnectionsResponse{}, err + } + return CloseUserConnectionsResponse{}, nil +} + +// closeUserConnectionsCreateRequest creates the CloseUserConnections request. +func (client *Client) closeUserConnectionsCreateRequest(ctx context.Context, hub string, userID string, options *CloseUserConnectionsOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/users/{userId}/:closeConnections" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if userID == "" { + return nil, errors.New("parameter userID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{userId}", url.PathEscape(userID)) + req, err := runtime.NewRequest(ctx, http.MethodPost, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + if options != nil && options.Excluded != nil { + for _, qv := range options.Excluded { + reqQP.Add("excluded", qv) + } + } + if options != nil && options.Reason != nil { + reqQP.Set("reason", *options.Reason) + } + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// connectionExists - Check if the connection with the given connectionId exists. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - connectionID - The connection Id. +// - options - ConnectionExistsOptions contains the optional parameters for the Client.connectionExists method. +func (client *Client) connectionExists(ctx context.Context, hub string, connectionID string, options *ConnectionExistsOptions) (ConnectionExistsResponse, error) { + var err error + req, err := client.connectionExistsCreateRequest(ctx, hub, connectionID, options) + if err != nil { + return ConnectionExistsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return ConnectionExistsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK, http.StatusNotFound) { + err = runtime.NewResponseError(httpResp) + return ConnectionExistsResponse{}, err + } + return ConnectionExistsResponse{}, nil +} + +// connectionExistsCreateRequest creates the connectionExists request. +func (client *Client) connectionExistsCreateRequest(ctx context.Context, hub string, connectionID string, options *ConnectionExistsOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/connections/{connectionId}" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if connectionID == "" { + return nil, errors.New("parameter connectionID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{connectionId}", url.PathEscape(connectionID)) + req, err := runtime.NewRequest(ctx, http.MethodHead, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + return req, nil +} + +// generateClientToken - Generate token for the client to connect Azure Web PubSub service. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - options - GenerateClientTokenOptions contains the optional parameters for the Client.generateClientToken method. +func (client *Client) generateClientToken(ctx context.Context, hub string, options *GenerateClientTokenOptions) (GenerateClientTokenResponse, error) { + var err error + req, err := client.generateClientTokenCreateRequest(ctx, hub, options) + if err != nil { + return GenerateClientTokenResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return GenerateClientTokenResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = runtime.NewResponseError(httpResp) + return GenerateClientTokenResponse{}, err + } + resp, err := client.generateTokenHandleResponse(httpResp) + return resp, err +} + +// generateClientTokenCreateRequest creates the generateClientToken request. +func (client *Client) generateClientTokenCreateRequest(ctx context.Context, hub string, options *GenerateClientTokenOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/:generateToken" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + req, err := runtime.NewRequest(ctx, http.MethodPost, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + if options != nil && options.UserID != nil { + reqQP.Set("userId", *options.UserID) + } + if options != nil && options.Role != nil { + for _, qv := range options.Role { + reqQP.Add("role", qv) + } + } + if options != nil && options.MinutesToExpire != nil { + reqQP.Set("minutesToExpire", strconv.FormatInt(int64(*options.MinutesToExpire), 10)) + } + reqQP.Set("api-version", "2023-07-01") + if options != nil && options.Group != nil { + for _, qv := range options.Group { + reqQP.Add("group", qv) + } + } + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json, text/json"} + return req, nil +} + +// generateTokenHandleResponse handles the generateClientToken response. +func (client *Client) generateTokenHandleResponse(resp *http.Response) (GenerateClientTokenResponse, error) { + result := GenerateClientTokenResponse{} + if err := runtime.UnmarshalAsJSON(resp, &result.TokenResponse); err != nil { + return GenerateClientTokenResponse{}, err + } + return result, nil +} + +// GrantPermission - Grant permission to the connection. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - permission - The permission: current supported actions are joinLeaveGroup and sendToGroup. +// - connectionID - Target connection Id. +// - options - GrantPermissionOptions contains the optional parameters for the Client.GrantPermission method. +func (client *Client) GrantPermission(ctx context.Context, hub string, permission Permission, connectionID string, options *GrantPermissionOptions) (GrantPermissionResponse, error) { + var err error + req, err := client.grantPermissionCreateRequest(ctx, hub, permission, connectionID, options) + if err != nil { + return GrantPermissionResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return GrantPermissionResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = runtime.NewResponseError(httpResp) + return GrantPermissionResponse{}, err + } + return GrantPermissionResponse{}, nil +} + +// grantPermissionCreateRequest creates the GrantPermission request. +func (client *Client) grantPermissionCreateRequest(ctx context.Context, hub string, permission Permission, connectionID string, options *GrantPermissionOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/permissions/{permission}/connections/{connectionId}" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if permission == "" { + return nil, errors.New("parameter permission cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{permission}", url.PathEscape(string(permission))) + if connectionID == "" { + return nil, errors.New("parameter connectionID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{connectionId}", url.PathEscape(connectionID)) + req, err := runtime.NewRequest(ctx, http.MethodPut, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + if options != nil && options.TargetName != nil { + reqQP.Set("targetName", *options.TargetName) + } + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// groupExists - Check if there are any client connections inside the given group +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - group - Target group name, which length should be greater than 0 and less than 1025. +// - options - GroupExistsOptions contains the optional parameters for the Client.groupExists method. +func (client *Client) groupExists(ctx context.Context, hub string, group string, options *GroupExistsOptions) (GroupExistsResponse, error) { + var err error + req, err := client.groupExistsCreateRequest(ctx, hub, group, options) + if err != nil { + return GroupExistsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return GroupExistsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK, http.StatusNotFound) { + err = runtime.NewResponseError(httpResp) + return GroupExistsResponse{}, err + } + return GroupExistsResponse{}, nil +} + +// groupExistsCreateRequest creates the groupExists request. +func (client *Client) groupExistsCreateRequest(ctx context.Context, hub string, group string, options *GroupExistsOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/groups/{group}" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if group == "" { + return nil, errors.New("parameter group cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{group}", url.PathEscape(group)) + req, err := runtime.NewRequest(ctx, http.MethodHead, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + return req, nil +} + +// RemoveConnectionFromAllGroups - Remove a connection from all groups. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - connectionID - Target connection Id. +// - options - RemoveConnectionFromAllGroupsOptions contains the optional parameters for the Client.RemoveConnectionFromAllGroups +// method. +func (client *Client) RemoveConnectionFromAllGroups(ctx context.Context, hub string, connectionID string, options *RemoveConnectionFromAllGroupsOptions) (RemoveConnectionFromAllGroupsResponse, error) { + var err error + req, err := client.removeConnectionFromAllGroupsCreateRequest(ctx, hub, connectionID, options) + if err != nil { + return RemoveConnectionFromAllGroupsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return RemoveConnectionFromAllGroupsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusNoContent) { + err = runtime.NewResponseError(httpResp) + return RemoveConnectionFromAllGroupsResponse{}, err + } + return RemoveConnectionFromAllGroupsResponse{}, nil +} + +// removeConnectionFromAllGroupsCreateRequest creates the RemoveConnectionFromAllGroups request. +func (client *Client) removeConnectionFromAllGroupsCreateRequest(ctx context.Context, hub string, connectionID string, options *RemoveConnectionFromAllGroupsOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/connections/{connectionId}/groups" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if connectionID == "" { + return nil, errors.New("parameter connectionID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{connectionId}", url.PathEscape(connectionID)) + req, err := runtime.NewRequest(ctx, http.MethodDelete, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// RemoveConnectionFromGroup - Remove a connection from the target group. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - group - Target group name, which length should be greater than 0 and less than 1025. +// - connectionID - Target connection Id. +// - options - RemoveConnectionFromGroupOptions contains the optional parameters for the Client.RemoveConnectionFromGroup +// method. +func (client *Client) RemoveConnectionFromGroup(ctx context.Context, hub string, group string, connectionID string, options *RemoveConnectionFromGroupOptions) (RemoveConnectionFromGroupResponse, error) { + var err error + req, err := client.removeConnectionFromGroupCreateRequest(ctx, hub, group, connectionID, options) + if err != nil { + return RemoveConnectionFromGroupResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return RemoveConnectionFromGroupResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusNoContent) { + err = runtime.NewResponseError(httpResp) + return RemoveConnectionFromGroupResponse{}, err + } + return RemoveConnectionFromGroupResponse{}, nil +} + +// removeConnectionFromGroupCreateRequest creates the RemoveConnectionFromGroup request. +func (client *Client) removeConnectionFromGroupCreateRequest(ctx context.Context, hub string, group string, connectionID string, options *RemoveConnectionFromGroupOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/groups/{group}/connections/{connectionId}" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if group == "" { + return nil, errors.New("parameter group cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{group}", url.PathEscape(group)) + if connectionID == "" { + return nil, errors.New("parameter connectionID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{connectionId}", url.PathEscape(connectionID)) + req, err := runtime.NewRequest(ctx, http.MethodDelete, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// RemoveConnectionsFromGroups - Remove filtered connections from multiple groups. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - groupsToRemove - Target groups and connection filter. +// - options - RemoveConnectionsFromGroupsOptions contains the optional parameters for the Client.RemoveConnectionsFromGroups +// method. +func (client *Client) RemoveConnectionsFromGroups(ctx context.Context, hub string, groupsToRemove RemoveFromGroupsRequest, options *RemoveConnectionsFromGroupsOptions) (RemoveConnectionsFromGroupsResponse, error) { + var err error + req, err := client.removeConnectionsFromGroupsCreateRequest(ctx, hub, groupsToRemove, options) + if err != nil { + return RemoveConnectionsFromGroupsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return RemoveConnectionsFromGroupsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = runtime.NewResponseError(httpResp) + return RemoveConnectionsFromGroupsResponse{}, err + } + return RemoveConnectionsFromGroupsResponse{}, nil +} + +// removeConnectionsFromGroupsCreateRequest creates the RemoveConnectionsFromGroups request. +func (client *Client) removeConnectionsFromGroupsCreateRequest(ctx context.Context, hub string, groupsToRemove RemoveFromGroupsRequest, options *RemoveConnectionsFromGroupsOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/:removeFromGroups" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + 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-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + if err := runtime.MarshalAsJSON(req, groupsToRemove); err != nil { + return nil, err + } + return req, nil +} + +// RemoveUserFromAllGroups - Remove a user from all groups. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - userID - Target user Id. +// - options - RemoveUserFromAllGroupsOptions contains the optional parameters for the Client.RemoveUserFromAllGroups +// method. +func (client *Client) RemoveUserFromAllGroups(ctx context.Context, hub string, userID string, options *RemoveUserFromAllGroupsOptions) (RemoveUserFromAllGroupsResponse, error) { + var err error + req, err := client.removeUserFromAllGroupsCreateRequest(ctx, hub, userID, options) + if err != nil { + return RemoveUserFromAllGroupsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return RemoveUserFromAllGroupsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusNoContent) { + err = runtime.NewResponseError(httpResp) + return RemoveUserFromAllGroupsResponse{}, err + } + return RemoveUserFromAllGroupsResponse{}, nil +} + +// removeUserFromAllGroupsCreateRequest creates the RemoveUserFromAllGroups request. +func (client *Client) removeUserFromAllGroupsCreateRequest(ctx context.Context, hub string, userID string, options *RemoveUserFromAllGroupsOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/users/{userId}/groups" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if userID == "" { + return nil, errors.New("parameter userID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{userId}", url.PathEscape(userID)) + req, err := runtime.NewRequest(ctx, http.MethodDelete, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// RemoveUserFromGroup - Remove a user from the target group. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - group - Target group name, which length should be greater than 0 and less than 1025. +// - userID - Target user Id. +// - options - RemoveUserFromGroupOptions contains the optional parameters for the Client.RemoveUserFromGroup method. +func (client *Client) RemoveUserFromGroup(ctx context.Context, hub string, group string, userID string, options *RemoveUserFromGroupOptions) (RemoveUserFromGroupResponse, error) { + var err error + req, err := client.removeUserFromGroupCreateRequest(ctx, hub, group, userID, options) + if err != nil { + return RemoveUserFromGroupResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return RemoveUserFromGroupResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusNoContent) { + err = runtime.NewResponseError(httpResp) + return RemoveUserFromGroupResponse{}, err + } + return RemoveUserFromGroupResponse{}, nil +} + +// removeUserFromGroupCreateRequest creates the RemoveUserFromGroup request. +func (client *Client) removeUserFromGroupCreateRequest(ctx context.Context, hub string, group string, userID string, options *RemoveUserFromGroupOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/users/{userId}/groups/{group}" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if group == "" { + return nil, errors.New("parameter group cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{group}", url.PathEscape(group)) + if userID == "" { + return nil, errors.New("parameter userID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{userId}", url.PathEscape(userID)) + req, err := runtime.NewRequest(ctx, http.MethodDelete, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// RevokePermission - Revoke permission for the connection. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - permission - The permission: current supported actions are joinLeaveGroup and sendToGroup. +// - connectionID - Target connection Id. +// - options - RevokePermissionOptions contains the optional parameters for the Client.RevokePermission method. +func (client *Client) RevokePermission(ctx context.Context, hub string, permission Permission, connectionID string, options *RevokePermissionOptions) (RevokePermissionResponse, error) { + var err error + req, err := client.revokePermissionCreateRequest(ctx, hub, permission, connectionID, options) + if err != nil { + return RevokePermissionResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return RevokePermissionResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusNoContent) { + err = runtime.NewResponseError(httpResp) + return RevokePermissionResponse{}, err + } + return RevokePermissionResponse{}, nil +} + +// revokePermissionCreateRequest creates the RevokePermission request. +func (client *Client) revokePermissionCreateRequest(ctx context.Context, hub string, permission Permission, connectionID string, options *RevokePermissionOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/permissions/{permission}/connections/{connectionId}" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if permission == "" { + return nil, errors.New("parameter permission cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{permission}", url.PathEscape(string(permission))) + if connectionID == "" { + return nil, errors.New("parameter connectionID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{connectionId}", url.PathEscape(connectionID)) + req, err := runtime.NewRequest(ctx, http.MethodDelete, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + if options != nil && options.TargetName != nil { + reqQP.Set("targetName", *options.TargetName) + } + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// SendToAll - Broadcast content inside request body to all the connected client connections. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - contentType - Upload file type +// - message - The payload body. +// - options - SendToAllOptions contains the optional parameters for the Client.SendToAll method. +func (client *Client) SendToAll(ctx context.Context, hub string, contentType ContentType, message io.ReadSeekCloser, options *SendToAllOptions) (SendToAllResponse, error) { + var err error + req, err := client.sendToAllCreateRequest(ctx, hub, contentType, message, options) + if err != nil { + return SendToAllResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return SendToAllResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusAccepted) { + err = runtime.NewResponseError(httpResp) + return SendToAllResponse{}, err + } + return SendToAllResponse{}, nil +} + +// sendToAllCreateRequest creates the SendToAll request. +func (client *Client) sendToAllCreateRequest(ctx context.Context, hub string, contentType ContentType, message io.ReadSeekCloser, options *SendToAllOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/:send" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + req, err := runtime.NewRequest(ctx, http.MethodPost, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + if options != nil && options.Excluded != nil { + for _, qv := range options.Excluded { + reqQP.Add("excluded", qv) + } + } + reqQP.Set("api-version", "2023-07-01") + if options != nil && options.Filter != nil { + reqQP.Set("filter", *options.Filter) + } + if options != nil && options.MessageTTLSeconds != nil { + reqQP.Set("messageTtlSeconds", strconv.FormatInt(int64(*options.MessageTTLSeconds), 10)) + } + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Content-Type"] = []string{string(contentType)} + req.Raw().Header["Accept"] = []string{"application/json"} + if err := req.SetBody(message, string(contentType)); err != nil { + return nil, err + } + return req, nil +} + +// SendToConnection - Send content inside request body to the specific connection. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - connectionID - The connection Id. +// - contentType - Upload file type +// - message - The payload body. +// - options - SendToConnectionOptions contains the optional parameters for the Client.SendToConnection method. +func (client *Client) SendToConnection(ctx context.Context, hub string, connectionID string, contentType ContentType, message io.ReadSeekCloser, options *SendToConnectionOptions) (SendToConnectionResponse, error) { + var err error + req, err := client.sendToConnectionCreateRequest(ctx, hub, connectionID, contentType, message, options) + if err != nil { + return SendToConnectionResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return SendToConnectionResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusAccepted) { + err = runtime.NewResponseError(httpResp) + return SendToConnectionResponse{}, err + } + return SendToConnectionResponse{}, nil +} + +// sendToConnectionCreateRequest creates the SendToConnection request. +func (client *Client) sendToConnectionCreateRequest(ctx context.Context, hub string, connectionID string, contentType ContentType, message io.ReadSeekCloser, options *SendToConnectionOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/connections/{connectionId}/:send" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if connectionID == "" { + return nil, errors.New("parameter connectionID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{connectionId}", url.PathEscape(connectionID)) + 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-07-01") + if options != nil && options.MessageTTLSeconds != nil { + reqQP.Set("messageTtlSeconds", strconv.FormatInt(int64(*options.MessageTTLSeconds), 10)) + } + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Content-Type"] = []string{string(contentType)} + req.Raw().Header["Accept"] = []string{"application/json"} + if err := req.SetBody(message, string(contentType)); err != nil { + return nil, err + } + return req, nil +} + +// SendToGroup - Send content inside request body to a group of connections. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - group - Target group name, which length should be greater than 0 and less than 1025. +// - contentType - Upload file type +// - message - The payload body. +// - options - SendToGroupOptions contains the optional parameters for the Client.SendToGroup method. +func (client *Client) SendToGroup(ctx context.Context, hub string, group string, contentType ContentType, message io.ReadSeekCloser, options *SendToGroupOptions) (SendToGroupResponse, error) { + var err error + req, err := client.sendToGroupCreateRequest(ctx, hub, group, contentType, message, options) + if err != nil { + return SendToGroupResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return SendToGroupResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusAccepted) { + err = runtime.NewResponseError(httpResp) + return SendToGroupResponse{}, err + } + return SendToGroupResponse{}, nil +} + +// sendToGroupCreateRequest creates the SendToGroup request. +func (client *Client) sendToGroupCreateRequest(ctx context.Context, hub string, group string, contentType ContentType, message io.ReadSeekCloser, options *SendToGroupOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/groups/{group}/:send" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if group == "" { + return nil, errors.New("parameter group cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{group}", url.PathEscape(group)) + req, err := runtime.NewRequest(ctx, http.MethodPost, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + if options != nil && options.Excluded != nil { + for _, qv := range options.Excluded { + reqQP.Add("excluded", qv) + } + } + reqQP.Set("api-version", "2023-07-01") + if options != nil && options.Filter != nil { + reqQP.Set("filter", *options.Filter) + } + if options != nil && options.MessageTTLSeconds != nil { + reqQP.Set("messageTtlSeconds", strconv.FormatInt(int64(*options.MessageTTLSeconds), 10)) + } + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Content-Type"] = []string{string(contentType)} + req.Raw().Header["Accept"] = []string{"application/json"} + if err := req.SetBody(message, string(contentType)); err != nil { + return nil, err + } + return req, nil +} + +// SendToUser - Send content inside request body to the specific user. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - userID - The user Id. +// - contentType - Upload file type +// - message - The payload body. +// - options - SendToUserOptions contains the optional parameters for the Client.SendToUser method. +func (client *Client) SendToUser(ctx context.Context, hub string, userID string, contentType ContentType, message io.ReadSeekCloser, options *SendToUserOptions) (SendToUserResponse, error) { + var err error + req, err := client.sendToUserCreateRequest(ctx, hub, userID, contentType, message, options) + if err != nil { + return SendToUserResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return SendToUserResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusAccepted) { + err = runtime.NewResponseError(httpResp) + return SendToUserResponse{}, err + } + return SendToUserResponse{}, nil +} + +// sendToUserCreateRequest creates the SendToUser request. +func (client *Client) sendToUserCreateRequest(ctx context.Context, hub string, userID string, contentType ContentType, message io.ReadSeekCloser, options *SendToUserOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/users/{userId}/:send" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if userID == "" { + return nil, errors.New("parameter userID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{userId}", url.PathEscape(userID)) + 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-07-01") + if options != nil && options.Filter != nil { + reqQP.Set("filter", *options.Filter) + } + if options != nil && options.MessageTTLSeconds != nil { + reqQP.Set("messageTtlSeconds", strconv.FormatInt(int64(*options.MessageTTLSeconds), 10)) + } + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Content-Type"] = []string{string(contentType)} + req.Raw().Header["Accept"] = []string{"application/json"} + if err := req.SetBody(message, string(contentType)); err != nil { + return nil, err + } + return req, nil +} + +// userExists - Check if there are any client connections connected for the given user. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01 +// - hub - Target hub name, which should start with alphabetic characters and only contain alpha-numeric characters or underscore. +// - userID - Target user Id. +// - options - UserExistsOptions contains the optional parameters for the Client.userExists method. +func (client *Client) userExists(ctx context.Context, hub string, userID string, options *UserExistsOptions) (UserExistsResponse, error) { + var err error + req, err := client.userExistsCreateRequest(ctx, hub, userID, options) + if err != nil { + return UserExistsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return UserExistsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK, http.StatusNotFound) { + err = runtime.NewResponseError(httpResp) + return UserExistsResponse{}, err + } + return UserExistsResponse{}, nil +} + +// userExistsCreateRequest creates the userExists request. +func (client *Client) userExistsCreateRequest(ctx context.Context, hub string, userID string, options *UserExistsOptions) (*policy.Request, error) { + urlPath := "/api/hubs/{hub}/users/{userId}" + if hub == "" { + return nil, errors.New("parameter hub cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{hub}", url.PathEscape(hub)) + if userID == "" { + return nil, errors.New("parameter userID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{userId}", url.PathEscape(userID)) + req, err := runtime.NewRequest(ctx, http.MethodHead, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-07-01") + req.Raw().URL.RawQuery = reqQP.Encode() + return req, nil +} diff --git a/sdk/messaging/azwebpubsub/client_custom.go b/sdk/messaging/azwebpubsub/client_custom.go new file mode 100644 index 000000000000..590db8c95e4c --- /dev/null +++ b/sdk/messaging/azwebpubsub/client_custom.go @@ -0,0 +1,191 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azwebpubsub + +import ( + "context" + "errors" + "fmt" + "net/url" + "strings" + "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/azcore/runtime" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azwebpubsub/internal" + "github.com/golang-jwt/jwt" +) + +const defaultExpirationTime = time.Hour + +// ClientOptions contains optional settings for [Client] +type ClientOptions struct { + azcore.ClientOptions +} + +// NewClient creates a client that manages Web PubSub service +func NewClient(endpoint string, credential azcore.TokenCredential, options *ClientOptions) (*Client, error) { + if options == nil { + options = &ClientOptions{} + } + + authPolicy := runtime.NewBearerTokenPolicy(credential, []string{internal.TokenScope}, nil) + azcoreClient, err := azcore.NewClient(internal.ModuleName, internal.ModuleVersion, + runtime.PipelineOptions{PerRetry: []policy.Policy{authPolicy}}, &options.ClientOptions) + if err != nil { + return nil, err + } + return &Client{ + internal: azcoreClient, + endpoint: endpoint, + }, nil +} + +// NewClientFromConnectionString creates a Client from a connection string +func NewClientFromConnectionString(connectionString string, options *ClientOptions) (*Client, error) { + if options == nil { + options = &ClientOptions{} + } + + props, err := internal.ParseConnectionString(connectionString) + + if err != nil { + return nil, err + } + + azcoreClient, err := azcore.NewClient(internal.ModuleName, internal.ModuleVersion, runtime.PipelineOptions{ + PerRetry: []policy.Policy{internal.NewWebPubSubKeyCredentialPolicy(props.AccessKey)}, + }, &options.ClientOptions) + + if err != nil { + return nil, err + } + + return &Client{ + internal: azcoreClient, + endpoint: props.Endpoint, + key: &props.AccessKey, + }, nil +} + +// GenerateClientAccessURLOptions represents the options for generating a client access url +type GenerateClientAccessURLOptions struct { + // UserID is the user ID for the client. + UserID string + + // Roles are the roles that the connection with the generated token will have. + // Roles give the client initial permissions to leave, join, or publish to groups when using PubSub subprotocol. + // Possible role values: + // - webpubsub.joinLeaveGroup: the client can join or leave any group. + // - webpubsub.sendToGroup: the client can send messages to any group. + // - webpubsub.joinLeaveGroup.: the client can join or leave group . + // - webpubsub.sendToGroup.: the client can send messages to group . + // More info: https://azure.github.io/azure-webpubsub/references/pubsub-websocket-subprotocol#permissions + Roles []string + + // ExpirationTimeInMinutes is the number of minutes until the token expires. Default value(60 minutes) is used if the value is 0. + ExpirationTimeInMinutes int32 + + // Groups are the groups to join when the client connects. + Groups []string +} + +// GenerateClientAccessURLResponse represents the response type for the generated client access url +type GenerateClientAccessURLResponse struct { + // The client token + Token string + // The base URL for the client to connect to + BaseURL string + // The URL client connects to with access_token query string + URL string +} + +// GenerateClientAccessURL - generate URL for the WebSocket clients +// - hub - The hub name. +// - options - GenerateClientAccessUrlOptions contains the optional parameters for the Client.GenerateClientAccessURL method. +func (c *Client) GenerateClientAccessURL(ctx context.Context, hub string, options *GenerateClientAccessURLOptions) (*GenerateClientAccessURLResponse, error) { + endpoint := c.endpoint + if hub == "" { + return nil, errors.New("empty hub name is not allowed") + } + hubPath := url.PathEscape(hub) + parsedURL, err := url.Parse(endpoint) + if err != nil { + return nil, errors.New("endpoint is not a valid URL") + } + + audience := fmt.Sprintf("%sclient/hubs/%s", parsedURL.String(), hubPath) + + parsedURL.Scheme = strings.Replace(strings.ToLower(parsedURL.Scheme), "http", "ws", 1) + baseURL := fmt.Sprintf("%sclient/hubs/%s", parsedURL.String(), hubPath) + + var token string + if c.key != nil { + token, err = c.signJwtToken(audience, options) + if err != nil { + return nil, err + } + } else { + var userId *string + if options.UserID == "" { + userId = nil + } else { + userId = &options.UserID + } + // Replace with your logic to generate the token using a webPubSub method + resp, err := c.generateClientToken(ctx, hub, &GenerateClientTokenOptions{UserID: userId, Role: options.Roles, Group: options.Groups, MinutesToExpire: &options.ExpirationTimeInMinutes}) + if err != nil { + return nil, err + } + + token = *resp.Token + } + + return &GenerateClientAccessURLResponse{ + Token: token, + BaseURL: baseURL, + URL: fmt.Sprintf("%s?access_token=%s", baseURL, url.QueryEscape(token)), + }, nil +} + +func (c *Client) signJwtToken(audience string, options *GenerateClientAccessURLOptions) (string, error) { + if c.key == nil { + return "", errors.New("key is nil") + } + key := []byte(*c.key) + var exp int64 + + if options == nil || options.ExpirationTimeInMinutes == 0 { + exp = time.Now().Add(defaultExpirationTime).Unix() + } else { + if options.ExpirationTimeInMinutes < 0 { + return "", errors.New("the value of ExpirationTimeInMinutes is out of range") + } + exp = time.Now().Add(time.Minute * time.Duration(options.ExpirationTimeInMinutes)).Unix() + } + claims := jwt.MapClaims{ + "aud": audience, + "exp": exp, + } + + if options != nil && options.UserID != "" { + claims["sub"] = options.UserID + } + + if options != nil && len(options.Groups) > 0 { + claims["webpubsub.group"] = options.Groups + } + + if options != nil && options.Roles != nil && len(options.Roles) > 0 { + claims["role"] = options.Roles + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + return token.SignedString(key) +} diff --git a/sdk/messaging/azwebpubsub/client_shared_test.go b/sdk/messaging/azwebpubsub/client_shared_test.go new file mode 100644 index 000000000000..a5abc7d405b5 --- /dev/null +++ b/sdk/messaging/azwebpubsub/client_shared_test.go @@ -0,0 +1,205 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azwebpubsub_test + +import ( + "crypto/tls" + "fmt" + "net/http" + "os" + "strings" + "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/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azwebpubsub" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azwebpubsub/internal" + "github.com/stretchr/testify/require" +) + +type clientWrapper struct { + *azwebpubsub.Client + TestVars testVars +} + +var fakeTestVars = testVars{ + ConnectionString: "Endpoint=https://fake.eastus-1.webpubsub.azure.com;AccessKey=ABCDE;", +} + +type testVars struct { + // NewClientFromConnectionString when ConnectionString is set + ConnectionString string + Endpoint string + // KeyLogPath is the value of environment "SSLKEYLOGFILE_TEST", which + // points to a file on disk where we'll write the TLS pre-master-secret. + // This is useful if you want to trace parts of this test using Wireshark. + 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{ + ConnectionString: get("WEBPUBSUB_CONNECTIONSTRING"), + Endpoint: get("WEBPUBSUB_ENDPOINT"), + } + + if len(missing) > 1 { + 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 env var to a path on disk and + // Go will write out the key. + tv.KeyLogPath = os.Getenv("SSLKEYLOGFILE") + return tv, nil +} + +func loadClientOptions(t *testing.T) (testVars, *azcore.ClientOptions) { + var tv testVars + var options *azcore.ClientOptions + if recording.GetRecordMode() != recording.PlaybackMode { + tmpTestVars, err := loadEnv() + require.NoError(t, err) + tv = tmpTestVars + } else { + tv = fakeTestVars + } + + if tv.ConnectionString != "" { + props, err := internal.ParseConnectionString(tv.ConnectionString) + require.NoError(t, err) + // always use ConnectionString's Endpoint if it is set + tv.Endpoint = props.Endpoint + } + + require.NotEmpty(t, tv.Endpoint) + + if recording.GetRecordMode() == recording.LiveMode { + if tv.KeyLogPath != "" { + 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} + options = &azcore.ClientOptions{ + Transport: httpClient, + } + } else { + options = nil + } + } else { + options = &azcore.ClientOptions{ + Transport: newRecordingTransporter(t, tv), + } + } + + return tv, options +} + +func newClientWrapper(t *testing.T) clientWrapper { + var client *azwebpubsub.Client + tv, coreOptions := loadClientOptions(t) + options := &azwebpubsub.ClientOptions{ + ClientOptions: *coreOptions, + } + if tv.ConnectionString != "" { + tmpClient, err := azwebpubsub.NewClientFromConnectionString(tv.ConnectionString, options) + require.NoError(t, err) + client = tmpClient + } else { + cred, err := azidentity.NewDefaultAzureCredential(nil) + require.NoError(t, err) + + tmpClient, err := azwebpubsub.NewClient(tv.Endpoint, cred, options) + 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/azwebpubsub/testdata", nil) + require.NoError(t, err) + err = recording.AddURISanitizer("https://fake_endpoint.com/", testVars.Endpoint, nil) + require.NoError(t, err) + + err = recording.AddGeneralRegexSanitizer(`"Date": "Wed, 15 Nov 2023 08:00:00 GMT"`, `"Date":".+?"`, 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( + `"succeededLockTokens": ["fake-lock-token", "fake-lock-token", "fake-lock-token"]`, + `"succeededLockTokens":\s*`+ + `\[`+ + `(\s*"[^"]+"\s*\,){2}`+ + `\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) + + err = recording.AddGeneralRegexSanitizer( + `"lockTokens": ["fake-lock-token", "fake-lock-token", "fake-lock-token"]`, + `"lockTokens":\s*`+ + `\[`+ + `(\s*"[^"]+"\s*\,){2}`+ + `\s*"[^"]+"\s*`+ + `\]`, nil) + require.NoError(t, err) + + t.Cleanup(func() { + err := recording.Stop(t, nil) + require.NoError(t, err) + }) + + return transport +} diff --git a/sdk/messaging/azwebpubsub/client_test.go b/sdk/messaging/azwebpubsub/client_test.go new file mode 100644 index 000000000000..f8bfd409e43c --- /dev/null +++ b/sdk/messaging/azwebpubsub/client_test.go @@ -0,0 +1,170 @@ +//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 azwebpubsub_test + +import ( + "bytes" + "context" + "io" + "net/url" + "strings" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azwebpubsub" + "github.com/golang-jwt/jwt" + "github.com/stretchr/testify/require" +) + +func TestClient_SendToAll(t *testing.T) { + client := newClientWrapper(t) + hub := "hub1" + _, err := client.SendToAll(context.Background(), hub, + azwebpubsub.ContentTypeTextPlain, newStream("Hello world!"), + &azwebpubsub.SendToAllOptions{}) + require.NoError(t, err) + + _, err = client.SendToAll(context.Background(), hub, + azwebpubsub.ContentTypeApplicationJSON, newStream("true"), + &azwebpubsub.SendToAllOptions{}) + require.NoError(t, err) + + _, err = client.SendToAll(context.Background(), hub, + azwebpubsub.ContentTypeApplicationOctetStream, newStream("true"), + &azwebpubsub.SendToAllOptions{}) + require.NoError(t, err) +} + +func TestClient_ManagePermissions(t *testing.T) { + client := newClientWrapper(t) + const hub = "chat" + const conn1 = "conn1" + group := "group1" + _, err := client.GrantPermission(context.Background(), hub, azwebpubsub.PermissionJoinLeaveGroup, conn1, &azwebpubsub.GrantPermissionOptions{ + TargetName: &group, + }) + require.ErrorContains(t, err, "404 Not Found") + _, err = client.RevokePermission(context.Background(), hub, azwebpubsub.PermissionJoinLeaveGroup, conn1, &azwebpubsub.RevokePermissionOptions{ + TargetName: &group, + }) + require.NoError(t, err) +} + +func TestClient_CloseConnections(t *testing.T) { + client := newClientWrapper(t) + const hub = "chat" + const conn1 = "conn1" + const group1 = "group1" + const user1 = "user1" + reason := "TestClient_CloseConnections" + _, err := client.CloseAllConnections(context.Background(), + hub, &azwebpubsub.CloseAllConnectionsOptions{Excluded: []string{conn1}, Reason: &reason}) + require.NoError(t, err) + _, err = client.CloseConnection(context.Background(), + hub, conn1, &azwebpubsub.CloseConnectionOptions{Reason: &reason}) + require.NoError(t, err) + _, err = client.CloseGroupConnections(context.Background(), + hub, group1, &azwebpubsub.CloseGroupConnectionsOptions{Excluded: []string{conn1}, Reason: &reason}) + require.NoError(t, err) + _, err = client.CloseUserConnections(context.Background(), + hub, user1, &azwebpubsub.CloseUserConnectionsOptions{Excluded: []string{conn1}, Reason: &reason}) + require.NoError(t, err) +} + +func TestClient_GenerateClientAccessURLFromConnectionString(t *testing.T) { + _, err1 := azwebpubsub.NewClientFromConnectionString("Endpoint=http://test/subpath;;;;", nil) + require.ErrorContains(t, err1, "connection string is either blank or malformed.") + + hub := "chat/go" + client, err := azwebpubsub.NewClientFromConnectionString("Endpoint=http://test/subpath;AccessKey=ABC;;;", nil) + require.NoError(t, err) + + token, err := client.GenerateClientAccessURL(context.Background(), hub, nil) + + require.NoError(t, err) + extract := extractToken(t, token, "http://test/subpath", "ABC", hub) + require.Nil(t, extract.Roles) + require.Nil(t, extract.Groups) + require.Empty(t, extract.UserID) + + user1 := "user1" + token, err = client.GenerateClientAccessURL(context.Background(), hub, &azwebpubsub.GenerateClientAccessURLOptions{ + UserID: user1, + Roles: []string{"admin"}, + Groups: []string{"group1"}, + }) + require.NoError(t, err) + parsedURL, err := url.Parse(token.URL) + require.NoError(t, err) + queryValues := parsedURL.Query() + accessToken := queryValues.Get("access_token") + require.NotEmpty(t, accessToken) + extract = extractToken(t, token, "http://test/subpath", "ABC", hub) + require.Equal(t, user1, extract.UserID) + require.Equal(t, "admin", extract.Roles[0]) + require.Equal(t, "group1", extract.Groups[0]) +} + +func extractToken(t *testing.T, token *azwebpubsub.GenerateClientAccessURLResponse, endpoint string, key string, hub string) azwebpubsub.GenerateClientAccessURLOptions { + expectedAudience := endpoint + "/client/hubs/" + url.PathEscape(hub) + expectedBaseUrl := strings.Replace(expectedAudience, "http", "ws", 1) + + require.Equal(t, expectedBaseUrl, token.BaseURL) + parsed, err := jwt.Parse(token.Token, func(token *jwt.Token) (interface{}, error) { + // Provide the secret key for validation + return []byte(key), nil + }) + require.NoError(t, err) + require.True(t, parsed.Valid, "token is not valid") + claims, ok := parsed.Claims.(jwt.MapClaims) + require.True(t, ok, "claims is not valid") + audience, ok := claims["aud"].(string) + require.True(t, ok, "audience is not valid") + require.Equal(t, expectedAudience, audience) + subject, ok := claims["sub"].(string) + var userId string + if ok { + userId = subject + } else { + userId = "" + } + rawRoles, ok := claims["role"].([]interface{}) + var roles []string + if ok { + // Convert the interface slice to a slice of strings + for _, role := range rawRoles { + if r, ok := role.(string); ok { + roles = append(roles, r) + } + } + } else { + roles = nil + } + + rawGroups, ok := claims["webpubsub.group"].([]interface{}) + var groups []string + if ok { + // Convert the interface slice to a slice of strings + for _, group := range rawGroups { + if r, ok := group.(string); ok { + groups = append(groups, r) + } + } + } else { + groups = nil + } + + return azwebpubsub.GenerateClientAccessURLOptions{ + UserID: userId, + Roles: roles, + Groups: groups, + } +} + +func newStream(message string) io.ReadSeekCloser { + return streaming.NopCloser(bytes.NewReader([]byte(message))) +} diff --git a/sdk/messaging/azwebpubsub/constants.go b/sdk/messaging/azwebpubsub/constants.go new file mode 100644 index 000000000000..9be3082eb405 --- /dev/null +++ b/sdk/messaging/azwebpubsub/constants.go @@ -0,0 +1,46 @@ +//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 azwebpubsub + +// ContentType - Content type for upload +type ContentType string + +const ( + // ContentTypeApplicationJSON - Content Type 'application/json' + ContentTypeApplicationJSON ContentType = "application/json" + // ContentTypeApplicationOctetStream - Content Type 'application/octet-stream' + ContentTypeApplicationOctetStream ContentType = "application/octet-stream" + // ContentTypeTextPlain - Content Type 'text/plain' + ContentTypeTextPlain ContentType = "text/plain" +) + +// PossibleContentTypeValues returns the possible values for the ContentType const type. +func PossibleContentTypeValues() []ContentType { + return []ContentType{ + ContentTypeApplicationJSON, + ContentTypeApplicationOctetStream, + ContentTypeTextPlain, + } +} + +// Permission contains the allowed permissions +type Permission string + +const ( + PermissionJoinLeaveGroup Permission = "joinLeaveGroup" + PermissionSendToGroup Permission = "sendToGroup" +) + +// PossiblePermissionValues returns the possible values for the Permission const type. +func PossiblePermissionValues() []Permission { + return []Permission{ + PermissionJoinLeaveGroup, + PermissionSendToGroup, + } +} diff --git a/sdk/messaging/azwebpubsub/example_test.go b/sdk/messaging/azwebpubsub/example_test.go new file mode 100644 index 000000000000..c6243c413968 --- /dev/null +++ b/sdk/messaging/azwebpubsub/example_test.go @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azwebpubsub_test + +import ( + "log" + "os" + + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azwebpubsub" +) + +// ExampleNewClient demonstrates how to create a new client with default Azure credentials. +func ExampleNewClient() { + + cred, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + // handle error + } + + endpoint := os.Getenv("WEBPUBSUB_ENDPOINT") + + if endpoint == "" { + return + } + + hub := os.Getenv("WEBPUBSUB_HUB") + if hub == "" { + return + } + client, err := azwebpubsub.NewClient(endpoint, cred, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + _ = client // ignore + + // Output: +} + +// ExampleNewClientFromConnectionString demonstrates how to create a new client with connection string. +func ExampleNewClientFromConnectionString() { + connectionString := os.Getenv("WEBPUBSUB_CONNECTIONSTRING") + if connectionString == "" { + return + } + + client, err := azwebpubsub.NewClientFromConnectionString(connectionString, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + _ = client // ignore + + // Output: +} diff --git a/sdk/messaging/azwebpubsub/go.mod b/sdk/messaging/azwebpubsub/go.mod new file mode 100644 index 000000000000..bdecf0454ab1 --- /dev/null +++ b/sdk/messaging/azwebpubsub/go.mod @@ -0,0 +1,28 @@ +module github.com/Azure/azure-sdk-for-go/sdk/messaging/azwebpubsub + +go 1.18 + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 + github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 + github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/golang-jwt/jwt/v5 v5.0.0 + github.com/stretchr/testify v1.8.4 +) + +require ( + github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dnaeon/go-vcr v1.2.0 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/sdk/messaging/azwebpubsub/go.sum b/sdk/messaging/azwebpubsub/go.sum new file mode 100644 index 000000000000..111b006fb5de --- /dev/null +++ b/sdk/messaging/azwebpubsub/go.sum @@ -0,0 +1,43 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 h1:fb8kj/Dh4CSwgsOzHeZY4Xh68cFVbzXx+ONXGMY//4w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 h1:d81/ng9rET2YqdVkVwkb6EXeRrLJIwyGnJcAlAWKwhs= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 h1:hVeq+yCyUi+MsoO/CU95yqCIcdzra5ovzk8Q2BBpV2M= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0/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 v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +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/azwebpubsub/internal/policy_key_credential.go b/sdk/messaging/azwebpubsub/internal/policy_key_credential.go new file mode 100644 index 000000000000..acee5b3c3de9 --- /dev/null +++ b/sdk/messaging/azwebpubsub/internal/policy_key_credential.go @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package internal + +import ( + "errors" + "net/http" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo" + "github.com/golang-jwt/jwt/v5" +) + +type KeyCredentialPolicy struct { + key string +} + +func NewWebPubSubKeyCredentialPolicy(key string) *KeyCredentialPolicy { + return &KeyCredentialPolicy{ + key: key, + } +} + +// Do implementes the Do method on the [policy.Polilcy] interface. +func (k *KeyCredentialPolicy) Do(req *policy.Request) (*http.Response, error) { + val := k.key + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "aud": req.Raw().URL.String(), + "exp": time.Now().Add(time.Hour).Unix(), + }) + secretKey := []byte(val) // Replace with your secret key + tokenString, err := token.SignedString(secretKey) + if err != nil { + return nil, errorinfo.NonRetriableError(errors.New("error signing the token")) + } + req.Raw().Header.Add("Authorization", "Bearer "+tokenString) + return req.Next() +} diff --git a/sdk/messaging/azwebpubsub/internal/policy_key_credential_test.go b/sdk/messaging/azwebpubsub/internal/policy_key_credential_test.go new file mode 100644 index 000000000000..77833ca65d31 --- /dev/null +++ b/sdk/messaging/azwebpubsub/internal/policy_key_credential_test.go @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package internal + +import ( + "context" + "net/http" + "strings" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "github.com/golang-jwt/jwt/v5" + "github.com/stretchr/testify/require" +) + +func TestKeyCredentialPolicy(t *testing.T) { + const key = "foo" + const target = "http://abc/de" + keyPolicy := NewWebPubSubKeyCredentialPolicy(key) + require.NotNil(t, keyPolicy) + verifier := PolicyFunc(func(req *policy.Request) (*http.Response, error) { + token := req.Raw().Header.Get("Authorization") + + require.True(t, strings.HasPrefix(token, "Bearer ")) + token = strings.TrimPrefix(token, "Bearer ") + + claims, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { + return []byte(key), nil + }) + + require.NoError(t, err) + require.Equal(t, target, claims.Claims.(jwt.MapClaims)["aud"]) + return &http.Response{}, nil + }) + + pl := newPipeline(runtime.PipelineOptions{PerCall: []policy.Policy{keyPolicy, verifier}}, + &policy.ClientOptions{}) + req, err := runtime.NewRequest(context.Background(), http.MethodGet, target) + require.NoError(t, err) + + _, err = pl.Do(req) + require.NoError(t, err) +} + +type PolicyFunc func(req *policy.Request) (*http.Response, error) + +func (f PolicyFunc) Do(req *policy.Request) (*http.Response, error) { + return f(req) +} + +func newPipeline(plOpts runtime.PipelineOptions, options *policy.ClientOptions) runtime.Pipeline { + return runtime.NewPipeline(ModuleName+".Client", ModuleVersion, plOpts, options) +} diff --git a/sdk/messaging/azwebpubsub/internal/util.go b/sdk/messaging/azwebpubsub/internal/util.go new file mode 100644 index 000000000000..ac77d2384736 --- /dev/null +++ b/sdk/messaging/azwebpubsub/internal/util.go @@ -0,0 +1,73 @@ +//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 internal + +import ( + "errors" + "net/url" + "strings" +) + +const ( + TokenScope = "https://webpubsub.azure.com/.default" +) + +var errConnectionString = errors.New("connection string is either blank or malformed. The expected connection string " + + "should contain key value pairs separated by semicolons. For example 'Endpoint=;AccessKey=;'") + +type ParsedConnectionString struct { + Endpoint string + AccessKey string +} + +func ParseConnectionString(connectionString string) (ParsedConnectionString, error) { + connStrMap := make(map[string]string) + connectionString = strings.TrimRight(connectionString, ";") + + splitString := strings.Split(connectionString, ";") + if len(splitString) == 0 { + return ParsedConnectionString{}, errConnectionString + } + for _, stringPart := range splitString { + parts := strings.SplitN(stringPart, "=", 2) + if len(parts) != 2 { + return ParsedConnectionString{}, errConnectionString + } + connStrMap[strings.ToLower(parts[0])] = parts[1] + } + + endpoint, ok := connStrMap["endpoint"] + if !ok { + return ParsedConnectionString{}, errConnectionString + } + + parsedURL, err := url.Parse(endpoint) + if err != nil { + return ParsedConnectionString{}, errors.New("endpoint is not a valid URL") + } + + port, has_port := connStrMap["port"] + if has_port { + parsedURL.Host = parsedURL.Hostname() + ":" + port + endpoint = parsedURL.String() + } + + if !strings.HasSuffix(endpoint, "/") { + // add a trailing slash to be consistent with the portal + endpoint += "/" + } + + key, ok := connStrMap["accesskey"] + if !ok { + return ParsedConnectionString{}, errConnectionString + } + + return ParsedConnectionString{ + Endpoint: endpoint, + AccessKey: key, + }, nil +} diff --git a/sdk/messaging/azwebpubsub/internal/util_test.go b/sdk/messaging/azwebpubsub/internal/util_test.go new file mode 100644 index 000000000000..52294f013705 --- /dev/null +++ b/sdk/messaging/azwebpubsub/internal/util_test.go @@ -0,0 +1,57 @@ +//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 internal + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseConnectionStringInvalid(t *testing.T) { + badConnectionStrings := []string{ + "", + "foobar", + "foo;bar;baz", + "foo=;bar=;", + "=", + ";", + "=;==", + "foobar=baz=foo", + } + + for _, badConnStr := range badConnectionStrings { + parsed, err := ParseConnectionString(badConnStr) + require.Error(t, err) + require.Zero(t, parsed) + //require.Contains(t, err.Error(), errConnectionString.Error()) + } +} + +func TestParseConnectionString(t *testing.T) { + connStr := "Endpoint=http://abc.com;AccessKey=ABC;" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "http://abc.com/", parsed.Endpoint) + require.Equal(t, "ABC", parsed.AccessKey) +} + +func TestParseConnectionStringLowercase(t *testing.T) { + connStr := "Endpoint=http://abc.com;accessKey=ABC;" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "http://abc.com/", parsed.Endpoint) + require.Equal(t, "ABC", parsed.AccessKey) +} + +func TestParseConnectionStringWithPort(t *testing.T) { + connStr := "Endpoint=http://abc.com:8080;accessKey=ABC;Port=8088;" + parsed, err := ParseConnectionString(connStr) + require.NoError(t, err) + require.Equal(t, "http://abc.com:8088/", parsed.Endpoint) + require.Equal(t, "ABC", parsed.AccessKey) +} diff --git a/sdk/messaging/azwebpubsub/internal/version.go b/sdk/messaging/azwebpubsub/internal/version.go new file mode 100644 index 000000000000..e0cba87ea360 --- /dev/null +++ b/sdk/messaging/azwebpubsub/internal/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 internal + +// Constants to identify the module +const ( + // ModuleName is the module name that shows in telemetry. + ModuleName = "github.com/Azure/azure-sdk-for-go/sdk/messaging/azwebpubsub" + + // ModuleVersion is the semantic version (see http://semver.org) of this module. + ModuleVersion = "v0.1.0" +) diff --git a/sdk/messaging/azwebpubsub/main_test.go b/sdk/messaging/azwebpubsub/main_test.go new file mode 100644 index 000000000000..fe69c91d60c6 --- /dev/null +++ b/sdk/messaging/azwebpubsub/main_test.go @@ -0,0 +1,41 @@ +//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 azwebpubsub_test + +import ( + "os" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" +) + +const recordingDirectory = "sdk/messaging/azwebpubsub/testdata" + +func TestMain(m *testing.M) { + code := run(m) + os.Exit(code) +} + +func run(m *testing.M) int { + var proxy *recording.TestProxyInstance + if recording.GetRecordMode() == recording.PlaybackMode || recording.GetRecordMode() == recording.RecordingMode { + var err error + proxy, err = recording.StartTestProxy(recordingDirectory, nil) + if err != nil { + panic(err) + } + + defer func() { + err := recording.StopTestProxy(proxy) + if err != nil { + panic(err) + } + }() + } + code := m.Run() + return code +} diff --git a/sdk/messaging/azwebpubsub/models.go b/sdk/messaging/azwebpubsub/models.go new file mode 100644 index 000000000000..e1b345e4d9cf --- /dev/null +++ b/sdk/messaging/azwebpubsub/models.go @@ -0,0 +1,33 @@ +//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 azwebpubsub + +// AddToGroupsRequest - The request object containing targets groups and a connection filter +type AddToGroupsRequest struct { + // An OData filter which target connections satisfy + Filter *string + + // A list of groups which target connections will be added into + Groups []string +} + +// TokenResponse - The response object containing the token for the client +type TokenResponse struct { + // The token value for the WebSocket client to connect to the service + Token *string +} + +// RemoveFromGroupsRequest - The request object containing targets groups and a connection filter +type RemoveFromGroupsRequest struct { + // An OData filter which target connections satisfy + Filter *string + + // A list of groups which target connections will be removed from + Groups []string +} diff --git a/sdk/messaging/azwebpubsub/models_serde.go b/sdk/messaging/azwebpubsub/models_serde.go new file mode 100644 index 000000000000..f6081a5ed268 --- /dev/null +++ b/sdk/messaging/azwebpubsub/models_serde.go @@ -0,0 +1,126 @@ +//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 azwebpubsub + +import ( + "encoding/json" + "fmt" + "reflect" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" +) + +// MarshalJSON implements the json.Marshaller interface for type AddToGroupsRequest. +func (a AddToGroupsRequest) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "filter", a.Filter) + populate(objectMap, "groups", a.Groups) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type AddToGroupsRequest. +func (a *AddToGroupsRequest) 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 "filter": + err = unpopulate(val, "Filter", &a.Filter) + delete(rawMsg, key) + case "groups": + err = unpopulate(val, "Groups", &a.Groups) + 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 TokenResponse. +func (c TokenResponse) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "token", c.Token) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type TokenResponse. +func (c *TokenResponse) 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 "token": + err = unpopulate(val, "Token", &c.Token) + 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 RemoveFromGroupsRequest. +func (r RemoveFromGroupsRequest) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "filter", r.Filter) + populate(objectMap, "groups", r.Groups) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type RemoveFromGroupsRequest. +func (r *RemoveFromGroupsRequest) 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 "filter": + err = unpopulate(val, "Filter", &r.Filter) + delete(rawMsg, key) + case "groups": + err = unpopulate(val, "Groups", &r.Groups) + 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 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/azwebpubsub/options.go b/sdk/messaging/azwebpubsub/options.go new file mode 100644 index 000000000000..4dcf245e4b72 --- /dev/null +++ b/sdk/messaging/azwebpubsub/options.go @@ -0,0 +1,185 @@ +//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 azwebpubsub + +// AddConnectionToGroupOptions contains the optional parameters for the Client.AddConnectionToGroup method. +type AddConnectionToGroupOptions struct { + // placeholder for future optional parameters +} + +// AddConnectionsToGroupsOptions contains the optional parameters for the Client.AddConnectionsToGroups method. +type AddConnectionsToGroupsOptions struct { + // placeholder for future optional parameters +} + +// AddUserToGroupOptions contains the optional parameters for the Client.AddUserToGroup method. +type AddUserToGroupOptions struct { + // placeholder for future optional parameters +} + +// CheckPermissionOptions contains the optional parameters for the Client.CheckPermission method. +type CheckPermissionOptions struct { + // The meaning of the target depends on the specific permission. For joinLeaveGroup and sendToGroup, targetName is a required + // parameter standing for the group name. + TargetName *string +} + +// CloseAllConnectionsOptions contains the optional parameters for the Client.CloseAllConnections method. +type CloseAllConnectionsOptions struct { + // Exclude these connectionIds when closing the connections in the hub. + Excluded []string + + // The reason closing the client connection. + Reason *string +} + +// CloseConnectionOptions contains the optional parameters for the Client.CloseConnection method. +type CloseConnectionOptions struct { + // The reason closing the client connection. + Reason *string +} + +// CloseGroupConnectionsOptions contains the optional parameters for the Client.CloseGroupConnections method. +type CloseGroupConnectionsOptions struct { + // Exclude these connectionIds when closing the connections in the group. + Excluded []string + + // The reason closing the client connection. + Reason *string +} + +// CloseUserConnectionsOptions contains the optional parameters for the Client.CloseUserConnections method. +type CloseUserConnectionsOptions struct { + // Exclude these connectionIds when closing the connections for the user. + Excluded []string + + // The reason closing the client connection. + Reason *string +} + +// ConnectionExistsOptions contains the optional parameters for the Client.ConnectionExists method. +type ConnectionExistsOptions struct { + // placeholder for future optional parameters +} + +// GenerateClientTokenOptions contains the optional parameters for the Client.GenerateClientToken method. +type GenerateClientTokenOptions struct { + // Groups that the connection will join when it connects. + Group []string + + // The expire time of the generated token. + MinutesToExpire *int32 + + // Roles that the connection with the generated token will have. + Role []string + + // User Id. + UserID *string +} + +// GrantPermissionOptions contains the optional parameters for the Client.GrantPermission method. +type GrantPermissionOptions struct { + // The meaning of the target depends on the specific permission. For joinLeaveGroup and sendToGroup, targetName is a required + // parameter standing for the group name. + TargetName *string +} + +// GroupExistsOptions contains the optional parameters for the Client.GroupExists method. +type GroupExistsOptions struct { + // placeholder for future optional parameters +} + +// RemoveConnectionFromAllGroupsOptions contains the optional parameters for the Client.RemoveConnectionFromAllGroups +// method. +type RemoveConnectionFromAllGroupsOptions struct { + // placeholder for future optional parameters +} + +// RemoveConnectionFromGroupOptions contains the optional parameters for the Client.RemoveConnectionFromGroup method. +type RemoveConnectionFromGroupOptions struct { + // placeholder for future optional parameters +} + +// RemoveConnectionsFromGroupsOptions contains the optional parameters for the Client.RemoveConnectionsFromGroups method. +type RemoveConnectionsFromGroupsOptions struct { + // placeholder for future optional parameters +} + +// RemoveUserFromAllGroupsOptions contains the optional parameters for the Client.RemoveUserFromAllGroups method. +type RemoveUserFromAllGroupsOptions struct { + // placeholder for future optional parameters +} + +// RemoveUserFromGroupOptions contains the optional parameters for the Client.RemoveUserFromGroup method. +type RemoveUserFromGroupOptions struct { + // placeholder for future optional parameters +} + +// RevokePermissionOptions contains the optional parameters for the Client.RevokePermission method. +type RevokePermissionOptions struct { + // The meaning of the target depends on the specific permission. For joinLeaveGroup and sendToGroup, targetName is a required + // parameter standing for the group name. + TargetName *string +} + +// SendToAllOptions contains the optional parameters for the Client.SendToAll method. +type SendToAllOptions struct { + // Excluded connection Ids. + Excluded []string + + // Following OData filter syntax to filter out the subscribers receiving the messages. + Filter *string + + // The time-to-live (TTL) value in seconds for messages sent to the service. 0 is the default value, which means the message + // never expires. 300 is the maximum value. If this parameter is non-zero, + // messages that are not consumed by the client within the specified TTL will be dropped by the service. This parameter can + // help when the client's bandwidth is limited. + MessageTTLSeconds *int32 +} + +// SendToConnectionOptions contains the optional parameters for the Client.SendToConnection method. +type SendToConnectionOptions struct { + // The time-to-live (TTL) value in seconds for messages sent to the service. 0 is the default value, which means the message + // never expires. 300 is the maximum value. If this parameter is non-zero, + // messages that are not consumed by the client within the specified TTL will be dropped by the service. This parameter can + // help when the client's bandwidth is limited. + MessageTTLSeconds *int32 +} + +// SendToGroupOptions contains the optional parameters for the Client.SendToGroup method. +type SendToGroupOptions struct { + // Excluded connection Ids + Excluded []string + + // Following OData filter syntax to filter out the subscribers receiving the messages. + Filter *string + + // The time-to-live (TTL) value in seconds for messages sent to the service. 0 is the default value, which means the message + // never expires. 300 is the maximum value. If this parameter is non-zero, + // messages that are not consumed by the client within the specified TTL will be dropped by the service. This parameter can + // help when the client's bandwidth is limited. + MessageTTLSeconds *int32 +} + +// SendToUserOptions contains the optional parameters for the Client.SendToUser method. +type SendToUserOptions struct { + // Following OData filter syntax to filter out the subscribers receiving the messages. + Filter *string + + // The time-to-live (TTL) value in seconds for messages sent to the service. 0 is the default value, which means the message + // never expires. 300 is the maximum value. If this parameter is non-zero, + // messages that are not consumed by the client within the specified TTL will be dropped by the service. This parameter can + // help when the client's bandwidth is limited. + MessageTTLSeconds *int32 +} + +// UserExistsOptions contains the optional parameters for the Client.UserExists method. +type UserExistsOptions struct { + // placeholder for future optional parameters +} diff --git a/sdk/messaging/azwebpubsub/response_types.go b/sdk/messaging/azwebpubsub/response_types.go new file mode 100644 index 000000000000..2381808231fd --- /dev/null +++ b/sdk/messaging/azwebpubsub/response_types.go @@ -0,0 +1,125 @@ +//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 azwebpubsub + +// AddConnectionToGroupResponse contains the response from method Client.AddConnectionToGroup. +type AddConnectionToGroupResponse struct { + // placeholder for future response values +} + +// AddConnectionsToGroupsResponse contains the response from method Client.AddConnectionsToGroups. +type AddConnectionsToGroupsResponse struct { + // placeholder for future response values +} + +// AddUserToGroupResponse contains the response from method Client.AddUserToGroup. +type AddUserToGroupResponse struct { + // placeholder for future response values +} + +// CheckPermissionResponse contains the response from method Client.CheckPermission. +type CheckPermissionResponse struct { + // placeholder for future response values +} + +// CloseAllConnectionsResponse contains the response from method Client.CloseAllConnections. +type CloseAllConnectionsResponse struct { + // placeholder for future response values +} + +// CloseConnectionResponse contains the response from method Client.CloseConnection. +type CloseConnectionResponse struct { + // placeholder for future response values +} + +// CloseGroupConnectionsResponse contains the response from method Client.CloseGroupConnections. +type CloseGroupConnectionsResponse struct { + // placeholder for future response values +} + +// CloseUserConnectionsResponse contains the response from method Client.CloseUserConnections. +type CloseUserConnectionsResponse struct { + // placeholder for future response values +} + +// ConnectionExistsResponse contains the response from method Client.ConnectionExists. +type ConnectionExistsResponse struct { + // placeholder for future response values +} + +// GenerateClientTokenResponse contains the response from method Client.GenerateClientToken. +type GenerateClientTokenResponse struct { + // The response object containing the token for the client + TokenResponse +} + +// GrantPermissionResponse contains the response from method Client.GrantPermission. +type GrantPermissionResponse struct { + // placeholder for future response values +} + +// GroupExistsResponse contains the response from method Client.GroupExists. +type GroupExistsResponse struct { + // placeholder for future response values +} + +// RemoveConnectionFromAllGroupsResponse contains the response from method Client.RemoveConnectionFromAllGroups. +type RemoveConnectionFromAllGroupsResponse struct { + // placeholder for future response values +} + +// RemoveConnectionFromGroupResponse contains the response from method Client.RemoveConnectionFromGroup. +type RemoveConnectionFromGroupResponse struct { + // placeholder for future response values +} + +// RemoveConnectionsFromGroupsResponse contains the response from method Client.RemoveConnectionsFromGroups. +type RemoveConnectionsFromGroupsResponse struct { + // placeholder for future response values +} + +// RemoveUserFromAllGroupsResponse contains the response from method Client.RemoveUserFromAllGroups. +type RemoveUserFromAllGroupsResponse struct { + // placeholder for future response values +} + +// RemoveUserFromGroupResponse contains the response from method Client.RemoveUserFromGroup. +type RemoveUserFromGroupResponse struct { + // placeholder for future response values +} + +// RevokePermissionResponse contains the response from method Client.RevokePermission. +type RevokePermissionResponse struct { + // placeholder for future response values +} + +// SendToAllResponse contains the response from method Client.SendToAll. +type SendToAllResponse struct { + // placeholder for future response values +} + +// SendToConnectionResponse contains the response from method Client.SendToConnection. +type SendToConnectionResponse struct { + // placeholder for future response values +} + +// SendToGroupResponse contains the response from method Client.SendToGroup. +type SendToGroupResponse struct { + // placeholder for future response values +} + +// SendToUserResponse contains the response from method Client.SendToUser. +type SendToUserResponse struct { + // placeholder for future response values +} + +// UserExistsResponse contains the response from method Client.UserExists. +type UserExistsResponse struct { + // placeholder for future response values +} diff --git a/sdk/messaging/azwebpubsub/sample.env b/sdk/messaging/azwebpubsub/sample.env new file mode 100644 index 000000000000..4b5a9bb95a71 --- /dev/null +++ b/sdk/messaging/azwebpubsub/sample.env @@ -0,0 +1,3 @@ +WEBPUBSUB_CONNECTIONSTRING= +WEBPUBSUB_HUB=chat +WEBPUBSUB_ENDPOINT=