prompty 2.0.0-beta.1

Prompty is an asset class and format for LLM prompts
Documentation
// Code generated by Prompty emitter; DO NOT EDIT.

#![allow(
    unused_imports,
    dead_code,
    non_camel_case_types,
    unused_variables,
    clippy::all
)]

use super::super::context::{LoadContext, SaveContext};

use super::super::model::model::Model;

use super::super::core::property::Property;

use super::super::template::template::Template;

use super::super::tools::tool::Tool;

/// A Prompty is a markdown file format for LLM prompts. The frontmatter defines structured metadata including model configuration, input/output schemas, tools, and template settings. The markdown body becomes the instructions. This is the single root type for the Prompty schema — there is no abstract base class or kind discriminator. A .prompty file always produces a Prompty instance.
#[derive(Debug, Clone, Default)]
pub struct Prompty {
    /// Human-readable name of the prompt
    pub name: String,
    /// Display name for UI purposes
    pub display_name: Option<String>,
    /// Description of the prompt's purpose
    pub description: Option<String>,
    /// Additional metadata including authors, tags, and other arbitrary properties
    pub metadata: serde_json::Value,
    /// Input parameters that participate in template rendering
    pub inputs: Vec<Property>,
    /// Expected output format and structure
    pub outputs: Vec<Property>,
    /// AI model configuration
    pub model: Model,
    /// Tools available for extended functionality
    pub tools: Vec<Tool>,
    /// Template configuration for prompt rendering
    pub template: Option<Template>,
    /// Clear directions on what the prompt should do. In .prompty files, this comes from the markdown body.
    pub instructions: Option<String>,
}

impl Prompty {
    /// Create a new Prompty with default values.
    pub fn new() -> Self {
        Self::default()
    }

    /// Load Prompty from a JSON string.
    pub fn from_json(json: &str, ctx: &LoadContext) -> Result<Self, serde_json::Error> {
        let value: serde_json::Value = serde_json::from_str(json)?;
        Ok(Self::load_from_value(&value, ctx))
    }

    /// Load Prompty from a YAML string.
    pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result<Self, serde_yaml::Error> {
        let value: serde_json::Value = serde_yaml::from_str(yaml)?;
        Ok(Self::load_from_value(&value, ctx))
    }

    /// Load Prompty from a `serde_json::Value`.
    ///
    /// Calls `ctx.process_input` before field extraction.
    pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self {
        let value = ctx.process_input(value.clone());
        Self {
            name: value
                .get("name")
                .and_then(|v| v.as_str())
                .unwrap_or_default()
                .to_string(),
            display_name: value
                .get("displayName")
                .and_then(|v| v.as_str())
                .map(|s| s.to_string()),
            description: value
                .get("description")
                .and_then(|v| v.as_str())
                .map(|s| s.to_string()),
            metadata: value
                .get("metadata")
                .cloned()
                .unwrap_or(serde_json::Value::Null),
            inputs: value
                .get("inputs")
                .map(|v| Self::load_inputs(v, ctx))
                .unwrap_or_default(),
            outputs: value
                .get("outputs")
                .map(|v| Self::load_outputs(v, ctx))
                .unwrap_or_default(),
            model: value
                .get("model")
                .filter(|v| v.is_object() || v.is_array() || v.is_string())
                .map(|v| Model::load_from_value(v, ctx))
                .unwrap_or_default(),
            tools: value
                .get("tools")
                .map(|v| Self::load_tools(v, ctx))
                .unwrap_or_default(),
            template: value
                .get("template")
                .filter(|v| v.is_object() || v.is_array() || v.is_string())
                .map(|v| Template::load_from_value(v, ctx)),
            instructions: value
                .get("instructions")
                .and_then(|v| v.as_str())
                .map(|s| s.to_string()),
        }
    }

    /// Serialize Prompty to a `serde_json::Value`.
    ///
    /// Calls `ctx.process_dict` after serialization.
    pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value {
        let mut result = serde_json::Map::new();
        // Write base fields
        if !self.name.is_empty() {
            result.insert(
                "name".to_string(),
                serde_json::Value::String(self.name.clone()),
            );
        }
        if let Some(ref val) = self.display_name {
            result.insert(
                "displayName".to_string(),
                serde_json::Value::String(val.clone()),
            );
        }
        if let Some(ref val) = self.description {
            result.insert(
                "description".to_string(),
                serde_json::Value::String(val.clone()),
            );
        }
        if !self.metadata.is_null() {
            result.insert("metadata".to_string(), self.metadata.clone());
        }
        if !self.inputs.is_empty() {
            result.insert("inputs".to_string(), Self::save_inputs(&self.inputs, ctx));
        }
        if !self.outputs.is_empty() {
            result.insert(
                "outputs".to_string(),
                Self::save_outputs(&self.outputs, ctx),
            );
        }
        {
            let nested = self.model.to_value(ctx);
            if !nested.is_null() {
                result.insert("model".to_string(), nested);
            }
        }
        if !self.tools.is_empty() {
            result.insert("tools".to_string(), Self::save_tools(&self.tools, ctx));
        }
        if let Some(ref val) = self.template {
            let nested = val.to_value(ctx);
            if !nested.is_null() {
                result.insert("template".to_string(), nested);
            }
        }
        if let Some(ref val) = self.instructions {
            result.insert(
                "instructions".to_string(),
                serde_json::Value::String(val.clone()),
            );
        }
        ctx.process_dict(serde_json::Value::Object(result))
    }

