fn do_cmd_certify()

in src/main.rs [689:1084]


fn do_cmd_certify(
    out: &Arc<dyn Out>,
    cfg: &Config,
    sub_args: &CertifyArgs,
    store: &mut Store,
    network: Option<&Network>,
    last_fetch: Option<FetchCommand>,
) -> Result<(), CertifyError> {
    // Before setting up magic, we need to agree on a package
    let package = if let Some(package) = &sub_args.package {
        package.clone()
    } else if let Some(last_fetch) = &last_fetch {
        // If we just fetched a package, assume we want to certify it
        last_fetch.package().to_owned()
    } else {
        return Err(CertifyError::CouldntGuessPackage);
    };

    // FIXME: can/should we check if the version makes sense..?
    if !sub_args.force
        && !foreign_packages(&cfg.metadata, &store.config).any(|pkg| pkg.name == *package)
    {
        return Err(CertifyError::NotAPackage(package));
    }

    #[derive(Debug)]
    enum CertifyKind {
        Delta {
            from: VetVersion,
            to: VetVersion,
        },
        Full {
            version: VetVersion,
        },
        Wildcard {
            user_login: String,
            user_id: CratesUserId,
            start: chrono::NaiveDate,
            end: chrono::NaiveDate,
            set_renew_false: bool,
        },
    }

    let kind = if let Some(login) = &sub_args.wildcard {
        // Fetch publisher information for relevant versions of `package`.
        let publishers = store.ensure_publisher_versions(cfg, network, &package)?;
        let published_versions = publishers
            .iter()
            .filter(|publisher| &publisher.user_login == login);

        let earliest = published_versions
            .min_by_key(|p| p.when)
            .ok_or_else(|| CertifyError::NotAPublisher(login.to_owned(), package.to_owned()))?;

        // Get the from and to dates, defaulting to a from date of the earliest
        // published package by the user, and a to date of 12 months from today.
        let start = sub_args.start_date.unwrap_or(earliest.when);

        let max_end = cfg.today() + chrono::Months::new(12);
        let end = sub_args.end_date.unwrap_or(max_end);
        let set_renew_false = sub_args.end_date.is_some();
        if end > max_end {
            return Err(CertifyError::BadWildcardEndDate(end));
        }

        CertifyKind::Wildcard {
            user_login: earliest.user_login.to_owned(),
            user_id: earliest.user_id,
            start,
            end,
            set_renew_false,
        }
    } else if let Some(v1) = &sub_args.version1 {
        // If explicit versions were provided, use those
        if let Some(v2) = &sub_args.version2 {
            // This is a delta audit
            CertifyKind::Delta {
                from: v1.clone(),
                to: v2.clone(),
            }
        } else {
            // This is a full audit
            CertifyKind::Full {
                version: v1.clone(),
            }
        }
    } else if let Some(fetch) = last_fetch.filter(|f| f.package() == package) {
        // Otherwise, is we just fetched this package, use the version(s) we fetched
        match fetch {
            FetchCommand::Inspect { version, .. } => CertifyKind::Full { version },
            FetchCommand::Diff {
                version1, version2, ..
            } => CertifyKind::Delta {
                from: version1,
                to: version2,
            },
        }
    } else {
        return Err(CertifyError::CouldntGuessVersion(package));
    };

    let (username, who) = if sub_args.who.is_empty() {
        let user_info = get_user_info()?;
        let who = format!("{} <{}>", user_info.username, user_info.email);
        (user_info.username, vec![Spanned::from(who)])
    } else {
        (
            sub_args.who.join(", "),
            sub_args
                .who
                .iter()
                .map(|w| Spanned::from(w.clone()))
                .collect(),
        )
    };

    let (criteria_guess, prompt) = if sub_args.criteria.is_empty() {
        // If we don't have explicit cli criteria, guess the criteria
        //
        // * Check what would cause `cargo vet` to encounter fewer errors
        // * Otherwise check what would cause `cargo vet suggest` to suggest fewer audits
        // * Otherwise guess nothing
        //
        // Regardless of the guess, prompt the user to confirm (just needs to mash enter)
        match &kind {
            CertifyKind::Full { version } => (
                guess_audit_criteria(cfg, store, &package, None, version),
                Some(format!(
                    "choose criteria to certify for {package}:{version}"
                )),
            ),
            CertifyKind::Delta { from, to } => (
                guess_audit_criteria(cfg, store, &package, Some(from), to),
                Some(format!(
                    "choose criteria to certify for {package}:{from} -> {to}"
                )),
            ),
            CertifyKind::Wildcard { .. } => {
                // FIXME: Consider predicting the criteria better for wildcard
                // audits in the future.
                (
                    vec![format::SAFE_TO_DEPLOY.to_owned()],
                    Some(format!("choose criteria to certify for {package}:*")),
                )
            }
        }
    } else {
        // If we do have explcit criteria, don't prompt, but still pass through
        // prompt_pick_criteria to simplify and validate.
        (sub_args.criteria.clone(), None)
    };
    let criteria_names =
        criteria_picker(out, &store.audits.criteria, criteria_guess, prompt.as_ref())?;

    let statement = match &kind {
        CertifyKind::Full { version } => {
            format!(
                    "I, {username}, certify that I have audited version {version} of {package} in accordance with the above criteria.",
                )
        }
        CertifyKind::Delta { from, to } => {
            format!(
                    "I, {username}, certify that I have audited the changes from version {from} to {to} of {package} in accordance with the above criteria.",
                )
        }
        CertifyKind::Wildcard {
            user_login,
            start,
            end,
            ..
        } => {
            format!(
                    "I, {username}, certify that any version of {package} published by '{user_login}' between {start} and {end} will satisfy the above criteria.",
                )
        }
    };

    let mut notes = sub_args.notes.clone();
    if !sub_args.accept_all {
        // Get all the EULAs at once
        let eulas = tokio::runtime::Handle::current().block_on(join_all(
            criteria_names.iter().map(|criteria| async {
                (
                    &criteria[..],
                    eula_for_criteria(network, &store.audits.criteria, criteria).await,
                )
            }),
        ));

        let mut editor = out.editor("VET_CERTIFY")?;
        if let Some(notes) = &notes {
            editor.select_comment_char(notes);
        }

        editor.add_comments(
            "Please read the following criteria and then follow the instructions below:",
        )?;
        editor.add_text("")?;

        for (criteria, eula) in &eulas {
            editor.add_comments(&format!("=== BEGIN CRITERIA {criteria:?} ==="))?;
            editor.add_comments("")?;
            editor.add_comments(eula)?;
            editor.add_comments("")?;
            editor.add_comments("=== END CRITERIA ===")?;
            editor.add_comments("")?;
        }
        editor.add_comments("Uncomment the following statement:")?;
        editor.add_text("")?;
        editor.add_comments(&statement)?;
        editor.add_text("")?;
        editor.add_comments("Add any notes about your audit below this line:")?;
        editor.add_text("")?;
        if let Some(notes) = &notes {
            editor.add_text(notes)?;
        }

        let editor_result = editor.edit()?;

        // Check to make sure that the statement was uncommented as the first
        // line in the parsed file, and remove blank lines between the statement
        // and notes.
        let new_notes = match editor_result.trim_start().strip_prefix(&statement) {
            Some(notes) => notes.trim_start_matches('\n'),
            None => {
                // FIXME: Might be nice to try to save any notes the user typed
                // in and re-try the prompt if the user asks for it, in case
                // they wrote some nice notes, but forgot to uncomment the
                // statement.
                return Err(CertifyError::CouldntFindCertifyStatement);
            }
        };

        // Strip trailing newline if notes would otherwise contain no newlines.
        let new_notes = new_notes
            .strip_suffix('\n')
            .filter(|s| !s.contains('\n'))
            .unwrap_or(new_notes);

        notes = if new_notes.is_empty() {
            None
        } else {
            Some(new_notes.to_owned())
        };
    }

    let criteria = criteria_names.into_iter().map(|s| s.into()).collect();
    match kind {
        CertifyKind::Full { version } => {
            let kind = AuditKind::Full { version };
            let importable = kind.default_importable();
            store
                .audits
                .audits
                .entry(package.clone())
                .or_default()
                .push(AuditEntry {
                    kind,
                    criteria,
                    who,
                    importable,
                    notes,
                    aggregated_from: vec![],
                    is_fresh_import: false,
                });
        }
        CertifyKind::Delta { from, to } => {
            let from_is_git_version = from.git_rev.is_some();
            let kind = AuditKind::Delta { from, to };
            let importable = kind.default_importable();

            let mut entry = AuditEntry {
                kind,
                criteria,
                who,
                importable,
                notes,
                aggregated_from: vec![],
                is_fresh_import: false,
            };

            // Collapse a delta audit with a git `from` version with a prior audit that is
            // non-importable and has identical and satisfied criteria.
            //
            // We merge an adjacent audit for a prior version with the new audit (updating the new
            // audit). The later `update_store` call will remove the now-unused prior audit.
            if from_is_git_version && !sub_args.no_collapse {
                // A closure which returns whether the given audit entry satisfies the criteria
                // being certified.
                let is_rooted_for_criteria = {
                    let mapper = CriteriaMapper::new(&store.audits.criteria);
                    let criteria = mapper.criteria_from_list(&entry.criteria);
                    // If the audit graph fails to load, we always return `false` and thus don't
                    // make any changes.
                    let audit_graph = match resolver::AuditGraph::build(
                        store, &mapper, &package, None,
                    ) {
                        Ok(graph) => Some(graph),
                        Err(_) => {
                            warn!(
                                "failed to build audit graph to determine audit collapse validity, so not collapsing any audits"
                            );
                            None
                        }
                    };

                    move |audit: &AuditEntry| {
                        let Some(audit_graph) = &audit_graph else {
                            return false;
                        };
                        let version = match &audit.kind {
                            AuditKind::Delta { from, .. } => from,
                            AuditKind::Full { .. } => return true,
                            AuditKind::Violation { .. } => return false,
                        };

                        // NOTE we use `criteria` of the certification rather than the target audit
                        // to check root accessibility, which is okay since later in
                        // `try_collapse_with_prior` we verify that the criteria of the audit is
                        // identical to that of the certification.
                        mapper.minimal_indices(&criteria).all(|idx| {
                            audit_graph
                                .search(idx, version, resolver::SearchMode::PreferExemptions)
                                .is_ok()
                        })
                    }
                };
                for audit in store
                    .audits
                    .audits
                    .get(&package)
                    .into_iter()
                    .flatten()
                    .filter(|a| !a.importable && is_rooted_for_criteria(a))
                {
                    if let Some(new_entry) = entry.try_collapse_with_prior(audit) {
                        entry = new_entry;
                        break;
                    }
                }
            }

            store
                .audits
                .audits
                .entry(package.clone())
                .or_default()
                .push(entry);
        }
        CertifyKind::Wildcard {
            user_id,
            start,
            end,
            set_renew_false,
            ..
        } => {
            store
                .audits
                .wildcard_audits
                .entry(package.clone())
                .or_default()
                .push(WildcardEntry {
                    who,
                    criteria,
                    user_id,
                    start: start.into(),
                    end: end.into(),
                    renew: set_renew_false.then_some(false),
                    notes,
                    aggregated_from: vec![],
                    is_fresh_import: false,
                });
        }
    };

    store
        .validate(cfg.today(), false)
        .expect("the new audit entry made the store invalid?");

    // Minimize exemptions after adding the new audit. This will be used to potentially update
    // imports, and remove now-unnecessary exemptions and audits for the target package. We only
    // prefer fresh imports and prune exemptions for the package we certified, to avoid unrelated
    // changes.
    resolver::update_store(cfg, store, |name| resolver::UpdateMode {
        search_mode: if name == &package[..] {
            resolver::SearchMode::PreferFreshImports
        } else {
            resolver::SearchMode::PreferExemptions
        },
        prune_exemptions: name == &package[..],
        prune_non_importable_audits: name == &package[..],
        prune_imports: false,
    });

    Ok(())
}