Skip to content

Commit

Permalink
Add single offer endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin-pease committed Mar 11, 2024
1 parent 70b2e1c commit 62b550a
Show file tree
Hide file tree
Showing 5 changed files with 398 additions and 0 deletions.
55 changes: 55 additions & 0 deletions src/horizon_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::{
prelude::{Ledger, LedgersRequest, LedgersResponse, SingleLedgerRequest},
single_ledger_request::Sequence,
},
offers::prelude::*,
models::{Request, Response},
};
use reqwest;
Expand Down Expand Up @@ -625,6 +626,60 @@ impl HorizonClient {
self.get::<EffectsResponse>(request).await
}

/// Retrieves detailed information for a specific offer from the Horizon server.
///
/// This asynchronous method fetches details of a single offer from the Horizon server.
/// It requires a [`SingleOfferRequest`] parameterized with `OfferId`, which includes the ID
/// of the offer to be retrieved.
///
/// Adheres to the <a href="https://developers.stellar.org/network/horizon/resources/get-offer-by-offer-id">Retrieve An Offer endpoint</a>
/// endpoint.
///
/// # Arguments
///
/// * `request` - A reference to a [`SingleOfferRequest<OfferId>`] instance, containing the
/// id of the offer for which details are to be fetched.
///
/// # Returns
///
/// Returns a `Result` containing an [`Offer`], which includes detailed
/// information about the requested offer. If the request fails, it returns an error
/// encapsulated within `Result`.
///
/// # Usage
/// To use this method, create an instance of [`SingleOfferRequest`] and set the
/// id of the offer to be queried.
///
/// ```
/// # use stellar_rs::offers::prelude::*;
/// # use stellar_rs::models::Request;
/// # use stellar_rs::horizon_client::HorizonClient;
/// #
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// # let base_url = "https://horizon-testnet.stellar.org".to_string();
/// # let horizon_client = HorizonClient::new(base_url)
/// # .expect("Failed to create Horizon Client");
/// let request = SingleOfferRequest::new()
/// .set_offer_id("1".to_string()) // example offer ID
/// .unwrap();
///
/// let response = horizon_client.get_single_offer(&request).await;
///
/// if let Ok(offer) = response {
/// println!("Offer ID: {}", offer.id());
/// // Additional processing...
/// }
/// # Ok({})
/// # }
/// ```
///
pub async fn get_single_offer(
&self,
request: &SingleOfferRequest<OfferId>,
) -> Result<SingleOfferResponse, String> {
self.get::<SingleOfferResponse>(request).await
}

/// Sends a GET request to the Horizon server and retrieves a specified response type.
///
/// This internal asynchronous method is designed to handle various GET requests to the
Expand Down
42 changes: 42 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,48 @@ pub mod ledgers;
///
pub mod effects;

/// Provides `Request` and `Response` structs for retrieving offers.
///
/// This module provides a set of specialized request and response structures designed for
/// interacting with the offer-related endpoints of the Horizon server. These structures
/// facilitate the construction of requests to query offer data and the interpretation of
/// the corresponding responses.
///
/// # Usage
///
/// This module is intended to be used in conjunction with the [`HorizonClient`](crate::horizon_client::HorizonClient)
/// for making specific offer-related API calls to the Horizon server. The request
/// structures are designed to be passed to the client's methods, which handle the
/// communication with the server and return the corresponding response structures.
///
/// # Example
///
/// /// To use this module, you can create an instance of a request struct, such as
/// `SingleOfferRequest`, set any desired query parameters, and pass the request to the
/// `HorizonClient`. The client will then execute the request and return the corresponding
/// response struct, like `SingleOfferResponse`.
///
/// ```rust
/// use stellar_rs::horizon_client::HorizonClient;
/// use stellar_rs::offers::prelude::*;
/// use stellar_rs::models::Request;
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let horizon_client = HorizonClient::new("https://horizon-testnet.stellar.org".to_string())?;
///
/// // Example: Fetching all effects
/// let single_offer_request = SingleOfferRequest::new()
/// .set_offer_id("1".to_string())
/// .unwrap();
/// let single_offer_response = horizon_client.get_single_offer(&single_offer_request).await?;
///
/// // Process the responses...
/// # Ok(())
/// # }
/// ```
///
pub mod offers;

