Skip to content

Commit

Permalink
feat(serde)!: use internal serialization to roundtrip crate types (#135)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `hcl::ser::Serializer` does not implement
`serde::Serializer` anymore. Use `serializer.serialize(&value)` in
places where `value.serialize(&mut serializer)` was used before.

This essentially uses the approach that the author of `minijinja`
describes in this blog post: https://lucumr.pocoo.org/2021/11/14/abusing-serde/

I originally read this before I even added serialization support to
`hcl-rs` and decided back then to go without internal serialization.

However, the number of types involved to represent the HCL structural,
expression and template languages now makes it necessary to switch to
internal serialization since each type needs a custom serializer
implementation just for the sake of roundtripping it through `serde`.
The amount of code that I wrote to make this work is already quite
massive and would even get bigger once serialization of template via
`serde` is fully supported. Also it was quite messy.

Thus, I switched to internal serialization to roundtrip all expression
and structure types. This meant that I could remove a lot of custom
serializers.

This also had some positive side effects:

- The `#[serde(rename = "$hcl::...")]` markers could be removed almost
  everywhere since they were only needed to make the roundtripping
  through serde work. The only type that still has this marker is `Body`
  since we need to special-case it during deserialization.
- The serializer code is much cleaner now.
- Serialization of types that are subject to roundtripping is quite a bit faster.
- It is now possible (and this change already implements most of it) to
  properly support serialization of HCL blocks (with or without labels)
  from custom types. The public API and documentation for this will be
  added as a followup. The `custom_blocks` test in
  `src/structure/ser/tests.rs` should give a first hint how this will
  work.
  • Loading branch information
martinohmann committed Nov 25, 2022
1 parent e39927d commit fbd555b
Show file tree
Hide file tree
Showing 33 changed files with 1,798 additions and 3,038 deletions.
7 changes: 4 additions & 3 deletions src/de/mod.rs
Expand Up @@ -9,6 +9,7 @@
mod tests;

use crate::{parser, Body, Error, Identifier, Result, Value};
use serde::de::value::StringDeserializer;
use serde::de::{self, Deserializer as _, IntoDeserializer};
use serde::forward_to_deserialize_any;
use std::fmt;
Expand Down Expand Up @@ -229,7 +230,7 @@ impl<'de> de::Deserializer<'de> for Deserializer {
where
V: de::Visitor<'de>,
{
if name == "$hcl::body" {
if name == "$hcl::Body" {
// Specialized handling of `hcl::Body`.
self.body.into_deserializer().deserialize_any(visitor)
} else {
Expand Down Expand Up @@ -459,9 +460,9 @@ where
}

impl<'de> IntoDeserializer<'de, Error> for Identifier {
type Deserializer = NewtypeStructDeserializer<String>;
type Deserializer = StringDeserializer<Error>;

fn into_deserializer(self) -> Self::Deserializer {
NewtypeStructDeserializer::new(self.into_inner())
self.into_inner().into_deserializer()
}
}
5 changes: 2 additions & 3 deletions src/expr/conditional.rs
@@ -1,10 +1,9 @@
use super::Expression;
use serde::{Deserialize, Serialize};
use serde::Deserialize;

/// The conditional operator allows selecting from one of two expressions based on the outcome of a
/// boolean expression.
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
#[serde(rename = "$hcl::conditional")]
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Conditional {
/// A condition expression that evaluates to a boolean value.
pub cond_expr: Expression,
Expand Down
31 changes: 26 additions & 5 deletions src/expr/de.rs
@@ -1,12 +1,33 @@
//! Deserialize impls for HCL structure types.

use super::*;
use crate::de::{NewtypeStructDeserializer, OptionDeserializer, VariantName};
use crate::de::{FromStrVisitor, OptionDeserializer, VariantName};
use crate::{Error, Identifier, Result};
use serde::de::value::{MapAccessDeserializer, StrDeserializer};
use serde::de::value::{MapAccessDeserializer, StrDeserializer, StringDeserializer};
use serde::de::{self, IntoDeserializer};
use serde::{forward_to_deserialize_any, Deserializer};

macro_rules! impl_deserialize_for_operator {
($($ty:ty => $expr:expr),*) => {
$(
impl<'de> de::Deserialize<'de> for $ty {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
deserializer.deserialize_any(FromStrVisitor::<Self>::new($expr))
}
}
)*
};
}

impl_deserialize_for_operator! {
UnaryOperator => "a unary operator",
BinaryOperator => "a binary operator",
HeredocStripMode => "a heredoc strip mode"
}

impl<'de> IntoDeserializer<'de, Error> for Expression {
type Deserializer = Self;

Expand Down Expand Up @@ -763,10 +784,10 @@ impl<'de> de::VariantAccess<'de> for ObjectKey {
}

impl<'de> IntoDeserializer<'de, Error> for RawExpression {
type Deserializer = NewtypeStructDeserializer<String>;
type Deserializer = StringDeserializer<Error>;

fn into_deserializer(self) -> Self::Deserializer {
NewtypeStructDeserializer::new(self.into_inner())
self.into_inner().into_deserializer()
}
}

Expand Down Expand Up @@ -875,7 +896,7 @@ impl<'de> IntoDeserializer<'de, Error> for HeredocStripMode {
}

impl<'de> IntoDeserializer<'de, Error> for Variable {
type Deserializer = NewtypeStructDeserializer<String>;
type Deserializer = StringDeserializer<Error>;

fn into_deserializer(self) -> Self::Deserializer {
self.into_inner().into_deserializer()
Expand Down
5 changes: 2 additions & 3 deletions src/expr/for_expr.rs
@@ -1,11 +1,10 @@
use super::Expression;
use crate::Identifier;
use serde::{Deserialize, Serialize};
use serde::Deserialize;

/// A for expression is a construct for constructing a collection by projecting the items from
/// another collection.
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
#[serde(rename = "$hcl::for_expr")]
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct ForExpr {
/// Optional name of the variable that will be temporarily assigned the key of each element
/// during iteration. If the source collection is an array, it gets assigned the zero-based
Expand Down
5 changes: 2 additions & 3 deletions src/expr/func_call.rs
@@ -1,10 +1,9 @@
use super::Expression;
use crate::Identifier;
use serde::{Deserialize, Serialize};
use serde::Deserialize;

/// Represents a function call expression with zero or more arguments.
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename = "$hcl::func_call")]
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct FuncCall {
/// The name of the function.
pub name: Identifier,
Expand Down
42 changes: 35 additions & 7 deletions src/expr/mod.rs
Expand Up @@ -25,7 +25,9 @@ pub use self::{
traversal::{Traversal, TraversalBuilder, TraversalOperator},
variable::Variable,
};
use crate::{format, Identifier, Number, Result, Value};
use crate::format;
use crate::ser::with_internal_serialization;
use crate::{Identifier, Number, Result, Value};
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::fmt::{self, Display, Write};
Expand All @@ -35,8 +37,7 @@ pub type Object<K, V> = vecmap::VecMap<K, V>;

/// A type representing the expression sub-language. It is used in HCL attributes to specify
/// values and in HCL templates.
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename = "$hcl::expression")]
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Expression {
/// Represents a null value.
Expand Down Expand Up @@ -74,6 +75,16 @@ pub enum Expression {
Raw(RawExpression),
}

impl Expression {
#[doc(hidden)]
pub fn from_serializable<T>(value: &T) -> Result<Expression>
where
T: ?Sized + Serialize,
{
with_internal_serialization(|| value.serialize(ExpressionSerializer))
}
}

impl From<Expression> for Value {
fn from(expr: Expression) -> Self {
match expr {
Expand Down Expand Up @@ -231,6 +242,18 @@ impl From<Operation> for Expression {
}
}

impl From<UnaryOp> for Expression {
fn from(op: UnaryOp) -> Self {
Expression::from(Operation::Unary(op))
}
}

impl From<BinaryOp> for Expression {
fn from(op: BinaryOp) -> Self {
Expression::from(Operation::Binary(op))
}
}

impl From<ForExpr> for Expression {
fn from(expr: ForExpr) -> Self {
Expression::ForExpr(Box::new(expr))
Expand All @@ -243,6 +266,12 @@ impl From<TemplateExpr> for Expression {
}
}

impl From<Heredoc> for Expression {
fn from(heredoc: Heredoc) -> Self {
Expression::from(TemplateExpr::Heredoc(heredoc))
}
}

impl From<Variable> for Expression {
fn from(variable: Variable) -> Self {
Expression::Variable(variable)
Expand All @@ -260,7 +289,6 @@ impl Display for Expression {

/// Represents an object key.
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename = "$hcl::object_key")]
#[non_exhaustive]
pub enum ObjectKey {
/// Represents an unquoted identifier used as object key.
Expand Down Expand Up @@ -327,8 +355,8 @@ impl Display for ObjectKey {
///
/// *Please note*: raw expressions are not validated during serialization, so it is your
/// responsiblity to ensure that they are valid HCL.
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(rename = "$hcl::raw_expression")]
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(transparent)]
pub struct RawExpression(String);

impl RawExpression {
Expand Down Expand Up @@ -396,5 +424,5 @@ pub fn to_expression<T>(value: T) -> Result<Expression>
where
T: Serialize,
{
value.serialize(ExpressionSerializer)
Expression::from_serializable(&value)
}
48 changes: 4 additions & 44 deletions src/expr/operation.rs
@@ -1,13 +1,11 @@
use super::Expression;
use crate::de::FromStrVisitor;
use crate::{Error, Result};
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use std::fmt;
use std::str::FromStr;

/// Operations apply a particular operator to either one or two expression terms.
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
#[serde(rename = "$hcl::operation")]
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Operation {
/// Represents an operation that applies an operator to a single expression.
Unary(UnaryOp),
Expand All @@ -28,8 +26,7 @@ impl From<BinaryOp> for Operation {
}

/// An operation that applies an operator to one expression.
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
#[serde(rename = "$hcl::unary_op")]
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct UnaryOp {
/// The unary operator to use on the expression.
pub operator: UnaryOperator,
Expand Down Expand Up @@ -87,27 +84,8 @@ impl FromStr for UnaryOperator {
}
}

impl Serialize for UnaryOperator {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}

impl<'de> Deserialize<'de> for UnaryOperator {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_any(FromStrVisitor::<Self>::new("a unary operator"))
}
}

/// An operation that applies an operator to two expressions.
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
#[serde(rename = "$hcl::binary_op")]
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct BinaryOp {
/// The expression on the left-hand-side of the operation.
pub lhs_expr: Expression,
Expand Down Expand Up @@ -322,21 +300,3 @@ impl FromStr for BinaryOperator {
}
}
}

impl Serialize for BinaryOperator {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}

impl<'de> Deserialize<'de> for BinaryOperator {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_any(FromStrVisitor::<Self>::new("a binary operator"))
}
}

0 comments on commit fbd555b

Please sign in to comment.