Skip to content
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 shield for display API #374

Merged
merged 17 commits into from
Feb 11, 2025
Merged

Conversation

giannis-rdx
Copy link
Contributor

@giannis-rdx giannis-rdx commented Feb 6, 2025

This PR is the last part of the ABW-4146.

It adds an API to

  • get entities linked to a given security structure
  • get shields for display

@giannis-rdx giannis-rdx self-assigned this Feb 6, 2025
@giannis-rdx giannis-rdx changed the title Ass shield for display API Add shield for display API Feb 6, 2025
Copy link

codecov bot commented Feb 6, 2025

Codecov Report

Attention: Patch coverage is 97.50000% with 2 lines in your changes missing coverage. Please review.

Project coverage is 92.68%. Comparing base (059e84b) to head (9945b92).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...ferences/security_structures/shield_for_display.rs 89.47% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #374      +/-   ##
==========================================
+ Coverage   92.66%   92.68%   +0.02%     
==========================================
  Files        1187     1190       +3     
  Lines       26781    26861      +80     
  Branches       85       85              
==========================================
+ Hits        24816    24896      +80     
  Misses       1954     1954              
  Partials       11       11              
Flag Coverage Δ
kotlin 98.64% <ø> (ø)
rust 92.32% <97.50%> (+0.02%) ⬆️
swift 92.66% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

