-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add path payment (find path) endpoint to cli #215
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking pretty good, let's integrate a few of the other things we've built to DRY up the PR
cli/src/find_path.rs
Outdated
source_account, | ||
destination_account, | ||
destination_asset, | ||
Amount::new(destination_amount.parse::<i64>().unwrap() * 10000000), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can understand why this doesn't make you feel good. We ultimately decided that because stellar works with stroops and displays units scaled down by 10^7 to its users, we would follow the same convention. https://www.stellar.org/developers/guides/concepts/assets.html#amount-precision-and-representation .
I would recommend adding underscores to that big number where a comma would typically go i.e. 10_000_000
cli/src/main.rs
Outdated
.takes_value(true) | ||
.help("The code of the destination asset"), | ||
) | ||
.arg( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've added a new asset_identifier struct to the CLI https://github.com/kbacha/stellar-sdk/blob/master/cli/src/asset_identifier.rs and we can now shorten the asset inputs like so https://github.com/kbacha/stellar-sdk/blob/master/cli/src/main.rs#L175-L180
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok sweet, noted. Got so wrapped up in work and that memo PR from last week I missed some of these 🔥 changes.
cli/src/fmt/simple/payment_path.rs
Outdated
); | ||
append_to_buffer!(buf, "PATH:"); | ||
for asset in payment_path.path().iter() { | ||
append_to_buffer!(buf, " Path asset code: {}", asset.code()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you have some leading spaces here to indent the path attributes. I can't wait to have @alexanderreiff 's pr merged so you can utilize all the awesome tooling he's putting out around this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dude pumped here too. I'll leave this here for now.
cli/src/find_path.rs
Outdated
|
||
if !&iter.peek().is_some() { | ||
println!("No results found. Please try again.") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that printing nothing on a get that has no results is fine, but happy to defer to kevin here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, Ill go with nothing -- will allow me to get rid of this weird peekable stuff.
Thanks for the review Scott. I say we put this on hold until after @alexanderreiff 's PR is merged so that I can reformat/adjust my indentation for the payment paths. @correlator one thing I noticed is, while the assetidentifier CLI is 🔥 , it requires you to do "XLM" -- it is case sensitive. Would y'all be open to matching any casing aka "xlm" or "XLM"? I am sure the answer is probably -- yeah sure go fix it 😄 but just wanted to hear your thoughts in case you think differently. |
@robertDurst the indent/nest stuff is finally merged! The macro names are now simplified slightly at @kbacha's suggestion: https://github.com/kbacha/stellar-sdk/blob/37764c033a75cb1bb1054e52f26e90daa5045d7d/cli/src/fmt/simple/mod.rs#L2-L23 |
Awesome! Updated for @alexanderreiff 's sweet formatting improvement. |
cli/src/fmt/simple/payment_path.rs
Outdated
append!( | ||
buf, | ||
"Destination asset code: {}", | ||
payment_path.destination_asset().code() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a Render
implementation for AssetIdentifier
now that gives the "standard" 1 liner: XLM
or <code>-<issuer>
. You could call self.render(payment_path.destination_asset()).unwrap()
and not have to append each part individually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch! Updated
cli/src/fmt/simple/payment_path.rs
Outdated
); | ||
append!( | ||
buf, | ||
"Source asset: {}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that we need so much space between label and value here.
cli/src/main.rs
Outdated
.help("The source account id of the payment path to query"), | ||
) | ||
.arg( | ||
Arg::with_name("destination_asset") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's only one asset that needs to be specified. It might make sense to label the action asset
instead of making it longer.
In fact, you may think about how we can make this easy to use from the command line. Long argument names are a pain to type out and so it might make sense to make them shorter (the name of the arg can stay the same):
stellar find-path --from=<source account id> --to=<destination account id> --asset=XLM --amount=1.123456
cli/src/find_path.rs
Outdated
source_account, | ||
destination_account, | ||
destination_asset, | ||
Amount::new(destination_amount.parse::<i64>().unwrap() * 10_000_000), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parsing to an i64
will lose the decimals: 1.123
will be 1
, etc. We should try to capture what they mean exactly, similar to how we do it in the client library. In fact, it might make sense to even implement a FromStr
trait on the amount type so that we can handle the parsing cleanly (and without fidelity loss).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so this was fun, but a little wonky -- I noted in the code w/ comments where the wonkiness comes into play.
@kbacha thanks for the feedback. Made the appropriate changes. Let me know if there is anything else, or if my Also note, a trade_aggregations test started to fail so I just extended the start_time parameter to encompass a trade. |
cli/src/find_path.rs
Outdated
source_account, | ||
destination_account, | ||
destination_asset, | ||
Amount::from_str(destination_amount).unwrap(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I generally like to use expect
here instead so that you can provide a message. Of course, we should really be returning an Result from this path and allowing it to be handled by the outer logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this be more appropriate -- to parse and throw an error as part of the argument matching process? Although I admit the double expect does not seem right.
let destination_amount: Amount = matches
.value_of("amount")
.expect("Destination amount is a required field")
.parse()
.expect("Amount must be properly formatted");
This would then allow:
let endpoint = payment::FindPath::new(
source_account,
destination_account,
destination_asset,
destination_amount,
);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, that would be better I think. I've been recommending just returning an Error
so that exit is centralized, but it's a CLI so it's not the end of the world.
Eventually, centralized error reporting, would provide a consistently formatted error text though.
client/src/endpoint/payment.rs
Outdated
let params = wrap.params(); | ||
let destination_asset = AssetIdentifier::new( | ||
params.get_ok("destination_asset_type")?, | ||
params.get_parse("destination_asset_code").ok(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are just strings right? Do you need to get parse them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, true. Maybe I am missing something, but when I do:
params.get("destination_asset_code")
I get the following error:
note: expected type `std::option::Option<std::string::String>`
found type `std::option::Option<&str>`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, it wants it to be owned, so you are actually parsing it. Carry on, nothing to see here.
client/src/endpoint/payment.rs
Outdated
source_account: params.get_parse("source_account")?, | ||
destination_account: params.get_parse("destination_account")?, | ||
destination_asset: destination_asset, | ||
destination_amount: Amount::new(params.get_parse("destination_account")?), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically if Amount
implements the FromStr
trait, you should be able to parse to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, makes sense. However (noob question alert 😄) I get this error:
the trait std::convert::From<resources::amount::ParseIntError>
is not implemented for uri::Error
meaning the two error types are different. Here and in general, how do I deal with situations where a functions expected error type and an expressions error type differ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, that would mean that we are missing a conversion for our error type for amount into the result error type.
Also, I just noticed that you are parsing the account into an amount. That can't be right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 that is a valid point
client/src/resources/amount.rs
Outdated
/// Recreating ParseIntError since its interior is private | ||
/// https://doc.rust-lang.org/src/core/num/mod.rs.html#3949 | ||
#[derive(Debug)] | ||
pub enum ParseIntError { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better would be to use ParseAmountError
since that's what we are parsing.
client/src/resources/amount.rs
Outdated
impl From<num::ParseIntError> for ParseIntError { | ||
fn from(error: num::ParseIntError) -> Self { | ||
match error.to_string().as_ref() { | ||
"cannot parse integer from empty string" => ParseIntError::Empty, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These may change with different versions of rust so I wouldn't rely on them for matching if possible. If you want to convert I would actually just wrap the ParseIntError:
pub enum ParseAmountError {
IntError(ParseIntError),
}
Then the from implementation is easier:
impl From<num::ParseIntError> for ParseAmountError {
fn from(error: num::ParseIntError) -> ParseAmountError {
ParseAmountError::IntError(error)
}
}
If desired, you can implement Error
trait on this if you want to output the inner description. But It should capture the data you need.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes, thanks good point. I guess the reason I created the entire matching was because I was unable to figure out how to properly throw an error here:
if number_decimals > 7 {
Err(ParseIntError::Underflow)
}
The reason is that the ParseIntError's value is private so I couldn't instantiate/create a new ParseIntError. Any recommendations here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, sure, let's start by thinking about what Underflow
means. That's when we exceed an interger bit-size and end up at the lowest value. That's not actually what this condition means, it's what the condition is preventing. The condition is saying that we can never have more than 7 decimal places.
As such, it would be good to have that as a specific Amount error:
pub enum ParseAmountError {
ExceedsDecimalLength,
IntError(num::ParseIntError),
}
client/src/resources/amount.rs
Outdated
type Err = ParseIntError; | ||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
match s.rfind('.') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is copy-pasta from the deserialize impl, I think. It would be better to switch to using this implementation to implement the Deserialize function and map to the correct errors that it expects there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I admit I copy-pasta this. To clarify, are you saying I should keep FromStr and then utilize the FromStr implementation as part of deserialize, removing the code duplication, then mapping the errors of FromStr to appropriately match the Deserialize error type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, use FromStr within the deserialize and map the "specific" type errors to a deserialize custom error.
client/src/resources/amount.rs
Outdated
|
||
#[test] | ||
fn it_raises_amount_by_ten_million() { | ||
let amount: Amount = Amount::from_str("2.12").unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another way:
let amount: Amount = "2.12".parse().unwrap();
@kbacha thanks for the review and thanks for your patience! I am struggling more so with this PR than I thought I would -- although I am having a pretty good time working with traits and what not. |
@kbacha so I made all the above edits except I still don't use the
Unless I am mistaken, I could use Must say the deserialize looks pretty awesome: impl<'de> Deserialize<'de> for Amount {
fn deserialize<D>(d: D) -> Result<Amount, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(d)?;
let amount: Amount = Amount::from_str(&s).map_err(|e| match e {
ParseAmountError::ExceedsDecimalLength => {
de::Error::custom("Amount has too many digits of precision.")
}
ParseAmountError::IntError(err) => de::Error::custom(err.to_string()),
})?;
Ok(amount)
}
} Map error with a match is pretty nifty -- assuming this is correct use of map_err + match 😄 Once this has the green light I will squash |
This latest commit may have gone a bit deep down the rabbit hole 😅 Essentially, I needed to implement impl TryFromUri for FindPath {
fn try_from_wrap(wrap: &UriWrap) -> ::std::result::Result<FindPath, uri::Error> {
let params = wrap.params();
Ok(FindPath {
source_account: params.get_parse("from")?,
destination_account: params.get_parse("to")?,
destination_asset: params.get_parse("asset")?,
destination_amount: params.get_parse("amount")?,
})
}
} To do this however, I had to do a few things:
I think this all logically is sound, however I am not 100% sure this is the best approach. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like you are using a lot of strings for errors, please avoid that.
client/src/resources/amount.rs
Outdated
pub enum ParseAmountError { | ||
/// An error describing the case where the amount contains | ||
/// too many digits of precision to parse correctly | ||
ExceedsDecimalLength(String), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need a string inside this type. I think the type explains what's wrong well enough.
client/src/resources/amount.rs
Outdated
@@ -141,23 +142,82 @@ impl<'de> Deserialize<'de> for Amount { | |||
D: Deserializer<'de>, | |||
{ | |||
let s = String::deserialize(d)?; | |||
let amount: Amount = Amount::from_str(&s).map_err(|e| match e { | |||
ParseAmountError::ExceedsDecimalLength(_val) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hopefully we can remove the string inner value since it's ignored anyhow.
client/src/resources/asset.rs
Outdated
/// you get an error. | ||
#[derive(Debug)] | ||
pub struct ParseAssetError { | ||
details: String, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strings make for bad error typing. You should use an Enum with the possible errors coded into it. Especially as there is only one error possible.
client/src/resources/asset.rs
Outdated
} | ||
} | ||
|
||
impl FromStr for AssetIdentifier { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see we are adding this trait impl here but I don't see it removed from the CLI, is there a reason for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will hold off for a future PR -- I should probably create a new issue
client/src/resources/asset.rs
Outdated
/// When a bad token or string is provided to parsing into an asset | ||
/// you get an error. | ||
#[derive(Debug)] | ||
pub struct ParseAssetError { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This error type is confusing. We are parsing the asset identifier, not an asset itself, right? If so, this is named incorrectly.
client/src/endpoint/trade.rs
Outdated
@@ -359,7 +359,7 @@ mod all_trades_tests { | |||
/// | |||
/// // Place the start time in the past so we capture it. | |||
/// let agg = trade::Aggregations::new(base, counter) | |||
/// .with_start_time(now - 3_000_000_000) | |||
/// .with_start_time(now - 50_000_000_000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is always going to be falling out of style over time. Should we just start at 0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔
"type":"{}", | ||
"transaction_hash":"123" | ||
}}"#, | ||
"id":"1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you are on an old version of rust now, try updating so this doesn't change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh man... let me check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kbacha just ran cargo fmt --all w/ 1.26.2 installed and nothing changed. However, moved it back and ran fmt and still nothing changed 🤔 . Anyway just reformatted and pushed.
@kbacha thoughts? |
cli/src/main.rs
Outdated
.subcommand( | ||
SubCommand::with_name("find-path") | ||
.about("Fetch possible payment paths") | ||
.arg( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoa, why is this indentation odd?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol again, not caught (and at the same time not fixed) by cargo fmt
client/src/resources/amount.rs
Outdated
} | ||
} | ||
|
||
/// Recreating ParseIntError since its interior is private |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment should be changed to reflect changes.
@kbacha changes made! |
Once this is good to go, will rebase + squash |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm good with the changes, squash down the commits and it's ready to merge!!
Is there a GIF that reflects how this work made you feel?
A few things to especially note, ponder, and critique:
No results found. Please try again.
find-path
. Should this be an underscore or is a dash fine (I based this off of pub_net vs pub-net -- we use the dash for pub-net)?Closes #168