use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use solana_clock::Slot;
use uuid::Uuid;
use crate::Idl;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
#[doc = "Defines how an account address should be determined"]
pub enum AccountAddress {
#[doc = "A specific public key"]
Pubkey(String),
#[doc = "A Program Derived Address with seeds"]
Pda {
program_id: String,
seeds: Vec<PdaSeed>,
},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
#[doc = "Seeds used for PDA derivation"]
pub enum PdaSeed {
Pubkey(String),
String(String),
Bytes(Vec<u8>),
PropertyRef(String),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OverrideTemplate {
pub id: String,
pub name: String,
pub description: String,
pub protocol: String,
pub idl: Idl,
pub address: AccountAddress,
pub account_type: String,
pub properties: Vec<String>,
pub tags: Vec<String>,
}
impl OverrideTemplate {
pub fn new(
id: String,
name: String,
description: String,
protocol: String,
idl: Idl,
address: AccountAddress,
properties: Vec<String>,
account_type: String,
) -> Self {
Self {
id,
name,
description,
protocol,
idl,
address,
account_type,
properties,
tags: Vec::new(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct OverrideInstance {
#[doc = "Unique identifier for the scenario"]
pub id: String,
#[doc = "Reference to the template being used"]
pub template_id: String,
#[doc = "Values for the template properties (flat key-value map with dot notation, e.g., 'price_message.price_value')"]
pub values: HashMap<String, serde_json::Value>,
#[doc = "Relative slot when this override should be applied (relative to scenario registration slot)"]
pub scenario_relative_slot: Slot,
#[doc = "Optional label for this instance"]
pub label: Option<String>,
#[doc = "Whether this override is enabled"]
pub enabled: bool,
#[doc = "Whether to fetch fresh account data just before transaction execution"]
#[serde(default)]
pub fetch_before_use: bool,
#[doc = "Account address to override"]
pub account: AccountAddress,
}
impl OverrideInstance {
pub fn new(template_id: String, scenario_relative_slot: Slot, account: AccountAddress) -> Self {
Self {
id: Uuid::new_v4().to_string(),
template_id,
values: HashMap::new(),
scenario_relative_slot,
label: None,
enabled: true,
fetch_before_use: false,
account,
}
}
pub fn with_values(mut self, values: HashMap<String, serde_json::Value>) -> Self {
self.values = values;
self
}
pub fn with_label(mut self, label: String) -> Self {
self.label = Some(label);
self
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct Scenario {
#[doc = "Unique identifier for the scenario"]
pub id: String,
#[doc = "Human-readable name"]
pub name: String,
#[doc = "Description of this scenario"]
pub description: String,
#[doc = "List of override instances in this scenario"]
pub overrides: Vec<OverrideInstance>,
#[doc = "Tags for categorization"]
pub tags: Vec<String>,
}
impl Scenario {
pub fn new(name: String, description: String) -> Self {
Self {
id: Uuid::new_v4().to_string(),
name,
description,
overrides: Vec::new(),
tags: Vec::new(),
}
}
pub fn add_override(&mut self, override_instance: OverrideInstance) {
self.overrides.push(override_instance);
self.overrides.sort_by_key(|o| o.scenario_relative_slot);
}
pub fn remove_override(&mut self, override_id: &str) {
self.overrides.retain(|o| o.id != override_id);
}
pub fn get_overrides_for_slot(&self, slot: Slot) -> Vec<&OverrideInstance> {
self.overrides
.iter()
.filter(|o| o.enabled && o.scenario_relative_slot == slot)
.collect()
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ScenarioConfig {
pub enabled: bool,
pub active_scenario: Option<String>,
pub auto_save: bool,
}
impl Default for ScenarioConfig {
fn default() -> Self {
Self {
enabled: false,
active_scenario: None,
auto_save: true,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct YamlOverrideTemplateFile {
pub id: String,
pub name: String,
pub description: String,
pub protocol: String,
pub version: String,
pub account_type: String,
pub properties: Vec<String>,
pub idl_file_path: String,
pub address: YamlAccountAddress,
#[serde(default)]
pub tags: Vec<String>,
}
impl YamlOverrideTemplateFile {
pub fn to_override_template(self, idl: Idl) -> OverrideTemplate {
OverrideTemplate {
id: self.id,
name: self.name,
description: self.description,
protocol: self.protocol,
idl,
address: self.address.into(),
account_type: self.account_type,
properties: self.properties,
tags: self.tags,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct YamlOverrideTemplateCollection {
pub protocol: String,
pub version: String,
pub idl_file_path: String,
#[serde(default)]
pub tags: Vec<String>,
pub templates: Vec<YamlOverrideTemplateEntry>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct YamlOverrideTemplateEntry {
pub id: String,
pub name: String,
pub description: String,
pub idl_account_name: String,
pub properties: Vec<String>,
pub address: YamlAccountAddress,
}
impl YamlOverrideTemplateCollection {
pub fn to_override_templates(self, idl: Idl) -> Vec<OverrideTemplate> {
self.templates
.into_iter()
.map(|entry| OverrideTemplate {
id: entry.id,
name: entry.name,
description: entry.description,
protocol: self.protocol.clone(),
idl: idl.clone(),
address: entry.address.into(),
account_type: entry.idl_account_name,
properties: entry.properties,
tags: self.tags.clone(),
})
.collect()
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct YamlOverrideTemplate {
pub id: String,
pub name: String,
pub description: String,
pub protocol: String,
pub version: String,
pub account_type: String,
pub idl: Idl,
pub address: YamlAccountAddress,
pub properties: Vec<String>,
#[serde(default)]
pub tags: Vec<String>,
}
impl YamlOverrideTemplate {
pub fn to_override_template(self) -> OverrideTemplate {
OverrideTemplate {
id: self.id,
name: self.name,
description: self.description,
protocol: self.protocol,
idl: self.idl,
address: self.address.into(),
account_type: self.account_type,
properties: self.properties,
tags: self.tags,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum YamlAccountAddress {
Pubkey {
#[serde(default)]
value: Option<String>,
},
Pda {
program_id: String,
seeds: Vec<YamlPdaSeed>,
},
}
impl From<YamlAccountAddress> for AccountAddress {
fn from(yaml: YamlAccountAddress) -> Self {
match yaml {
YamlAccountAddress::Pubkey { value } => {
AccountAddress::Pubkey(value.unwrap_or_default())
}
YamlAccountAddress::Pda { program_id, seeds } => AccountAddress::Pda {
program_id,
seeds: seeds.into_iter().map(|s| s.into()).collect(),
},
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum YamlPdaSeed {
String { value: String },
Bytes { value: Vec<u8> },
Pubkey { value: String },
PropertyRef { value: String },
}
impl From<YamlPdaSeed> for PdaSeed {
fn from(yaml: YamlPdaSeed) -> Self {
match yaml {
YamlPdaSeed::String { value } => PdaSeed::String(value),
YamlPdaSeed::Bytes { value } => PdaSeed::Bytes(value),
YamlPdaSeed::Pubkey { value } => PdaSeed::Pubkey(value),
YamlPdaSeed::PropertyRef { value } => PdaSeed::PropertyRef(value),
}
}
}