open_agent_profile/
parse.rs1use std::{fs, path::Path};
2
3use serde::{
4 Deserialize, Deserializer,
5 de::{MapAccess, SeqAccess, Visitor},
6};
7use serde_json::{Map, Number, Value};
8use thiserror::Error;
9
10use crate::{Document, object};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum OapFormat {
15 Yaml,
17 Json,
19 Markdown,
21}
22
23impl OapFormat {
24 pub fn from_path(path: &Path) -> Self {
26 match path
27 .extension()
28 .and_then(|value| value.to_str())
29 .unwrap_or_default()
30 .to_ascii_lowercase()
31 .as_str()
32 {
33 "json" => Self::Json,
34 "md" => Self::Markdown,
35 _ => Self::Yaml,
36 }
37 }
38}
39
40#[derive(Debug, Error)]
42#[error("{0}")]
43pub struct ParseError(pub String);
44
45pub fn parse(input: &str, format: OapFormat) -> Result<Document, ParseError> {
47 if input.trim().is_empty() {
48 return Err(ParseError("parse error: empty document".into()));
49 }
50 if format == OapFormat::Markdown {
51 let rest = input.strip_prefix("---\n").ok_or_else(|| {
52 ParseError("Markdown profile must begin with YAML frontmatter".into())
53 })?;
54 let (frontmatter, body) = rest
55 .split_once("\n---\n")
56 .ok_or_else(|| ParseError("unterminated Markdown frontmatter".into()))?;
57 let mut document = parse(frontmatter, OapFormat::Yaml)?;
58 let role = document
59 .get_mut("spec")
60 .and_then(Value::as_object_mut)
61 .and_then(|spec| spec.get_mut("role"))
62 .and_then(Value::as_object_mut);
63 if let Some(role) = role {
64 if role.contains_key("instructions") && !body.trim().is_empty() {
65 return Err(ParseError("Markdown encoding supplies spec.role.instructions in both frontmatter and body".into()));
66 }
67 role.insert("instructions".into(), Value::String(body.trim().into()));
68 }
69 return Ok(document);
70 }
71 let value: Value = match format {
72 OapFormat::Json => serde_json::from_str(input)
73 .map_err(|error| ParseError(format!("parse error: {error}")))?,
74 OapFormat::Yaml => serde_yaml_ng::from_str::<UniqueValue>(input)
75 .map(|value| value.0)
76 .map_err(|error| ParseError(format!("parse error: {error}")))?,
77 OapFormat::Markdown => unreachable!(),
78 };
79 value
80 .as_object()
81 .cloned()
82 .ok_or_else(|| ParseError("document root must be an object".into()))
83}
84
85pub fn load(path: impl AsRef<Path>) -> Result<Document, ParseError> {
87 let path = path.as_ref();
88 let input = fs::read_to_string(path).map_err(|error| ParseError(error.to_string()))?;
89 parse(&input, OapFormat::from_path(path))
90}
91
92struct UniqueValue(Value);
93impl<'de> Deserialize<'de> for UniqueValue {
94 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
95 struct ValueVisitor;
96 impl<'de> Visitor<'de> for ValueVisitor {
97 type Value = UniqueValue;
98 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
99 formatter.write_str("a JSON-compatible YAML value")
100 }
101 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> {
102 Ok(UniqueValue(Value::Bool(v)))
103 }
104 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> {
105 Ok(UniqueValue(Value::Number(v.into())))
106 }
107 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> {
108 Ok(UniqueValue(Value::Number(v.into())))
109 }
110 fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<Self::Value, E> {
111 Number::from_f64(v)
112 .map(Value::Number)
113 .map(UniqueValue)
114 .ok_or_else(|| E::custom("non-finite number"))
115 }
116 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> {
117 Ok(UniqueValue(Value::String(v.into())))
118 }
119 fn visit_string<E>(self, v: String) -> Result<Self::Value, E> {
120 Ok(UniqueValue(Value::String(v)))
121 }
122 fn visit_none<E>(self) -> Result<Self::Value, E> {
123 Ok(UniqueValue(Value::Null))
124 }
125 fn visit_unit<E>(self) -> Result<Self::Value, E> {
126 Ok(UniqueValue(Value::Null))
127 }
128 fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
129 let mut out = vec![];
130 while let Some(v) = seq.next_element::<UniqueValue>()? {
131 out.push(v.0);
132 }
133 Ok(UniqueValue(Value::Array(out)))
134 }
135 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
136 let mut out = Map::new();
137 while let Some((key, value)) = map.next_entry::<String, UniqueValue>()? {
138 if out.contains_key(&key) {
139 return Err(serde::de::Error::custom(format!("duplicate key {key:?}")));
140 }
141 out.insert(key, value.0);
142 }
143 Ok(UniqueValue(Value::Object(out)))
144 }
145 }
146 deserializer.deserialize_any(ValueVisitor)
147 }
148}
149
150#[allow(dead_code)]
151fn _object(document: &Document) {
152 let _ = object(document.get("spec"));
153}