#[cfg(feature = "ext-bazaar-discovery")]
#[cfg_attr(docsrs, doc(cfg(feature = "ext-bazaar-discovery")))]
pub mod discovery;
use compact_str::CompactString;
use r402_protocol::extension::{AdvertiseContext, Extension};
use r402_protocol::payment::ExtensionEntry;
use serde_json::{Value, json};
pub const BAZAAR_KEY: &str = "bazaar";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BazaarQueryMethod {
Get,
Head,
Delete,
}
impl BazaarQueryMethod {
const fn as_str(self) -> &'static str {
match self {
Self::Get => "GET",
Self::Head => "HEAD",
Self::Delete => "DELETE",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BazaarBodyMethod {
Post,
Put,
Patch,
}
impl BazaarBodyMethod {
const fn as_str(self) -> &'static str {
match self {
Self::Post => "POST",
Self::Put => "PUT",
Self::Patch => "PATCH",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BazaarBodyType {
Json,
FormData,
Text,
}
impl BazaarBodyType {
const fn as_str(self) -> &'static str {
match self {
Self::Json => "json",
Self::FormData => "form-data",
Self::Text => "text",
}
}
}
#[derive(Debug, Clone)]
pub struct BazaarExtension {
info: Value,
schema: Value,
}
impl BazaarExtension {
#[must_use]
pub fn http_query(method: BazaarQueryMethod, query_params: Value) -> Self {
Self {
info: wrap_input(http_input([
("type", Value::from("http")),
("method", Value::from(method.as_str())),
("queryParams", query_params),
])),
schema: query_schema(method),
}
}
#[must_use]
pub fn http_body(method: BazaarBodyMethod, body_type: BazaarBodyType, body: Value) -> Self {
Self {
info: wrap_input(http_input([
("type", Value::from("http")),
("method", Value::from(method.as_str())),
("bodyType", Value::from(body_type.as_str())),
("body", body),
])),
schema: body_schema(method),
}
}
#[must_use]
pub fn mcp(tool_name: impl Into<CompactString>, input_schema: Value) -> Self {
let tool_name = tool_name.into();
Self {
info: wrap_input(http_input([
("type", Value::from("mcp")),
("toolName", Value::from(tool_name.as_str())),
("inputSchema", input_schema),
])),
schema: mcp_schema(),
}
}
#[must_use]
pub fn with_output(mut self, example: Value) -> Self {
if let Some(obj) = self.info.as_object_mut() {
let _ = obj.insert(
"output".into(),
http_input([("type", Value::from("json")), ("example", example)]),
);
}
self
}
}
fn http_input<const N: usize>(fields: [(&str, Value); N]) -> Value {
Value::Object(fields.into_iter().map(|(k, v)| (k.to_owned(), v)).collect())
}
fn wrap_input(input: Value) -> Value {
Value::Object(serde_json::Map::from_iter([("input".into(), input)]))
}
impl Extension for BazaarExtension {
fn id(&self) -> &'static str {
BAZAAR_KEY
}
fn advertise(&self, _ctx: &AdvertiseContext<'_>) -> Option<ExtensionEntry> {
Some(ExtensionEntry::with_schema(
self.info.clone(),
self.schema.clone(),
))
}
}
fn query_schema(method: BazaarQueryMethod) -> Value {
json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"input": {
"type": "object",
"properties": {
"type": { "type": "string", "const": "http" },
"method": { "type": "string", "enum": [method.as_str()] },
"queryParams": { "type": "object" },
"headers": { "type": "object", "additionalProperties": { "type": "string" } }
},
"required": ["type", "method"],
"additionalProperties": false
},
"output": {
"type": "object",
"properties": {
"type": { "type": "string" },
"example": { "type": "object" }
},
"required": ["type"]
}
},
"required": ["input"]
})
}
fn body_schema(method: BazaarBodyMethod) -> Value {
json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"input": {
"type": "object",
"properties": {
"type": { "type": "string", "const": "http" },
"method": { "type": "string", "enum": [method.as_str()] },
"bodyType": { "type": "string", "enum": ["json", "form-data", "text"] },
"body": { "type": "object" },
"queryParams": { "type": "object", "additionalProperties": { "type": "string" } },
"headers": { "type": "object", "additionalProperties": { "type": "string" } }
},
"required": ["type", "method", "bodyType", "body"],
"additionalProperties": false
},
"output": {
"type": "object",
"properties": {
"type": { "type": "string" },
"example": { "type": "object" }
},
"required": ["type"]
}
},
"required": ["input"]
})
}
fn mcp_schema() -> Value {
json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"input": {
"type": "object",
"properties": {
"type": { "type": "string", "const": "mcp" },
"toolName": { "type": "string" },
"description": { "type": "string" },
"transport": { "type": "string", "enum": ["streamable-http", "sse"] },
"inputSchema": { "type": "object" },
"example": { "type": "object" }
},
"required": ["type", "toolName", "inputSchema"],
"additionalProperties": false
},
"output": {
"type": "object",
"properties": {
"type": { "type": "string" },
"example": { "type": "object" }
},
"required": ["type"]
}
},
"required": ["input"]
})
}