api-macro: move json part to util module
The json value type is more of an intermediate step between the TokenStream and the Schema type and should stay somewhat independent of it. We may want to reuse it for the router? Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
c5f9227c23
commit
97c29f0db5
@ -1,7 +1,6 @@
|
||||
extern crate proc_macro;
|
||||
extern crate proc_macro2;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::mem;
|
||||
|
||||
@ -10,195 +9,11 @@ use failure::Error;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::Ident;
|
||||
use syn::{parenthesized, Token};
|
||||
|
||||
use crate::util::SimpleIdent;
|
||||
|
||||
/// Most of our schema definition consists of a json-like notation.
|
||||
/// For parsing we mostly just need to destinguish between objects and non-objects.
|
||||
/// For specific expression types we match on the contained expression later on.
|
||||
enum JSONValue {
|
||||
Object(JSONObject),
|
||||
Expr(syn::Expr),
|
||||
}
|
||||
|
||||
impl JSONValue {
|
||||
/// When we expect an object, it's nicer to know why/what kind, so instead of
|
||||
/// `TryInto<JSONObject>` we provide this method:
|
||||
fn into_object(self, expected: &str) -> Result<JSONObject, syn::Error> {
|
||||
match self {
|
||||
JSONValue::Object(s) => Ok(s),
|
||||
JSONValue::Expr(e) => bail!(e => "expected {}", expected),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expect a json value to be an expression, not an object:
|
||||
impl TryFrom<JSONValue> for syn::Expr {
|
||||
type Error = syn::Error;
|
||||
fn try_from(value: JSONValue) -> Result<Self, syn::Error> {
|
||||
match value {
|
||||
JSONValue::Object(s) => bail!(s.brace_token.span, "unexpected object"),
|
||||
JSONValue::Expr(e) => Ok(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expect a json value to be a literal string:
|
||||
impl TryFrom<JSONValue> for syn::LitStr {
|
||||
type Error = syn::Error;
|
||||
fn try_from(value: JSONValue) -> Result<Self, syn::Error> {
|
||||
let expr = syn::Expr::try_from(value)?;
|
||||
if let syn::Expr::Lit(lit) = expr {
|
||||
if let syn::Lit::Str(lit) = lit.lit {
|
||||
return Ok(lit);
|
||||
}
|
||||
bail!(lit => "expected string literal");
|
||||
}
|
||||
bail!(expr => "expected string literal");
|
||||
}
|
||||
}
|
||||
|
||||
/// Expect a json value to be a literal boolean:
|
||||
impl TryFrom<JSONValue> for syn::LitBool {
|
||||
type Error = syn::Error;
|
||||
fn try_from(value: JSONValue) -> Result<Self, syn::Error> {
|
||||
let expr = syn::Expr::try_from(value)?;
|
||||
if let syn::Expr::Lit(lit) = expr {
|
||||
if let syn::Lit::Bool(lit) = lit.lit {
|
||||
return Ok(lit);
|
||||
}
|
||||
bail!(lit => "expected literal boolean");
|
||||
}
|
||||
bail!(expr => "expected literal boolean");
|
||||
}
|
||||
}
|
||||
|
||||
/// Expect a json value to be an identifier:
|
||||
impl TryFrom<JSONValue> for Ident {
|
||||
type Error = syn::Error;
|
||||
fn try_from(value: JSONValue) -> Result<Self, syn::Error> {
|
||||
let expr = syn::Expr::try_from(value)?;
|
||||
let span = expr.span();
|
||||
if let syn::Expr::Path(path) = expr {
|
||||
let mut iter = path.path.segments.into_pairs();
|
||||
let segment = iter
|
||||
.next()
|
||||
.ok_or_else(|| format_err!(span, "expected an identify, got an empty path"))?
|
||||
.into_value();
|
||||
if iter.next().is_some() {
|
||||
bail!(span, "expected an identifier, not a path");
|
||||
}
|
||||
if !segment.arguments.is_empty() {
|
||||
bail!(segment.arguments => "unexpected path arguments, expected an identifier");
|
||||
}
|
||||
return Ok(segment.ident);
|
||||
}
|
||||
bail!(expr => "expected an identifier");
|
||||
}
|
||||
}
|
||||
|
||||
/// Expect a json value to be our "simple" identifier, which can be either an Ident or a String, or
|
||||
/// the 'type' keyword:
|
||||
impl TryFrom<JSONValue> for SimpleIdent {
|
||||
type Error = syn::Error;
|
||||
fn try_from(value: JSONValue) -> Result<Self, syn::Error> {
|
||||
Ok(SimpleIdent::from(Ident::try_from(value)?))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parsing a json value should be simple enough: braces means we have an object, otherwise it must
|
||||
/// be an "expression".
|
||||
impl Parse for JSONValue {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
Ok(if lookahead.peek(syn::token::Brace) {
|
||||
JSONValue::Object(input.parse()?)
|
||||
} else {
|
||||
JSONValue::Expr(input.parse()?)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The "core" of our schema is a json object.
|
||||
struct JSONObject {
|
||||
pub brace_token: syn::token::Brace,
|
||||
pub elements: HashMap<SimpleIdent, JSONValue>,
|
||||
}
|
||||
|
||||
impl Parse for JSONObject {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let content;
|
||||
Ok(Self {
|
||||
brace_token: syn::braced!(content in input),
|
||||
elements: {
|
||||
let map_elems: Punctuated<JSONMapEntry, Token![,]> =
|
||||
content.parse_terminated(JSONMapEntry::parse)?;
|
||||
let mut elems = HashMap::with_capacity(map_elems.len());
|
||||
for c in map_elems {
|
||||
if elems.insert(c.key.clone().into(), c.value).is_some() {
|
||||
bail!(&c.key => "duplicate '{}' in schema", c.key);
|
||||
}
|
||||
}
|
||||
elems
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for JSONObject {
|
||||
type Target = HashMap<SimpleIdent, JSONValue>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.elements
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for JSONObject {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.elements
|
||||
}
|
||||
}
|
||||
|
||||
impl JSONObject {
|
||||
fn span(&self) -> Span {
|
||||
self.brace_token.span
|
||||
}
|
||||
|
||||
fn remove_required_element(&mut self, name: &str) -> Result<JSONValue, syn::Error> {
|
||||
self.remove(name)
|
||||
.ok_or_else(|| format_err!(self.span(), "missing required element: {}", name))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for JSONObject {
|
||||
type Item = <HashMap<SimpleIdent, JSONValue> as IntoIterator>::Item;
|
||||
type IntoIter = <HashMap<SimpleIdent, JSONValue> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.elements.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// An element in a json style map.
|
||||
struct JSONMapEntry {
|
||||
pub key: SimpleIdent,
|
||||
pub colon_token: Token![:],
|
||||
pub value: JSONValue,
|
||||
}
|
||||
|
||||
impl Parse for JSONMapEntry {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
Ok(Self {
|
||||
key: input.parse()?,
|
||||
colon_token: input.parse()?,
|
||||
value: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
use crate::util::{JSONObject, JSONValue, SimpleIdent};
|
||||
|
||||
/// The main `Schema` type.
|
||||
///
|
||||
|
@ -1,8 +1,12 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::Token;
|
||||
|
||||
/// A more relaxed version of Ident which allows hyphens.
|
||||
@ -102,3 +106,186 @@ impl Parse for SimpleIdent {
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Most of our schema definition consists of a json-like notation.
|
||||
/// For parsing we mostly just need to destinguish between objects and non-objects.
|
||||
/// For specific expression types we match on the contained expression later on.
|
||||
pub enum JSONValue {
|
||||
Object(JSONObject),
|
||||
Expr(syn::Expr),
|
||||
}
|
||||
|
||||
impl JSONValue {
|
||||
/// When we expect an object, it's nicer to know why/what kind, so instead of
|
||||
/// `TryInto<JSONObject>` we provide this method:
|
||||
pub fn into_object(self, expected: &str) -> Result<JSONObject, syn::Error> {
|
||||
match self {
|
||||
JSONValue::Object(s) => Ok(s),
|
||||
JSONValue::Expr(e) => bail!(e => "expected {}", expected),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expect a json value to be an expression, not an object:
|
||||
impl TryFrom<JSONValue> for syn::Expr {
|
||||
type Error = syn::Error;
|
||||
fn try_from(value: JSONValue) -> Result<Self, syn::Error> {
|
||||
match value {
|
||||
JSONValue::Object(s) => bail!(s.brace_token.span, "unexpected object"),
|
||||
JSONValue::Expr(e) => Ok(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expect a json value to be a literal string:
|
||||
impl TryFrom<JSONValue> for syn::LitStr {
|
||||
type Error = syn::Error;
|
||||
fn try_from(value: JSONValue) -> Result<Self, syn::Error> {
|
||||
let expr = syn::Expr::try_from(value)?;
|
||||
if let syn::Expr::Lit(lit) = expr {
|
||||
if let syn::Lit::Str(lit) = lit.lit {
|
||||
return Ok(lit);
|
||||
}
|
||||
bail!(lit => "expected string literal");
|
||||
}
|
||||
bail!(expr => "expected string literal");
|
||||
}
|
||||
}
|
||||
|
||||
/// Expect a json value to be a literal boolean:
|
||||
impl TryFrom<JSONValue> for syn::LitBool {
|
||||
type Error = syn::Error;
|
||||
fn try_from(value: JSONValue) -> Result<Self, syn::Error> {
|
||||
let expr = syn::Expr::try_from(value)?;
|
||||
if let syn::Expr::Lit(lit) = expr {
|
||||
if let syn::Lit::Bool(lit) = lit.lit {
|
||||
return Ok(lit);
|
||||
}
|
||||
bail!(lit => "expected literal boolean");
|
||||
}
|
||||
bail!(expr => "expected literal boolean");
|
||||
}
|
||||
}
|
||||
|
||||
/// Expect a json value to be an identifier:
|
||||
impl TryFrom<JSONValue> for Ident {
|
||||
type Error = syn::Error;
|
||||
fn try_from(value: JSONValue) -> Result<Self, syn::Error> {
|
||||
let expr = syn::Expr::try_from(value)?;
|
||||
let span = expr.span();
|
||||
if let syn::Expr::Path(path) = expr {
|
||||
let mut iter = path.path.segments.into_pairs();
|
||||
let segment = iter
|
||||
.next()
|
||||
.ok_or_else(|| format_err!(span, "expected an identify, got an empty path"))?
|
||||
.into_value();
|
||||
if iter.next().is_some() {
|
||||
bail!(span, "expected an identifier, not a path");
|
||||
}
|
||||
if !segment.arguments.is_empty() {
|
||||
bail!(segment.arguments => "unexpected path arguments, expected an identifier");
|
||||
}
|
||||
return Ok(segment.ident);
|
||||
}
|
||||
bail!(expr => "expected an identifier");
|
||||
}
|
||||
}
|
||||
|
||||
/// Expect a json value to be our "simple" identifier, which can be either an Ident or a String, or
|
||||
/// the 'type' keyword:
|
||||
impl TryFrom<JSONValue> for SimpleIdent {
|
||||
type Error = syn::Error;
|
||||
fn try_from(value: JSONValue) -> Result<Self, syn::Error> {
|
||||
Ok(SimpleIdent::from(Ident::try_from(value)?))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parsing a json value should be simple enough: braces means we have an object, otherwise it must
|
||||
/// be an "expression".
|
||||
impl Parse for JSONValue {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
Ok(if lookahead.peek(syn::token::Brace) {
|
||||
JSONValue::Object(input.parse()?)
|
||||
} else {
|
||||
JSONValue::Expr(input.parse()?)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The "core" of our schema is a json object.
|
||||
pub struct JSONObject {
|
||||
pub brace_token: syn::token::Brace,
|
||||
pub elements: HashMap<SimpleIdent, JSONValue>,
|
||||
}
|
||||
|
||||
impl Parse for JSONObject {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let content;
|
||||
Ok(Self {
|
||||
brace_token: syn::braced!(content in input),
|
||||
elements: {
|
||||
let map_elems: Punctuated<JSONMapEntry, Token![,]> =
|
||||
content.parse_terminated(JSONMapEntry::parse)?;
|
||||
let mut elems = HashMap::with_capacity(map_elems.len());
|
||||
for c in map_elems {
|
||||
if elems.insert(c.key.clone().into(), c.value).is_some() {
|
||||
bail!(&c.key => "duplicate '{}' in schema", c.key);
|
||||
}
|
||||
}
|
||||
elems
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for JSONObject {
|
||||
type Target = HashMap<SimpleIdent, JSONValue>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.elements
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for JSONObject {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.elements
|
||||
}
|
||||
}
|
||||
|
||||
impl JSONObject {
|
||||
pub fn span(&self) -> Span {
|
||||
self.brace_token.span
|
||||
}
|
||||
|
||||
pub fn remove_required_element(&mut self, name: &str) -> Result<JSONValue, syn::Error> {
|
||||
self.remove(name)
|
||||
.ok_or_else(|| format_err!(self.span(), "missing required element: {}", name))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for JSONObject {
|
||||
type Item = <HashMap<SimpleIdent, JSONValue> as IntoIterator>::Item;
|
||||
type IntoIter = <HashMap<SimpleIdent, JSONValue> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.elements.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// An element in a json style map.
|
||||
struct JSONMapEntry {
|
||||
pub key: SimpleIdent,
|
||||
pub colon_token: Token![:],
|
||||
pub value: JSONValue,
|
||||
}
|
||||
|
||||
impl Parse for JSONMapEntry {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
Ok(Self {
|
||||
key: input.parse()?,
|
||||
colon_token: input.parse()?,
|
||||
value: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user