adapt book to error rework (#14)

This commit is contained in:
Paul 2022-02-21 11:16:34 +01:00 committed by GitHub
parent b992f4828b
commit beeebca7af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 26 additions and 22 deletions

View File

@ -7,23 +7,25 @@ The autogenerated clients can automatically parse Anchor Internal Errors and Cus
## Anchor Internal Errors ## Anchor Internal Errors
> [Anchor Internal Error Code Reference](https://docs.rs/anchor-lang/latest/anchor_lang/__private/enum.ErrorCode.html) > [Anchor Internal Error Code Reference](https://docs.rs/anchor-lang/latest/anchor_lang/error/enum.ErrorCode.html)
Anchor has many different internal error codes. These are not meant to be used by users, but it's useful to study the reference to learn about the mappings between codes and their causes. They are, for example, thrown when a constraint has been violated, e.g. when an account is marked with `mut` but its `is_writable` property is `false`. Anchor has many different internal error codes. These are not meant to be used by users, but it's useful to study the reference to learn about the mappings between codes and their causes. They are, for example, thrown when a constraint has been violated, e.g. when an account is marked with `mut` but its `is_writable` property is `false`.
## Custom Errors ## Custom Errors
You can add errors that are unique to your program by using the error attribute. You can add errors that are unique to your program by using the `error_code` attribute.
Simply add it to an enum with a name of your choice. You can then use the variants of the enum as errors in your program. Additionally, you can add a message attribute to the individual variants. Clients will then display this error message if the error occurs. Custom Error code numbers start at the [custom error offset](https://docs.rs/anchor-lang/latest/anchor_lang/__private/constant.ERROR_CODE_OFFSET.html). Simply add it to an enum with a name of your choice. You can then use the variants of the enum as errors in your program. Additionally, you can add a message attribute to the individual variants. Clients will then display this error message if the error occurs. Custom Error code numbers start at the [custom error offset](https://docs.rs/anchor-lang/latest/anchor_lang/error/constant.ERROR_CODE_OFFSET.html).
To actually throw an error use the [`err!`](https://docs.rs/anchor-lang/latest/anchor_lang/macro.err.html) or the [`error!`](https://docs.rs/anchor-lang/latest/anchor_lang/prelude/macro.error.html) macro. These add file and line information to the error that is then logged by anchor.
```rust,ignore ```rust,ignore
#[program] #[program]
mod hello_anchor { mod hello_anchor {
use super::*; use super::*;
pub fn set_data(ctx: Context<SetData>, data: MyAccount) -> ProgramResult { pub fn set_data(ctx: Context<SetData>, data: MyAccount) -> Result<()> {
if data.data >= 100 { if data.data >= 100 {
return Err(MyError::DataTooLarge.into()); return err!(MyError::DataTooLarge);
} }
ctx.accounts.my_account.set_inner(data); ctx.accounts.my_account.set_inner(data);
Ok(()) Ok(())
@ -31,19 +33,21 @@ mod hello_anchor {
} }
#[error] #[error_code]
pub enum MyError { pub enum MyError {
#[msg("MyAccount may only hold data below 100")] #[msg("MyAccount may only hold data below 100")]
DataTooLarge DataTooLarge
} }
``` ```
### require!
You can use the [`require`](https://docs.rs/anchor-lang/latest/anchor_lang/macro.require.html) macro to simplify writing errors. The code above can be simplified to this (Note that the `>=` flips to `<`): You can use the [`require`](https://docs.rs/anchor-lang/latest/anchor_lang/macro.require.html) macro to simplify writing errors. The code above can be simplified to this (Note that the `>=` flips to `<`):
```rust,ignore ```rust,ignore
#[program] #[program]
mod hello_anchor { mod hello_anchor {
use super::*; use super::*;
pub fn set_data(ctx: Context<SetData>, data: MyAccount) -> ProgramResult { pub fn set_data(ctx: Context<SetData>, data: MyAccount) -> Result<()> {
require!(data.data < 100, MyError::DataTooLarge); require!(data.data < 100, MyError::DataTooLarge);
ctx.accounts.my_account.set_inner(data); ctx.accounts.my_account.set_inner(data);
Ok(()) Ok(())
@ -51,7 +55,7 @@ mod hello_anchor {
} }
#[error] #[error_code]
pub enum MyError { pub enum MyError {
#[msg("MyAccount may only hold data below 100")] #[msg("MyAccount may only hold data below 100")]
DataTooLarge DataTooLarge

View File

@ -13,7 +13,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program] #[program]
mod hello_anchor { mod hello_anchor {
use super::*; use super::*;
pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult { pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
Ok(()) Ok(())
} }
} }

View File

@ -109,7 +109,7 @@ And with this, `SetupGame` is complete and we can move on to the `setup_game` fu
Let's start by adding an argument to the `setup_game` function. Let's start by adding an argument to the `setup_game` function.
```rust,ignore ```rust,ignore
pub fn setup_game(ctx: Context<SetupGame>, player_two: Pubkey) -> ProgramResult { pub fn setup_game(ctx: Context<SetupGame>, player_two: Pubkey) -> Result<()> {
Ok(()) Ok(())
} }
``` ```
@ -117,7 +117,7 @@ Why didn't we just add `player_two` as an account in the accounts struct? There
Finish the instruction function by setting the game to its initial values: Finish the instruction function by setting the game to its initial values:
```rust,ignore ```rust,ignore
pub fn setup_game(ctx: Context<SetupGame>, player_two: Pubkey) -> ProgramResult { pub fn setup_game(ctx: Context<SetupGame>, player_two: Pubkey) -> Result<()> {
let game = &mut ctx.accounts.game; let game = &mut ctx.accounts.game;
game.players = [ctx.accounts.player_one.key(), player_two]; game.players = [ctx.accounts.player_one.key(), player_two];
game.turn = 1; game.turn = 1;
@ -202,9 +202,9 @@ impl Game {
self.players[self.current_player_index()] self.players[self.current_player_index()]
} }
pub fn play(&mut self, tile: &Tile) -> ProgramResult { pub fn play(&mut self, tile: &Tile) -> Result<()> {
if !self.is_active() { if !self.is_active() {
return Err(TicTacToeError::GameAlreadyOver.into()); return err!(TicTacToeError::GameAlreadyOver);
} }
match tile { match tile {
tile tile
@ -212,13 +212,13 @@ impl Game {
row: 0..=2, row: 0..=2,
column: 0..=2, column: 0..=2,
} => match self.board[tile.row as usize][tile.column as usize] { } => match self.board[tile.row as usize][tile.column as usize] {
Some(_) => return Err(TicTacToeError::TileAlreadySet.into()), Some(_) => return err!(TicTacToeError::TileAlreadySet),
None => { None => {
self.board[tile.row as usize][tile.column as usize] = self.board[tile.row as usize][tile.column as usize] =
Some(Sign::from_usize(self.current_player_index()).unwrap()); Some(Sign::from_usize(self.current_player_index()).unwrap());
} }
}, },
_ => return Err(TicTacToeError::TileOutOfBounds.into()), _ => return err!(TicTacToeError::TileOutOfBounds),
} }
self.update_state(); self.update_state();
@ -302,7 +302,7 @@ pub struct Tile {
and the `TicTacToeError` type. and the `TicTacToeError` type.
```rust,ignore ```rust,ignore
#[error] #[error_code]
pub enum TicTacToeError { pub enum TicTacToeError {
TileOutOfBounds, TileOutOfBounds,
TileAlreadySet, TileAlreadySet,
@ -313,7 +313,7 @@ pub enum TicTacToeError {
Finally, we can add the `play` function inside the program module. Finally, we can add the `play` function inside the program module.
```rust,ignore ```rust,ignore
pub fn play(ctx: Context<Play>, tile: Tile) -> ProgramResult { pub fn play(ctx: Context<Play>, tile: Tile) -> Result<()> {
let game = &mut ctx.accounts.game; let game = &mut ctx.accounts.game;
require!( require!(

View File

@ -22,7 +22,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program] #[program]
mod hello_anchor { mod hello_anchor {
use super::*; use super::*;
pub fn set_data(ctx: Context<SetData>, data: u64) -> ProgramResult { pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
ctx.accounts.my_account.data = data; ctx.accounts.my_account.data = data;
Ok(()) Ok(())
} }
@ -57,7 +57,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program] #[program]
mod hello_anchor { mod hello_anchor {
use super::*; use super::*;
pub fn set_data(ctx: Context<SetData>, data: u64) -> ProgramResult { pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
if ctx.accounts.token_account.amount > 0 { if ctx.accounts.token_account.amount > 0 {
ctx.accounts.my_account.data = data; ctx.accounts.my_account.data = data;
} }

View File

@ -5,7 +5,7 @@ The program module is where you define your business logic. You do so by writing
#[program] #[program]
mod hello_anchor { mod hello_anchor {
use super::*; use super::*;
pub fn set_data(ctx: Context<SetData>, data: u64) -> ProgramResult { pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
if ctx.accounts.token_account.amount > 0 { if ctx.accounts.token_account.amount > 0 {
ctx.accounts.my_account.data = data; ctx.accounts.my_account.data = data;
} }
@ -30,7 +30,7 @@ If your function requires instruction data, you can add it by adding arguments t
#[program] #[program]
mod hello_anchor { mod hello_anchor {
use super::*; use super::*;
pub fn set_data(ctx: Context<SetData>, data: Data) -> ProgramResult { pub fn set_data(ctx: Context<SetData>, data: Data) -> Result<()> {
ctx.accounts.my_account.data = data.data; ctx.accounts.my_account.data = data.data;
ctx.accounts.my_account.age = data.age; ctx.accounts.my_account.age = data.age;
Ok(()) Ok(())
@ -61,7 +61,7 @@ Conveniently, `#[account]` implements `Anchor(De)Serialize` for `MyAccount`, so
#[program] #[program]
mod hello_anchor { mod hello_anchor {
use super::*; use super::*;
pub fn set_data(ctx: Context<SetData>, data: MyAccount) -> ProgramResult { pub fn set_data(ctx: Context<SetData>, data: MyAccount) -> Result<()> {
ctx.accounts.my_account.set_inner(data); ctx.accounts.my_account.set_inner(data);
Ok(()) Ok(())
} }