Files
khadhroony-solana-project/crates/ksp-core-lib/USAGE.md
2026-08-23 11:15:57 +02:00

7.3 KiB
Raw Blame History

Utilisation de ksp-core-lib

Utiliser le type d'erreur commun

Une crate KSP définit des codes stables puis retourne ksp_core_lib::Result<T> :

const ERROR_CODE_LOAD_FAILED: ksp_core_lib::ErrorCode =
    ksp_core_lib::ErrorCode::new("store", "load_failed");

fn load_value() -> ksp_core_lib::Result<u64> {
    let error = ksp_core_lib::Error::new(
        ERROR_CODE_LOAD_FAILED,
        "unable to load value",
    )
    .with_context("operation", "load_value");

    return std::result::Result::Err(error);
}

Le domaine et le code sont accessibles séparément :

let code = ERROR_CODE_LOAD_FAILED;
assert_eq!(code.domain(), "store");
assert_eq!(code.code(), "load_failed");

Le message et les champs de contexte restent accessibles sans parser Display :

let error = ksp_core_lib::Error::new(
    ERROR_CODE_LOAD_FAILED,
    "unable to load value",
)
.with_context("component", "postgres")
.with_context("operation", "load_value");

assert_eq!(error.code(), ERROR_CODE_LOAD_FAILED);
assert_eq!(error.message(), "unable to load value");
assert_eq!(error.context()[0].key(), "component");
assert_eq!(error.context()[0].value(), "postgres");

Un ErrorContext peut aussi être construit indépendamment lorsquun caller prépare explicitement son contexte :

let context = ksp_core_lib::ErrorContext::new(
    "operation",
    "load_value",
);

assert_eq!(context.key(), "operation");
assert_eq!(context.value(), "load_value");

Les valeurs de contexte doivent être sûres à exposer. Ne pas y placer de secret, credential, seed, key material, URL contenant une clé API ou payload brut volumineux.

Conserver une erreur source

Une erreur externe compatible Send + Sync + 'static peut rester dans la chaîne standard :

#[derive(Debug)]
struct SourceError;

impl std::fmt::Display for SourceError {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        return formatter.write_str("source failure");
    }
}

impl std::error::Error for SourceError {}

let error = ksp_core_lib::Error::new(
    ERROR_CODE_LOAD_FAILED,
    "unable to load value",
)
.with_source(SourceError);

let source = std::error::Error::source(&error);
assert!(source.is_some());

La source sert au chaînage d'erreurs ; son contenu ne doit pas être recopié sans contrôle dans un contexte ou un log public.

Utiliser Pubkey

Core réexporte la primitive Solana utilisée par les contrats KSP :

let system = ksp_core_lib::Pubkey::from_str_const(
    ksp_core_lib::PRGID_SOLANA_SYSTEM,
);

assert_eq!(system, ksp_core_lib::PRGIDPK_SOLANA_SYSTEM);

Une crate consommatrice peut donc utiliser ksp_core_lib::Pubkey dans sa propre API sans dépendre directement de solana-pubkey lorsque Core est déjà le propriétaire architectural de cette primitive.

Utiliser les constantes Program ID

Chaque Program ID Core possède une forme texte et une forme typée :

let system_text: &str = ksp_core_lib::PRGID_SOLANA_SYSTEM;
let system_pubkey: ksp_core_lib::Pubkey =
    ksp_core_lib::PRGIDPK_SOLANA_SYSTEM;

assert_eq!(
    system_pubkey,
    ksp_core_lib::Pubkey::from_str_const(system_text),
);

Les constantes PRGID_* sont utiles pour les wires, diagnostics sûrs ou comparaisons texte. Les constantes PRGIDPK_* sont préférées dès qu'un contrat manipule une adresse Solana typée.

Déclarer une paire texte / Pubkey

declare_program_id! permet à une couche KSP propriétaire d'un Program ID de déclarer les deux représentations depuis une seule valeur Base58 :