&self,
shield_id: SecurityStructureID,
) -> Result<EntitiesLinkedToSecurityStructure> {
let accounts = self
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: does this work:

fn filter<E>(entity: &E) -> bool where E:  HasSecurityState { entity.is_linked_to_security_structure(shield_id) }

and then .filter(filter) in the four places below.

or does compiler complain about shield_id being captured?

can you do `let filter = |entity: &(impl HasSecurityState)| { .. }; ? or does that not work?

mostly curiuos...

Copy link
Contributor

Choose a reason for hiding this comment

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

this might help as guide

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I saw it and indeed it works, but I didn't see any value, that's why I passed.

If you want I can add it.

Copy link
Contributor

Choose a reason for hiding this comment

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

DRY

But only truly helps IMO if one can pass .filter(filter) which is possible in Swift, but trickier in Rust since the fn or closure captures the shield_id variable

{
return provisional_security_structure
.security_structure_id
== shield_id;
Copy link
Contributor

Choose a reason for hiding this comment

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

undefined behaviour here... if the provisional is different, then we return false here, even though the actual ("committed") is linked. So you need to doc this method what is it suppose to do? what does linked mean? I think linked means "provisionally_referenced_or_de_facto_in_use" ?

Copy link
Contributor

@Sajjon Sajjon Feb 6, 2025

Choose a reason for hiding this comment

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

I would actually split this into three methods:

trait HasSecurityState {
fn is_currently_or_provisionally_securified_with(&self, shield_id: SecurityStructureID) {
    self.is_currently_securified_with(shield_id) || self.is_provisionally_securified_with(shield_id)
}

fn is_currently_securified_with(&self, shield_id: SecurityStructureID) {
    self.security_state().as_securified().map(|s| s.shield_id == shield_id).unwrap_or(false)
}

fn is_provisionally_securified_with(&self, shield_id: SecurityStructureID) {
    self.provisional_security_config.map(|s| s.shield_id() == shield_id).unwrap_or(false)
}
}
impl ProvisionalSecurityConfig {
    fn shield_id(&self) -> bool {
       match self { ... }
    }
}

Copy link
Contributor

Choose a reason for hiding this comment

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

@giannis-rdx furthermore, this should be in profile-logic crate. All Profile logic goes into profile-logic crate :)

Copy link
Contributor

Choose a reason for hiding this comment

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

if the provisional is different, then we return false here, even though the actual ("committed") is linked

That is the behaviour we want, or at least agreed to - if an account has a provisional shield set, then that is shown throughout the app. What you are proposing implies showing users that an account is linked to two shields at the same time, no?

Copy link
Contributor

Choose a reason for hiding this comment

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

@GhenadieVP my point is that is_linked is too broad a term. so the method should be renamed and it should it should be documented.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated

{
return provisional_security_structure
.security_structure_id
== shield_id;
Copy link
Contributor

@CyonAlexRDX CyonAlexRDX Feb 6, 2025

Choose a reason for hiding this comment

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

undefined behaviour here... if the provisional is different, then we return false here, even though the actual ("committed") is linked. So you need to doc this method what is it suppose to do? what does linked mean? I think linked means "provisionally_referenced_or_de_facto_in_use" ? if so, you should provisional_security_structure .security_structure_id == shield_id || value.security_structure.security_structure_id == shield_id

#[display("{}", self.metadata.display_name)]
pub struct ShieldForDisplay {
pub metadata: SecurityStructureMetadata,
pub number_of_linked_accounts: usize,
Copy link
Contributor

Choose a reason for hiding this comment

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

this can probably be put in profile-logic crate. it is not a model used by the Profile.

Copy link
Contributor Author

@giannis-rdx giannis-rdx Feb 7, 2025

Choose a reason for hiding this comment

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

I agree with you on that
However, the reasons I placed it there are as follows:

  1. there is already an existing account_for_display which also is not used by the Profile. For consistency, I added shield_for_display here.
  2. I am not sure where exactly I should place it in the profile-logic crate. We do not have a model directory there... Oops! model ≠ logic :/

Copy link
Contributor

Choose a reason for hiding this comment

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

account_for_display should be moved to profile-logic too. :) The "logic" in profile-logic does not mean "cannot contain structs or enums". It means "here is code that DOES stuff using models from Profile models crate OR using models declared in this crate". Where as profile model crates should as much as possible just be the Serde impl of Profile model. Without logic. Why? Well since we might need to migrate v100 Profile to v101 Profile someday and then we need to copy paste all profile models and do some trait IntoLatest and impl that for v100 models. But we should not duplicate logic, neither should the logic operate on v100 models then, only v101 models.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok where do you think we should place it in profile-logic?
under app_preferences?
or create a new directory security_structures and place it there?

Copy link
Contributor

@Sajjon Sajjon Feb 7, 2025

Choose a reason for hiding this comment

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

either is fine :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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


/// This is the result of checking what entities are linked to a given `SecurityStructure`.
#[derive(Clone, Debug, PartialEq)]
pub struct EntitiesLinkedToSecurityStructure {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not Profile model so move to profile-logic crate I think.

Copy link
Contributor

@Sajjon Sajjon Feb 6, 2025

Choose a reason for hiding this comment

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

Furthermore, this model does not contain which Shield ID, which looks like an oversight! If you include it the code later can be more functional.

If you include the SecurityStructureMetadata then you can do more functional stuff below:

https://github.com/radixdlt/sargon/pull/374/files#r1945169455

Copy link
Contributor Author

@giannis-rdx giannis-rdx Feb 7, 2025

Choose a reason for hiding this comment

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

Not Profile model so move to profile-logic crate I think.

now that i see this then better to have a security_structures in profile-logic crate and place the EntitiesLinkedToSecurityStructure and ShieldForDisplay there.

right?

Copy link
Contributor

Choose a reason for hiding this comment

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

sure

Copy link
Contributor Author

@giannis-rdx giannis-rdx Feb 7, 2025

Choose a reason for hiding this comment

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

Not Profile model so move to profile-logic crate I think.

done

Copy link
Contributor Author

@giannis-rdx giannis-rdx Feb 9, 2025

Choose a reason for hiding this comment

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

Furthermore, this model does not contain which Shield ID, which looks like an oversight! If you include it the code later can be more functional.
If you include the SecurityStructureMetadata ...

if I include the SecurityStructureMetadata in the EntitiesLinkedToSecurityStructure, I need:

  • either update the API
pub async fn entities_linked_to_security_structure(
        &self,
        metadata: SecurityStructureMetadata, // from shield_id: SecurityStructureID
        profile_to_check: ProfileToCheck,
    )
// so I can pass the metadata all the way down to build the `EntitiesLinkedToSecurityStructure` struct.
  • or leave the API as it is with shield_id: SecurityStructureID and internally fetch the SecurityStructureMetadata.

I think the latter is more appropriate.
what do you think @CyonAlexRDX ?

edit: done

)
.await?;

shields_for_display.insert(ShieldForDisplay {
Copy link
Contributor

@Sajjon Sajjon Feb 6, 2025

Choose a reason for hiding this comment

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

Should use constructor ShieldForDisplay::new also prefer functional:

let security_structures = self.security_structures_of_factor_source_ids()?;
let shields_for_display = security_structures
    .iter()
    .map(|shield| shield.metadata) 
    .map(|metadata| self.entities_linked_to_security_structure(metadata, ProfileToCheck::Current))
    .map(ShieldForDisplay::new)
    .collect::<ShieldsForDisplay>();

Ok(shields_for_display)

which I think is much more clean

Copy link
Contributor Author

Choose a reason for hiding this comment

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

although I updated the EntitiesLinkedToSecurityStructure struct by adding the metadata: SecurityStructureMetadata, this functional code doesn't work because map doesn't support async

Unless there is another way that I am not aware of.

Copy link
Contributor

Choose a reason for hiding this comment

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

@giannis-rdx you should be able to use futures::future::join_all, so:

let security_structures = self.security_structures_of_factor_source_ids()?;
let shields_for_display = security_structures
    .iter()
    .map(|shield| shield.metadata) 
    .map(|metadata| self.entities_linked_to_security_structure(metadata, ProfileToCheck::Current))
    .map(ShieldForDisplay::new)

let shields_for_display = futures::future::join_all(shields_for_display)
     .await
    .collect::<ShieldsForDisplay>();

Ok(shields_for_display)

I think that should work.

See join_all, which does preserve order:

The returned future will drive execution for all of its underlying futures, collecting the results into a destination Vec in the same order as they were provided.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no it doesn't work. I get a couple of errors:

function is expected to take 1 argument, but it takes 5 arguments
.map(ShieldForDisplay::new);

Vec<_> is not an iterator
   --> crates/system/os/factors/src/sargon_os_security_structures.rs:262:14
    |
260 |           let shields_for_display = futures::future::join_all(shields_for_display)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the only way to make it work is this:

let security_structures = self.security_structures_of_factor_source_ids()?;

        let shields_for_display_futures = security_structures.iter().map(|security_structure| {
                let metadata = security_structure.metadata.clone();

                async {
                    let linked_entities = self
                        .entities_linked_to_security_structure(
                            metadata.id,
                            ProfileToCheck::Current,
                        )
                        .await?;

                    Ok(ShieldForDisplay::new(
                        metadata,
                        linked_entities.accounts.len(),
                        linked_entities.hidden_accounts.len(),
                        linked_entities.personas.len(),
                        linked_entities.hidden_personas.len(),
                    ))
                }
            });

        let shields_for_display =
            futures::future::join_all(shields_for_display_futures)
                .await
                .into_iter()
                .collect::<Result<ShieldsForDisplay>>()?;

        Ok(shields_for_display)
    }

which is quite ugly.

Copy link
Contributor Author

@giannis-rdx giannis-rdx Feb 10, 2025

Choose a reason for hiding this comment

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

I think I will stick to my original solution. Way simpler and clean.

Copy link
Contributor

Choose a reason for hiding this comment

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

What I mean by ShieldForDisplay::new if ofc a constructor which takes a EntitiesLinkedToSecurityStructure... maybe fn with_linked and use .map(ShieldForDisplay::with_linked) :)

Copy link
Contributor

@Sajjon Sajjon left a comment

Choose a reason for hiding this comment

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

Great first start! Some crate admin needed. Logic into logic :)

@giannis-rdx giannis-rdx added Rust 🦀 Changes in Rust Sargon UniFFI 🦄 Changes in UniFFI exported APIs labels Feb 10, 2025
Copy link
Contributor

@CyonAlexRDX CyonAlexRDX left a comment

Choose a reason for hiding this comment

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

LGTM! Hope join_all works as I thought, else lets skip it.

@@ -32,3 +32,4 @@ preinterpret = { workspace = true }
pretty_assertions = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
futures = "0.3.31"
Copy link
Contributor

Choose a reason for hiding this comment

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

Use workspace, always

@giannis-rdx giannis-rdx force-pushed the gt/add-shield-for-display-api-abw4146 branch from 1c50e93 to 829526a Compare February 11, 2025 08:37
Copy link
Contributor

@CyonAlexRDX CyonAlexRDX left a comment

Choose a reason for hiding this comment

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

Good job

@giannis-rdx giannis-rdx merged commit 8b3f5c7 into main Feb 11, 2025
13 checks passed
@giannis-rdx giannis-rdx deleted the gt/add-shield-for-display-api-abw4146 branch February 11, 2025 09:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Rust 🦀 Changes in Rust Sargon UniFFI 🦄 Changes in UniFFI exported APIs
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants