Parse #[env] attribute and representing it in meta types

This commit is contained in:
Lukas Kalbertodt
2021-07-25 11:36:36 +02:00
parent 3c7376035c
commit db2aebc270
5 changed files with 114 additions and 34 deletions

View File

@@ -15,6 +15,7 @@ struct Conf {
#[derive(Debug, Config)]
struct Http {
/// The port the server will listen on.
#[config(env = "PORT")]
port: u16,
/// The bind address of the server. Can be set to `0.0.0.0` for example, to

View File

@@ -2,7 +2,7 @@ use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, format_ident, quote};
use syn::Ident;
use crate::ir::{self, Expr, FieldKind};
use crate::ir::{self, Expr, FieldKind, LeafKind};
pub(crate) fn gen(input: ir::Input) -> TokenStream {
@@ -31,10 +31,10 @@ fn gen_config_impl(input: &ir::Input) -> TokenStream {
})?
}
}
FieldKind::OptionalLeaf { .. } => {
FieldKind::Leaf { kind: LeafKind::Optional { .. }, .. } => {
quote! { partial.#field_name }
}
FieldKind::RequiredLeaf { .. } => {
FieldKind::Leaf { kind: LeafKind::Required { .. }, .. } => {
quote! {
partial.#field_name
.ok_or(confique::internal::missing_value_error(#path.into()))?
@@ -81,8 +81,12 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream {
let struct_fields = input.fields.iter().map(|f| {
let name = &f.name;
match &f.kind {
FieldKind::RequiredLeaf { ty, .. } => quote! { #inner_vis #name: Option<#ty> },
FieldKind::OptionalLeaf { inner_ty } => quote! { #inner_vis #name: Option<#inner_ty> },
FieldKind::Leaf { kind: LeafKind::Required { ty, .. }, .. } => quote! {
#inner_vis #name: Option<#ty>
},
FieldKind::Leaf { kind: LeafKind::Optional { inner_ty }, .. } => quote! {
#inner_vis #name: Option<#inner_ty>
},
FieldKind::Nested { ty } => quote! {
#[serde(default = "confique::Partial::empty")]
#inner_vis #name: <#ty as confique::Config>::Partial
@@ -100,10 +104,7 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream {
let defaults = input.fields.iter().map(|f| {
match &f.kind {
FieldKind::RequiredLeaf { default: None, .. } | FieldKind::OptionalLeaf { .. } => {
quote! { None }
}
FieldKind::RequiredLeaf { default: Some(default), .. } => {
FieldKind::Leaf { kind: LeafKind::Required { default: Some(default), .. }, .. } => {
let msg = format!(
"default config value for `{}::{}` cannot be deserialized",
input.name,
@@ -114,6 +115,7 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream {
Some(confique::internal::deserialize_default(#default).expect(#msg))
}
}
FieldKind::Leaf { .. } => quote! { None },
FieldKind::Nested { .. } => quote! { confique::Partial::default_values() },
}
});
@@ -138,9 +140,14 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream {
let is_complete_expr = input.fields.iter().map(|f| {
let name = &f.name;
match f.kind {
FieldKind::OptionalLeaf { .. } => quote! { true },
FieldKind::RequiredLeaf { .. } => quote! { self.#name.is_some() },
match &f.kind {
FieldKind::Leaf { kind, .. } => {
if kind.is_required() {
quote! { self.#name.is_some() }
} else {
quote! { true }
}
}
FieldKind::Nested { .. } => quote! { self.#name.is_complete() },
}
});
@@ -188,6 +195,13 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream {
/// Generates the whole `const META` item.
fn gen_meta(input: &ir::Input) -> TokenStream {
fn env_tokens(env: &Option<String>) -> TokenStream {
match env {
Some(key) => quote! { Some(#key) },
None => quote! { None },
}
}
let name_str = input.name.to_string();
let doc = &input.doc;
let meta_fields = input.fields.iter().map(|f| {
@@ -199,15 +213,25 @@ fn gen_meta(input: &ir::Input) -> TokenStream {
confique::meta::FieldKind::Nested { meta: &<#ty as confique::Config>::META }
}
}
FieldKind::OptionalLeaf { .. } => {
FieldKind::Leaf { env, kind: LeafKind::Optional { .. }} => {
let env = env_tokens(env);
quote! {
confique::meta::FieldKind::OptionalLeaf
confique::meta::FieldKind::Leaf {
env: #env,
kind: confique::meta::LeafKind::Optional,
}
}
}
FieldKind::RequiredLeaf { default, ty } => {
FieldKind::Leaf { env, kind: LeafKind::Required { default, ty, .. }} => {
let env = env_tokens(env);
let default_value = gen_meta_default(default, &ty);
quote! {
confique::meta::FieldKind::RequiredLeaf { default: #default_value }
confique::meta::FieldKind::Leaf {
env: #env,
kind: confique::meta::LeafKind::Required {
default: #default_value,
},
}
}
}
};

View File

@@ -28,15 +28,9 @@ pub(crate) struct Field {
#[derive(Debug)]
pub(crate) enum FieldKind {
/// A non-optional leaf. `ty` is not `Option<_>`.
RequiredLeaf {
default: Option<Expr>,
ty: syn::Type,
},
/// A leaf with type `Option<_>`.
OptionalLeaf {
inner_ty: syn::Type,
Leaf {
env: Option<String>,
kind: LeafKind,
},
/// A nested configuration. The type is never `Option<_>`.
@@ -45,6 +39,26 @@ pub(crate) enum FieldKind {
},
}
#[derive(Debug)]
pub(crate) enum LeafKind {
/// A non-optional leaf. `ty` is not `Option<_>`.
Required {
default: Option<Expr>,
ty: syn::Type,
},
/// A leaf with type `Option<_>`.
Optional {
inner_ty: syn::Type,
},
}
impl LeafKind {
pub(crate) fn is_required(&self) -> bool {
matches!(self, Self::Required { .. })
}
}
/// The kinds of expressions (just literals) we allow for default or example
/// values.
#[derive(Debug)]
@@ -100,11 +114,23 @@ impl Field {
"cannot specify `nested` and `default` attributes at the same time",
));
}
if attrs.env.is_some() {
return Err(Error::new(
field.ident.span(),
"cannot specify `nested` and `env` attributes at the same time",
));
}
FieldKind::Nested { ty: field.ty }
} else {
match unwrap_option(&field.ty) {
None => FieldKind::RequiredLeaf { default: attrs.default, ty: field.ty },
None => FieldKind::Leaf {
env: attrs.env,
kind: LeafKind::Required {
default: attrs.default,
ty: field.ty,
},
},
Some(inner) => {
if attrs.default.is_some() {
return Err(Error::new(
@@ -114,7 +140,12 @@ impl Field {
));
}
FieldKind::OptionalLeaf { inner_ty: inner.clone() }
FieldKind::Leaf {
env: attrs.env,
kind: LeafKind::Optional {
inner_ty: inner.clone(),
},
}
}
}
};
@@ -127,7 +158,7 @@ impl Field {
}
pub(crate) fn is_leaf(&self) -> bool {
matches!(self.kind, FieldKind::RequiredLeaf { .. } | FieldKind::OptionalLeaf { .. })
matches!(self.kind, FieldKind::Leaf { .. })
}
}
@@ -218,6 +249,10 @@ fn extract_internal_attrs(
duplicate_if!(out.nested);
out.nested = true;
}
InternalAttr::Env(key) => {
duplicate_if!(out.env.is_some());
out.env = Some(key);
}
}
}
@@ -228,11 +263,13 @@ fn extract_internal_attrs(
struct InternalAttrs {
nested: bool,
default: Option<Expr>,
env: Option<String>
}
enum InternalAttr {
Nested,
Default(Expr),
Env(String),
}
impl InternalAttr {
@@ -240,6 +277,7 @@ impl InternalAttr {
match self {
Self::Nested => "nested",
Self::Default(_) => "default",
Self::Env(_) => "env",
}
}
}
@@ -252,12 +290,21 @@ impl Parse for InternalAttr {
assert_empty(input)?;
Ok(Self::Nested)
}
"default" => {
let _: Token![=] = input.parse()?;
let expr = Expr::from_lit(input.parse()?)?;
assert_empty(input)?;
Ok(Self::Default(expr))
}
"env" => {
let _: Token![=] = input.parse()?;
let key: syn::LitStr = input.parse()?;
assert_empty(input)?;
Ok(Self::Env(key.value()))
}
_ => Err(syn::Error::new(ident.span(), "unknown confique attribute")),
}
}

View File

@@ -27,15 +27,23 @@ pub struct Field {
#[derive(Clone, Copy, Debug)]
pub enum FieldKind {
RequiredLeaf {
default: Option<Expr>,
Leaf {
env: Option<&'static str>,
kind: LeafKind,
},
OptionalLeaf,
Nested {
meta: &'static Meta,
},
}
#[derive(Clone, Copy, Debug)]
pub enum LeafKind {
Required {
default: Option<Expr>,
},
Optional,
}
#[derive(Debug, Clone, Copy)]
pub enum Expr {
Str(&'static str),

View File

@@ -3,7 +3,7 @@
use std::fmt::{self, Write};
use crate::{Config, meta::{Expr, FieldKind, Meta}};
use crate::{Config, meta::{Expr, FieldKind, LeafKind, Meta}};
@@ -141,7 +141,7 @@ fn format_impl(
}
match &field.kind {
FieldKind::RequiredLeaf { default } => {
FieldKind::Leaf { kind: LeafKind::Required { default }, .. } => {
// Emit comment about default value or the value being required
if options.comments {
if let Some(v) = default {
@@ -164,7 +164,7 @@ fn format_impl(
}
}
FieldKind::OptionalLeaf => emit!("#{} =", field.name),
FieldKind::Leaf { kind: LeafKind::Optional, .. } => emit!("#{} =", field.name),
FieldKind::Nested { meta } => {
let child_path = path.iter().copied().chain([field.name]).collect();