Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,33 @@ This is a straight fork from https://github.com/robfig/cron with (currently) two
* [Get the previous schedule time](https://github.com/robfig/cron/pull/361)
* [Support for 'L' expression](https://github.com/robfig/cron/pull/325)

I'll try my best to keep this up to date, tested and merge additional PRs moving forward.
I'll try my best to keep this up to date, tested and merge additional PRs moving forward.

## Rust SDK

A Rust SDK is available in the [wasm](./wasm) directory. It wraps this Go library using Extism (a WebAssembly-based plugin system) to provide cron parsing and scheduling functionality in Rust.

This SDK is designed for use in Rust services that need to call Go code, using Extism as the bridge between the languages.

### Basic Usage

```rust
use cron_sdk::Schedule;
use chrono::Utc;

fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse a cron expression
let schedule = Schedule::parse("0 0 * * *")?; // Midnight every day

// Get the current time
let now = Utc::now();

// Get the next activation time
let next = schedule.next(&now)?;
println!("Next activation: {}", next);

Ok(())
}
```

See the [Rust SDK README](./wasm/rust/README.md) for more details, including custom parse options, benchmarks, and advanced usage examples.
21 changes: 21 additions & 0 deletions wasm/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.PHONY: build-plugin clean

# Directory structure
GO_DIR = go
WASM_OUT_DIR = build

# Build the Go Extism plugin
build-plugin:
@echo "Building Go Extism plugin..."
@mkdir -p $(WASM_OUT_DIR)
cd $(GO_DIR) && go mod tidy
cd $(GO_DIR) && tinygo build -no-debug -target wasi -opt=2 -scheduler=none -o ../$(WASM_OUT_DIR)/cron.wasm .
@wasm-opt --enable-bulk-memory -Oz -o $(WASM_OUT_DIR)/cron.opt.wasm $(WASM_OUT_DIR)/cron.wasm
@mv $(WASM_OUT_DIR)/cron.opt.wasm $(WASM_OUT_DIR)/cron.wasm
@echo "Extism plugin build complete. Output in $(WASM_OUT_DIR)/"

# Clean build artifacts
clean:
@echo "Cleaning build artifacts..."
@rm -rf $(WASM_OUT_DIR)
@echo "Clean complete."
47 changes: 47 additions & 0 deletions wasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Cron Extism SDK

This directory contains a Rust SDK for the cron library, implemented by wrapping the Go library with Extism.

## Structure

- `go/`: Contains the Go Extism plugin that exposes the cron library functionality
- `rust/`: Contains the Rust SDK that uses the Extism plugin
- `Makefile`: Build automation for the Extism plugin

## How It Works

1. The Go Extism plugin (`go/main.go`) exposes key functions from the cron library:
- `parse_schedule`: Parse a cron expression
- `get_next_time`: Get the next activation time
- `get_prev_time`: Get the previous activation time

2. The Makefile compiles the Go code to a WebAssembly plugin for Extism, producing `build/cron.wasm`

3. The Rust SDK loads and executes the Extism plugin, providing a clean API:
- `Schedule::parse()`: Parse a cron expression
- `Schedule::next()`: Get the next activation time
- `Schedule::prev()`: Get the previous activation time

## Building

To build the entire SDK:

```bash
# Build the Extism plugin
cd wasm
make build-plugin

# Build the Rust SDK
cd rust
cargo build
```

## Usage

See the [Rust SDK README](rust/README.md) for detailed usage instructions.

## Requirements

- Go 1.20 or later (with WASI support)
- Rust 1.56 or later
- Extism SDK
Binary file added wasm/build/cron.wasm
Binary file not shown.
12 changes: 12 additions & 0 deletions wasm/go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module github.com/clarkmcc/cron/wasm/go

go 1.21.0

toolchain go1.24.2

require (
github.com/clarkmcc/cron v0.0.0
github.com/extism/go-pdk v1.0.0
)

replace github.com/clarkmcc/cron => ../..
2 changes: 2 additions & 0 deletions wasm/go/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/extism/go-pdk v1.0.0 h1:/VlFLDnpYfooMl+VW94VHrbdruDyKkpa47yYJ7YcCAE=
github.com/extism/go-pdk v1.0.0/go.mod h1:Gz+LIU/YCKnKXhgge8yo5Yu1F/lbv7KtKFkiCSzW/P4=
156 changes: 156 additions & 0 deletions wasm/go/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package main

import (
"encoding/json"
"time"

"github.com/clarkmcc/cron"
"github.com/extism/go-pdk"
)

// Request structure for parsing a cron expression
type ParseRequest struct {
Expression string `json:"expression"`
Options int `json:"options,omitempty"`
}

// Request structure for getting next/prev times
type TimeRequest struct {
Expression string `json:"expression"`
Timestamp int64 `json:"timestamp"`
Options int `json:"options,omitempty"`
}

// Response structure for success/error
type Response struct {
Success bool `json:"success,omitempty"`
Error string `json:"error,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
}

//export parse_schedule
func parse_schedule() int32 {
// Get input from the host
input := pdk.Input()

// Parse the request
var req ParseRequest
if err := json.Unmarshal(input, &req); err != nil {
return writeError("Invalid request format: " + err.Error())
}

if req.Expression == "" {
return writeError("Missing cron expression")
}

p := cron.NewParser(cron.ParseOption(req.Options))

// Parse the cron expression
_, err := p.Parse(req.Expression)
if err != nil {
return writeError("Failed to parse cron expression: " + err.Error())
}

// Return success
resp := Response{Success: true}
return writeResponse(resp)
}

//export get_next_time
func get_next_time() int32 {
// Get input from the host
input := pdk.Input()

// Parse the request
var req TimeRequest
if err := json.Unmarshal(input, &req); err != nil {
return writeError("Invalid request format: " + err.Error())
}

if req.Expression == "" {
return writeError("Missing cron expression")
}

// Create a parser with the provided options or use the default parser
p := cron.NewParser(cron.ParseOption(req.Options))

// Parse the cron expression
schedule, err := p.Parse(req.Expression)
if err != nil {
return writeError("Failed to parse cron expression: " + err.Error())
}

// Calculate the next time
t := time.Unix(req.Timestamp, 0)
next := schedule.Next(t)

// Return the result
resp := Response{
Success: true,
Timestamp: next.Unix(),
}
return writeResponse(resp)
}

//export get_prev_time
func get_prev_time() int32 {
// Get input from the host
input := pdk.Input()

// Parse the request
var req TimeRequest
if err := json.Unmarshal(input, &req); err != nil {
return writeError("Invalid request format: " + err.Error())
}

if req.Expression == "" {
return writeError("Missing cron expression")
}

// Create a parser with the provided options or use the default parser
p := cron.NewParser(cron.ParseOption(req.Options))

// Parse the cron expression
schedule, err := p.Parse(req.Expression)
if err != nil {
return writeError("Failed to parse cron expression: " + err.Error())
}

// Calculate the previous time
t := time.Unix(req.Timestamp, 0)
prev := schedule.Prev(t)

// Return the result
resp := Response{
Success: true,
Timestamp: prev.Unix(),
}
return writeResponse(resp)
}

// Helper function to write an error response
func writeError(message string) int32 {
resp := Response{Error: message}
return writeResponse(resp)
}

// Helper function to write a response
func writeResponse(resp Response) int32 {
data, err := json.Marshal(resp)
if err != nil {
// If we can't marshal the response, create a simple error message
errorData := []byte(`{"error":"Failed to marshal response"}`)
mem := pdk.AllocateBytes(errorData)
pdk.OutputMemory(mem)
return 0
}

// Allocate memory for the response and set it as the output
mem := pdk.AllocateBytes(data)
pdk.OutputMemory(mem)
return 0
}

func main() {
// This function is required but not used with Extism
}
Loading