1use core::fmt::Display;
25use core::str::FromStr;
26use std::convert::Infallible;
27
28use amplify::confinement::{ConfinedVec, TinyBlob};
29use baid64::Baid64ParseError;
30use chrono::{DateTime, Utc};
31use indexmap::IndexMap;
32use strict_types::{StrictType, StrictVal, TypeName, VariantName};
33use ultrasonic::{AuthToken, ContractId};
34
35use crate::LIB_NAME_SONIC;
36
37pub type StateName = VariantName;
38pub type MethodName = VariantName;
39
40#[derive(Clone, Eq, PartialEq, Hash, Debug)]
42#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
43#[strict_type(lib = LIB_NAME_SONIC)]
44#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase", bound = ""))]
45pub struct CallState {
46 pub method: MethodName,
47 pub destructible: Option<StateName>,
48}
49
50impl CallState {
51 pub fn new(method: impl Into<MethodName>) -> Self { Self { method: method.into(), destructible: None } }
52
53 pub fn with(method: impl Into<MethodName>, destructible: impl Into<StateName>) -> Self {
54 Self {
55 method: method.into(),
56 destructible: Some(destructible.into()),
57 }
58 }
59}
60
61#[derive(Clone, Eq, PartialEq, Debug)]
105#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
106#[cfg_attr(
107 feature = "serde",
108 serde(bound(
109 serialize = "T: serde::Serialize, A: serde::Serialize",
110 deserialize = "T: serde::Deserialize<'de>, A: serde::Deserialize<'de>"
111 ))
112)]
113pub struct CallRequest<T = CallScope, A = AuthToken> {
114 pub scope: T,
115 pub api: Option<TypeName>,
116 pub call: Option<CallState>,
117 pub auth: A,
118 pub data: Option<StrictVal>,
119 pub lock: Option<TinyBlob>,
120 pub expiry: Option<DateTime<Utc>>,
121 pub endpoints: ConfinedVec<Endpoint, 0, 10>,
122 pub unknown_query: IndexMap<String, String>,
123}
124
125impl<Q: Display + FromStr, A> CallRequest<CallScope<Q>, A> {
126 pub fn unwrap_contract_with<E>(
127 self,
128 f: impl FnOnce(Q) -> Result<ContractId, E>,
129 ) -> Result<CallRequest<ContractId, A>, E> {
130 let id = match self.scope {
131 CallScope::ContractId(id) => id,
132 CallScope::ContractQuery(query) => f(query)?,
133 };
134 Ok(CallRequest {
135 scope: id,
136 api: self.api,
137 call: self.call,
138 auth: self.auth,
139 data: self.data,
140 lock: self.lock,
141 expiry: self.expiry,
142 endpoints: self.endpoints,
143 unknown_query: self.unknown_query,
144 })
145 }
146}
147
148#[derive(Clone, Eq, PartialEq, Debug, Display)]
149#[cfg_attr(
150 feature = "serde",
151 derive(Serialize, Deserialize),
152 serde(
153 try_from = "String",
154 into = "String",
155 bound(serialize = "Q: Display + FromStr + Clone", deserialize = "Q: Display + FromStr"),
156 )
157)]
158pub enum CallScope<Q: Display + FromStr = String> {
159 #[display(inner)]
160 ContractId(ContractId),
161
162 #[display("contract:{0}")]
163 ContractQuery(Q),
164}
165
166impl<Q: Display + FromStr> FromStr for CallScope<Q> {
167 type Err = Baid64ParseError;
168
169 fn from_str(s: &str) -> Result<Self, Self::Err> {
170 match ContractId::from_str(s) {
171 Ok(id) => Ok(Self::ContractId(id)),
172 Err(err_contract_id) => match s.strip_prefix("contract:") {
173 Some(query_str) => Q::from_str(query_str)
174 .map(Self::ContractQuery)
175 .map_err(|_| err_contract_id),
176 None => Err(err_contract_id),
177 },
178 }
179 }
180}
181
182impl<Q: Display + FromStr> TryFrom<String> for CallScope<Q> {
183 type Error = Baid64ParseError;
184
185 fn try_from(value: String) -> Result<Self, Self::Error> { Self::from_str(&value) }
186}
187
188impl<Q: Display + FromStr + Clone> From<CallScope<Q>> for String {
189 fn from(value: CallScope<Q>) -> Self { value.to_string() }
190}
191
192#[derive(Clone, Eq, PartialEq, Debug, Display)]
193#[display(inner)]
194#[non_exhaustive]
195#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
196#[cfg_attr(feature = "serde", serde(try_from = "String", into = "String"))]
197pub enum Endpoint {
198 JsonRpc(String),
199 RestHttp(String),
200 WebSockets(String),
201 Storm(String),
202 UnspecifiedMeans(String),
203}
204
205impl FromStr for Endpoint {
206 type Err = Infallible;
207
208 fn from_str(s: &str) -> Result<Self, Self::Err> {
209 let s = s.to_lowercase();
210 #[allow(clippy::if_same_then_else)] if s.starts_with("http://") || s.starts_with("https://") {
212 Ok(Endpoint::RestHttp(s))
213 } else if s.starts_with("http+json-rpc://") || s.starts_with("https+json-rpc://") {
214 Ok(Endpoint::RestHttp(s))
215 } else if s.starts_with("ws://") || s.starts_with("wss://") {
216 Ok(Endpoint::WebSockets(s))
217 } else if s.starts_with("storm://") {
218 Ok(Endpoint::Storm(s))
219 } else {
220 Ok(Endpoint::UnspecifiedMeans(s.to_string()))
221 }
222 }
223}
224
225impl TryFrom<String> for Endpoint {
226 type Error = Infallible;
227
228 fn try_from(value: String) -> Result<Self, Self::Error> { Self::from_str(&value) }
229}
230
231impl From<Endpoint> for String {
232 fn from(value: Endpoint) -> Self { value.to_string() }
233}