    /// Serialize Prompty to a JSON string.
    pub fn to_json(&self, ctx: &SaveContext) -> Result<String, serde_json::Error> {
        serde_json::to_string_pretty(&self.to_value(ctx))
    }

    /// Serialize Prompty to a YAML string.
    pub fn to_yaml(&self, ctx: &SaveContext) -> Result<String, serde_yaml::Error> {
        serde_yaml::to_string(&self.to_value(ctx))
    }
    /// Returns typed reference to the map if the field is an object.
    /// Returns `None` if the field is null or not an object.
    pub fn as_metadata_dict(&self) -> Option<&serde_json::Map<String, serde_json::Value>> {
        self.metadata.as_object()
    }

    /// Load a collection of Property from a JSON value.
    /// Handles both array format `[{...}]` and dict format `{"name": {...}}`.
    fn load_inputs(data: &serde_json::Value, ctx: &LoadContext) -> Vec<Property> {
        match data {
            serde_json::Value::Array(arr) => arr
                .iter()
                .map(|v| Property::load_from_value(v, ctx))
                .collect(),

            serde_json::Value::Object(obj) => obj
                .iter()
                .filter_map(|(name, value)| {
                    if value.is_array() {
                        return None;
                    }
                    let mut v = if value.is_object() {
                        value.clone()
                    } else {
                        serde_json::json!({ "kind": value })
                    };
                    if let serde_json::Value::Object(ref mut m) = v {
                        m.entry("name".to_string())
                            .or_insert_with(|| serde_json::Value::String(name.clone()));
                    }
                    Some(Property::load_from_value(&v, ctx))
                })
                .collect(),
            _ => Vec::new(),
        }
    }

    /// Save a collection of Property to a JSON value.
    fn save_inputs(items: &[Property], ctx: &SaveContext) -> serde_json::Value {
        if ctx.collection_format == "array" {
            return serde_json::Value::Array(
                items
                    .iter()
                    .map(|item| item.to_value(ctx))
                    .collect::<Vec<_>>(),
            );
        }
        // Object format: use name as key
        let mut result = serde_json::Map::new();
        for item in items {
            let mut item_data = match item.to_value(ctx) {
                serde_json::Value::Object(m) => m,
                other => {
                    let mut m = serde_json::Map::new();
                    m.insert("value".to_string(), other);
                    m
                }
            };
            if let Some(serde_json::Value::String(name)) = item_data.remove("name") {
                result.insert(name, serde_json::Value::Object(item_data));
            }
        }
        serde_json::Value::Object(result)
    }

    /// Load a collection of Property from a JSON value.
    /// Handles both array format `[{...}]`.
    fn load_outputs(data: &serde_json::Value, ctx: &LoadContext) -> Vec<Property> {
        match data {
            serde_json::Value::Array(arr) => arr
                .iter()
                .map(|v| Property::load_from_value(v, ctx))
                .collect(),

            _ => Vec::new(),
        }
    }

    /// Save a collection of Property to a JSON value.
    fn save_outputs(items: &[Property], ctx: &SaveContext) -> serde_json::Value {
        serde_json::Value::Array(
            items
                .iter()
                .map(|item| item.to_value(ctx))
                .collect::<Vec<_>>(),
        )
    }

    /// Load a collection of Tool from a JSON value.
    /// Handles both array format `[{...}]` and dict format `{"name": {...}}`.
    fn load_tools(data: &serde_json::Value, ctx: &LoadContext) -> Vec<Tool> {
        match data {
            serde_json::Value::Array(arr) => {
                arr.iter().map(|v| Tool::load_from_value(v, ctx)).collect()
            }

            serde_json::Value::Object(obj) => obj
                .iter()
                .filter_map(|(name, value)| {
                    if value.is_array() {
                        return None;
                    }
                    let mut v = if value.is_object() {
                        value.clone()
                    } else {
                        serde_json::json!({ "kind": value })
                    };
                    if let serde_json::Value::Object(ref mut m) = v {
                        m.entry("name".to_string())
                            .or_insert_with(|| serde_json::Value::String(name.clone()));
                    }
                    Some(Tool::load_from_value(&v, ctx))
                })
                .collect(),
            _ => Vec::new(),
        }
    }

    /// Save a collection of Tool to a JSON value.
    fn save_tools(items: &[Tool], ctx: &SaveContext) -> serde_json::Value {
        if ctx.collection_format == "array" {
            return serde_json::Value::Array(
                items
                    .iter()
                    .map(|item| item.to_value(ctx))
                    .collect::<Vec<_>>(),
            );
        }
        // Object format: use name as key
        let mut result = serde_json::Map::new();
        for item in items {
            let mut item_data = match item.to_value(ctx) {
                serde_json::Value::Object(m) => m,
                other => {
                    let mut m = serde_json::Map::new();
                    m.insert("value".to_string(), other);
                    m
                }
            };
            if let Some(serde_json::Value::String(name)) = item_data.remove("name") {
                result.insert(name, serde_json::Value::Object(item_data));
            }
        }
        serde_json::Value::Object(result)
    }
}