diff --git a/sdk/data/azcosmos/README.md b/sdk/data/azcosmos/README.md index 2acd29a1c526..055b43c12d6a 100644 --- a/sdk/data/azcosmos/README.md +++ b/sdk/data/azcosmos/README.md @@ -78,9 +78,8 @@ properties := azcosmos.ContainerProperties{ } throughput := azcosmos.NewManualThroughputProperties(400) -response, err := database.CreateContainer(context, properties, &CreateContainerOptions{ThroughputProperties: throughput}) +response, err := database.CreateContainer(context, properties, &CreateContainerOptions{ThroughputProperties: &throughput}) handle(err) -container := resp.ContainerProperties.Container ``` ### CRUD operation on Items @@ -92,21 +91,21 @@ item := map[string]string{ } // Create partition key -container := client.GetContainer(dbName, containerName) -pk, err := azcosmos.NewPartitionKey("1") +container, err := client.NewContainer(dbName, containerName) handle(err) +pk := azcosmos.NewPartitionKeyString("1") // Create an item -itemResponse, err := container.CreateItem(context, &pk, item, nil) +itemResponse, err := container.CreateItem(context, pk, item, nil) handle(err) -itemResponse, err = container.ReadItem(context, &pk, "1", nil) +itemResponse, err = container.ReadItem(context, pk, "1", nil) handle(err) -itemResponse, err = container.ReplaceItem(context, &pk, "1", item, nil) +itemResponse, err = container.ReplaceItem(context, pk, "1", item, nil) handle(err) -itemResponse, err = container.DeleteItem(context, &pk, "1", nil) +itemResponse, err = container.DeleteItem(context, pk, "1", nil) handle(err) ``` diff --git a/sdk/data/azcosmos/async_cache_test.go b/sdk/data/azcosmos/async_cache_test.go index 6b68f8bf63cf..69e26ec5b553 100644 --- a/sdk/data/azcosmos/async_cache_test.go +++ b/sdk/data/azcosmos/async_cache_test.go @@ -14,19 +14,19 @@ import ( func Test_set(t *testing.T) { key := "someKey" - expectedValue := ContainerProperties{Id: "someId"} + expectedValue := ContainerProperties{ID: "someId"} cache := newAsyncCache() cache.setValue(key, expectedValue) value, _ := cache.getValue(key) containerProps, _ := value.(ContainerProperties) - assert.Equal(t, expectedValue.Id, containerProps.Id) + assert.Equal(t, expectedValue.ID, containerProps.ID) } func Test_setAsync(t *testing.T) { key := "someKeyAsync" - expectedValue := ContainerProperties{Id: "someIdAsync"} + expectedValue := ContainerProperties{ID: "someIdAsync"} cache := newAsyncCache() @@ -38,13 +38,13 @@ func Test_setAsync(t *testing.T) { _ = cache.set(key, f, context.Background()) value, _ := cache.getValue(key) containerProps, _ := value.(ContainerProperties) - assert.Equal(t, expectedValue.Id, containerProps.Id) + assert.Equal(t, expectedValue.ID, containerProps.ID) } func Test_getAsync_not_obsolete(t *testing.T) { key := "testAsyncKey" - expectedValue0 := ContainerProperties{Id: "0"} - expectedValue1 := ContainerProperties{Id: "1"} + expectedValue0 := ContainerProperties{ID: "0"} + expectedValue1 := ContainerProperties{ID: "1"} f1Called := false f2Called := false @@ -80,17 +80,17 @@ func Test_getAsync_not_obsolete(t *testing.T) { assert.False(t, f2Called) containerProps, _ := value.(ContainerProperties) - assert.Equal(t, expectedValue1.Id, containerProps.Id) + assert.Equal(t, expectedValue1.ID, containerProps.ID) containerProps2, _ := value2.(ContainerProperties) - assert.Equal(t, expectedValue1.Id, containerProps2.Id) + assert.Equal(t, expectedValue1.ID, containerProps2.ID) } func Test_getAsync_obsolete(t *testing.T) { key := "testAsyncObsoleteKey" - expectedValue0 := ContainerProperties{Id: "0"} - expectedValue1 := ContainerProperties{Id: "1"} - expectedValue2 := ContainerProperties{Id: "2"} + expectedValue0 := ContainerProperties{ID: "0"} + expectedValue1 := ContainerProperties{ID: "1"} + expectedValue2 := ContainerProperties{ID: "2"} f1Called := false f2Called := false @@ -127,15 +127,15 @@ func Test_getAsync_obsolete(t *testing.T) { assert.True(t, f1Called) assert.True(t, f2Called) - assert.Equal(t, expectedValue2.Id, containerProps.Id) - assert.Equal(t, expectedValue2.Id, containerProps2.Id) + assert.Equal(t, expectedValue2.ID, containerProps.ID) + assert.Equal(t, expectedValue2.ID, containerProps2.ID) } func Test_getAsync_obsolete_with_error(t *testing.T) { key := "testAsyncObsoleteKey" - expectedValue0 := ContainerProperties{Id: "0"} - expectedValue1 := ContainerProperties{Id: "1"} - expectedValue2 := ContainerProperties{Id: "2"} + expectedValue0 := ContainerProperties{ID: "0"} + expectedValue1 := ContainerProperties{ID: "1"} + expectedValue2 := ContainerProperties{ID: "2"} f1Called := false f2Called := false @@ -176,9 +176,9 @@ func Test_getAsync_obsolete_with_error(t *testing.T) { func Test_getAsync_obsolete_with_context_error(t *testing.T) { key := "testAsyncObsoleteKey" - expectedValue0 := ContainerProperties{Id: "0"} - expectedValue1 := ContainerProperties{Id: "1"} - expectedValue2 := ContainerProperties{Id: "2"} + expectedValue0 := ContainerProperties{ID: "0"} + expectedValue1 := ContainerProperties{ID: "1"} + expectedValue2 := ContainerProperties{ID: "2"} f1Called := false f2Called := false @@ -221,14 +221,14 @@ func Test_getAsync_obsolete_with_context_error(t *testing.T) { func Test_remove(t *testing.T) { key := "someKeyToRemove" - expectedValue := ContainerProperties{Id: "someIdToRemove"} + expectedValue := ContainerProperties{ID: "someIdToRemove"} cache := newAsyncCache() cache.setValue(key, expectedValue) value, _ := cache.getValue(key) containerProps, _ := value.(ContainerProperties) - assert.Equal(t, expectedValue.Id, containerProps.Id) + assert.Equal(t, expectedValue.ID, containerProps.ID) cache.remove(key) @@ -239,21 +239,21 @@ func Test_remove(t *testing.T) { func Test_clear(t *testing.T) { key := "someKeyToClear" - expectedValue := ContainerProperties{Id: "someIdToDelete"} + expectedValue := ContainerProperties{ID: "someIdToDelete"} key2 := "someKeyToClear2" - expectedValue2 := ContainerProperties{Id: "someIdToDelete2"} + expectedValue2 := ContainerProperties{ID: "someIdToDelete2"} cache := newAsyncCache() cache.setValue(key, expectedValue) value, _ := cache.getValue(key) containerProps, _ := value.(ContainerProperties) - assert.Equal(t, expectedValue.Id, containerProps.Id) + assert.Equal(t, expectedValue.ID, containerProps.ID) cache.setValue(key2, expectedValue2) value2, _ := cache.getValue(key2) containerProps2, _ := value2.(ContainerProperties) - assert.Equal(t, expectedValue2.Id, containerProps2.Id) + assert.Equal(t, expectedValue2.ID, containerProps2.ID) cache.clear() diff --git a/sdk/data/azcosmos/cosmos_client.go b/sdk/data/azcosmos/cosmos_client.go index 1794c1da94cf..5668dcecb10d 100644 --- a/sdk/data/azcosmos/cosmos_client.go +++ b/sdk/data/azcosmos/cosmos_client.go @@ -4,16 +4,21 @@ package azcosmos import ( + "bytes" "context" "errors" + "net/http" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming" ) // Cosmos client is used to interact with the Azure Cosmos DB database service. type Client struct { - endpoint string - connection *cosmosClientConnection - cred *KeyCredential - options *CosmosClientOptions + endpoint string + pipeline azruntime.Pipeline } // Endpoint used to create the client. @@ -25,39 +30,53 @@ func (c *Client) Endpoint() string { // endpoint - The cosmos service endpoint to use. // cred - The credential used to authenticate with the cosmos service. // options - Optional Cosmos client options. Pass nil to accept default values. -func NewClientWithKey(endpoint string, cred *KeyCredential, options *CosmosClientOptions) (*Client, error) { +func NewClientWithKey(endpoint string, cred KeyCredential, o *ClientOptions) (*Client, error) { + return &Client{endpoint: endpoint, pipeline: newPipeline(cred, o)}, nil +} + +func newPipeline(cred KeyCredential, options *ClientOptions) azruntime.Pipeline { if options == nil { - options = &CosmosClientOptions{} + options = &ClientOptions{} } - connection := newCosmosClientConnection(endpoint, cred, options) - - return &Client{endpoint: endpoint, connection: connection, cred: cred, options: options}, nil + return azruntime.NewPipeline("azcosmos", serviceLibVersion, + []policy.Policy{ + newSharedKeyCredPolicy(cred), + &headerPolicies{ + enableContentResponseOnWrite: options.EnableContentResponseOnWrite, + }}, + nil, + &options.ClientOptions) } // GetDatabase returns a Database object. // id - The id of the database. -func (c *Client) GetDatabase(id string) (*Database, error) { +func (c *Client) NewDatabase(id string) (DatabaseClient, error) { if id == "" { - return nil, errors.New("id is required") + return DatabaseClient{}, errors.New("id is required") } - return newDatabase(id, c), nil + return newDatabase(id, c) } -// GetContainer returns a Container object. +// NewContainer returns a Container object. // databaseId - The id of the database. // containerId - The id of the container. -func (c *Client) GetContainer(databaseId string, containerId string) (*Container, error) { +func (c *Client) NewContainer(databaseId string, containerId string) (ContainerClient, error) { if databaseId == "" { - return nil, errors.New("databaseId is required") + return ContainerClient{}, errors.New("databaseId is required") } if containerId == "" { - return nil, errors.New("containerId is required") + return ContainerClient{}, errors.New("containerId is required") } - return newDatabase(databaseId, c).GetContainer(containerId) + db, err := newDatabase(databaseId, c) + if err != nil { + return ContainerClient{}, err + } + + return db.NewContainer(containerId) } // CreateDatabase creates a new database. @@ -72,22 +91,16 @@ func (c *Client) CreateDatabase( o = &CreateDatabaseOptions{} } - operationContext := cosmosOperationContext{ + operationContext := pipelineRequestOptions{ resourceType: resourceTypeDatabase, - resourceAddress: "", - } + resourceAddress: ""} path, err := generatePathForNameBased(resourceTypeDatabase, "", true) if err != nil { return DatabaseResponse{}, err } - database, err := c.GetDatabase(databaseProperties.Id) - if err != nil { - return DatabaseResponse{}, err - } - - azResponse, err := c.connection.sendPostRequest( + azResponse, err := c.sendPostRequest( path, ctx, databaseProperties, @@ -98,5 +111,185 @@ func (c *Client) CreateDatabase( return DatabaseResponse{}, err } - return newDatabaseResponse(azResponse, database) + return newDatabaseResponse(azResponse) +} + +func (c *Client) sendPostRequest( + path string, + ctx context.Context, + content interface{}, + operationContext pipelineRequestOptions, + requestOptions cosmosRequestOptions, + requestEnricher func(*policy.Request)) (*http.Response, error) { + req, err := c.createRequest(path, ctx, http.MethodPost, operationContext, requestOptions, requestEnricher) + if err != nil { + return nil, err + } + + err = c.attachContent(content, req) + if err != nil { + return nil, err + } + + return c.executeAndEnsureSuccessResponse(req) +} + +func (c *Client) sendQueryRequest( + path string, + ctx context.Context, + query string, + operationContext pipelineRequestOptions, + requestOptions cosmosRequestOptions, + requestEnricher func(*policy.Request)) (*http.Response, error) { + req, err := c.createRequest(path, ctx, http.MethodPost, operationContext, requestOptions, requestEnricher) + if err != nil { + return nil, err + } + + type queryBody struct { + Query string `json:"query"` + } + + err = azruntime.MarshalAsJSON(req, queryBody{ + Query: query, + }) + if err != nil { + return nil, err + } + + req.Raw().Header.Add(cosmosHeaderQuery, "True") + // Override content type for query + req.Raw().Header.Set(headerContentType, cosmosHeaderValuesQuery) + + return c.executeAndEnsureSuccessResponse(req) +} + +func (c *Client) sendPutRequest( + path string, + ctx context.Context, + content interface{}, + operationContext pipelineRequestOptions, + requestOptions cosmosRequestOptions, + requestEnricher func(*policy.Request)) (*http.Response, error) { + req, err := c.createRequest(path, ctx, http.MethodPut, operationContext, requestOptions, requestEnricher) + if err != nil { + return nil, err + } + + err = c.attachContent(content, req) + if err != nil { + return nil, err + } + + return c.executeAndEnsureSuccessResponse(req) +} + +func (c *Client) sendGetRequest( + path string, + ctx context.Context, + operationContext pipelineRequestOptions, + requestOptions cosmosRequestOptions, + requestEnricher func(*policy.Request)) (*http.Response, error) { + req, err := c.createRequest(path, ctx, http.MethodGet, operationContext, requestOptions, requestEnricher) + if err != nil { + return nil, err + } + + return c.executeAndEnsureSuccessResponse(req) +} + +func (c *Client) sendDeleteRequest( + path string, + ctx context.Context, + operationContext pipelineRequestOptions, + requestOptions cosmosRequestOptions, + requestEnricher func(*policy.Request)) (*http.Response, error) { + req, err := c.createRequest(path, ctx, http.MethodDelete, operationContext, requestOptions, requestEnricher) + if err != nil { + return nil, err + } + + return c.executeAndEnsureSuccessResponse(req) +} + +func (c *Client) createRequest( + path string, + ctx context.Context, + method string, + operationContext pipelineRequestOptions, + requestOptions cosmosRequestOptions, + requestEnricher func(*policy.Request)) (*policy.Request, error) { + + // todo: endpoint will be set originally by globalendpointmanager + finalURL := c.endpoint + + if path != "" { + finalURL = azruntime.JoinPaths(c.endpoint, path) + } + + req, err := azruntime.NewRequest(ctx, method, finalURL) + if err != nil { + return nil, err + } + + if requestOptions != nil { + headers := requestOptions.toHeaders() + if headers != nil { + for k, v := range *headers { + req.Raw().Header.Set(k, v) + } + } + } + + req.Raw().Header.Set(headerXmsDate, time.Now().UTC().Format(http.TimeFormat)) + req.Raw().Header.Set(headerXmsVersion, "2020-11-05") + + req.SetOperationValue(operationContext) + + if requestEnricher != nil { + requestEnricher(req) + } + + return req, nil +} + +func (c *Client) attachContent(content interface{}, req *policy.Request) error { + var err error + switch v := content.(type) { + case []byte: + // If its a raw byte array, we can just set the body + err = req.SetBody(streaming.NopCloser(bytes.NewReader(v)), "application/json") + default: + // Otherwise, we need to marshal it + err = azruntime.MarshalAsJSON(req, content) + + } + + if err != nil { + return err + } + + return nil +} + +func (c *Client) executeAndEnsureSuccessResponse(request *policy.Request) (*http.Response, error) { + response, err := c.pipeline.Do(request) + if err != nil { + return nil, err + } + + successResponse := (response.StatusCode >= 200 && response.StatusCode < 300) || response.StatusCode == 304 + if successResponse { + return response, nil + } + + return nil, newCosmosError(response) +} + +type pipelineRequestOptions struct { + headerOptionsOverride *headerOptionsOverride + resourceType resourceType + resourceAddress string + isRidBased bool + isWriteOperation bool } diff --git a/sdk/data/azcosmos/cosmos_client_connection.go b/sdk/data/azcosmos/cosmos_client_connection.go deleted file mode 100644 index f1997c873490..000000000000 --- a/sdk/data/azcosmos/cosmos_client_connection.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcosmos - -import ( - "context" - "net/http" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" -) - -// cosmosClientConnection maintains a Pipeline for the client. -// The Pipeline is build based on the CosmosClientOptions. -type cosmosClientConnection struct { - endpoint string - Pipeline azruntime.Pipeline -} - -// newConnection creates an instance of the connection type with the specified endpoint. -// Pass nil to accept the default options; this is the same as passing a zero-value options. -func newCosmosClientConnection(endpoint string, cred *KeyCredential, options *CosmosClientOptions) *cosmosClientConnection { - policies := []policy.Policy{ - azruntime.NewTelemetryPolicy("azcosmos", serviceLibVersion, &options.Telemetry), - } - policies = append(policies, options.PerCallPolicies...) - policies = append(policies, azruntime.NewRetryPolicy(&options.Retry)) - policies = append(policies, options.PerRetryPolicies...) - policies = append(policies, options.getSDKInternalPolicies()...) - policies = append(policies, newSharedKeyCredPolicy(cred)) - policies = append(policies, azruntime.NewLogPolicy(&options.Logging)) - return &cosmosClientConnection{endpoint: endpoint, Pipeline: azruntime.NewPipeline(options.HTTPClient, policies...)} -} - -func (c *cosmosClientConnection) sendPostRequest( - path string, - ctx context.Context, - content interface{}, - operationContext cosmosOperationContext, - requestOptions cosmosRequestOptions, - requestEnricher func(*policy.Request)) (*http.Response, error) { - req, err := c.createRequest(path, ctx, http.MethodPost, operationContext, requestOptions, requestEnricher) - if err != nil { - return nil, err - } - - err = azruntime.MarshalAsJSON(req, content) - if err != nil { - return nil, err - } - - return c.executeAndEnsureSuccessResponse(req) -} - -func (c *cosmosClientConnection) sendQueryRequest( - path string, - ctx context.Context, - query string, - operationContext cosmosOperationContext, - requestOptions cosmosRequestOptions, - requestEnricher func(*policy.Request)) (*http.Response, error) { - req, err := c.createRequest(path, ctx, http.MethodPost, operationContext, requestOptions, requestEnricher) - if err != nil { - return nil, err - } - - type queryBody struct { - Query string `json:"query"` - } - - err = azruntime.MarshalAsJSON(req, queryBody{ - Query: query, - }) - if err != nil { - return nil, err - } - - req.Raw().Header.Add(cosmosHeaderQuery, "True") - // Override content type for query - req.Raw().Header.Set(headerContentType, cosmosHeaderValuesQuery) - - return c.executeAndEnsureSuccessResponse(req) -} - -func (c *cosmosClientConnection) sendPutRequest( - path string, - ctx context.Context, - content interface{}, - operationContext cosmosOperationContext, - requestOptions cosmosRequestOptions, - requestEnricher func(*policy.Request)) (*http.Response, error) { - req, err := c.createRequest(path, ctx, http.MethodPut, operationContext, requestOptions, requestEnricher) - if err != nil { - return nil, err - } - - err = azruntime.MarshalAsJSON(req, content) - if err != nil { - return nil, err - } - - return c.executeAndEnsureSuccessResponse(req) -} - -func (c *cosmosClientConnection) sendGetRequest( - path string, - ctx context.Context, - operationContext cosmosOperationContext, - requestOptions cosmosRequestOptions, - requestEnricher func(*policy.Request)) (*http.Response, error) { - req, err := c.createRequest(path, ctx, http.MethodGet, operationContext, requestOptions, requestEnricher) - if err != nil { - return nil, err - } - - return c.executeAndEnsureSuccessResponse(req) -} - -func (c *cosmosClientConnection) sendDeleteRequest( - path string, - ctx context.Context, - operationContext cosmosOperationContext, - requestOptions cosmosRequestOptions, - requestEnricher func(*policy.Request)) (*http.Response, error) { - req, err := c.createRequest(path, ctx, http.MethodDelete, operationContext, requestOptions, requestEnricher) - if err != nil { - return nil, err - } - - return c.executeAndEnsureSuccessResponse(req) -} - -func (c *cosmosClientConnection) createRequest( - path string, - ctx context.Context, - method string, - operationContext cosmosOperationContext, - requestOptions cosmosRequestOptions, - requestEnricher func(*policy.Request)) (*policy.Request, error) { - - // todo: endpoint will be set originally by globalendpointmanager - finalURL := c.endpoint - - if path != "" { - finalURL = azruntime.JoinPaths(c.endpoint, path) - } - - req, err := azruntime.NewRequest(ctx, method, finalURL) - if err != nil { - return nil, err - } - - if requestOptions != nil { - headers := requestOptions.toHeaders() - if headers != nil { - for k, v := range *headers { - req.Raw().Header.Set(k, v) - } - } - } - - req.Raw().Header.Set(headerXmsDate, time.Now().UTC().Format(http.TimeFormat)) - req.Raw().Header.Set(headerXmsVersion, "2020-11-05") - - req.SetOperationValue(operationContext) - - if requestEnricher != nil { - requestEnricher(req) - } - - return req, nil -} - -func (c *cosmosClientConnection) executeAndEnsureSuccessResponse(request *policy.Request) (*http.Response, error) { - response, err := c.Pipeline.Do(request) - if err != nil { - return nil, err - } - - successResponse := (response.StatusCode >= 200 && response.StatusCode < 300) || response.StatusCode == 304 - if successResponse { - return response, nil - } - - return nil, newCosmosError(response) -} diff --git a/sdk/data/azcosmos/cosmos_client_connection_test.go b/sdk/data/azcosmos/cosmos_client_connection_test.go deleted file mode 100644 index 807aa837eb01..000000000000 --- a/sdk/data/azcosmos/cosmos_client_connection_test.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcosmos - -import ( - "context" - "encoding/json" - "net/http" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" - "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" -) - -func TestEnsureErrorIsGeneratedOnResponse(t *testing.T) { - someError := &cosmosErrorResponse{ - Code: "SomeCode", - } - - jsonString, err := json.Marshal(someError) - if err != nil { - t.Fatal(err) - } - - srv, close := mock.NewTLSServer() - defer close() - srv.SetResponse( - mock.WithBody(jsonString), - mock.WithStatusCode(404)) - - pl := azruntime.NewPipeline(srv) - connection := &cosmosClientConnection{endpoint: srv.URL(), Pipeline: pl} - operationContext := cosmosOperationContext{ - resourceType: resourceTypeDatabase, - resourceAddress: "", - } - _, err = connection.sendGetRequest("/", context.Background(), operationContext, &ReadContainerOptions{}, nil) - if err == nil { - t.Fatal("Expected error") - } - - asError := err.(*cosmosError) - if asError.ErrorCode() != someError.Code { - t.Errorf("Expected %v, but got %v", someError.Code, asError.ErrorCode()) - } - - if err.Error() != asError.Error() { - t.Errorf("Expected %v, but got %v", err.Error(), asError.Error()) - } -} - -func TestEnsureErrorIsNotGeneratedOnResponse(t *testing.T) { - srv, close := mock.NewTLSServer() - defer close() - srv.SetResponse( - mock.WithStatusCode(200)) - - pl := azruntime.NewPipeline(srv) - connection := &cosmosClientConnection{endpoint: srv.URL(), Pipeline: pl} - operationContext := cosmosOperationContext{ - resourceType: resourceTypeDatabase, - resourceAddress: "", - } - _, err := connection.sendGetRequest("/", context.Background(), operationContext, &ReadContainerOptions{}, nil) - if err != nil { - t.Fatal(err) - } -} - -func TestRequestEnricherIsCalled(t *testing.T) { - srv, close := mock.NewTLSServer() - defer close() - srv.SetResponse( - mock.WithStatusCode(200)) - - pl := azruntime.NewPipeline(srv) - connection := &cosmosClientConnection{endpoint: srv.URL(), Pipeline: pl} - operationContext := cosmosOperationContext{ - resourceType: resourceTypeDatabase, - resourceAddress: "", - } - - addHeader := func(r *policy.Request) { - r.Raw().Header.Add("my-header", "12345") - } - - req, err := connection.createRequest("/", context.Background(), http.MethodGet, operationContext, &ReadContainerOptions{}, addHeader) - if err != nil { - t.Fatal(err) - } - - if req.Raw().Header.Get("my-header") != "12345" { - t.Errorf("Expected %v, but got %v", "12345", req.Raw().Header.Get("my-header")) - } -} - -func TestNoOptionsIsCalled(t *testing.T) { - srv, close := mock.NewTLSServer() - defer close() - srv.SetResponse( - mock.WithStatusCode(200)) - - pl := azruntime.NewPipeline(srv) - connection := &cosmosClientConnection{endpoint: srv.URL(), Pipeline: pl} - operationContext := cosmosOperationContext{ - resourceType: resourceTypeDatabase, - resourceAddress: "", - } - - _, err := connection.createRequest("/", context.Background(), http.MethodGet, operationContext, nil, nil) - if err != nil { - t.Fatal(err) - } -} diff --git a/sdk/data/azcosmos/cosmos_client_options.go b/sdk/data/azcosmos/cosmos_client_options.go index 938fc0d945b3..8095f8bc2050 100644 --- a/sdk/data/azcosmos/cosmos_client_options.go +++ b/sdk/data/azcosmos/cosmos_client_options.go @@ -4,53 +4,13 @@ package azcosmos import ( - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" ) -// CosmosClientOptions defines the options for the Cosmos client. -type CosmosClientOptions struct { - // HTTPClient sets the transport for making HTTP requests. - HTTPClient policy.Transporter - // Retry configures the built-in retry policy behavior. - Retry policy.RetryOptions - // Telemetry configures the built-in telemetry policy behavior. - Telemetry policy.TelemetryOptions - // Logging configures the built-in logging policy behavior. - Logging policy.LogOptions - // PerCallPolicies contains custom policies to inject into the pipeline. - // Each policy is executed once per request. - PerCallPolicies []policy.Policy - // PerRetryPolicies contains custom policies to inject into the pipeline. - // Each policy is executed once per request, and for each retry request. - PerRetryPolicies []policy.Policy - // ApplicationPreferredRegions defines list of preferred regions for the client to connect to. - ApplicationPreferredRegions *[]string - // ConsistencyLevel can be used to weaken the database account consistency level for read operations. If this is not set the database account consistency level will be used for all requests. - ConsistencyLevel ConsistencyLevel +// ClientOptions defines the options for the Cosmos client. +type ClientOptions struct { + azcore.ClientOptions // When EnableContentResponseOnWrite is false will cause the response to have a null resource. This reduces networking and CPU load by not sending the resource back over the network and serializing it on the client. // The default is false. EnableContentResponseOnWrite bool - // LimitToEndpoint limits the operations to the provided endpoint on the Cosmos client. See https://docs.microsoft.com/azure/cosmos-db/troubleshoot-sdk-availability - LimitToEndpoint bool - // RateLimitedRetry defines the retry configuration for rate limited requests. - // By default, the SDK will do 9 retries. - RateLimitedRetry *CosmosClientOptionsRateLimitedRetry -} - -type CosmosClientOptionsRateLimitedRetry struct { - // MaxRetryAttempts specifies the number of retries to perform on rate limited requests. - MaxRetryAttempts int - // MaxRetryWaitTime specifies the maximum time to wait for retries. - MaxRetryWaitTime time.Duration -} - -// getSDKInternalPolicies builds a list of internal retry policies for the cosmos service. -// This includes throttling and failover policies. -func (o *CosmosClientOptions) getSDKInternalPolicies() []policy.Policy { - return []policy.Policy{ - newResourceThrottleRetryPolicy(o), - // TODO: Add more policies here. - } } diff --git a/sdk/data/azcosmos/cosmos_client_options_test.go b/sdk/data/azcosmos/cosmos_client_options_test.go deleted file mode 100644 index 7f6ce471dbe1..000000000000 --- a/sdk/data/azcosmos/cosmos_client_options_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcosmos - -import ( - "testing" -) - -func TestGetSDKInternalPolicies(t *testing.T) { - cosmosClientOptions := &CosmosClientOptions{} - policies := cosmosClientOptions.getSDKInternalPolicies() - if policies == nil { - t.Error("Expected policies to be not nil") - } - - if len(policies) == 0 { - t.Error("Expected policies to have more than 0 items ") - } -} - -func Test_newCosmosClientConnection(t *testing.T) { - cred, _ := NewKeyCredential("someKey") - connection := newCosmosClientConnection("https://test.com", cred, &CosmosClientOptions{}) - if connection == nil { - t.Error("Expected connection to be not nil") - } -} diff --git a/sdk/data/azcosmos/cosmos_client_test.go b/sdk/data/azcosmos/cosmos_client_test.go new file mode 100644 index 000000000000..a73ea5af7059 --- /dev/null +++ b/sdk/data/azcosmos/cosmos_client_test.go @@ -0,0 +1,371 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azcosmos + +import ( + "context" + "encoding/json" + "io/ioutil" + "net/http" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" +) + +func TestEnsureErrorIsGeneratedOnResponse(t *testing.T) { + someError := &cosmosErrorResponse{ + Code: "SomeCode", + } + + jsonString, err := json.Marshal(someError) + if err != nil { + t.Fatal(err) + } + + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse( + mock.WithBody(jsonString), + mock.WithStatusCode(404)) + + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + client := &Client{endpoint: srv.URL(), pipeline: pl} + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDatabase, + resourceAddress: "", + } + _, err = client.sendGetRequest("/", context.Background(), operationContext, &ReadContainerOptions{}, nil) + if err == nil { + t.Fatal("Expected error") + } + + asError := err.(*cosmosError) + if asError.ErrorCode() != someError.Code { + t.Errorf("Expected %v, but got %v", someError.Code, asError.ErrorCode()) + } + + if err.Error() != asError.Error() { + t.Errorf("Expected %v, but got %v", err.Error(), asError.Error()) + } +} + +func TestEnsureErrorIsNotGeneratedOnResponse(t *testing.T) { + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse( + mock.WithStatusCode(200)) + + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + client := &Client{endpoint: srv.URL(), pipeline: pl} + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDatabase, + resourceAddress: "", + } + _, err := client.sendGetRequest("/", context.Background(), operationContext, &ReadContainerOptions{}, nil) + if err != nil { + t.Fatal(err) + } +} + +func TestRequestEnricherIsCalled(t *testing.T) { + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse( + mock.WithStatusCode(200)) + + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + client := &Client{endpoint: srv.URL(), pipeline: pl} + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDatabase, + resourceAddress: "", + } + + addHeader := func(r *policy.Request) { + r.Raw().Header.Add("my-header", "12345") + } + + req, err := client.createRequest("/", context.Background(), http.MethodGet, operationContext, &ReadContainerOptions{}, addHeader) + if err != nil { + t.Fatal(err) + } + + if req.Raw().Header.Get("my-header") != "12345" { + t.Errorf("Expected %v, but got %v", "12345", req.Raw().Header.Get("my-header")) + } +} + +func TestNoOptionsIsCalled(t *testing.T) { + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse( + mock.WithStatusCode(200)) + + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + client := &Client{endpoint: srv.URL(), pipeline: pl} + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDatabase, + resourceAddress: "", + } + + _, err := client.createRequest("/", context.Background(), http.MethodGet, operationContext, nil, nil) + if err != nil { + t.Fatal(err) + } +} + +func TestAttachContent(t *testing.T) { + srv, close := mock.NewTLSServer() + defer close() + + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + client := &Client{endpoint: srv.URL(), pipeline: pl} + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDatabase, + resourceAddress: "", + } + + body := map[string]string{ + "foo": "bar", + } + + marshalled, _ := json.Marshal(body) + + // Using the interface{} + req, err := client.createRequest("/", context.Background(), http.MethodGet, operationContext, nil, nil) + if err != nil { + t.Fatal(err) + } + + err = client.attachContent(body, req) + if err != nil { + t.Fatal(err) + } + + readBody, _ := ioutil.ReadAll(req.Body()) + + if string(readBody) != string(marshalled) { + t.Errorf("Expected %v, but got %v", string(marshalled), string(readBody)) + } + + // Using the raw []byte + req, err = client.createRequest("/", context.Background(), http.MethodGet, operationContext, nil, nil) + if err != nil { + t.Fatal(err) + } + + err = client.attachContent(marshalled, req) + if err != nil { + t.Fatal(err) + } + + readBody, _ = ioutil.ReadAll(req.Body()) + + if string(readBody) != string(marshalled) { + t.Errorf("Expected %v, but got %v", string(marshalled), string(readBody)) + } +} + +func TestCreateRequest(t *testing.T) { + srv, close := mock.NewTLSServer() + defer close() + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + client := &Client{endpoint: srv.URL(), pipeline: pl} + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDatabase, + resourceAddress: "", + } + + req, err := client.createRequest("/", context.Background(), http.MethodGet, operationContext, nil, nil) + if err != nil { + t.Fatal(err) + } + + if req.Raw().URL.String() != srv.URL()+"/" { + t.Errorf("Expected %v, but got %v", srv.URL()+"/", req.Raw().URL.String()) + } + + if req.Raw().Method != http.MethodGet { + t.Errorf("Expected %v, but got %v", http.MethodGet, req.Raw().Method) + } + + if req.Raw().Header.Get(headerXmsDate) == "" { + t.Errorf("Expected %v, but got %v", "", req.Raw().Header.Get(headerXmsDate)) + } + + if req.Raw().Header.Get(headerXmsVersion) != "2020-11-05" { + t.Errorf("Expected %v, but got %v", "2020-11-05", req.Raw().Header.Get(headerXmsVersion)) + } + + opValue := pipelineRequestOptions{} + if !req.OperationValue(&opValue) { + t.Error("Expected to find operation value") + } +} + +func TestSendDelete(t *testing.T) { + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse( + mock.WithStatusCode(200)) + verifier := pipelineVerifier{} + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{&verifier}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + client := &Client{endpoint: srv.URL(), pipeline: pl} + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDatabase, + resourceAddress: "", + } + + _, err := client.sendDeleteRequest("/", context.Background(), operationContext, &DeleteDatabaseOptions{}, nil) + if err != nil { + t.Fatal(err) + } + + if verifier.method != http.MethodDelete { + t.Errorf("Expected %v, but got %v", http.MethodDelete, verifier.method) + } +} + +func TestSendGet(t *testing.T) { + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse( + mock.WithStatusCode(200)) + verifier := pipelineVerifier{} + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{&verifier}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + client := &Client{endpoint: srv.URL(), pipeline: pl} + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDatabase, + resourceAddress: "", + } + + _, err := client.sendGetRequest("/", context.Background(), operationContext, &DeleteDatabaseOptions{}, nil) + if err != nil { + t.Fatal(err) + } + + if verifier.method != http.MethodGet { + t.Errorf("Expected %v, but got %v", http.MethodGet, verifier.method) + } +} + +func TestSendPut(t *testing.T) { + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse( + mock.WithStatusCode(200)) + verifier := pipelineVerifier{} + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{&verifier}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + client := &Client{endpoint: srv.URL(), pipeline: pl} + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDatabase, + resourceAddress: "", + } + + body := map[string]string{ + "foo": "bar", + } + + marshalled, _ := json.Marshal(body) + + _, err := client.sendPutRequest("/", context.Background(), body, operationContext, &DeleteDatabaseOptions{}, nil) + if err != nil { + t.Fatal(err) + } + + if verifier.method != http.MethodPut { + t.Errorf("Expected %v, but got %v", http.MethodPut, verifier.method) + } + + if verifier.body != string(marshalled) { + t.Errorf("Expected %v, but got %v", string(marshalled), verifier.body) + } +} + +func TestSendPost(t *testing.T) { + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse( + mock.WithStatusCode(200)) + verifier := pipelineVerifier{} + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{&verifier}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + client := &Client{endpoint: srv.URL(), pipeline: pl} + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDatabase, + resourceAddress: "", + } + + body := map[string]string{ + "foo": "bar", + } + + marshalled, _ := json.Marshal(body) + + _, err := client.sendPostRequest("/", context.Background(), body, operationContext, &DeleteDatabaseOptions{}, nil) + if err != nil { + t.Fatal(err) + } + + if verifier.method != http.MethodPost { + t.Errorf("Expected %v, but got %v", http.MethodPost, verifier.method) + } + + if verifier.body != string(marshalled) { + t.Errorf("Expected %v, but got %v", string(marshalled), verifier.body) + } +} + +func TestSendQuery(t *testing.T) { + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse( + mock.WithStatusCode(200)) + verifier := pipelineVerifier{} + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{&verifier}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + client := &Client{endpoint: srv.URL(), pipeline: pl} + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDatabase, + resourceAddress: "", + } + + _, err := client.sendQueryRequest("/", context.Background(), "SELECT * FROM c", operationContext, &DeleteDatabaseOptions{}, nil) + if err != nil { + t.Fatal(err) + } + + if verifier.method != http.MethodPost { + t.Errorf("Expected %v, but got %v", http.MethodPost, verifier.method) + } + + if verifier.isQuery != true { + t.Errorf("Expected %v, but got %v", true, verifier.isQuery) + } + + if verifier.contentType != cosmosHeaderValuesQuery { + t.Errorf("Expected %v, but got %v", cosmosHeaderValuesQuery, verifier.contentType) + } + + if verifier.body != "{\"query\":\"SELECT * FROM c\"}" { + t.Errorf("Expected %v, but got %v", "{\"query\":\"SELECT * FROM c\"}", verifier.body) + } +} + +type pipelineVerifier struct { + method string + body string + contentType string + isQuery bool +} + +func (p *pipelineVerifier) Do(req *policy.Request) (*http.Response, error) { + p.method = req.Raw().Method + if req.Body() != nil { + readBody, _ := ioutil.ReadAll(req.Body()) + p.body = string(readBody) + } + p.contentType = req.Raw().Header.Get(headerContentType) + p.isQuery = req.Raw().Method == http.MethodPost && req.Raw().Header.Get(cosmosHeaderQuery) == "True" + return req.Next() +} diff --git a/sdk/data/azcosmos/cosmos_container.go b/sdk/data/azcosmos/cosmos_container.go index 700532afedca..5d50ac9e3b32 100644 --- a/sdk/data/azcosmos/cosmos_container.go +++ b/sdk/data/azcosmos/cosmos_container.go @@ -9,35 +9,39 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" ) -// A Container lets you perform read, update, change throughput, and delete container operations. +// A ContainerClient lets you perform read, update, change throughput, and delete container operations. // It also lets you perform read, update, change throughput, and delete item operations. -type Container struct { +type ContainerClient struct { // The Id of the Cosmos container - Id string + id string // The database that contains the container - Database *Database + database *DatabaseClient // The resource link link string } -func newContainer(id string, database *Database) *Container { - return &Container{ - Id: id, - Database: database, - link: createLink(database.link, pathSegmentCollection, id)} +func newContainer(id string, database *DatabaseClient) (ContainerClient, error) { + return ContainerClient{ + id: id, + database: database, + link: createLink(database.link, pathSegmentCollection, id)}, nil +} + +func (c *ContainerClient) ID() string { + return c.id } // Read obtains the information for a Cosmos container. // ctx - The context for the request. // o - Options for the operation. -func (c *Container) Read( +func (c *ContainerClient) Read( ctx context.Context, o *ReadContainerOptions) (ContainerResponse, error) { if o == nil { o = &ReadContainerOptions{} } - operationContext := cosmosOperationContext{ + operationContext := pipelineRequestOptions{ resourceType: resourceTypeCollection, resourceAddress: c.link, } @@ -47,7 +51,7 @@ func (c *Container) Read( return ContainerResponse{}, err } - azResponse, err := c.Database.client.connection.sendGetRequest( + azResponse, err := c.database.client.sendGetRequest( path, ctx, operationContext, @@ -57,13 +61,13 @@ func (c *Container) Read( return ContainerResponse{}, err } - return newContainerResponse(azResponse, c) + return newContainerResponse(azResponse) } // Replace a Cosmos container. // ctx - The context for the request. // o - Options for the operation. -func (c *Container) Replace( +func (c *ContainerClient) Replace( ctx context.Context, containerProperties ContainerProperties, o *ReplaceContainerOptions) (ContainerResponse, error) { @@ -71,7 +75,7 @@ func (c *Container) Replace( o = &ReplaceContainerOptions{} } - operationContext := cosmosOperationContext{ + operationContext := pipelineRequestOptions{ resourceType: resourceTypeCollection, resourceAddress: c.link, } @@ -81,7 +85,7 @@ func (c *Container) Replace( return ContainerResponse{}, err } - azResponse, err := c.Database.client.connection.sendPutRequest( + azResponse, err := c.database.client.sendPutRequest( path, ctx, containerProperties, @@ -92,20 +96,20 @@ func (c *Container) Replace( return ContainerResponse{}, err } - return newContainerResponse(azResponse, c) + return newContainerResponse(azResponse) } // Delete a Cosmos container. // ctx - The context for the request. // o - Options for the operation. -func (c *Container) Delete( +func (c *ContainerClient) Delete( ctx context.Context, o *DeleteContainerOptions) (ContainerResponse, error) { if o == nil { o = &DeleteContainerOptions{} } - operationContext := cosmosOperationContext{ + operationContext := pipelineRequestOptions{ resourceType: resourceTypeCollection, resourceAddress: c.link, } @@ -115,7 +119,7 @@ func (c *Container) Delete( return ContainerResponse{}, err } - azResponse, err := c.Database.client.connection.sendDeleteRequest( + azResponse, err := c.database.client.sendDeleteRequest( path, ctx, operationContext, @@ -125,13 +129,13 @@ func (c *Container) Delete( return ContainerResponse{}, err } - return newContainerResponse(azResponse, c) + return newContainerResponse(azResponse) } // ReadThroughput obtains the provisioned throughput information for the container. // ctx - The context for the request. // o - Options for the operation. -func (c *Container) ReadThroughput( +func (c *ContainerClient) ReadThroughput( ctx context.Context, o *ThroughputOptions) (ThroughputResponse, error) { if o == nil { @@ -143,7 +147,7 @@ func (c *Container) ReadThroughput( return ThroughputResponse{}, err } - offers := &cosmosOffers{connection: c.Database.client.connection} + offers := &cosmosOffers{client: c.database.client} return offers.ReadThroughputIfExists(ctx, rid, o) } @@ -151,7 +155,7 @@ func (c *Container) ReadThroughput( // ctx - The context for the request. // throughputProperties - The throughput configuration of the container. // o - Options for the operation. -func (c *Container) ReplaceThroughput( +func (c *ContainerClient) ReplaceThroughput( ctx context.Context, throughputProperties ThroughputProperties, o *ThroughputOptions) (ThroughputResponse, error) { @@ -164,7 +168,7 @@ func (c *Container) ReplaceThroughput( return ThroughputResponse{}, err } - offers := &cosmosOffers{connection: c.Database.client.connection} + offers := &cosmosOffers{client: c.database.client} return offers.ReadThroughputIfExists(ctx, rid, o) } @@ -173,38 +177,39 @@ func (c *Container) ReplaceThroughput( // partitionKey - The partition key for the item. // item - The item to create. // o - Options for the operation. -func (c *Container) CreateItem( +func (c *ContainerClient) CreateItem( ctx context.Context, partitionKey PartitionKey, - item interface{}, + item []byte, o *ItemOptions) (ItemResponse, error) { - - addHeader, err := c.buildRequestEnricher(partitionKey, o, true) - if err != nil { - return ItemResponse{}, err + h := headerOptionsOverride{ + partitionKey: &partitionKey, } if o == nil { o = &ItemOptions{} + } else { + h.enableContentResponseOnWrite = &o.EnableContentResponseOnWrite } - operationContext := cosmosOperationContext{ - resourceType: resourceTypeDocument, - resourceAddress: c.link, - } + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDocument, + resourceAddress: c.link, + isWriteOperation: true, + headerOptionsOverride: &h} path, err := generatePathForNameBased(resourceTypeDocument, c.link, true) if err != nil { return ItemResponse{}, err } - azResponse, err := c.Database.client.connection.sendPostRequest( + azResponse, err := c.database.client.sendPostRequest( path, ctx, item, operationContext, o, - addHeader) + nil) if err != nil { return ItemResponse{}, err } @@ -217,37 +222,37 @@ func (c *Container) CreateItem( // partitionKey - The partition key for the item. // item - The item to upsert. // o - Options for the operation. -func (c *Container) UpsertItem( +func (c *ContainerClient) UpsertItem( ctx context.Context, partitionKey PartitionKey, - item interface{}, + item []byte, o *ItemOptions) (ItemResponse, error) { - - addHeaderInternal, err := c.buildRequestEnricher(partitionKey, o, true) - if err != nil { - return ItemResponse{}, err + h := headerOptionsOverride{ + partitionKey: &partitionKey, } addHeader := func(r *policy.Request) { - addHeaderInternal(r) r.Raw().Header.Add(cosmosHeaderIsUpsert, "true") } if o == nil { o = &ItemOptions{} + } else { + h.enableContentResponseOnWrite = &o.EnableContentResponseOnWrite } - operationContext := cosmosOperationContext{ - resourceType: resourceTypeDocument, - resourceAddress: c.link, - } + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDocument, + resourceAddress: c.link, + isWriteOperation: true, + headerOptionsOverride: &h} path, err := generatePathForNameBased(resourceTypeDocument, c.link, true) if err != nil { return ItemResponse{}, err } - azResponse, err := c.Database.client.connection.sendPostRequest( + azResponse, err := c.database.client.sendPostRequest( path, ctx, item, @@ -267,39 +272,40 @@ func (c *Container) UpsertItem( // itemId - The id of the item to replace. // item - The content to be used to replace. // o - Options for the operation. -func (c *Container) ReplaceItem( +func (c *ContainerClient) ReplaceItem( ctx context.Context, partitionKey PartitionKey, itemId string, - item interface{}, + item []byte, o *ItemOptions) (ItemResponse, error) { - - addHeader, err := c.buildRequestEnricher(partitionKey, o, true) - if err != nil { - return ItemResponse{}, err + h := headerOptionsOverride{ + partitionKey: &partitionKey, } if o == nil { o = &ItemOptions{} + } else { + h.enableContentResponseOnWrite = &o.EnableContentResponseOnWrite } - operationContext := cosmosOperationContext{ - resourceType: resourceTypeDocument, - resourceAddress: createLink(c.link, pathSegmentDocument, itemId), - } + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDocument, + resourceAddress: createLink(c.link, pathSegmentDocument, itemId), + isWriteOperation: true, + headerOptionsOverride: &h} path, err := generatePathForNameBased(resourceTypeDocument, operationContext.resourceAddress, false) if err != nil { return ItemResponse{}, err } - azResponse, err := c.Database.client.connection.sendPutRequest( + azResponse, err := c.database.client.sendPutRequest( path, ctx, item, operationContext, o, - addHeader) + nil) if err != nil { return ItemResponse{}, err } @@ -312,37 +318,35 @@ func (c *Container) ReplaceItem( // partitionKey - The partition key for the item. // itemId - The id of the item to read. // o - Options for the operation. -func (c *Container) ReadItem( +func (c *ContainerClient) ReadItem( ctx context.Context, partitionKey PartitionKey, itemId string, o *ItemOptions) (ItemResponse, error) { - - addHeader, err := c.buildRequestEnricher(partitionKey, o, false) - if err != nil { - return ItemResponse{}, err + h := headerOptionsOverride{ + partitionKey: &partitionKey, } if o == nil { o = &ItemOptions{} } - operationContext := cosmosOperationContext{ - resourceType: resourceTypeDocument, - resourceAddress: createLink(c.link, pathSegmentDocument, itemId), - } + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDocument, + resourceAddress: createLink(c.link, pathSegmentDocument, itemId), + headerOptionsOverride: &h} path, err := generatePathForNameBased(resourceTypeDocument, operationContext.resourceAddress, false) if err != nil { return ItemResponse{}, err } - azResponse, err := c.Database.client.connection.sendGetRequest( + azResponse, err := c.database.client.sendGetRequest( path, ctx, operationContext, o, - addHeader) + nil) if err != nil { return ItemResponse{}, err } @@ -355,37 +359,38 @@ func (c *Container) ReadItem( // partitionKey - The partition key for the item. // itemId - The id of the item to delete. // o - Options for the operation. -func (c *Container) DeleteItem( +func (c *ContainerClient) DeleteItem( ctx context.Context, partitionKey PartitionKey, itemId string, o *ItemOptions) (ItemResponse, error) { - - addHeader, err := c.buildRequestEnricher(partitionKey, o, true) - if err != nil { - return ItemResponse{}, err + h := headerOptionsOverride{ + partitionKey: &partitionKey, } if o == nil { o = &ItemOptions{} + } else { + h.enableContentResponseOnWrite = &o.EnableContentResponseOnWrite } - operationContext := cosmosOperationContext{ - resourceType: resourceTypeDocument, - resourceAddress: createLink(c.link, pathSegmentDocument, itemId), - } + operationContext := pipelineRequestOptions{ + resourceType: resourceTypeDocument, + resourceAddress: createLink(c.link, pathSegmentDocument, itemId), + isWriteOperation: true, + headerOptionsOverride: &h} path, err := generatePathForNameBased(resourceTypeDocument, operationContext.resourceAddress, false) if err != nil { return ItemResponse{}, err } - azResponse, err := c.Database.client.connection.sendDeleteRequest( + azResponse, err := c.database.client.sendDeleteRequest( path, ctx, operationContext, o, - addHeader) + nil) if err != nil { return ItemResponse{}, err } @@ -393,35 +398,11 @@ func (c *Container) DeleteItem( return newItemResponse(azResponse) } -func (c *Container) buildRequestEnricher( - partitionKey PartitionKey, - requestOptions *ItemOptions, - writeOperation bool) (func(r *policy.Request), error) { - pk, err := partitionKey.toJsonString() - if err != nil { - return nil, err - } - - var enableContentResponseOnWrite bool - if requestOptions == nil { - enableContentResponseOnWrite = c.Database.client.options.EnableContentResponseOnWrite - } else { - enableContentResponseOnWrite = requestOptions.EnableContentResponseOnWrite - } - - return func(r *policy.Request) { - r.Raw().Header.Add(cosmosHeaderPartitionKey, pk) - if writeOperation && !enableContentResponseOnWrite { - r.Raw().Header.Add(cosmosHeaderPrefer, cosmosHeaderValuesPreferMinimal) - } - }, nil -} - -func (c *Container) getRID(ctx context.Context) (string, error) { +func (c *ContainerClient) getRID(ctx context.Context) (string, error) { containerResponse, err := c.Read(ctx, nil) if err != nil { return "", err } - return containerResponse.ContainerProperties.ResourceId, nil + return containerResponse.ContainerProperties.ResourceID, nil } diff --git a/sdk/data/azcosmos/cosmos_container_properties.go b/sdk/data/azcosmos/cosmos_container_properties.go index b56feb0459a2..6697273c1f95 100644 --- a/sdk/data/azcosmos/cosmos_container_properties.go +++ b/sdk/data/azcosmos/cosmos_container_properties.go @@ -3,26 +3,28 @@ package azcosmos -import "github.com/Azure/azure-sdk-for-go/sdk/azcore" +import ( + "github.com/Azure/azure-sdk-for-go/sdk/azcore" +) // ContainerProperties represents the properties of a container. type ContainerProperties struct { - // Id contains the unique id of the container. - Id string `json:"id"` + // ID contains the unique id of the container. + ID string `json:"id"` // ETag contains the entity etag of the container. - ETag azcore.ETag `json:"_etag,omitempty"` + ETag *azcore.ETag `json:"_etag,omitempty"` // SelfLink contains the self-link of the container. SelfLink string `json:"_self,omitempty"` - // ResourceId contains the resource id of the container. - ResourceId string `json:"_rid,omitempty"` + // ResourceID contains the resource id of the container. + ResourceID string `json:"_rid,omitempty"` // LastModified contains the last modified time of the container. - LastModified *UnixTime `json:"_ts,omitempty"` + LastModified int64 `json:"_ts,omitempty"` // DefaultTimeToLive contains the default time to live in seconds for items in the container. // For more information see https://docs.microsoft.com/azure/cosmos-db/time-to-live#time-to-live-configurations - DefaultTimeToLive *int `json:"defaultTtl,omitempty"` + DefaultTimeToLive *int32 `json:"defaultTtl,omitempty"` // AnalyticalStoreTimeToLiveInSeconds contains the default time to live in seconds for analytical store in the container. // For more information see https://docs.microsoft.com/azure/cosmos-db/analytical-store-introduction#analytical-ttl - AnalyticalStoreTimeToLiveInSeconds *int `json:"analyticalStorageTtl,omitempty"` + AnalyticalStoreTimeToLiveInSeconds *int32 `json:"analyticalStorageTtl,omitempty"` // PartitionKeyDefinition contains the partition key definition of the container. PartitionKeyDefinition PartitionKeyDefinition `json:"partitionKey,omitempty"` // IndexingPolicy contains the indexing definition of the container. @@ -31,6 +33,4 @@ type ContainerProperties struct { UniqueKeyPolicy *UniqueKeyPolicy `json:"uniqueKeyPolicy,omitempty"` // ConflictResolutionPolicy contains the conflict resolution policy of the container. ConflictResolutionPolicy *ConflictResolutionPolicy `json:"conflictResolutionPolicy,omitempty"` - // Container represented by these properties - Container *Container `json:"-"` } diff --git a/sdk/data/azcosmos/cosmos_container_properties_test.go b/sdk/data/azcosmos/cosmos_container_properties_test.go index 9195225da538..12ad6741c47b 100644 --- a/sdk/data/azcosmos/cosmos_container_properties_test.go +++ b/sdk/data/azcosmos/cosmos_container_properties_test.go @@ -7,24 +7,24 @@ import ( "encoding/json" "testing" "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" ) func TestContainerPropertiesSerialization(t *testing.T) { nowAsUnix := time.Now().Unix() - now := UnixTime{ - Time: time.Unix(nowAsUnix, 0), - } + etag := azcore.ETag("etag") properties := &ContainerProperties{ - Id: "someId", - ETag: "someEtag", + ID: "someId", + ETag: &etag, SelfLink: "someSelfLink", - ResourceId: "someResourceId", - LastModified: &now, + ResourceID: "someResourceId", + LastModified: nowAsUnix, PartitionKeyDefinition: PartitionKeyDefinition{ Paths: []string{"somePath"}, - Version: PartitionKeyDefinitionVersion2, + Version: 2, }, IndexingPolicy: &IndexingPolicy{ IncludedPaths: []IncludedPath{ @@ -66,24 +66,24 @@ func TestContainerPropertiesSerialization(t *testing.T) { t.Fatal(err, string(jsonString)) } - if properties.Id != otherProperties.Id { - t.Errorf("Expected Id to be %s, but got %s", properties.Id, otherProperties.Id) + if properties.ID != otherProperties.ID { + t.Errorf("Expected Id to be %s, but got %s", properties.ID, otherProperties.ID) } - if properties.ETag != otherProperties.ETag { - t.Errorf("Expected ETag to be %s, but got %s", properties.ETag, otherProperties.ETag) + if *properties.ETag != *otherProperties.ETag { + t.Errorf("Expected ETag to be %s, but got %s", *properties.ETag, *otherProperties.ETag) } if properties.SelfLink != otherProperties.SelfLink { t.Errorf("Expected SelfLink to be %s, but got %s", properties.SelfLink, otherProperties.SelfLink) } - if properties.ResourceId != otherProperties.ResourceId { - t.Errorf("Expected ResourceId to be %s, but got %s", properties.ResourceId, otherProperties.ResourceId) + if properties.ResourceID != otherProperties.ResourceID { + t.Errorf("Expected ResourceId to be %s, but got %s", properties.ResourceID, otherProperties.ResourceID) } - if properties.LastModified.Time != otherProperties.LastModified.Time { - t.Errorf("Expected LastModified.Time to be %s, but got %s", properties.LastModified.Time.UTC(), otherProperties.LastModified.Time.UTC()) + if properties.LastModified != otherProperties.LastModified { + t.Errorf("Expected LastModified.Time to be %v, but got %v", properties.LastModified, otherProperties.LastModified) } if otherProperties.AnalyticalStoreTimeToLiveInSeconds != nil { diff --git a/sdk/data/azcosmos/cosmos_container_request_options_test.go b/sdk/data/azcosmos/cosmos_container_request_options_test.go index f95911a61cb7..df667d0ad52d 100644 --- a/sdk/data/azcosmos/cosmos_container_request_options_test.go +++ b/sdk/data/azcosmos/cosmos_container_request_options_test.go @@ -16,7 +16,7 @@ func TestContainerRequestOptionsToHeaders(t *testing.T) { options.PopulateQuotaInfo = true header := options.toHeaders() if header == nil { - t.Error("toHeaders should return non-nil") + t.Fatal("toHeaders should return non-nil") } headers := *header diff --git a/sdk/data/azcosmos/cosmos_container_response.go b/sdk/data/azcosmos/cosmos_container_response.go index 44cda0195039..9d216ca3861d 100644 --- a/sdk/data/azcosmos/cosmos_container_response.go +++ b/sdk/data/azcosmos/cosmos_container_response.go @@ -16,7 +16,7 @@ type ContainerResponse struct { Response } -func newContainerResponse(resp *http.Response, container *Container) (ContainerResponse, error) { +func newContainerResponse(resp *http.Response) (ContainerResponse, error) { response := ContainerResponse{ Response: newResponse(resp), } @@ -26,6 +26,5 @@ func newContainerResponse(resp *http.Response, container *Container) (ContainerR return response, err } response.ContainerProperties = properties - response.ContainerProperties.Container = container return response, nil } diff --git a/sdk/data/azcosmos/cosmos_container_response_test.go b/sdk/data/azcosmos/cosmos_container_response_test.go index 39547fc4a686..e4d31c292d27 100644 --- a/sdk/data/azcosmos/cosmos_container_response_test.go +++ b/sdk/data/azcosmos/cosmos_container_response_test.go @@ -10,6 +10,8 @@ import ( "testing" "time" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" ) @@ -17,26 +19,19 @@ import ( func TestContainerResponseParsing(t *testing.T) { nowAsUnix := time.Now().Unix() - now := UnixTime{ - Time: time.Unix(nowAsUnix, 0), - } - + etag := azcore.ETag("etag") properties := &ContainerProperties{ - Id: "someId", - ETag: "someEtag", + ID: "someId", + ETag: &etag, SelfLink: "someSelfLink", - ResourceId: "someResourceId", - LastModified: &now, + ResourceID: "someResourceId", + LastModified: nowAsUnix, PartitionKeyDefinition: PartitionKeyDefinition{ Paths: []string{"somePath"}, - Version: PartitionKeyDefinitionVersion2, + Version: 2, }, } - container := &Container{ - Id: "someId", - } - jsonString, err := json.Marshal(properties) if err != nil { t.Fatal(err) @@ -55,9 +50,9 @@ func TestContainerResponseParsing(t *testing.T) { t.Fatal(err) } - pl := azruntime.NewPipeline(srv) + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) resp, _ := pl.Do(req) - parsedResponse, err := newContainerResponse(resp, container) + parsedResponse, err := newContainerResponse(resp) if err != nil { t.Fatal(err) } @@ -70,24 +65,24 @@ func TestContainerResponseParsing(t *testing.T) { t.Fatal("parsedResponse.ContainerProperties is nil") } - if properties.Id != parsedResponse.ContainerProperties.Id { - t.Errorf("Expected Id to be %s, but got %s", properties.Id, parsedResponse.ContainerProperties.Id) + if properties.ID != parsedResponse.ContainerProperties.ID { + t.Errorf("Expected Id to be %s, but got %s", properties.ID, parsedResponse.ContainerProperties.ID) } - if properties.ETag != parsedResponse.ContainerProperties.ETag { - t.Errorf("Expected ETag to be %s, but got %s", properties.ETag, parsedResponse.ContainerProperties.ETag) + if *properties.ETag != *parsedResponse.ContainerProperties.ETag { + t.Errorf("Expected ETag to be %s, but got %s", *properties.ETag, *parsedResponse.ContainerProperties.ETag) } if properties.SelfLink != parsedResponse.ContainerProperties.SelfLink { t.Errorf("Expected SelfLink to be %s, but got %s", properties.SelfLink, parsedResponse.ContainerProperties.SelfLink) } - if properties.ResourceId != parsedResponse.ContainerProperties.ResourceId { - t.Errorf("Expected ResourceId to be %s, but got %s", properties.ResourceId, parsedResponse.ContainerProperties.ResourceId) + if properties.ResourceID != parsedResponse.ContainerProperties.ResourceID { + t.Errorf("Expected ResourceId to be %s, but got %s", properties.ResourceID, parsedResponse.ContainerProperties.ResourceID) } - if properties.LastModified.Time != parsedResponse.ContainerProperties.LastModified.Time { - t.Errorf("Expected LastModified.Time to be %s, but got %s", properties.LastModified.Time.UTC(), parsedResponse.ContainerProperties.LastModified.Time.UTC()) + if properties.LastModified != parsedResponse.ContainerProperties.LastModified { + t.Errorf("Expected LastModified.Time to be %v, but got %v", properties.LastModified, parsedResponse.ContainerProperties.LastModified) } if properties.PartitionKeyDefinition.Paths[0] != parsedResponse.ContainerProperties.PartitionKeyDefinition.Paths[0] { @@ -109,8 +104,4 @@ func TestContainerResponseParsing(t *testing.T) { if parsedResponse.ETag != "someEtag" { t.Errorf("Expected ETag to be %s, but got %s", "someEtag", parsedResponse.ETag) } - - if parsedResponse.ContainerProperties.Container != container { - t.Errorf("Expected Container to be %v, but got %v", container, parsedResponse.ContainerProperties.Container) - } } diff --git a/sdk/data/azcosmos/cosmos_database.go b/sdk/data/azcosmos/cosmos_database.go index 2f6ee77eef87..37d6fa9b2b4d 100644 --- a/sdk/data/azcosmos/cosmos_database.go +++ b/sdk/data/azcosmos/cosmos_database.go @@ -8,38 +8,42 @@ import ( "errors" ) -// A Database lets you perform read, update, change throughput, and delete database operations. -type Database struct { +// A DatabaseClient lets you perform read, update, change throughput, and delete database operations. +type DatabaseClient struct { // The Id of the Cosmos database - Id string + id string // The client associated with the Cosmos database client *Client // The resource link link string } -func newDatabase(id string, client *Client) *Database { - return &Database{ - Id: id, +func newDatabase(id string, client *Client) (DatabaseClient, error) { + return DatabaseClient{ + id: id, client: client, - link: createLink("", pathSegmentDatabase, id)} + link: createLink("", pathSegmentDatabase, id)}, nil } -// GetContainer returns a Container object for the container. +func (db *DatabaseClient) ID() string { + return db.id +} + +// NewContainer returns a Container object for the container. // id - The id of the container. -func (db *Database) GetContainer(id string) (*Container, error) { +func (db *DatabaseClient) NewContainer(id string) (ContainerClient, error) { if id == "" { - return nil, errors.New("id is required") + return ContainerClient{}, errors.New("id is required") } - return newContainer(id, db), nil + return newContainer(id, db) } // CreateContainer creates a container in the Cosmos database. // ctx - The context for the request. // containerProperties - The properties for the container. // o - Options for the create container operation. -func (db *Database) CreateContainer( +func (db *DatabaseClient) CreateContainer( ctx context.Context, containerProperties ContainerProperties, o *CreateContainerOptions) (ContainerResponse, error) { @@ -47,7 +51,7 @@ func (db *Database) CreateContainer( o = &CreateContainerOptions{} } - operationContext := cosmosOperationContext{ + operationContext := pipelineRequestOptions{ resourceType: resourceTypeCollection, resourceAddress: db.link, } @@ -57,12 +61,7 @@ func (db *Database) CreateContainer( return ContainerResponse{}, err } - container, err := db.GetContainer(containerProperties.Id) - if err != nil { - return ContainerResponse{}, err - } - - azResponse, err := db.client.connection.sendPostRequest( + azResponse, err := db.client.sendPostRequest( path, ctx, containerProperties, @@ -73,20 +72,20 @@ func (db *Database) CreateContainer( return ContainerResponse{}, err } - return newContainerResponse(azResponse, container) + return newContainerResponse(azResponse) } // Read obtains the information for a Cosmos database. // ctx - The context for the request. // o - Options for Read operation. -func (db *Database) Read( +func (db *DatabaseClient) Read( ctx context.Context, o *ReadDatabaseOptions) (DatabaseResponse, error) { if o == nil { o = &ReadDatabaseOptions{} } - operationContext := cosmosOperationContext{ + operationContext := pipelineRequestOptions{ resourceType: resourceTypeDatabase, resourceAddress: db.link, } @@ -96,7 +95,7 @@ func (db *Database) Read( return DatabaseResponse{}, err } - azResponse, err := db.client.connection.sendGetRequest( + azResponse, err := db.client.sendGetRequest( path, ctx, operationContext, @@ -106,13 +105,13 @@ func (db *Database) Read( return DatabaseResponse{}, err } - return newDatabaseResponse(azResponse, db) + return newDatabaseResponse(azResponse) } // ReadThroughput obtains the provisioned throughput information for the database. // ctx - The context for the request. // o - Options for the operation. -func (db *Database) ReadThroughput( +func (db *DatabaseClient) ReadThroughput( ctx context.Context, o *ThroughputOptions) (ThroughputResponse, error) { if o == nil { @@ -124,7 +123,7 @@ func (db *Database) ReadThroughput( return ThroughputResponse{}, err } - offers := &cosmosOffers{connection: db.client.connection} + offers := &cosmosOffers{client: db.client} return offers.ReadThroughputIfExists(ctx, rid, o) } @@ -132,7 +131,7 @@ func (db *Database) ReadThroughput( // ctx - The context for the request. // throughputProperties - The throughput configuration of the database. // o - Options for the operation. -func (db *Database) ReplaceThroughput( +func (db *DatabaseClient) ReplaceThroughput( ctx context.Context, throughputProperties ThroughputProperties, o *ThroughputOptions) (ThroughputResponse, error) { @@ -145,21 +144,21 @@ func (db *Database) ReplaceThroughput( return ThroughputResponse{}, err } - offers := &cosmosOffers{connection: db.client.connection} + offers := &cosmosOffers{client: db.client} return offers.ReadThroughputIfExists(ctx, rid, o) } // Delete a Cosmos database. // ctx - The context for the request. // o - Options for Read operation. -func (db *Database) Delete( +func (db *DatabaseClient) Delete( ctx context.Context, o *DeleteDatabaseOptions) (DatabaseResponse, error) { if o == nil { o = &DeleteDatabaseOptions{} } - operationContext := cosmosOperationContext{ + operationContext := pipelineRequestOptions{ resourceType: resourceTypeDatabase, resourceAddress: db.link, } @@ -169,7 +168,7 @@ func (db *Database) Delete( return DatabaseResponse{}, err } - azResponse, err := db.client.connection.sendDeleteRequest( + azResponse, err := db.client.sendDeleteRequest( path, ctx, operationContext, @@ -179,14 +178,14 @@ func (db *Database) Delete( return DatabaseResponse{}, err } - return newDatabaseResponse(azResponse, db) + return newDatabaseResponse(azResponse) } -func (db *Database) getRID(ctx context.Context) (string, error) { +func (db *DatabaseClient) getRID(ctx context.Context) (string, error) { dbResponse, err := db.Read(ctx, nil) if err != nil { return "", err } - return dbResponse.DatabaseProperties.ResourceId, nil + return dbResponse.DatabaseProperties.ResourceID, nil } diff --git a/sdk/data/azcosmos/cosmos_database_properties.go b/sdk/data/azcosmos/cosmos_database_properties.go index cccec0da469d..da9aa7afffb5 100644 --- a/sdk/data/azcosmos/cosmos_database_properties.go +++ b/sdk/data/azcosmos/cosmos_database_properties.go @@ -3,20 +3,20 @@ package azcosmos -import "github.com/Azure/azure-sdk-for-go/sdk/azcore" +import ( + "github.com/Azure/azure-sdk-for-go/sdk/azcore" +) // DatabaseProperties represents the properties of a database. type DatabaseProperties struct { - // Id contains the unique id of the database. - Id string `json:"id"` + // ID contains the unique id of the database. + ID string `json:"id"` // ETag contains the entity etag of the database ETag azcore.ETag `json:"_etag,omitempty"` // SelfLink contains the self-link of the database SelfLink string `json:"_self,omitempty"` - // ResourceId contains the resource id of the database - ResourceId string `json:"_rid,omitempty"` + // ResourceID contains the resource id of the database + ResourceID string `json:"_rid,omitempty"` // LastModified contains the last modified time of the database - LastModified *UnixTime `json:"_ts,omitempty"` - // Database represented by these properties - Database *Database `json:"-"` + LastModified int64 `json:"_ts,omitempty"` } diff --git a/sdk/data/azcosmos/cosmos_database_properties_test.go b/sdk/data/azcosmos/cosmos_database_properties_test.go index 68c25fa7877b..6a6a0ebf03f3 100644 --- a/sdk/data/azcosmos/cosmos_database_properties_test.go +++ b/sdk/data/azcosmos/cosmos_database_properties_test.go @@ -12,16 +12,12 @@ import ( func TestDatabasePropertiesSerialization(t *testing.T) { nowAsUnix := time.Now().Unix() - now := UnixTime{ - Time: time.Unix(nowAsUnix, 0), - } - properties := &DatabaseProperties{ - Id: "someId", + ID: "someId", ETag: "someEtag", SelfLink: "someSelfLink", - ResourceId: "someResourceId", - LastModified: &now, + ResourceID: "someResourceId", + LastModified: nowAsUnix, } jsonString, err := json.Marshal(properties) @@ -35,8 +31,8 @@ func TestDatabasePropertiesSerialization(t *testing.T) { t.Fatal(err, string(jsonString)) } - if properties.Id != otherProperties.Id { - t.Errorf("Expected otherProperties.Id to be %s, but got %s", properties.Id, otherProperties.Id) + if properties.ID != otherProperties.ID { + t.Errorf("Expected otherProperties.Id to be %s, but got %s", properties.ID, otherProperties.ID) } if properties.ETag != otherProperties.ETag { @@ -47,12 +43,12 @@ func TestDatabasePropertiesSerialization(t *testing.T) { t.Errorf("Expected otherProperties.SelfLink to be %s, but got %s", properties.SelfLink, otherProperties.SelfLink) } - if properties.ResourceId != otherProperties.ResourceId { - t.Errorf("Expected otherProperties.ResourceId to be %s, but got %s", properties.ResourceId, otherProperties.ResourceId) + if properties.ResourceID != otherProperties.ResourceID { + t.Errorf("Expected otherProperties.ResourceId to be %s, but got %s", properties.ResourceID, otherProperties.ResourceID) } - if properties.LastModified.Time != otherProperties.LastModified.Time { - t.Errorf("Expected otherProperties.LastModified.Time to be %s, but got %s", properties.LastModified.Time.UTC(), otherProperties.LastModified.Time.UTC()) + if properties.LastModified != otherProperties.LastModified { + t.Errorf("Expected otherProperties.LastModified.Time to be %v, but got %v", properties.LastModified, otherProperties.LastModified) } } diff --git a/sdk/data/azcosmos/cosmos_database_request_options_test.go b/sdk/data/azcosmos/cosmos_database_request_options_test.go index 3e3f70b7ea2b..ad6b9868f0b8 100644 --- a/sdk/data/azcosmos/cosmos_database_request_options_test.go +++ b/sdk/data/azcosmos/cosmos_database_request_options_test.go @@ -21,7 +21,7 @@ func TestDatabaseOptionsToHeaders(t *testing.T) { options.IfNoneMatchEtag = &noneEtagValue header := options.toHeaders() if header == nil { - t.Error("toHeaders should return non-nil") + t.Fatal("toHeaders should return non-nil") } headers := *header @@ -45,7 +45,7 @@ func TestDeleteDatabaseOptionsToHeaders(t *testing.T) { options.IfNoneMatchEtag = &noneEtagValue header := options.toHeaders() if header == nil { - t.Error("toHeaders should return non-nil") + t.Fatal("toHeaders should return non-nil") } headers := *header diff --git a/sdk/data/azcosmos/cosmos_database_response.go b/sdk/data/azcosmos/cosmos_database_response.go index f4972d26550e..614f4fbefc8f 100644 --- a/sdk/data/azcosmos/cosmos_database_response.go +++ b/sdk/data/azcosmos/cosmos_database_response.go @@ -16,7 +16,7 @@ type DatabaseResponse struct { Response } -func newDatabaseResponse(resp *http.Response, database *Database) (DatabaseResponse, error) { +func newDatabaseResponse(resp *http.Response) (DatabaseResponse, error) { response := DatabaseResponse{ Response: newResponse(resp), } @@ -26,6 +26,5 @@ func newDatabaseResponse(resp *http.Response, database *Database) (DatabaseRespo return response, err } response.DatabaseProperties = properties - response.DatabaseProperties.Database = database return response, nil } diff --git a/sdk/data/azcosmos/cosmos_database_response_test.go b/sdk/data/azcosmos/cosmos_database_response_test.go index 00a9e8f5d31f..8995103841c7 100644 --- a/sdk/data/azcosmos/cosmos_database_response_test.go +++ b/sdk/data/azcosmos/cosmos_database_response_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" ) @@ -17,20 +18,12 @@ import ( func TestDatabaseResponseParsing(t *testing.T) { nowAsUnix := time.Now().Unix() - now := UnixTime{ - Time: time.Unix(nowAsUnix, 0), - } - properties := &DatabaseProperties{ - Id: "someId", + ID: "someId", ETag: "someEtag", SelfLink: "someSelfLink", - ResourceId: "someResourceId", - LastModified: &now, - } - - database := &Database{ - Id: "someId", + ResourceID: "someResourceId", + LastModified: nowAsUnix, } jsonString, err := json.Marshal(properties) @@ -51,9 +44,9 @@ func TestDatabaseResponseParsing(t *testing.T) { t.Fatal(err) } - pl := azruntime.NewPipeline(srv) + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) resp, _ := pl.Do(req) - parsedResponse, err := newDatabaseResponse(resp, database) + parsedResponse, err := newDatabaseResponse(resp) if err != nil { t.Fatal(err) } @@ -66,8 +59,8 @@ func TestDatabaseResponseParsing(t *testing.T) { t.Fatal("parsedResponse.DatabaseProperties is nil") } - if properties.Id != parsedResponse.DatabaseProperties.Id { - t.Errorf("Expected properties.Id to be %s, but got %s", properties.Id, parsedResponse.DatabaseProperties.Id) + if properties.ID != parsedResponse.DatabaseProperties.ID { + t.Errorf("Expected properties.Id to be %s, but got %s", properties.ID, parsedResponse.DatabaseProperties.ID) } if properties.ETag != parsedResponse.DatabaseProperties.ETag { @@ -78,12 +71,12 @@ func TestDatabaseResponseParsing(t *testing.T) { t.Errorf("Expected properties.SelfLink to be %s, but got %s", properties.SelfLink, parsedResponse.DatabaseProperties.SelfLink) } - if properties.ResourceId != parsedResponse.DatabaseProperties.ResourceId { - t.Errorf("Expected properties.ResourceId to be %s, but got %s", properties.ResourceId, parsedResponse.DatabaseProperties.ResourceId) + if properties.ResourceID != parsedResponse.DatabaseProperties.ResourceID { + t.Errorf("Expected properties.ResourceId to be %s, but got %s", properties.ResourceID, parsedResponse.DatabaseProperties.ResourceID) } - if properties.LastModified.Time != parsedResponse.DatabaseProperties.LastModified.Time { - t.Errorf("Expected properties.LastModified.Time to be %s, but got %s", properties.LastModified.Time.UTC(), parsedResponse.DatabaseProperties.LastModified.Time.UTC()) + if properties.LastModified != parsedResponse.DatabaseProperties.LastModified { + t.Errorf("Expected properties.LastModified.Time to be %v, but got %v", properties.LastModified, parsedResponse.DatabaseProperties.LastModified) } if parsedResponse.ActivityId != "someActivityId" { @@ -97,8 +90,4 @@ func TestDatabaseResponseParsing(t *testing.T) { if parsedResponse.ETag != "someEtag" { t.Errorf("Expected ETag to be %s, but got %s", "someEtag", parsedResponse.ETag) } - - if parsedResponse.DatabaseProperties.Database != database { - t.Errorf("Expected database to be %v, but got %v", database, parsedResponse.DatabaseProperties.Database) - } } diff --git a/sdk/data/azcosmos/cosmos_error_test.go b/sdk/data/azcosmos/cosmos_error_test.go index db0d40642bab..539df8c69e90 100644 --- a/sdk/data/azcosmos/cosmos_error_test.go +++ b/sdk/data/azcosmos/cosmos_error_test.go @@ -9,6 +9,7 @@ import ( "net/http" "testing" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" ) @@ -24,7 +25,7 @@ func TestCosmosErrorOnEmptyResponse(t *testing.T) { t.Fatal(err) } - pl := azruntime.NewPipeline(srv) + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) resp, _ := pl.Do(req) cError := newCosmosError(resp) @@ -45,7 +46,7 @@ func TestCosmosErrorOnNonJsonBody(t *testing.T) { t.Fatal(err) } - pl := azruntime.NewPipeline(srv) + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) resp, _ := pl.Do(req) cError := newCosmosError(resp) @@ -75,7 +76,7 @@ func TestCosmosErrorOnJsonBody(t *testing.T) { t.Fatal(err) } - pl := azruntime.NewPipeline(srv) + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) resp, _ := pl.Do(req) cError := newCosmosError(resp) diff --git a/sdk/data/azcosmos/cosmos_headers.go b/sdk/data/azcosmos/cosmos_headers.go index 4747b500e1a7..87dc74440054 100644 --- a/sdk/data/azcosmos/cosmos_headers.go +++ b/sdk/data/azcosmos/cosmos_headers.go @@ -4,7 +4,6 @@ package azcosmos const ( - cosmosHeaderRetryAfter string = "x-ms-retryafter" cosmosHeaderRequestCharge string = "x-ms-request-charge" cosmosHeaderActivityId string = "x-ms-activity-id" cosmosHeaderEtag string = "etag" diff --git a/sdk/data/azcosmos/cosmos_headers_policy.go b/sdk/data/azcosmos/cosmos_headers_policy.go new file mode 100644 index 000000000000..23ca2d4f5773 --- /dev/null +++ b/sdk/data/azcosmos/cosmos_headers_policy.go @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azcosmos + +import ( + "net/http" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" +) + +type headerPolicies struct { + enableContentResponseOnWrite bool +} + +type headerOptionsOverride struct { + enableContentResponseOnWrite *bool + partitionKey *PartitionKey +} + +func (p *headerPolicies) Do(req *policy.Request) (*http.Response, error) { + o := pipelineRequestOptions{} + if req.OperationValue(&o) { + enableContentResponseOnWrite := p.enableContentResponseOnWrite + + if o.headerOptionsOverride != nil { + if o.headerOptionsOverride.enableContentResponseOnWrite != nil { + enableContentResponseOnWrite = *o.headerOptionsOverride.enableContentResponseOnWrite + } + + if o.headerOptionsOverride.partitionKey != nil { + pkAsString, err := o.headerOptionsOverride.partitionKey.toJsonString() + if err != nil { + return nil, err + } + req.Raw().Header.Add(cosmosHeaderPartitionKey, string(pkAsString)) + } + } + + if o.isWriteOperation && !enableContentResponseOnWrite { + req.Raw().Header.Add(cosmosHeaderPrefer, cosmosHeaderValuesPreferMinimal) + } + } + + return req.Next() +} diff --git a/sdk/data/azcosmos/cosmos_headers_policy_test.go b/sdk/data/azcosmos/cosmos_headers_policy_test.go new file mode 100644 index 000000000000..a41ed0f36d5a --- /dev/null +++ b/sdk/data/azcosmos/cosmos_headers_policy_test.go @@ -0,0 +1,205 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azcosmos + +import ( + "context" + "net/http" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" +) + +func TestAddContentHeaderDefaultOnWriteOperation(t *testing.T) { + headerPolicy := &headerPolicies{} + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse(mock.WithStatusCode(http.StatusOK)) + + verifier := headerPoliciesVerify{} + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{headerPolicy, &verifier}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + req, err := azruntime.NewRequest(context.Background(), http.MethodGet, srv.URL()) + req.SetOperationValue(pipelineRequestOptions{ + isWriteOperation: true, + }) + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + _, err = pl.Do(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !verifier.isEnableContentResponseOnWriteHeaderSet { + t.Fatalf("expected content response header to be set") + } +} + +func TestAddContentHeaderDefaultOnReadOperation(t *testing.T) { + headerPolicy := &headerPolicies{} + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse(mock.WithStatusCode(http.StatusOK)) + + verifier := headerPoliciesVerify{} + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{headerPolicy, &verifier}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + req, err := azruntime.NewRequest(context.Background(), http.MethodGet, srv.URL()) + req.SetOperationValue(pipelineRequestOptions{ + isWriteOperation: false, + }) + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + _, err = pl.Do(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if verifier.isEnableContentResponseOnWriteHeaderSet { + t.Fatalf("expected content response header to not be set") + } +} + +func TestAddContentHeaderOnWriteOperation(t *testing.T) { + headerPolicy := &headerPolicies{ + enableContentResponseOnWrite: true, + } + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse(mock.WithStatusCode(http.StatusOK)) + + verifier := headerPoliciesVerify{} + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{headerPolicy, &verifier}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + req, err := azruntime.NewRequest(context.Background(), http.MethodGet, srv.URL()) + req.SetOperationValue(pipelineRequestOptions{ + isWriteOperation: true, + }) + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + _, err = pl.Do(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if verifier.isEnableContentResponseOnWriteHeaderSet { + t.Fatalf("expected content response header to not be set") + } +} + +func TestAddContentHeaderOnWriteOperationWithOverride(t *testing.T) { + headerPolicy := &headerPolicies{ + enableContentResponseOnWrite: true, + } + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse(mock.WithStatusCode(http.StatusOK)) + + verifier := headerPoliciesVerify{} + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{headerPolicy, &verifier}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + req, err := azruntime.NewRequest(context.Background(), http.MethodGet, srv.URL()) + contentOverride := false + req.SetOperationValue(pipelineRequestOptions{ + isWriteOperation: true, + headerOptionsOverride: &headerOptionsOverride{ + enableContentResponseOnWrite: &contentOverride, + }, + }) + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + _, err = pl.Do(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !verifier.isEnableContentResponseOnWriteHeaderSet { + t.Fatalf("expected content response header to be set") + } +} + +func TestAddContentHeaderDefaultOnWriteOperationWithOverride(t *testing.T) { + headerPolicy := &headerPolicies{} + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse(mock.WithStatusCode(http.StatusOK)) + + verifier := headerPoliciesVerify{} + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{headerPolicy, &verifier}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + req, err := azruntime.NewRequest(context.Background(), http.MethodGet, srv.URL()) + contentOverride := true + req.SetOperationValue(pipelineRequestOptions{ + isWriteOperation: true, + headerOptionsOverride: &headerOptionsOverride{ + enableContentResponseOnWrite: &contentOverride, + }, + }) + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + _, err = pl.Do(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if verifier.isEnableContentResponseOnWriteHeaderSet { + t.Fatalf("expected content response header to not be set") + } +} + +func TestAddPartitionKeyHeader(t *testing.T) { + headerPolicy := &headerPolicies{} + srv, close := mock.NewTLSServer() + defer close() + srv.SetResponse(mock.WithStatusCode(http.StatusOK)) + + verifier := headerPoliciesVerify{} + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{headerPolicy, &verifier}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) + req, err := azruntime.NewRequest(context.Background(), http.MethodGet, srv.URL()) + + partitionKey := NewPartitionKeyString("some string") + req.SetOperationValue(pipelineRequestOptions{ + isWriteOperation: true, + headerOptionsOverride: &headerOptionsOverride{ + partitionKey: &partitionKey, + }, + }) + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + _, err = pl.Do(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if verifier.isPartitionKeyHeaderSet != "[\"some string\"]" { + t.Fatalf("expected pk header to be set") + } +} + +type headerPoliciesVerify struct { + isEnableContentResponseOnWriteHeaderSet bool + isPartitionKeyHeaderSet string +} + +func (p *headerPoliciesVerify) Do(req *policy.Request) (*http.Response, error) { + p.isEnableContentResponseOnWriteHeaderSet = req.Raw().Header.Get(cosmosHeaderPrefer) != "" + p.isPartitionKeyHeaderSet = req.Raw().Header.Get(cosmosHeaderPartitionKey) + + return req.Next() +} diff --git a/sdk/data/azcosmos/cosmos_item_request_options_test.go b/sdk/data/azcosmos/cosmos_item_request_options_test.go index 02625f799b68..d9d08da73c63 100644 --- a/sdk/data/azcosmos/cosmos_item_request_options_test.go +++ b/sdk/data/azcosmos/cosmos_item_request_options_test.go @@ -20,7 +20,7 @@ func TestItemRequestOptionsToHeaders(t *testing.T) { options.IfMatchEtag = &etagValue header := options.toHeaders() if header == nil { - t.Error("toHeaders should return non-nil") + t.Fatal("toHeaders should return non-nil") } headers := *header diff --git a/sdk/data/azcosmos/cosmos_item_response_test.go b/sdk/data/azcosmos/cosmos_item_response_test.go index 90c3c3f78351..4b6c22baad2a 100644 --- a/sdk/data/azcosmos/cosmos_item_response_test.go +++ b/sdk/data/azcosmos/cosmos_item_response_test.go @@ -9,6 +9,7 @@ import ( "net/http" "testing" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" ) @@ -37,7 +38,7 @@ func TestItemResponseParsing(t *testing.T) { t.Fatal(err) } - pl := azruntime.NewPipeline(srv) + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) resp, _ := pl.Do(req) parsedResponse, err := newItemResponse(resp) if err != nil { diff --git a/sdk/data/azcosmos/cosmos_offers.go b/sdk/data/azcosmos/cosmos_offers.go index e04e14453864..9b4d2b8238f6 100644 --- a/sdk/data/azcosmos/cosmos_offers.go +++ b/sdk/data/azcosmos/cosmos_offers.go @@ -12,7 +12,7 @@ import ( ) type cosmosOffers struct { - connection *cosmosClientConnection + client *Client } type cosmosOffersResponse struct { @@ -24,7 +24,7 @@ func (c cosmosOffers) ReadThroughputIfExists( targetRID string, requestOptions *ThroughputOptions) (ThroughputResponse, error) { // TODO: might want to replace with query iterator once that is in - operationContext := cosmosOperationContext{ + operationContext := pipelineRequestOptions{ resourceType: resourceTypeOffer, resourceAddress: "", } @@ -34,7 +34,7 @@ func (c cosmosOffers) ReadThroughputIfExists( return ThroughputResponse{}, err } - azResponse, err := c.connection.sendQueryRequest( + azResponse, err := c.client.sendQueryRequest( path, ctx, fmt.Sprintf(`SELECT * FROM c WHERE c.offerResourceId = '%s'`, targetRID), @@ -57,7 +57,7 @@ func (c cosmosOffers) ReadThroughputIfExists( } // Now read the individual offer - operationContext = cosmosOperationContext{ + operationContext = pipelineRequestOptions{ resourceType: resourceTypeOffer, resourceAddress: theOffers.Offers[0].offerId, isRidBased: true, @@ -68,7 +68,7 @@ func (c cosmosOffers) ReadThroughputIfExists( return ThroughputResponse{}, err } - azResponse, err = c.connection.sendGetRequest( + azResponse, err = c.client.sendGetRequest( path, ctx, operationContext, @@ -95,7 +95,7 @@ func (c cosmosOffers) ReplaceThroughputIfExists( readRequestCharge := readResponse.RequestCharge readResponse.ThroughputProperties.offer = properties.offer - operationContext := cosmosOperationContext{ + operationContext := pipelineRequestOptions{ resourceType: resourceTypeOffer, resourceAddress: readResponse.ThroughputProperties.offerId, isRidBased: true, @@ -106,7 +106,7 @@ func (c cosmosOffers) ReplaceThroughputIfExists( return ThroughputResponse{}, err } - azResponse, err := c.connection.sendPutRequest( + azResponse, err := c.client.sendPutRequest( path, ctx, readResponse.ThroughputProperties, diff --git a/sdk/data/azcosmos/cosmos_operation_context.go b/sdk/data/azcosmos/cosmos_operation_context.go deleted file mode 100644 index 49692a4b28ce..000000000000 --- a/sdk/data/azcosmos/cosmos_operation_context.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcosmos - -type cosmosOperationContext struct { - resourceType resourceType - resourceAddress string - isRidBased bool -} diff --git a/sdk/data/azcosmos/emulator_cosmos_container_test.go b/sdk/data/azcosmos/emulator_cosmos_container_test.go index 420938662384..dd7bc4a218fa 100644 --- a/sdk/data/azcosmos/emulator_cosmos_container_test.go +++ b/sdk/data/azcosmos/emulator_cosmos_container_test.go @@ -15,7 +15,7 @@ func TestContainerCRUD(t *testing.T) { database := emulatorTests.createDatabase(t, context.TODO(), client, "containerCRUD") defer emulatorTests.deleteDatabase(t, context.TODO(), database) properties := ContainerProperties{ - Id: "aContainer", + ID: "aContainer", PartitionKeyDefinition: PartitionKeyDefinition{ Paths: []string{"/id"}, }, @@ -33,12 +33,12 @@ func TestContainerCRUD(t *testing.T) { throughput := NewManualThroughputProperties(400) - resp, err := database.CreateContainer(context.TODO(), properties, &CreateContainerOptions{ThroughputProperties: throughput}) + resp, err := database.CreateContainer(context.TODO(), properties, &CreateContainerOptions{ThroughputProperties: &throughput}) if err != nil { t.Fatalf("Failed to create container: %v", err) } - if resp.ContainerProperties.Id != properties.Id { + if resp.ContainerProperties.ID != properties.ID { t.Errorf("Unexpected id match: %v", resp.ContainerProperties) } @@ -46,14 +46,14 @@ func TestContainerCRUD(t *testing.T) { t.Errorf("Unexpected path match: %v", resp.ContainerProperties) } - container := resp.ContainerProperties.Container + container, _ := database.NewContainer("aContainer") resp, err = container.Read(context.TODO(), nil) if err != nil { t.Fatalf("Failed to read container: %v", err) } updatedProperties := ContainerProperties{ - Id: "aContainer", + ID: "aContainer", PartitionKeyDefinition: PartitionKeyDefinition{ Paths: []string{"/id"}, }, @@ -85,7 +85,7 @@ func TestContainerCRUD(t *testing.T) { } newScale := NewManualThroughputProperties(500) - _, err = container.ReplaceThroughput(context.TODO(), *newScale, nil) + _, err = container.ReplaceThroughput(context.TODO(), newScale, nil) if err != nil { t.Errorf("Failed to read throughput: %v", err) } @@ -103,7 +103,7 @@ func TestContainerAutoscaleCRUD(t *testing.T) { database := emulatorTests.createDatabase(t, context.TODO(), client, "containerCRUD") defer emulatorTests.deleteDatabase(t, context.TODO(), database) properties := ContainerProperties{ - Id: "aContainer", + ID: "aContainer", PartitionKeyDefinition: PartitionKeyDefinition{ Paths: []string{"/id"}, }, @@ -121,12 +121,12 @@ func TestContainerAutoscaleCRUD(t *testing.T) { throughput := NewAutoscaleThroughputProperties(5000) - resp, err := database.CreateContainer(context.TODO(), properties, &CreateContainerOptions{ThroughputProperties: throughput}) + resp, err := database.CreateContainer(context.TODO(), properties, &CreateContainerOptions{ThroughputProperties: &throughput}) if err != nil { t.Fatalf("Failed to create container: %v", err) } - if resp.ContainerProperties.Id != properties.Id { + if resp.ContainerProperties.ID != properties.ID { t.Errorf("Unexpected id match: %v", resp.ContainerProperties) } @@ -134,7 +134,7 @@ func TestContainerAutoscaleCRUD(t *testing.T) { t.Errorf("Unexpected path match: %v", resp.ContainerProperties) } - container := resp.ContainerProperties.Container + container, _ := database.NewContainer("aContainer") resp, err = container.Read(context.TODO(), nil) if err != nil { t.Fatalf("Failed to read container: %v", err) @@ -155,7 +155,7 @@ func TestContainerAutoscaleCRUD(t *testing.T) { } newScale := NewAutoscaleThroughputProperties(10000) - _, err = container.ReplaceThroughput(context.TODO(), *newScale, nil) + _, err = container.ReplaceThroughput(context.TODO(), newScale, nil) if err != nil { t.Errorf("Failed to read throughput: %v", err) } diff --git a/sdk/data/azcosmos/emulator_cosmos_database_test.go b/sdk/data/azcosmos/emulator_cosmos_database_test.go index 3566b2e69b5a..83e760eaa604 100644 --- a/sdk/data/azcosmos/emulator_cosmos_database_test.go +++ b/sdk/data/azcosmos/emulator_cosmos_database_test.go @@ -12,32 +12,33 @@ func TestDatabaseCRUD(t *testing.T) { emulatorTests := newEmulatorTests(t) client := emulatorTests.getClient(t) - database := DatabaseProperties{Id: "baseDbTest"} + database := DatabaseProperties{ID: "baseDbTest"} resp, err := client.CreateDatabase(context.TODO(), database, nil) if err != nil { t.Fatalf("Failed to create database: %v", err) } - if resp.DatabaseProperties.Id != database.Id { + if resp.DatabaseProperties.ID != database.ID { t.Errorf("Unexpected id match: %v", resp.DatabaseProperties) } - resp, err = resp.DatabaseProperties.Database.Read(context.TODO(), nil) + db, _ := client.NewDatabase("baseDbTest") + resp, err = db.Read(context.TODO(), nil) if err != nil { t.Fatalf("Failed to read database: %v", err) } - if resp.DatabaseProperties.Id != database.Id { + if resp.DatabaseProperties.ID != database.ID { t.Errorf("Unexpected id match: %v", resp.DatabaseProperties) } - throughputResponse, err := resp.DatabaseProperties.Database.ReadThroughput(context.TODO(), nil) + throughputResponse, err := db.ReadThroughput(context.TODO(), nil) if err == nil { t.Fatalf("Expected not finding throughput but instead got : %v", throughputResponse) } - resp, err = resp.DatabaseProperties.Database.Delete(context.TODO(), nil) + resp, err = db.Delete(context.TODO(), nil) if err != nil { t.Fatalf("Failed to delete database: %v", err) } @@ -47,27 +48,28 @@ func TestDatabaseWithOfferCRUD(t *testing.T) { emulatorTests := newEmulatorTests(t) client := emulatorTests.getClient(t) - database := DatabaseProperties{Id: "baseDbTest"} + database := DatabaseProperties{ID: "baseDbTest"} tp := NewManualThroughputProperties(400) - resp, err := client.CreateDatabase(context.TODO(), database, &CreateDatabaseOptions{ThroughputProperties: tp}) + resp, err := client.CreateDatabase(context.TODO(), database, &CreateDatabaseOptions{ThroughputProperties: &tp}) if err != nil { t.Fatalf("Failed to create database: %v", err) } - if resp.DatabaseProperties.Id != database.Id { + if resp.DatabaseProperties.ID != database.ID { t.Errorf("Unexpected id match: %v", resp.DatabaseProperties) } - resp, err = resp.DatabaseProperties.Database.Read(context.TODO(), nil) + db, _ := client.NewDatabase("baseDbTest") + resp, err = db.Read(context.TODO(), nil) if err != nil { t.Fatalf("Failed to read database: %v", err) } - if resp.DatabaseProperties.Id != database.Id { + if resp.DatabaseProperties.ID != database.ID { t.Errorf("Unexpected id match: %v", resp.DatabaseProperties) } - throughputResponse, err := resp.DatabaseProperties.Database.ReadThroughput(context.TODO(), nil) + throughputResponse, err := db.ReadThroughput(context.TODO(), nil) if err != nil { t.Fatalf("Failed to read throughput: %v", err) } @@ -82,12 +84,12 @@ func TestDatabaseWithOfferCRUD(t *testing.T) { } newScale := NewManualThroughputProperties(500) - _, err = resp.DatabaseProperties.Database.ReplaceThroughput(context.TODO(), *newScale, nil) + _, err = db.ReplaceThroughput(context.TODO(), newScale, nil) if err != nil { t.Errorf("Failed to read throughput: %v", err) } - resp, err = resp.DatabaseProperties.Database.Delete(context.TODO(), nil) + resp, err = db.Delete(context.TODO(), nil) if err != nil { t.Fatalf("Failed to delete database: %v", err) } diff --git a/sdk/data/azcosmos/emulator_cosmos_item_test.go b/sdk/data/azcosmos/emulator_cosmos_item_test.go index c1569967ddec..b4d2e90c1f8d 100644 --- a/sdk/data/azcosmos/emulator_cosmos_item_test.go +++ b/sdk/data/azcosmos/emulator_cosmos_item_test.go @@ -16,13 +16,13 @@ func TestItemCRUD(t *testing.T) { database := emulatorTests.createDatabase(t, context.TODO(), client, "itemCRUD") defer emulatorTests.deleteDatabase(t, context.TODO(), database) properties := ContainerProperties{ - Id: "aContainer", + ID: "aContainer", PartitionKeyDefinition: PartitionKeyDefinition{ Paths: []string{"/id"}, }, } - resp, err := database.CreateContainer(context.TODO(), properties, nil) + _, err := database.CreateContainer(context.TODO(), properties, nil) if err != nil { t.Fatalf("Failed to create container: %v", err) } @@ -32,13 +32,15 @@ func TestItemCRUD(t *testing.T) { "value": "2", } - container := resp.ContainerProperties.Container - pk, err := NewPartitionKey("1") + container, _ := database.NewContainer("aContainer") + pk := NewPartitionKeyString("1") + + marshalled, err := json.Marshal(item) if err != nil { - t.Fatalf("Failed to create pk: %v", err) + t.Fatal(err) } - itemResponse, err := container.CreateItem(context.TODO(), *pk, item, nil) + itemResponse, err := container.CreateItem(context.TODO(), pk, marshalled, nil) if err != nil { t.Fatalf("Failed to create item: %v", err) } @@ -52,7 +54,7 @@ func TestItemCRUD(t *testing.T) { t.Fatalf("Expected empty response, got %v", itemResponse.Value) } - itemResponse, err = container.ReadItem(context.TODO(), *pk, "1", nil) + itemResponse, err = container.ReadItem(context.TODO(), pk, "1", nil) if err != nil { t.Fatalf("Failed to read item: %v", err) } @@ -74,7 +76,11 @@ func TestItemCRUD(t *testing.T) { } item["value"] = "3" - itemResponse, err = container.ReplaceItem(context.TODO(), *pk, "1", item, &ItemOptions{EnableContentResponseOnWrite: true}) + marshalled, err = json.Marshal(item) + if err != nil { + t.Fatal(err) + } + itemResponse, err = container.ReplaceItem(context.TODO(), pk, "1", marshalled, &ItemOptions{EnableContentResponseOnWrite: true}) if err != nil { t.Fatalf("Failed to replace item: %v", err) } @@ -96,7 +102,11 @@ func TestItemCRUD(t *testing.T) { } item["value"] = "4" - itemResponse, err = container.UpsertItem(context.TODO(), *pk, item, &ItemOptions{EnableContentResponseOnWrite: true}) + marshalled, err = json.Marshal(item) + if err != nil { + t.Fatal(err) + } + itemResponse, err = container.UpsertItem(context.TODO(), pk, marshalled, &ItemOptions{EnableContentResponseOnWrite: true}) if err != nil { t.Fatalf("Failed to upsert item: %v", err) } @@ -117,7 +127,7 @@ func TestItemCRUD(t *testing.T) { t.Fatalf("Expected value to be 4, got %v", itemResponseBody["value"]) } - itemResponse, err = container.DeleteItem(context.TODO(), *pk, "1", nil) + itemResponse, err = container.DeleteItem(context.TODO(), pk, "1", nil) if err != nil { t.Fatalf("Failed to replace item: %v", err) } diff --git a/sdk/data/azcosmos/emulator_tests.go b/sdk/data/azcosmos/emulator_tests.go index 97b825488a5f..1cdfabb2787f 100644 --- a/sdk/data/azcosmos/emulator_tests.go +++ b/sdk/data/azcosmos/emulator_tests.go @@ -40,24 +40,25 @@ func (e *emulatorTests) createDatabase( t *testing.T, ctx context.Context, client *Client, - dbName string) *Database { - database := DatabaseProperties{Id: dbName} + dbName string) DatabaseClient { + database := DatabaseProperties{ID: dbName} resp, err := client.CreateDatabase(ctx, database, nil) if err != nil { t.Fatalf("Failed to create database: %v", err) } - if resp.DatabaseProperties.Id != database.Id { + if resp.DatabaseProperties.ID != database.ID { t.Errorf("Unexpected id match: %v", resp.DatabaseProperties) } - return resp.DatabaseProperties.Database + db, _ := client.NewDatabase(dbName) + return db } func (e *emulatorTests) deleteDatabase( t *testing.T, ctx context.Context, - database *Database) { + database DatabaseClient) { _, err := database.Delete(ctx, nil) if err != nil { t.Fatalf("Failed to delete database: %v", err) diff --git a/sdk/data/azcosmos/example_test.go b/sdk/data/azcosmos/example_test.go index ebf066a89294..59387d4a8180 100644 --- a/sdk/data/azcosmos/example_test.go +++ b/sdk/data/azcosmos/example_test.go @@ -36,16 +36,18 @@ func Example() { // ===== 1. Creating a database ===== - databaseName := azcosmos.DatabaseProperties{Id: "databaseName"} - database, err := client.CreateDatabase(ctx, databaseName, nil) + databaseProperties := azcosmos.DatabaseProperties{ID: "databaseName"} + databaseResponse, err := client.CreateDatabase(ctx, databaseProperties, nil) if err != nil { log.Fatal(err) } + log.Printf("Database created. ActivityId %s", databaseResponse.ActivityId) + // ===== 2. Creating a container ===== properties := azcosmos.ContainerProperties{ - Id: "aContainer", + ID: "aContainer", PartitionKeyDefinition: azcosmos.PartitionKeyDefinition{ Paths: []string{"/myPartitionKey"}, }, @@ -53,12 +55,22 @@ func Example() { throughput := azcosmos.NewManualThroughputProperties(400) - resp, err := database.DatabaseProperties.Database.CreateContainer(ctx, properties, &azcosmos.CreateContainerOptions{ThroughputProperties: throughput}) + database, err := client.NewDatabase("databaseName") + if err != nil { + log.Fatal(err) + } + + resp, err := database.CreateContainer(ctx, properties, &azcosmos.CreateContainerOptions{ThroughputProperties: &throughput}) if err != nil { log.Fatal(err) } - container := resp.ContainerProperties.Container + log.Printf("Container created. ActivityId %s", resp.ActivityId) + + container, err := client.NewContainer("databaseName", "aContainer") + if err != nil { + log.Fatal(err) + } // ===== 3. Update container properties ===== @@ -68,11 +80,10 @@ func Example() { } updatedProperties := azcosmos.ContainerProperties{ - Id: "aContainer", + ID: "aContainer", PartitionKeyDefinition: azcosmos.PartitionKeyDefinition{ Paths: []string{"/myPartitionKey"}, }, - ETag: "someEtag", IndexingPolicy: &azcosmos.IndexingPolicy{ IncludedPaths: []azcosmos.IncludedPath{}, ExcludedPaths: []azcosmos.ExcludedPath{}, @@ -102,14 +113,14 @@ func Example() { // Replace manual throughput property newScale := azcosmos.NewManualThroughputProperties(500) - _, err = container.ReplaceThroughput(ctx, *newScale, nil) + _, err = container.ReplaceThroughput(ctx, newScale, nil) if err != nil { log.Fatal(err) } // Migrate from manual throughput to autoscale newScale = azcosmos.NewAutoscaleThroughputProperties(10000) - replaceThroughputResponse, err := container.ReplaceThroughput(ctx, *newScale, nil) + replaceThroughputResponse, err := container.ReplaceThroughput(ctx, newScale, nil) if err != nil { log.Fatal(err) } @@ -124,7 +135,7 @@ func Example() { // ===== 4. Item CRUD ===== // Items in an Azure Cosmos container are uniquely identified by their id and partition key value. - pk, err := azcosmos.NewPartitionKey("newPartitionKey") + pk := azcosmos.NewPartitionKeyString("newPartitionKey") item := map[string]string{ "id": "1", @@ -132,14 +143,19 @@ func Example() { "myPartitionKey": "newPartitionKey", } + marshalled, err := json.Marshal(item) + if err != nil { + log.Fatal(err) + } + // Create item. - itemResponse, err := container.CreateItem(ctx, *pk, item, nil) + itemResponse, err := container.CreateItem(ctx, pk, marshalled, nil) if err != nil { log.Fatal(err) } // Read item. - itemResponse, err = container.ReadItem(ctx, *pk, "1", nil) + itemResponse, err = container.ReadItem(ctx, pk, "1", nil) if err != nil { log.Fatal(err) } @@ -152,28 +168,32 @@ func Example() { // Modify some property itemResponseBody["value"] = "newValue" + marshalledReplace, err := json.Marshal(itemResponseBody) + if err != nil { + log.Fatal(err) + } // Replace item - itemResponse, err = container.ReplaceItem(ctx, *pk, "1", itemResponseBody, nil) + itemResponse, err = container.ReplaceItem(ctx, pk, "1", marshalledReplace, nil) if err != nil { log.Fatal(err) } // Delete item. - itemResponse, err = container.DeleteItem(ctx, *pk, "1", nil) + itemResponse, err = container.DeleteItem(ctx, pk, "1", nil) if err != nil { log.Fatal(err) } // ===== 5. Session consistency ===== - itemResponse, err = container.UpsertItem(ctx, *pk, item, nil) + itemResponse, err = container.UpsertItem(ctx, pk, marshalled, nil) if err != nil { log.Fatal(err) } itemSessionToken := itemResponse.SessionToken - itemResponse, err = container.ReadItem(ctx, *pk, "1", &azcosmos.ItemOptions{SessionToken: itemSessionToken}) + itemResponse, err = container.ReadItem(ctx, pk, "1", &azcosmos.ItemOptions{SessionToken: itemSessionToken}) if err != nil { log.Fatal(err) } @@ -184,7 +204,7 @@ func Example() { // Check the item response status code. If an error is imitted and the response code is 412 then retry operation. numberRetry := 3 err = retryOptimisticConcurrency(numberRetry, 1000*time.Millisecond, func() (bool, error) { - itemResponse, err = container.ReadItem(ctx, *pk, "1", nil) + itemResponse, err = container.ReadItem(ctx, pk, "1", nil) if err != nil { log.Fatal(err) } @@ -198,9 +218,14 @@ func Example() { // Change a value in the item response body. itemResponseBody["value"] = "newValue" + marshalledReplace, err := json.Marshal(itemResponseBody) + if err != nil { + log.Fatal(err) + } + // Replace with Etag etag := itemResponse.ETag - itemResponse, err = container.ReplaceItem(ctx, *pk, "1", itemResponseBody, &azcosmos.ItemOptions{IfMatchEtag: &etag}) + itemResponse, err = container.ReplaceItem(ctx, pk, "1", marshalledReplace, &azcosmos.ItemOptions{IfMatchEtag: &etag}) var httpErr azcore.HTTPResponse return (errors.As(err, &httpErr) && itemResponse.RawResponse.StatusCode == 412), err diff --git a/sdk/data/azcosmos/go.mod b/sdk/data/azcosmos/go.mod index e7b91b970732..1d01b67de5e0 100644 --- a/sdk/data/azcosmos/go.mod +++ b/sdk/data/azcosmos/go.mod @@ -4,7 +4,9 @@ go 1.16 require ( github.com/Azure/azure-sdk-for-go v57.3.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0 - github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v0.20.0 + github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.1 + github.com/davecgh/go-spew v1.1.1 // indirect github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/sdk/data/azcosmos/go.sum b/sdk/data/azcosmos/go.sum index df703095a287..6c817e79efe9 100644 --- a/sdk/data/azcosmos/go.sum +++ b/sdk/data/azcosmos/go.sum @@ -1,30 +1,39 @@ github.com/Azure/azure-sdk-for-go v57.3.0+incompatible h1:zxuxvsRYSXcowMuT/P5b7o6YJYuGYP74jCb9IvlgOLA= github.com/Azure/azure-sdk-for-go v57.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0 h1:lhSJz9RMbJcTgxifR1hUNJnn6CNYtbgEDtQV22/9RBA= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0 h1:v9p9TfTbf7AwNb5NYQt7hI41IfPoLFiFkLtb+bmGjT0= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.20.0 h1:KQgdWmEOmaJKxaUUZwHAYh12t+b+ZJf8q3friycK1kA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.20.0/go.mod h1:ZPW/Z0kLCTdDZaDbYTetxc9Cxl/2lNqxYHYNOF2bti0= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.1 h1:BUYIbDf/mMZ8945v3QkG3OuqGVyS4Iek0AOLwdRAYoc= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.1/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= diff --git a/sdk/data/azcosmos/partition_key.go b/sdk/data/azcosmos/partition_key.go index dfad7db1ea75..7199397e0b56 100644 --- a/sdk/data/azcosmos/partition_key.go +++ b/sdk/data/azcosmos/partition_key.go @@ -3,41 +3,51 @@ package azcosmos -import "encoding/json" +import ( + "encoding/json" + "fmt" +) // PartitionKey represents a logical partition key value. type PartitionKey struct { - isNone bool - partitionKeyInternal *partitionKeyInternal + values []interface{} } -// NewPartitionKeyNone creates a partition key value for non-partitioned containers. -func NewPartitionKeyNone() *PartitionKey { - return &PartitionKey{ - isNone: true, - partitionKeyInternal: nonePartitionKey, +func (p PartitionKey) MarshalJSON() ([]byte, error) { + // TODO: support multicomponent partition keys + switch val := p.values[0].(type) { + case nil: + return []byte("null"), nil + case bool, string, float64: + return json.Marshal(val) + default: + return nil, fmt.Errorf("PartitionKey can only be a string, bool, or a number: '%T'", p.values[0]) } } -// NewPartitionKey creates a new partition key. -// value - the partition key value. -func NewPartitionKey(value interface{}) (*PartitionKey, error) { - pkInternal, err := newPartitionKeyInternal([]interface{}{value}) - if err != nil { - return nil, err +func NewPartitionKeyString(value string) PartitionKey { + components := []interface{}{value} + return PartitionKey{ + values: components, } - return &PartitionKey{ - partitionKeyInternal: pkInternal, - isNone: false, - }, nil } -func (pk *PartitionKey) toJsonString() (string, error) { - if pk.isNone { - return "", nil +func NewPartitionKeyBool(value bool) PartitionKey { + components := []interface{}{value} + return PartitionKey{ + values: components, } +} - res, err := json.Marshal(pk.partitionKeyInternal) +func NewPartitionKeyNumber(value float64) PartitionKey { + components := []interface{}{value} + return PartitionKey{ + values: components, + } +} + +func (pk *PartitionKey) toJsonString() (string, error) { + res, err := json.Marshal(pk.values) if err != nil { return "", err } diff --git a/sdk/data/azcosmos/partition_key_definition.go b/sdk/data/azcosmos/partition_key_definition.go index 65268d81dab4..f77d3bdff8c0 100644 --- a/sdk/data/azcosmos/partition_key_definition.go +++ b/sdk/data/azcosmos/partition_key_definition.go @@ -9,5 +9,5 @@ type PartitionKeyDefinition struct { // Paths returns the list of partition key paths of the container. Paths []string `json:"paths"` // Version returns the version of the hash partitioning of the container. - Version PartitionKeyDefinitionVersion `json:"version,omitempty"` + Version int `json:"version,omitempty"` } diff --git a/sdk/data/azcosmos/partition_key_definition_version.go b/sdk/data/azcosmos/partition_key_definition_version.go deleted file mode 100644 index 2e5db610ec2b..000000000000 --- a/sdk/data/azcosmos/partition_key_definition_version.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcosmos - -// Version of the hash partitioning -type PartitionKeyDefinitionVersion int - -const ( - // Original version of hash partitioning. - PartitionKeyDefinitionVersion1 PartitionKeyDefinitionVersion = 1 - // Enhanced version of hash partitioning - offers better distribution of long partition keys and uses less storage. - PartitionKeyDefinitionVersion2 PartitionKeyDefinitionVersion = 2 -) - -// Returns a list of available consistency levels -func PartitionKeyDefinitionVersionValues() []PartitionKeyDefinitionVersion { - return []PartitionKeyDefinitionVersion{PartitionKeyDefinitionVersion1, PartitionKeyDefinitionVersion2} -} - -func (c PartitionKeyDefinitionVersion) ToPtr() *PartitionKeyDefinitionVersion { - return &c -} diff --git a/sdk/data/azcosmos/partition_key_internal.go b/sdk/data/azcosmos/partition_key_internal.go deleted file mode 100644 index fc548614538c..000000000000 --- a/sdk/data/azcosmos/partition_key_internal.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcosmos - -import ( - "encoding/json" - "fmt" -) - -var ( - nonePartitionKey = &partitionKeyInternal{components: nil} - emptyPartitionKey = &partitionKeyInternal{components: []interface{}{}} - undefinedPartitionKey = &partitionKeyInternal{components: []interface{}{&partitionKeyUndefinedComponent{}}} -) - -type partitionKeyInternal struct { - components []interface{} -} - -func newPartitionKeyInternal(values []interface{}) (*partitionKeyInternal, error) { - components := make([]interface{}, len(values)) - for i, v := range values { - var component interface{} - switch val := v.(type) { - case nil: - component = &partitionKeyNullComponent{} - case bool: - component = &partitionKeyBoolComponent{val} - case string: - component = &partitionKeyStringComponent{val} - case int: - component = &partitionKeyNumberComponent{float64(val)} - case int8: - component = &partitionKeyNumberComponent{float64(val)} - case int16: - component = &partitionKeyNumberComponent{float64(val)} - case int32: - component = &partitionKeyNumberComponent{float64(val)} - case int64: - component = &partitionKeyNumberComponent{float64(val)} - case uint: - component = &partitionKeyNumberComponent{float64(val)} - case uint8: - component = &partitionKeyNumberComponent{float64(val)} - case uint16: - component = &partitionKeyNumberComponent{float64(val)} - case uint32: - component = &partitionKeyNumberComponent{float64(val)} - case uint64: - component = &partitionKeyNumberComponent{float64(val)} - case float32: - component = &partitionKeyNumberComponent{float64(val)} - case float64: - component = &partitionKeyNumberComponent{val} - default: - return nil, fmt.Errorf("PartitionKey can only be a string, bool, or a number: '%T'", v) - } - - components[i] = component - } - - return &partitionKeyInternal{components: components}, nil -} - -func (p *partitionKeyInternal) MarshalJSON() ([]byte, error) { - return json.Marshal(p.components) -} - -type partitionKeyNumberComponent struct { - value float64 -} - -func (p *partitionKeyNumberComponent) MarshalJSON() ([]byte, error) { - return json.Marshal(p.value) -} - -type partitionKeyBoolComponent struct { - value bool -} - -func (p *partitionKeyBoolComponent) MarshalJSON() ([]byte, error) { - return json.Marshal(p.value) -} - -type partitionKeyStringComponent struct { - value string -} - -func (p *partitionKeyStringComponent) MarshalJSON() ([]byte, error) { - return json.Marshal(p.value) -} - -type partitionKeyNullComponent struct{} - -func (p *partitionKeyNullComponent) MarshalJSON() ([]byte, error) { - return []byte("null"), nil -} - -type partitionKeyUndefinedComponent struct{} - -func (p *partitionKeyUndefinedComponent) MarshalJSON() ([]byte, error) { - return []byte("{}"), nil -} diff --git a/sdk/data/azcosmos/partition_key_test.go b/sdk/data/azcosmos/partition_key_test.go index d9b1545d222d..44096f5444c5 100644 --- a/sdk/data/azcosmos/partition_key_test.go +++ b/sdk/data/azcosmos/partition_key_test.go @@ -8,57 +8,23 @@ import ( "testing" ) -func TestInvalidPartitionKeyValues(t *testing.T) { - invalidTypes := []interface{}{ - complex64(0), - complex128(0), - []byte(nil), - []byte{}, - // whatever other type of struct - cosmosOperationContext{}, +func TestSerialization(t *testing.T) { + validTypes := map[string]PartitionKey{ + "[10.5]": NewPartitionKeyNumber(float64(10.5)), + "[10]": NewPartitionKeyNumber(float64(10)), + "[\"some string\"]": NewPartitionKeyString("some string"), + "[true]": NewPartitionKeyBool(true), + "[false]": NewPartitionKeyBool(false), } - for _, invalidType := range invalidTypes { - _, err := NewPartitionKey(invalidType) - if err == nil { - t.Errorf("Expected error for partition key type %v", invalidType) - } - } -} - -func TestValidPartitionKeyValues(t *testing.T) { - validTypes := map[interface{}]string{ - nil: "[null]", - true: "[true]", - false: "[false]", - "some string": "[\"some string\"]", - int(10): "[10]", - int8(10): "[10]", - int16(10): "[10]", - int32(10): "[10]", - int64(10): "[10]", - uint(10): "[10]", - uint8(10): "[10]", - uint16(10): "[10]", - uint32(10): "[10]", - uint64(10): "[10]", - float32(10.5): "[10.5]", - float64(10.5): "[10.5]", - } - - for validType, expectedSerialization := range validTypes { - pk, err := NewPartitionKey(validType) - if err != nil { - t.Errorf("Expected success for partition key type %v and got %v", validType, err) - } - - if len(pk.partitionKeyInternal.components) != 1 { - t.Errorf("Expected partition key to have 1 component, but it has %v", len(pk.partitionKeyInternal.components)) + for expectedSerialization, pk := range validTypes { + if len(pk.values) != 1 { + t.Errorf("Expected partition key to have 1 component, but it has %v", len(pk.values)) } serialization, err := pk.toJsonString() if err != nil { - t.Errorf("Failed to serialize PK for %v, got %v", validType, err) + t.Errorf("Failed to serialize PK for %v, got %v", pk, err) } if serialization != expectedSerialization { @@ -67,65 +33,39 @@ func TestValidPartitionKeyValues(t *testing.T) { } } -func TestPartitionKeyEmpty(t *testing.T) { - pk := &PartitionKey{ - partitionKeyInternal: emptyPartitionKey, - } +func TestPartitionKeyEquality(t *testing.T) { + pk := NewPartitionKeyNumber(float64(10.5)) + pk2 := NewPartitionKeyNumber(float64(10.5)) - serialization, err := pk.toJsonString() - if err != nil { - t.Errorf("Failed to serialize PK, %v", err) + if !reflect.DeepEqual(pk, pk2) { + t.Errorf("Expected %v to equal %v", pk, pk2) } - if serialization != "[]" { - t.Errorf("Expected serialization [], but got %v", serialization) - } -} + pk = NewPartitionKeyNumber(float64(50)) + pk2 = NewPartitionKeyNumber(float64(50)) -func TestPartitionKeyUndefined(t *testing.T) { - pk := &PartitionKey{ - partitionKeyInternal: undefinedPartitionKey, + if !reflect.DeepEqual(pk, pk2) { + t.Errorf("Expected %v to equal %v", pk, pk2) } - serialization, err := pk.toJsonString() - if err != nil { - t.Errorf("Failed to serialize PK, %v", err) - } + pk = NewPartitionKeyBool(true) + pk2 = NewPartitionKeyBool(true) - if serialization != "[{}]" { - t.Errorf("Expected serialization [{}], but got %v", serialization) + if !reflect.DeepEqual(pk, pk2) { + t.Errorf("Expected %v to equal %v", pk, pk2) } -} -func TestPartitionKeyEquality(t *testing.T) { - validTypes := []interface{}{ - nil, - true, - false, - "some string", - int(10), - int8(10), - int16(10), - int32(10), - int64(10), - uint(10), - uint8(10), - uint16(10), - uint32(10), - uint64(10), - float32(10.5), - float64(10.5), + pk = NewPartitionKeyBool(false) + pk2 = NewPartitionKeyBool(false) + + if !reflect.DeepEqual(pk, pk2) { + t.Errorf("Expected %v to equal %v", pk, pk2) } - for _, validType := range validTypes { - pk, err := NewPartitionKey(validType) - if err != nil { - t.Errorf("Expected success for partition key type %v and got %v", validType, err) - } + pk = NewPartitionKeyString("some string") + pk2 = NewPartitionKeyString("some string") - pk2, _ := NewPartitionKey(validType) - if !reflect.DeepEqual(pk, pk2) { - t.Errorf("Expected %v to equal %v", pk, pk2) - } + if !reflect.DeepEqual(pk, pk2) { + t.Errorf("Expected %v to equal %v", pk, pk2) } } diff --git a/sdk/data/azcosmos/resource_throttle_retry_policy.go b/sdk/data/azcosmos/resource_throttle_retry_policy.go deleted file mode 100644 index c27566857c0f..000000000000 --- a/sdk/data/azcosmos/resource_throttle_retry_policy.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcosmos - -import ( - "net/http" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" - "github.com/Azure/azure-sdk-for-go/sdk/internal/log" -) - -const ( - defaultResourceThrottleRetryPolicyMaxWaitTime time.Duration = 60 * time.Second - defaultResourceThrottleRetryPolicyRetryCount int = 9 -) - -// resourceThrottleRetryPolicy retries on HTTP 429. -type resourceThrottleRetryPolicy struct { - MaxWaitTime time.Duration - MaxRetryCount int -} - -func newResourceThrottleRetryPolicy(o *CosmosClientOptions) *resourceThrottleRetryPolicy { - if o.RateLimitedRetry == nil { - return &resourceThrottleRetryPolicy{ - MaxWaitTime: defaultResourceThrottleRetryPolicyMaxWaitTime, - MaxRetryCount: defaultResourceThrottleRetryPolicyRetryCount} - } - - return &resourceThrottleRetryPolicy{ - MaxWaitTime: o.RateLimitedRetry.MaxRetryWaitTime, - MaxRetryCount: o.RateLimitedRetry.MaxRetryAttempts} -} - -func (p *resourceThrottleRetryPolicy) Do(req *policy.Request) (*http.Response, error) { - // Policy disabled - if p.MaxRetryCount == 0 { - return req.Next() - } - - var resp *http.Response - var err error - var cumulativeWaitTime time.Duration - for attempts := 0; attempts < p.MaxRetryCount; attempts++ { - err = req.RewindBody() - if err != nil { - return resp, err - } - - resp, err = req.Next() - if err != nil || resp.StatusCode != http.StatusTooManyRequests { - return resp, err - } - - retryAfter := resp.Header.Get(cosmosHeaderRetryAfter) - retryAfterDuration := parseRetryAfter(retryAfter) - cumulativeWaitTime += retryAfterDuration - - if retryAfterDuration > p.MaxWaitTime || cumulativeWaitTime > p.MaxWaitTime { - return resp, err - } - - // drain before retrying so nothing is leaked - azruntime.Drain(resp) - - select { - case <-time.After(retryAfterDuration): - // retry - case <-req.Raw().Context().Done(): - err = req.Raw().Context().Err() - log.Writef(log.RetryPolicy, "ResourceThrottleRetryPolicy abort due to %v", err) - return resp, err - } - } - - return resp, err -} - -func parseRetryAfter(retryAfter string) time.Duration { - if retryAfter == "" { - return 0 - } - - retryAfterDuration, err := time.ParseDuration(retryAfter + "ms") - if err != nil { - return 0 - } - - return retryAfterDuration -} diff --git a/sdk/data/azcosmos/resource_throttle_retry_policy_test.go b/sdk/data/azcosmos/resource_throttle_retry_policy_test.go deleted file mode 100644 index b796046ccc54..000000000000 --- a/sdk/data/azcosmos/resource_throttle_retry_policy_test.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcosmos - -import ( - "context" - "net/http" - "testing" - "time" - - azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" - "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" -) - -func TestDefaultRetryConfiguration(t *testing.T) { - cosmosClientOptions := &CosmosClientOptions{} - retryPolicy := newResourceThrottleRetryPolicy(cosmosClientOptions) - if retryPolicy.MaxRetryCount != defaultResourceThrottleRetryPolicyRetryCount { - t.Errorf("Expected the MaxRetryCount to match the default but got, but got %d", retryPolicy.MaxRetryCount) - } - - if retryPolicy.MaxWaitTime != defaultResourceThrottleRetryPolicyMaxWaitTime { - t.Errorf("Expected the MaxWaitTime to match the default but got, but got %s", retryPolicy.MaxWaitTime) - } -} - -func TestCustomRetryConfiguration(t *testing.T) { - cosmosClientOptions := &CosmosClientOptions{} - maxRetryCount := 5 - maxRetryDuration, _ := time.ParseDuration("1s") - cosmosClientOptions.RateLimitedRetry = &CosmosClientOptionsRateLimitedRetry{ - MaxRetryAttempts: maxRetryCount, - MaxRetryWaitTime: maxRetryDuration, - } - retryPolicy := newResourceThrottleRetryPolicy(cosmosClientOptions) - if retryPolicy.MaxRetryCount != maxRetryCount { - t.Errorf("Expected the MaxRetryCount to match the %d but got, but got %d", maxRetryCount, retryPolicy.MaxRetryCount) - } - - if retryPolicy.MaxWaitTime != maxRetryDuration { - t.Errorf("Expected the MaxWaitTime to match the %s but got, but got %s", maxRetryDuration, retryPolicy.MaxWaitTime) - } -} - -func TestRetryOn429WithCustomCount(t *testing.T) { - cosmosClientOptions := &CosmosClientOptions{} - maxRetryCount := 5 - maxRetryDuration, _ := time.ParseDuration("5s") - cosmosClientOptions.RateLimitedRetry = &CosmosClientOptionsRateLimitedRetry{ - MaxRetryAttempts: maxRetryCount, - MaxRetryWaitTime: maxRetryDuration, - } - retryPolicy := newResourceThrottleRetryPolicy(cosmosClientOptions) - - srv, close := mock.NewTLSServer() - defer close() - srv.SetResponse(mock.WithStatusCode(http.StatusTooManyRequests)) - - pl := azruntime.NewPipeline(srv, retryPolicy) - req, err := azruntime.NewRequest(context.Background(), http.MethodGet, srv.URL()) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - resp, _ := pl.Do(req) - if resp.StatusCode != http.StatusTooManyRequests { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - if r := srv.Requests(); r != maxRetryCount { - t.Fatalf("wrong request count, got %d expected %d", r, maxRetryCount) - } -} - -func TestRetryOn429WithCustomTime(t *testing.T) { - cosmosClientOptions := &CosmosClientOptions{} - maxRetryCount := 5 - maxRetryDuration, _ := time.ParseDuration("1s") - cosmosClientOptions.RateLimitedRetry = &CosmosClientOptionsRateLimitedRetry{ - MaxRetryAttempts: maxRetryCount, - MaxRetryWaitTime: maxRetryDuration, - } - retryPolicy := newResourceThrottleRetryPolicy(cosmosClientOptions) - - srv, close := mock.NewTLSServer() - defer close() - // Should wait only 1 second and when the retry comes, it should stop - srv.SetResponse(mock.WithStatusCode(http.StatusTooManyRequests), mock.WithHeader(cosmosHeaderRetryAfter, "1000")) - - pl := azruntime.NewPipeline(srv, retryPolicy) - req, err := azruntime.NewRequest(context.Background(), http.MethodGet, srv.URL()) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - resp, _ := pl.Do(req) - if resp.StatusCode != http.StatusTooManyRequests { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - if r := srv.Requests(); r != 2 { - t.Fatalf("wrong request count, got %d expected %d", r, 2) - } -} diff --git a/sdk/data/azcosmos/shared_key_credential.go b/sdk/data/azcosmos/shared_key_credential.go index d17f29b03530..9e10c96b6afd 100644 --- a/sdk/data/azcosmos/shared_key_credential.go +++ b/sdk/data/azcosmos/shared_key_credential.go @@ -20,12 +20,12 @@ import ( // KeyCredential creates an immutable KeyCredential containing the // account's primary or secondary key. -func NewKeyCredential(accountKey string) (*KeyCredential, error) { +func NewKeyCredential(accountKey string) (KeyCredential, error) { c := KeyCredential{} if err := c.Update(accountKey); err != nil { - return nil, err + return c, err } - return &c, nil + return c, nil } // KeyCredential contains an account's name and its primary or secondary key. @@ -53,7 +53,7 @@ func (c *KeyCredential) computeHMACSHA256(s string) (base64String string) { } func (c *KeyCredential) buildCanonicalizedAuthHeaderFromRequest(req *policy.Request) (string, error) { - var opValues cosmosOperationContext + var opValues pipelineRequestOptions value := "" if req.OperationValue(&opValues) { @@ -88,10 +88,10 @@ func (c *KeyCredential) buildCanonicalizedAuthHeader(method, resourceType, resou } type sharedKeyCredPolicy struct { - cred *KeyCredential + cred KeyCredential } -func newSharedKeyCredPolicy(cred *KeyCredential) *sharedKeyCredPolicy { +func newSharedKeyCredPolicy(cred KeyCredential) *sharedKeyCredPolicy { s := &sharedKeyCredPolicy{ cred: cred, } @@ -117,7 +117,7 @@ func (s *sharedKeyCredPolicy) Do(req *policy.Request) (*http.Response, error) { response, err := req.Next() if err != nil && response != nil && response.StatusCode == http.StatusForbidden { // Service failed to authenticate request, log it - log.Write(log.Response, "===== HTTP Forbidden status, Authorization:\n"+authHeader+"\n=====\n") + log.Write(log.EventResponse, "===== HTTP Forbidden status, Authorization:\n"+authHeader+"\n=====\n") } return response, err } diff --git a/sdk/data/azcosmos/shared_key_credential_test.go b/sdk/data/azcosmos/shared_key_credential_test.go index 55e50639eee5..bacb67c993d1 100644 --- a/sdk/data/azcosmos/shared_key_credential_test.go +++ b/sdk/data/azcosmos/shared_key_credential_test.go @@ -63,7 +63,7 @@ func Test_buildCanonicalizedAuthHeaderFromRequest(t *testing.T) { expected := url.QueryEscape(fmt.Sprintf("type=%s&ver=%s&sig=%s", tokenType, version, signature)) req, _ := azruntime.NewRequest(context.TODO(), http.MethodGet, "http://localhost") - operationContext := cosmosOperationContext{ + operationContext := pipelineRequestOptions{ resourceType: resourceTypeDatabase, resourceAddress: "dbs/testdb", } @@ -95,7 +95,7 @@ func Test_buildCanonicalizedAuthHeaderFromRequestWithRid(t *testing.T) { expected := url.QueryEscape(fmt.Sprintf("type=%s&ver=%s&sig=%s", tokenType, version, signature)) req, _ := azruntime.NewRequest(context.TODO(), http.MethodGet, "http://localhost") - operationContext := cosmosOperationContext{ + operationContext := pipelineRequestOptions{ resourceType: resourceTypeDatabase, resourceAddress: "dbs/Rid", isRidBased: true, diff --git a/sdk/data/azcosmos/throughput_properties.go b/sdk/data/azcosmos/throughput_properties.go index 6009c1520284..c718edb4a5a8 100644 --- a/sdk/data/azcosmos/throughput_properties.go +++ b/sdk/data/azcosmos/throughput_properties.go @@ -19,8 +19,8 @@ const ( // ThroughputProperties describes the throughput configuration of a resource. type ThroughputProperties struct { - ETag azcore.ETag - LastModified *UnixTime + ETag *azcore.ETag + LastModified int64 version string offerType string @@ -32,8 +32,8 @@ type ThroughputProperties struct { // NewManualThroughputProperties returns a ThroughputProperties object with the given throughput in manual mode. // throughput - the throughput in RU/s -func NewManualThroughputProperties(throughput int) *ThroughputProperties { - return &ThroughputProperties{ +func NewManualThroughputProperties(throughput int32) ThroughputProperties { + return ThroughputProperties{ version: offerVersion2, offer: newManualOffer(throughput), } @@ -42,8 +42,8 @@ func NewManualThroughputProperties(throughput int) *ThroughputProperties { // NewAutoscaleThroughputPropertiesWithIncrement returns a ThroughputProperties object with the given max throughput on autoscale mode. // maxThroughput - the max throughput in RU/s // incrementPercentage - the auto upgrade max throughput increment percentage -func NewAutoscaleThroughputPropertiesWithIncrement(startingMaxThroughput int, incrementPercentage int) *ThroughputProperties { - return &ThroughputProperties{ +func NewAutoscaleThroughputPropertiesWithIncrement(startingMaxThroughput int32, incrementPercentage int32) ThroughputProperties { + return ThroughputProperties{ version: offerVersion2, offer: newAutoscaleOfferWithIncrement(startingMaxThroughput, incrementPercentage), } @@ -51,8 +51,8 @@ func NewAutoscaleThroughputPropertiesWithIncrement(startingMaxThroughput int, in // NewAutoscaleThroughputProperties returns a ThroughputProperties object with the given max throughput on autoscale mode. // maxThroughput - the max throughput in RU/s -func NewAutoscaleThroughputProperties(startingMaxThroughput int) *ThroughputProperties { - return &ThroughputProperties{ +func NewAutoscaleThroughputProperties(startingMaxThroughput int32) ThroughputProperties { + return ThroughputProperties{ version: offerVersion2, offer: newAutoscaleOffer(startingMaxThroughput), } @@ -79,7 +79,7 @@ func (tp *ThroughputProperties) MarshalJSON() ([]byte, error) { buffer.WriteString(fmt.Sprintf(",\"offerType\":\"%s\"", tp.offerType)) buffer.WriteString(fmt.Sprintf(",\"offerVersion\":\"%s\"", tp.version)) - if tp.ETag != "" { + if tp.ETag != nil { buffer.WriteString(",\"_etag\":") etag, err := json.Marshal(tp.ETag) if err != nil { @@ -92,7 +92,7 @@ func (tp *ThroughputProperties) MarshalJSON() ([]byte, error) { buffer.WriteString(fmt.Sprintf(",\"_self\":\"%s\"", tp.selfLink)) } - if tp.LastModified != nil { + if tp.LastModified > 0 { buffer.WriteString(",\"_ts\":") ts, err := json.Marshal(tp.LastModified) if err != nil { @@ -164,7 +164,7 @@ func (tp *ThroughputProperties) UnmarshalJSON(b []byte) error { } // ManualThroughput returns the provisioned throughput in manual mode. -func (tp *ThroughputProperties) ManualThroughput() (int, error) { +func (tp *ThroughputProperties) ManualThroughput() (int32, error) { if tp.offer.Throughput == nil { return 0, fmt.Errorf("offer is not a manual offer") } @@ -173,7 +173,7 @@ func (tp *ThroughputProperties) ManualThroughput() (int, error) { } // AutoscaleMaxThroughput returns the configured max throughput on autoscale mode. -func (tp *ThroughputProperties) AutoscaleMaxThroughput() (int, error) { +func (tp *ThroughputProperties) AutoscaleMaxThroughput() (int32, error) { if tp.offer.AutoScale == nil { return 0, fmt.Errorf("offer is not an autoscale offer") } @@ -187,24 +187,24 @@ func (tp *ThroughputProperties) addHeadersToRequest(req *policy.Request) { } if tp.offer.Throughput != nil { - req.Raw().Header.Add(cosmosHeaderOfferThroughput, strconv.Itoa(*tp.offer.Throughput)) + req.Raw().Header.Add(cosmosHeaderOfferThroughput, strconv.Itoa(int(*tp.offer.Throughput))) } else { req.Raw().Header.Add(cosmosHeaderOfferAutoscale, tp.offer.AutoScale.ToJsonString()) } } type offer struct { - Throughput *int `json:"offerThroughput,omitempty"` + Throughput *int32 `json:"offerThroughput,omitempty"` AutoScale *autoscaleSettings `json:"offerAutopilotSettings,omitempty"` } -func newManualOffer(throughput int) *offer { +func newManualOffer(throughput int32) *offer { return &offer{ Throughput: &throughput, } } -func newAutoscaleOfferWithIncrement(startingMaxThroughput int, incrementPercentage int) *offer { +func newAutoscaleOfferWithIncrement(startingMaxThroughput int32, incrementPercentage int32) *offer { return &offer{ AutoScale: &autoscaleSettings{ MaxThroughput: startingMaxThroughput, @@ -217,7 +217,7 @@ func newAutoscaleOfferWithIncrement(startingMaxThroughput int, incrementPercenta } } -func newAutoscaleOffer(startingMaxThroughput int) *offer { +func newAutoscaleOffer(startingMaxThroughput int32) *offer { return &offer{ AutoScale: &autoscaleSettings{ MaxThroughput: startingMaxThroughput, @@ -226,7 +226,7 @@ func newAutoscaleOffer(startingMaxThroughput int) *offer { } type autoscaleSettings struct { - MaxThroughput int `json:"maxThroughput,omitempty"` + MaxThroughput int32 `json:"maxThroughput,omitempty"` AutoscaleAutoUpgradeProperties *autoscaleAutoUpgradeProperties `json:"autoUpgradePolicy,omitempty"` } @@ -245,5 +245,5 @@ type autoscaleAutoUpgradeProperties struct { } type autoscaleThroughputPolicy struct { - IncrementPercent int `json:"incrementPercent,omitempty"` + IncrementPercent int32 `json:"incrementPercent,omitempty"` } diff --git a/sdk/data/azcosmos/throughput_properties_test.go b/sdk/data/azcosmos/throughput_properties_test.go index d16faecaf6e2..58b7dde8dec1 100644 --- a/sdk/data/azcosmos/throughput_properties_test.go +++ b/sdk/data/azcosmos/throughput_properties_test.go @@ -7,10 +7,12 @@ import ( "encoding/json" "testing" "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" ) func TestThroughputPropertiesManualRawSerialization(t *testing.T) { - nowAsUnix := time.Unix(1630100602, 0) + nowAsUnix := int64(1630100602) jsonString := []byte("{\"offerType\":\"Invalid\",\"offerResourceId\":\"4SRTANCD3Dw=\",\"offerVersion\":\"V2\",\"content\":{\"offerThroughput\":400},\"id\":\"HFln\",\"_etag\":\"\\\"00000000-0000-0000-9b8c-8ea3e19601d7\\\"\",\"_ts\":1630100602}") @@ -36,12 +38,12 @@ func TestThroughputPropertiesManualRawSerialization(t *testing.T) { t.Errorf("OfferId mismatch %v", otherProperties.offerId) } - if otherProperties.ETag != "\"00000000-0000-0000-9b8c-8ea3e19601d7\"" { + if *otherProperties.ETag != "\"00000000-0000-0000-9b8c-8ea3e19601d7\"" { t.Errorf("Etag mismatch %v", otherProperties.ETag) } - if otherProperties.LastModified.Time != nowAsUnix { - t.Errorf("Timestamp mismatch %v", otherProperties.LastModified.Time) + if otherProperties.LastModified != nowAsUnix { + t.Errorf("Timestamp mismatch %v", otherProperties.LastModified) } mt, err := otherProperties.ManualThroughput() @@ -57,16 +59,13 @@ func TestThroughputPropertiesManualRawSerialization(t *testing.T) { func TestThroughputPropertiesManualE2ESerialization(t *testing.T) { nowAsUnix := time.Now().Unix() - now := UnixTime{ - Time: time.Unix(nowAsUnix, 0), - } - + etag := azcore.ETag("\"00000000-0000-0000-9b8c-8ea3e19601d7\"") properties := NewManualThroughputProperties(400) properties.offerId = "HFln" properties.offerResourceId = "4SRTANCD3Dw=" - properties.ETag = "\"00000000-0000-0000-9b8c-8ea3e19601d7\"" - properties.LastModified = &now - jsonString, err := json.Marshal(properties) + properties.ETag = &etag + properties.LastModified = nowAsUnix + jsonString, err := json.Marshal(&properties) if err != nil { t.Fatal(err) } @@ -93,12 +92,12 @@ func TestThroughputPropertiesManualE2ESerialization(t *testing.T) { t.Errorf("OfferId mismatch %v", otherProperties.offerId) } - if otherProperties.ETag != "\"00000000-0000-0000-9b8c-8ea3e19601d7\"" { + if *otherProperties.ETag != etag { t.Errorf("Etag mismatch %v", otherProperties.ETag) } - if otherProperties.LastModified.Time != properties.LastModified.Time { - t.Errorf("Timestamp mismatch %v", otherProperties.LastModified.Time) + if otherProperties.LastModified != properties.LastModified { + t.Errorf("Timestamp mismatch %v", otherProperties.LastModified) } mt, err := otherProperties.ManualThroughput() @@ -114,16 +113,13 @@ func TestThroughputPropertiesManualE2ESerialization(t *testing.T) { func TestThroughputPropertiesAutoscaleE2ESerialization(t *testing.T) { nowAsUnix := time.Now().Unix() - now := UnixTime{ - Time: time.Unix(nowAsUnix, 0), - } - + etag := azcore.ETag("\"00000000-0000-0000-9b8c-8ea3e19601d7\"") properties := NewAutoscaleThroughputProperties(400) properties.offerId = "HFln" properties.offerResourceId = "4SRTANCD3Dw=" - properties.ETag = "\"00000000-0000-0000-9b8c-8ea3e19601d7\"" - properties.LastModified = &now - jsonString, err := json.Marshal(properties) + properties.ETag = &etag + properties.LastModified = nowAsUnix + jsonString, err := json.Marshal(&properties) if err != nil { t.Fatal(err) } @@ -150,12 +146,12 @@ func TestThroughputPropertiesAutoscaleE2ESerialization(t *testing.T) { t.Errorf("OfferId mismatch %v", otherProperties.offerId) } - if otherProperties.ETag != "\"00000000-0000-0000-9b8c-8ea3e19601d7\"" { + if *otherProperties.ETag != etag { t.Errorf("Etag mismatch %v", otherProperties.ETag) } - if otherProperties.LastModified.Time != properties.LastModified.Time { - t.Errorf("Timestamp mismatch %v", otherProperties.LastModified.Time) + if otherProperties.LastModified != properties.LastModified { + t.Errorf("Timestamp mismatch %v", otherProperties.LastModified) } at, err := otherProperties.AutoscaleMaxThroughput() @@ -175,16 +171,13 @@ func TestThroughputPropertiesAutoscaleE2ESerialization(t *testing.T) { func TestThroughputPropertiesAutoscaleIncrementE2ESerialization(t *testing.T) { nowAsUnix := time.Now().Unix() - now := UnixTime{ - Time: time.Unix(nowAsUnix, 0), - } - + etag := azcore.ETag("\"00000000-0000-0000-9b8c-8ea3e19601d7\"") properties := NewAutoscaleThroughputPropertiesWithIncrement(400, 10) properties.offerId = "HFln" properties.offerResourceId = "4SRTANCD3Dw=" - properties.ETag = "\"00000000-0000-0000-9b8c-8ea3e19601d7\"" - properties.LastModified = &now - jsonString, err := json.Marshal(properties) + properties.ETag = &etag + properties.LastModified = nowAsUnix + jsonString, err := json.Marshal(&properties) if err != nil { t.Fatal(err) } @@ -211,12 +204,12 @@ func TestThroughputPropertiesAutoscaleIncrementE2ESerialization(t *testing.T) { t.Errorf("OfferId mismatch %v", otherProperties.offerId) } - if otherProperties.ETag != "\"00000000-0000-0000-9b8c-8ea3e19601d7\"" { + if *otherProperties.ETag != etag { t.Errorf("Etag mismatch %v", otherProperties.ETag) } - if otherProperties.LastModified.Time != properties.LastModified.Time { - t.Errorf("Timestamp mismatch %v", otherProperties.LastModified.Time) + if otherProperties.LastModified != properties.LastModified { + t.Errorf("Timestamp mismatch %v", otherProperties.LastModified) } at, err := otherProperties.AutoscaleMaxThroughput() diff --git a/sdk/data/azcosmos/throughput_request_options_test.go b/sdk/data/azcosmos/throughput_request_options_test.go index d6bc919f1123..30c413447a2c 100644 --- a/sdk/data/azcosmos/throughput_request_options_test.go +++ b/sdk/data/azcosmos/throughput_request_options_test.go @@ -21,8 +21,8 @@ func TestThroughputRequestOptionsToHeaders(t *testing.T) { options.IfNoneMatchEtag = &noneetag header := options.toHeaders() - if *header == nil { - t.Error("toHeaders should return non-nil") + if header == nil { + t.Fatal("toHeaders should return non-nil") } headers := *header diff --git a/sdk/data/azcosmos/throughput_response_test.go b/sdk/data/azcosmos/throughput_response_test.go index e3f0cade795a..c894967b3162 100644 --- a/sdk/data/azcosmos/throughput_response_test.go +++ b/sdk/data/azcosmos/throughput_response_test.go @@ -9,16 +9,21 @@ import ( "net/http" "testing" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" ) func TestThroughputResponseParsing(t *testing.T) { properties := NewManualThroughputProperties(400) + + etag := azcore.ETag("\"00000000-0000-0000-9b8c-8ea3e19601d7\"") + properties.offerId = "HFln" properties.offerResourceId = "4SRTANCD3Dw=" - properties.ETag = "\"00000000-0000-0000-9b8c-8ea3e19601d7\"" - jsonString, err := json.Marshal(properties) + properties.ETag = &etag + jsonString, err := json.Marshal(&properties) if err != nil { t.Fatal(err) } @@ -35,8 +40,7 @@ func TestThroughputResponseParsing(t *testing.T) { if err != nil { t.Fatal(err) } - - pl := azruntime.NewPipeline(srv) + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) resp, _ := pl.Do(req) parsedResponse, err := newThroughputResponse(resp, nil) if err != nil { @@ -59,8 +63,8 @@ func TestThroughputResponseParsing(t *testing.T) { t.Fatalf("parsedResponse.ThroughputProperties.offerResourceId is %s, expected %s", parsedResponse.ThroughputProperties.offerResourceId, properties.offerResourceId) } - if parsedResponse.ThroughputProperties.ETag != properties.ETag { - t.Fatalf("parsedResponse.ThroughputProperties.ETag is %s, expected %s", parsedResponse.ThroughputProperties.ETag, properties.ETag) + if *parsedResponse.ThroughputProperties.ETag != *properties.ETag { + t.Fatalf("parsedResponse.ThroughputProperties.ETag is %s, expected %s", *parsedResponse.ThroughputProperties.ETag, *properties.ETag) } if parsedResponse.ActivityId != "someActivityId" { @@ -79,11 +83,12 @@ func TestThroughputResponseParsing(t *testing.T) { func TestThroughputResponseParsingWithPreviousRU(t *testing.T) { var queryRequestCharge float32 = 10.0 + etag := azcore.ETag("\"00000000-0000-0000-9b8c-8ea3e19601d7\"") properties := NewManualThroughputProperties(400) properties.offerId = "HFln" properties.offerResourceId = "4SRTANCD3Dw=" - properties.ETag = "\"00000000-0000-0000-9b8c-8ea3e19601d7\"" - jsonString, err := json.Marshal(properties) + properties.ETag = &etag + jsonString, err := json.Marshal(&properties) if err != nil { t.Fatal(err) } @@ -101,7 +106,7 @@ func TestThroughputResponseParsingWithPreviousRU(t *testing.T) { t.Fatal(err) } - pl := azruntime.NewPipeline(srv) + pl := azruntime.NewPipeline("azcosmostest", "v1.0.0", []policy.Policy{}, []policy.Policy{}, &policy.ClientOptions{Transport: srv}) resp, _ := pl.Do(req) parsedResponse, err := newThroughputResponse(resp, &queryRequestCharge) if err != nil { @@ -124,8 +129,8 @@ func TestThroughputResponseParsingWithPreviousRU(t *testing.T) { t.Fatalf("parsedResponse.ThroughputProperties.offerResourceId is %s, expected %s", parsedResponse.ThroughputProperties.offerResourceId, properties.offerResourceId) } - if parsedResponse.ThroughputProperties.ETag != properties.ETag { - t.Fatalf("parsedResponse.ThroughputProperties.ETag is %s, expected %s", parsedResponse.ThroughputProperties.ETag, properties.ETag) + if *parsedResponse.ThroughputProperties.ETag != *properties.ETag { + t.Fatalf("parsedResponse.ThroughputProperties.ETag is %s, expected %s", *parsedResponse.ThroughputProperties.ETag, *properties.ETag) } if parsedResponse.ActivityId != "someActivityId" { diff --git a/sdk/data/azcosmos/unixtime.go b/sdk/data/azcosmos/unixtime.go deleted file mode 100644 index d0c9a000185e..000000000000 --- a/sdk/data/azcosmos/unixtime.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcosmos - -import ( - "encoding/json" - "strconv" - "time" -) - -type UnixTime struct { - time.Time -} - -func (u *UnixTime) UnmarshalJSON(b []byte) error { - var timestamp int64 - err := json.Unmarshal(b, ×tamp) - if err != nil { - return err - } - u.Time = time.Unix(timestamp, 0) - return nil -} - -func (u *UnixTime) MarshalJSON() ([]byte, error) { - return []byte(strconv.FormatInt(u.Time.Unix(), 10)), nil -}