Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ change is needed, state why in the final report.
- Preserve thin-wrapper behavior. Prefer adding the missing endpoint mapping,
request params, response types, and tests over redesigning the client.
- Follow the existing crate shape: `api_client.rs`, `api_client/builder.rs`, API module
files such as `api_v1.rs`, `api_v1/request.rs`, `api_v1/response.rs`, and
`api_v1/types.rs`.
files such as `v1.rs`, `v1/request.rs`, `v1/response.rs`, and `v1/types.rs`
for versioned REST APIs. Keep unversioned service groups in a descriptive
module such as `api.rs` or `graphql.rs`.
- Use the existing `derive_setters` builder pattern for client configuration.
- Prefer typed request parameter structs with serde derives over manual query
string construction.
Expand Down Expand Up @@ -138,8 +139,8 @@ For GraphQL crates:

1. Keep checked-in schema/query files aligned with the generated types used in
tests or examples.
2. Preserve the `exec_graphql(operation_name, query)` boundary unless the user
asks for a higher-level wrapper.
2. Preserve the `exec(operation_name, query)` GraphQL boundary on the child
GraphQL client unless the user asks for a higher-level wrapper.
3. Verify operation names and headers against the live endpoint.
4. Document usage through tests or README examples.

Expand Down
3 changes: 2 additions & 1 deletion bidask_api_client/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ changes.
The crate currently contains a thin typed client:

- `BidaskApiClient::builder().build()?`
- `client.exec_api(&Request::...)`
- `client.api.exec(&Request::...)`
- request enum in `api/request.rs`
- response enum and models in `api/response.rs` and `api/types.rs`

Expand All @@ -35,6 +35,7 @@ Treat these as public contracts:

- `BidaskApiClient`
- `DEFAULT_API_URL`
- `api::ApiClient`
- `Request`
- `Response`
- public response/type structs
Expand Down
71 changes: 71 additions & 0 deletions bidask_api_client/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,77 @@ mod request;
mod response;
mod types;

use api_clients_core::{ApiClientsResult, Executor};
use serde::Serialize;
use std::sync::Arc;

pub use request::*;
pub use response::*;
pub use types::*;

#[derive(Clone)]
pub struct ApiClient {
executor: Arc<Executor>,
}

impl ApiClient {
pub(crate) fn new(executor: Arc<Executor>) -> Self { Self { executor } }

pub async fn exec<REQUEST>(&self, request: REQUEST) -> ApiClientsResult<Response>
where
REQUEST: Into<Request>,
{
let response = match request.into() {
Request::Pools => Response::Pools(self.fetch_all_pool_pages().await?),
};
Ok(response)
}

async fn fetch_all_pool_pages(&self) -> ApiClientsResult<Vec<PoolInfo>> {
let mut page_number = 1_i64;
let mut total_pages: Option<i64> = None;
let mut aggregated_pages = Vec::new();

loop {
let params = PaginationParams { page: page_number };
let PaginatedResponse {
page: page_info,
result: pools,
} = self.executor.exec_get_extra("pools", &params, &[]).await?;

let page_count = match total_pages {
Some(count) => count,
None => {
let computed = total_pages_from(&page_info);
total_pages = Some(computed);
computed
}
};

let is_empty_page = pools.is_empty();
aggregated_pages.extend_from_slice(&pools);

if page_number >= page_count || is_empty_page {
break;
}

page_number += 1;
}

Ok(aggregated_pages)
}
}

#[derive(Serialize)]
struct PaginationParams {
page: i64,
}

fn total_pages_from(page_info: &PageInfo) -> i64 {
if page_info.size <= 0 {
return 1;
}

let pages = (page_info.total + page_info.size - 1) / page_info.size;
pages.max(1)
}
70 changes: 3 additions & 67 deletions bidask_api_client/src/api_client.rs
Original file line number Diff line number Diff line change
@@ -1,80 +1,16 @@
mod builder;

use crate::api::PageInfo;
use crate::api::PaginatedResponse;
use crate::api::PoolInfo;
use crate::api::Request;
use crate::api::Response;
use crate::api::ApiClient;
use crate::api_client::builder::Builder;
use api_clients_core::{ApiClientsResult, Executor};
use serde::Serialize;
use std::sync::Arc;

pub const DEFAULT_API_URL: &str = "https://api.bidask.finance/api";

#[derive(Clone)]
#[non_exhaustive]
pub struct BidaskApiClient {
executor: Arc<Executor>,
pub api: ApiClient,
}

