use crate::{Decoder, Encoder, Error, Result, SchemaType};
pub trait Schema {
fn schema() -> SchemaType;
}
impl Schema for bool { fn schema() -> SchemaType { SchemaType::Bool } }
impl Schema for u8 { fn schema() -> SchemaType { SchemaType::U8 } }
impl Schema for u16 { fn schema() -> SchemaType { SchemaType::U16 } }
impl Schema for u32 { fn schema() -> SchemaType { SchemaType::U32 } }
impl Schema for u64 { fn schema() -> SchemaType { SchemaType::U64 } }
impl Schema for i8 { fn schema() -> SchemaType { SchemaType::S8 } }
impl Schema for i16 { fn schema() -> SchemaType { SchemaType::S16 } }
impl Schema for i32 { fn schema() -> SchemaType { SchemaType::S32 } }
impl Schema for i64 { fn schema() -> SchemaType { SchemaType::S64 } }
impl Schema for f32 { fn schema() -> SchemaType { SchemaType::F32 } }
impl Schema for f64 { fn schema() -> SchemaType { SchemaType::F64 } }
impl Schema for char { fn schema() -> SchemaType { SchemaType::Char } }
impl Schema for () { fn schema() -> SchemaType { SchemaType::Unit } }
impl Schema for String { fn schema() -> SchemaType { SchemaType::String } }
impl Schema for str { fn schema() -> SchemaType { SchemaType::String } }
impl<T: Schema> Schema for Vec<T> {
fn schema() -> SchemaType { SchemaType::List(Box::new(T::schema())) }
}
impl<T: Schema> Schema for Option<T> {
fn schema() -> SchemaType { SchemaType::Option(Box::new(T::schema())) }
}
impl<T: Schema, E: Schema> Schema for std::result::Result<T, E> {
fn schema() -> SchemaType {
SchemaType::Result {
ok: Box::new(T::schema()),
err: Box::new(E::schema()),
}
}
}
impl<T: Schema> Schema for Box<T> {
fn schema() -> SchemaType { T::schema() }
}
impl<const N: usize> Schema for [u8; N] {
fn schema() -> SchemaType { SchemaType::Bytes }
}
pub fn lower(schema: &SchemaType, input: &str) -> Result<Vec<u8>> {
let mut p = TextParser::new(input);
let mut enc = Encoder::new();
p.value(schema, &mut enc)?;
p.skip_ws();
if p.pos < p.src.len() {
return Err(Error::Text(format!("trailing input at position {}", p.pos)));
}
enc.into_bytes()
}
pub fn raise(schema: &SchemaType, bytes: &[u8]) -> Result<String> {
let mut dec = Decoder::new(bytes);
let mut out = String::new();
raise_value(schema, &mut dec, &mut out)?;
Ok(out)
}
struct TextParser<'a> {
src: &'a str,
pos: usize,
}
impl<'a> TextParser<'a> {
fn new(src: &'a str) -> Self { Self { src, pos: 0 } }
fn skip_ws(&mut self) {
while self.pos < self.src.len() && self.src.as_bytes()[self.pos].is_ascii_whitespace() {
self.pos += 1;
}
}
fn peek(&mut self) -> Option<u8> {
self.skip_ws();
self.src.as_bytes().get(self.pos).copied()
}
fn eat(&mut self, c: u8) -> Result<()> {
self.skip_ws();
if self.src.as_bytes().get(self.pos) == Some(&c) {
self.pos += 1;
Ok(())
} else {
Err(Error::Text(format!("expected '{}'", c as char)))
}
}
fn word(&mut self) -> Result<&'a str> {
self.skip_ws();
let start = self.pos;
while self.pos < self.src.len() {
let b = self.src.as_bytes()[self.pos];
if b.is_ascii_alphanumeric() || b == b'_' {
self.pos += 1;
} else {
break;
}
}
if self.pos == start { return Err(Error::Text("expected identifier".into())); }
Ok(&self.src[start..self.pos])
}
fn number_str(&mut self) -> Result<&'a str> {
self.skip_ws();
let start = self.pos;
if self.pos < self.src.len() && self.src.as_bytes()[self.pos] == b'-' {
self.pos += 1;
}
if self.pos >= self.src.len() || !self.src.as_bytes()[self.pos].is_ascii_digit() {
self.pos = start;
return Err(Error::Text("expected number".into()));
}
while self.pos < self.src.len() && self.src.as_bytes()[self.pos].is_ascii_digit() {
self.pos += 1;
}
if self.pos < self.src.len() && self.src.as_bytes()[self.pos] == b'.' {
self.pos += 1;
while self.pos < self.src.len() && self.src.as_bytes()[self.pos].is_ascii_digit() {
self.pos += 1;
}
}
if self.pos < self.src.len() && matches!(self.src.as_bytes()[self.pos], b'e' | b'E') {
self.pos += 1;
if self.pos < self.src.len() && matches!(self.src.as_bytes()[self.pos], b'+' | b'-') {
self.pos += 1;
}
while self.pos < self.src.len() && self.src.as_bytes()[self.pos].is_ascii_digit() {
self.pos += 1;
}
}
Ok(&self.src[start..self.pos])
}
fn string_lit(&mut self) -> Result<String> {
self.eat(b'"')?;
let mut s = String::new();
loop {
if self.pos >= self.src.len() {
return Err(Error::Text("unterminated string".into()));
}
let ch = self.src[self.pos..].chars().next().unwrap();
self.pos += ch.len_utf8();
match ch {
'"' => return Ok(s),
'\\' => {
if self.pos >= self.src.len() {
return Err(Error::Text("unterminated escape".into()));
}
let esc = self.src.as_bytes()[self.pos];
self.pos += 1;
match esc {
b'n' => s.push('\n'),
b'r' => s.push('\r'),
b't' => s.push('\t'),
b'\\' => s.push('\\'),
b'"' => s.push('"'),
b'0' => s.push('\0'),
_ => return Err(Error::Text(format!("unknown escape: \\{}", esc as char))),
}
}
c => s.push(c),
}
}
}
fn char_lit(&mut self) -> Result<char> {
self.eat(b'\'')?;
let ch = if self.pos < self.src.len() && self.src.as_bytes()[self.pos] == b'\\' {
self.pos += 1;
if self.pos >= self.src.len() {
return Err(Error::Text("unterminated escape".into()));
}
let esc = self.src.as_bytes()[self.pos];
self.pos += 1;
match esc {
b'n' => '\n',
b'r' => '\r',
b't' => '\t',
b'\\' => '\\',
b'\'' => '\'',
b'0' => '\0',
_ => return Err(Error::Text(format!("unknown escape: \\{}", esc as char))),
}
} else if self.pos < self.src.len() {
let c = self.src[self.pos..].chars().next().unwrap();
self.pos += c.len_utf8();
c
} else {
return Err(Error::Text("unterminated char".into()));
};
self.eat(b'\'')?;
Ok(ch)
}
fn hex_bytes(&mut self) -> Result<Vec<u8>> {
self.eat(b'#')?;
let start = self.pos;
while self.pos < self.src.len() && self.src.as_bytes()[self.pos].is_ascii_hexdigit() {
self.pos += 1;
}
let hex = &self.src[start..self.pos];
if hex.len() % 2 != 0 {
return Err(Error::Text("hex bytes must have even length".into()));
}
let mut out = Vec::with_capacity(hex.len() / 2);
for i in (0..hex.len()).step_by(2) {
out.push(u8::from_str_radix(&hex[i..i + 2], 16)
.map_err(|_| Error::Text("invalid hex".into()))?);
}
Ok(out)
}
fn comma_sep<F>(&mut self, close: u8, mut item: F) -> Result<()>
where F: FnMut(&mut Self) -> Result<()> {
if self.peek() != Some(close) {
item(self)?;
while self.peek() == Some(b',') {
self.eat(b',')?;
if self.peek() == Some(close) { break; }
item(self)?;
}
}
self.eat(close)
}
fn value(&mut self, schema: &SchemaType, enc: &mut Encoder) -> Result<()> {
match schema {
SchemaType::Bool => {
match self.word()? {
"true" => enc.bool(true),
"false" => enc.bool(false),
w => Err(Error::Text(format!("expected bool, got '{w}'"))),
}
}
SchemaType::U8 => { let s = self.number_str()?; enc.u8(s.parse().map_err(|e| Error::Text(format!("{e}")))?) }
SchemaType::U16 => { let s = self.number_str()?; enc.u16(s.parse().map_err(|e| Error::Text(format!("{e}")))?) }
SchemaType::U32 => { let s = self.number_str()?; enc.u32(s.parse().map_err(|e| Error::Text(format!("{e}")))?) }
SchemaType::U64 => { let s = self.number_str()?; enc.u64(s.parse().map_err(|e| Error::Text(format!("{e}")))?) }
SchemaType::S8 => { let s = self.number_str()?; enc.s8(s.parse().map_err(|e| Error::Text(format!("{e}")))?) }
SchemaType::S16 => { let s = self.number_str()?; enc.s16(s.parse().map_err(|e| Error::Text(format!("{e}")))?) }
SchemaType::S32 => { let s = self.number_str()?; enc.s32(s.parse().map_err(|e| Error::Text(format!("{e}")))?) }
SchemaType::S64 => { let s = self.number_str()?; enc.s64(s.parse().map_err(|e| Error::Text(format!("{e}")))?) }
SchemaType::F32 => { let s = self.number_str()?; enc.f32(s.parse().map_err(|e| Error::Text(format!("{e}")))?) }
SchemaType::F64 => { let s = self.number_str()?; enc.f64(s.parse().map_err(|e| Error::Text(format!("{e}")))?) }
SchemaType::Char => { let c = self.char_lit()?; enc.char(c) }
SchemaType::Unit => { self.eat(b'(')?; self.eat(b')')?; enc.unit() }
SchemaType::String => { let s = self.string_lit()?; enc.str(&s) }
SchemaType::Bytes => { let b = self.hex_bytes()?; enc.bytes(&b) }
SchemaType::List(inner) => {
self.eat(b'[')?;
enc.list_begin()?;
self.comma_sep(b']', |p| p.value(inner, enc))?;
enc.list_end()
}
SchemaType::Tuple(types) => {
self.eat(b'(')?;
enc.list_begin()?;
let mut it = types.iter();
self.comma_sep(b')', |p| {
let ty = it.next().ok_or(Error::Text("too many tuple elements".into()))?;
p.value(ty, enc)
})?;
enc.list_end()
}
SchemaType::Struct(fields) => {
self.eat(b'{')?;
enc.list_begin()?;
let mut it = fields.iter();
self.comma_sep(b'}', |p| {
let f = it.next().ok_or(Error::Text("too many struct fields".into()))?;
let name = p.word()?;
if name != f.name {
return Err(Error::Text(format!("expected field '{}', got '{name}'", f.name)));
}
p.eat(b':')?;
p.value(&f.schema, enc)
})?;
enc.list_end()
}
SchemaType::Enum(variants) => {
let name = self.word()?;
let field = variants.iter().find(|v| v.name == name)
.ok_or_else(|| Error::Text(format!("unknown variant '{name}'")))?;
enc.variant_begin(&field.name)?;
match &field.schema {
SchemaType::Unit => enc.unit()?,
SchemaType::Struct(fields) => {
self.eat(b'{')?;
enc.list_begin()?;
let mut it = fields.iter();
self.comma_sep(b'}', |p| {
let f = it.next().ok_or(Error::Text("too many fields".into()))?;
let n = p.word()?;
if n != f.name {
return Err(Error::Text(format!("expected field '{}', got '{n}'", f.name)));
}
p.eat(b':')?;
p.value(&f.schema, enc)
})?;
enc.list_end()?;
}
SchemaType::Tuple(types) => {
self.eat(b'(')?;
enc.list_begin()?;
let mut it = types.iter();
self.comma_sep(b')', |p| {
let ty = it.next().ok_or(Error::Text("too many elements".into()))?;
p.value(ty, enc)
})?;
enc.list_end()?;
}
other => {
self.eat(b'(')?;
self.value(other, enc)?;
self.eat(b')')?;
}
}
enc.variant_end()
}
SchemaType::Map(value_schema) => {
self.eat(b'{')?;
enc.map_begin()?;
self.comma_sep(b'}', |p| {
let key = if p.peek() == Some(b'"') {
p.string_lit()?
} else {
p.word()?.to_string()
};
p.eat(b':')?;
enc.variant_begin(&key)?;
p.value(value_schema, enc)?;
enc.variant_end()
})?;
enc.map_end()
}
SchemaType::Option(inner) => {
match self.word()? {
"None" => enc.option_none(),
"Some" => {
self.eat(b'(')?;
enc.option_some_begin()?;
self.value(inner, enc)?;
enc.option_some_end()?;
self.eat(b')')
}
w => Err(Error::Text(format!("expected Some or None, got '{w}'"))),
}
}
SchemaType::Result { ok, err } => {
match self.word()? {
"Ok" => {
self.eat(b'(')?;
enc.result_ok_begin()?;
self.value(ok, enc)?;
enc.result_ok_end()?;
self.eat(b')')
}
"Err" => {
self.eat(b'(')?;
enc.result_err_begin()?;
self.value(err, enc)?;
enc.result_err_end()?;
self.eat(b')')
}
w => Err(Error::Text(format!("expected Ok or Err, got '{w}'"))),
}
}
SchemaType::Any => Err(Error::Text("cannot lower SchemaType::Any".into())),
}
}
}
fn raise_value(schema: &SchemaType, dec: &mut Decoder, out: &mut String) -> Result<()> {
use std::fmt::Write;
match schema {
SchemaType::Bool => write!(out, "{}", dec.bool()?).unwrap(),
SchemaType::U8 => write!(out, "{}", dec.u8()?).unwrap(),
SchemaType::U16 => write!(out, "{}", dec.u16()?).unwrap(),
SchemaType::U32 => write!(out, "{}", dec.u32()?).unwrap(),
SchemaType::U64 => write!(out, "{}", dec.u64()?).unwrap(),
SchemaType::S8 => write!(out, "{}", dec.s8()?).unwrap(),
SchemaType::S16 => write!(out, "{}", dec.s16()?).unwrap(),
SchemaType::S32 => write!(out, "{}", dec.s32()?).unwrap(),
SchemaType::S64 => write!(out, "{}", dec.s64()?).unwrap(),
SchemaType::F32 => {
let v = dec.f32()?;
let s = v.to_string();
if s.contains('.') || s.contains('e') || s.contains('E') {
out.push_str(&s);
} else {
write!(out, "{s}.0").unwrap();
}
}
SchemaType::F64 => {
let v = dec.f64()?;
let s = v.to_string();
if s.contains('.') || s.contains('e') || s.contains('E') {
out.push_str(&s);
} else {
write!(out, "{s}.0").unwrap();
}
}
SchemaType::Char => {
let c = dec.char()?;
match c {
'\n' => out.push_str("'\\n'"),
'\r' => out.push_str("'\\r'"),
'\t' => out.push_str("'\\t'"),
'\\' => out.push_str("'\\\\'"),
'\'' => out.push_str("'\\''"),
'\0' => out.push_str("'\\0'"),
c => write!(out, "'{c}'").unwrap(),
}
}
SchemaType::Unit => { dec.unit()?; out.push_str("()"); }
SchemaType::String => {
let s = dec.str()?;
out.push('"');
for c in s.chars() {
match c {
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
'\\' => out.push_str("\\\\"),
'"' => out.push_str("\\\""),
'\0' => out.push_str("\\0"),
c => out.push(c),
}
}
out.push('"');
}
SchemaType::Bytes => {
let b = dec.bytes()?;
out.push('#');
for byte in b { write!(out, "{byte:02x}").unwrap(); }
}
SchemaType::List(inner) => {
out.push('[');
let mut list = dec.list()?;
let mut first = true;
while let Some(mut item) = list.next() {
if !first { out.push_str(", "); }
raise_value(inner, &mut item, out)?;
first = false;
}
out.push(']');
}
SchemaType::Tuple(types) => {
out.push('(');
let mut list = dec.list()?;
for (i, ty) in types.iter().enumerate() {
if i > 0 { out.push_str(", "); }
let mut item = list.next().ok_or(Error::UnexpectedEnd)?;
raise_value(ty, &mut item, out)?;
}
out.push(')');
}
SchemaType::Struct(fields) => {
out.push_str("{ ");
let mut list = dec.list()?;
for (i, f) in fields.iter().enumerate() {
if i > 0 { out.push_str(", "); }
let mut item = list.next().ok_or(Error::UnexpectedEnd)?;
write!(out, "{}: ", f.name).unwrap();
raise_value(&f.schema, &mut item, out)?;
}
out.push_str(" }");
}
SchemaType::Enum(variants) => {
let (name, mut payload) = dec.variant()?;
let field = variants.iter().find(|v| v.name == name)
.ok_or_else(|| Error::Text(format!("unknown variant '{name}'")))?;
out.push_str(name);
match &field.schema {
SchemaType::Unit => { payload.unit()?; }
SchemaType::Struct(fields) => {
out.push_str(" { ");
let mut list = payload.list()?;
for (i, f) in fields.iter().enumerate() {
if i > 0 { out.push_str(", "); }
let mut item = list.next().ok_or(Error::UnexpectedEnd)?;
write!(out, "{}: ", f.name).unwrap();
raise_value(&f.schema, &mut item, out)?;
}
out.push_str(" }");
}
SchemaType::Tuple(types) => {
out.push('(');
let mut list = payload.list()?;
for (i, ty) in types.iter().enumerate() {
if i > 0 { out.push_str(", "); }
let mut item = list.next().ok_or(Error::UnexpectedEnd)?;
raise_value(ty, &mut item, out)?;
}
out.push(')');
}
other => {
out.push('(');
raise_value(other, &mut payload, out)?;
out.push(')');
}
}
}
SchemaType::Map(value_schema) => {
out.push_str("{ ");
let mut map = dec.map()?;
let mut first = true;
while let Some((key, mut val)) = map.next()? {
if !first { out.push_str(", "); }
if !key.is_empty() && key.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_') {
out.push_str(key);
} else {
write!(out, "\"{key}\"").unwrap();
}
out.push_str(": ");
raise_value(value_schema, &mut val, out)?;
first = false;
}
out.push_str(" }");
}
SchemaType::Option(inner) => {
match dec.option()? {
None => out.push_str("None"),
Some(mut d) => {
out.push_str("Some(");
raise_value(inner, &mut d, out)?;
out.push(')');
}
}
}
SchemaType::Result { ok, err } => {
match dec.result()? {
Ok(mut d) => {
out.push_str("Ok(");
raise_value(ok, &mut d, out)?;
out.push(')');
}
Err(mut d) => {
out.push_str("Err(");
raise_value(err, &mut d, out)?;
out.push(')');
}
}
}
SchemaType::Any => return Err(Error::Text("cannot raise SchemaType::Any".into())),
}
Ok(())
}