Skip to content

Commit

Permalink
Implement proc macro support
Browse files Browse the repository at this point in the history
  • Loading branch information
nyurik committed Sep 15, 2024
1 parent 00e1ef4 commit e4e57b4
Show file tree
Hide file tree
Showing 28 changed files with 2,646 additions and 144 deletions.
21 changes: 19 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ serde = ["dep:serde"]
[dependencies]
pkg-config.workspace = true
serde = { workspace = true, optional = true }
varnish-macros.workspace = true

[build-dependencies]
pkg-config.workspace = true
Expand All @@ -29,19 +30,35 @@ workspace = true
#

[workspace]
members = ["vmod_test", "examples/vmod_*"]
members = ["vmod_test", "examples/vmod_*", "varnish-macros"]

[workspace.package]
# These fields could be used by multiple crates
# These fields are used by multiple crates, so it's defined here.
# Version must also be updated in the `varnish-macros` dependency below.
version = "0.0.19"
repository = "https://github.com/gquintard/varnish-rs"
edition = "2021"
license = "BSD-3-Clause"

[workspace.dependencies]
# This version must match the one in the [workspace.package] section above
varnish-macros = { path = "./varnish-macros", version = "=0.0.19" }
# These dependencies are used by one or more crates, and easier to maintain in one place.
bindgen = "0.70.1"
insta = "1"
pkg-config = "0.3.30"
prettyplease = "0.2.22"
proc-macro2 = "1.0.86"
quote = "1.0.37"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.10.8"
syn = "2.0.77"

[profile.dev.package]
# Optimize build speed -- https://docs.rs/insta/latest/insta/#optional-faster-runs
insta.opt-level = 3
similar.opt-level = 3

[workspace.lints.rust]
unused_qualifications = "warn"
Expand Down
3 changes: 0 additions & 3 deletions examples/vmod_error/build.rs

This file was deleted.

98 changes: 52 additions & 46 deletions examples/vmod_error/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,55 @@
varnish::boilerplate!();

use std::fs::read_to_string;

use varnish::vcl::ctx::Ctx;

varnish::vtc!(test01);

// no error, just return 0 if anything goes wrong
pub fn cannot_fail(_: &Ctx, fp: &str) -> i64 {
// try to read the path at fp into a string, but return if there was an error
let Ok(content) = read_to_string(fp) else {
return 0;
};

// try to convert the string into an i64, if parsing fails, force 0
// no need to return as the last expression is automatically returned
content.parse::<i64>().unwrap_or(0)
}