impl BidaskApiClient {
pub fn builder() -> Builder { Builder::new() }

pub async fn exec_api<REQUEST>(&self, request: REQUEST) -> ApiClientsResult<Response>
where
REQUEST: Into<Request>,
{
let response = match request.into() {
Request::Pools => Response::Pools(self.fetch_all_pool_pages().await?),
};
Ok(response)
}

async fn fetch_all_pool_pages(&self) -> ApiClientsResult<Vec<PoolInfo>> {
let mut page_number = 1_i64;
let mut total_pages: Option<i64> = None;
let mut aggregated_pages = Vec::new();

loop {
let params = PaginationParams { page: page_number };
let PaginatedResponse {
page: page_info,
result: pools,
} = self.executor.exec_get_extra("pools", &params, &[]).await?;

let page_count = match total_pages {
Some(count) => count,
None => {
let computed = total_pages_from(&page_info);
total_pages = Some(computed);
computed
}
};

let is_empty_page = pools.is_empty();
aggregated_pages.extend_from_slice(&pools);

if page_number >= page_count || is_empty_page {
break;
}

page_number += 1;
}

Ok(aggregated_pages)
}
}

#[derive(Serialize)]
struct PaginationParams {
page: i64,
}

fn total_pages_from(page_info: &PageInfo) -> i64 {
if page_info.size <= 0 {
return 1;
}

let pages = (page_info.total + page_info.size - 1) / page_info.size;
pages.max(1)
}
4 changes: 3 additions & 1 deletion bidask_api_client/src/api_client/builder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::api::ApiClient;
use crate::api_client::{BidaskApiClient, DEFAULT_API_URL};
use api_clients_core::{ApiClientsResult, Executor};
use derive_setters::Setters;
Expand Down Expand Up @@ -25,6 +26,7 @@ impl Builder {
None => Executor::builder(self.api_url).build()?.into(),
};

Ok(BidaskApiClient { executor })
let api = ApiClient::new(executor);
Ok(BidaskApiClient { api })
}
}
2 changes: 1 addition & 1 deletion bidask_api_client/tests/test_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// async fn test_pools() -> Result<()> {
// let client = init_env();
// let request = Request::Pools;
// let response = unwrap_response!(Pools, client.exec_api(&request).await?)?;
// let response = unwrap_response!(Pools, client.api.exec(&request).await?)?;
// assert_ne!(response, vec![]);
// log::debug!("{:?}", response.len());
// Ok(())
Expand Down
11 changes: 10 additions & 1 deletion bidask_api_client/tests/test_construction.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
use bidask_api_client::api::{PageInfo, PaginatedResponse, PoolInfo, TokenInfo};
use bidask_api_client::api::{PageInfo, PaginatedResponse, PoolInfo, Request, TokenInfo};
use bidask_api_client::api_client::BidaskApiClient;

#[test]
fn test_client_exposes_api_executor() -> anyhow::Result<()> {
let client = BidaskApiClient::builder().build()?;
let future = client.api.exec(Request::Pools);
drop(future);
Ok(())
}

