Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

On-going PR to merge feature-macro branch #91

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

Conversation

nyurik
Copy link
Collaborator

@nyurik nyurik commented Oct 7, 2024

This PR is used to ensure feature-macro can be merged with main and compile properly.

** MUST BE MERGED WITHOUT SQUASHING **

Implement procedure macro support, replacing the need for the build script and the boilerplate code. Includes a testing framework (using insta), reports multiple compiler errors linking to offending code with tests using trybuild, and autogenerated documentation.

All examples have been migrated in the #85

Usage example

/// Attaching `vmod` attribute to a `mod` exports its content as VMOD functions, methods, and objects
/// The name of VMOD is the name of this module.
/// If `docs = "file"` param is set, a documentation file will be auto-generated on each build.
/// The path is relative to the root of the project.
#[varnish::vmod(docs = "README.md"]
mod my_vmod {
    /// Simple function exported to VCL with a single mandatory param
    pub fn simple(val: i64) -> bool {
        todo!()
    }

    /// Simple function exported to VCL with a positional and an optional param
    pub fn optional(val: i64, opt: Option<i64>) -> bool {
        todo!()
    }

    /// A function that requests a few additional values automatically provided.
    /// Note that this function is identical to the previous one from the VCL perspective.
    pub fn complex(
        /// The context of the current VCL call
        ctx: &Ctx,
        /// Readonly shared struct. Only one "per_vcl" type is allowed per VMOD
        #[shared_per_vcl] shared1: Option<&MyPerVclStruct>,
        /// Mutable per-task struct. Only one "per_task" type is allowed per VMOD
        #[shared_per_task] shared2: &mut Option<Box<MyPerTaskStruct>>,
        /// Same params as the previous function 
        val: i64,
        opt: Option<i64>,
    ) -> Result<i64, &str> {
        todo!()
    }

    /// On VCL event like loading, call this function in an exclusive context. Must be tagged with `#[event]`
    #[event] pub fn on_event(
        /// Same type as in complex, but this time we get exclusive access to it
        #[shared_per_vcl] val: &mut Option<Box<sub::MyPerVclStruct>>,
        /// What event caused this function to be called
        evt: Event,
    ) -> i64 { todo!() }

    /// All these functions will work as methods for the object `MyObject`
    /// The actual object can be declared in another module to reduce clutter in the exported mod.
    impl MyObject {
        /// Constructor for the object. Must be tagged with `#[constructor]`
        pub fn new(
            /// The name of the object in VCL - this is auto-provided, not part of params in VCL
            #[vcl_name] name: &str,
            /// This value is passed from VCL: `new foo = my_vmod.MyObject(42);
            val: i64,
        ) -> Self {
            todo!()
        }

        /// Method on the object - same as regular functions, but with an additional `self` param
        pub fn method(&self, val: i64) -> i64 {
            todo!()
        }
    }
}

/// The actual struct and other non-public functionality should be outside of `mod my_vmod`
struct MyObject;

impl MyObject {
    pub fn non_exported_functions() { todo!() }
}

Converted Examples

  • vmod_test
  • vmod_be
  • vmod_error
  • vmod_event
  • vmod_example
  • vmod_infiniteloop
  • vmod_object
  • vmod_timestamp
  • vmod_vdp
  • vmod_vfp

Notes

Tasks

  • Generate extern "C" wrapper for each fn (partially done, needs a bit more cleanup)
  • Generate struct for optional parameter passing Varnish->Wrapper
  • Generate struct VmodExports with all functions as static callbacks, and instantiate it as global static VMOD_EXPORTS.
  • Generate $CPROTO C header snippet describing function signatures
  • Generate description JSON
  • Generate Vmod_{name}_Data global static instance
  • Object support
  • Optional types
  • Generate documentation
  • Report multiple errors at once, with ui tests
  • Support default values, e.g. fn foo(#[arg(default = "arg")], param: &str)

Other Ideas and TODOs

  • Support for copying result into workspace and returning a ref to it, instead of double-copying
  • Nullable types like SocketAddr, Probe, and COWProbe create a bit of confusion because they do not have to be "optional", they can just be nullable, and in Rust nullable and optional is the same thing. This creates a problem if the user wants a function like foo(ip: SocketAddr, val: i64) -- the first optional won't work properly unless the second is also optional.

Supported types

  • Context type &Ctx or &mut Ctx as input param is supported in all user functions
  • VCL name can be taken as a &str input param for object constructors. Requires #[vcl_name] attribute.
Input Output Rust Varnish C type
VCL_ACL *const vrt_acl
VCLBackendPtr VCL_BACKEND *const director
VCL_BLOB *const vrt_blob
VCL_BODY *const ::std::os::raw::c_void
bool VCL_BOOL ::std::os::raw::c_uint
VCL_BYTES
Duration VCL_DURATION vtim_dur
VCL_ENUM *const ::std::os::raw::c_char
VCL_HEADER *const gethdr_s
VCL_HTTP *mut http
VCL_INSTANCE ::std::os::raw::c_void
i64 VCL_INT
2 SocketAddr VCL_IP *const suckaddr
2 Probe VCL_PROBE *const vrt_backend_probe
2 COWProbe " "
f64 VCL_REAL
VCL_REGEX *const vre
VCL_STEVEDORE *const stevedore
VCL_STRANDS *const strands
VCL_STRING VCL_STRING *const ::std::os::raw::c_char
1 &str " "
String " "
&CStr " "
CString " "
VCL_SUB *const vcl_sub
VCL_TIME vtim_real
VCL_VCL *mut vcl
n/a VCL_VOID ::std::os::raw::c_void
i8
i16
i32
i128
isize
u8
u16
u32
u64
u128
usize
[u8]
Vec<u8>
Box<dyn Error> 3
  • 1 - these types support #[arg(default = ...)] attribute, allowing a function to be called from VCL with this argument set to the VMOD-provided default.
  • 2 - these types must be declared as Option<T> in Rust code, making it both optional and nullable, e.g. Option<SocketAddr>.
  • 3 - Box<dyn Error> is a special case, as it is not a Varnish type, but it is a common return type for error handling in Rust. It is supported as an error return type, but not as an input or output type.

Closes #73

* Use VclError in InitResult
* Use wrapper types for enums
* Handle `&lifetime [u8]` types
* support tuples for shared types
* Change `#[arg]` into `#[default(...)]` and `#[required]`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

New VPriv support
1 participant