Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix find blob by tag #1622

Open
wants to merge 6 commits into
base: legacy
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdk/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ rust-version = "1.74.0"

[dependencies]
async-trait = "0.1"
base64 = "0.21"
base64 = "0.22"
bytes = "1.0"
dyn-clone = "1.0"
futures = "0.3"
Expand Down
121 changes: 105 additions & 16 deletions sdk/storage_blobs/src/service/operations/find_blobs_by_tags.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
use crate::prelude::BlobServiceClient;
use azure_core::{prelude::*, Response as HttpResponse};
use azure_storage::headers::CommonStorageResponseHeaders;
use url::Url;

operation! {
#[stream]
FindBlobsByTags,
client: BlobServiceClient,
expression: String,
?next_marker: NextMarker,
?max_results: MaxResults
?marker: NextMarker,
?max_results: MaxResults,
}

impl FindBlobsByTagsBuilder {
pub fn into_stream(self) -> FindBlobsByTags {
let make_request = move |next_marker: Option<NextMarker>| {
let make_request = move |continuation: Option<NextMarker>| {
let this = self.clone();
let mut ctx = self.context.clone();
async move {
let mut url = this.client.url()?;

url.query_pairs_mut().append_pair("comp", "blobs");
if let Some(next_marker) = next_marker {
if let Some(next_marker) = continuation.or(this.marker) {
next_marker.append_to_url_query(&mut url);
}
url.query_pairs_mut().append_pair("where", &this.expression);
this.max_results.append_to_url_query(&mut url);

make_url_compatible_with_api(&mut url);

let mut request = BlobServiceClient::finalize_request(
url,
azure_core::Method::Get,
Expand All @@ -40,13 +45,20 @@ impl FindBlobsByTagsBuilder {
}
}

/// ` AND ` in the spec can be converted to `+AND+`, however, azure rest api won't accept it unless we do a `%20AND%20`
fn make_url_compatible_with_api(url: &mut Url) {
let compatible_query = url.query().map(|q| q.replace("+", "%20"));

url.set_query(compatible_query.as_deref());
}
Comment on lines +49 to +53
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General note, requiring %20 instead of + as the proper encoding for a space character is service-wide. Any URL encoding done by the library for storage requests needs to do this. Probably beyond the scope of this PR, but good future reference.


pub type FindBlobsByTags = azure_core::Pageable<FindBlobsByTagsResponse, azure_core::error::Error>;

#[derive(Debug, Clone)]
pub struct FindBlobsByTagsResponse {
pub blobs: Vec<Blob>,
pub delimiter: Option<String>,
next_marker: Option<NextMarker>,
pub next_marker: Option<NextMarker>,
pub r#where: Option<String>,
pub common: CommonStorageResponseHeaders,
}
Expand Down Expand Up @@ -93,32 +105,109 @@ struct Blobs {
pub struct Blob {
pub name: String,
pub container_name: String,
pub tag_value: String,
pub tags: Tags,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Tags {
pub tag_set: TagSet,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct TagSet {
#[serde(rename = "Tag", default)]
pub tags: Vec<Tag>,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Tag {
pub key: String,
pub value: String,
}

#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::ClientBuilder;
use azure_core::xml::read_xml;
use azure_storage::StorageCredentials;
use futures::StreamExt;
use std::str::FromStr;

#[test]
fn parse_body() -> azure_core::Result<()> {
const BODY: &[u8] = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>
<EnumerationResults ServiceEndpoint=\"https://hsdgeventstoredev.blob.core.windows.net/\">
const BODY: &[u8] = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>
<EnumerationResults ServiceEndpoint=\"http://myaccount.blob.core.windows.net/\">
<Where>tag1='value1'</Where>
<Blobs>
<Blob>
<Name>test1</Name>
<ContainerName>container1</ContainerName>
<TagValue>value1</TagValue>
</Blob>
</Blobs>
<NextMarker/>
<Blobs>
<Blob>
<Name>test1</Name>
<ContainerName>container-name</ContainerName>
<Tags>
<TagSet>
<Tag>
<Key>matching-tag-name1</Key>
<Value>matching-tag-value1</Value>
</Tag>
<Tag>
<Key>matching-tag-name2</Key>
<Value>matching-tag-value2</Value>
</Tag>
</TagSet>
</Tags>
</Blob>
</Blobs>
<NextMarker />
</EnumerationResults>";

let body: ListBlobsByTagsBody = read_xml(BODY)?;

assert_eq!(body.blobs.blobs.len(), 1);
assert_eq!(body.blobs.blobs[0].name, "test1");
assert_eq!(
body.blobs.blobs[0].tags.tag_set.tags[0].key,
"matching-tag-name1"
);
assert_eq!(
body.blobs.blobs[0].tags.tag_set.tags[0].value,
"matching-tag-value1"
);
Ok(())
}

#[tokio::test]
async fn test_multi_tags_query() {
let account_name =
std::env::var("STORAGE_ACCOUNT").expect("Set env variable STORAGE_ACCOUNT first!");
let key = std::env::var("STORAGE_ACCESS_KEY")
.expect("Set env variable STORAGE_ACCESS_KEY first!");

let storage_credentials = StorageCredentials::access_key(&account_name, key.to_string());
let client_builder = ClientBuilder::new(&account_name, storage_credentials);
let container_name = "valid-container-name";

let service_client = client_builder.blob_service_client();

let query_expression = format!("@container='{container_name}' AND tagis='a'");

let _filtered_blobs = service_client
.find_blobs_by_tags(query_expression)
.into_stream()
.next()
.await
.unwrap()
.unwrap();
}

#[test]
fn test_make_url_compatible() {
let mut url = Url::from_str("https://test.blob.core.windows.net/?comp=blobs&where=%40container%3D%27valid-container-name%27+AND+tagis%3D%27a%27").unwrap();

make_url_compatible_with_api(&mut url);

assert_eq!("https://test.blob.core.windows.net/?comp=blobs&where=%40container%3D%27valid-container-name%27%20AND%20tagis%3D%27a%27", url.as_str() );
}
}