【问题标题】:Deserialize hcl with labels使用标签反序列化 hcl
【发布时间】:2022-11-10 13:59:04
【问题描述】:

我正在尝试使用 hcl-rs = 0.7.0 来解析一些 HCL。我只是在试验任意 HCL,所以我不想解析 terraform 特定的代码。

我希望能够解析这样的块并将其标签作为结果的一部分

nested_block "nested_block_label" {
    foo = 123
}

这目前不起作用,但希望它表明我的意图。这样的事情可能吗?

#[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());
}

【问题讨论】:

    标签: rust serde hcl


    【解决方案1】:

    您的问题是 hcl 默认情况下似乎可以解释

    nested_block "nested_block_label" {
        foo = 123
    }
    

    如以下“serde结构”:

    "nested_block" -> {
      "nested_block_label" -> {
        "foo" -> 123
      }
    }
    

    但是对于您的 Rust 结构,它必须是

    "nested_block" -> {
      "label" -> "nested_block_label"
      "foo" -> 123
    }
    

    我不知道有任何attributes 可以让您将前者弯曲成后者。

    像往常一样,当遇到这种情况时,通常最容易首先反序列化为hcl::Block 这样的通用结构,然后手动转换为您想要的任何结构。缺点是您必须分别为每个结构执行此操作。

    理论上,您可以实现一个通用的反序列化函数,该函数将接收到的Deserializer 包装起来,并将您获得的两级结构展平为您想要的一级结构。但是实现反序列化器需要大量的样板文件。可能以前有人做过,但我不知道有任何板条箱可以在这里帮助你。

    作为一种中等努力的解决方案,您可以拥有一个特殊的 Labelled 包装器结构,它始终可以捕获并展平这个中间级别:

    #[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()))
        }
    }
    

    会这样使用:

    #[derive(Deserialize, PartialEq, Debug)]
    struct TestRoot {
        nested_block: Labelled<TestNested>,
    }
    #[derive(Deserialize, PartialEq, Debug)]
    struct TestNested {
        foo: u32,
    }
    

    最后,可能会有一些技巧,比如添加#[serde(rename = "$hcl::label")]。其他序列化库(例如quick-xml)有类似的并允许以这种方式将字段标记为特殊的东西。 hcl-rs 在内部做同样的事情,但它没有记录,我无法从源头上弄清楚你需要什么是可能的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-01
      • 1970-01-01
      相关资源
      最近更新 更多