├── .gitignore ├── .gitlab-ci.yml ├── README.md ├── configure ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── src │ ├── default │ │ ├── env_deserializer.rs │ │ └── mod.rs │ ├── lib.rs │ ├── null_deserializer.rs │ └── source.rs ├── test-setup │ ├── Cargo.toml │ ├── alt-toml │ │ └── Cargo.toml │ └── lib.rs └── tests │ ├── defaults_only.rs │ ├── mixed.rs │ ├── with_env_vars.rs │ └── with_toml.rs └── configure_derive ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── src ├── attrs.rs └── lib.rs └── tests └── example.rs /.gitignore: -------------------------------------------------------------------------------- 1 | */target/ 2 | */Cargo.lock 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: "rust:latest" 2 | 3 | pages: 4 | script: 5 | - cargo doc --no-deps 6 | - cp -r ./target/doc ./public 7 | artifacts: 8 | paths: 9 | - public 10 | only: 11 | - master 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # configure - pull in configuration from the environment 2 | 3 | ["The 12 Factor App"][12-factor] has this very good advice about managing 4 | configuration: 5 | 6 | > Store config in the environment 7 | 8 | > An app’s config is everything that is likely to vary between deploys 9 | > (staging, production, developer environments, etc). This includes: 10 | > - Resource handles to the database, Memcached, and other backing services 11 | > - Credentials to external services such as Amazon S3 or Twitter 12 | > - Per-deploy values such as the canonical hostname for the deploy 13 | 14 | For reasons laid out in the linked web page, it is poor practice to store this 15 | information inline in the source code. Setting aside even their reasons, it is 16 | especially inconvenient in compiled languages like Rust, as it requires 17 | recompiling the entire project between deployment environments, and it means 18 | performing an entire redeploy to change one of these values. 19 | 20 | However, most libraries today take these kinds of configuration as an argument 21 | to their constructor, leaving application authors responsible for developing 22 | their own system for pulling those configurations from the environment. 23 | Configure is an attempt to create a standardized way for libraries to pull 24 | configuration from the environment, making it easier for end users to follow 25 | best practices regarding configuration. 26 | 27 | This doesn't apply to all kinds of "configuration." For example, if it is 28 | performance critical that a configuration be applied at compile time (say, to 29 | drive monomorphization in order to get inlining benefits), using this library 30 | would not be appropriate. If the configuration could vary every time an 31 | operation is performed (say because its configuring a kind of pretty printer or 32 | formatter), using this library would probably not be appropriate either. 33 | 34 | ## The Configure trait 35 | 36 | Libraries adopting this model should put the appropriate configuration all into 37 | a struct: 38 | 39 | ```rust 40 | struct Config { 41 | socket_addr: SocketAddr, 42 | tls_cert: Option, 43 | // ... etc 44 | } 45 | ``` 46 | 47 | This struct that needs to implement `Configure`. The easiest way to get 48 | `Configure` implemented correctly is to derive it. Deriving `Configure` 49 | requires the struct to also implement `Deserialize`, which can also be derived: 50 | 51 | ```rust 52 | #[macro_use] extern crate configure; 53 | 54 | extern crate serde; 55 | #[macro_use] extern crate serde_derive; 56 | 57 | #[derive(Deserialize, Configure)] 58 | struct Config { 59 | socket_addr: SocketAddr, 60 | tls_cert: Option, 61 | // ... etc 62 | } 63 | ``` 64 | 65 | The Configure trait provides two functions: 66 | 67 | - `Configure::generate`, a constructor which generates the configuration from 68 | the environment. 69 | - `Configure::regenerate`, a method that updates the configuration by pulling 70 | from the environment again. 71 | 72 | The generated implementation of `Configure` all pull the configuration from a 73 | **configuration source**, which is controlled by the end application. 74 | 75 | ## Configuration sources 76 | 77 | Ultimately, the end application controls the source from which configuration is 78 | generation. This control is applied through the `CONFIGURATION` static. The 79 | `Configure` impl will access the source using `CONFIGURATION.get`. 80 | 81 | A default source is provided for you, so applications for which the default is 82 | satisfactory don't need to do anything. Applications which want to store 83 | configuration can override that source. 84 | 85 | ### The default source 86 | 87 | By default, `configure` provides a source of configuration that users can rely 88 | on. Users can use this source using the `use_default_config!` macro at the 89 | beginning of their main function. The default source is targeted at network 90 | services, and may not be appropriate to all other domains. 91 | 92 | It works like this: 93 | 94 | 1. By default, it looks up configuration values using environment variables. 95 | For example, if the library `foo` had a config struct with the field `bar`, 96 | that field would be controlled by the `FOO_BAR` environment variable. 97 | 2. If no environment variable is set, it will fall back to looking in the 98 | `Cargo.toml`. If there is a `Cargo.toml` present, and it contains a 99 | `[package.metadata.foo]` table (where `foo` is the name of the library), the 100 | `bar` member of that table will control the `bar` field in `foo`'s config 101 | struct. 102 | 103 | In general, it is recommended that most environments use env vars to control 104 | configuration. The `Cargo.toml` fallback is intended for development 105 | environments, so that you can check these values into the `Cargo.toml` and have 106 | them be consistent across every developer's machine. 107 | 108 | ### Custom configuration source 109 | 110 | Users can override the default configuration for their app using the 111 | `use_config_from!` macro. This macro should only be invoked once, in the final 112 | binary. First, you will need to prepare a type that implements `ConfigSource` 113 | to be used as the source of configuration. 114 | 115 | This allows users to control their configuration source without recompiling all 116 | of the library code that depends on it, as would occur if the configuration 117 | source were a type parameter. 118 | 119 | [12-factor]: https://12factor.net/config 120 | -------------------------------------------------------------------------------- /configure/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "configure" 3 | authors = ["Without Boats "] 4 | description = "Configuration management" 5 | documentation = "https://docs.rs/configure" 6 | repository = "https://github.com/withoutboats/configure" 7 | license = "MIT OR Apache-2.0" 8 | version = "0.1.1" 9 | 10 | [dependencies] 11 | erased-serde = "0.3.3" 12 | heck = "0.3.0" 13 | serde = "1.0.21" 14 | toml = "0.4.5" 15 | 16 | [dependencies.configure_derive] 17 | path = "../configure_derive" 18 | version = "0.1.0" 19 | 20 | [dev-dependencies] 21 | serde_derive = "1.0.21" 22 | 23 | [dev-dependencies.test-setup] 24 | path = "./test-setup" 25 | version = "0.1.0" 26 | -------------------------------------------------------------------------------- /configure/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /configure/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /configure/src/default/env_deserializer.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use serde::de::*; use serde::de::{Error as ErrorTrait}; 4 | use erased_serde::Error; 5 | 6 | pub struct EnvDeserializer<'a>(pub Cow<'a, str>); 7 | 8 | impl<'a, 'de> IntoDeserializer<'de, Error> for EnvDeserializer<'a> { 9 | type Deserializer = Self; 10 | fn into_deserializer(self) -> Self { self } 11 | } 12 | 13 | macro_rules! deserialize_number { 14 | ($($f:ident($t:ty): $v:ident;)*) => {$( 15 | fn $f(self, visitor: V) -> Result 16 | where V: Visitor<'de>, 17 | { 18 | let x = self.0.parse::<$t>().map_err(|e| Error::custom(e.to_string()))?; 19 | visitor.$v(x) 20 | } 21 | )*} 22 | } 23 | 24 | impl<'a, 'de> Deserializer<'de> for EnvDeserializer<'a> { 25 | type Error = Error; 26 | 27 | fn deserialize_any(self, visitor: V) -> Result 28 | where V: Visitor<'de>, 29 | { 30 | visitor.visit_str(&self.0) 31 | } 32 | 33 | fn deserialize_bool(self, visitor: V) -> Result 34 | where V: Visitor<'de> 35 | { 36 | match &self.0[..] { 37 | "0" | "false" | "False" | "FALSE" => visitor.visit_bool(false), 38 | "1" | "true" | "True" | "TRUE" => visitor.visit_bool(true), 39 | _ => { 40 | Err(Error::invalid_value(Unexpected::Str(&self.0), &visitor)) 41 | } 42 | } 43 | } 44 | 45 | deserialize_number! { 46 | deserialize_i8(i8): visit_i8; 47 | deserialize_i16(i16): visit_i16; 48 | deserialize_i32(i32): visit_i32; 49 | deserialize_i64(i64): visit_i64; 50 | deserialize_u8(u8): visit_u8; 51 | deserialize_u16(u16): visit_u16; 52 | deserialize_u32(u32): visit_u32; 53 | deserialize_u64(u64): visit_u64; 54 | deserialize_f32(f32): visit_f32; 55 | deserialize_f64(f64): visit_f64; 56 | } 57 | 58 | fn deserialize_char(self, visitor: V) -> Result 59 | where V: Visitor<'de> 60 | { 61 | let mut chars = self.0.chars(); 62 | if let Some(c) = chars.next() { 63 | if chars.next().is_none() { 64 | return visitor.visit_char(c) 65 | } 66 | } 67 | Err(Error::invalid_value(Unexpected::Str(&self.0), &visitor)) 68 | } 69 | 70 | fn deserialize_str(self, visitor: V) -> Result 71 | where V: Visitor<'de> 72 | { 73 | visitor.visit_str(&self.0) 74 | } 75 | 76 | fn deserialize_string(self, visitor: V) -> Result 77 | where V: Visitor<'de> 78 | { 79 | visitor.visit_string(self.0.into_owned()) 80 | } 81 | 82 | fn deserialize_bytes(self, visitor: V) -> Result 83 | where V: Visitor<'de> 84 | { 85 | if let Some(bytes) = hex(&self.0[..]) { 86 | visitor.visit_bytes(&bytes[..]) 87 | } else { 88 | Err(Error::invalid_value(Unexpected::Str(&self.0), &visitor)) 89 | } 90 | } 91 | 92 | fn deserialize_byte_buf(self, visitor: V) -> Result 93 | where V: Visitor<'de> 94 | { 95 | if let Some(bytes) = hex(&self.0[..]) { 96 | visitor.visit_byte_buf(bytes) 97 | } else { 98 | Err(Error::invalid_value(Unexpected::Str(&self.0), &visitor)) 99 | } 100 | } 101 | 102 | fn deserialize_option(self, visitor: V) -> Result 103 | where V: Visitor<'de> 104 | { 105 | visitor.visit_some(self) 106 | } 107 | 108 | fn deserialize_unit(self, visitor: V) -> Result 109 | where V: Visitor<'de> 110 | { 111 | visitor.visit_unit() 112 | } 113 | 114 | fn deserialize_unit_struct( 115 | self, 116 | _name: &'static str, 117 | visitor: V, 118 | ) -> Result 119 | where V: Visitor<'de> 120 | { 121 | visitor.visit_unit() 122 | } 123 | 124 | fn deserialize_newtype_struct( 125 | self, 126 | _name: &'static str, 127 | visitor: V, 128 | ) -> Result 129 | where V: Visitor<'de> 130 | { 131 | visitor.visit_newtype_struct(self) 132 | } 133 | 134 | fn deserialize_seq(self, visitor: V) -> Result 135 | where V: Visitor<'de> 136 | { 137 | let seq = self.0.split(',').map(|s| EnvDeserializer(Cow::Borrowed(s))); 138 | visitor.visit_seq(value::SeqDeserializer::new(seq)) 139 | } 140 | 141 | fn deserialize_tuple( 142 | self, 143 | _len: usize, 144 | visitor: V, 145 | ) -> Result 146 | where V: Visitor<'de> 147 | { 148 | let seq = self.0.split(',').map(|s| EnvDeserializer(Cow::Borrowed(s))); 149 | visitor.visit_seq(value::SeqDeserializer::new(seq)) 150 | } 151 | 152 | fn deserialize_tuple_struct( 153 | self, 154 | _name: &'static str, 155 | _len: usize, 156 | visitor: V, 157 | ) -> Result 158 | where V: Visitor<'de> 159 | { 160 | let seq = self.0.split(',').map(|s| EnvDeserializer(Cow::Borrowed(s))); 161 | visitor.visit_seq(value::SeqDeserializer::new(seq)) 162 | } 163 | 164 | fn deserialize_map(self, visitor: V) -> Result 165 | where V: Visitor<'de> 166 | { 167 | Err(Error::invalid_type(Unexpected::Map, &visitor)) 168 | } 169 | 170 | fn deserialize_struct( 171 | self, 172 | _name: &'static str, 173 | _fields: &'static [&'static str], 174 | visitor: V, 175 | ) -> Result 176 | where V: Visitor<'de> 177 | { 178 | Err(Error::invalid_type(Unexpected::Map, &visitor)) 179 | } 180 | 181 | fn deserialize_enum( 182 | self, 183 | _name: &'static str, 184 | variants: &'static [&'static str], 185 | visitor: V, 186 | ) -> Result 187 | where V: Visitor<'de> 188 | { 189 | visitor.visit_enum(EnumAccessor { 190 | env_var: &self.0, 191 | variants: variants, 192 | }) 193 | } 194 | 195 | fn deserialize_identifier( 196 | self, 197 | _visitor: V 198 | ) -> Result 199 | where V: Visitor<'de> 200 | { 201 | Err(Error::custom("cannot deserialize identifier from env var")) 202 | } 203 | 204 | fn deserialize_ignored_any( 205 | self, 206 | visitor: V 207 | ) -> Result 208 | where V: Visitor<'de> 209 | { 210 | visitor.visit_str(&self.0) 211 | } 212 | } 213 | 214 | struct EnumAccessor<'a> { 215 | env_var: &'a str, 216 | variants: &'static [&'static str], 217 | } 218 | 219 | impl<'a, 'de> EnumAccess<'de> for EnumAccessor<'a> { 220 | type Error = Error; 221 | type Variant = VariantAccessor; 222 | 223 | fn variant_seed( 224 | self, 225 | seed: V 226 | ) -> Result<(V::Value, Self::Variant), Self::Error> 227 | where V: DeserializeSeed<'de> 228 | { 229 | if let Some(&variant) = self.variants.iter().find(|&&v| v == self.env_var) { 230 | let value = seed.deserialize(variant.into_deserializer())?; 231 | Ok((value, VariantAccessor)) 232 | } else { 233 | Err(Error::unknown_variant(self.env_var, self.variants)) 234 | } 235 | } 236 | } 237 | 238 | struct VariantAccessor; 239 | 240 | impl<'de> VariantAccess<'de> for VariantAccessor { 241 | type Error = Error; 242 | 243 | fn unit_variant(self) -> Result<(), Self::Error> { 244 | Ok(()) 245 | } 246 | 247 | fn newtype_variant_seed(self, _seed: T) -> Result 248 | where T: DeserializeSeed<'de> 249 | { 250 | Err(Error::invalid_type(Unexpected::NewtypeVariant, &"a unit variant")) 251 | } 252 | 253 | fn tuple_variant( 254 | self, 255 | _len: usize, 256 | _visitor: V 257 | ) -> Result 258 | where V: Visitor<'de> 259 | { 260 | Err(Error::invalid_type(Unexpected::TupleVariant, &"a unit variant")) 261 | } 262 | 263 | fn struct_variant( 264 | self, 265 | _fields: &'static [&'static str], 266 | _visitor: V 267 | ) -> Result 268 | where V: Visitor<'de> 269 | { 270 | Err(Error::invalid_type(Unexpected::StructVariant, &"a unit variant")) 271 | } 272 | 273 | } 274 | 275 | fn hex(s: &str) -> Option> { 276 | let s = if s.starts_with("0x") { &s[2..] } else { s }; 277 | 278 | let mut bytes = Vec::with_capacity(s.len() / 2); 279 | 280 | let mut char_indices = s.char_indices(); 281 | 282 | while let Some((init, _)) = char_indices.next() { 283 | if let Some(_) = char_indices.next() { 284 | match u8::from_str_radix(&s[init..(init + 2)], 16) { 285 | Ok(byte) => bytes.push(byte), 286 | Err(_) => return None, 287 | } 288 | } else { 289 | return None 290 | } 291 | } 292 | 293 | Some(bytes) 294 | } 295 | 296 | #[cfg(test)] 297 | mod tests { 298 | use super::*; 299 | 300 | fn deserializer(s: &'static str) -> EnvDeserializer<'static> { 301 | EnvDeserializer(Cow::Borrowed(s)) 302 | } 303 | 304 | #[test] 305 | fn test_hex_parser() { 306 | assert_eq!(hex(""), Some(vec![])); 307 | assert_eq!(hex("01"), Some(vec![0x1])); 308 | assert_eq!(hex("ff"), Some(vec![0xff])); 309 | assert_eq!(hex("01ff70"), Some(vec![0x1, 0xff, 0x70])); 310 | assert_eq!(hex("0x04"), Some(vec![0x4])); 311 | assert_eq!(hex("0xdeadbeef"), Some(vec![0xde, 0xad, 0xbe, 0xef])); 312 | assert_eq!(hex("1"), None); 313 | assert_eq!(hex("not hexadecimal"), None); 314 | } 315 | 316 | #[test] 317 | fn test_enum_accessor() { 318 | #[derive(Deserialize, Eq, PartialEq, Debug)] 319 | enum Foo { 320 | Bar, 321 | Baz, 322 | } 323 | 324 | assert_eq!(Foo::deserialize(deserializer("Bar")).unwrap(), Foo::Bar); 325 | assert_eq!(Foo::deserialize(deserializer("Baz")).unwrap(), Foo::Baz); 326 | assert!(Foo::deserialize(deserializer("Foo")).is_err()); 327 | } 328 | 329 | #[test] 330 | fn test_numbers() { 331 | assert_eq!( i8::deserialize(deserializer("-7")).unwrap(), -7i8); 332 | assert_eq!(i16::deserialize(deserializer("-300")).unwrap(), -300i16); 333 | assert_eq!(i32::deserialize(deserializer("-55555")).unwrap(), -55555i32); 334 | assert_eq!(u64::deserialize(deserializer("1")).unwrap(), 1u64); 335 | assert_eq!(f32::deserialize(deserializer("0.25")).unwrap(), 0.25f32); 336 | } 337 | 338 | #[test] 339 | fn test_strings() { 340 | assert_eq!(String::deserialize(deserializer("Hello world!")).unwrap(), 341 | String::from("Hello world!")) 342 | } 343 | 344 | #[test] 345 | fn test_booleans() { 346 | assert_eq!(bool::deserialize(deserializer("0")).unwrap(), false); 347 | assert_eq!(bool::deserialize(deserializer("1")).unwrap(), true); 348 | assert_eq!(bool::deserialize(deserializer("false")).unwrap(), false); 349 | assert_eq!(bool::deserialize(deserializer("true")).unwrap(), true); 350 | assert_eq!(bool::deserialize(deserializer("False")).unwrap(), false); 351 | assert_eq!(bool::deserialize(deserializer("True")).unwrap(), true); 352 | assert_eq!(bool::deserialize(deserializer("FALSE")).unwrap(), false); 353 | assert_eq!(bool::deserialize(deserializer("TRUE")).unwrap(), true); 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /configure/src/default/mod.rs: -------------------------------------------------------------------------------- 1 | mod env_deserializer; 2 | 3 | use std::borrow::Cow; 4 | use std::env::{self, VarError}; 5 | use std::fs::File; 6 | use std::io::Read; 7 | use std::path::PathBuf; 8 | use std::slice; 9 | use std::sync::Arc; 10 | 11 | use serde::de::{self, Deserializer, IntoDeserializer, MapAccess, Error as ErrorTrait, Visitor}; 12 | use erased_serde::{Error, Deserializer as DynamicDeserializer}; 13 | use heck::ShoutySnakeCase; 14 | use toml; 15 | 16 | use source::ConfigSource; 17 | use self::env_deserializer::EnvDeserializer; 18 | 19 | /// The default source for configuration values. You can set this as the 20 | /// source of configuration using the `use_default_config!` macro. 21 | #[derive(Clone)] 22 | pub struct DefaultSource { 23 | toml: Option>, 24 | } 25 | 26 | impl ConfigSource for DefaultSource { 27 | fn init() -> DefaultSource { 28 | DefaultSource { 29 | toml: DefaultSource::toml().map(Arc::new), 30 | } 31 | } 32 | 33 | fn prepare(&self, package: &'static str) -> Box> { 34 | let deserializer = DefaultDeserializer { 35 | source: self.clone(), 36 | package: package, 37 | }; 38 | Box::new(DynamicDeserializer::erase(deserializer)) as Box 39 | } 40 | } 41 | 42 | impl DefaultSource { 43 | #[cfg(test)] 44 | pub fn test(toml: Option) -> DefaultSource { 45 | DefaultSource { 46 | toml: toml.map(Arc::new), 47 | } 48 | } 49 | 50 | fn toml() -> Option { 51 | let path = match env::var_os("CARGO_MANIFEST_DIR") { 52 | Some(string) => { 53 | let dir: PathBuf = string.into(); 54 | dir.join("Cargo.toml") 55 | } 56 | None => return None, 57 | }; 58 | 59 | let mut file = match File::open(path) { 60 | Ok(file) => file, 61 | Err(_) => return None, 62 | }; 63 | 64 | let mut string = String::new(); 65 | let _ = file.read_to_string(&mut string); 66 | let manifest: toml::Value = match toml::from_str(&string) { 67 | Ok(toml) => toml, 68 | Err(_) => return None, 69 | }; 70 | manifest.get("package") 71 | .and_then(|package| package.get("metadata")) 72 | .map(|metadata| metadata.clone()) 73 | } 74 | } 75 | 76 | struct DefaultDeserializer { 77 | source: DefaultSource, 78 | package: &'static str, 79 | } 80 | 81 | impl<'de> Deserializer<'de> for DefaultDeserializer { 82 | type Error = Error; 83 | 84 | fn deserialize_any(self, _visitor: V) -> Result 85 | where V: Visitor<'de>, 86 | { 87 | Err(Error::custom("The default configuration deserializer only supports / 88 | deserializing structs.")) 89 | } 90 | 91 | fn deserialize_struct( 92 | self, 93 | _name: &'static str, 94 | fields: &'static [&'static str], 95 | visitor: V, 96 | ) -> Result 97 | where V: Visitor<'de>, 98 | { 99 | visitor.visit_map(MapAccessor { 100 | deserializer: self, 101 | fields: fields.iter(), 102 | next_val: None, 103 | }) 104 | } 105 | 106 | fn deserialize_unit_struct( 107 | self, 108 | _name: &'static str, 109 | visitor: V 110 | ) -> Result 111 | where V: Visitor<'de>, 112 | { 113 | self.deserialize_struct(_name, &[], visitor) 114 | } 115 | 116 | forward_to_deserialize_any! { 117 | bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit seq 118 | bytes byte_buf map tuple_struct newtype_struct 119 | tuple ignored_any identifier enum option 120 | } 121 | } 122 | 123 | struct MapAccessor { 124 | deserializer: DefaultDeserializer, 125 | fields: slice::Iter<'static, &'static str>, 126 | next_val: Option, 127 | } 128 | 129 | enum Either { 130 | Env(String), 131 | Toml(toml::Value), 132 | } 133 | 134 | impl<'de> MapAccess<'de> for MapAccessor { 135 | type Error = Error; 136 | 137 | fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> 138 | where K: de::DeserializeSeed<'de>, 139 | { 140 | while let Some(field) = self.fields.next() { 141 | let var_name = format!("{}_{}", self.deserializer.package, field) 142 | .to_shouty_snake_case(); 143 | match env::var(&var_name) { 144 | Ok(env_var) => { 145 | self.next_val = Some(Either::Env(env_var)); 146 | } 147 | Err(VarError::NotPresent) => { 148 | let toml = self.deserializer.source.toml.as_ref() 149 | .and_then(|toml| toml.get(self.deserializer.package)) 150 | .and_then(|package| package.get(field)); 151 | 152 | match toml { 153 | Some(toml) => { 154 | self.next_val = Some(Either::Toml(toml.clone())); 155 | } 156 | // If there is neither an env var nor a toml value, 157 | // this field is not set. Skip it. 158 | None => continue, 159 | } 160 | } 161 | Err(VarError::NotUnicode(_)) => { 162 | return Err(Error::custom(format!("`{}` is not valid unicode", var_name))); 163 | } 164 | } 165 | 166 | let key = seed.deserialize(field.into_deserializer())?; 167 | return Ok(Some(key)); 168 | } 169 | 170 | Ok(None) 171 | } 172 | 173 | fn next_value_seed(&mut self, seed: V) -> Result 174 | where V: de::DeserializeSeed<'de>, 175 | { 176 | match self.next_val.take() { 177 | Some(Either::Env(env)) => { 178 | seed.deserialize(EnvDeserializer(Cow::Owned(env))) 179 | } 180 | Some(Either::Toml(toml)) => { 181 | seed.deserialize(toml).map_err(|e| Error::custom(e.to_string())) 182 | } 183 | None => { 184 | Err(Error::custom("called `next_value` without calling `next_key`")) 185 | } 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /configure/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Configuration management. 2 | //! 3 | //! This crate is intended to automatically bridge the gap from 4 | //! envionmental configuration into your project. By deriving the 5 | //! `Configure` trait for your configuration, you can get an automatic 6 | //! system for managing your configuration at runtime. 7 | //! 8 | //! # Deriving `Configure` 9 | //! 10 | //! This library provides a custom derive which lets you derive the `Configure` 11 | //! trait. It requires that your type implement `Deserialize`. 12 | //! 13 | //! We recommend that you implement configuration using these steps: 14 | //! 15 | //! 1. Implement `Default` for your type, which provides the default values for 16 | //! each configuration field. 17 | //! 2. Derive both `Deserialize` and `Configure` for your type. Use the 18 | //! `#[serde(default)]` attribute to fall back to the default implementation 19 | //! when the configurable values are not set. 20 | //! 21 | //! For example: 22 | //! 23 | //! ```ignore 24 | //! #[macro_use] 25 | //! extern crate configure; 26 | //! extern crate serde; 27 | //! #[macro_use] 28 | //! extern crate serde_derive; 29 | //! 30 | //! use std::net::SocketAddr; 31 | //! use std::path::PathBuf; 32 | //! 33 | //! #[derive(Deserialize, Configure)] 34 | //! #[serde(default)] 35 | //! pub struct Config { 36 | //! addr: SocketAddr, 37 | //! tls_cert: Option, 38 | //! } 39 | //! 40 | //! impl Default for Config { 41 | //! fn default() -> Config { 42 | //! Config { 43 | //! addr: "127.0.0.1:7878".parse().unwrap(), 44 | //! tls_cert: None, 45 | //! } 46 | //! } 47 | //! } 48 | //! ``` 49 | //! 50 | //! With this code, you can call `Config::generate` to pull you configuration 51 | //! from the environment, falling back to these default values if the end user 52 | //! has not set custom configuration for it. 53 | #![deny(missing_docs)] 54 | #[macro_use] extern crate serde; 55 | extern crate erased_serde; 56 | extern crate heck; 57 | extern crate toml; 58 | 59 | #[allow(unused_imports)] 60 | #[macro_use] extern crate configure_derive; 61 | 62 | #[cfg(test)] 63 | #[macro_use] extern crate serde_derive; 64 | 65 | pub mod source; 66 | mod null_deserializer; 67 | mod default; 68 | 69 | pub use erased_serde::Error as DeserializeError; 70 | 71 | #[doc(hidden)] 72 | pub use configure_derive::*; 73 | 74 | /// A configuration struct which can be generated from the environment. 75 | /// 76 | /// This trait is normally derived using the `configure_derive` crate. 77 | /// 78 | /// ```rust,ignore 79 | /// #[derive(Configure)] 80 | /// pub struct Config { 81 | /// /// This can be set through the LIBNAME_HOST variable 82 | /// pub host: SocketAddr, 83 | /// /// This can be set through the LIBNAME_THREADS variable 84 | /// pub threads: usize, 85 | /// } 86 | /// 87 | /// // To generate your configuration from the environment: 88 | /// let cfg = Config::generate()?; 89 | /// ``` 90 | pub trait Configure: Sized { 91 | /// Generate this configuration from the ambient environment. 92 | fn generate() -> Result; 93 | 94 | /// Regenerate this configuration. 95 | fn regenerate(&mut self) -> Result<(), DeserializeError> { 96 | *self = Self::generate()?; 97 | Ok(()) 98 | } 99 | } 100 | 101 | /// 102 | #[macro_export] 103 | macro_rules! use_config_from { 104 | ($source:ty) => { 105 | $crate::source::CONFIGURATION.set(<$source as $crate::source::ConfigSource>::init()) 106 | } 107 | } 108 | 109 | #[macro_export] 110 | macro_rules! use_default_config { 111 | () => { 112 | use_config_from!($crate::source::DefaultSource) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /configure/src/null_deserializer.rs: -------------------------------------------------------------------------------- 1 | pub struct NullDeserializer; 2 | use serde::de::{self, Deserializer, MapAccess, Error as ErrorTrait, Visitor}; 3 | use erased_serde::Error; 4 | 5 | impl<'de> Deserializer<'de> for NullDeserializer { 6 | type Error = Error; 7 | fn deserialize_any(self, visitor: V) -> Result 8 | where V: Visitor<'de>, 9 | { 10 | visitor.visit_map(NullMapAccessor) 11 | } 12 | 13 | fn deserialize_struct( 14 | self, 15 | _name: &'static str, 16 | _fields: &'static [&'static str], 17 | visitor: V, 18 | ) -> Result 19 | where V: Visitor<'de>, 20 | { 21 | visitor.visit_map(NullMapAccessor) 22 | } 23 | 24 | fn deserialize_tuple_struct( 25 | self, 26 | _name: &'static str, 27 | _fields: usize, 28 | visitor: V 29 | ) -> Result 30 | where V: Visitor<'de>, 31 | { 32 | visitor.visit_map(NullMapAccessor) 33 | } 34 | 35 | fn deserialize_newtype_struct( 36 | self, 37 | _name: &'static str, 38 | visitor: V 39 | ) -> Result 40 | where V: Visitor<'de>, 41 | { 42 | visitor.visit_map(NullMapAccessor) 43 | } 44 | 45 | 46 | fn deserialize_unit_struct( 47 | self, 48 | _name: &'static str, 49 | visitor: V 50 | ) -> Result 51 | where V: Visitor<'de>, 52 | { 53 | visitor.visit_map(NullMapAccessor) 54 | } 55 | 56 | forward_to_deserialize_any! { 57 | bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit seq 58 | bytes byte_buf map 59 | tuple ignored_any identifier enum option 60 | } 61 | } 62 | 63 | struct NullMapAccessor; 64 | 65 | impl<'de> MapAccess<'de> for NullMapAccessor { 66 | type Error = Error; 67 | 68 | fn next_key_seed(&mut self, _seed: K) -> Result, Self::Error> 69 | where K: de::DeserializeSeed<'de>, 70 | { 71 | Ok(None) 72 | } 73 | 74 | fn next_value_seed(&mut self, _seed: V) -> Result 75 | where V: de::DeserializeSeed<'de>, 76 | { 77 | Err(Error::custom("called `next_value` without calling `next_key`")) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /configure/src/source.rs: -------------------------------------------------------------------------------- 1 | //! Controlling the source of configuration. 2 | //! 3 | //! A source of configuration is something that implements Deserializer. 4 | //! The configuration for each package will pass the name of that package to 5 | //! the source of configuration to get a deserializer for that package's 6 | //! configuration struct. 7 | //! 8 | //! If you are happy with the default configuration source - pulling from 9 | //! environmental variables and falling back to your Cargo.toml - nothing in 10 | //! this module should be of interest to you. 11 | //! 12 | //! Libraries should **never** try to set the configuration source; only 13 | //! binaries should ever override the default. 14 | use std::sync::{Once, ONCE_INIT}; 15 | use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT}; 16 | 17 | use erased_serde::Deserializer as DynamicDeserializer; 18 | 19 | pub use default::DefaultSource; 20 | use null_deserializer::NullDeserializer; 21 | 22 | /// The global static holding the active configuration source for this project. 23 | pub static CONFIGURATION: ActiveConfiguration = ActiveConfiguration { 24 | init: ONCE_INIT, 25 | is_overriden: ATOMIC_BOOL_INIT, 26 | }; 27 | 28 | static mut SOURCE: Option<&'static (Fn(&'static str) -> Box + Send + Sync + 'static)> = None; 29 | 30 | /// A source for configuration. 31 | /// 32 | /// If an end user wishes to pull configuration from the environment, they must 33 | /// specify their source, which is a type that implements ConfigSource. The 34 | /// source can be specified using the `use_config_from!` macro. 35 | /// 36 | /// This crate ships a default source, called DefaultSource, which implements 37 | /// this trait. 38 | pub trait ConfigSource: Send + Sync + 'static { 39 | /// Initialize this source. This will be called once when the program 40 | /// begins and then never called again. 41 | fn init() -> Self; 42 | /// Prepare a deserializer for a particular package. This will be called 43 | /// every time we generate configuration for that package. 44 | fn prepare(&self, package: &'static str) -> Box>; 45 | } 46 | 47 | /// The active configuration source. 48 | /// 49 | /// The only value of this type is the CONFIGURATION global static, which 50 | /// controls what the source of configuration values is. End users can set 51 | /// the configuration source using the `set` method, while libraries which 52 | /// need to be configured can use the `get` method. 53 | pub struct ActiveConfiguration { 54 | init: Once, 55 | is_overriden: AtomicBool, 56 | } 57 | 58 | impl ActiveConfiguration { 59 | /// Set the active configuration. 60 | /// 61 | /// This can only be called once. If it is called more than once, 62 | /// subsequent calls have no effect. This should only be called by the 63 | /// final binary which is using the configuration, it should not be called 64 | /// by libraries. 65 | /// 66 | /// If you set the active configuration, you should do so very early in 67 | /// your program, preferably as close to the beginning of main as possible. 68 | /// That way, the configuration source is consistent for every dependency. 69 | pub fn set(&'static self, source: T) { 70 | self.init.call_once(|| { 71 | self.is_overriden.store(true, Ordering::Relaxed); 72 | let init = Box::new(move |s| source.prepare(s)); 73 | unsafe { SOURCE = Some(&*Box::into_raw(init)) } 74 | }); 75 | } 76 | 77 | /// Get the active configuration. 78 | /// 79 | /// Libraries which need to construct configuration can use this to get 80 | /// the active source of configuration. Normally they would derive 81 | /// Configure for their config struct, which will call this method. 82 | pub fn get(&'static self, package: &'static str) -> Box { 83 | self.init.call_once(|| { 84 | fn null_deserializer(_package: &'static str) -> Box { 85 | Box::new(DynamicDeserializer::erase(NullDeserializer)) 86 | } 87 | unsafe { SOURCE = Some(&null_deserializer) } 88 | }); 89 | unsafe { SOURCE.unwrap()(package) } 90 | } 91 | 92 | /// Returns true if the configuration source is the default source. 93 | /// 94 | /// The opposite of `CONFIGURATION.is_overriden()` 95 | pub fn is_default(&'static self) -> bool { 96 | !self.is_overriden() 97 | } 98 | 99 | /// Returns true if the configuration source has been overriden. 100 | /// 101 | /// The opposite of `CONFIGURATION.is_default()` 102 | pub fn is_overriden(&'static self) -> bool { 103 | self.is_overriden.load(Ordering::Relaxed) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /configure/test-setup/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Without Boats "] 3 | name = "test-setup" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | erased-serde = "0.3.3" 8 | serde = "1.0.21" 9 | 10 | [dependencies.configure] 11 | path = "../" 12 | 13 | [lib] 14 | path = "./lib.rs" 15 | 16 | [package.metadata.test] 17 | first_field = 9 18 | second_field = "Colonel Aureliano Buendia" 19 | third_field = [10, 20, 30] 20 | 21 | -------------------------------------------------------------------------------- /configure/test-setup/alt-toml/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package.metadata.test] 2 | first_field = "100" 3 | second_field = "Labyrinth" 4 | -------------------------------------------------------------------------------- /configure/test-setup/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate erased_serde; 2 | extern crate serde; 3 | extern crate configure; 4 | 5 | use serde::de::Deserializer; 6 | use erased_serde::Error; 7 | 8 | use configure::Configure; 9 | 10 | const FIELDS: &[&str] = &["first_field", "second_field", "third_field"]; 11 | 12 | #[derive(Debug, Eq, PartialEq)] 13 | pub struct Configuration { 14 | pub first_field: u32, 15 | pub second_field: String, 16 | pub third_field: Option>, 17 | } 18 | 19 | impl Default for Configuration { 20 | fn default() -> Configuration { 21 | Configuration { 22 | first_field: 100, 23 | second_field: String::from("FooBar"), 24 | third_field: Some(vec![]), 25 | } 26 | } 27 | } 28 | impl Configure for Configuration { 29 | fn generate() -> Result { 30 | let mut cfg = Configuration::default(); 31 | cfg.regenerate()?; 32 | Ok(cfg) 33 | } 34 | 35 | fn regenerate(&mut self) -> Result<(), Error> { 36 | let deserializer = configure::source::CONFIGURATION.get("test"); 37 | deserializer.deserialize_struct("Configuration", FIELDS, visitors::CfgVisitor { 38 | default: self, 39 | })?; 40 | Ok(()) 41 | 42 | } 43 | } 44 | 45 | mod visitors { 46 | use std::fmt; 47 | 48 | use serde::de::*; 49 | use super::Configuration; 50 | 51 | enum Field { 52 | First, 53 | Second, 54 | Third, 55 | } 56 | 57 | struct FieldVisitor; 58 | 59 | impl<'de> Visitor<'de> for FieldVisitor { 60 | type Value = Field; 61 | 62 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { 63 | write!(f, "expecting a field name") 64 | } 65 | 66 | fn visit_str(self, v: &str) -> Result { 67 | match v { 68 | "first_field" => Ok(Field::First), 69 | "second_field" => Ok(Field::Second), 70 | "third_field" => Ok(Field::Third), 71 | field => Err(E::unknown_field(field, super::FIELDS)), 72 | } 73 | } 74 | } 75 | 76 | impl<'de> Deserialize<'de> for Field { 77 | fn deserialize>(deserializer: D) -> Result { 78 | deserializer.deserialize_identifier(FieldVisitor) 79 | } 80 | } 81 | 82 | pub struct CfgVisitor<'a> { 83 | pub default: &'a mut Configuration, 84 | } 85 | 86 | impl<'a, 'de> Visitor<'de> for CfgVisitor<'a> { 87 | type Value = &'a mut Configuration; 88 | 89 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { 90 | write!(f, "expecting a configuration struct") 91 | } 92 | 93 | fn visit_map(self, mut map: A) -> Result 94 | where A: MapAccess<'de>, 95 | { 96 | while let Some(field) = map.next_key()? { 97 | match field { 98 | Field::First => { 99 | self.default.first_field = map.next_value()?; 100 | } 101 | Field::Second => { 102 | self.default.second_field = map.next_value()?; 103 | } 104 | Field::Third => { 105 | self.default.third_field = map.next_value()?; 106 | } 107 | } 108 | } 109 | Ok(self.default) 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /configure/tests/defaults_only.rs: -------------------------------------------------------------------------------- 1 | extern crate configure; 2 | extern crate test_setup; 3 | 4 | use std::env; 5 | 6 | use configure::Configure; 7 | use test_setup::Configuration; 8 | 9 | #[test] 10 | fn env_vars_set() { 11 | env::remove_var("CARGO_MANIFEST_DIR"); 12 | 13 | assert_eq!(Configuration::generate().unwrap(), Configuration::default()); 14 | } 15 | -------------------------------------------------------------------------------- /configure/tests/mixed.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate configure; 2 | extern crate test_setup; 3 | 4 | use std::env; 5 | use std::path::PathBuf; 6 | 7 | use configure::Configure; 8 | use test_setup::Configuration; 9 | 10 | #[test] 11 | fn mixed() { 12 | let dir: PathBuf = env::var_os("CARGO_MANIFEST_DIR").unwrap().into(); 13 | env::set_var("CARGO_MANIFEST_DIR", dir.join("test-setup").join("alt-toml")); 14 | env::set_var("TEST_FIRST_FIELD", "12"); 15 | use_default_config!(); 16 | 17 | assert_eq!(Configuration::generate().unwrap(), Configuration { 18 | first_field: 12, 19 | second_field: String::from("Labyrinth"), 20 | ..Configuration::default() 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /configure/tests/with_env_vars.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate configure; 2 | extern crate test_setup; 3 | 4 | use std::env; 5 | 6 | use configure::Configure; 7 | use test_setup::Configuration; 8 | 9 | #[test] 10 | fn env_vars_set() { 11 | env::remove_var("CARGO_MANIFEST_DIR"); 12 | env::set_var("TEST_FIRST_FIELD", "7"); 13 | env::set_var("TEST_SECOND_FIELD", "BazQuux"); 14 | env::set_var("TEST_THIRD_FIELD", "0,1"); 15 | use_default_config!(); 16 | 17 | assert_eq!(Configuration::generate().unwrap(), Configuration { 18 | first_field: 7, 19 | second_field: String::from("BazQuux"), 20 | third_field: Some(vec![0, 1]), 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /configure/tests/with_toml.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate configure; 2 | extern crate test_setup; 3 | 4 | use std::env; 5 | use std::path::PathBuf; 6 | 7 | use configure::Configure; 8 | use test_setup::Configuration; 9 | 10 | #[test] 11 | fn from_toml_values() { 12 | let dir: PathBuf = env::var_os("CARGO_MANIFEST_DIR").unwrap().into(); 13 | env::set_var("CARGO_MANIFEST_DIR", dir.join("test-setup")); 14 | use_default_config!(); 15 | 16 | assert_eq!(Configuration::generate().unwrap(), Configuration { 17 | first_field: 9, 18 | second_field: String::from("Colonel Aureliano Buendia"), 19 | third_field: Some(vec![10, 20, 30]), 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /configure_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Without Boats "] 3 | description = "Derives for the configure crate." 4 | repository = "https://github.com/withoutboats/configure" 5 | license = "MIT OR Apache-2.0" 6 | name = "configure_derive" 7 | version = "0.1.1" 8 | 9 | [dependencies] 10 | heck = "0.3.0" 11 | quote = "0.3.15" 12 | syn = "0.11.11" 13 | 14 | [dev-dependencies] 15 | serde = "1.0.21" 16 | serde_derive = "1.0.21" 17 | 18 | [dev-dependencies.configure] 19 | path = "../configure" 20 | version = "0.1.0" 21 | 22 | [lib] 23 | proc-macro = true 24 | -------------------------------------------------------------------------------- /configure_derive/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /configure_derive/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /configure_derive/src/attrs.rs: -------------------------------------------------------------------------------- 1 | use syn::*; 2 | 3 | pub struct CfgAttrs { 4 | pub name: Option, 5 | pub docs: bool, 6 | } 7 | 8 | impl CfgAttrs { 9 | pub fn new(attrs: &[Attribute]) -> CfgAttrs { 10 | let cfg_attrs = filter_attrs(attrs); 11 | 12 | let mut cfg = CfgAttrs { 13 | name: None, 14 | docs: false, 15 | }; 16 | 17 | // Parse the cfg attrs 18 | for attr in cfg_attrs { 19 | if let NestedMetaItem::MetaItem(ref attr) = *attr { 20 | match attr.name() { 21 | "name" if cfg.name.is_some() => panic!("Multiple `name` attributes"), 22 | "name" => cfg.name = project_name(attr), 23 | "generate_docs" if cfg.docs => panic!("Multiple `generate_docs` attributes"), 24 | "generate_docs" => cfg.docs = gen_docs(attr), 25 | unknown => { 26 | panic!("Unrecognized configure attribute `{}`", unknown) 27 | } 28 | } 29 | } else { panic!("Unrecognized configure attribute literal") } 30 | } 31 | 32 | cfg 33 | } 34 | } 35 | 36 | pub struct FieldAttrs { 37 | pub docs: Option, 38 | } 39 | 40 | impl FieldAttrs { 41 | pub fn new(field: &Field) -> FieldAttrs { 42 | 43 | let mut cfg = FieldAttrs { docs: None }; 44 | 45 | let cfg_attrs = filter_attrs(&field.attrs); 46 | 47 | for attr in cfg_attrs { 48 | if let NestedMetaItem::MetaItem(ref attr) = *attr { 49 | match attr.name() { 50 | "docs" if cfg.docs.is_some() => { 51 | let name = field.ident.as_ref().unwrap(); 52 | panic!("Multiple `docs` attributes on one field: `{}`.", name) 53 | } 54 | "docs" => { 55 | cfg.docs = Some(field_docs(attr)) 56 | } 57 | unknown => { 58 | panic!("Unrecognized configure attribute `{}`", unknown) 59 | } 60 | } 61 | } else { panic!("Unrecognized configure attribute literal") } 62 | } 63 | 64 | if cfg.docs.is_none() { 65 | cfg.docs = desugared_docs(&field.attrs); 66 | } 67 | 68 | cfg 69 | } 70 | } 71 | 72 | fn filter_attrs(attrs: &[Attribute]) -> Vec<&NestedMetaItem> { 73 | let mut cfg_attrs = vec![]; 74 | for attr in attrs { 75 | match attr.value { 76 | MetaItem::List(ref name, ref members) if name.as_ref() == "configure" => { 77 | cfg_attrs.extend(members); 78 | } 79 | _ => continue 80 | } 81 | } 82 | 83 | cfg_attrs 84 | } 85 | 86 | fn project_name(attr: &MetaItem) -> Option { 87 | if let MetaItem::NameValue(_, ref name) = *attr { 88 | if let Lit::Str(ref string, _) = *name { 89 | return Some(string.clone()) 90 | } 91 | } 92 | panic!("Unsupported `configure(name)` attribute; only supported form is #[configure(name = \"$NAME\")]") 93 | } 94 | 95 | fn gen_docs(attr: &MetaItem) -> bool { 96 | if let MetaItem::Word(_) = *attr { 97 | return true 98 | } else { 99 | panic!("Unsupported `configure(docs)` attribute; only supported form is #[configure(docs)]") 100 | } 101 | } 102 | 103 | fn field_docs(attr: &MetaItem) -> String { 104 | if let MetaItem::NameValue(_, ref name) = *attr { 105 | if let Lit::Str(ref string, _) = *name { 106 | return string.clone() 107 | } 108 | } 109 | panic!("Unsupported `configure(docs)` attribute; only supported form is #[configure(docs = \"$NAME\")]") 110 | } 111 | 112 | fn desugared_docs(attrs: &[Attribute]) -> Option { 113 | if let Some(attr) = attrs.iter().find(|attr| attr.is_sugared_doc) { 114 | if let MetaItem::NameValue(_, ref name) = attr.value { 115 | if let Lit::Str(ref string, _) = *name { 116 | return Some(string.trim_left_matches('/').to_owned()) 117 | } 118 | } 119 | } 120 | None 121 | } 122 | -------------------------------------------------------------------------------- /configure_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate heck; 2 | extern crate proc_macro; 3 | extern crate syn; 4 | 5 | #[macro_use] extern crate quote; 6 | 7 | mod attrs; 8 | 9 | use std::env; 10 | use std::fmt::Write; 11 | 12 | use heck::ShoutySnakeCase; 13 | use proc_macro::TokenStream; 14 | use quote::Tokens; 15 | use syn::*; 16 | 17 | use attrs::{CfgAttrs, FieldAttrs}; 18 | 19 | #[proc_macro_derive(Configure, attributes(configure))] 20 | pub fn derive_configure(input: TokenStream) -> TokenStream { 21 | let ast = parse_derive_input(&input.to_string()).unwrap(); 22 | let gen = impl_configure(ast); 23 | gen.parse().unwrap() 24 | } 25 | 26 | fn impl_configure(ast: DeriveInput) -> Tokens { 27 | let ty = &ast.ident; 28 | let generics = &ast.generics; 29 | let cfg_attrs = CfgAttrs::new(&ast.attrs[..]); 30 | let fields = assert_ast_is_struct(&ast); 31 | let project = cfg_attrs.name.or_else(|| env::var("CARGO_PKG_NAME").ok()).unwrap(); 32 | let docs = if cfg_attrs.docs { Some(docs(fields, &project)) } else { None }; 33 | 34 | quote!{ 35 | impl #generics ::configure::Configure for #ty #generics { 36 | fn generate() -> ::std::result::Result { 37 | let deserializer = ::configure::source::CONFIGURATION.get(#project); 38 | ::serde::Deserialize::deserialize(deserializer) 39 | } 40 | } 41 | 42 | #docs 43 | } 44 | } 45 | 46 | fn assert_ast_is_struct(ast: &DeriveInput) -> &[Field] { 47 | match ast.body { 48 | Body::Struct(VariantData::Struct(ref fields)) => fields, 49 | Body::Struct(VariantData::Unit) => &[], 50 | Body::Struct(VariantData::Tuple(_)) => { 51 | panic!("Cannot derive `Configure` for tuple struct") 52 | } 53 | Body::Enum(_) => { 54 | panic!("Cannot derive `Configure` for enum") 55 | } 56 | } 57 | } 58 | 59 | fn docs(fields: &[Field], project: &str) -> Tokens { 60 | let mut docs = format!("These environment variables can be used to configure {}.\n\n", project); 61 | for field in fields { 62 | let name = field.ident.as_ref().unwrap(); 63 | let ty = &field.ty; 64 | 65 | let attrs = FieldAttrs::new(field); 66 | 67 | let var_name = format!("{}_{}", project, name).to_shouty_snake_case(); 68 | let var_type = quote! { #ty }; 69 | 70 | if let Some(field_docs) = attrs.docs { 71 | let _ = writeln!(docs, "- **{}** ({}): {}", var_name, var_type, field_docs); 72 | } else { 73 | let _ = writeln!(docs, "- **{}** ({})", var_name, var_type); 74 | } 75 | } 76 | 77 | docs.push_str("\nThis library uses the configure crate to manage its configuration; you can\ 78 | also override how configuration is handled using the API in that crate."); 79 | 80 | quote! { 81 | #[doc = #docs] 82 | pub mod environment_variables { } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /configure_derive/tests/example.rs: -------------------------------------------------------------------------------- 1 | extern crate serde; 2 | 3 | #[macro_use] extern crate configure; 4 | #[macro_use] extern crate configure_derive; 5 | #[macro_use] extern crate serde_derive; 6 | 7 | use std::env; 8 | use std::path::PathBuf; 9 | use std::net::SocketAddr; 10 | 11 | use configure::Configure; 12 | 13 | #[derive(Configure, Deserialize)] 14 | #[configure(name = "example")] 15 | #[configure(generate_docs)] 16 | #[serde(default)] 17 | pub struct Config { 18 | #[configure(docs = "This is a socket address.")] 19 | socket_addr: SocketAddr, 20 | /// This is the cert path. 21 | tls_cert: Option, 22 | } 23 | 24 | impl Default for Config { 25 | fn default() -> Config { 26 | Config { 27 | socket_addr: "127.0.0.1:7878".parse().unwrap(), 28 | tls_cert: None, 29 | } 30 | } 31 | } 32 | 33 | #[test] 34 | fn check_that_it_works() { 35 | env::remove_var("CARGO_MANIFEST_DIR"); 36 | env::set_var("EXAMPLE_TLS_CERT", "etc/certificate"); 37 | use_default_config!(); 38 | 39 | let cfg = Config::generate().unwrap(); 40 | 41 | assert_eq!(cfg.socket_addr, "127.0.0.1:7878".parse().unwrap()); 42 | assert_eq!(cfg.tls_cert, Some("etc/certificate".into())); 43 | } 44 | --------------------------------------------------------------------------------