1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
use doc::{Identifier, Object};
use error::Error;

pub trait Resource: Sized {
    fn ident(&self) -> Result<Identifier, Error>;
    fn object(&self) -> Result<Object, Error>;
}

#[macro_export]
macro_rules! resource {
    ($target:ident, |&$ctx:ident| { $($rest:tt)* }) => {
        impl $crate::Resource for $target {
            fn ident(&$ctx) -> Result<$crate::doc::Identifier, $crate::error::Error> {
                let mut ident = $crate::doc::Identifier::build();

                expand_resource_impl!(@id $ctx, ident, { $($rest)* });
                expand_resource_impl!(@kind $ctx, ident, { $($rest)* });
                expand_resource_impl!(@meta $ctx, ident, { $($rest)* });

                ident.finalize()
            }

            fn object(&$ctx) -> Result<$crate::doc::Object, $crate::error::Error> {
                let mut object = $crate::doc::Object::build();

                expand_resource_impl!(@attrs $ctx, object, { $($rest)* });
                expand_resource_impl!(@id $ctx, object, { $($rest)* });
                expand_resource_impl!(@kind $ctx, object, { $($rest)* });
                expand_resource_impl!(@links $ctx, object, { $($rest)* });
                expand_resource_impl!(@meta $ctx, object, { $($rest)* });
                expand_resource_impl!(@rel $ctx, object, { $($rest)* });

                object.finalize()
            }
        }
    };
}

