Skip to main content

declarative_env/
lib.rs

1//! This crate contains a macro for generating a struct which reads the environmental variables
2//! defined in the configured file.
3//!
4//! This allows to have a single point of definition for the env configuration, thus avoiding a
5//! possible incoherence between the documentation and the implementation.
6//!
7//! # Usage
8//! The macro must be attached to an empty struct definition:
9//! ```ignore
10//!    #[declarative_env(path = "./example.hjson", format = "hjson")]
11//!    struct MyConfig;
12//! ```
13//! > Note: the struct can have any visibility.
14//!
15//! The macro creates a struct with the same name of the empty struct, but it injects as fields all
16//! the variables defined in the `path` keyword.
17//!
18//! For example, the definition file:
19//! ```hjson
20//! SERVER_PORT: {
21//!    type: u16
22//!    description: The port the server will listen on
23//!    default: 8080
24//! }
25//! ```
26//! generates the struct
27//! ```
28//! struct MyConfig {
29//!    SERVER_PORT: u16,
30//! }
31//! ```
32//!
33//! The path of the definition file is relative to the cargo manifest dir (the one with
34//! `Cargo.toml`).
35//!
36//! The supported formats are specified in the `AcceptedFormat` enum; formats are the lowercase
37//! version of the enum variants, so `AcceptedFormat::Hjson` is selected with `format = "hjson"`.
38//!
39//! ## Env Variable Definition
40//! The env variable definition file contains a map of objects, where the key is the name of the
41//! env variable, and the value contains the metadata of the variable:
42//! - the **type** (accepted types are defined in `AcceptedRustType`).
43//! - the **description**, unused in generated code, required for documentation purpose.
44//! - the **default** value, optional (it must be parsable as the defiend **type**).
45//!
46//! ### Accepted formats
47//! This crate supports the following formats for the definition file:
48//! - hjson
49//!
50//! ### Accepted types
51//! This crate supports the following types for the variables defined:
52//! - iX (X = {8, 16, 32, 64, 128})
53//! - uX (X = {8, 16, 32, 64, 128})
54//! - bool
55//! - String
56//! - f32,f64
57#![deny(missing_docs)]
58
59mod accepted_rust_type;
60mod declarative_env_generator;
61mod empty_struct;
62mod macro_config;
63mod variable_declarations;
64
65use std::path::PathBuf;
66
67use proc_macro::TokenStream;
68use proc_macro2::Span;
69use syn::{parse_macro_input, Error as SynError};
70
71use crate::{
72    declarative_env_generator::DeclarativeEnvGenerator, empty_struct::EmptyStruct,
73    macro_config::MacroConfig, variable_declarations::EnvVariableDeclarations,
74};
75
76extern crate proc_macro;
77
78/// This is the entrypoint for the crate.
79#[proc_macro_attribute]
80pub fn declarative_env(attr: TokenStream, item: TokenStream) -> TokenStream {
81    let macro_config = parse_macro_input!(attr as MacroConfig);
82    let file_content = match read_env_definition_file(macro_config.path()) {
83        Ok(c) => c,
84        Err(e) => return e.to_compile_error().into(),
85    };
86
87    let empty_struct = parse_macro_input!(item as EmptyStruct);
88    let variable_configs: EnvVariableDeclarations = match macro_config.format() {
89        macro_config::AcceptedFormat::Hjson => match deser_hjson::from_str(&file_content) {
90            Ok(v) => v,
91            Err(e) => {
92                return SynError::new(Span::call_site(), e)
93                    .to_compile_error()
94                    .into()
95            }
96        },
97    };
98
99    DeclarativeEnvGenerator::new(empty_struct, variable_configs).generate()
100}
101
102#[doc(hidden)]
103fn read_env_definition_file(path: &str) -> Result<String, SynError> {
104    let cargo_manifest_dir =
105        std::env::var("CARGO_MANIFEST_DIR").map_err(|it| SynError::new(Span::call_site(), it))?;
106    let config_file_path = PathBuf::from(cargo_manifest_dir).join(path);
107    std::fs::read_to_string(config_file_path).map_err(|it| SynError::new(Span::call_site(), it))
108}