I'm confused why I am getting an Err(missing field "web3_node_provider") error when I cargo run using config-rs. Appears to fail at s.try_deserialize():
use config::{Config, ConfigError, Environment, File};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[allow(unused)]
struct Web3NodeProvider {
ethereum_mainnet_node_url_http: String,
alchemy_api_key: String,
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Settings {
web3_node_provider: Web3NodeProvider,
}
impl Settings {
pub fn new() -> Result<Self, ConfigError> {
let s = Config::builder()
.add_source(File::with_name("config/default"))
.add_source(File::with_name("config/local").required(false))
.add_source(Environment::with_prefix("app"))
.build()?;
s.try_deserialize()
}
}
fn main() {
let settings = Settings::new();
println!("{:?}", settings);
}
I've pretty much followed the hierarchy example in config-rs, so I'm sure I'm just misunderstanding something basic or missing something. I am able to use "Web3NodeProvider.url" but not "web3_node_provider.ethereum_mainnet_node_url_http".
default.toml
[Web3NodeProvider]
ethereum_mainnet_node_url_http = "https://eth-mainnet.g.alchemy.com/v2/"
alchemy_api_key = "alchemy-api-key"
local.toml
[Web3NodeProvider]
alchemy_api_key = "randomapikey"
As per example, you have to name your field in config as name of attribute not name of structure (type). Like so:
[web3_node_provider]
ethereum_mainnet_node_url_http = "https://eth-mainnet.g.alchemy.com/v2/"
alchemy_api_key = "alchemy-api-key"
Related
I'm attempting to use hcl-rs = 0.7.0 to parse some HCL. I'm just experimenting with arbitrary HCL, so I'm not looking to parse terraform specific code.
I'd like to be able to parse a block like this and get it's label as part of result
nested_block "nested_block_label" {
foo = 123
}
This currently doesn't work, but hopefully it shows my intention. Is something like this possible?
#[test]
fn deserialize_struct_with_label() {
#[derive(Deserialize, PartialEq, Debug)]
struct TestRoot {
nested_block: TestNested,
}
#[derive(Deserialize, PartialEq, Debug)]
struct TestNested {
label: String,
foo: u32,
}
let input = r#"
nested_block "nested_block_label" {
foo = 123
}"#;
let expected = TestRoot{ nested_block: TestNested { label: String::from("nested_block_label"), foo: 123 } };
assert_eq!(expected, from_str::<TestRoot>(input).unwrap());
}
Your problem is that hcl by default seems to interpret
nested_block "nested_block_label" {
foo = 123
}
as the following "serde structure":
"nested_block" -> {
"nested_block_label" -> {
"foo" -> 123
}
}
but for your Rust structs, it would have to be
"nested_block" -> {
"label" -> "nested_block_label"
"foo" -> 123
}
I'm not aware of any attributes that would allow you to bend the former into the latter.
As usual, when faced with this kind of situation, it is often easiest to first deserialize to a generic structure like hcl::Block and then convert to whatever struct you want manually. Disadvantage is that you'd have to do that for every struct separately.
You could, in theory, implement a generic deserialization function that wraps the Deserializer it receives and flattens the two-level structure you get into the one-level structure you want. But implementing Deserializers requires massive boilerplate. Possibly, somebody has done that before, but I'm not aware of any crate that would help you here, either.
As a sort of medium effort solution, you could have a special Labelled wrapper structure that always catches and flattens this intermediate level:
#[derive(Debug, PartialEq)]
struct Labelled<T> {
label: String,
t: T,
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for Labelled<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct V<T>(std::marker::PhantomData<T>);
impl<'de, T: Deserialize<'de>> serde::de::Visitor<'de> for V<T> {
type Value = Labelled<T>;
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
if let (Some((label, t)), None) =
(map.next_entry()?, map.next_entry::<String, ()>()?)
{
Ok(Labelled { label, t })
} else {
Err(serde::de::Error::invalid_type(
serde::de::Unexpected::Other("Singleton map"),
&self,
))
}
}
}
deserializer.deserialize_map(V::<T>(Default::default()))
}
}
would be used like this:
#[derive(Deserialize, PartialEq, Debug)]
struct TestRoot {
nested_block: Labelled<TestNested>,
}
#[derive(Deserialize, PartialEq, Debug)]
struct TestNested {
foo: u32,
}
Lastly, there may be some trick like adding #[serde(rename = "$hcl::label")]. Other serialization libraries (e.g. quick-xml) have similar and allow marking fields as something special this way. hcl-rs does the same internally, but it's undocumented and I can't figure out from the source whether what you need is possible.
I have a simple structure that contains a resources field. I would like my resources to always be Urls.
In the actual file that I am trying to deserialize, resources can either be URLs or Path.
Here is my structure:
pub struct Myfile {
pub resources: Vec<Resource>,
}
pub type Resource = Url;
I would like to use serde to:
Try to deserialize each Resource using the implementation from the url crate.
If it fails, try to deserialize each one of them into a Path and then use url::from_*_path() to get a Url.
I am trying to adapt the string or map,map and structure examples but I am struggling to understand where to even start.
Since my end result will by a Url, the examples seem to show that I should be implementing Deserialize for Url. But I still need to current implementation. My Resource is an alias so I can't implement Deserialize for it.
Is there any simple way to deserialize both Paths and Urls into Urls?
PS: I will probably go for the answer proposed but after reading through this post, I tried the following which seems to work too:
#[derive(Clone, Debug)]
pub struct Resource(Url);
impl<'de> Deserialize<'de> for Resource {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>
{
let s = String::deserialize(deserializer)?;
if let path = Path::new(&s) {
if path.is_file() {
Ok(Resource(Url::from_file_path(path).unwrap()))
} else {
Ok(Resource(Url::from_directory_path(path).unwrap()))
}
} else {
Url::deserialize(s.into_deserializer()).map(|x| Resource(x))
}
}
}
But is less convenient to work with than regular Urls.
I think the key to doing this with reasonable effort is to realize that serde::Deserialize for Url is also just cooking with water, i.e. just expecting a string and calling Url::parse on it.
So it's time to deploy my favourite serde trick: deserialize to a struct that serde can handle easily:
#[derive(Deserialize)]
pub struct MyfileDeserialize {
pub resources: Vec<String>,
}
Tell serde that it should get the struct you finally want from that easily handlable struct:
#[derive(Deserialize, Debug)]
#[serde(try_from = "MyfileDeserialize")]
pub struct Myfile {
pub resources: Vec<Resource>,
}
Finally, you just need to define how to turn MyfileDeserialize into Myfile.
impl TryFrom<MyfileDeserialize> for Myfile {
type Error = &'static str; // TODO: replace with anyhow or a proper error type
fn try_from(t: MyfileDeserialize) -> Result<Self, &'static str> {
Ok(Self {
resources: t
.resources
.into_iter()
.map(|url| {
if let Ok(url) = Url::parse(&url) {
return Ok(url);
};
if let Ok(url) = Url::from_file_path(url) {
return Ok(url);
};
// try more from_*_path here.
Err("Can't as url or path")
})
.collect::<Result<Vec<_>, _>>()?,
})
}
}
Playground
Edit regarding your PS:
If you are willing to mess around with manually implementing deserializer traits and functions, I suppose you do have the option of completely getting rid of any wrapper structs or mediating TryFrom types: add a #[serde(deserialize_with = …)] to your resources, and in there, first do a <Vec<String>>::deserialize(de)?, and then turn that into a Vec<Url> as usual.
Playground
Wondering if there's a "proper" way of converting an Enum to a &str and back.
The problem I'm trying to solve:
In the clap crate, args/subcommands are defined and identified by &strs. (Which I'm assuming don't fully take advantage of the type checker.) I'd like to pass a Command Enum to my application instead of a &str which would be verified by the type-checker and also save me from typing (typo-ing?) strings all over the place.
This is what I came up with from searching StackOverflow and std:
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Command {
EatCake,
MakeCake,
}
impl FromStr for Command {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"eat-cake" => Ok(Self::EatCake),
"make-cake" => Ok(Self::MakeCake),
_ => Err(()),
}
}
}
impl<'a> From<Command> for &'a str {
fn from(c: Command) -> Self {
match c {
Command::EatCake => "eat-cake",
Command::MakeCake => "make-cake",
}
}
}
fn main() {
let command_from_str: Command = "eat-cake".to_owned().parse().unwrap();
let str_from_command: &str = command_from_str.into();
assert_eq!(command_from_str, Command::EatCake);
assert_eq!(str_from_command, "eat-cake");
}
And here's a working playground:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b5e9ac450fd6a79b855306e96d4707fa
Here's an abridged version of what I'm running in clap.
let matches = App::new("cake")
.setting(AppSettings::SubcommandRequiredElseHelp)
// ...
.subcommand(
SubCommand::with_name(Command::MakeCake.into())
// ...
)
.subcommand(
SubCommand::with_name(Command::EatCake.into())
// ...
)
.get_matches();
It seems to work, but I'm not sure if I'm missing something / a bigger picture.
Related:
How to use an internal library Enum for Clap Args
How do I return an error within match statement while implementing from_str in rust?
Thanks!
The strum crate may save you some work. Using strum I was able to get the simple main() you have to work without any additional From implementations.
use strum_macros::{Display, EnumString, IntoStaticStr};
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Display, EnumString, IntoStaticStr)] // strum macros.
pub enum Command {
#[strum(serialize = "eat-cake")]
EatCake,
#[strum(serialize = "make-cake")]
MakeCake,
}
fn main() {
let command_from_str: Command = "eat-cake".to_owned().parse().unwrap();
let str_from_command: &str = command_from_str.into();
assert_eq!(command_from_str, Command::EatCake);
assert_eq!(str_from_command, "eat-cake");
}
I am trying to implement an API class using wasm_bindgen with asynchronous calls.
#![allow(non_snake_case)]
use std::future::Future;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use js_sys::Promise;
use web_sys::{Request, RequestInit, RequestMode, Response};
use wasm_bindgen_futures::future_to_promise;
use crate::fetch_example::*;
#[wasm_bindgen]
#[derive(Debug, Serialize, Deserialize)]
pub struct API {
root: String,
}
#[wasm_bindgen]
impl API {
pub fn new(root: &str) -> Self {
Self {
root: root.to_string(),
}
}
pub fn getVersion(&self) -> Promise {
let url = format!("{}/version", self.root);
future_to_promise(async move {
_request_json(&url, "GET")
})
}
// other methods ...
}
// What is the correct returned type instead of Result<JsValue, JsValue> ???
async fn _request_json(url: &str, method: &str) -> Result<JsValue, JsValue> {
let mut opts = RequestInit::new();
opts.method(method);
opts.mode(RequestMode::Cors);
let request = Request::new_with_str_and_init(&url, &opts)?;
request.headers().set("Accept", "application/json")?;
let window = web_sys::window().unwrap();
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
let resp: Response = resp_value.dyn_into().unwrap();
let json = JsFuture::from(resp.json()?).await?;
Ok(json)
}
As it is seen, I took out the HTTP call into a separate private function _request_json to avoid code duplication in different API methods (they are supposed to be several, not getVersion only).
I took the base example of an HTTP call from here: https://rustwasm.github.io/wasm-bindgen/examples/fetch.html
Also I found that asynchronous calls of struct methods should be implemented with the help of future_to_promise: https://github.com/rustwasm/wasm-bindgen/issues/1858
So the issue is with the returned type by _request_json. I cannot guess it correctly.
When I compile the project I get the error:
type mismatch resolving `<impl std::future::Future as std::future::Future>::Output == std::result::Result<wasm_bindgen::JsValue, wasm_bindgen::JsValue>`
If I copy the body of _request_json inside of future_to_promise everything works fine.
What is the correct returned expression for _request_json to make the code works?
This construction:
async move {
_request_json(&url, "GET")
}
has the type impl Future<Output = impl Future<Output = Result<JsValue, JsValue>>>.
You can fix it like this:
future_to_promise(async move {
_request_json(&url, "GET").await
})
and probably also like this:
future_to_promise(_request_json(&url, "GET"))
I'm looking for a way to initialize a structopt Vec field with multiple items by default. I can do it for a single item with:
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
pub struct Cli {
#[structopt(default_value = "foo")]
foo: Vec<String>,
}
fn main() {
let cli = Cli::from_iter(Vec::<String>::new());
assert_eq!(cli.foo, vec!["foo"]);
}
But how to make cli.foo to be equal let's say vec!["foo", "bar"] by default?
I've followed the L. Riemer advice, and it seems it's enough to implement FromStr only:
use structopt::StructOpt;
#[derive(Debug, PartialEq)]
struct Foo(Vec<String>);
impl std::str::FromStr for Foo {
type Err = Box<dyn std::error::Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Foo(s.split(",").map(|x| x.trim().to_owned()).collect()))
}
}
#[derive(StructOpt)]
pub struct Cli {
#[structopt(long, default_value = "foo, bar")]
foo: Foo,
}
fn main() {
let cli = Cli::from_iter(Vec::<String>::new());
assert_eq!(cli.foo, Foo(vec!["foo".into(), "bar".into()]));
let cli = Cli::from_iter(vec!["", "--foo", "foo"]);
assert_eq!(cli.foo, Foo(vec!["foo".into()]));
let cli = Cli::from_iter(vec!["", "--foo", "foo,bar,baz"]);
assert_eq!(cli.foo, Foo(vec!["foo".into(), "bar".into(), "baz".into()]));
}
I don't think you can do that: while StructOpt has some tricks around default values, I expect this still ends with the default value being injected in the CLI parsing as if it had been provided explicitly, which means there is likely no way to provide multiple default values (though I could certainly be wrong).
You probably want to handle this at the application-level e.g. right after having parsed the CLI, check for the arity of foo and update it if it's empty.