#[doc(hidden)]
#[macro_export]
macro_rules! expand_resource_impl {
    (@id $ctx:ident, $builder:ident, { id $value:block $($rest:tt)* }) => {
        $builder.id($value);
    };

    (@kind $ctx:ident, $builder:ident, { kind $value:block $($rest:tt)* }) => {
        $builder.kind($value);
    };

    (@attrs $ctx:ident, $builder:ident, { attr $key:expr, $value:block $($rest:tt)* }) => {
        $builder.attribute($key, $crate::to_value($value)?);
        expand_resource_impl!(@attrs $ctx, $builder, { $($rest)* });
    };

    (@attrs $ctx:ident, $builder:ident, { attr $field:ident; $($rest:tt)* }) => {
        expand_resource_impl!(@attrs $ctx, $builder, {
            attr stringify!($field), &$ctx.$field;
            $($rest)*
        });
    };

    (@attrs $ctx:ident, $builder:ident, { attrs $($field:ident),+; $($rest:tt)* }) => {
        expand_resource_impl!(@attrs $ctx, $builder,  {
            $(attr $field;)+
            $($rest)*
        });
    };

    (@rel $ctx:ident, $builder:ident, { has_many $key:expr, { $($body:tt)* } $($rest:tt)* }) => {
        $builder.relationship($key, {
            let mut relationship = $crate::doc::Relationship::build();
            expand_resource_impl!(@has_many $ctx, relationship, { $($body)* });
            relationship.finalize()?
        });

        expand_resource_impl!(@rel $ctx, $builder, { $($rest)* });
    };

    (@rel $ctx:ident, $builder:ident, { has_one $key:expr, { $($body:tt)* } $($rest:tt)* }) => {
        $builder.relationship($key, {
            let mut relationship = $crate::doc::Relationship::build();
            expand_resource_impl!(@has_one $ctx, relationship, { $($body)* });
            relationship.finalize()?
        });

        expand_resource_impl!(@rel $ctx, $builder, { $($rest)* });
    };

    (@rel $ctx:ident, $builder:ident, { has_many $($field:ident),*; $($rest:tt)* }) => {
        expand_resource_impl!(@rel $ctx, $builder, {
            $(has_many stringify!($field), { data $ctx.$field.iter(); })*
            $($rest)*
        });
    };

    (@rel $ctx:ident, $builder:ident, { has_one $($field:ident),*; $($rest:tt)* }) => {
        expand_resource_impl!(@rel $ctx, $builder, {
            $(has_one stringify!($field), { data $ctx.$field.as_ref(); })*
            $($rest)*
        });
    };

    (@has_many $ctx:ident, $builder:ident, { data $value:block $($rest:tt)* }) => {
        $builder.data({
            let value = ($value).map($crate::Resource::ident).collect::<Result<_, _>>()?;
            $crate::doc::Data::Collection(value)
        });

        expand_resource_impl!(@meta $ctx, $builder, { $($rest)* });
        expand_resource_impl!(@links $ctx, $builder, { $($rest)* });
    };

    (@has_one $ctx:ident, $builder:ident, { data $value:block $($rest:tt)* }) => {
        if let Some(value) = $value {
            let ident = $crate::Resource::ident(value)?;
            $builder.data($crate::doc::Data::Member(Box::new(Some(ident))));
        }

        expand_resource_impl!(@meta $ctx, $builder, { $($rest)* });
        expand_resource_impl!(@links $ctx, $builder, { $($rest)* });
    };

    (@links $ctx:ident, $builder:ident, { link $key:expr, { $($body:tt)* } $($rest:tt)* }) => {
        $builder.link($key, { expand_resource_impl!(@link $ctx, $builder, { $($body)* }) });
        expand_resource_impl!(@links $ctx, $builder, { $($rest)* });
    };

    (@links $($args:ident),+, { link $key:expr, $value:expr; $($rest:tt)* }) => {
        expand_resource_impl!(@links $($args),+, { link $key, { href { $value } } $($rest)* });
    };

    (@link $ctx:ident, $builder:ident, { href $value:block $($rest:tt)* }) => {{
        let mut link = $crate::doc::Link::build();

        link.href($value);
        expand_resource_impl!(@meta $ctx, link, { $($rest)* });
        link.finalize()?
    }};

    (@meta $ctx:ident, $builder:ident, { meta $key:expr, $value:block $($rest:tt)* }) => {
        $builder.meta($key, $crate::to_value($value)?);
        expand_resource_impl!(@meta $ctx, $builder, { $($rest)* });
    };

    // Ignore has_many specific syntax in other scopes.
    (@$scope:tt $($args:ident),+, { has_many $key:expr, { $($body:tt)* } $($rest:tt)* }) => {
        expand_resource_impl!(@$scope $($args),+, { $($rest)* });
    };

    // Ignore has_one specific syntax in other scopes.
    (@$scope:tt $($args:ident),+, { has_one $key:expr, { $($body:tt)* } $($rest:tt)* }) => {
        expand_resource_impl!(@$scope $($args),+, { $($rest)* });
    };

    // Ignore link specific syntax in other scopes.
    (@$scope:tt $($args:ident),+, { link $key:expr, { $($body:tt)* } $($rest:tt)* }) => {
        expand_resource_impl!(@$scope $($args),+, { $($rest)* });
    };

    (@$scope:tt $($args:ident),+, { $kwd:ident $value:expr; $($rest:tt)* }) => {
        expand_resource_impl!(@$scope $($args),+, { $kwd { $value } $($rest)* });
    };

    (@$scope:tt $($args:ident),+, { has_many $key:expr, $value:block $($rest:tt)* }) => {
        expand_resource_impl!(@$scope $($args),+, { $($rest)* });
    };

    (@$scope:tt $($args:ident),+, { has_one $key:expr, $value:block $($rest:tt)* }) => {
        expand_resource_impl!(@$scope $($args),+, { $($rest)* });
    };

    (@$scope:tt $($args:ident),+, { link $key:expr, $value:block $($rest:tt)* }) => {
        expand_resource_impl!(@$scope $($args),+, { $($rest)* });
    };

    (@$scope:tt $($args:ident),+, { $kwd:ident $key:expr, $value:expr; $($rest:tt)* }) => {
        expand_resource_impl!(@$scope $($args),+, { $kwd $key, { $value } $($rest)* });
    };

    (@$scope:tt $($args:ident),+, { $skip:tt $($rest:tt)* }) => {
        expand_resource_impl!(@$scope $($args),+, { $($rest)* });
    };

    ($($rest:tt)*) => ();
}