Skip to content

Commit fcfa0d9

Browse files
feat(pyth-lazer-protocol): add validations for price request types, add unknown_symbols to InvalidFeedSubscriptionDetails (#3065)
* feat(protocol): add unknown_symbols feed subscription response type * feat(pyth-lazer-protocol): bump proto version * feat(pyth-lazer-protocol): add deserializers with validation for LatestPriceRequest and PriceRequest * extract out validation functions * feat(lazer/js/sdk): add unknown_symbols to InvalidFeedSubscriptionDetails
1 parent db01db1 commit fcfa0d9

File tree

5 files changed

+163
-39
lines changed

5 files changed

+163
-39
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lazer/sdk/js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pythnetwork/pyth-lazer-sdk",
3-
"version": "3.0.0",
3+
"version": "3.0.1",
44
"description": "Pyth Lazer SDK",
55
"publishConfig": {
66
"access": "public"

lazer/sdk/js/src/protocol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export type JsonBinaryData = {
5050

5151
export type InvalidFeedSubscriptionDetails = {
5252
unknownIds: number[];
53+
unknownSymbols: string[];
5354
unsupportedChannels: number[];
5455
unstable: number[];
5556
};

lazer/sdk/rust/client/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pyth-lazer-client"
3-
version = "8.0.1"
3+
version = "8.1.0"
44
edition = "2021"
55
description = "A Rust client for Pyth Lazer"
66
license = "Apache-2.0"

lazer/sdk/rust/protocol/src/api.rs

Lines changed: 159 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use crate::{
1616

1717
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1818
#[serde(rename_all = "camelCase")]
19-
pub struct LatestPriceRequest {
19+
pub struct LatestPriceRequestRepr {
20+
// Either price feed ids or symbols must be specified.
2021
pub price_feed_ids: Option<Vec<PriceFeedId>>,
2122
pub symbols: Option<Vec<String>>,
2223
pub properties: Vec<PriceFeedProperty>,
@@ -32,9 +33,55 @@ pub struct LatestPriceRequest {
3233
pub channel: Channel,
3334
}
3435

36+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
37+
#[serde(rename_all = "camelCase")]
38+
pub struct LatestPriceRequest(LatestPriceRequestRepr);
39+
40+
impl<'de> Deserialize<'de> for LatestPriceRequest {
41+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
42+
where
43+
D: serde::Deserializer<'de>,
44+
{
45+
let value = LatestPriceRequestRepr::deserialize(deserializer)?;
46+
Self::new(value).map_err(Error::custom)
47+
}
48+
}
49+
50+
impl LatestPriceRequest {
51+
pub fn new(value: LatestPriceRequestRepr) -> Result<Self, &'static str> {
52+
validate_price_feed_ids_or_symbols(&value.price_feed_ids, &value.symbols)?;
53+
validate_optional_nonempty_vec_has_unique_elements(
54+
&value.price_feed_ids,
55+
"no price feed ids specified",
56+
"duplicate price feed ids specified",
57+
)?;
58+
validate_optional_nonempty_vec_has_unique_elements(
59+
&value.symbols,
60+
"no symbols specified",
61+
"duplicate symbols specified",
62+
)?;
63+
validate_formats(&value.formats)?;
64+
validate_properties(&value.properties)?;
65+
Ok(Self(value))
66+
}
67+
}
68+
69+
impl Deref for LatestPriceRequest {
70+
type Target = LatestPriceRequestRepr;
71+
72+
fn deref(&self) -> &Self::Target {
73+
&self.0
74+
}
75+
}
76+
impl DerefMut for LatestPriceRequest {
77+
fn deref_mut(&mut self) -> &mut Self::Target {
78+
&mut self.0
79+
}
80+
}
81+
3582
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
3683
#[serde(rename_all = "camelCase")]
37-
pub struct PriceRequest {
84+
pub struct PriceRequestRepr {
3885
pub timestamp: TimestampUs,
3986
// Either price feed ids or symbols must be specified.
4087
pub price_feed_ids: Option<Vec<PriceFeedId>>,
@@ -50,6 +97,52 @@ pub struct PriceRequest {
5097
pub channel: Channel,
5198
}
5299

100+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
101+
#[serde(rename_all = "camelCase")]
102+
pub struct PriceRequest(PriceRequestRepr);
103+
104+
impl<'de> Deserialize<'de> for PriceRequest {
105+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106+
where
107+
D: serde::Deserializer<'de>,
108+
{
109+
let value = PriceRequestRepr::deserialize(deserializer)?;
110+
Self::new(value).map_err(Error::custom)
111+
}
112+
}
113+
114+
impl PriceRequest {
115+
pub fn new(value: PriceRequestRepr) -> Result<Self, &'static str> {
116+
validate_price_feed_ids_or_symbols(&value.price_feed_ids, &value.symbols)?;
117+
validate_optional_nonempty_vec_has_unique_elements(
118+
&value.price_feed_ids,
119+
"no price feed ids specified",
120+
"duplicate price feed ids specified",
121+
)?;
122+
validate_optional_nonempty_vec_has_unique_elements(
123+
&value.symbols,
124+
"no symbols specified",
125+
"duplicate symbols specified",
126+
)?;
127+
validate_formats(&value.formats)?;
128+
validate_properties(&value.properties)?;
129+
Ok(Self(value))
130+
}
131+
}
132+
133+
impl Deref for PriceRequest {
134+
type Target = PriceRequestRepr;
135+
136+
fn deref(&self) -> &Self::Target {
137+
&self.0
138+
}
139+
}
140+
impl DerefMut for PriceRequest {
141+
fn deref_mut(&mut self) -> &mut Self::Target {
142+
&mut self.0
143+
}
144+
}
145+
53146
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
54147
#[serde(rename_all = "camelCase")]
55148
pub struct ReducePriceRequest {
@@ -221,40 +314,19 @@ impl<'de> Deserialize<'de> for SubscriptionParams {
221314

222315
impl SubscriptionParams {
223316
pub fn new(value: SubscriptionParamsRepr) -> Result<Self, &'static str> {
224-
if value.price_feed_ids.is_none() && value.symbols.is_none() {
225-
return Err("either price feed ids or symbols must be specified");
226-
}
227-
if value.price_feed_ids.is_some() && value.symbols.is_some() {
228-
return Err("either price feed ids or symbols must be specified, not both");
229-
}
230-
231-
if let Some(ref ids) = value.price_feed_ids {
232-
if ids.is_empty() {
233-
return Err("no price feed ids specified");
234-
}
235-
if !ids.iter().all_unique() {
236-
return Err("duplicate price feed ids specified");
237-
}
238-
}
239-
240-
if let Some(ref symbols) = value.symbols {
241-
if symbols.is_empty() {
242-
return Err("no symbols specified");
243-
}
244-
if !symbols.iter().all_unique() {
245-
return Err("duplicate symbols specified");
246-
}
247-
}
248-
249-
if !value.formats.iter().all_unique() {
250-
return Err("duplicate formats or chains specified");
251-
}
252-
if value.properties.is_empty() {
253-
return Err("no properties specified");
254-
}
255-
if !value.properties.iter().all_unique() {
256-
return Err("duplicate properties specified");
257-
}
317+
validate_price_feed_ids_or_symbols(&value.price_feed_ids, &value.symbols)?;
318+
validate_optional_nonempty_vec_has_unique_elements(
319+
&value.price_feed_ids,
320+
"no price feed ids specified",
321+
"duplicate price feed ids specified",
322+
)?;
323+
validate_optional_nonempty_vec_has_unique_elements(
324+
&value.symbols,
325+
"no symbols specified",
326+
"duplicate symbols specified",
327+
)?;
328+
validate_formats(&value.formats)?;
329+
validate_properties(&value.properties)?;
258330
Ok(Self(value))
259331
}
260332
}
@@ -467,6 +539,7 @@ pub struct SubscribedResponse {
467539
#[serde(rename_all = "camelCase")]
468540
pub struct InvalidFeedSubscriptionDetails {
469541
pub unknown_ids: Vec<PriceFeedId>,
542+
pub unknown_symbols: Vec<String>,
470543
pub unsupported_channels: Vec<PriceFeedId>,
471544
pub unstable: Vec<PriceFeedId>,
472545
}
@@ -511,3 +584,53 @@ pub struct StreamUpdatedResponse {
511584
#[serde(flatten)]
512585
pub payload: JsonUpdate,
513586
}
587+
588+
// Common validation functions
589+
fn validate_price_feed_ids_or_symbols(
590+
price_feed_ids: &Option<Vec<PriceFeedId>>,
591+
symbols: &Option<Vec<String>>,
592+
) -> Result<(), &'static str> {
593+
if price_feed_ids.is_none() && symbols.is_none() {
594+
return Err("either price feed ids or symbols must be specified");
595+
}
596+
if price_feed_ids.is_some() && symbols.is_some() {
597+
return Err("either price feed ids or symbols must be specified, not both");
598+
}
599+
Ok(())
600+
}
601+
602+
fn validate_optional_nonempty_vec_has_unique_elements<T>(
603+
vec: &Option<Vec<T>>,
604+
empty_msg: &'static str,
605+
duplicate_msg: &'static str,
606+
) -> Result<(), &'static str>
607+
where
608+
T: Eq + std::hash::Hash,
609+
{
610+
if let Some(ref items) = vec {
611+
if items.is_empty() {
612+
return Err(empty_msg);
613+
}
614+
if !items.iter().all_unique() {
615+
return Err(duplicate_msg);
616+
}
617+
}
618+
Ok(())
619+
}
620+
621+
fn validate_properties(properties: &[PriceFeedProperty]) -> Result<(), &'static str> {
622+
if properties.is_empty() {
623+
return Err("no properties specified");
624+
}
625+
if !properties.iter().all_unique() {
626+
return Err("duplicate properties specified");
627+
}
628+
Ok(())
629+
}
630+
631+
fn validate_formats(formats: &[Format]) -> Result<(), &'static str> {
632+
if !formats.iter().all_unique() {
633+
return Err("duplicate formats or chains specified");
634+
}
635+
Ok(())
636+
}

0 commit comments

Comments
 (0)