1use std::collections::HashMap;
6use std::hash::BuildHasher;
7
8use panproto_gat::Theory;
9use panproto_schema::{EdgeRule, Protocol, Schema, SchemaBuilder};
10
11use crate::emit::{children_by_edge, find_roots, vertex_constraints};
12use crate::error::ProtocolError;
13use crate::theories;
14
15#[must_use]
17pub fn protocol() -> Protocol {
18 Protocol {
19 name: "raml".into(),
20 schema_theory: "ThRamlSchema".into(),
21 instance_theory: "ThRamlInstance".into(),
22 edge_rules: edge_rules(),
23 obj_kinds: vec![
24 "resource".into(),
25 "method".into(),
26 "type".into(),
27 "trait".into(),
28 "string".into(),
29 "integer".into(),
30 "number".into(),
31 "boolean".into(),
32 "array".into(),
33 "object".into(),
34 "date".into(),
35 "file".into(),
36 "nil".into(),
37 ],
38 constraint_sorts: vec![
39 "required".into(),
40 "pattern".into(),
41 "minLength".into(),
42 "maxLength".into(),
43 "minimum".into(),
44 "maximum".into(),
45 "enum".into(),
46 "default".into(),
47 ],
48 has_order: true,
49 has_coproducts: true,
50 has_recursion: true,
51 nominal_identity: true,
52 ..Protocol::default()
53 }
54}
55
56pub fn register_theories<S: BuildHasher>(registry: &mut HashMap<String, Theory, S>) {
58 theories::register_constrained_multigraph_wtype(registry, "ThRamlSchema", "ThRamlInstance");
59}
60
61pub fn parse_raml_schema(json: &serde_json::Value) -> Result<Schema, ProtocolError> {
67 let proto = protocol();
68 let mut builder = SchemaBuilder::new(&proto);
69 let mut counter: usize = 0;
70
71 if let Some(types) = json.get("types").and_then(serde_json::Value::as_object) {
73 for (name, def) in types {
74 builder = walk_raml_type(builder, def, name, &mut counter)?;
75 }
76 }
77
78 if let Some(resources) = json.get("resources").and_then(serde_json::Value::as_object) {
80 for (path, res_def) in resources {
81 let res_id = format!("resource:{path}");
82 builder = builder.vertex(&res_id, "resource", None)?;
83
84 if let Some(methods) = res_def
85 .get("methods")
86 .and_then(serde_json::Value::as_object)
87 {
88 for (method_name, method_def) in methods {
89 let method_id = format!("{res_id}:{method_name}");
90 builder = builder.vertex(&method_id, "method", None)?;
91 builder = builder.edge(&res_id, &method_id, "prop", Some(method_name))?;
92
93 if let Some(body) = method_def.get("body") {
94 counter += 1;
95 let body_id = format!("{method_id}:body{counter}");
96 builder = walk_raml_type(builder, body, &body_id, &mut counter)?;
97 builder = builder.edge(&method_id, &body_id, "prop", Some("body"))?;
98 }
99 }
100 }
101 }
102 }
103
104 let schema = builder.build()?;
105 Ok(schema)
106}
107
108fn walk_raml_type(
110 mut builder: SchemaBuilder,
111 def: &serde_json::Value,
112 current_id: &str,
113 counter: &mut usize,
114) -> Result<SchemaBuilder, ProtocolError> {
115 let type_str = def
116 .get("type")
117 .and_then(serde_json::Value::as_str)
118 .unwrap_or("object");
119
120 let kind = raml_type_to_kind(type_str);
121 builder = builder.vertex(current_id, kind, None)?;
122
123 for field in &[
125 "pattern",
126 "minLength",
127 "maxLength",
128 "minimum",
129 "maximum",
130 "default",
131 ] {
132 if let Some(val) = def.get(field) {
133 let val_str = match val {
134 serde_json::Value::String(s) => s.clone(),
135 serde_json::Value::Number(n) => n.to_string(),
136 _ => val.to_string(),
137 };
138 builder = builder.constraint(current_id, field, &val_str);
139 }
140 }
141
142 if let Some(enum_val) = def.get("enum").and_then(serde_json::Value::as_array) {
143 let vals: Vec<String> = enum_val
144 .iter()
145 .map(|v| v.as_str().map_or_else(|| v.to_string(), String::from))
146 .collect();
147 builder = builder.constraint(current_id, "enum", &vals.join(","));
148 }
149
150 if let Some(properties) = def.get("properties").and_then(serde_json::Value::as_object) {
152 for (prop_name, prop_def) in properties {
153 let prop_id = format!("{current_id}.{prop_name}");
154 builder = walk_raml_type(builder, prop_def, &prop_id, counter)?;
155 builder = builder.edge(current_id, &prop_id, "prop", Some(prop_name))?;
156 }
157 }
158
159 if let Some(items) = def.get("items") {
161 let items_id = format!("{current_id}:items");
162 builder = walk_raml_type(builder, items, &items_id, counter)?;
163 builder = builder.edge(current_id, &items_id, "items", None)?;
164 }
165
166 Ok(builder)
167}
168
169fn raml_type_to_kind(t: &str) -> &'static str {
171 match t {
172 "string" => "string",
173 "integer" => "integer",
174 "number" => "number",
175 "boolean" => "boolean",
176 "array" => "array",
177 "object" => "object",
178 "date-only" | "time-only" | "datetime-only" | "datetime" => "date",
179 "file" => "file",
180 "nil" => "nil",
181 _ => "type",
182 }
183}
184
185pub fn emit_raml_schema(schema: &Schema) -> Result<serde_json::Value, ProtocolError> {
191 let structural = &["prop", "items"];
192 let roots = find_roots(schema, structural);
193
194 let mut types = serde_json::Map::new();
195 let mut resources = serde_json::Map::new();
196
197 for root in &roots {
198 if root.kind.as_str() == "resource" {
199 let path = root.id.strip_prefix("resource:").unwrap_or(&root.id);
200 let methods = children_by_edge(schema, &root.id, "prop");
201 let mut methods_obj = serde_json::Map::new();
202 for (edge, child) in &methods {
203 let name = edge.name.as_deref().unwrap_or(&child.id);
204 methods_obj.insert(name.to_string(), serde_json::json!({}));
205 }
206 resources.insert(
207 path.to_string(),
208 serde_json::json!({"methods": methods_obj}),
209 );
210 } else {
211 let obj = emit_raml_type(schema, &root.id);
212 types.insert(root.id.to_string(), obj);
213 }
214 }
215
216 let mut result = serde_json::Map::new();
217 if !types.is_empty() {
218 result.insert("types".into(), serde_json::Value::Object(types));
219 }
220 if !resources.is_empty() {
221 result.insert("resources".into(), serde_json::Value::Object(resources));
222 }
223
224 Ok(serde_json::Value::Object(result))
225}
226
227fn emit_raml_type(schema: &Schema, vertex_id: &str) -> serde_json::Value {
229 let vertex = match schema.vertices.get(vertex_id) {
230 Some(v) => v,
231 None => return serde_json::json!({}),
232 };
233
234 let mut obj = serde_json::Map::new();
235 obj.insert("type".into(), serde_json::json!(vertex.kind));
236
237 for c in vertex_constraints(schema, vertex_id) {
238 if let Ok(n) = c.value.parse::<i64>() {
239 obj.insert(c.sort.to_string(), serde_json::json!(n));
240 } else {
241 obj.insert(c.sort.to_string(), serde_json::json!(c.value));
242 }
243 }
244
245 let props = children_by_edge(schema, vertex_id, "prop");
246 if !props.is_empty() {
247 let mut properties = serde_json::Map::new();
248 for (edge, child) in &props {
249 let name = edge.name.as_deref().unwrap_or(&child.id);
250 let val = emit_raml_type(schema, &child.id);
251 properties.insert(name.to_string(), val);
252 }
253 obj.insert("properties".into(), serde_json::Value::Object(properties));
254 }
255
256 serde_json::Value::Object(obj)
257}
258
259fn edge_rules() -> Vec<EdgeRule> {
260 vec![
261 EdgeRule {
262 edge_kind: "prop".into(),
263 src_kinds: vec![
264 "resource".into(),
265 "method".into(),
266 "object".into(),
267 "type".into(),
268 ],
269 tgt_kinds: vec![],
270 },
271 EdgeRule {
272 edge_kind: "items".into(),
273 src_kinds: vec!["array".into()],
274 tgt_kinds: vec![],
275 },
276 ]
277}
278
279#[cfg(test)]
280#[allow(clippy::expect_used, clippy::unwrap_used)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn protocol_def() {
286 let p = protocol();
287 assert_eq!(p.name, "raml");
288 }
289
290 #[test]
291 fn register_theories_works() {
292 let mut registry = HashMap::new();
293 register_theories(&mut registry);
294 assert!(registry.contains_key("ThRamlSchema"));
295 }
296
297 #[test]
298 fn parse_and_emit() {
299 let json = serde_json::json!({
300 "types": {
301 "User": {
302 "type": "object",
303 "properties": {
304 "name": {"type": "string"},
305 "age": {"type": "integer"}
306 }
307 }
308 },
309 "resources": {
310 "/users": {
311 "methods": {
312 "get": {}
313 }
314 }
315 }
316 });
317 let schema = parse_raml_schema(&json).expect("should parse");
318 assert!(schema.has_vertex("User"));
319 assert!(schema.has_vertex("resource:/users"));
320 let emitted = emit_raml_schema(&schema).expect("emit");
321 assert!(emitted.get("types").is_some());
322 }
323
324 #[test]
325 fn roundtrip() {
326 let json = serde_json::json!({
327 "types": {
328 "Item": {
329 "type": "object",
330 "properties": {
331 "id": {"type": "integer"}
332 }
333 }
334 }
335 });
336 let s1 = parse_raml_schema(&json).expect("parse");
337 let emitted = emit_raml_schema(&s1).expect("emit");
338 let s2 = parse_raml_schema(&emitted).expect("re-parse");
339 assert_eq!(s1.vertex_count(), s2.vertex_count());
340 }
341}