mirror of
https://github.com/OMGeeky/confique.git
synced 2026-01-03 18:15:15 +01:00
Parse #[env] attribute and representing it in meta types
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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")),
|
||||
}
|
||||
}
|
||||
|
||||
14
src/meta.rs
14
src/meta.rs
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user