percli_core/agent/
feed.rs1use serde::Deserialize;
2use std::io::BufRead;
3use std::path::PathBuf;
4
5#[derive(Debug, Clone, Deserialize)]
7pub struct PriceTick {
8 pub oracle: u64,
9 pub slot: u64,
10}
11
12#[derive(Debug, Clone, Deserialize)]
14#[serde(tag = "type", rename_all = "snake_case")]
15pub enum FeedConfig {
16 Inline { prices: Vec<PriceTick> },
17 Csv { path: PathBuf },
18 Stdin,
19}
20
21impl Default for FeedConfig {
22 fn default() -> Self {
23 Self::Inline {
24 prices: vec![PriceTick {
25 oracle: 1000,
26 slot: 100,
27 }],
28 }
29 }
30}
31
32impl FeedConfig {
33 pub fn into_tick_iter(self) -> anyhow::Result<Box<dyn Iterator<Item = PriceTick>>> {
37 match self {
38 FeedConfig::Inline { prices } => Ok(Box::new(prices.into_iter())),
39 FeedConfig::Csv { path } => {
40 let content = std::fs::read_to_string(&path).map_err(|e| {
41 anyhow::anyhow!("failed to read feed CSV {}: {}", path.display(), e)
42 })?;
43 let ticks: Vec<PriceTick> = content
44 .lines()
45 .filter(|line| !line.starts_with('#') && !line.is_empty())
46 .filter_map(|line| {
47 let parts: Vec<&str> = line.split(',').map(|s| s.trim()).collect();
48 if parts.len() >= 2 {
49 let oracle = parts[0].parse().ok()?;
50 let slot = parts[1].parse().ok()?;
51 Some(PriceTick { oracle, slot })
52 } else {
53 None
54 }
55 })
56 .collect();
57 Ok(Box::new(ticks.into_iter()))
58 }
59 FeedConfig::Stdin => {
60 let reader = std::io::stdin().lock();
61 let ticks: Vec<PriceTick> = reader
62 .lines()
63 .map_while(Result::ok)
64 .filter(|line| !line.trim().is_empty())
65 .filter_map(|line| serde_json::from_str(&line).ok())
66 .collect();
67 Ok(Box::new(ticks.into_iter()))
68 }
69 }
70 }
71}