Skip to content
Merged
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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ on:
- 'openstack_sdk/**'
- 'openstack_tui/**'
- 'openstack_types/**'
- 'sdk-*/**'
- 'sdk/**'
- 'fuzz/**'

env:
Expand Down
90 changes: 87 additions & 3 deletions sdk/core/src/api/rest_endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ where
if let Some(root_key) = self.response_key() {
v = v[root_key.as_ref()].take();
}
if let (Some(item_key), Some(array)) = (self.response_list_item_key(), v.as_array_mut()) {
for elem in array {
*elem = elem[item_key.as_ref()].take();
}
}

let headers = rsp.headers();
// Process headers which endpoint wants to capture
Expand Down Expand Up @@ -260,6 +265,12 @@ where
v = v[root_key.as_ref()].take();
}

if let (Some(item_key), Some(array)) = (self.response_list_item_key(), v.as_array_mut()) {
for elem in array {
*elem = elem[item_key.as_ref()].take();
}
}

let headers = rsp.headers();
// Process headers which endpoint wants to capture
for (header_key, target_val) in self.response_headers().iter() {
Expand Down Expand Up @@ -376,16 +387,19 @@ where
}
}

#[cfg(feature = "sync")]
#[cfg(test)]
mod tests {
use http::StatusCode;
use httpmock::MockServer;
use serde::Deserialize;
use serde_json::json;

use crate::api::ApiError;
#[cfg(feature = "sync")]
use crate::api::Query;
#[cfg(feature = "async")]
use crate::api::QueryAsync;
use crate::api::rest_endpoint_prelude::*;
use crate::api::{ApiError, Query};
use crate::test::client::FakeOpenStackClient;
use crate::types::ServiceType;

Expand All @@ -405,11 +419,12 @@ mod tests {
}
}

#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, PartialEq)]
struct DummyResult {
value: u8,
}

#[cfg(feature = "sync")]
#[test]
fn test_non_json_response() {
let server = MockServer::start();
Expand All @@ -429,6 +444,7 @@ mod tests {
mock.assert();
}

#[cfg(feature = "sync")]
#[test]
fn test_empty_response() {
let server = MockServer::start();
Expand All @@ -448,6 +464,7 @@ mod tests {
mock.assert();
}

#[cfg(feature = "sync")]
#[test]
fn test_error_not_found() {
let server = MockServer::start();
Expand All @@ -466,6 +483,7 @@ mod tests {
mock.assert();
}

#[cfg(feature = "sync")]
#[test]
fn test_error_bad_json() {
let server = MockServer::start();
Expand All @@ -485,6 +503,7 @@ mod tests {
mock.assert();
}

#[cfg(feature = "sync")]
#[test]
fn test_error_detection() {
let server = MockServer::start();
Expand All @@ -511,6 +530,7 @@ mod tests {
mock.assert();
}

#[cfg(feature = "sync")]
#[test]
fn test_error_detection_unknown() {
let server = MockServer::start();
Expand All @@ -537,6 +557,7 @@ mod tests {
mock.assert();
}

#[cfg(feature = "sync")]
#[test]
fn test_bad_deserialization() {
let server = MockServer::start();
Expand All @@ -560,6 +581,7 @@ mod tests {
mock.assert();
}

#[cfg(feature = "sync")]
#[test]
fn test_good_deserialization() {
let server = MockServer::start();
Expand All @@ -573,4 +595,66 @@ mod tests {
assert_eq!(res.unwrap().value, 0);
mock.assert();
}

struct DummyLi;
impl RestEndpoint for DummyLi {
fn method(&self) -> http::Method {
http::Method::GET
}

fn endpoint(&self) -> Cow<'static, str> {
"dummy".into()
}

fn service_type(&self) -> ServiceType {
ServiceType::from("dummy")
}
fn response_key(&self) -> Option<Cow<'static, str>> {
Some("container".into())
}

fn response_list_item_key(&self) -> Option<Cow<'static, str>> {
Some("data".into())
}
}

#[cfg(feature = "sync")]
#[test]
fn test_resource_with_list_inside() {
let server = MockServer::start();
let client = FakeOpenStackClient::new(server.base_url());
let mock = server.mock(|when, then| {
when.method(httpmock::Method::GET).path("/dummy");
then.status(200).json_body(json!({
"container": [
{"data": {"value": 0}},
{"data": {"value": 2}}
]}));
});

let res: Vec<DummyResult> = DummyLi.query(&client).unwrap();
assert!(res.contains(&DummyResult { value: 0 }));
assert!(res.contains(&DummyResult { value: 2 }));
mock.assert();
}

#[cfg(feature = "async")]
#[tokio::test]
async fn test_resource_with_list_inside_async() {
let server = MockServer::start_async().await;
let client = FakeOpenStackClient::new(server.base_url());
let mock = server.mock(|when, then| {
when.method(httpmock::Method::GET).path("/dummy");
then.status(200).json_body(json!({
"container": [
{"data": {"value": 0}},
{"data": {"value": 1}}
]}));
});

let res: Vec<DummyResult> = DummyLi.query_async(&client).await.unwrap();
assert!(res.contains(&DummyResult { value: 0 }), "{:?}", res);
assert!(res.contains(&DummyResult { value: 1 }), "{:?}", res);
mock.assert();
}
}
Loading