Skip to main content

agent_playground/
schema.rs

1//! JSON Schema site generation utilities.
2//!
3//! This module materializes schema files for the public configuration models
4//! and writes a tiny static index page that can be hosted as documentation.
5
6use std::{
7    fs,
8    path::{Path, PathBuf},
9};
10
11use anyhow::{Context, Result};
12use serde::Serialize;
13
14use crate::config::{PlaygroundConfigFile, RootConfigFile};
15
16const ROOT_SCHEMA_FILE_NAME: &str = "root-config.schema.json";
17const PLAYGROUND_SCHEMA_FILE_NAME: &str = "playground-config.schema.json";
18
19/// Generates JSON Schema artifacts and an index page into `output_dir`.
20///
21/// The command writes:
22///
23/// - `schemas/root-config.schema.json`
24/// - `schemas/playground-config.schema.json`
25/// - `index.html`
26///
27/// Existing files at those paths are overwritten.
28///
29/// # Errors
30///
31/// Returns an error when output directories cannot be created, schema values
32/// cannot be serialized, or files cannot be written.
33pub fn write_schema_site(output_dir: &Path) -> Result<()> {
34    let schemas_dir = output_dir.join("schemas");
35    fs::create_dir_all(&schemas_dir)
36        .with_context(|| format!("failed to create {}", schemas_dir.display()))?;
37
38    write_json_file(
39        &schemas_dir.join(ROOT_SCHEMA_FILE_NAME),
40        &RootConfigFile::json_schema(),
41    )?;
42    write_json_file(
43        &schemas_dir.join(PLAYGROUND_SCHEMA_FILE_NAME),
44        &PlaygroundConfigFile::json_schema(),
45    )?;
46    fs::write(output_dir.join("index.html"), render_schema_index()).with_context(|| {
47        format!(
48            "failed to write {}",
49            output_dir.join("index.html").display()
50        )
51    })?;
52
53    Ok(())
54}
55
56/// Returns the default output location used for generated schema artifacts.
57///
58/// The returned path is relative to the current working directory:
59/// `target/schema-site`.
60pub fn default_schema_site_dir() -> PathBuf {
61    PathBuf::from("target").join("schema-site")
62}
63
64fn write_json_file<T>(path: &Path, value: &T) -> Result<()>
65where
66    T: Serialize,
67{
68    let content =
69        serde_json::to_string_pretty(value).context("failed to serialize schema to JSON")?;
70    fs::write(path, content).with_context(|| format!("failed to write {}", path.display()))
71}
72
73fn render_schema_index() -> &'static str {
74    r#"<!doctype html>
75<html lang="en">
76  <head>
77    <meta charset="utf-8">
78    <meta name="viewport" content="width=device-width, initial-scale=1">
79    <title>agent-playground JSON Schemas</title>
80    <style>
81      :root {
82        color-scheme: light dark;
83        font-family: ui-sans-serif, system-ui, sans-serif;
84      }
85      body {
86        margin: 0 auto;
87        max-width: 48rem;
88        padding: 3rem 1.25rem;
89        line-height: 1.6;
90      }
91      code {
92        font-family: ui-monospace, SFMono-Regular, monospace;
93      }
94    </style>
95  </head>
96  <body>
97    <h1>agent-playground JSON Schemas</h1>
98    <p>Generated from the Rust config file models in this repository.</p>
99    <ul>
100      <li><a href="./schemas/root-config.schema.json"><code>root-config.schema.json</code></a></li>
101      <li><a href="./schemas/playground-config.schema.json"><code>playground-config.schema.json</code></a></li>
102    </ul>
103  </body>
104</html>
105"#
106}
107
108#[cfg(test)]
109mod tests {
110    use anyhow::Result;
111    use serde_json::Value;
112    use tempfile::tempdir;
113
114    use super::{PLAYGROUND_SCHEMA_FILE_NAME, ROOT_SCHEMA_FILE_NAME, write_schema_site};
115
116    #[test]
117    fn writes_schema_site_artifacts() -> Result<()> {
118        let output_dir = tempdir()?;
119
120        write_schema_site(output_dir.path())?;
121
122        let root_schema_path = output_dir
123            .path()
124            .join("schemas")
125            .join(ROOT_SCHEMA_FILE_NAME);
126        let playground_schema_path = output_dir
127            .path()
128            .join("schemas")
129            .join(PLAYGROUND_SCHEMA_FILE_NAME);
130        let index_path = output_dir.path().join("index.html");
131
132        assert!(root_schema_path.is_file());
133        assert!(playground_schema_path.is_file());
134        assert!(index_path.is_file());
135
136        let root_schema: Value = serde_json::from_str(&std::fs::read_to_string(root_schema_path)?)?;
137        let playground_schema: Value =
138            serde_json::from_str(&std::fs::read_to_string(playground_schema_path)?)?;
139        let index_html = std::fs::read_to_string(index_path)?;
140
141        assert_eq!(root_schema["type"], Value::String("object".to_string()));
142        assert_eq!(
143            playground_schema["type"],
144            Value::String("object".to_string())
145        );
146        assert!(index_html.contains("root-config.schema.json"));
147        assert!(index_html.contains("playground-config.schema.json"));
148
149        Ok(())
150    }
151}