ksp_core_lib::declare_program_id!(
    PRGID_EXAMPLE,
    PRGIDPK_EXAMPLE,
    "11111111111111111111111111111111"
);

assert_eq!(PRGID_EXAMPLE, ksp_core_lib::PRGID_SOLANA_SYSTEM);
assert_eq!(PRGIDPK_EXAMPLE, ksp_core_lib::PRGIDPK_SOLANA_SYSTEM);

La macro ne signifie pas que toute nouvelle constante doit être ajoutée au registre Core. La couche propriétaire du domaine décide où vit le nouveau Program ID.

Parcourir le registre canonique

entries() retourne le registre complet en ordre déterministe :

for entry in ksp_core_lib::entries() {
    let code = entry.code();
    let name = entry.name();
    let text = entry.program_id();
    let pubkey = entry.pubkey();
    let domain = entry.domain();
    let family = entry.family();
    let protocol = entry.protocol();
    let subfamily = entry.subfamily();
    let program_version = entry.program_version();
    let kind = entry.kind();

    let _ = (
        code,
        name,
        text,
        pubkey,
        domain,
        family,
        protocol,
        subfamily,
        program_version,
        kind,
    );
}

native_program_ids() fournit la vue des Program IDs Solana fondamentaux actuellement enregistrés :

let count = ksp_core_lib::native_program_ids().count();
assert_eq!(count, 18);

Rechercher une entrée

Recherche par représentation Base58 :

let entry = ksp_core_lib::find_program_id(
    ksp_core_lib::PRGID_SOLANA_SYSTEM,
);

let entry = match entry {
    std::option::Option::Some(value) => value,
    std::option::Option::None => return,
};

assert_eq!(entry.code(), "solana.system");

Recherche par Pubkey :

let entry = ksp_core_lib::find_program_pubkey(
    &ksp_core_lib::PRGIDPK_SOLANA_VOTE,
);

assert!(entry.is_some());

Une recherche inconnue retourne None; le registre n'invente pas une entrée générique.

Filtrer par axe simple

Les helpers spécialisés conviennent aux filtres simples :

let loaders = ksp_core_lib::program_ids_by_family("loader");
for loader in loaders {
    assert_eq!(loader.family(), "loader");
}

Les vues disponibles peuvent être consommées directement :

let solana_entries = ksp_core_lib::program_ids_by_domain("solana").count();
let loaders = ksp_core_lib::program_ids_by_family("loader").count();
let solana_protocol = ksp_core_lib::program_ids_by_protocol("solana").count();

assert_eq!(solana_entries, 18);
assert_eq!(loaders, 5);
assert_eq!(solana_protocol, 18);

Combiner plusieurs axes

ProgramIdFilter compose les contraintes sans allocation de collection intermédiaire :

let filter = ksp_core_lib::ProgramIdFilter::new()
    .with_domain("solana")
    .with_family("loader")
    .with_protocol("solana")
    .with_subfamily("bpf")
    .with_program_version("v2")
    .with_kind(ksp_core_lib::ProgramIdKind::Loader);

for entry in ksp_core_lib::program_ids(filter) {
    assert_eq!(entry.domain(), "solana");
    assert_eq!(entry.family(), "loader");
    assert_eq!(entry.protocol(), "solana");
    assert_eq!(entry.subfamily(), std::option::Option::Some("bpf"));
    assert_eq!(entry.program_version(), std::option::Option::Some("v2"));
    assert_eq!(entry.kind(), ksp_core_lib::ProgramIdKind::Loader);
}

Un ProgramIdFilter::new() vide correspond à toutes les entrées du registre.

Choisir entre texte, Pubkey et descriptor

Utiliser :

PRGID_* / &str       pour une représentation Base58 canonique
PRGIDPK_* / Pubkey   pour un contrat Solana typé
ProgramIdEntry       lorsqu'il faut aussi la taxonomie KSP

Ne pas reparcourir les constantes ou reconstruire une taxonomie parallèle dans une crate consommatrice lorsque le registre Core fournit déjà l'information requise.