From a11872a556962a86624bad00e6e66dae46469bee Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Tue, 14 Apr 2026 03:44:59 +0200 Subject: [PATCH] Initial commit --- .gitignore | 20 ++++ 01_declarative/Cargo.toml | 6 ++ 01_declarative/src/main.rs | 31 ++++++ 02_procedural/Cargo.toml | 10 ++ 02_procedural/demo_macro/Cargo.toml | 12 +++ 02_procedural/demo_macro/src/lib.rs | 70 ++++++++++++++ 02_procedural/src/main.rs | 23 +++++ 03_derive/Cargo.toml | 6 ++ 03_derive/src/main.rs | 26 +++++ 04_serde/Cargo.toml | 10 ++ 04_serde/src/main.rs | 45 +++++++++ 04_serde/user.json | 6 ++ 05_sqlx/.env | 1 + 05_sqlx/Cargo.toml | 8 ++ 05_sqlx/docker-compose.yaml | 15 +++ 05_sqlx/migrations/20260414000150_init.sql | 8 ++ 05_sqlx/src/get_users.sql | 1 + 05_sqlx/src/main.rs | 23 +++++ 06_workshop/Cargo.toml | 12 +++ 06_workshop/build.rs | 4 + 06_workshop/src/main.rs | 11 +++ 06_workshop/users.json | 8 ++ 06_workshop/workshop_macro/Cargo.toml | 15 +++ 06_workshop/workshop_macro/src/lib.rs | 34 +++++++ 06_workshop_solution/Cargo.toml | 12 +++ 06_workshop_solution/build.rs | 4 + 06_workshop_solution/schema.json | 21 ++++ 06_workshop_solution/src/main.rs | 11 +++ 06_workshop_solution/users.json | 8 ++ .../workshop_macro/Cargo.toml | 15 +++ .../workshop_macro/src/lib.rs | 96 +++++++++++++++++++ README.md | 17 ++++ 32 files changed, 589 insertions(+) create mode 100644 .gitignore create mode 100644 01_declarative/Cargo.toml create mode 100644 01_declarative/src/main.rs create mode 100644 02_procedural/Cargo.toml create mode 100644 02_procedural/demo_macro/Cargo.toml create mode 100644 02_procedural/demo_macro/src/lib.rs create mode 100644 02_procedural/src/main.rs create mode 100644 03_derive/Cargo.toml create mode 100644 03_derive/src/main.rs create mode 100644 04_serde/Cargo.toml create mode 100644 04_serde/src/main.rs create mode 100644 04_serde/user.json create mode 100644 05_sqlx/.env create mode 100644 05_sqlx/Cargo.toml create mode 100644 05_sqlx/docker-compose.yaml create mode 100644 05_sqlx/migrations/20260414000150_init.sql create mode 100644 05_sqlx/src/get_users.sql create mode 100644 05_sqlx/src/main.rs create mode 100644 06_workshop/Cargo.toml create mode 100644 06_workshop/build.rs create mode 100644 06_workshop/src/main.rs create mode 100644 06_workshop/users.json create mode 100644 06_workshop/workshop_macro/Cargo.toml create mode 100644 06_workshop/workshop_macro/src/lib.rs create mode 100644 06_workshop_solution/Cargo.toml create mode 100644 06_workshop_solution/build.rs create mode 100644 06_workshop_solution/schema.json create mode 100644 06_workshop_solution/src/main.rs create mode 100644 06_workshop_solution/users.json create mode 100644 06_workshop_solution/workshop_macro/Cargo.toml create mode 100644 06_workshop_solution/workshop_macro/src/lib.rs create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..389159a --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust +# Edit at https://www.toptal.com/developers/gitignore?templates=rust + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# End of https://www.toptal.com/developers/gitignore/api/rust diff --git a/01_declarative/Cargo.toml b/01_declarative/Cargo.toml new file mode 100644 index 0000000..fdd32e9 --- /dev/null +++ b/01_declarative/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "demo" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/01_declarative/src/main.rs b/01_declarative/src/main.rs new file mode 100644 index 0000000..88345a3 --- /dev/null +++ b/01_declarative/src/main.rs @@ -0,0 +1,31 @@ +// Declarative macros + +// macro_rules! custom_vec { +// ( $( $x:expr ),* ) => { +// { +// let mut temp_vec = Vec::new(); +// $( +// temp_vec.push($x); +// )* +// temp_vec +// } +// }; +// // ( $( $x:expr ),*; mult = $mult:expr ) => { +// // custom_vec![$( $x * $mult ),*] +// // }; +// } + +fn main() { + let mut a = Vec::new(); + a.push(1); + a.push(2); + a.push(3); + + // let a = vec![1, 2, 3]; + + // let a = custom_vec!(1, 2, 3); + + // let a = custom_vec!(1, 2, 3; mult = 2); + + println!("{a:?}"); +} diff --git a/02_procedural/Cargo.toml b/02_procedural/Cargo.toml new file mode 100644 index 0000000..89a4c0a --- /dev/null +++ b/02_procedural/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "demo" +version = "0.1.0" +edition = "2024" + +[workspace] +members = ["demo_macro"] + +[dependencies] +demo_macro = { path = "demo_macro" } diff --git a/02_procedural/demo_macro/Cargo.toml b/02_procedural/demo_macro/Cargo.toml new file mode 100644 index 0000000..32fa51e --- /dev/null +++ b/02_procedural/demo_macro/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "demo_macro" +version = "0.1.0" +edition = "2024" + +[dependencies] +proc-macro2 = "1.0.106" +quote = "1.0.45" +syn = "2.0.117" + +[lib] +proc-macro = true diff --git a/02_procedural/demo_macro/src/lib.rs b/02_procedural/demo_macro/src/lib.rs new file mode 100644 index 0000000..2d6028e --- /dev/null +++ b/02_procedural/demo_macro/src/lib.rs @@ -0,0 +1,70 @@ +use proc_macro::TokenStream; + +#[proc_macro] +pub fn hello_world(_item: TokenStream) -> TokenStream { + "println!(\"Hello, World\")".parse().unwrap() +} + +// #[proc_macro] +// pub fn make_answer(_item: TokenStream) -> TokenStream { +// "fn answer() -> u32 { 42 }".parse().unwrap() +// } + +// use quote::quote; +// +// #[proc_macro] +// pub fn make_answer_quote(_item: TokenStream) -> TokenStream { +// let value: u32 = 43; +// +// quote! { +// fn answer_quote() -> u32 { +// #value +// } +// } +// .into() +// } + +// use quote::ToTokens; +// use syn::{ +// Error, LitInt, Token, +// parse::{Parse, ParseStream}, +// parse_macro_input, +// }; +// +// struct Custom { +// value: LitInt, +// } +// +// impl Parse for Custom { +// // answer = +// fn parse(input: ParseStream) -> syn::Result { +// let ident: syn::Ident = input.parse()?; +// if ident != "answer" { +// return Err(Error::new(ident.span(), "expected 'answer'")); +// } +// +// input.parse::()?; +// +// let value = input.parse()?; +// +// Ok(Custom { value }) +// } +// } +// +// impl ToTokens for Custom { +// fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { +// self.value.to_tokens(tokens); +// } +// } +// +// #[proc_macro] +// pub fn make_answer_custom(input: TokenStream) -> TokenStream { +// let custom = parse_macro_input!(input as Custom); +// +// quote! { +// fn answer_custom() -> u32 { +// #custom +// } +// } +// .into() +// } diff --git a/02_procedural/src/main.rs b/02_procedural/src/main.rs new file mode 100644 index 0000000..4a23329 --- /dev/null +++ b/02_procedural/src/main.rs @@ -0,0 +1,23 @@ +use demo_macro::hello_world; + +// use demo_macro::make_answer; +// make_answer!(); + +// use demo_macro::make_answer_quote; +// make_answer_quote!(); + +// use demo_macro::make_answer_custom; +// make_answer_custom!(answer = 33); + +fn main() { + hello_world!(); + + // let a = answer(); + // println!("{a}"); + + // let b = answer_quote(); + // println!("{b}"); + + // let c = answer_custom(); + // println!("{c}"); +} diff --git a/03_derive/Cargo.toml b/03_derive/Cargo.toml new file mode 100644 index 0000000..fdd32e9 --- /dev/null +++ b/03_derive/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "demo" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/03_derive/src/main.rs b/03_derive/src/main.rs new file mode 100644 index 0000000..7f92d46 --- /dev/null +++ b/03_derive/src/main.rs @@ -0,0 +1,26 @@ +// Procedural macro +// Derive macro + +// #[derive(Debug)] +struct User { + first_name: String, + last_name: String, + email: String, +} + +impl std::fmt::Display for User { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} {}", self.first_name, self.last_name) + } +} + +fn main() { + let tim = User { + first_name: "Tim".into(), + last_name: "Huizinga".into(), + email: "tim@huizinga.dev".into(), + }; + + println!("User: {tim}"); + // println!("User: {tim:?}"); +} diff --git a/04_serde/Cargo.toml b/04_serde/Cargo.toml new file mode 100644 index 0000000..f73cc37 --- /dev/null +++ b/04_serde/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "demo" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde = { version = "1.0.228", features = ["derive"] } +serde-xml-rs = "0.8.2" +serde_json = "1.0.149" +serde_yaml = "0.9.34" diff --git a/04_serde/src/main.rs b/04_serde/src/main.rs new file mode 100644 index 0000000..b01d8e9 --- /dev/null +++ b/04_serde/src/main.rs @@ -0,0 +1,45 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +// #[serde(rename_all = "camelCase")] +// #[serde(rename_all = "PascalCase")] +enum Access { + Guest { vip: bool }, + Normal, + Admin, +} + +#[derive(Debug, Serialize, Deserialize)] +// #[serde(rename_all = "camelCase")] +// #[serde(rename_all = "PascalCase")] +struct User { + // #[serde(rename = "@firstName")] + first_name: String, + // #[serde(rename = "@lastName")] + last_name: String, + // #[serde(skip)] + // #[serde(rename = "@email")] + email: String, + access: Access, +} + +fn main() { + let content = std::fs::read_to_string("user.json").unwrap(); + let user: User = serde_json::from_str(&content).unwrap(); + + println!("{user:#?}"); + + // let tim = User { + // first_name: "Tim".into(), + // last_name: "Huizinga".into(), + // email: "tim@huizinga.dev".into(), + // access: Access::Normal, + // // access: Access::Guest { vip: true }, + // }; + // + // println!("{}", serde_json::to_string_pretty(&tim).unwrap()); + // + // println!("{}", serde_yaml::to_string(&tim).unwrap()); + // + // println!("{}", serde_xml_rs::to_string(&tim).unwrap()); +} diff --git a/04_serde/user.json b/04_serde/user.json new file mode 100644 index 0000000..7d3756f --- /dev/null +++ b/04_serde/user.json @@ -0,0 +1,6 @@ +{ + "first_name": "Sebastiano", + "last_name": "Tronto", + "email": "sebastiano@tronto.net", + "access": "Normal" +} diff --git a/05_sqlx/.env b/05_sqlx/.env new file mode 100644 index 0000000..455d445 --- /dev/null +++ b/05_sqlx/.env @@ -0,0 +1 @@ +export DATABASE_URL=postgres://postgres:rustiscool@localhost/postgres diff --git a/05_sqlx/Cargo.toml b/05_sqlx/Cargo.toml new file mode 100644 index 0000000..fb3aa35 --- /dev/null +++ b/05_sqlx/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "demo" +version = "0.1.0" +edition = "2024" + +[dependencies] +sqlx = { version = "0.8.6", features = ["macros", "postgres", "runtime-tokio"] } +tokio = { version = "1.51.1", features = ["full"] } diff --git a/05_sqlx/docker-compose.yaml b/05_sqlx/docker-compose.yaml new file mode 100644 index 0000000..1ed8dfe --- /dev/null +++ b/05_sqlx/docker-compose.yaml @@ -0,0 +1,15 @@ +services: + database: + image: postgres:18 + ports: + - 5432:5432 + environment: + - TZ=Europe/Amsterdam + - PGTZ=Europe/Amsterdam + - POSTGRES_PASSWORD=rustiscool + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"] + interval: 10s + retries: 5 + start_period: 10s + timeout: 10s diff --git a/05_sqlx/migrations/20260414000150_init.sql b/05_sqlx/migrations/20260414000150_init.sql new file mode 100644 index 0000000..b64e120 --- /dev/null +++ b/05_sqlx/migrations/20260414000150_init.sql @@ -0,0 +1,8 @@ +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + first_name TEXT NOT NULL, + last_name TEXT NOT NULL +); + +INSERT INTO users (first_name, last_name) VALUES ('Tim', 'Huizinga'); +INSERT INTO users (first_name, last_name) VALUES ('Sebastiano', 'Tronto'); diff --git a/05_sqlx/src/get_users.sql b/05_sqlx/src/get_users.sql new file mode 100644 index 0000000..b3a81ac --- /dev/null +++ b/05_sqlx/src/get_users.sql @@ -0,0 +1 @@ +SELECT (first_name) FROM users; diff --git a/05_sqlx/src/main.rs b/05_sqlx/src/main.rs new file mode 100644 index 0000000..5fb00c8 --- /dev/null +++ b/05_sqlx/src/main.rs @@ -0,0 +1,23 @@ +use sqlx::{Connection, PgConnection}; + +#[tokio::main] +async fn main() { + let url = std::env::var("DATABASE_URL").unwrap(); + let mut conn = PgConnection::connect(&url).await.unwrap(); + + // sqlx::migrate!("migrations").run(&conn).await?; + + let users = sqlx::query!("SELECT * FROM users") + .fetch_all(&mut conn) + .await + .unwrap(); + + // let users = sqlx::query_file!("src/get_users.sql") + // .fetch_all(&mut conn) + // .await + // .unwrap(); + + println!("{users:#?}"); + + // users[0].first_name; +} diff --git a/06_workshop/Cargo.toml b/06_workshop/Cargo.toml new file mode 100644 index 0000000..fa0ee7a --- /dev/null +++ b/06_workshop/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "workshop" +version = "0.1.0" +edition = "2024" + +[workspace] +members = ["workshop_macro"] + +[dependencies] +workshop_macro = { path = "./workshop_macro" } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" diff --git a/06_workshop/build.rs b/06_workshop/build.rs new file mode 100644 index 0000000..401bae8 --- /dev/null +++ b/06_workshop/build.rs @@ -0,0 +1,4 @@ +fn main() { + // Trigger a rebuild if the schema file has changed + println!("cargo::rerun-if-changed=schema.json"); +} diff --git a/06_workshop/src/main.rs b/06_workshop/src/main.rs new file mode 100644 index 0000000..c0a267f --- /dev/null +++ b/06_workshop/src/main.rs @@ -0,0 +1,11 @@ +use workshop_macro::from_schema; + +from_schema!("https://download.huizinga.dev/workshop/schema.json"); + +fn main() { + let content = std::fs::read_to_string("./users.json").unwrap(); + + let users: Vec = serde_json::from_str(&content).unwrap(); + + println!("{users:#?}"); +} diff --git a/06_workshop/users.json b/06_workshop/users.json new file mode 100644 index 0000000..1e68eea --- /dev/null +++ b/06_workshop/users.json @@ -0,0 +1,8 @@ +[ + { + "first_name": "Tim", + "last_name": "Huizinga", + "age": 28, + "admin": true + } +] diff --git a/06_workshop/workshop_macro/Cargo.toml b/06_workshop/workshop_macro/Cargo.toml new file mode 100644 index 0000000..1acd941 --- /dev/null +++ b/06_workshop/workshop_macro/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "workshop_macro" +version = "0.1.0" +edition = "2024" + +[dependencies] +proc-macro2 = "1.0.106" +quote = "1.0.45" +reqwest = { version = "0.13.2", features = ["blocking"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +syn = "2.0.117" + +[lib] +proc-macro = true diff --git a/06_workshop/workshop_macro/src/lib.rs b/06_workshop/workshop_macro/src/lib.rs new file mode 100644 index 0000000..c232aa1 --- /dev/null +++ b/06_workshop/workshop_macro/src/lib.rs @@ -0,0 +1,34 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{LitStr, parse_macro_input}; + +// HINT: Maybe with creating some structs/enums that match with the format of schema.json? + +// HINT: The word `type` is reserved in rust but can still be used if you write `r#type` + +// HINT: Maybe implementing ToTokens can be useful? + +// HINT: You can access fields in the quote macro (e.g. `#schema.name`) so instead first bind it +// to a local variable (e.g. `let name = &schema.name`). + +// HINT: If you place use a string variable inside of the quote macro it will place it as a +// string (e.g. "User"). You need to first create a syn::Ident +// (see: https://docs.rs/syn/latest/syn/struct.Ident.html) + +// HINT: If you have a iterable of qoutes! you can use and expand this in another quote macro +// with `#(#fields),*`, similar to how it works with declarative macros. + +// HINT: Since the macro will be placed in the context of the "callsite" it is best to use the +// fully qualified path (e.g. `serde::Deserialize`), otherwise the user of the macro needs to +// manually add `use serde::Deserialize` to their file. + +#[proc_macro] +pub fn from_schema(input: TokenStream) -> TokenStream { + let url = parse_macro_input!(input as LitStr).value(); + let schema = reqwest::blocking::get(url).unwrap().text().unwrap(); + + // TODO: Print out the received schema text during compilation + println!("{schema:#?}"); + + quote! {}.into() +} diff --git a/06_workshop_solution/Cargo.toml b/06_workshop_solution/Cargo.toml new file mode 100644 index 0000000..fa0ee7a --- /dev/null +++ b/06_workshop_solution/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "workshop" +version = "0.1.0" +edition = "2024" + +[workspace] +members = ["workshop_macro"] + +[dependencies] +workshop_macro = { path = "./workshop_macro" } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" diff --git a/06_workshop_solution/build.rs b/06_workshop_solution/build.rs new file mode 100644 index 0000000..401bae8 --- /dev/null +++ b/06_workshop_solution/build.rs @@ -0,0 +1,4 @@ +fn main() { + // Trigger a rebuild if the schema file has changed + println!("cargo::rerun-if-changed=schema.json"); +} diff --git a/06_workshop_solution/schema.json b/06_workshop_solution/schema.json new file mode 100644 index 0000000..17ac032 --- /dev/null +++ b/06_workshop_solution/schema.json @@ -0,0 +1,21 @@ +{ + "name": "User", + "fields": [ + { + "name": "first_name", + "type": "string" + }, + { + "name": "last_name", + "type": "string" + }, + { + "name": "age", + "type": "number" + }, + { + "name": "admin", + "type": "bool" + } + ] +} diff --git a/06_workshop_solution/src/main.rs b/06_workshop_solution/src/main.rs new file mode 100644 index 0000000..c0a267f --- /dev/null +++ b/06_workshop_solution/src/main.rs @@ -0,0 +1,11 @@ +use workshop_macro::from_schema; + +from_schema!("https://download.huizinga.dev/workshop/schema.json"); + +fn main() { + let content = std::fs::read_to_string("./users.json").unwrap(); + + let users: Vec = serde_json::from_str(&content).unwrap(); + + println!("{users:#?}"); +} diff --git a/06_workshop_solution/users.json b/06_workshop_solution/users.json new file mode 100644 index 0000000..1e68eea --- /dev/null +++ b/06_workshop_solution/users.json @@ -0,0 +1,8 @@ +[ + { + "first_name": "Tim", + "last_name": "Huizinga", + "age": 28, + "admin": true + } +] diff --git a/06_workshop_solution/workshop_macro/Cargo.toml b/06_workshop_solution/workshop_macro/Cargo.toml new file mode 100644 index 0000000..1acd941 --- /dev/null +++ b/06_workshop_solution/workshop_macro/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "workshop_macro" +version = "0.1.0" +edition = "2024" + +[dependencies] +proc-macro2 = "1.0.106" +quote = "1.0.45" +reqwest = { version = "0.13.2", features = ["blocking"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +syn = "2.0.117" + +[lib] +proc-macro = true diff --git a/06_workshop_solution/workshop_macro/src/lib.rs b/06_workshop_solution/workshop_macro/src/lib.rs new file mode 100644 index 0000000..7b18fd5 --- /dev/null +++ b/06_workshop_solution/workshop_macro/src/lib.rs @@ -0,0 +1,96 @@ +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::{ToTokens, quote}; +use serde::Deserialize; +use syn::{Ident, LitStr, parse_macro_input}; + +// HINT: Maybe with creating some structs/enums that match with the format of schema.json? + +// HINT: The word `type` is reserved in rust but can still be used if you write `r#type` + +// HINT: Maybe implementing ToTokens can be useful? + +// HINT: You can access fields in the quote macro (e.g. `#schema.name`) so instead first bind it +// to a local variable (e.g. `let name = &schema.name`). + +// HINT: If you place use a string variable inside of the quote macro it will place it as a +// string (e.g. "User"). You need to first create a syn::Ident +// (see: https://docs.rs/syn/latest/syn/struct.Ident.html) + +// HINT: If you have a iterable of qoutes! you can use and expand this in another quote macro +// with `#(#fields),*`, similar to how it works with declarative macros. + +// HINT: Since the macro will be placed in the context of the "callsite" it is best to use the +// fully qualified path (e.g. `serde::Deserialize`), otherwise the user of the macro needs to +// manually add `use serde::Deserialize` to their file. + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +enum Type { + String, + Number, + Bool, +} + +impl ToTokens for Type { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let ts = match self { + Type::String => quote! { String }, + Type::Number => quote! { i64 }, + Type::Bool => quote! { bool }, + }; + + tokens.extend(ts); + } +} + +#[derive(Debug, Deserialize)] +struct Field { + name: String, + r#type: Type, +} +impl ToTokens for Field { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let name = Ident::new(&self.name, Span::call_site()); + let r#type = &self.r#type; + + tokens.extend(quote! { + #name: #r#type + }); + } +} + +#[derive(Debug, Deserialize)] +struct Schema { + name: String, + fields: Vec, +} + +impl ToTokens for Schema { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let name = Ident::new(&self.name, Span::call_site()); + + let fields = &self.fields; + + tokens.extend(quote! { + #[derive(Debug, serde::Deserialize)] + struct #name { + #(#fields),* + } + }); + } +} + +#[proc_macro] +pub fn from_schema(input: TokenStream) -> TokenStream { + let url = parse_macro_input!(input as LitStr).value(); + let schema = reqwest::blocking::get(url).unwrap().text().unwrap(); + + let schema: Schema = serde_json::from_str(&schema).unwrap(); + + // HINT: There are two different TokenStreams, proc_macro::TokenStream (which the macro should + // return) and proc_macro2::TokenStream (which syn uses). This can be a bit confusing, so you + // will likely need to call `.into()` on your final TokenStream to convert it to the correct + // type. + schema.to_token_stream().into() +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..3498f20 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Knowledge Group Advanced Software: Rust macros + +This repo contains the examples and short workshop (and possible solution) from the talk. + +## Notes: + +Example 05 requires that you are running a postgres database, this can be started by running `docker compose up` in the example directory. +It also requires that the `DATABASE_URL` environment variable is set, this can be achieved by sourcing the `.env` file. + +To setup the schema and data in the database you can use `sqlx-cli` (install: `cargo install sqlx-cli`), to apply to migrations run: + +```bash +sqlx migrate run +``` + +For the workshop in 06 several hints are giving on things that might cause trouble. +To help debug the macro you can install `cargo expand`: `cargo install cargo-expand`.