rig icon indicating copy to clipboard operation
rig copied to clipboard

feat: Better typing for JSON Schema

Open Sytten opened this issue 5 months ago • 6 comments

  • [x] I have looked for existing issues (including closed) about this

Feature Request

Right now the schemas are just serde_json::Value. I am wondering if it would be worth either building primitives to better type that or at least verify that this is a valid JSON Schema.

Motivation

The JSON value is very barebone and doesn't offer a lot of security/guard rails for the user. It is even worse if you have to accept user provided data for tools. That would also allow us to transform the schema if need be for each provider, though most of them seem to support part of the JSON Schema (at whatever draft version it is at right now).

Proposal

  1. We could start with a custom wrapper around the Value that would check with jsonschema that it is a valid JSON Schema
  2. We could build/contribute to an existing crate a macro/builder for JSON Schema and a parser for it (similar to what valico offers

Alternatives

  • The user needs to do it's own parsing

Sytten avatar Jul 31 '25 16:07 Sytten

Hi, I'd like to work on this

aybdee avatar Aug 20 '25 17:08 aybdee

I came up with with this for a wrapper around Value that adds validation, what do you think about the approach ?

use serde_json::Value;

#[derive(Debug)]
enum ValidationError {
    ValidationError(String),
    SerdeError(serde_json::Error),
}

trait Schema {
    fn schema() -> String;
    fn validate(value: &Value) -> Result<(), ValidationError> {
        let schema = serde_json::from_str::<Value>(&Self::schema())
            .map_err(|e| ValidationError::SerdeError(e))?;

        jsonschema::validate(&schema, value)
            .map_err(|e| ValidationError::ValidationError(e.to_string()))?;

        Ok(())
    }
}

struct ValidatedValue<T: Schema> {
    value: Value,
    marker: std::marker::PhantomData<T>,
}

impl<T: Schema> TryFrom<Value> for ValidatedValue<T> {
    type Error = ValidationError;

    fn try_from(value: Value) -> Result<Self, Self::Error> {
        T::validate(&value)?;
        Ok(ValidatedValue {
            value,
            marker: std::marker::PhantomData,
        })
    }
}

you'd define a schema like this

struct ToolUseParameters;
impl Schema for ToolUseParameters {
    fn schema() -> String {
        r#"
        {
            "type": "object",
            "properties": {
                "name": { "type": "string" },
                "description": { "type": "string" }
            },
            "required": ["name", "description"]
        }
        "#
        .to_string()
    }
}

struct ToolDefinition {
    name: String,
    parameters: ValidatedValue<ToolUseParameters>,
}

and then use it like this

fn test() {
    Definition {
        name: "Example Tool".to_string(),
        parameters: json!({
            "name": "Add",
            "description": "add stuff"
        })
        .try_into()
        .unwrap(),
    };
}

I think doing it this way still keeps the interface really clean for the user and makes it easy to define and reuse schemas. we could also potentially use a macro to make things even simpler for users

macro_rules! validated_json {
    ($($value:tt)+) => {
        json!($($value)+).try_into().unwrap()
    };
}

validated_json!({
            "name": "Add",
            "description": "add stuff"
        })

aybdee avatar Aug 21 '25 00:08 aybdee

My initial thought is that goal is to have a proper DSL so you don't have to know the structure of a json schema to build one, this doesn't really help for that.

The output likely needs to a wrapper over a serde_json::Value with validation and a custom deserializer for it.

Sytten avatar Aug 21 '25 01:08 Sytten

oh I see, I was headed in the wrong direction then, what do you think about this for proposal 1

struct Schema(Value);

impl TryFrom<Value> for Schema {
    type Error = ValidationError<'static>;

    fn try_from(value: Value) -> Result<Self, Self::Error> {
        jsonschema::validator_for(&value)?;
        Ok(Schema(value))
    }
}

also, what kind of structure do you have in mind for the DSL (sorry if i'm asking too many questions)

aybdee avatar Aug 21 '25 08:08 aybdee

I think this might actually be a job better suited for schemars, since we already use it internally.

What do you think?

joshua-mo-143 avatar Sep 10 '25 23:09 joshua-mo-143