266 lines
7.3 KiB
Markdown
266 lines
7.3 KiB
Markdown
<!-- file: crates/ksp-core-lib/USAGE.md -->
|
||
<!-- version: 1 -->
|
||
|
||
# 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>` :
|
||
|
||
```rust
|
||
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 :
|
||
|
||
```rust
|
||
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` :
|
||
|
||
```rust
|
||
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 lorsqu’un caller prépare explicitement son contexte :
|
||
|
||
```rust
|
||
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 :
|
||
|
||
```rust
|
||
#[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 :
|
||
|
||
```rust
|
||
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 :
|
||
|
||
```rust
|
||
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 :
|
||
|
||
```rust
|
||
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 :
|
||
|
||
```rust
|
||
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 :
|
||
|
||
```rust
|
||
let count = ksp_core_lib::native_program_ids().count();
|
||
assert_eq!(count, 18);
|
||
```
|
||
|
||
## Rechercher une entrée
|
||
|
||
Recherche par représentation Base58 :
|
||
|
||
```rust
|
||
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` :
|
||
|
||
```rust
|
||
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 :
|
||
|
||
```rust
|
||
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 :
|
||
|
||
```rust
|
||
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 :
|
||
|
||
```rust
|
||
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 :
|
||
|
||
```text
|
||
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.
|