use yog_data::{Schema, Value};
use yog_ids::serve_method as M;
use yog_ids::serve_param as P;
use super::{Reply, ServiceError};
#[derive(Schema)]
#[data(tag = "t")]
enum Typed {
Ref { kind: String, id: String },
Tag { kind: String, id: String },
Res { set: String, key: String },
Image { source: String },
Lang { key: String },
Bytes,
Str { v: String },
}
#[derive(Schema)]
struct NotTyped {
error: String,
}
pub(super) fn method(method: &str, params: &Value) -> Option<Reply> {
Some(match method {
M::VERSION => Ok(env!("CARGO_PKG_VERSION").into()),
M::DATA_PARSE => text(params, P::TEXT)
.and_then(|t| yog_data::text::parse_value(t).map_err(|e| ServiceError::syntax(&e))),
M::DATA_PATCH => text(params, P::TEXT)
.and_then(|t| Ok(yog_data::patch::patch(t, param(params, P::NEXT)?).into())),
M::DATA_FORMAT => {
let level = params.get(P::LEVEL).and_then(Value::as_i64).unwrap_or(0);
param(params, P::VALUE)
.map(|v| yog_data::patch::format(v, usize::try_from(level).unwrap_or(0)).into())
}
M::TYPED_CLASSIFY => param(params, P::VALUES).and_then(|values| {
let values = values.as_list().ok_or("`values` is a list")?;
Ok(Value::List(values.iter().map(classified).collect()))
}),
M::SOURCE_READ => text(params, P::PATH).and_then(|p| {
crate::files::read_source(p)
.map(Value::from)
.map_err(|e| format!("{p}: {e}").into())
}),
M::SOURCE_WRITE => text(params, P::PATH).and_then(|p| {
let bytes = yog_data::source::binary(text(params, P::TEXT)?)
.map_err(|e| ServiceError::syntax(&e))?;
std::fs::write(p, bytes).map_err(|e| ServiceError::from(format!("{p}: {e}")))?;
Ok(Value::Null)
}),
M::SOURCE_CHECK => text(params, P::PATH)
.and_then(|p| Ok(super::check::check(p, text(params, P::TEXT)?).write())),
M::FORMAT_COMPLETE => param(params, P::FORMAT).and_then(|name| {
let name = name.as_str().ok_or("`format` is a name")?;
let format = yog_ids::Format::of(name).ok_or_else(|| format!("no format `{name}`"))?;
yog_formats::complete(format, param(params, P::VALUE)?)
.map_err(|e| ServiceError::from(crate::diagnostics::listed(&e)))
}),
M::FORMAT_SHAPE => param(params, P::FORMAT).and_then(|name| {
let name = name.as_str().ok_or("`format` is a name")?;
yog_formats::shape_of(name)
.map(|shape| shape.describe().write())
.ok_or_else(|| format!("no format `{name}`").into())
}),
_ => return None,
})
}
fn param<'a>(params: &'a Value, name: &str) -> Result<&'a Value, ServiceError> {
params
.get(name)
.ok_or_else(|| format!("missing `{name}`").into())
}
fn text<'a>(params: &'a Value, name: &str) -> Result<&'a str, ServiceError> {
param(params, name)?
.as_str()
.ok_or_else(|| format!("`{name}` is text").into())
}
fn classified(v: &Value) -> Value {
use yog_data::typed::Spelled;
let typed = match yog_data::typed::classify(v.as_str().unwrap_or_default()) {
Ok(Spelled::Ref(r)) => match yog_data::Meaning::of(r.clone()) {
Some(yog_data::Meaning::Tag(t)) => Typed::Tag {
kind: t.kind,
id: t.id,
},
Some(yog_data::Meaning::Res(r)) => Typed::Res {
set: r.set,
key: r.key,
},
Some(yog_data::Meaning::Image(source)) => Typed::Image { source },
Some(yog_data::Meaning::Lang(key)) => Typed::Lang { key },
Some(yog_data::Meaning::Content(c)) => Typed::Ref {
kind: c.kind.as_str().to_owned(),
id: c.id.as_str().to_owned(),
},
None => {
return NotTyped {
error: format!(
"`{r}`: `{}` isn't an id (lower case `namespace:path`)",
r.id
),
}
.write();
}
},
Ok(Spelled::Bytes(_)) => Typed::Bytes,
Ok(Spelled::Plain(v)) => Typed::Str { v },
Err(e) => {
return NotTyped {
error: e.to_string(),
}
.write();
}
};
typed.write()
}