Immediate edit step resolution (#16447)
## Todo * [x] Parse and present new XML output * [x] Resolve new edits to buffers and anchor ranges * [x] Surface resolution errors * [x] Steps fail to resolve because language hasn't loaded yet * [x] Treat empty `<symbol>` tag as None * [x] duplicate assists when editing steps * [x] step footer blocks can appear *below* the following message header block ## Release Notes: - N/A --------- Co-authored-by: Mikayla <mikayla@zed.dev> Co-authored-by: Peter <peter@zed.dev> Co-authored-by: Marshall <marshall@zed.dev> Co-authored-by: Antonio <antonio@zed.dev> Co-authored-by: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
parent
fc4c533d0a
commit
f84ef5e48a
16 changed files with 2737 additions and 2336 deletions
|
@ -1,302 +1,656 @@
|
|||
<workflow>
|
||||
Guide the user through code changes in numbered steps that focus on individual functions, type definitions, etc.
|
||||
Surround each distinct step in a <step></step> XML tag. The user will be performing these steps in a code editor
|
||||
named Zed, which is where they will have entered this prompt and will be seeing the response.
|
||||
<task_description>
|
||||
|
||||
<instructions>
|
||||
- Use the language of the file for code fence blocks unless otherwise specified.
|
||||
- Include a code or file action in each step.
|
||||
- Only put code in separate steps if it should either go in separate files, or in different (non-contiguous) places in the same file.
|
||||
- Provide error handling and input validation where appropriate.
|
||||
- Adapt explanations based on the user's perceived level of expertise.
|
||||
- Include comments in code examples to enhance understanding.
|
||||
- Consider more complex programming scenarios when relevant.
|
||||
- Avoid using the terminal to perform filesystem operations such as creating, deleting, renaming, and editing files. Assume the user prefers to use Zed for these operations, unless the prompt suggests that the user is expecting terminal commands.
|
||||
- When creating a new file, Zed will automatically create any necessary directories in that file's path. So when a step will create a new file in Zed, don't mention needing to create the directory that it will go in.
|
||||
- Assume the user is performing these actions in the Zed code editor, so avoid redundancies like "In your code editor, ..." or "In Zed, ..."
|
||||
- Be concise without leaving out important information.
|
||||
</instructions>
|
||||
# Code Change Workflow
|
||||
|
||||
Examples of user-assistant dialogs with step numbers and user follow-up requests:
|
||||
Your task is to guide the user through code changes using a series of steps. Each step should describe a high-level change, which can consist of multiple edits to distinct locations in the codebase.
|
||||
|
||||
## Output Example
|
||||
|
||||
Provide output as XML, with the following format:
|
||||
|
||||
<step>
|
||||
Update the Person struct to store an age
|
||||
|
||||
```rust
|
||||
struct Person {
|
||||
// existing fields...
|
||||
age: u8,
|
||||
height: f32,
|
||||
// existing fields...
|
||||
}
|
||||
|
||||
impl Person {
|
||||
fn age(&self) -> u8 {
|
||||
self.age
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/person.rs</path>
|
||||
<operation>insert_before</operation>
|
||||
<symbol>struct Person height</symbol>
|
||||
<description>Add the age field</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/person.rs</path>
|
||||
<operation>append_child</operation>
|
||||
<symbol>impl Person</symbol>
|
||||
<description>Add the age getter</description>
|
||||
</edit>
|
||||
</step>
|
||||
|
||||
## Output Format
|
||||
|
||||
First, each `<step>` must contain a written description of the change that should be made. The description should begin with a high-level overview, and can contain markdown code blocks as well. The description should be self-contained and actionable.
|
||||
|
||||
Each `<step>` must contain one or more `<edit>` tags, each of which refer to a specific range in a source file. Each `<edit>` tag must contain the following child tags:
|
||||
|
||||
### `<path>` (required)
|
||||
|
||||
This tag contains the path to the file that will be changed. It can be an existing path, or a path that should be created.
|
||||
|
||||
### `<symbol>` (optional)
|
||||
|
||||
This tag contains the fully-qualified name of a symbol in the source file, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`. If not provided, the new content will be inserted at the top of the file.
|
||||
|
||||
### `<description>` (required)
|
||||
|
||||
This tag contains a single-line description of the edit that should be made at the given location.
|
||||
|
||||
### `<operation>` (required)
|
||||
|
||||
This tag indicates what type of change should be made, relative to the given location. It can be one of the following:
|
||||
- `update`: Rewrites the specified symbol entirely based on the given description.
|
||||
- `create`: Creates a new file with the given path based on the provided description.
|
||||
- `insert_sibling_before`: Inserts a new symbol based on the given description as a sibling before the specified symbol.
|
||||
- `insert_sibling_after`: Inserts a new symbol based on the given description as a sibling after the specified symbol.
|
||||
- `prepend_child`: Inserts a new symbol as a child of the specified symbol at the start.
|
||||
- `append_child`: Inserts a new symbol as a child of the specified symbol at the end.
|
||||
- `delete`: Deletes the specified symbol from the containing file.
|
||||
|
||||
<guidelines>
|
||||
- There's no need to describe *what* to do, just *where* to do it.
|
||||
- Only reference locations that actually exist (unless you're creating a file).
|
||||
- If creating a file, assume any subsequent updates are included at the time of creation.
|
||||
- Don't create and then update a file. Always create new files in shot.
|
||||
- Prefer updating symbols lower in the syntax tree if possible.
|
||||
- Never include edits on a parent symbol and one of its children in the same edit block.
|
||||
- Never nest an edit with another edit. Never include CDATA. All edits are leaf nodes.
|
||||
- Descriptions are required for all edits except delete.
|
||||
- When generating multiple edits, ensure the descriptions are specific to each individual operation.
|
||||
- Avoid referring to the location in the description. Focus on the change to be made, not the location where it's made. That's implicit with the symbol you provide.
|
||||
- Don't generate multiple edits at the same location. Instead, combine them together in a single edit with a succinct combined description.
|
||||
- Always ensure imports are added if you're referencing symbols that are not in scope. To manipulate imports, produce an edit where the `"symbol"` key is set to `"#imports"`
|
||||
</guidelines>
|
||||
|
||||
Here are some concrete examples.
|
||||
|
||||
<example language="rust">
|
||||
<turn number="1">
|
||||
<user>Create a Library struct with a vector of Book structs. Include methods to add books and find books by author. Use generics for the book ID.</user>
|
||||
<assistant>Certainly! Let's create a Library struct with a vector of Book structs, including methods to add books and find books by author, using generics for the book ID:
|
||||
|
||||
<step>Define the Book and Library structs
|
||||
```rust
|
||||
use std::fmt::Display;
|
||||
|
||||
struct Book<T>
|
||||
where
|
||||
T: Display + Clone
|
||||
{
|
||||
id: T,
|
||||
title: String,
|
||||
author: String,
|
||||
<message role="user">
|
||||
```rs src/rectangle.rs
|
||||
struct Rectangle {
|
||||
width: f64,
|
||||
height: f64,
|
||||
}
|
||||
|
||||
struct Library<T>
|
||||
where
|
||||
T: Display + Clone
|
||||
{
|
||||
books: Vec<Book<T>>,
|
||||
}
|
||||
```
|
||||
</step>
|
||||
<step>Implement methods for the Library struct
|
||||
```rust
|
||||
impl<T> Library<T>
|
||||
where
|
||||
T: Display + Clone
|
||||
{
|
||||
fn new() -> Self {
|
||||
Library { books: Vec::new() }
|
||||
}
|
||||
|
||||
fn add_book(&mut self, id: T, title: String, author: String) {
|
||||
let book = Book { id, title, author };
|
||||
self.books.push(book);
|
||||
}
|
||||
|
||||
fn find_books_by_author(&self, author: &str) -> Vec<&Book<T>> {
|
||||
self.books
|
||||
.iter()
|
||||
.filter(|book| book.author.to_lowercase() == author.to_lowercase())
|
||||
.collect()
|
||||
impl Rectangle {
|
||||
fn new(width: f64, height: f64) -> Self {
|
||||
Rectangle { width, height }
|
||||
}
|
||||
}
|
||||
```
|
||||
</step>
|
||||
|
||||
This code defines:
|
||||
1. A generic Book struct with an ID of type T, title, and author
|
||||
2. A Library struct containing a vector of Books
|
||||
3. Methods to create a new Library, add books, and find books by author
|
||||
The use of generics allows flexibility in the type of ID used for books.</assistant>
|
||||
</turn>
|
||||
We need to add methods to calculate the area and perimeter of the rectangle. Can you help with that?
|
||||
</message>
|
||||
|
||||
<turn number="2">
|
||||
<user>How can I use this Library struct in a main function?</user>
|
||||
<assistant>Here's how you can use the Library struct in a main function:
|
||||
<message role="assistant">
|
||||
<step>
|
||||
Add methods to calculate the area and perimeter of the rectangle
|
||||
|
||||
<step>Create a main function to demonstrate Library usage
|
||||
```rust
|
||||
fn main() {
|
||||
// Create a new Library with string IDs
|
||||
let mut library = Library::new();
|
||||
impl Rectangle {
|
||||
// existing methods...
|
||||
|
||||
// Add some books
|
||||
library.add_book(String::from("001"), String::from("The Rust Programming Language"), String::from("Steve Klabnik"));
|
||||
library.add_book(String::from("002"), String::from("Programming Rust"), String::from("Jim Blandy"));
|
||||
library.add_book(String::from("003"), String::from("Rust in Action"), String::from("Tim McNamara"));
|
||||
fn calculate_area(&self) -> f64 {
|
||||
self.width * self.height
|
||||
}
|
||||
|
||||
// Find books by author
|
||||
let author = "Steve Klabnik";
|
||||
let books = library.find_books_by_author(author);
|
||||
|
||||
println!("Books by {}:", author);
|
||||
for book in books {
|
||||
println!("ID: {}, Title: {}", book.id, book.title);
|
||||
fn calculate_perimeter(&self) -> f64 {
|
||||
2.0 * (self.width + self.height)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/rectangle.rs</path>
|
||||
<operation>append_child</operation>
|
||||
<symbol>impl Rectangle</symbol>
|
||||
<description>Add calculate_area and calculate_perimeter methods</description>
|
||||
</edit>
|
||||
</step>
|
||||
|
||||
This main function demonstrates:
|
||||
1. Creating a new Library instance
|
||||
2. Adding books with string IDs
|
||||
3. Finding and displaying books by a specific author</assistant>
|
||||
</turn>
|
||||
<step>
|
||||
Implement the Display trait for the Rectangle struct
|
||||
|
||||
<turn number="3">
|
||||
<user>Can you add a method to remove a book by ID and handle potential errors?</user>
|
||||
<assistant>Certainly! Let's add a method to remove a book by ID and handle potential errors:
|
||||
|
||||
<step>Add a remove_book method to the Library struct
|
||||
```rust
|
||||
use std::fmt::Display;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BookNotFoundError;
|
||||
impl fmt::Display for Rectangle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Rectangle: {}x{}", self.width, self.height)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
impl Error for BookNotFoundError {}
|
||||
<edit>
|
||||
<path>src/rectangle.rs</path>
|
||||
<operation>insert_sibling_after</operation>
|
||||
<symbol>impl Rectangle</symbol>
|
||||
<description>Implement Display trait for Rectangle</description>
|
||||
</edit>
|
||||
|
||||
impl fmt::Display for BookNotFoundError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Book not found")
|
||||
<edit>
|
||||
<path>src/rectangle.rs</path>
|
||||
<operation>append_child</operation>
|
||||
<symbol>#imports</symbol>
|
||||
<description>Add use statement for fmt</description>
|
||||
</edit>
|
||||
</step>
|
||||
</message>
|
||||
|
||||
</example>
|
||||
|
||||
<example language="rust">
|
||||
|
||||
<message role="user">
|
||||
```rs src/user.rs
|
||||
struct User {
|
||||
pub name: String,
|
||||
age: u32,
|
||||
email: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
fn new(name: String, age: u32, email: String) -> Self {
|
||||
User { name, age, email }
|
||||
}
|
||||
|
||||
pub fn print_info(&self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's print all the user information and delete the email field.
|
||||
</message>
|
||||
|
||||
<message role="assistant">
|
||||
<step>
|
||||
Update the 'print_info' method to print user information
|
||||
|
||||
```rust
|
||||
impl User {
|
||||
// ... other methods ...
|
||||
|
||||
pub fn print_info(&self) {
|
||||
println!("Name: {name}, Age: {age}", name = self.name, age = self.age);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/user.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl User pub fn print_info</symbol>
|
||||
<description>Print all the user information</description>
|
||||
</edit>
|
||||
</step>
|
||||
|
||||
<step>
|
||||
Remove the 'email' field from the User struct
|
||||
|
||||
<edit>
|
||||
<path>src/user.rs</path>
|
||||
<operation>delete</operation>
|
||||
<symbol>struct User email</symbol>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/user.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl User fn new</symbol>
|
||||
<description>Remove email parameter from new method</description>
|
||||
</edit>
|
||||
|
||||
</step>
|
||||
</message>
|
||||
|
||||
</example>
|
||||
|
||||
<example language="rust">
|
||||
|
||||
<message role="user">
|
||||
```rs src/vehicle.rs
|
||||
struct Vehicle {
|
||||
make: String,
|
||||
model: String,
|
||||
year: u32,
|
||||
}
|
||||
|
||||
impl Vehicle {
|
||||
fn new(make: String, model: String, year: u32) -> Self {
|
||||
Vehicle { make, model, year }
|
||||
}
|
||||
|
||||
fn print_year(&self) {
|
||||
println!("Year: {}", self.year);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's add a `start_engine` method for the vehicle and print how long it takes to start it.
|
||||
</message>
|
||||
|
||||
<message role="assistant">
|
||||
<step>
|
||||
Add a 'use std::time::Instant;' statement at the beginning of the file
|
||||
|
||||
<edit>
|
||||
<path>src/vehicle.rs</path>
|
||||
<operation>prepend_child</operation>
|
||||
<symbol>#imports</symbol>
|
||||
<description>Add use statement for std::time::Instant</description>
|
||||
</edit>
|
||||
</step>
|
||||
|
||||
<step>
|
||||
Add a new method 'start_engine' in the Vehicle impl block
|
||||
|
||||
```rust
|
||||
impl Vehicle {
|
||||
// ... existing methods ...
|
||||
|
||||
fn start_engine(&self) {
|
||||
let t0 = Instant::now();
|
||||
println!("Starting the engine of {} {} {}. Took: {:?}",
|
||||
self.year, self.make, self.model, t0.elapsed());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/vehicle.rs</path>
|
||||
<operation>append_child</operation>
|
||||
<symbol>impl Vehicle</symbol>
|
||||
<description>Add start_engine method</description>
|
||||
</edit>
|
||||
</step>
|
||||
</message>
|
||||
|
||||
</example>
|
||||
|
||||
<example language="rust">
|
||||
|
||||
<message role="user">
|
||||
```rs src/employee.rs
|
||||
struct Employee {
|
||||
name: String,
|
||||
position: String,
|
||||
salary: u32,
|
||||
department: String,
|
||||
}
|
||||
|
||||
impl Employee {
|
||||
fn new(name: String, position: String, salary: u32, department: String) -> Self {
|
||||
Employee { name, position, salary, department }
|
||||
}
|
||||
|
||||
fn print_details(&self) {
|
||||
println!("Name: {}, Position: {}, Salary: {}, Department: {}",
|
||||
self.name, self.position, self.salary, self.department);
|
||||
}
|
||||
|
||||
fn give_raise(&mut self, amount: u32) {
|
||||
self.salary += amount;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's turn the salary field into an f32. Also, let's delete the department field.
|
||||
</message>
|
||||
|
||||
<message role="assistant">
|
||||
<step>
|
||||
Make salary an f32
|
||||
|
||||
```rust
|
||||
struct Employee {
|
||||
name: String,
|
||||
position: String,
|
||||
salary: f32,
|
||||
department: String,
|
||||
}
|
||||
|
||||
impl Employee {
|
||||
fn new(name: String, position: String, salary: f32, department: String) -> Self {
|
||||
Employee { name, position, salary, department }
|
||||
}
|
||||
|
||||
// ... other methods ...
|
||||
|
||||
fn give_raise(&mut self, amount: f32) {
|
||||
self.salary += amount;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/employee.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>struct Employee salary</symbol>
|
||||
<description>Change salary type to f32</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/employee.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl Employee fn new</symbol>
|
||||
<description>Update new method to use f32 for salary</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/employee.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl Employee fn give_raise</symbol>
|
||||
<description>Update give_raise method to use f32 for amount</description>
|
||||
</edit>
|
||||
</step>
|
||||
|
||||
<step>
|
||||
Remove the 'department' field and update the 'print_details' method
|
||||
|
||||
```rust
|
||||
struct Employee {
|
||||
name: String,
|
||||
position: String,
|
||||
salary: f32,
|
||||
}
|
||||
|
||||
impl Employee {
|
||||
fn new(name: String, position: String, salary: f32) -> Self {
|
||||
Employee { name, position, salary }
|
||||
}
|
||||
|
||||
fn print_details(&self) {
|
||||
println!("Name: {}, Position: {}, Salary: {}",
|
||||
self.name, self.position, self.salary);
|
||||
}
|
||||
|
||||
// ... other methods ...
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/employee.rs</path>
|
||||
<operation>delete</operation>
|
||||
<symbol>struct Employee department</symbol>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/employee.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl Employee fn new</symbol>
|
||||
<description>Remove department parameter from new method</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/employee.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl Employee fn print_details</symbol>
|
||||
<description>Remove department from print_details method</description>
|
||||
</edit>
|
||||
</step>
|
||||
</message>
|
||||
|
||||
</example>
|
||||
|
||||
<example language="rust">
|
||||
|
||||
<message role="user">
|
||||
```rs src/game.rs
|
||||
struct Player {
|
||||
name: String,
|
||||
health: i32,
|
||||
pub score: u32,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new(name: String) -> Self {
|
||||
Player { name, health: 100, score: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Library<T>
|
||||
where
|
||||
T: Display + Clone + PartialEq
|
||||
{
|
||||
// ... (previous methods remain the same)
|
||||
struct Game {
|
||||
players: Vec<Player>,
|
||||
}
|
||||
|
||||
fn remove_book(&mut self, id: &T) -> Result<Book<T>, BookNotFoundError> {
|
||||
if let Some(index) = self.books.iter().position(|book| &book.id == id) {
|
||||
Ok(self.books.remove(index))
|
||||
} else {
|
||||
Err(BookNotFoundError)
|
||||
impl Game {
|
||||
fn new() -> Self {
|
||||
Game { players: Vec::new() }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Introduce a level field.
|
||||
</message>
|
||||
|
||||
<message role="assistant">
|
||||
<step>
|
||||
Add a 'level' field to Player and update the 'new' method
|
||||
|
||||
```rust
|
||||
struct Player {
|
||||
name: String,
|
||||
health: i32,
|
||||
pub score: u32,
|
||||
level: u8,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new(name: String) -> Self {
|
||||
Player { name, health: 100, score: 0, level: 1 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/game.rs</path>
|
||||
<operation>insert_sibling_after</operation>
|
||||
<symbol>struct Player pub score</symbol>
|
||||
<description>Add level field to Player struct</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/game.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl Player pub fn new</symbol>
|
||||
<description>Initialize level in new method</description>
|
||||
</edit>
|
||||
</step>
|
||||
</message>
|
||||
|
||||
</example>
|
||||
|
||||
<example language="rust">
|
||||
|
||||
<message role="user">
|
||||
```rs src/config.rs
|
||||
use std::collections::HashMap;
|
||||
|
||||
struct Config {
|
||||
settings: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn new() -> Self {
|
||||
Config { settings: HashMap::new() }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
I want to load the configuration from a file.
|
||||
</message>
|
||||
|
||||
<message role="assistant">
|
||||
<step>
|
||||
Add a 'load_from_file' method to Config and import necessary modules
|
||||
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead};
|
||||
use std::path::Path;
|
||||
|
||||
struct Config {
|
||||
settings: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn new() -> Self {
|
||||
Config { settings: HashMap::new() }
|
||||
}
|
||||
|
||||
fn load_from_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
let file = File::open(path)?;
|
||||
for line in io::BufReader::new(file).lines() {
|
||||
let line = line?;
|
||||
if let Some((key, value)) = line.split_once('=') {
|
||||
self.settings.insert(key.trim().to_string(), value.trim().to_string());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/config.rs</path>
|
||||
<operation>append_child</operation>
|
||||
<symbol>#imports</symbol>
|
||||
<description>Import necessary modules for file operations</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/config.rs</path>
|
||||
<operation>append_child</operation>
|
||||
<symbol>impl Config</symbol>
|
||||
<description>Add load_from_file method</description>
|
||||
</edit>
|
||||
</step>
|
||||
</message>
|
||||
|
||||
</example>
|
||||
|
||||
<example language="rust">
|
||||
|
||||
<message role="user">
|
||||
```rs src/database.rs
|
||||
pub(crate) struct Database {
|
||||
connection: Connection,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
fn new(url: &str) -> Result<Self, Error> {
|
||||
let connection = Connection::connect(url)?;
|
||||
Ok(Database { connection })
|
||||
}
|
||||
|
||||
async fn query(&self, sql: &str) -> Result<Vec<Row>, Error> {
|
||||
self.connection.query(sql, &[])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
I want to add error handling to this module.
|
||||
</message>
|
||||
|
||||
<message role="assistant">
|
||||
<step>
|
||||
Add error handling to the 'query' method and create a custom error type
|
||||
|
||||
```rust
|
||||
use std::fmt;
|
||||
use std::error::Error as StdError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DatabaseError {
|
||||
ConnectionError(String),
|
||||
QueryError(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for DatabaseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
DatabaseError::ConnectionError(msg) => write!(f, "Connection error: {}", msg),
|
||||
DatabaseError::QueryError(msg) => write!(f, "Query error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</step>
|
||||
<step>Update the main function to demonstrate book removal
|
||||
```rust
|
||||
fn main() {
|
||||
// ... (previous code remains the same)
|
||||
|
||||
// Attempt to remove a book
|
||||
let id_to_remove = String::from("002");
|
||||
match library.remove_book(&id_to_remove) {
|
||||
Ok(removed_book) => println!("Removed book: {} by {}", removed_book.title, removed_book.author),
|
||||
Err(e) => println!("Error removing book: {}", e),
|
||||
impl StdError for DatabaseError {}
|
||||
|
||||
// ...omitted code...
|
||||
|
||||
impl Database {
|
||||
fn new(url: &str) -> Result<Self, DatabaseError> {
|
||||
let connection = Connection::connect(url)
|
||||
.map_err(|e| DatabaseError::ConnectionError(e.to_string()))?;
|
||||
Ok(Database { connection })
|
||||
}
|
||||
|
||||
// Attempt to remove a non-existent book
|
||||
let non_existent_id = String::from("999");
|
||||
match library.remove_book(&non_existent_id) {
|
||||
Ok(_) => println!("Book removed successfully"),
|
||||
Err(e) => println!("Error removing book: {}", e),
|
||||
}
|
||||
|
||||
// Display remaining books
|
||||
println!("\nRemaining books:");
|
||||
for book in &library.books {
|
||||
println!("ID: {}, Title: {} by {}", book.id, book.title, book.author);
|
||||
async fn query(&self, sql: &str) -> Result<Vec<Row>, DatabaseError> {
|
||||
self.connection.query(sql, &[])
|
||||
.await
|
||||
.map_err(|e| DatabaseError::QueryError(e.to_string()))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/database.rs</path>
|
||||
<operation>prepend_child</operation>
|
||||
<symbol>#imports</symbol>
|
||||
<description>Import necessary error handling modules</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/database.rs</path>
|
||||
<operation>insert_sibling_before</operation>
|
||||
<symbol>pub(crate) struct Database</symbol>
|
||||
<description>Define custom DatabaseError enum</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/database.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl Database fn new</symbol>
|
||||
<description>Update new method to use DatabaseError</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/database.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl Database async fn query</symbol>
|
||||
<description>Update query method to use DatabaseError</description>
|
||||
</edit>
|
||||
</step>
|
||||
</message>
|
||||
|
||||
This update includes:
|
||||
1. A new remove_book method that returns a Result type
|
||||
2. A custom BookNotFoundError for error handling
|
||||
3. Demonstration of successful book removal and error handling for non-existent books
|
||||
4. Display of remaining books after removal attempts
|
||||
|
||||
The remove_book method uses the PartialEq trait to compare book IDs, allowing for flexible ID types while maintaining type safety.</assistant>
|
||||
</turn>
|
||||
</example>
|
||||
|
||||
<example language="go">
|
||||
<turn number="1">
|
||||
<user>Create a Person struct with name, age, and email fields, and a method to greet. Include error handling for age.</user>
|
||||
<assistant>Certainly! Let's create a Person struct with the specified fields and a method to greet in Go:
|
||||
You should think step by step. When possible, produce smaller, coherent logical steps as opposed to one big step that combines lots of heterogeneous edits.
|
||||
|
||||
<step>Define the Person struct and NewPerson function with error handling
|
||||
```go
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Email string
|
||||
}
|
||||
|
||||
func NewPerson(name string, age int, email string) (*Person, error) {
|
||||
if age < 0 || age > 150 {
|
||||
return nil, fmt.Errorf("invalid age: %d", age)
|
||||
}
|
||||
return &Person{Name: name, Age: age, Email: email}, nil
|
||||
}
|
||||
```
|
||||
</step>
|
||||
<step>Implement the Greet method for the Person struct
|
||||
```go
|
||||
func (p *Person) Greet() string {
|
||||
return fmt.Sprintf("Hello, I'm %s, %d years old. You can reach me at %s.", p.Name, p.Age, p.Email)
|
||||
}
|
||||
```
|
||||
</step>
|
||||
|
||||
This code defines a Person struct, a NewPerson function with age validation, and a Greet method.</assistant>
|
||||
</turn>
|
||||
|
||||
<turn number="2">
|
||||
<user>How can I use these in a main function?</user>
|
||||
<assistant>Here's how to use the Person struct and its methods in a main function:
|
||||
|
||||
<step>Create a main function with error handling
|
||||
```go
|
||||
func main() {
|
||||
// Create a new Person instance
|
||||
person, err := NewPerson("Alice", 30, "alice@example.com")
|
||||
if err != nil {
|
||||
fmt.Println("Error creating person:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Use the Greet method
|
||||
fmt.Println(person.Greet())
|
||||
|
||||
// Demonstrate error handling with invalid age
|
||||
invalidPerson, err := NewPerson("Bob", 200, "bob@example.com")
|
||||
if err != nil {
|
||||
fmt.Println("Error creating person with invalid age:", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
</step>
|
||||
|
||||
This main function demonstrates:
|
||||
1. Creating a valid Person instance
|
||||
2. Using the Greet method
|
||||
3. Handling errors when creating a Person with an invalid age</assistant>
|
||||
</turn>
|
||||
|
||||
<turn number="3">
|
||||
<user>Can you add a method to update the email with validation?</user>
|
||||
<assistant>Certainly! Let's add a method to update the email with validation:
|
||||
|
||||
<step>Add UpdateEmail method with validation
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func (p *Person) UpdateEmail(newEmail string) error {
|
||||
// Simple email validation using regex
|
||||
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
|
||||
if !emailRegex.MatchString(newEmail) {
|
||||
return fmt.Errorf("invalid email format: %s", newEmail)
|
||||
}
|
||||
p.Email = newEmail
|
||||
return nil
|
||||
}
|
||||
```
|
||||
</step>
|
||||
<step>Update the main function to demonstrate email update
|
||||
```go
|
||||
func main() {
|
||||
// ... (previous code remains the same)
|
||||
|
||||
// Demonstrate email update
|
||||
err = person.UpdateEmail("alice.new@example.com")
|
||||
if err != nil {
|
||||
fmt.Println("Error updating email:", err)
|
||||
} else {
|
||||
fmt.Println("Updated greeting:", person.Greet())
|
||||
}
|
||||
|
||||
// Demonstrate email update with invalid email
|
||||
err = person.UpdateEmail("invalid-email")
|
||||
if err != nil {
|
||||
fmt.Println("Error updating email:", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
</step>
|
||||
|
||||
This update includes:
|
||||
1. An UpdateEmail method with email format validation
|
||||
2. Demonstration of successful email update in the main function
|
||||
3. Handling of invalid email update attempt</assistant>
|
||||
</turn>
|
||||
</example>
|
||||
</workflow>
|
||||
</task_description>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue