mod common;
use std::sync::Mutex;
use cardanowall::client::{
parse_http_error, AccountBalance, ClientError, ClientResponse, ClientTransport, HttpErrorKind,
InvalidClientConfigError, Label309Client, Label309ClientConfig, Label309HttpError, MerkleLeaf,
ParseHttpErrorArgs, PoeVerifyInput, PublishBatchEntry, PublishBatchInput,
PublishBatchResultEntry, PublishContentInput, PublishInput, PublishMerkleInput,
PublishPrehashedInput, PublishSealedInput, QuoteInput, RecordSignature, RecordsListInput,
RequestBody, ResponseHeaders, SealedKemChoice, Signer, SignerError, SupportedHashAlg,
UploadsInput,
};
use cardanowall::verifier::fetch::{HttpMethod, OutboundError};
#[derive(Debug, Clone)]
struct Captured {
url: String,
method: HttpMethod,
headers: Vec<(String, String)>,
body: RequestBodySnapshot,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct MultipartFieldSnapshot {
name: String,
filename: Option<String>,
content_type: Option<String>,
value: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum RequestBodySnapshot {
None,
Json(String),
Multipart(Vec<MultipartFieldSnapshot>),
}
impl RequestBodySnapshot {
fn from(body: &RequestBody) -> Self {
match body {
RequestBody::None => RequestBodySnapshot::None,
RequestBody::Json(s) => RequestBodySnapshot::Json(s.clone()),
RequestBody::Multipart(fields) => RequestBodySnapshot::Multipart(
fields
.iter()
.map(|f| MultipartFieldSnapshot {
name: f.name.clone(),
filename: f.filename.clone(),
content_type: f.content_type.clone(),
value: f.value.clone(),
})
.collect(),
),
}
}
fn as_json(&self) -> &str {
match self {
RequestBodySnapshot::Json(s) => s,
_ => panic!("expected a JSON body, got {self:?}"),
}
}
}
#[derive(Clone)]
struct StubResponse {
status: u16,
body: Vec<u8>,
headers: ResponseHeaders,
}
impl StubResponse {
fn json(status: u16, value: serde_json::Value) -> Self {
Self {
status,
body: serde_json::to_vec(&value).unwrap(),
headers: ResponseHeaders::default(),
}
}
fn with_request_id(mut self, id: &str) -> Self {
self.headers.request_id = Some(id.to_string());
self
}
fn with_retry_after(mut self, seconds: u64) -> Self {
self.headers.retry_after_seconds = Some(seconds);
self
}
}
struct MockTransport {
captured: Mutex<Vec<Captured>>,
responses: Mutex<Vec<StubResponse>>,
}
impl MockTransport {
fn new(responses: Vec<StubResponse>) -> Self {
Self {
captured: Mutex::new(Vec::new()),
responses: Mutex::new(responses),
}
}
fn single(response: StubResponse) -> Self {
Self::new(vec![response])
}
fn first(&self) -> Captured {
self.captured.lock().unwrap()[0].clone()
}
fn nth(&self, index: usize) -> Captured {
self.captured.lock().unwrap()[index].clone()
}
fn call_count(&self) -> usize {
self.captured.lock().unwrap().len()
}
}
impl ClientTransport for MockTransport {
fn send(
&self,
url: &str,
method: HttpMethod,
headers: &[(String, String)],
body: &RequestBody,
) -> Result<ClientResponse, OutboundError> {
self.captured.lock().unwrap().push(Captured {
url: url.to_string(),
method,
headers: headers.to_vec(),
body: RequestBodySnapshot::from(body),
});
let mut responses = self.responses.lock().unwrap();
let stub = if responses.len() == 1 {
responses[0].clone()
} else {
responses.remove(0)
};
Ok(ClientResponse {
status: stub.status,
body: stub.body,
headers: stub.headers,
})
}
}
const QUOTE_ID: &str = "01956b41-7c00-7000-8000-000000000001";
fn bearer_key() -> String {
"opaque-bearer-aaaa".to_string()
}
fn header(captured: &Captured, name: &str) -> Option<String> {
captured
.headers
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(name))
.map(|(_, v)| v.clone())
}
fn client_with(
base_url: &str,
api_key: Option<&str>,
transport: Box<MockTransport>,
) -> (Label309Client, *const MockTransport) {
let ptr: *const MockTransport = transport.as_ref();
let client = Label309Client::with_transport(
Label309ClientConfig {
api_key: api_key.map(str::to_string),
base_url: Some(base_url.to_string()),
},
transport,
)
.unwrap();
(client, ptr)
}
fn mock<'a>(ptr: *const MockTransport) -> &'a MockTransport {
unsafe { &*ptr }
}
fn problem_body(overrides: serde_json::Value) -> serde_json::Value {
let mut base = serde_json::json!({
"type": "https://cardanowall.com/problems/example",
"title": "Example",
"status": 400,
"detail": "Example failure.",
"code": "example",
"trace_id": "01977c00-0000-7000-8000-000000000000",
});
if let (serde_json::Value::Object(b), serde_json::Value::Object(o)) = (&mut base, &overrides) {
for (k, v) in o {
b.insert(k.clone(), v.clone());
}
}
base
}
fn publish_success_body() -> serde_json::Value {
serde_json::json!({
"id": "poe_06bqrjg0csvqfanaqexvqexvqc",
"tx_hash": null,
"status": "submitting",
"items_count": 1,
"signed": false,
"sealed": false,
"items": [],
"conformance_profile": "core",
"balance_after_usd_micros": "4500000",
})
}
fn records_list_body(
data: serde_json::Value,
has_more: bool,
next: Option<&str>,
) -> serde_json::Value {
serde_json::json!({
"object": "list",
"data": data,
"has_more": has_more,
"next_cursor": next,
"url": "/api/v1/records",
})
}
include!("client_parts/request_parity.rs");
include!("client_parts/error_mapping.rs");
include!("client_parts/namespaces.rs");
include!("client_parts/off_host_sign.rs");