#[test]
fn test_response_models_support_default_setter_construction() {
Expand Down
13 changes: 7 additions & 6 deletions dedust_api_client/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ changes.
The crate exposes a thin typed client:

- `DedustApiClient::builder().build()?`
- `client.exec_api_v2(&V2Request::...)`
- request params in `api_v2/request.rs`
- response enums and models in `api_v2/response.rs` and `api_v2/types.rs`
- `client.v2.exec(&V2Request::...)`
- request params in `v2/request.rs`
- response enums and models in `v2/response.rs` and `v2/types.rs`

Keep DeDust-specific address formatting and endpoint mapping in this crate.

Expand All @@ -26,6 +26,7 @@ Treat these as public contracts:

- `DedustApiClient`
- `DEFAULT_API_V2_URL`
- `V2ApiClient`
- `V2Request`
- `RoutingPlanParams`
- `V2Response` and public response/type structs
Expand All @@ -34,7 +35,7 @@ Treat these as public contracts:
Request parameter and response/model POD structs are `#[non_exhaustive]`; use
`Default::default().with_<field>(...)` or request parameter constructors instead
of struct literals in downstream examples and integration tests. Pass request
parameters directly to `exec_api_v2` where `Into<V2Request>` is implemented.
parameters directly to `client.v2.exec` where `Into<V2Request>` is implemented.
Public enums are `#[non_exhaustive]`; downstream matches need wildcard arms.

`RoutingPlanParams::new` maps the zero TON address to `native` and all other
Expand All @@ -50,8 +51,8 @@ counts, routing amounts, or ordering.
## Downstream Integration Example

```rust
use dedust_api_client::api_v2::{RoutingPlanParams, V2Request, V2Response};
use dedust_api_client::api_client::DedustApiClient;
use dedust_api_client::v2::{RoutingPlanParams, V2Request, V2Response};

# async fn example() -> anyhow::Result<()> {
let client = DedustApiClient::builder().build()?;
Expand All @@ -60,7 +61,7 @@ let params = RoutingPlanParams::new(
"0:0000000000000000000000000000000000000000000000000000000000000000",
"1000000000",
);
let response = client.exec_api_v2(params).await?;
let response = client.v2.exec(params).await?;

match response {
V2Response::RoutingPlan(routes) => println!("routes: {}", routes.len()),
Expand Down
8 changes: 4 additions & 4 deletions dedust_api_client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ wildcard arm.

```rust,no_run
use dedust_api_client::api_client::DedustApiClient;
use dedust_api_client::api_v2::{RoutingPlanParams, V2Response};
use dedust_api_client::v2::{RoutingPlanParams, V2Response};

# async fn example() -> Result<(), Box<dyn std::error::Error>> {
let client = DedustApiClient::builder().build()?;
Expand All @@ -29,7 +29,7 @@ let params = RoutingPlanParams::new(
"0:0000000000000000000000000000000000000000000000000000000000000000",
"1000000000",
);
let response = client.exec_api_v2(params).await?;
let response = client.v2.exec(params).await?;

match response {
V2Response::RoutingPlan(routes) => println!("route groups: {}", routes.len()),
Expand Down Expand Up @@ -70,8 +70,8 @@ addresses to `jetton:<address>`.
Public request and response types are marked `#[non_exhaustive]` for semver
headroom. Build public POD structs with `Default::default().with_<field>(...)`
or request parameter constructors, pass request parameters directly to
`exec_api_v2` where `Into<V2Request>` is implemented, and include a wildcard arm
when matching response enums.
`client.v2.exec` where `Into<V2Request>` is implemented, and include a wildcard
arm when matching response enums.

Live API tests hit DeDust directly. Pool counts, routing amounts, and ordering
can drift with upstream state.
28 changes: 3 additions & 25 deletions dedust_api_client/src/api_client.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,16 @@
mod builder;

use crate::api_client::builder::Builder;
use crate::api_v2::V2Request;
use crate::api_v2::V2Response;
use api_clients_core::{ApiClientsResult, Executor};
use std::sync::Arc;
use crate::v2::V2ApiClient;

pub const DEFAULT_API_V2_URL: &str = "https://api.dedust.io/v2";

#[derive(Clone)]
#[non_exhaustive]
pub struct DedustApiClient {
executor: Arc<Executor>,
pub v2: V2ApiClient,
}

impl DedustApiClient {
pub fn builder() -> Builder { Builder::new() }

pub async fn exec_api_v2<REQUEST>(&self, request: REQUEST) -> ApiClientsResult<V2Response>
where
REQUEST: Into<V2Request>,
{
let request = request.into();
let response = match &request {
V2Request::Assets => V2Response::Assets(self.executor.exec_get("assets").await?),
V2Request::Pools => V2Response::Pools(self.executor.exec_get("pools").await?),
V2Request::PoolsLite => V2Response::PoolsLite(self.executor.exec_get("pools-lite").await?),
V2Request::PoolTrades(pool_addr) => {
let path = format!("pools/{}/trades", pool_addr);
V2Response::PoolTrades(self.executor.exec_get(&path).await?)
}
V2Request::RoutingPlan(params) => {
V2Response::RoutingPlan(self.executor.exec_post_body("routing/plan", params, &[]).await?)
}
};
Ok(response)
}
}
4 changes: 3 additions & 1 deletion dedust_api_client/src/api_client/builder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::api_client::{DedustApiClient, DEFAULT_API_V2_URL};
use crate::v2::V2ApiClient;
use api_clients_core::{ApiClientsResult, Executor};
use derive_setters::Setters;
use std::sync::Arc;
Expand All @@ -25,6 +26,7 @@ impl Builder {
None => Executor::builder(self.api_url).build()?.into(),
};

Ok(DedustApiClient { executor })
let v2 = V2ApiClient::new(executor);
Ok(DedustApiClient { v2 })
}
}
7 changes: 0 additions & 7 deletions dedust_api_client/src/api_v2.rs

This file was deleted.

2 changes: 1 addition & 1 deletion dedust_api_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

pub use api_clients_core; // re-export
pub mod api_client;
pub mod api_v2;
pub mod v2;
Loading
Loading