diff --git a/CHANGELOG.md b/CHANGELOG.md index de4d420f..5ebcae38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## Unreleased + +- Added support for creating and updating documents containing + non-ASCII characters by adding Encoding parameter to `New-CosmosDbDocument` + and `Set-CosmosDbDocument` functions - fixes [Issue #151](https://github.com/PlagueHO/CosmosDB/issues/151). +- Fix table of contents link in README.MD. + ## 2.1.9.92 - Improved unit test reliability on MacOS and Linux. diff --git a/README.md b/README.md index c94de233..fa35f276 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ - [Create a Context specifying the Key Manually](#create-a-context-specifying-the-key-manually) - [Use CosmosDB Module to Retrieve Key from Azure Management Portal](#use-cosmosdb-module-to-retrieve-key-from-azure-management-portal) - [Create a Context for a Cosmos DB Emulator](#create-a-context-for-a-cosmos-db-emulator) - - [Create a Context from Resource Authorization Tokens](#create-a-Context-from-resource-authorization-tokens) + - [Create a Context from Resource Authorization Tokens](#create-a-context-from-resource-authorization-tokens) - [Working with Accounts](#working-with-accounts) - [Working with Databases](#working-with-databases) - [Working with Offers](#working-with-offers) @@ -357,6 +357,20 @@ New-CosmosDbDocument -Context $cosmosDbContext -CollectionId 'MyNewCollection' - } ``` +Create a new document containing non-ASCII characters in a collection in the +database: + +```powershell +$document = @" +{ + `"id`": `"$([Guid]::NewGuid().ToString())`", + `"content`": `"杉本 司`" +} +"@ +New-CosmosDbDocument -Context $cosmosDbContext -CollectionId 'MyNewCollection' -DocumentBody $document -Encoding 'UTF-8' +} +``` + Get the first 5 documents from the collection in the database: ```powershell diff --git a/docs/Invoke-CosmosDbRequest.md b/docs/Invoke-CosmosDbRequest.md index 720aad43..a3939da2 100644 --- a/docs/Invoke-CosmosDbRequest.md +++ b/docs/Invoke-CosmosDbRequest.md @@ -18,7 +18,7 @@ Execute a new request to a Cosmos DB REST endpoint. ```powershell Invoke-CosmosDbRequest -Context [-Database ] [-Key ] [-KeyType ] [-Method ] -ResourceType [-ResourcePath ] [-Body ] [-ApiVersion ] - [-Headers ] [-ContentType ] [] + [-Headers ] [-ContentType ] [-Encoding ] [] ``` ### Account @@ -26,7 +26,7 @@ Invoke-CosmosDbRequest -Context [-Database ] [-Key [-Database ] [-Key ] [-KeyType ] [-Method ] -ResourceType [-ResourcePath ] [-Body ] [-ApiVersion ] - [-Headers ] [-ContentType ] [] + [-Headers ] [-ContentType ] [-Encoding ] [] ``` ## DESCRIPTION @@ -260,6 +260,24 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -Encoding + +This parameter allows the Encoding to be set in the ContentType of the +request to allow other encoding formats. Currently only UTF-8 is supported. +If this parameter is not specified the default encoding is used. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. diff --git a/docs/New-CosmosDbDocument.md b/docs/New-CosmosDbDocument.md index b2d0311f..1e934774 100644 --- a/docs/New-CosmosDbDocument.md +++ b/docs/New-CosmosDbDocument.md @@ -18,7 +18,7 @@ Create a new document for a collection in a Cosmos DB database. ```powershell New-CosmosDbDocument -Context [-KeyType ] [-Key ] [-Database ] -CollectionId -DocumentBody [-IndexingDirective ] [-Upsert ] - [-PartitionKey ] [] + [-PartitionKey ] [-Encoding ] [] ``` ### Account @@ -26,7 +26,7 @@ New-CosmosDbDocument -Context [-KeyType ] [-Key ```powershell New-CosmosDbDocument -Account [-KeyType ] [-Key ] [-Database ] -CollectionId -DocumentBody [-IndexingDirective ] [-Upsert ] - [-PartitionKey ] [] + [-PartitionKey ] [-Encoding ] [] ``` ## DESCRIPTION @@ -65,6 +65,21 @@ PS C:\> New-CosmosDbDocument -Context $cosmosDbContext -CollectionId 'Partitione Create a new document in the 'en-us' in a partitioned collection in a database. +### Example 3 + +```powershell +PS C:\> $document = @" +{ + `"id`": `"en-us`", + `"content`": `"杉本 司`" +} +"@ +PS C:\> New-CosmosDbDocument -Context $cosmosDbContext -CollectionId 'PartitionedCollection' -DocumentBody $document -Encoding 'UTF-8' +``` + +Create a new document containing UTF-8 encoded strings in a non-partitioned collection +in a database. + ## PARAMETERS ### -Context @@ -240,6 +255,24 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -Encoding + +This parameter allows the Encoding to be set to UTF-8 for documents that contain +non-ASCII characters. If this parameter is not specified the default encoding is +used. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. diff --git a/docs/Set-CosmosDbDocument.md b/docs/Set-CosmosDbDocument.md index 7683d723..530a2e0b 100644 --- a/docs/Set-CosmosDbDocument.md +++ b/docs/Set-CosmosDbDocument.md @@ -18,7 +18,7 @@ Update a document from a Cosmos DB collection. ```powershell Set-CosmosDbDocument -Context [-Database ] [-Key ] -CollectionId -Id -DocumentBody [-IndexingDirective ] [-PartitionKey ] - [] + [-Encoding ] [] ``` ### Account @@ -26,7 +26,7 @@ Set-CosmosDbDocument -Context [-Database ] [-Key [-Database ] [-Key ] [-KeyType ] -CollectionId -Id -DocumentBody [-IndexingDirective ] - [-PartitionKey ] [] + [-PartitionKey ] [-Encoding ] [] ``` ## DESCRIPTION @@ -50,6 +50,21 @@ PS C:\> Set-CosmosDbDocument -Context $cosmosDbContext -CollectionId 'MyNewColle Replace the content of a document in a collection in the database. +### Example 2 + +```powershell +PS C:\> $newDocument = @" +{ + `"id`": `"ac12345`", + `"content`": `"杉本 司`" +} +"@ +PS C:\> Set-CosmosDbDocument -Context $cosmosDbContext -CollectionId 'MyNewCollection' -Id 'ac12345' -DocumentBody $newDocument -Encoding 'UTF-8' +``` + +Replace the content of a document in a collection in the database with a +document using UTF-8 encoding. + ## PARAMETERS ### -Context @@ -70,6 +85,7 @@ Accept wildcard characters: False ``` ### -Account + The account name of the Cosmos DB to access. ```yaml @@ -85,6 +101,7 @@ Accept wildcard characters: False ``` ### -Database + The name of the database to access in the Cosmos DB account. ```yaml @@ -100,6 +117,7 @@ Accept wildcard characters: False ``` ### -Key + The key to be used to access this Cosmos DB. ```yaml @@ -115,6 +133,7 @@ Accept wildcard characters: False ``` ### -KeyType + The type of key that will be used to access ths Cosmos DB. ```yaml @@ -130,6 +149,7 @@ Accept wildcard characters: False ``` ### -CollectionId + This is the Id of the collection to update the document for. ```yaml @@ -145,6 +165,7 @@ Accept wildcard characters: False ``` ### -Id + This is the Id of the document to update. ```yaml @@ -160,6 +181,7 @@ Accept wildcard characters: False ``` ### -DocumentBody + This is the body of the document to update. It must be formatted as a JSON string and contain the Id value of the @@ -178,6 +200,7 @@ Accept wildcard characters: False ``` ### -IndexingDirective + Include includes the document in the indexing path while Exclude omits the document from indexing. @@ -194,6 +217,7 @@ Accept wildcard characters: False ``` ### -PartitionKey + The partition key value for the document to be deleted. Required if and must be specified only if the collection is created with a partitionKey definition. @@ -210,6 +234,24 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -Encoding + +This parameter allows the Encoding to be set to UTF-8 for documents that contain +non-ASCII characters. If this parameter is not specified the default encoding is +used. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). diff --git a/src/lib/documents.ps1 b/src/lib/documents.ps1 index 7d4bed4a..700994be 100644 --- a/src/lib/documents.ps1 +++ b/src/lib/documents.ps1 @@ -247,8 +247,10 @@ function Get-CosmosDbDocument } } - # Because the headers of this request will contain important information - # then we need to use a plain web request. + <# + Because the headers of this request will contain important information + then we need to use a plain web request. + #> $result = Invoke-CosmosDbRequest @PSBoundParameters ` -Method $method ` -ResourceType 'docs' ` @@ -326,7 +328,12 @@ function New-CosmosDbDocument [Parameter()] [ValidateNotNullOrEmpty()] [System.String[]] - $PartitionKey + $PartitionKey, + + [Parameter()] + [ValidateSet('Default', 'UTF-8')] + [System.String] + $Encoding = 'Default' ) $null = $PSBoundParameters.Remove('CollectionId') @@ -500,7 +507,12 @@ function Set-CosmosDbDocument [Parameter()] [ValidateNotNullOrEmpty()] [System.String] - $PartitionKey + $PartitionKey, + + [Parameter()] + [ValidateSet('Default', 'UTF-8')] + [System.String] + $Encoding = 'Default' ) $null = $PSBoundParameters.Remove('CollectionId') diff --git a/src/lib/utils.ps1 b/src/lib/utils.ps1 index e6c1ff06..afee27e5 100644 --- a/src/lib/utils.ps1 +++ b/src/lib/utils.ps1 @@ -370,7 +370,12 @@ function Invoke-CosmosDbRequest [Parameter()] [System.String] - $ContentType = 'application/json' + $ContentType = 'application/json', + + [Parameter()] + [ValidateSet('Default', 'UTF-8')] + [System.String] + $Encoding = 'Default' ) if ($PSCmdlet.ParameterSetName -eq 'Account') @@ -532,27 +537,46 @@ function Invoke-CosmosDbRequest } $invokeWebRequestParameters = @{ - Uri = $uri - Headers = $Headers - Method = $method - ContentType = $ContentType + Uri = $uri + Headers = $Headers + Method = $method + ContentType = $ContentType + UseBasicParsing = $true } if ($Method -in ('Put', 'Post', 'Patch')) { if ($Method -eq 'Patch') { - $invokeWebRequestParameters['contentType'] = 'application/json-patch+json' + $invokeWebRequestParameters['ContentType'] = 'application/json-patch+json' } - $invokeWebRequestParameters += @{ - Body = $Body + if ($Encoding -eq 'UTF-8') + { + <# + An encoding type of UTF-8 was passed so explictly set this in the + request and convert to the body string to UTF8 bytes. + #> + $invokeWebRequestParameters['ContentType'] = ('{0}; charset={1}' -f $invokeWebRequestParameters['ContentType'], $Encoding) + $invokeWebRequestParameters += @{ + Body = [System.Text.Encoding]::UTF8.GetBytes($Body) + } + } + else + { + $invokeWebRequestParameters += @{ + Body = $Body + } } } $requestComplete = $false $retry = 0 - # this should initially be set to $false and $true when fatal error is caught otherwise retry is not working + + <# + This should initially be set to $false and changed to $true when fatal error + is caught + #> $fatal = $false do @@ -560,7 +584,7 @@ function Invoke-CosmosDbRequest try { - $requestResult = Invoke-WebRequest -UseBasicParsing @invokeWebRequestParameters + $requestResult = Invoke-WebRequest @invokeWebRequestParameters $requestComplete = $true } catch [System.Net.WebException],[Microsoft.PowerShell.Commands.HttpResponseException] diff --git a/test/Integration/CosmosDB.integration.Tests.ps1 b/test/Integration/CosmosDB.integration.Tests.ps1 index e0ba0cf3..ec87067d 100644 --- a/test/Integration/CosmosDB.integration.Tests.ps1 +++ b/test/Integration/CosmosDB.integration.Tests.ps1 @@ -47,6 +47,21 @@ $script:testDocumentBody = @" `"more`": `"Some other string`" } "@ +$script:testDocumentUTF8Id = [Guid]::NewGuid().ToString() +$script:testDocumentUTF8Content = "我能吞下玻璃而不伤身" +$script:testDocumentUTF8Body = @" +{ + `"id`": `"$script:testDocumentUTF8Id`", + `"content`": `"$script:testDocumentUTF8Content`" +} +"@ +$script:testDocumentUTF8UpdateContent = "我能吞下玻璃而不伤身" +$script:testDocumentUTF8UpdateBody = @" +{ + `"id`": `"$script:testDocumentUTF8Id`", + `"content`": `"$script:testDocumentUTF8UpdateContent`" +} +"@ $script:testAttachmentId = 'testAttachment' $script:testAttachmentContentType = 'image/jpg' $script:testAttachmentMedia = 'www.bing.com' @@ -948,6 +963,95 @@ Describe 'Cosmos DB Module' -Tag 'Integration' { } } + Context 'When adding a UTF-8 document to a collection' { + It 'Should not throw an exception' { + $script:result = New-CosmosDbDocument ` + -Context $script:testContext ` + -CollectionId $script:testCollection ` + -DocumentBody $script:testDocumentUTF8Body ` + -Encoding 'UTF-8' ` + -Verbose + } + + It 'Should return expected object' { + $script:result.Timestamp | Should -BeOfType [System.DateTime] + $script:result.Etag | Should -BeOfType [System.String] + $script:result.ResourceId | Should -BeOfType [System.String] + $script:result.Uri | Should -BeOfType [System.String] + $script:result.Attachments | Should -BeOfType [System.String] + $script:result.Id | Should -Be $script:testDocumentUTF8Id + } + } + + Context 'When getting newly created UTF-8 document from a collection by using the Id' { + It 'Should not throw an exception' { + $script:result = Get-CosmosDbDocument ` + -Context $script:testContext ` + -CollectionId $script:testCollection ` + -Id $script:testDocumentUTF8Id ` + -Verbose + } + + It 'Should return expected object' { + $script:result.Timestamp | Should -BeOfType [System.DateTime] + $script:result.Etag | Should -BeOfType [System.String] + $script:result.ResourceId | Should -BeOfType [System.String] + $script:result.Uri | Should -BeOfType [System.String] + $script:result.Attachments | Should -BeOfType [System.String] + $script:result.Id | Should -Be $script:testDocumentUTF8Id + } + } + + Context 'When updating an existing UTF-8 document to a collection' { + It 'Should not throw an exception' { + $script:result = Set-CosmosDbDocument ` + -Context $script:testContext ` + -CollectionId $script:testCollection ` + -Id $script:testDocumentUTF8Id ` + -DocumentBody $script:testDocumentUTF8UpdateBody ` + -Encoding 'UTF-8' ` + -Verbose + } + + It 'Should return expected object' { + $script:result.Timestamp | Should -BeOfType [System.DateTime] + $script:result.Etag | Should -BeOfType [System.String] + $script:result.ResourceId | Should -BeOfType [System.String] + $script:result.Uri | Should -BeOfType [System.String] + $script:result.Attachments | Should -BeOfType [System.String] + $script:result.Id | Should -Be $script:testDocumentUTF8Id + } + } + + Context 'When getting updated UTF-8 document from a collection by using the Id' { + It 'Should not throw an exception' { + $script:result = Get-CosmosDbDocument ` + -Context $script:testContext ` + -CollectionId $script:testCollection ` + -Id $script:testDocumentUTF8Id ` + -Verbose + } + + It 'Should return expected object' { + $script:result.Timestamp | Should -BeOfType [System.DateTime] + $script:result.Etag | Should -BeOfType [System.String] + $script:result.ResourceId | Should -BeOfType [System.String] + $script:result.Uri | Should -BeOfType [System.String] + $script:result.Attachments | Should -BeOfType [System.String] + $script:result.Id | Should -Be $script:testDocumentUTF8Id + } + } + + Context 'When removing a existing UTF-8 document from a collection' { + It 'Should not throw an exception' { + $script:result = Remove-CosmosDbDocument ` + -Context $script:testContext ` + -CollectionId $script:testCollection ` + -Id $script:testDocumentUTF8Id ` + -Verbose + } + } + Context 'When removing existing collection' { It 'Should not throw an exception' { $script:result = Remove-CosmosDbCollection -Context $script:testContext -Id $script:testCollection -Verbose diff --git a/test/Unit/CosmosDB.documents.Tests.ps1 b/test/Unit/CosmosDB.documents.Tests.ps1 index a9244522..49b6a7af 100644 --- a/test/Unit/CosmosDB.documents.Tests.ps1 +++ b/test/Unit/CosmosDB.documents.Tests.ps1 @@ -289,6 +289,41 @@ InModuleScope CosmosDB { } } + Context 'When called with context parameter and an Id and Encoding is UTF-8' { + $script:result = $null + $invokeCosmosDbRequest_parameterfilter = { + $Method -eq 'Post' -and ` + $ResourceType -eq 'docs' -and ` + $Encoding -eq 'UTF-8' + } + + Mock ` + -CommandName Invoke-CosmosDbRequest ` + -MockWith { $script:testGetDocumentResultSingle } + + It 'Should not throw exception' { + $newCosmosDbDocumentParameters = @{ + Context = $script:testContext + CollectionId = $script:testCollection + DocumentBody = $script:testDocumentBody + Encoding = 'UTF-8' + } + + { $script:result = New-CosmosDbDocument @newCosmosDbDocumentParameters } | Should -Not -Throw + } + + It 'Should return expected result' { + $script:result.id | Should -Be $script:testDocument1 + } + + It 'Should call expected mocks' { + Assert-MockCalled ` + -CommandName Invoke-CosmosDbRequest ` + -ParameterFilter $invokeCosmosDbRequest_parameterfilter ` + -Exactly -Times 1 + } + } + Context 'When called with context parameter and an Id and Partition Key' { $script:result = $null $invokeCosmosDbRequest_parameterfilter = { @@ -465,5 +500,41 @@ InModuleScope CosmosDB { -Exactly -Times 1 } } + + Context 'When called with context parameter and an Id and Encoding is UTF-8' { + $script:result = $null + $invokeCosmosDbRequest_parameterfilter = { + $Method -eq 'Put' -and ` + $ResourceType -eq 'docs' -and ` + $Encoding -eq 'UTF-8' + } + + Mock ` + -CommandName Invoke-CosmosDbRequest ` + -MockWith { $script:testGetDocumentResultSingle } + + It 'Should not throw exception' { + $setCosmosDbDocumentParameters = @{ + Context = $script:testContext + CollectionId = $script:testCollection + Id = $script:testDocument1 + DocumentBody = $script:testDocumentBody + Encoding = 'UTF-8' + } + + { $script:result = Set-CosmosDbDocument @setCosmosDbDocumentParameters } | Should -Not -Throw + } + + It 'Should return expected result' { + $script:result.id | Should -Be $script:testDocument1 + } + + It 'Should call expected mocks' { + Assert-MockCalled ` + -CommandName Invoke-CosmosDbRequest ` + -ParameterFilter $invokeCosmosDbRequest_parameterfilter ` + -Exactly -Times 1 + } + } } } diff --git a/test/Unit/CosmosDB.utils.Tests.ps1 b/test/Unit/CosmosDB.utils.Tests.ps1 index a2fa1015..c5c61621 100644 --- a/test/Unit/CosmosDB.utils.Tests.ps1 +++ b/test/Unit/CosmosDB.utils.Tests.ps1 @@ -761,8 +761,8 @@ console.log("done"); Context 'When called with Get method and ResourceType is ''colls'' and a resource token context without matching token and no master key' { $InvokeWebRequest_parameterfilter = { $Method -eq 'Get' -and ` - $ContentType -eq 'application/json' -and ` - $Uri -eq ('{0}dbs/{1}/colls/{2}' -f $script:testContext.BaseUri, $script:testContext.Database, 'anotherCollection') + $ContentType -eq 'application/json' -and ` + $Uri -eq ('{0}dbs/{1}/colls/{2}' -f $script:testContext.BaseUri, $script:testContext.Database, 'anotherCollection') } Mock ` @@ -799,8 +799,8 @@ console.log("done"); Context 'When called with context parameter and Post method' { $InvokeWebRequest_parameterfilter = { $Method -eq 'Post' -and ` - $ContentType -eq 'application/query+json' -and ` - $Uri -eq ('{0}dbs/{1}/{2}' -f $script:testContext.BaseUri, $script:testContext.Database, 'users') + $ContentType -eq 'application/query+json' -and ` + $Uri -eq ('{0}dbs/{1}/{2}' -f $script:testContext.BaseUri, $script:testContext.Database, 'users') } Mock ` @@ -835,6 +835,47 @@ console.log("done"); } } + Context 'When called with context parameter and Post method and Encoding set to UTF-8' { + $InvokeWebRequest_parameterfilter = { + $Method -eq 'Post' -and ` + $ContentType -eq 'application/json; charset=UTF-8' -and ` + $Uri -eq ('{0}dbs/{1}/colls/{2}/docs' -f $script:testContext.BaseUri, $script:testContext.Database, 'anotherCollection') + } + + Mock ` + -CommandName Invoke-WebRequest ` + -MockWith $InvokeWebRequest_mockwith + + $script:result = $null + + It 'Should not throw exception' { + $invokeCosmosDbRequestparameters = @{ + Context = $script:testContext + Method = 'Post' + ResourceType = 'docs' + ResourcePath = ('colls/{0}/docs' -f 'anotherCollection') + ContentType = 'application/json' + Body = "{ `"id`": `"daniel`", `"content`": `"我能吞下玻璃而不伤身`" }" + Encoding = 'UTF-8' + Verbose = $true + } + + { $script:result = (Invoke-CosmosDbRequest @invokeCosmosDbRequestparameters).Content | ConvertFrom-Json } | Should -Not -Throw + } + + It 'Should return expected result' { + $script:result._count | Should -Be 1 + } + + It 'Should call expected mocks' { + Assert-MockCalled ` + -CommandName Invoke-WebRequest ` + -ParameterFilter $InvokeWebRequest_parameterfilter ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-Date -Exactly -Times 1 + } + } + Context 'When called with context parameter and Get method but System.Net.WebException exception is thrown' { $InvokeWebRequest_parameterfilter = { $Method -eq 'Get' -and `