/// Contains core data structures and traits.
///
/// This module is used by the Stellar Rust SDK to interact with the Horizon API.
Expand Down
121 changes: 121 additions & 0 deletions src/offers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/// Provides the `SingleOfferRequest`.
///
/// This module provides the `SingleOfferRequest` struct, specifically designed for
/// constructing requests to query information about a single offer from the Horizon
/// server. It is tailored for use with the [`HorizonClient::get_single_offer`](crate::horizon_client::HorizonClient::get_single_offer)
/// method.
///
pub mod single_offer_request;

/// Provides the `SingleOfferResponse`.
///
/// This module defines structures representing the response from the Horizon API when querying
/// for a single offer. The structures are designed to deserialize the JSON response into Rust
/// objects, enabling straightforward access to various details of a single Stellar account.
///
/// These structures are equipped with serialization capabilities to handle the JSON data from the
/// Horizon server and with getter methods for easy field access.
///
pub mod response;

/// The base path for offer-related endpoints in the Horizon API.
///
/// # Usage
/// This variable is intended to be used internally by the request-building logic
/// to ensure consistent and accurate path construction for offer-related API calls.
///
static OFFERS_PATH: &str = "offers";

/// The `prelude` module of the `offers` module.
///
/// This module serves as a convenience for users of the Horizon Rust SDK, allowing for easy and
/// ergonomic import of the most commonly used items across various modules. It re-exports
/// key structs and traits from the sibling modules, simplifying access to these components
/// when using the library.
///
/// By importing the contents of `prelude`, users can conveniently access the primary
/// functionalities of the offer-related modules without needing to import each item
/// individually.
///
/// # Contents
///
/// The `prelude` includes the following re-exports:
///
/// * From `single_offer_request`: All items (e.g. `SingleOfferRequest`).
/// * From `response`: All items (e.g. `SingleOfferResponse`, `PriceR`, etc.).
///
/// # Example
/// ```
/// # use crate::stellar_rs::models::*;
/// // Import the contents of the offers prelude
/// use stellar_rs::offers::prelude::*;
///
/// // Now you can directly use SingleOfferRequest, SingleOfferResponse, etc.
/// let single_offer_request = SingleOfferRequest::new();
/// ```
///
pub mod prelude {
pub use super::single_offer_request::*;
pub use super::response::*;
}

#[cfg(test)]
pub mod test {
use super::prelude::*;
use crate::horizon_client::HorizonClient;

#[tokio::test]
async fn test_get_single_offer() {
const LINK_SELF: &str = "https://horizon-testnet.stellar.org/offers/1";
const LINK_OFFER_MAKER: &str = "https://horizon-testnet.stellar.org/accounts/GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5";
const OFFER_ID: &str = "1";
const PAGING_TOKEN: &str = "1";
const SELLER: &str = "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5";
const SELLING_ASSET_TYPE: &str = "credit_alphanum4";
const SELLING_ASSET_CODE: &str = "USDC";
const SELLING_ASSET_ISSUER: &str = "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5";
const BUYING_ASSET_TYPE: &str = "credit_alphanum12";
const BUYING_ASSET_CODE: &str = "USDCAllow";
const BUYING_ASSET_ISSUER: &str = "GAWZGWFOURKXZ4XYXBGFADZM4QIG6BJNM74XIZCEIU3BHM62RN2MDEZN";
const AMOUNT: &str = "914187974680.9075807";
const PRICE_R_N: &u32 = &1;
const PRICE_R_D: &u32 = &1;
const PRICE: &str = "1.0000000";
const LAST_MODIFIED_LEDGER: &u32 = &364472;
const LAST_MODIFIED_TIME: &str = "2024-02-28T21:39:09Z";

let horizon_client =
HorizonClient::new("https://horizon-testnet.stellar.org"
.to_string())
.unwrap();

let single_offer_request =
SingleOfferRequest::new()
.set_offer_id(OFFER_ID.to_string())
.unwrap();

let single_offer_response = horizon_client
.get_single_offer(&single_offer_request)
.await;

assert!(single_offer_response.clone().is_ok());
let response = single_offer_response.unwrap();
assert_eq!(response.links().self_link().href().as_ref().unwrap(), LINK_SELF);
assert_eq!(response.links().offer_maker().href().as_ref().unwrap(), LINK_OFFER_MAKER);
assert_eq!(response.id(), OFFER_ID);
assert_eq!(response.paging_token(), PAGING_TOKEN);
assert_eq!(response.seller(), SELLER);
assert_eq!(response.selling().asset_type(), SELLING_ASSET_TYPE);
assert_eq!(response.selling().asset_code().as_ref().unwrap(), SELLING_ASSET_CODE);
assert_eq!(response.selling().asset_issuer().as_ref().unwrap(), SELLING_ASSET_ISSUER);
assert_eq!(response.buying().asset_type(), BUYING_ASSET_TYPE);
assert_eq!(response.buying().asset_code().as_ref().unwrap(), BUYING_ASSET_CODE);
assert_eq!(response.buying().asset_issuer().as_ref().unwrap(), BUYING_ASSET_ISSUER);
assert_eq!(response.amount(), AMOUNT);
assert_eq!(response.price_r().n(), PRICE_R_N);
assert_eq!(response.price_r().d(), PRICE_R_D);
assert_eq!(response.price(), PRICE);
assert_eq!(response.last_modified_ledger(), LAST_MODIFIED_LEDGER);
assert_eq!(response.last_modified_time(), LAST_MODIFIED_TIME);
}
}
86 changes: 86 additions & 0 deletions src/offers/response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use crate::models::prelude::*;
use derive_getters::Getters;
use serde::{Deserialize, Serialize};

