diff --git a/examples/simple.rs b/examples/simple.rs index bc4ba22..dda569f 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,26 +1,18 @@ +use std::net::IpAddr; +use confique::{Config, Partial}; -mod config { - use std::path::PathBuf; +#[derive(Debug, Config)] +struct Http { + #[config(default = 8080)] + port: u16, - confique::config! { - #![derive_for_all(Debug, Clone)] + #[config(default = "127.0.0.1")] + bind: IpAddr, - config: { - log: { - /// Determines how many messages are logged. Log messages below - /// this level are not emitted. Possible values: "trace", "debug", - /// "info", "warn", "error" and "off". - level: log::LevelFilter = "debug", - - /// If this is set, log messages are also written to this file. - #[example = "/var/log/test.log"] - file: Option, - }, - } - } + foo: Option, } fn main() { - + println!("{:?}", Http::from_partial(::Partial::default_values())); } diff --git a/macro/Cargo.toml b/macro/Cargo.toml index 0c8cdc0..aa743bd 100644 --- a/macro/Cargo.toml +++ b/macro/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" proc-macro = true [dependencies] -syn = "1.0" +syn = { version = "1.0", features = ["extra-traits"] } quote = "1.0" proc-macro2 = "1.0" heck = "0.3.2" diff --git a/macro/src/ast.rs b/macro/src/ast.rs deleted file mode 100644 index c397ddc..0000000 --- a/macro/src/ast.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! Definition of the intermediate representation or AST. - -use proc_macro2::{Ident, TokenStream}; - - -/// The parsed input to the `gen_config` macro. -pub(crate) struct Input { - pub(crate) root: Node, - pub(crate) visibility: Option, - pub(crate) derive_for_all: Option, -} - -/// One node in the tree of the configuration format. Can either be a leaf node -/// (a string, int, float or bool value) or an internal node that contains -/// children. -pub(crate) enum NodeKind { - Obj(Obj), - Leaf(Leaf), -} - -pub(crate) struct Node { - /// The doc string lines. - pub(crate) doc: Vec, - /// Attributes that are used as specified and not interpreted by us. - pub(crate) attrs: Vec, - - pub(crate) name: syn::Ident, - pub(crate) kind: NodeKind, -} - -pub(crate) struct Obj { - pub(crate) typename: Option, - pub(crate) children: Vec, -} - -pub(crate) struct Leaf { - pub(crate) ty: syn::Type, - pub(crate) default: Option, - pub(crate) example: Option, -} - -/// The kinds of expressions (just literals) we allow for default or example -/// values. -pub(crate) enum Expr { - Str(syn::LitStr), - Int(syn::LitInt), - Float(syn::LitFloat), - Bool(syn::LitBool), -} - -impl Node { - pub(crate) fn typename(&self) -> Option { - match &self.kind { - NodeKind::Obj(Obj { typename, .. }) => { - use heck::CamelCase; - - let out = typename.clone().unwrap_or_else(|| { - Ident::new(&self.name.to_string().to_camel_case(), self.name.span()) - }); - - Some(out) - } - NodeKind::Leaf(_) => None, - } - } -} diff --git a/macro/src/gen.rs b/macro/src/gen.rs index 646d552..7222c19 100644 --- a/macro/src/gen.rs +++ b/macro/src/gen.rs @@ -1,367 +1,122 @@ use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; +use quote::{ToTokens, format_ident, quote}; use syn::Ident; -use std::fmt::{self, Write}; -use crate::ast::{Expr, Input, Leaf, Node, NodeKind, Obj}; +use crate::ir; -pub(crate) fn gen(input: Input) -> TokenStream { - let visibility = input.visibility.clone().unwrap_or(quote! { pub(crate) }); - let toml = gen_toml(&input); - let types = gen_types(&input, &visibility); +pub(crate) fn gen(input: ir::Input) -> TokenStream { + let partial_mod = gen_partial_mod(&input); + let config_impl = gen_config_impl(&input); quote! { - #visibility const TOML_TEMPLATE: &str = #toml; - - #types + #config_impl + #partial_mod } } -/// Generates the struct fields for both, the raw struct and the main struct. -fn gen_struct_fields(children: &[Node], visibility: &TokenStream) -> (TokenStream, TokenStream) { - let mut raw_fields = TokenStream::new(); - let mut main_fields = TokenStream::new(); - - for child in children { - let name = &child.name; - let doc = &child.doc; - - match &child.kind { - NodeKind::Obj(_) => { - let child_typename = child.typename().unwrap(); - let default_path = format!("{}::empty", child_typename); - raw_fields.extend(quote! { - #[serde(default = #default_path)] - #visibility #name: #child_typename, - }); - main_fields.extend(quote! { - #visibility #name: #child_typename, - }); - } - NodeKind::Leaf(Leaf { ty, .. }) => { - let inner = as_option(&ty).unwrap_or(&ty); - raw_fields.extend(quote! { - #visibility #name: Option<#inner>, - }); - main_fields.extend(quote! { - #( #[doc = #doc] )* - #visibility #name: #ty, - }); - } - } - } - - (raw_fields, main_fields) -} - -/// Generates the definition for `default_values`, a function associated with raw types. -fn gen_raw_default_constructor( - children: &[Node], - path: &[String], - visibility: &TokenStream, -) -> TokenStream { - let fields = collect_tokens(children, |node| { - let name = &node.name; - match &node.kind { - NodeKind::Leaf(Leaf { default: None, .. }) => quote! { #name: None, }, - NodeKind::Leaf(Leaf { default: Some(expr), ty, .. }) => { - // TODO: we can specialize this for certain types such that we - // don't have to invoke serde. - let inner_type = as_option(ty).unwrap_or(ty); - let path = append_path(path, name); - let msg = format!( - "default configuration value for '{}' cannot be deserialized as '{}'", - path, - inner_type.to_token_stream(), - ); +fn gen_config_impl(input: &ir::Input) -> TokenStream { + let name = &input.name; + let (partial_mod_name, partial_struct_name) = partial_names(&input.name); + let field_names = input.fields.iter().map(|f| &f.name); + let from_exprs = input.fields.iter().map(|f| { + let field_name = &f.name; + match unwrap_option(&f.ty) { + Some(_) => quote! { partial.#field_name }, + None => { + let path = field_name.to_string(); quote! { - #name: Some({ - let result: Result<_, confique::serde::de::value::Error> - = Deserialize::deserialize(#expr.into_deserializer()); - result.expect(#msg) - }), - } - }, - NodeKind::Obj(_) => { - let child_typename = node.typename().unwrap(); - quote! { - #name: #child_typename::default_values(), + partial.#field_name.ok_or(confique::Error::MissingValue(#path))? } } } }); quote! { - /// Returns an instance of `Self` that contains the specified default - /// configuration values. All fields that don't have a default value - /// specified are `None`. - #visibility fn default_values() -> Self { - Self { #fields } - } - } -} + impl confique::Config for #name { + type Partial = #partial_mod_name::#partial_struct_name; -/// Generates the definition for `empty`, a function associated with raw types. -fn gen_raw_empty_constructor(children: &[Node], visibility: &TokenStream) -> TokenStream { - let fields = collect_tokens(children, |node| { - let name = &node.name; - match &node.kind { - NodeKind::Leaf(_) => quote! { #name: None, }, - NodeKind::Obj(_) => { - let child_typename = node.typename().unwrap(); - quote! { - #name: #child_typename::empty(), - } - } - } - }); - - quote! { - /// Returns an instance of `Self` where all values are `None`. - #visibility fn empty() -> Self { - Self { #fields } - } - } -} - -/// Generates the definition of the `overwrite_with` method on raw types. -fn gen_raw_overwrite_with_method(children: &[Node], visibility: &TokenStream) -> TokenStream { - let fields = collect_tokens(children, |Node { name, kind, .. }| { - match kind { - NodeKind::Leaf(_) => quote! { #name: other.#name.or(self.#name), }, - NodeKind::Obj(_) => quote! { #name: self.#name.overwrite_with(other.#name), }, - } - }); - - quote! { - // TODO: Find better name - #visibility fn overwrite_with(self, other: Self) -> Self { - Self { #fields } - } - } -} - -/// Generates the impl to convert from a raw type to a main type. -fn gen_try_from_impl(typename: &Ident, children: &[Node], path: &[String]) -> TokenStream { - let fields = collect_tokens(children, |Node { name, kind, .. }| { - match kind { - NodeKind::Leaf(Leaf { ty, .. }) => { - if as_option(ty).is_some() { - // If this value is optional, we just move it as it can never fail. - quote! { #name: src.#name, } - } else { - // Otherwise, we return an error if the value hasn't been specified. - let path = append_path(path, name); - - quote! { - #name: src.#name.ok_or(confique::TryFromError { path: #path })?, - } - } - }, - NodeKind::Obj(_) => quote! { - #name: std::convert::TryFrom::try_from(src.#name)?, - }, - } - }); - - quote! { - impl std::convert::TryFrom for #typename { - type Error = confique::TryFromError; - fn try_from(src: raw::#typename) -> Result { + fn from_partial(partial: Self::Partial) -> Result { Ok(Self { - #fields + #( #field_names: #from_exprs, )* }) } } } } -fn append_path(path: &[String], name: &Ident) -> String { - if path.is_empty() { - name.to_string() - } else { - format!("{}.{}", path.join("."), name) - } + +/// Returns the names of the module and struct for the partial type: +/// `(mod_name, struct_name)`. +fn partial_names(original_name: &Ident) -> (Ident, Ident) { + use heck::SnakeCase; + ( + format_ident!("confique_partial_{}", original_name.to_string().to_snake_case()), + format_ident!("Partial{}", original_name), + ) } -fn gen_types(input: &Input, visibility: &TokenStream) -> TokenStream { - let mut raw_types = TokenStream::new(); - let mut main_types = TokenStream::new(); +fn gen_partial_mod(input: &ir::Input) -> TokenStream { + let (mod_name, struct_name) = partial_names(&input.name); + let visibility = &input.visibility; + let inner_visibility = inner_visibility(&input.visibility); - visit(input, |node, path| { - if let NodeKind::Obj(Obj { children, .. }) = &node.kind { - let typename = node.typename().unwrap(); + // Prepare some tokens per field. + let field_names = input.fields.iter().map(|f| &f.name).collect::>(); + let field_types = input.fields.iter().map(|f| { + let inner = unwrap_option(&f.ty).unwrap_or(&f.ty); + quote! { Option<#inner> } + }); + let defaults = input.fields.iter().map(|f| { + match &f.default { + None => quote! { None }, + Some(default) => { + let msg = format!( + "default config value for `{}::{}` cannot be deserialized", + input.name, + f.name, + ); - let (raw_fields, main_fields) = gen_struct_fields(&children, visibility); - let raw_default_constructor = gen_raw_default_constructor(&children, path, visibility); - let raw_empty_constructor = gen_raw_empty_constructor(&children, visibility); - let overwrite_with_method = gen_raw_overwrite_with_method(&children, visibility); - let try_from_impl = gen_try_from_impl(&typename, &children, path); - - // Raw type definition - raw_types.extend(quote! { - #[derive(Debug, Deserialize)] - #visibility struct #typename { - #raw_fields + quote! { + Some(confique::internal::deserialize_default(#default).expect(#msg)) } - - impl #typename { - #raw_default_constructor - #raw_empty_constructor - #overwrite_with_method - } - }); - - // Main type definition - let doc = &node.doc; - let attrs = &node.attrs; - let derives = input.derive_for_all.clone().unwrap_or(quote! { Debug }); - - main_types.extend(quote! { - #( #[doc = #doc] )* - #( #attrs )* - #[derive( #derives )] - #visibility struct #typename { - #main_fields - } - - #try_from_impl - }); + }, } }); quote! { - /// Types where all configuration values are optional. - /// - /// The types in this module also represent the full configuration tree, - /// but all values are optional. That's useful for intermediate steps or - /// "layers" of configuration sources. Imagine that the three layers: - /// environment variables, a TOML file and the fixed default values. The - /// only thing that matters is that required values are present after - /// merging all sources, but each individual is allowed to lack required - /// values. - /// - /// These types implement `serde::Deserialize` and `Debug`. - #visibility mod raw { - // We have to add this blanket use to be able to refer to all the - // types the user referred to. + #visibility mod #mod_name { use super::*; - use confique::serde::{Deserialize, de::IntoDeserializer}; - - #raw_types - } - - #main_types - } -} - - -/// Generates the TOML template file. -fn gen_toml(input: &Input) -> String { - /// Writes all doc comments to the file. - fn write_doc(out: &mut String, doc: &[String]) { - for line in doc { - writeln!(out, "#{}", line).unwrap(); - } - } - - /// Adds zero, one or two line breaks to make sure that there are at least - /// two line breaks at the end of the string. - fn add_empty_line(out: &mut String) { - match () { - () if out.ends_with("\n\n") => {}, - () if out.ends_with('\n') => out.push('\n'), - _ => out.push_str("\n\n"), - } - } - - - let mut out = String::new(); - visit(input, |Node { name, doc, kind, .. }, path| { - match kind { - NodeKind::Obj(_) => { - write_doc(&mut out, doc); - - // If a new subsection starts, we always print the header, even if not - // strictly necessary. - if path.is_empty() { - add_empty_line(&mut out); - } else { - writeln!(out, "[{}]", path.join(".")).unwrap(); - } + #[derive(confique::serde::Deserialize)] + #inner_visibility struct #struct_name { + #( #inner_visibility #field_names: #field_types, )* } - NodeKind::Leaf(Leaf { ty, default, example }) => { - write_doc(&mut out, doc); - - // Add note about default value or the value being required. - match default { - Some(default) => { - if !doc.is_empty() { - writeln!(out, "#").unwrap(); - } - writeln!(out, "# Default: {}", default).unwrap(); - } - None if as_option(ty).is_some() => {} - None => { - if !doc.is_empty() { - writeln!(out, "#").unwrap(); - } - writeln!(out, "# Required! This value must be specified.").unwrap(); + impl confique::Partial for #struct_name { + fn empty() -> Self { + Self { + #( #field_names: None, )* } } - // Commented out key and optional example. - match example.as_ref().or(default.as_ref()) { - Some(example) => writeln!(out, "#{} = {}", name, example).unwrap(), - None => writeln!(out, "#{} =", name).unwrap() + fn default_values() -> Self { + Self { + #( #field_names: #defaults, )* + } } - - add_empty_line(&mut out); - } - } - }); - - // Make sure there is only a single trailing newline. - while out.ends_with("\n\n") { - out.pop(); - } - - out -} - -/// Visits all nodes in depth-first session (visiting the parent before its -/// children). -fn visit(input: &Input, mut visitor: F) -where - F: FnMut(&Node, &[String]), -{ - let mut stack = vec![(&input.root, vec![])]; - while let Some((node, path)) = stack.pop() { - visitor(&node, &path); - - if let NodeKind::Obj(Obj { children, .. }) = &node.kind { - for child in children.iter().rev() { - let mut child_path = path.clone(); - child_path.push(child.name.to_string()); - stack.push((child, child_path)); + fn with_fallback(self, fallback: Self) -> Self { + Self { + #( #field_names: self.#field_names.or(fallback.#field_names), )* + } + } } } } } -/// Iterates over `it`, calling `f` for each element, collecting all returned -/// token streams into one. -fn collect_tokens( - it: impl IntoIterator, - f: impl FnMut(T) -> TokenStream, -) -> TokenStream { - it.into_iter().map(f).collect() -} /// Checks if the given type is an `Option` and if so, return the inner type. /// @@ -370,7 +125,7 @@ fn collect_tokens( /// can only check if it "looks" like an `Option`. Of course, stuff can go /// wrong. But that's the best we can do and it's highly unlikely that someone /// shadows `Option`. -fn as_option(ty: &syn::Type) -> Option<&syn::Type> { +fn unwrap_option(ty: &syn::Type) -> Option<&syn::Type> { let ty = match ty { syn::Type::Path(path) => path, _ => return None, @@ -404,8 +159,7 @@ fn as_option(ty: &syn::Type) -> Option<&syn::Type> { } } - -impl ToTokens for Expr { +impl ToTokens for ir::Expr { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::Str(lit) => lit.to_tokens(tokens), @@ -416,16 +170,23 @@ impl ToTokens for Expr { } } -// This `Display` impl is for writing into a TOML file. -impl fmt::Display for Expr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - // TODO: not sure if `escape_debug` is really what we want here, but - // it's working for now. - Self::Str(lit) => write!(f, "\"{}\"", lit.value().escape_debug()), - Self::Int(lit) => lit.fmt(f), - Self::Float(lit) => lit.fmt(f), - Self::Bool(lit) => lit.value.fmt(f), +fn inner_visibility(outer: &syn::Visibility) -> TokenStream { + match outer { + // These visibilities can be used as they are. No adjustment needed. + syn::Visibility::Public(_) | syn::Visibility::Crate(_) => quote! { outer }, + + // The inherited one is relative to the parent module. + syn::Visibility::Inherited => quote! { pub(super) }, + + // If the path in the `pub(in )` visibility is absolute, we can + // use it like that as well. + syn::Visibility::Restricted(r) if r.path.leading_colon.is_some() => quote! { outer }, + + // But in the case `pub(in )` with a relative path, we have to + // prefix `super::`. + syn::Visibility::Restricted(r) => { + let path = &r.path; + quote! { pub(in super::#path) } } } } diff --git a/macro/src/ir.rs b/macro/src/ir.rs new file mode 100644 index 0000000..fe375f5 --- /dev/null +++ b/macro/src/ir.rs @@ -0,0 +1,184 @@ +//! Definition of the intermediate representation. + +use syn::{Error, Token, parse::{Parse, ParseStream}, spanned::Spanned}; + + +/// The parsed input to the `gen_config` macro. +#[derive(Debug)] +pub(crate) struct Input { + pub(crate) doc: Vec, + pub(crate) visibility: syn::Visibility, + pub(crate) name: syn::Ident, + pub(crate) fields: Vec, +} + +#[derive(Debug)] +pub(crate) struct Field { + pub(crate) doc: Vec, + pub(crate) name: syn::Ident, + pub(crate) ty: syn::Type, + pub(crate) default: Option, + + // TODO: + // - serde attributes + // - attributes + // - example +} + +/// The kinds of expressions (just literals) we allow for default or example +/// values. +#[derive(Debug)] +pub(crate) enum Expr { + Str(syn::LitStr), + Int(syn::LitInt), + Float(syn::LitFloat), + Bool(syn::LitBool), + // TODO: arrays? +} + +impl Input { + pub(crate) fn from_ast(mut input: syn::DeriveInput) -> Result { + let fields = match input.data { + syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(f), .. }) => f, + _ => return Err(Error::new( + input.span(), + "`confique::Config` can only be derive for structs with named fields", + )), + }; + + let doc = extract_doc(&mut input.attrs); + let fields = fields.named.into_iter() + .map(Field::from_ast) + .collect::, _>>()?; + + + Ok(Self { + doc, + visibility: input.vis, + name: input.ident, + fields, + }) + } +} + +impl Field { + fn from_ast(mut field: syn::Field) -> Result { + let doc = extract_doc(&mut field.attrs); + let attrs = extract_internal_attrs(&mut field.attrs)?; + // TODO: check no other attributes are here + + Ok(Self { + doc, + default: attrs.default, + name: field.ident.expect("bug: expected named field"), + ty: field.ty, + }) + } +} + +impl Expr { + fn from_lit(lit: syn::Lit) -> Result { + match lit { + syn::Lit::Str(l) => Ok(Self::Str(l)), + syn::Lit::Int(l) => Ok(Self::Int(l)), + syn::Lit::Float(l) => Ok(Self::Float(l)), + syn::Lit::Bool(l) => Ok(Self::Bool(l)), + + _ => { + let msg = "only string, integer, float and bool literals are allowed here"; + Err(Error::new(lit.span(), msg)) + } + } + } +} + +/// Extracts all doc string attributes from the list and return them as list of +/// strings (in order). +fn extract_doc(attrs: &mut Vec) -> Vec { + extract_attrs(attrs, |attr| { + match attr.parse_meta().ok()? { + syn::Meta::NameValue(syn::MetaNameValue { + lit: syn::Lit::Str(s), + path, + .. + }) if path.is_ident("doc") => Some(s.value()), + _ => None, + } + }) +} + +fn extract_attrs(attrs: &mut Vec, mut pred: P) -> Vec +where + P: FnMut(&syn::Attribute) -> Option, +{ + // TODO: use `Vec::drain_filter` once stabilized. The current impl is O(n²). + let mut i = 0; + let mut out = Vec::new(); + while i < attrs.len() { + match pred(&attrs[i]) { + Some(v) => { + out.push(v); + attrs.remove(i); + } + None => i += 1, + } + } + + out +} + +fn extract_internal_attrs( + attrs: &mut Vec, +) -> Result { + let internal_attrs = extract_attrs(attrs, |attr| { + if attr.path.is_ident("config") { + // TODO: clone not necessary once we use drain_filter + Some(attr.clone()) + } else { + None + } + }); + + let mut out = InternalAttrs::default(); + for attr in internal_attrs { + match attr.parse_args::()? { + InternalAttr::Default(expr) => { + if out.default.is_some() { + let msg = format!( + "duplicate '{}' confique attribute", + attr.path.get_ident().unwrap() + ); + return Err(Error::new(attr.span(), msg)); + } + + out.default = Some(expr); + } + } + } + + Ok(out) +} + +#[derive(Default)] +struct InternalAttrs { + default: Option, +} + +enum InternalAttr { + Default(Expr), +} + +impl Parse for InternalAttr { + fn parse(input: ParseStream) -> Result { + let ident: syn::Ident = input.parse()?; + match &*ident.to_string() { + "default" => { + let _: Token![=] = input.parse()?; + let expr = Expr::from_lit(input.parse()?)?; + Ok(Self::Default(expr)) + } + + _ => Err(syn::Error::new(ident.span(), "unknown confique attribute")), + } + } +} diff --git a/macro/src/lib.rs b/macro/src/lib.rs index a213226..228e646 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -1,16 +1,14 @@ use proc_macro::TokenStream as TokenStream1; -mod ast; mod gen; -mod parse; +mod ir; -/// Defines a configuration in a special syntax. TODO: explain what this -/// generates. -#[proc_macro] +#[proc_macro_derive(Config, attributes(config))] pub fn config(input: TokenStream1) -> TokenStream1 { - syn::parse2::(input.into()) + syn::parse2::(input.into()) + .and_then(ir::Input::from_ast) .map(gen::gen) .unwrap_or_else(|e| e.to_compile_error()) .into() diff --git a/macro/src/parse.rs b/macro/src/parse.rs deleted file mode 100644 index 106e255..0000000 --- a/macro/src/parse.rs +++ /dev/null @@ -1,252 +0,0 @@ -use proc_macro2::TokenStream; -use quote::ToTokens; -use syn::{ - Error, Ident, - parse::{Parse, ParseStream}, - punctuated::Punctuated, - spanned::Spanned, -}; - -use crate::ast::{Expr, Input, Leaf, Node, NodeKind, Obj}; - - - -impl Parse for Input { - fn parse(input: ParseStream) -> Result { - let mut outer_attrs = input.call(syn::Attribute::parse_inner)?; - let visibility = extract_visibility(&mut outer_attrs)?; - let derive_for_all = extract_single_list_attr("derive_for_all", &mut outer_attrs)?; - assert_no_extra_attrs(&outer_attrs)?; - - // Parse top level object. - let root: Node = input.parse()?; - if root.name != "config" { - return Err(syn::Error::new( - root.name.span(), - "top level object must have the name 'config'", - )); - } - - // Make sure we have at most one trailing comma - if input.peek(syn::Token![,]) { - let _: syn::Token![,] = input.parse().unwrap(); - } - - if !input.is_empty() { - return Err(syn::Error::new( - input.span(), - "unexpected additional tokens (only one root element allowed)", - )); - } - - Ok(Self { root, visibility, derive_for_all }) - } -} - - -impl Parse for Node { - fn parse(input: ParseStream) -> Result { - let mut attrs = input.call(syn::Attribute::parse_outer)?; - let doc = extract_doc(&mut attrs)?; - - // All nodes start with an identifier and a colon. - let name = input.parse()?; - let _: syn::Token![:] = input.parse()?; - - let out = if input.lookahead1().peek(syn::token::Brace) { - // --- A nested Internal --- - let typename = extract_typename(&mut attrs)?; - let forwarded_attrs = extract_attrs(&["derive"], &mut attrs); - - let inner; - syn::braced!(inner in input); - let fields = inner.call(>::parse_terminated)?; - - Self { - doc, - attrs: forwarded_attrs, - name, - kind: NodeKind::Obj(Obj { - typename, - children: fields.into_iter().collect(), - }), - } - } else { - // --- A single value --- - - // Type is mandatory. - let ty = input.parse()?; - - // Optional default value. - let default = if input.lookahead1().peek(syn::Token![=]) { - let _: syn::Token![=] = input.parse()?; - Some(input.parse()?) - } else { - None - }; - - // Optional example value. - let example = attrs.iter() - .position(|attr| attr.path.is_ident("example")) - .map(|i| { - let attr = attrs.remove(i); - parse_attr_value::(attr.tokens) - }) - .transpose()?; - - Self { - doc, - attrs: vec![], - name, - kind: NodeKind::Leaf(Leaf { ty, default, example }), - } - }; - - assert_no_extra_attrs(&attrs)?; - - Ok(out) - } -} - -impl Parse for Expr { - fn parse(input: ParseStream) -> Result { - let lit = input.parse::()?; - let out = match lit { - syn::Lit::Str(l) => Self::Str(l), - syn::Lit::Int(l) => Self::Int(l), - syn::Lit::Float(l) => Self::Float(l), - syn::Lit::Bool(l) => Self::Bool(l), - - _ => { - let msg = "only string, integer, float and bool literals are allowed here"; - return Err(Error::new(lit.span(), msg)); - } - }; - - Ok(out) - } -} - -/// Makes sure that the given list is empty or returns an error otherwise. -fn assert_no_extra_attrs(attrs: &[syn::Attribute]) -> Result<(), Error> { - if let Some(attr) = attrs.get(0) { - let msg = "unknown/unexpected/duplicate attribute in this position"; - return Err(Error::new(attr.span(), msg)); - } - - Ok(()) -} - -/// Parses the tokenstream as a `T` preceeded by a `=`. This is useful for -/// attributes of the form `#[foo = ]`. -fn parse_attr_value(tokens: TokenStream) -> Result { - use syn::parse::Parser; - - fn parser(input: ParseStream) -> Result { - let _: syn::Token![=] = input.parse()?; - input.parse() - } - - parser.parse2(tokens) -} - -/// Extract all doc attributes from the list and return them as simple strings. -fn extract_doc(attrs: &mut Vec) -> Result, Error> { - let out = attrs.iter() - .filter(|attr| attr.path.is_ident("doc")) - .map(|attr| parse_attr_value::(attr.tokens.clone()).map(|lit| lit.value())) - .collect::>()?; - - // I know this is algorithmically not optimal, but `drain_filter` is still - // unstable and I can't be bothered to write the proper algorithm right now. - attrs.retain(|attr| !attr.path.is_ident("doc")); - - Ok(out) -} - -/// Extracts all attributes with a path contained in `names`. -fn extract_attrs(names: &[&str], attrs: &mut Vec) -> Vec { - let (matches, rest) = attrs.drain(..) - .partition(|attr| names.iter().any(|n| attr.path.is_ident(n))); - - *attrs = rest; - matches -} - -fn extract_single_attr( - name: &str, - attrs: &mut Vec, -) -> Result, Error> { - let attr = match attrs.iter().position(|attr| attr.path.is_ident(name)) { - None => return Ok(None), - Some(pos) => attrs.remove(pos), - }; - - if let Some(dupe) = attrs.iter().find(|attr| attr.path.is_ident(name)) { - let msg = format!("duplicate `{}` attribute", name); - return Err(Error::new(dupe.span(), msg)); - } - - Ok(Some(attr)) -} - -fn extract_single_name_value_attr( - name: &str, - attrs: &mut Vec, -) -> Result, Error> { - let attr = match extract_single_attr(name, attrs)? { - None => return Ok(None), - Some(attr) => attr, - }; - - match attr.parse_meta()? { - syn::Meta::NameValue(nv) => Ok(Some(nv.lit)), - other => { - let msg = format!(r#"expected `name = "value"` attribute syntax for `{}`"#, name); - Err(Error::new(other.span(), msg)) - } - } -} - -fn extract_single_list_attr( - name: &str, - attrs: &mut Vec, -) -> Result, Error> { - let attr = match extract_single_attr(name, attrs)? { - None => return Ok(None), - Some(attr) => attr, - }; - - match attr.parse_meta()? { - syn::Meta::List(list) => Ok(Some(list.nested.to_token_stream())), - other => { - let msg = format!(r#"expected `{}(...)` attribute syntax"#, name); - return Err(Error::new(other.span(), msg)); - } - } -} - -fn assert_string_lit(lit: syn::Lit) -> Result { - match lit { - syn::Lit::Str(s) => Ok(s.value()), - _ => Err(Error::new(lit.span(), "expected string literal")), - } -} - -/// `#[visibility = "..."]` -fn extract_visibility(attrs: &mut Vec) -> Result, Error> { - extract_single_name_value_attr("visibility", attrs)? - .map(|v| Ok::<_, syn::Error>(assert_string_lit(v)?.parse::()?)) - .transpose() -} - -/// `#[typename = "..."]` -fn extract_typename(attrs: &mut Vec) -> Result, Error> { - extract_single_name_value_attr("typename", attrs)? - .map(|lit| { - let span = lit.span(); - let s = assert_string_lit(lit)?; - Ok(Ident::new(&s, span)) - }) - .transpose() -} diff --git a/src/internal.rs b/src/internal.rs new file mode 100644 index 0000000..175523d --- /dev/null +++ b/src/internal.rs @@ -0,0 +1,11 @@ +//! These functions are used by the code generated by the macro, but is not +//! intended to be used directly. None of this is covered by semver! Do not use +//! any of this directly. + +pub fn deserialize_default(src: I) -> Result +where + I: for<'de> serde::de::IntoDeserializer<'de>, + O: for<'de> serde::Deserialize<'de>, +{ + O::deserialize(src.into_deserializer()) +} diff --git a/src/lib.rs b/src/lib.rs index 76952cc..4020536 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,35 +1,53 @@ use std::fmt; -pub use confique_macro::config as config; +use serde::Deserialize; + pub use serde; +pub use confique_macro::Config; -#[cfg(feature = "doc-example")] -pub mod example; +// #[cfg(feature = "doc-example")] +// pub mod example; + +#[doc(hidden)] +pub mod internal; -/// Error for the `TryFrom` conversion from raw types to the main types. -/// -/// This error is returned when a required value is `None` in the raw type. -#[derive(Clone)] -pub struct TryFromError { - /// This is only public so that macro generated code can created instances - /// of this type. - #[doc(hidden)] - pub path: &'static str, +pub trait Config: Sized { + type Partial: Partial; + + fn from_partial(partial: Self::Partial) -> Result; } -impl fmt::Display for TryFromError { +pub trait Partial: for<'de> Deserialize<'de> { + fn empty() -> Self; + fn default_values() -> Self; + fn with_fallback(self, fallback: Self) -> Self; +} + + +pub enum Error { + /// Returned by `Config::from_partial` when the partial does not contain + /// values for all required configuration values. The string is a + /// human-readable path to the value, e.g. `http.port`. + MissingValue(&'static str), +} + +impl std::error::Error for Error {} + +impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - std::write!(f, "required configuration value is missing: '{}'", self.path) + match self { + Self::MissingValue(path) => { + std::write!(f, "required configuration value is missing: '{}'", path) + } + } } } -impl fmt::Debug for TryFromError { +impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) } } - -impl std::error::Error for TryFromError {}