aws_smithy_http_server/plugin/
scoped.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use std::marker::PhantomData;
7
8use super::{HttpMarker, ModelMarker, Plugin};
9
10/// Marker struct for `true`.
11///
12/// Implements [`ConditionalApply`] which applies the [`Plugin`].
13pub struct True;
14
15/// Marker struct for `false`.
16///
17/// Implements [`ConditionalApply`] which does nothing.
18pub struct False;
19
20/// Conditionally applies a [`Plugin`] `Pl` to some service `S`.
21///
22/// See [`True`] and [`False`].
23pub trait ConditionalApply<Ser, Op, Pl, T> {
24    type Service;
25
26    fn apply(plugin: &Pl, svc: T) -> Self::Service;
27}
28
29impl<Ser, Op, Pl, T> ConditionalApply<Ser, Op, Pl, T> for True
30where
31    Pl: Plugin<Ser, Op, T>,
32{
33    type Service = Pl::Output;
34
35    fn apply(plugin: &Pl, input: T) -> Self::Service {
36        plugin.apply(input)
37    }
38}
39
40impl<P, Op, Pl, T> ConditionalApply<P, Op, Pl, T> for False {
41    type Service = T;
42
43    fn apply(_plugin: &Pl, input: T) -> Self::Service {
44        input
45    }
46}
47
48/// A [`Plugin`] which scopes the application of an inner [`Plugin`].
49///
50/// In cases where operation selection must be performed at runtime [`filter_by_operation`](crate::plugin::filter_by_operation)
51/// can be used.
52///
53/// Operations within the scope will have the inner [`Plugin`] applied.
54///
55/// # Example
56///
57/// ```rust
58/// # use aws_smithy_http_server::{scope, plugin::Scoped};
59/// # struct OperationA; struct OperationB; struct OperationC;
60/// # let plugin = ();
61///
62/// // Define a scope over a service with 3 operations
63/// scope! {
64///     struct OnlyAB {
65///         includes: [OperationA, OperationB],
66///         excludes: [OperationC]
67///     }
68/// }
69///
70/// // Create a scoped plugin
71/// let scoped_plugin = Scoped::new::<OnlyAB>(plugin);
72/// ```
73pub struct Scoped<Scope, Pl> {
74    scope: PhantomData<Scope>,
75    plugin: Pl,
76}
77
78impl<Pl> Scoped<(), Pl> {
79    /// Creates a new [`Scoped`] from a `Scope` and [`Plugin`].
80    pub fn new<Scope>(plugin: Pl) -> Scoped<Scope, Pl> {
81        Scoped {
82            scope: PhantomData,
83            plugin,
84        }
85    }
86}
87
88/// A trait marking which operations are in scope via the associated type [`Membership::Contains`].
89pub trait Membership<Op> {
90    type Contains;
91}
92
93impl<Ser, Op, T, Scope, Pl> Plugin<Ser, Op, T> for Scoped<Scope, Pl>
94where
95    Scope: Membership<Op>,
96    Scope::Contains: ConditionalApply<Ser, Op, Pl, T>,
97{
98    type Output = <Scope::Contains as ConditionalApply<Ser, Op, Pl, T>>::Service;
99
100    fn apply(&self, input: T) -> Self::Output {
101        <Scope::Contains as ConditionalApply<Ser, Op, Pl, T>>::apply(&self.plugin, input)
102    }
103}
104
105impl<Scope, Pl> HttpMarker for Scoped<Scope, Pl> where Pl: HttpMarker {}
106impl<Scope, Pl> ModelMarker for Scoped<Scope, Pl> where Pl: ModelMarker {}
107
108/// A macro to help with scoping [plugins](crate::plugin) to a subset of all operations.
109///
110/// The scope must partition _all_ operations, that is, each and every operation must be included or excluded, but not
111/// both.
112///
113/// The generated server SDK exports a similar `scope` macro which is aware of a service's operations and can complete
114/// underspecified scopes automatically.
115///
116/// # Example
117///
118/// For a service with three operations: `OperationA`, `OperationB`, `OperationC`.
119///
120/// ```rust
121/// # use aws_smithy_http_server::scope;
122/// # struct OperationA; struct OperationB; struct OperationC;
123/// scope! {
124///     struct OnlyAB {
125///         includes: [OperationA, OperationB],
126///         excludes: [OperationC]
127///     }
128/// }
129/// ```
130#[macro_export]
131macro_rules! scope {
132    (
133        $(#[$attrs:meta])*
134        $vis:vis struct $name:ident {
135            includes: [$($include:ty),*],
136            excludes: [$($exclude:ty),*]
137        }
138    ) => {
139        $(#[$attrs])*
140        $vis struct $name;
141
142        $(
143            impl $crate::plugin::scoped::Membership<$include> for $name {
144                type Contains = $crate::plugin::scoped::True;
145            }
146        )*
147        $(
148            impl $crate::plugin::scoped::Membership<$exclude> for $name {
149                type Contains = $crate::plugin::scoped::False;
150            }
151        )*
152    };
153}
154
155#[cfg(test)]
156mod tests {
157    use crate::plugin::Plugin;
158
159    use super::Scoped;
160
161    struct OperationA;
162    struct OperationB;
163
164    scope! {
165        /// Includes A, not B.
166        pub struct AuthScope {
167            includes: [OperationA],
168            excludes: [OperationB]
169        }
170    }
171
172    struct MockPlugin;
173
174    impl<P, Op> Plugin<P, Op, u32> for MockPlugin {
175        type Output = String;
176
177        fn apply(&self, svc: u32) -> Self::Output {
178            svc.to_string()
179        }
180    }
181
182    #[test]
183    fn scope() {
184        let plugin = MockPlugin;
185        let scoped_plugin = Scoped::new::<AuthScope>(plugin);
186
187        let out: String = Plugin::<(), OperationA, _>::apply(&scoped_plugin, 3_u32);
188        assert_eq!(out, "3".to_string());
189        let out: u32 = Plugin::<(), OperationB, _>::apply(&scoped_plugin, 3_u32);
190        assert_eq!(out, 3);
191    }
192}