/// Represents the asset to buy or to sell.
///
/// This struct details information about the asset to buy or to sell, including its type,
/// code (optional) and issuer (optional).
///
#[derive(Debug, Deserialize, Clone, Getters)]
pub struct Transaction {
/// The type of asset (e.g. "credit_alphanum4", "credit_alphanum12").
asset_type: String,
/// Optional. The code of the asset.
asset_code: Option<String>,
/// Optional. The public key of the issuer.
asset_issuer: Option<String>,
}

/// Represents the precise buy and sell price of the assets on offer.
///
/// This struct contains a numenator and a denominator, so that the price can be determined
/// in a precise manner.
///
#[derive(Debug, Deserialize, Clone, Getters)]
pub struct PriceR {
/// The numenator.
n: u32,
/// The denominator.
d: u32,
}

/// Represents the navigational links in a single offer response from the Horizon API.
///
/// This struct includes various hyperlinks such as links to the offer itself
/// and the offer maker.
///
#[derive(Debug, Deserialize, Serialize, Clone, Getters)]
pub struct OfferResponseLinks {
/// The link to the offer itself.
#[serde(rename = "self")]
self_link: Link,
/// Link to the offer's maker.
offer_maker: Link,
}

/// Represents the response for a single offer query in the Horizon API.
///
/// This struct defines the overall structure of the response for a single offer query.
/// It includes navigational links, offer identifiers, the seller, the assets to buy and sell,
/// the amount, the price and additional data.
///
#[derive(Debug, Deserialize, Clone, Getters)]
pub struct SingleOfferResponse {
/// Navigational links related to the offer.
#[serde(rename = "_links")]
links: OfferResponseLinks,
/// The unique identifier for the offer.
id: String,
/// A token used for paging through results.
paging_token: String,
/// The ID of the offer making the offer.
seller: String,
/// The asset the offer wants to sell.
selling: Transaction,
/// The asset the offer wants to buy.
buying: Transaction,
/// The amount of `selling` that the account making this offer is willing to sell.
amount: String,
/// A precise representation of the buy and sell price of the assets on offer.
price_r: PriceR,
/// A number representing the decimal form of `price_r`.
price: String,
/// The sequence number of the last ledger in which the offer was modified.
last_modified_ledger: u32,
/// The time at which the offer was last modified.
last_modified_time: String,
/// The account ID of the sponsor who is paying the reserves for this offer.
sponsor: Option<String>,
}

impl Response for SingleOfferResponse {
fn from_json(json: String) -> Result<Self, String> {
serde_json::from_str(&json).map_err(|e| e.to_string())
}
}
Loading

0 comments on commit 62b550a

Please sign in to comment.