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
4 changes: 2 additions & 2 deletions crates/core/examples/streams_e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ async fn main() {
.include_stream_metadata(StreamMetadataLocation::Body)
.destination_attributes(DestinationAttributes::Webhook(WebhookAttributes {
url: "https://webhook.site/ae19071a-2dcc-4035-9cdf-406dcb4719ef".to_string(),
compression: "none".to_string(),
compression: Some("none".to_string()),
max_retry: 3,
retry_interval_sec: 1,
post_timeout_sec: 10,
security_token: None,
}))
.extra_destinations(vec![DestinationAttributes::Webhook(WebhookAttributes {
url: "https://webhook.site/ae19071a-2dcc-4035-9cdf-406dcb4719ef".to_string(),
compression: "none".to_string(),
compression: Some("none".to_string()),
max_retry: 3,
retry_interval_sec: 1,
post_timeout_sec: 10,
Expand Down
2 changes: 1 addition & 1 deletion crates/core/src/admin/bulk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub struct BulkUpdateEndpointStatusResponse {
pub struct BulkAddTagRequest {
/// Endpoint ids to tag.
pub ids: Vec<String>,
/// Label of the tag to apply (created if it doesn't exist).
/// Label of the tag to apply (created if it doesn't exist). Maximum 25 characters.
pub label: String,
}

Expand Down
2 changes: 1 addition & 1 deletion crates/core/src/admin/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ pub struct UpdateEndpointStatusResponse {
#[cfg_attr(feature = "rust", derive(Builder))]
#[derive(Debug, Clone, Default, Serialize)]
pub struct CreateTagRequest {
/// Label for the new tag.
/// Label for the new tag. Maximum 25 characters.
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
Expand Down
56 changes: 54 additions & 2 deletions crates/core/src/kvstore/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,20 @@ use napi_derive::napi;
use pyo3::{pyclass, pymethods};
#[cfg(feature = "python")]
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize};

use crate::{config::KvStoreConfig, errors::SdkError, SdkConfig};

// The KV index endpoints return `data: null` when the store has no entries.
// Map that to the default value so consumers get `[]` (or an empty struct) instead of a decode error.
fn null_as_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + Default,
{
Option::<T>::deserialize(deserializer).map(Option::unwrap_or_default)
}

const KV_STORE_BASE_URL: &str = "https://api.quicknode.com/kv/rest/v1/";

// ── Resolved config ────────────────────────────────────────────────────────
Expand Down Expand Up @@ -172,6 +182,7 @@ impl KvSetEntry {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetSetsResponse {
/// Key/value entries on the current page.
#[serde(default, deserialize_with = "null_as_default")]
pub data: Vec<KvSetEntry>,
/// Cursor for the next page; empty string when there are no more pages.
pub cursor: String,
Expand Down Expand Up @@ -213,7 +224,7 @@ impl GetSetResponse {
#[cfg_attr(feature = "python", gen_stub_pyclass)]
#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
#[cfg_attr(feature = "node", napi(object))]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GetListsData {
/// List keys on the current page.
pub keys: Vec<String>,
Expand All @@ -237,6 +248,7 @@ impl GetListsData {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetListsResponse {
/// List keys on the current page.
#[serde(default, deserialize_with = "null_as_default")]
pub data: GetListsData,
/// Cursor for the next page; empty string when there are no more pages.
pub cursor: String,
Expand Down Expand Up @@ -767,6 +779,26 @@ mod tests {
assert_eq!(resp.data[0].key, "k1");
}

#[tokio::test]
async fn get_sets_null_data_empty_store() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/sets"))
.respond_with(ResponseTemplate::new(200).set_body_json(
serde_json::json!({"code": 200, "msg": "", "data": null, "cursor": ""}),
))
.mount(&server)
.await;
let sdk = make_sdk(format!("{}/", server.uri()));
let resp = sdk
.kvstore
.get_sets(&GetSetsParams::default())
.await
.unwrap();
assert!(resp.data.is_empty());
assert_eq!(resp.cursor, "");
}

#[tokio::test]
async fn get_sets_api_error() {
let server = MockServer::start().await;
Expand Down Expand Up @@ -1029,6 +1061,26 @@ mod tests {
assert_eq!(resp.data.keys, vec!["list1", "list2"]);
}

#[tokio::test]
async fn get_lists_null_data_empty_store() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/lists"))
.respond_with(ResponseTemplate::new(200).set_body_json(
serde_json::json!({"code": 200, "msg": "", "data": null, "cursor": ""}),
))
.mount(&server)
.await;
let sdk = make_sdk(format!("{}/", server.uri()));
let resp = sdk
.kvstore
.get_lists(&GetListsParams::default())
.await
.unwrap();
assert!(resp.data.keys.is_empty());
assert_eq!(resp.cursor, "");
}

#[tokio::test]
async fn get_lists_api_error() {
let server = MockServer::start().await;
Expand Down
27 changes: 13 additions & 14 deletions crates/core/src/streams/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
pub mod stream;

pub use stream::{
AddressBookConfig, AzureAttributes, ClickhouseAttributes, CreateStreamParams,
DestinationAttributes, EnabledCountResponse, FilterLanguage, KafkaAttributes,
ListStreamsParams, ListStreamsResponse, MongoAttributes, MysqlAttributes, PageInfo,
PostgresAttributes, ProductType, RedisAttributes, S3Attributes, SnowflakeAttributes, Stream,
StreamDataset, StreamDestination, StreamMetadataLocation, StreamRegion, StreamStatus,
TestFilterParams, TestFilterResponse, UpdateStreamParams, WebhookAttributes,
AddressBookConfig, AzureAttributes, CreateStreamParams, DestinationAttributes,
EnabledCountResponse, FilterLanguage, KafkaAttributes, ListStreamsParams, ListStreamsResponse,
PageInfo, PostgresAttributes, ProductType, S3Attributes, Stream, StreamDataset,
StreamDestination, StreamMetadataLocation, StreamRegion, StreamStatus, TestFilterParams,
TestFilterResponse, UpdateStreamParams, WebhookAttributes,
};

use crate::{config::StreamsConfig, errors::SdkError, SdkConfig};
Expand Down Expand Up @@ -345,12 +344,12 @@ mod tests {
max_retry: 3,
retry_interval_sec: 1,
post_timeout_sec: 10,
compression: "none".to_string(),
compression: Some("none".to_string()),
security_token: None,
}),
plan: "growth_plan".to_string(),
threshold_fetch_buffer: 1000,
dataset_batch_size: None,
plan: Some("growth_plan".to_string()),
threshold_fetch_buffer: Some(1000),
dataset_batch_size: 1,
max_batch_size: None,
max_buffer_range_size: None,
max_buffer_processing_workers: None,
Expand All @@ -364,7 +363,7 @@ mod tests {
notification_email: None,
charge_min_cap: None,
fix_block_reorgs: None,
elastic_batch_enabled: None,
elastic_batch_enabled: true,
extra_destinations: None,
}
}
Expand Down Expand Up @@ -451,7 +450,7 @@ mod tests {
max_retry: 3,
retry_interval_sec: 1,
post_timeout_sec: 10,
compression: "none".to_string(),
compression: Some("none".to_string()),
security_token: None,
}),
..webhook_params()
Expand Down Expand Up @@ -506,7 +505,7 @@ mod tests {
max_retry: 5,
retry_interval_sec: 2,
post_timeout_sec: 15,
compression: "none".to_string(),
compression: Some("none".to_string()),
security_token: None,
}),
DestinationAttributes::S3(S3Attributes {
Expand Down Expand Up @@ -559,7 +558,7 @@ mod tests {
max_retry: 1,
retry_interval_sec: 1,
post_timeout_sec: 5,
compression: "none".to_string(),
compression: Some("none".to_string()),
security_token: None,
})]),
..Default::default()
Expand Down
Loading
Loading