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
// Copyright 2020 Tetrate
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use super::dispatcher::{ContextSelector, VoidContextSelector};
use crate::extension::{Module, Result};

/// Generates the [`_start`] function that will be called by `Envoy` to let
/// WebAssembly module initialize itself.
///
/// [`_start`]: https://github.com/proxy-wasm/spec/blob/master/abi-versions/vNEXT/README.md#_start
///
/// # Examples
///
/// ```
/// # use envoy_sdk as envoy;
/// # use envoy::extension::{self, AccessLogger, NetworkFilter, HttpFilter, InstanceId, ExtensionFactory};
/// #
/// # struct MyHttpFilter;
/// # impl HttpFilter for MyHttpFilter {}
/// #
/// # struct MyHttpFilterFactory;
/// # impl MyHttpFilterFactory {
/// #     fn default() -> extension::Result<Self> { Ok(MyHttpFilterFactory) }
/// # }
/// # impl ExtensionFactory for MyHttpFilterFactory {
/// #     type Extension = MyHttpFilter;
/// #
/// #     fn name() -> &'static str { "my_http_filter" }
/// #
/// #     fn new_extension(&mut self, instance_id: InstanceId) -> extension::Result<Self::Extension> {
/// #         Ok(MyHttpFilter)
/// #     }
/// # }
/// #
/// # struct MyNetworkFilter;
/// # impl NetworkFilter for MyNetworkFilter {}
/// #
/// # struct MyNetworkFilterFactory;
/// # impl MyNetworkFilterFactory {
/// #     fn default() -> extension::Result<Self> { Ok(MyNetworkFilterFactory) }
/// # }
/// # impl ExtensionFactory for MyNetworkFilterFactory {
/// #     type Extension = MyNetworkFilter;
/// #
/// #     fn name() -> &'static str { "my_network_filter" }
/// #
/// #     fn new_extension(&mut self, instance_id: InstanceId) -> extension::Result<Self::Extension> {
/// #         Ok(MyNetworkFilter)
/// #     }
/// # }
/// #
/// # struct MyAccessLogger;
/// # impl AccessLogger for MyAccessLogger {
/// #     fn name() -> &'static str { "my_access_logger" }
/// # }
/// # impl MyAccessLogger {
/// #     fn default() -> extension::Result<Self> { Ok(MyAccessLogger) }
/// # }
/// #
/// use envoy::extension::{entrypoint, Module, Result};
///
/// entrypoint! { initialize } // put initialization logic into a function to make it unit testable
///
/// /// Does one-time initialization.
/// ///
/// /// Returns a registry of extensions provided by this module.
/// fn initialize() -> Result<Module> {
///     // arbitrary initialization steps
///
///     Module::new()
///         .add_http_filter(|_instance_id| MyHttpFilterFactory::default())?
///         .add_network_filter(|_instance_id| MyNetworkFilterFactory::default())?
///         .add_access_logger(|_instance_id| MyAccessLogger::default())
/// }
/// ```
#[macro_export]
macro_rules! entrypoint {
    // Apparently, Rust toolchain doesn't handle well exported name `_start`
    // when a package is compiled to targets other than `wasm32-unknown-unknown`.
    // Specifically, linking issues have been observed with targets `wasm32-wasi`
    // and `x86_64-unknown-linux-gnu`, which blocks unit testing.
    // Therefore, only use export name `_start` when in the context of target
    // `wasm32-unknown-unknown`.
    ($init_fn:expr) => {
        #[cfg_attr(
            all(
                target_arch = "wasm32",
                target_vendor = "unknown",
                target_os = "unknown"
            ),
            export_name = "_start"
        )]
        #[no_mangle]
        extern "C" fn start() {
            use $crate::extension::{self, Module, Result};
            use $crate::host::log;

            fn init<F>(init_fn: F)
            where
                F: FnOnce() -> Result<Module>,
            {
                // Apparently, `proxy_wasm` uses `set_log_level`
                // to set a custom panic handler that will log panics using Envoy Log API.
                // To be sure that panics will always be set,
                // we call `set_log_level` ourselves instead of leaving it up to a user.
                log::set_max_level(log::LogLevel::Info);

                // Call the init callback provided as an argument.
                extension::install(init_fn());
            }

            init($init_fn);
        }
    };
}

#[doc(hidden)]
pub fn install(config: Result<Module>) {
    match config {
        Ok(module) => ContextSelector::with_default_ops(module.into()).install(),
        Err(err) => VoidContextSelector::new(err).install(),
    }
}