use crate::v2;
use super::v1;
pub const SCHEMA: &str = "ridl.codegen.v1";
pub trait Backend {
fn language(&self) -> &str;
fn generate(&self, request: &v1::CodegenRequest) -> v1::CodegenResponse;
}
#[derive(Clone, Copy)]
pub struct RawIr<'a> {
pub package: &'a v2::Package,
pub others: &'a [&'a v2::Package],
}
pub struct ModelBackend;
impl Backend for ModelBackend {
fn language(&self) -> &str {
"model"
}
fn generate(&self, request: &v1::CodegenRequest) -> v1::CodegenResponse {
if let Some(option) = request.options.first() {
return v1::CodegenResponse {
files: Vec::new(),
diagnostics: vec![error(format!(
"the model backend takes no option; `{}` is not one it knows",
option.key
))],
};
}
let Some(model) = &request.model else {
return v1::CodegenResponse {
files: Vec::new(),
diagnostics: vec![error("the request carries no model".to_string())],
};
};
match super::to_json_pretty(model) {
Ok(json) => v1::CodegenResponse {
files: vec![text_file(
format!("{}.codegen.json", request.artifact_base),
json,
)],
diagnostics: Vec::new(),
},
Err(err) => v1::CodegenResponse {
files: Vec::new(),
diagnostics: vec![error(err.to_string())],
},
}
}
}
pub fn text_file(path: String, text: String) -> v1::GeneratedFile {
v1::GeneratedFile {
path,
content: Some(v1::generated_file::Content::Text(text)),
}
}
pub fn error(message: String) -> v1::Diagnostic {
v1::Diagnostic {
severity: v1::DiagnosticSeverity::Error as i32,
message,
}
}
pub fn has_error(response: &v1::CodegenResponse) -> bool {
response.diagnostics.iter().any(|diagnostic| {
!matches!(
v1::DiagnosticSeverity::try_from(diagnostic.severity),
Ok(v1::DiagnosticSeverity::Warning | v1::DiagnosticSeverity::Info)
)
})
}
pub fn check_path(path: &str) -> Result<(), String> {
if path.is_empty() {
return Err("the path is empty".to_string());
}
if path.contains('\0') {
return Err("the path contains a NUL byte".to_string());
}
if path.contains('\\') {
return Err("the path contains `\\`; components are separated by `/`".to_string());
}
if path.starts_with('/') {
return Err("the path is absolute".to_string());
}
if path.as_bytes().get(1).is_some_and(|byte| *byte == b':')
&& path.as_bytes()[0].is_ascii_alphabetic()
{
return Err("the path starts with a drive letter".to_string());
}
for component in path.split('/') {
match component {
"" => return Err("the path has an empty component".to_string()),
"." | ".." => {
return Err(format!("the path has a `{component}` component"));
}
_ => {}
}
}
Ok(())
}
pub fn request_to_json(request: &v1::CodegenRequest) -> Result<String, super::SerializeError> {
v2::render_json(request).map_err(super::SerializeError::from_v2)
}
pub fn request_from_json(text: &str) -> Result<v1::CodegenRequest, serde_json::Error> {
v2::read_json(text)
}
pub fn response_to_json(response: &v1::CodegenResponse) -> Result<String, super::SerializeError> {
v2::render_json(response).map_err(super::SerializeError::from_v2)
}
pub fn response_from_json(text: &str) -> Result<v1::CodegenResponse, serde_json::Error> {
v2::read_json(text)
}