{"id":45579962,"url":"https://github.com/openjobspec/ojs-rust-sdk","last_synced_at":"2026-02-28T17:07:49.703Z","repository":{"id":338688941,"uuid":"1156988224","full_name":"openjobspec/ojs-rust-sdk","owner":"openjobspec","description":"The official Rust SDK for Open Job Spec (OJS), a language-agnostic standard for background job processing.","archived":false,"fork":false,"pushed_at":"2026-02-23T14:49:57.000Z","size":220,"stargazers_count":0,"open_issues_count":6,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-23T18:08:48.491Z","etag":null,"topics":["async","background-jobs","job-queue","ojs","openjobspec","rust","sdk","tokio","worker"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/openjobspec.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-13T09:44:04.000Z","updated_at":"2026-02-18T20:04:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/openjobspec/ojs-rust-sdk","commit_stats":null,"previous_names":["openjobspec/ojs-rust-sdk"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/openjobspec/ojs-rust-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openjobspec%2Fojs-rust-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openjobspec%2Fojs-rust-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openjobspec%2Fojs-rust-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openjobspec%2Fojs-rust-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/openjobspec","download_url":"https://codeload.github.com/openjobspec/ojs-rust-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openjobspec%2Fojs-rust-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29943804,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-28T13:49:17.081Z","status":"ssl_error","status_checked_at":"2026-02-28T13:48:50.396Z","response_time":90,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","background-jobs","job-queue","ojs","openjobspec","rust","sdk","tokio","worker"],"created_at":"2026-02-23T11:42:11.142Z","updated_at":"2026-02-28T17:07:49.690Z","avatar_url":"https://github.com/openjobspec.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ojs - Open Job Spec SDK for Rust\n\n[![CI](https://github.com/openjobspec/ojs-rust-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/openjobspec/ojs-rust-sdk/actions/workflows/ci.yml)\n[![Crates.io](https://img.shields.io/crates/v/ojs.svg)](https://crates.io/crates/ojs)\n[![docs.rs](https://docs.rs/ojs/badge.svg)](https://docs.rs/ojs)\n[![License](https://img.shields.io/crates/l/ojs.svg)](LICENSE)\n[![MSRV](https://img.shields.io/badge/MSRV-1.75-blue.svg)](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)\n\nThe official Rust SDK for the [Open Job Spec](https://openjobspec.org) (OJS) protocol. OJS is a language-agnostic specification for background job processing, providing interoperability across languages and backends.\n\n\u003e **🚀 Try it now:** [Open in Playground](https://playground.openjobspec.org?lang=rust) · [Run on CodeSandbox](https://codesandbox.io/p/sandbox/openjobspec-rust-quickstart) · [Docker Quickstart](https://github.com/openjobspec/openjobspec/blob/main/docker-compose.quickstart.yml)\n\n## Features\n\n- **Async-first** - Built on `tokio` for high-performance async I/O\n- **Type-safe** - Strong typing with `serde` serialization/deserialization\n- **Typed handlers** - Generic `register_typed::\u003cT\u003e()` for compile-time arg safety\n- **Middleware** - Tower-inspired middleware chain for cross-cutting concerns (logging, tracing, metrics, OpenTelemetry)\n- **Workflows** - Chain, group, and batch workflow primitives\n- **Builder pattern** - Ergonomic client and worker configuration\n- **Full OJS compliance** - Implements OJS v1.0 specification\n\n## Installation\n\nAdd to your `Cargo.toml`:\n\n```toml\n[dependencies]\nojs = \"0.1\"\ntokio = { version = \"1\", features = [\"full\"] }\nserde_json = \"1\"\n```\n\n## Quick Start\n\n### Enqueuing Jobs (Producer)\n\n```rust\nuse ojs::{Client, RetryPolicy};\nuse serde_json::json;\nuse std::time::Duration;\n\n#[tokio::main]\nasync fn main() -\u003e ojs::Result\u003c()\u003e {\n    let client = Client::builder()\n        .url(\"http://localhost:8080\")\n        .build()?;\n\n    // Simple enqueue\n    let job = client\n        .enqueue(\"email.send\", json!({\"to\": \"user@example.com\"}))\n        .await?;\n\n    // Enqueue with options\n    let job = client\n        .enqueue(\"report.generate\", json!({\"id\": 42}))\n        .queue(\"reports\")\n        .delay(Duration::from_secs(300))\n        .retry(RetryPolicy::new().max_attempts(5))\n        .send()\n        .await?;\n\n    Ok(())\n}\n```\n\n### Processing Jobs (Consumer)\n\n```rust\nuse ojs::{Worker, JobContext};\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -\u003e ojs::Result\u003c()\u003e {\n    let worker = Worker::builder()\n        .url(\"http://localhost:8080\")\n        .queues(vec![\"default\", \"email\"])\n        .concurrency(10)\n        .build()?;\n\n    worker.register(\"email.send\", |ctx: JobContext| async move {\n        let to: String = ctx.job.arg(\"to\")?;\n        // send the email...\n        Ok(json!({\"message_id\": \"msg_123\"}))\n    }).await;\n\n    worker.start().await?;\n    Ok(())\n}\n```\n\n### Workflows\n\n```rust\nuse ojs::{Client, chain, group, batch, Step, BatchCallbacks};\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -\u003e ojs::Result\u003c()\u003e {\n    let client = Client::builder()\n        .url(\"http://localhost:8080\")\n        .build()?;\n\n    // Chain: sequential execution (A -\u003e B -\u003e C)\n    let workflow = client.create_workflow(\n        chain(vec![\n            Step::new(\"data.fetch\", json!({\"url\": \"https://api.example.com\"})),\n            Step::new(\"data.transform\", json!({\"format\": \"csv\"})),\n            Step::new(\"data.notify\", json!({\"channel\": \"slack\"})),\n        ]).name(\"ETL Pipeline\")\n    ).await?;\n\n    // Group: parallel execution\n    let workflow = client.create_workflow(\n        group(vec![\n            Step::new(\"export.csv\", json!({\"id\": 1})),\n            Step::new(\"export.pdf\", json!({\"id\": 1})),\n        ])\n    ).await?;\n\n    // Batch: parallel with callbacks\n    let workflow = client.create_workflow(\n        batch(\n            BatchCallbacks::new()\n                .on_complete(Step::new(\"report\", json!({}))),\n            vec![\n                Step::new(\"email.send\", json!({\"to\": \"a@b.com\"})),\n                Step::new(\"email.send\", json!({\"to\": \"c@d.com\"})),\n            ],\n        )\n    ).await?;\n\n    Ok(())\n}\n```\n\n### Middleware\n\n```rust\nuse ojs::{Worker, Middleware, Next, JobContext, BoxFuture, HandlerResult};\n\nstruct LoggingMiddleware;\n\nimpl Middleware for LoggingMiddleware {\n    fn handle(\u0026self, ctx: JobContext, next: Next) -\u003e BoxFuture\u003c'static, HandlerResult\u003e {\n        Box::pin(async move {\n            let start = std::time::Instant::now();\n            let result = next.run(ctx).await;\n            println!(\"Job processed in {:?}\", start.elapsed());\n            result\n        })\n    }\n}\n\n#[tokio::main]\nasync fn main() -\u003e ojs::Result\u003c()\u003e {\n    let worker = Worker::builder()\n        .url(\"http://localhost:8080\")\n        .build()?;\n\n    worker.use_middleware(\"logging\", LoggingMiddleware).await;\n    // register handlers...\n    worker.start().await\n}\n```\n\n### Typed Handlers\n\nAuto-deserialize job args with compile-time type safety:\n\n```rust\nuse ojs::{Worker, JobContext};\nuse serde::Deserialize;\nuse serde_json::json;\n\n#[derive(Deserialize)]\nstruct EmailArgs {\n    to: String,\n    subject: String,\n}\n\n#[tokio::main]\nasync fn main() -\u003e ojs::Result\u003c()\u003e {\n    let worker = Worker::builder()\n        .url(\"http://localhost:8080\")\n        .build()?;\n\n    // Type-safe: args auto-deserialized via serde\n    worker.register_typed(\"email.send\", |ctx: JobContext, args: EmailArgs| async move {\n        println!(\"Sending to {}: {}\", args.to, args.subject);\n        Ok(json!({\"status\": \"sent\"}))\n    }).await;\n\n    worker.start().await\n}\n```\n\n### OpenTelemetry Integration\n\nNative OTel tracing and metrics (enable `otel-middleware` feature):\n\n```toml\n[dependencies]\nojs = { version = \"0.1\", features = [\"otel-middleware\"] }\n```\n\n```rust\nuse ojs::otel::{OtelTracingMiddleware, OtelMetricsMiddleware};\n\n// Uses global OTel providers by default\nworker.use_middleware(\"otel-tracing\", OtelTracingMiddleware::new()).await;\nworker.use_middleware(\"otel-metrics\", OtelMetricsMiddleware::new()).await;\n```\n\nRecorded spans: `ojs.job {type}` with attributes `ojs.job.type`, `ojs.job.id`, `ojs.job.queue`, `ojs.job.attempt`.\nRecorded metrics: `ojs.job.started`, `ojs.job.completed`, `ojs.job.failed` (counters), `ojs.job.duration` (histogram).\n\n## Architecture\n\n```mermaid\ngraph LR\n    subgraph Producer\n        C[Client] --\u003e|enqueue| S[OJS Server]\n    end\n    subgraph Consumer\n        S --\u003e|fetch| W[Worker]\n        W --\u003e|ack/nack| S\n    end\n    subgraph Middleware Chain\n        W --\u003e M1[OTel Tracing]\n        M1 --\u003e M2[Metrics]\n        M2 --\u003e M3[Timeout]\n        M3 --\u003e H[Handler]\n    end\n```\n\n```mermaid\nstateDiagram-v2\n    [*] --\u003e Running : start()\n    Running --\u003e Quiet : server directive\n    Running --\u003e Terminate : ctrl+c / shutdown\n    Quiet --\u003e Terminate : ctrl+c / shutdown\n    Terminate --\u003e [*] : grace period\n```\n\n## API Reference\n\n### Client\n\n| Method | Description |\n|--------|-------------|\n| `enqueue(type, args)` | Enqueue a single job |\n| `enqueue_batch(requests)` | Atomically enqueue multiple jobs |\n| `get_job(id)` | Get job details |\n| `cancel_job(id)` | Cancel a job |\n| `create_workflow(def)` | Create a workflow |\n| `get_workflow(id)` | Get workflow status |\n| `cancel_workflow(id)` | Cancel a workflow |\n| `list_queues()` | List all queues |\n| `get_queue_stats(name)` | Get queue statistics |\n| `pause_queue(name)` | Pause a queue |\n| `resume_queue(name)` | Resume a queue |\n| `list_dead_letter_jobs(...)` | List dead letter jobs |\n| `retry_dead_letter_job(id)` | Retry a dead letter job |\n| `list_cron_jobs()` | List cron jobs |\n| `register_cron_job(req)` | Register a cron job |\n| `health()` | Server health check |\n| `manifest()` | Server conformance manifest |\n\n### Worker\n\n| Method | Description |\n|--------|-------------|\n| `register(type, handler)` | Register a job handler |\n| `register_typed::\u003cT\u003e(type, handler)` | Register a typed handler with auto-deserialization |\n| `use_middleware(name, mw)` | Add middleware |\n| `start()` | Start processing (blocks until shutdown) |\n| `state()` | Get current worker state |\n| `id()` | Get worker ID |\n\n## Feature Flags\n\n| Feature | Description |\n|---------|-------------|\n| `reqwest-transport` | HTTP transport via reqwest (default) |\n| `common-middleware` | Built-in logging, timeout, metrics middleware |\n| `tracing-middleware` | Structured tracing spans via `tracing` crate |\n| `otel-middleware` | Native OpenTelemetry tracing and metrics |\n| `testing` | Test utilities and mock builders |\n\n## MSRV\n\nThe minimum supported Rust version is **1.75**.\n\n## License\n\nApache-2.0\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenjobspec%2Fojs-rust-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopenjobspec%2Fojs-rust-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenjobspec%2Fojs-rust-sdk/lists"}