// we call ctx.fail() ourselves, but we still need to return an i64 (which will
// be discarded), so we just convert the 0_u8 returned into an i64 (.into() is
// smart enough to infer the type)
pub fn manual_fail(ctx: &mut Ctx, fp: &str) -> i64 {
// try to read the path at fp into a string, but return if there was an error
let Ok(content) = read_to_string(fp) else {
return ctx
.fail("manual_fail: couldn't read file into string")
.into();
};

// try to convert the string into an i64
// no need to return as the last expression is automatically returned
content
.parse::<i64>()
.unwrap_or_else(|_| ctx.fail("manual_fail: conversion failed").into())
/// This is a simple example of how to handle errors in a Varnish VMOD.
#[varnish::vmod]
mod error {
use std::fs::read_to_string;

use varnish::vcl::ctx::Ctx;

/// This function never fails, returning 0 if anything goes wrong
pub fn cannot_fail(path: &str) -> i64 {
// try to read the path at fp into a string, but return if there was an error
let Ok(content) = read_to_string(path) else {
return 0;
};

// try to convert the string into an i64, if parsing fails, force 0
// no need to return as the last expression is automatically returned
content.parse::<i64>().unwrap_or(0)
}

// we call ctx.fail() ourselves, but we still need to return an i64 (which will
// be discarded), so we just convert the 0_u8 returned into an i64 (.into() is
// smart enough to infer the type)
pub fn manual_fail(ctx: &mut Ctx, fp: &str) -> i64 {
// try to read the path at fp into a string, but return if there was an error
let Ok(content) = read_to_string(fp) else {
return ctx
.fail("manual_fail: couldn't read file into string")
.into();
};

// try to convert the string into an i64
// no need to return as the last expression is automatically returned
content
.parse::<i64>()
.unwrap_or_else(|_| ctx.fail("manual_fail: conversion failed").into())
}

// more idiomatic, we return a Result, and the generated boilerplate will be in charge of
// calling `ctx.fail() and return a dummy value
pub fn result_fail(fp: &str) -> Result<i64, String> {
read_to_string(fp) // read the file
.map_err(|e| format!("result_fail: {e}"))? // convert the error (if any!), into a string
// the ? will automatically return in case
// of an error
.parse::<i64>() // convert
// map the type, and we are good to
// automatically return
.map_err(|e| format!("result_fail: {e}"))
}
}

// more idiomatic, we return a Result, and the generated boilerplate will be in charge of
// calling `ctx.fail() and return a dummy value
pub fn result_fail(_: &mut Ctx, fp: &str) -> Result<i64, String> {
read_to_string(fp) // read the file
.map_err(|e| format!("result_fail: {e}"))? // convert the error (if any!), into a string
// the ? will automatically return in case
// of an error
.parse::<i64>() // convert
.map_err(|e| format!("result_fail: {e}")) // map the type, and we are good to
// automatically return
#[cfg(test)]
mod tests {
varnish::vtc!(test01);
}
File renamed without changes.
4 changes: 0 additions & 4 deletions examples/vmod_example/build.rs

This file was deleted.

65 changes: 25 additions & 40 deletions examples/vmod_example/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,60 +1,45 @@
// import the generated boilerplate
varnish::boilerplate!();
#[varnish::vmod]
mod example {
// we now implement both functions from vmod.vcc, but with rust types.
// Don't forget to make the function public with "pub" in front of them

// even though we won't use it here, we still need to know what the context type is
use varnish::vcl::ctx::Ctx;

// we now implement both functions from vmod.vcc, but with rust types.
// Don't forget to make the function public with "pub" in front of them

pub fn is_even(_: &Ctx, n: i64) -> bool {
n % 2 == 0
}
pub fn is_even(n: i64) -> bool {
n % 2 == 0
}

// in vmod.vcc, n was an optional INT, so here it translates into an Option<i64>
pub fn captain_obvious(_: &Ctx, opt: Option<i64>) -> String {
// we need to first "match" to know if a number was provided, if not,
// return a default message, otherwise, build a custom one
match opt {
// no need to return, we are the last expression of the function!
None => String::from("I was called without an argument"),
// pattern matching FTW!
Some(n) => format!("I was given {n} as argument"),
// in vmod.vcc, n was an optional INT, so here it translates into an Option<i64>
pub fn captain_obvious(opt: Option<i64>) -> String {
// we need to first "match" to know if a number was provided, if not,
// return a default message, otherwise, build a custom one
match opt {
// no need to return, we are the last expression of the function!
None => String::from("I was called without an argument"),
// pattern matching FTW!
Some(n) => format!("I was given {n} as argument"),
}
}
}

#[cfg(test)]
mod tests {
use varnish::vcl::ctx::TestCtx;
use super::example::*;

use super::*;
// test with test/test01.vtc
varnish::vtc!(test01);

#[test]
fn obviousness() {
let mut test_ctx = TestCtx::new(100);
let ctx = test_ctx.ctx();

assert_eq!(
"I was called without an argument",
captain_obvious(&ctx, None)
);
assert_eq!("I was called without an argument", captain_obvious(None));
assert_eq!(
"I was given 975322 as argument",
captain_obvious(&ctx, Some(975_322))
captain_obvious(Some(975_322))
);
}

#[test]
fn even_test() {
// we don't use it, but we still need one
let mut test_ctx = TestCtx::new(100);
let ctx = test_ctx.ctx();

assert!(is_even(&ctx, 0));
assert!(is_even(&ctx, 1024));
assert!(!is_even(&ctx, 421_321));
assert!(is_even(0));
assert!(is_even(1024));
assert!(!is_even(421_321));
}

// we also want to run test/test01.vtc
varnish::vtc!(test01);
}
File renamed without changes.
3 changes: 0 additions & 3 deletions examples/vmod_infiniteloop/build.rs

This file was deleted.

42 changes: 24 additions & 18 deletions examples/vmod_infiniteloop/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
varnish::boilerplate!();
#[varnish::vmod]
mod infiniteloop {
use varnish::ffi::{BUSYOBJ_MAGIC, REQ_MAGIC};
use varnish::vcl::ctx::Ctx;

use varnish::ffi::{BUSYOBJ_MAGIC, REQ_MAGIC};
use varnish::vcl::ctx::Ctx;

varnish::vtc!(test01);

/// # Safety
/// this function is unsafe from the varnish point of view, doing away with
/// important safeguards, but it's also unsafe in the rust way: it dereferences
/// pointers which may lead nowhere
pub unsafe fn reset(ctx: &mut Ctx) {
if let Some(req) = ctx.raw.req.as_mut() {
assert_eq!(req.magic, REQ_MAGIC);
req.restarts = 0;
}
if let Some(bo) = ctx.raw.bo.as_mut() {
assert_eq!(bo.magic, BUSYOBJ_MAGIC);
bo.retries = 0;
/// # Safety
/// this function is unsafe from the varnish point of view, doing away with
/// important safeguards, but it's also unsafe in the rust way: it dereferences
/// pointers which may lead nowhere
pub fn reset(ctx: &mut Ctx) {
unsafe {
if let Some(req) = ctx.raw.req.as_mut() {
assert_eq!(req.magic, REQ_MAGIC);
req.restarts = 0;
}
if let Some(bo) = ctx.raw.bo.as_mut() {
assert_eq!(bo.magic, BUSYOBJ_MAGIC);
bo.retries = 0;
}
}
}
}

#[cfg(test)]
mod tests {
varnish::vtc!(test01);
}
File renamed without changes.
3 changes: 0 additions & 3 deletions examples/vmod_timestamp/build.rs

This file was deleted.

49 changes: 27 additions & 22 deletions examples/vmod_timestamp/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
varnish::boilerplate!();
#[varnish::vmod]
mod timestamp {
use std::mem;
use std::time::{Duration, Instant};

use std::time::{Duration, Instant};
/// Returns the time interval since the last time this function was called in the current task's context.
/// There could be only one type of per-task shared context data type in a Varnish VMOD.
pub fn timestamp(#[shared_per_task] task_ctx: &mut Option<Box<Instant>>) -> Duration {
// we will need this either way
let now = Instant::now();

use varnish::vcl::ctx::Ctx;
use varnish::vcl::vpriv::VPriv;

varnish::vtc!(test01);

// VPriv can wrap any (possibly custom) struct, here we only need an Instant from std::time.
// Storing and getting is up to the vmod writer but this removes the worry of NULL dereferencing
// and of the memory management
pub fn timestamp(_: &Ctx, vp: &mut VPriv<Instant>) -> Duration {
// we will need this either way
let now = Instant::now();
match task_ctx {
None => {
// This is the first time we're running this function in the task's context
*task_ctx = Some(Box::new(now));
Duration::default()
}
Some(task_ctx) => {
// Update box content in-place to the new value, and get the old value
let old_now = mem::replace(&mut **task_ctx, now);
// Since Instant implements Copy, we can continue using it and subtract the old value
now.duration_since(old_now)
}
}
}
}

let interval = match vp.as_ref() {
// if `.get()` returns None, we just store `now` and interval is 0
None => Duration::new(0, 0),
// if there was a value, compute the difference with now
Some(old_now) => now.duration_since(*old_now),
};
// store the current time and return `interval`
vp.store(now);
interval
#[cfg(test)]
mod tests {
varnish::vtc!(test01);
}
File renamed without changes.
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ pub mod vcl {
mod utils;
}

pub use varnish_macros::vmod;

/// Automate VTC testing
///
/// Varnish provides a very handy tool for end-to-end testing:
Expand Down
Loading

0 comments on commit e4e57b4

Please sign in to comment.