Skip to content

Foundry UI #3392

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

Open
wants to merge 43 commits into
base: master
Choose a base branch
from
Open

Foundry UI #3392

wants to merge 43 commits into from

Conversation

franciszekjob
Copy link
Contributor

@franciszekjob franciszekjob commented May 22, 2025

Towards #3022

Stack:

Introduced changes

  • Introduce foundry-ui crate
    • Add UI, Message

Checklist

  • Linked relevant issue
  • Updated relevant documentation
  • Added relevant tests
  • Performed self-review of the code
  • Added changes to CHANGELOG.md

This was referenced May 22, 2025
@franciszekjob franciszekjob marked this pull request as ready for review May 22, 2025 23:58
@franciszekjob franciszekjob requested a review from a team as a code owner May 22, 2025 23:58
Copy link
Member

@cptartur cptartur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good I think, left some comments about possible improvements

Comment on lines +13 to +20
#[must_use]
pub fn from_flag(json: bool) -> Self {
if json {
OutputFormat::Json
} else {
OutputFormat::Human
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd just allow creating it by passing an enum and force the caller to deal with that. from_flag seems bit weird.

Copy link
Contributor Author

@franciszekjob franciszekjob May 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this enum was previously in print.rs, I've just moved it. from_flag corresponds to --json flag so I think it actually makes sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd still prefer we remove that

Copy link
Contributor Author

@franciszekjob franciszekjob May 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any specific reason? I don't really see a point to remove it - it's the simplest way to create it based on bool:

let output_format = OutputFormat::from_flag(cli.json);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's unnecessary indirection for something this simple. The usage you posted is obvious but from other places, it's not obvious at all what from_flag method does since arguments name aren't easily visible to the caller.

Why not

let output_format = if cli.json {
  OutputFormat::Json
} else {
  OutputFormat::Human
};

at the caller site? We aren't saving any code from doing this.

Copy link
Contributor Author

@franciszekjob franciszekjob May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not at the caller site?

Imo it's not a good practice to write ifs logic directly at the level of main function, even if they are simple.

Let's follow your reasoning and assume we introduce other simple flags in future.

fn main() -> Result<()> {
    let cli = Cli::parse();

    let numbers_format = NumbersFormat::from_flags(cli.hex_format, cli.int_format);

    let output_format = if cli.json {
        OutputFormat::Json
    } else {
        OutputFormat::Human
    };

    let other_value = cli.other_flag {
        ...
    } else {
        ...
    }
   
   ...
}

Then our main function would get cluttered over the time.

I'm open for discussion but the arguments you've provided don't really convince me 😅

@ksew1 @kkawula wdyt about this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the solution that @franciszekjob proposed, it takes away logic from main function, let's stick to it

@franciszekjob franciszekjob requested a review from cptartur May 25, 2025 16:55
@franciszekjob franciszekjob mentioned this pull request May 26, 2025
5 tasks
Copy link
Member

@cptartur cptartur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Responded to comments

@@ -154,6 +127,14 @@ impl From<Value> for OutputData {
}
}

impl<T: CommandResponse + Serialize> From<&T> for OutputData {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CommandResponse + Serialize = CommandResponse
Is it correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's used in text and json in SncastMessage

@@ -63,6 +64,7 @@ pub async fn deploy(
keystore_path_,
)
.await
.map(Into::into)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we get rid of this map somehow?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that now we return a separate type AccountDeployResponse which is constructed from InvokeResponse, hence the usage of into.

@franciszekjob franciszekjob requested a review from kkawula May 27, 2025 09:59

/// Get the output format of this [`UI`] instance.
#[must_use]
pub fn output_format(&self) -> OutputFormat {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like how we are using this. We are doing ui.output_format() -> format message according to output_format -> print message. The flow should be i give message to ui and it knows how to format it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the idea is you create a UI instance per output format (json or human readable) and you pass it through the application. To me it makes sense that the UI has a (immutable) state like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ksew1 you're right, output format should be held only in UI instance and not exposed - note this will be removed in #3396, which focuses on refactoring print-related code in cast 😄

Comment on lines +13 to +20
#[must_use]
pub fn from_flag(json: bool) -> Self {
if json {
OutputFormat::Json
} else {
OutputFormat::Human
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's unnecessary indirection for something this simple. The usage you posted is obvious but from other places, it's not obvious at all what from_flag method does since arguments name aren't easily visible to the caller.

Why not

let output_format = if cli.json {
  OutputFormat::Json
} else {
  OutputFormat::Human
};

at the caller site? We aren't saving any code from doing this.


/// Get the output format of this [`UI`] instance.
#[must_use]
pub fn output_format(&self) -> OutputFormat {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the idea is you create a UI instance per output format (json or human readable) and you pass it through the application. To me it makes sense that the UI has a (immutable) state like this.

Comment on lines 21 to 31
impl Message for &str {
fn text(&self) -> String {
(*self).to_string()
}
}

impl Message for String {
fn text(&self) -> String {
self.to_string()
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does that play with the default conversion to json? The user will just get the encoded string?

Not sure how we can enforce this on the type level (or if), but for json output maybe it makes sense to return objects always?

What I mean is that maybe there is a reason for the output to always be of the format

SomeObject { 
  "key1": "value1"
}

and with these default impls the user will get just "string text".

cc @kkawula @ksew1

Copy link
Contributor Author

@franciszekjob franciszekjob May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I mean is that maybe there is a reason for the output to always be of the format

You mean that json should be pretty-printed? If so, I don't think we really need to introduce this. Keep in mind that we've been always returning outputs in json format not pretty printed, so I don't see the reason to change it.

};

if let Ok(provider) = explorer.unwrap_or_default().as_provider(network) {
response.print_links(provider);
return Some(response.print_links(provider));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the method should be renamed. It doesn't seem like it is printing anything anymore

Comment on lines +27 to +35
#[derive(Debug)]
pub struct UI {
output_format: OutputFormat,
// TODO(3395): Add state here, that can be used for spinner
}

impl UI {
/// Create a new [`UI`] instance configured with the given output format.
#[must_use]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think of it, we should probably define a trait for UI and implement it here.

So you can type our function as accepting the UI trait, not the specific implementation of it.
The benefit will be you will be able to mock the UI in tests or provide an implementation that allows us to just capture what is printed so we can assert it (e.g. for tests that show warnings about snforge_std).

cc @kkawula @ksew1

@franciszekjob franciszekjob requested a review from ksew1 May 27, 2025 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants