uniffi_bindgen/lib.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! # Uniffi: easily build cross-platform software components in Rust
6//!
7//! This is a highly-experimental crate for building cross-language software components
8//! in Rust, based on things we've learned and patterns we've developed in the
9//! [mozilla/application-services](https://github.com/mozilla/application-services) project.
10//!
11//! The idea is to let you write your code once, in Rust, and then re-use it from many
12//! other programming languages via Rust's C-compatible FFI layer and some automagically
13//! generated binding code. If you think of it as a kind of [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen)
14//! wannabe, with a clunkier developer experience but support for more target languages,
15//! you'll be pretty close to the mark.
16//!
17//! Currently supported target languages include Kotlin, Swift and Python.
18//!
19//! ## Usage
20//
21//! To build a cross-language component using `uniffi`, follow these steps.
22//!
23//! ### 1) Specify your Component Interface
24//!
25//! Start by thinking about the interface you want to expose for use
26//! from other languages. Use the Interface Definition Language to specify your interface
27//! in a `.udl` file, where it can be processed by the tools from this crate.
28//! For example you might define an interface like this:
29//!
30//! ```text
31//! namespace example {
32//! u32 foo(u32 bar);
33//! }
34//!
35//! dictionary MyData {
36//! u32 num_foos;
37//! bool has_a_bar;
38//! }
39//! ```
40//!
41//! ### 2) Implement the Component Interface as a Rust crate
42//!
43//! With the interface, defined, provide a corresponding implementation of that interface
44//! as a standard-looking Rust crate, using functions and structs and so-on. For example
45//! an implementation of the above Component Interface might look like this:
46//!
47//! ```text
48//! fn foo(bar: u32) -> u32 {
49//! // TODO: a better example!
50//! bar + 42
51//! }
52//!
53//! struct MyData {
54//! num_foos: u32,
55//! has_a_bar: bool
56//! }
57//! ```
58//!
59//! ### 3) Generate and include component scaffolding from the UDL file
60//!
61//! Add to your crate `uniffi_build` under `[build-dependencies]`,
62//! then add a `build.rs` script to your crate and have it call `uniffi_build::generate_scaffolding`
63//! to process your `.udl` file. This will generate some Rust code to be included in the top-level source
64//! code of your crate. If your UDL file is named `example.udl`, then your build script would call:
65//!
66//! ```text
67//! uniffi_build::generate_scaffolding("src/example.udl")
68//! ```
69//!
70//! This would output a rust file named `example.uniffi.rs`, ready to be
71//! included into the code of your rust crate like this:
72//!
73//! ```text
74//! include_scaffolding!("example");
75//! ```
76//!
77//! ### 4) Generate foreign language bindings for the library
78//!
79//! You will need ensure a local `uniffi-bindgen` - see <https://mozilla.github.io/uniffi-rs/tutorial/foreign_language_bindings.html>
80//! This utility provides a command-line tool that can produce code to
81//! consume the Rust library in any of several supported languages.
82//! It is done by calling (in kotlin for example):
83//!
84//! ```text
85//! cargo run --bin -p uniffi-bindgen --language kotlin ./src/example.udl
86//! ```
87//!
88//! This will produce a file `example.kt` in the same directory as the .udl file, containing kotlin bindings
89//! to load and use the compiled rust code via its C-compatible FFI.
90//!
91
92#![warn(rust_2018_idioms, unused_qualifications)]
93#![allow(unknown_lints)]
94
95use anyhow::{anyhow, bail, Context, Result};
96use camino::{Utf8Path, Utf8PathBuf};
97use fs_err::{self as fs, File};
98use serde::Deserialize;
99use std::fmt;
100use std::io::prelude::*;
101use std::io::ErrorKind;
102use std::process::Command;
103
104mod bindgen_paths;
105pub mod bindings;
106pub mod interface;
107pub mod library_mode;
108mod loader;
109pub mod macro_metadata;
110pub mod pipeline;
111pub mod scaffolding;
112
113#[cfg(feature = "cargo-metadata")]
114pub mod cargo_metadata;
115
116use crate::interface::CallbackInterface;
117use crate::interface::{
118 Argument, Constructor, Enum, FfiArgument, FfiField, Field, Function, Method, Object, Record,
119 Variant,
120};
121pub use bindgen_paths::{BindgenPaths, BindgenPathsLayer};
122pub use interface::ComponentInterface;
123pub use library_mode::find_components;
124use scaffolding::RustScaffolding;
125use uniffi_meta::Type;
126
127pub use loader::BindgenLoader;
128
129/// The options used when creating bindings. Named such
130/// it doesn't cause confusion that it's settings specific to
131/// the generator itself.
132// TODO: We should try and move the public interface of the module to
133// this struct. For now, only the BindingGenerator uses it.
134#[derive(Debug, Default)]
135pub struct GenerationSettings {
136 pub out_dir: Utf8PathBuf,
137 pub try_format_code: bool,
138 pub cdylib: Option<String>,
139}
140
141/// A trait representing a UniFFI Binding Generator
142///
143/// External crates that implement binding generators, should implement this type
144/// and call the [`generate_external_bindings`] using a type that implements this trait.
145///
146/// Deprecated: External crates are encouraged to use the `BindgenLoader` type instead, which lets
147/// you control the binding generation process more directly.
148pub trait BindingGenerator: Sized {
149 /// Handles configuring the bindings
150 type Config;
151
152 /// Creates a new config.
153 fn new_config(&self, root_toml: &toml::Value) -> Result<Self::Config>;
154
155 /// Update the various config items in preparation to write one or more of them.
156 ///
157 /// # Arguments
158 /// - `cdylib`: The name of the cdylib file, if known.
159 /// - `library_path`: The name of library used to extract the symbols.
160 /// - `components`: A mutable array of [`Component`]s to be updated.
161 fn update_component_configs(
162 &self,
163 settings: &GenerationSettings,
164 components: &mut Vec<Component<Self::Config>>,
165 ) -> Result<()>;
166
167 /// Writes the bindings to the output directory
168 ///
169 /// # Arguments
170 /// - `components`: An array of [`Component`]s representing the items to be generated.
171 /// - `out_dir`: The path to where the binding generator should write the output bindings
172 fn write_bindings(
173 &self,
174 settings: &GenerationSettings,
175 components: &[Component<Self::Config>],
176 ) -> Result<()>;
177}
178
179/// A trait to alter language specific type representations.
180///
181/// It is meant to be implemented by each language oracle. It takes a
182/// ['ComponentInterface'] and uses its own specific language adjustment
183/// functions to be able to generate language specific templates.
184pub trait VisitMut {
185 /// Go through each `Record` of a [`ComponentInterface`] and
186 /// adjust it to language specific naming conventions.
187 fn visit_record(&self, record: &mut Record);
188
189 /// Change the name of an `Object` of a [`ComponentInterface`]
190 /// to language specific naming conventions.
191 fn visit_object(&self, object: &mut Object);
192
193 /// Change the name of an `Object` of a [`ComponentInterface`]
194 /// to language specific naming conventions.
195 fn visit_callback_interface(&self, _iface: &mut CallbackInterface) {}
196
197 /// Change the name of a `Field` of an `Enum` `Variant`
198 /// to language specific naming conventions.
199 fn visit_field(&self, _field: &mut Field) {}
200
201 /// Change the name of a `FfiField` inside a `FfiStruct`
202 /// to language specific naming conventions.
203 fn visit_ffi_field(&self, _ffi_field: &mut FfiField) {}
204
205 /// Change the `Arugment` of a `FfiFunction` in the [`ComponentInterface`]
206 /// to language specific naming conventions.
207 fn visit_ffi_argument(&self, _ffi_argument: &mut FfiArgument) {}
208
209 /// Change the `FfiType` objects in the [`ComponentInterface`]
210 /// to language specific naming conventions.
211 fn visit_ffi_type(&self, _ffi_type: &mut interface::FfiType) {}
212
213 /// Go through each `Enum` of a [`ComponentInterface`] and
214 /// adjust it to language specific naming conventions.
215 fn visit_enum(&self, is_error: bool, enum_: &mut Enum);
216
217 /// Go through each `Variant` of an `Enum` and
218 /// adjust it to language specific naming conventions.
219 fn visit_variant(&self, _is_error: bool, _variant: &mut Variant) {}
220
221 /// Go through each `Type` in the `TypeUniverse` of
222 /// a [`ComponentInterface`] and adjust it to language specific
223 /// naming conventions.
224 fn visit_type(&self, type_: &mut Type);
225
226 /// Go through each error name in the interface and adjust it to language specific naming
227 /// conventions. The new name must match the name of the Enum/Object definition after it's
228 /// visited.
229 fn visit_error_name(&self, name: &mut String);
230
231 /// Go through each `Method` of an `Object` or `CallbackInterface` and
232 /// adjust it to language specific naming conventions.
233 fn visit_method(&self, object_name: &str, method: &mut Method);
234
235 /// Go through each `Argument` of a `Function` and
236 /// adjust it to language specific naming conventions.
237 fn visit_argument(&self, _argument: &mut Argument) {}
238
239 /// Go through each `Constructor` of a [`ComponentInterface`] and
240 /// adjust it to language specific naming conventions.
241 fn visit_constructor(&self, object_name: &str, constructor: &mut Constructor);
242
243 /// Go through each `Function` of a [`ComponentInterface`] and
244 /// adjust it to language specific naming conventions.
245 fn visit_function(&self, function: &mut Function);
246}
247
248/// Everything needed to generate a ComponentInterface.
249#[derive(Debug)]
250pub struct Component<Config> {
251 pub ci: ComponentInterface,
252 pub config: Config,
253}
254
255/// A trait used by the bindgen to obtain config information about a source crate
256/// which was found in the metadata for the library.
257///
258/// This is an abstraction around needing the source directory for a crate.
259/// In most cases `cargo_metadata` can be used, but this should be able to work in
260/// more environments.
261pub trait BindgenCrateConfigSupplier {
262 /// Get a `toml::value::Table` instance for the crate.
263 fn get_toml(&self, _crate_name: &str) -> Result<Option<toml::value::Table>> {
264 Ok(None)
265 }
266
267 /// Get the path to the TOML file for a crate.
268 ///
269 /// This is usually the `uniffi.toml` path in the root of the crate source.
270 fn get_toml_path(&self, _crate_name: &str) -> Option<Utf8PathBuf> {
271 None
272 }
273
274 /// Obtains the contents of the named UDL file which was referenced by the type metadata.
275 fn get_udl(&self, crate_name: &str, udl_name: &str) -> Result<String> {
276 bail!("Crate {crate_name} has no UDL {udl_name}")
277 }
278}
279
280pub struct EmptyCrateConfigSupplier;
281impl BindgenCrateConfigSupplier for EmptyCrateConfigSupplier {}
282
283impl BindgenCrateConfigSupplier for &&dyn BindgenCrateConfigSupplier {
284 fn get_toml(&self, crate_name: &str) -> Result<Option<toml::value::Table>> {
285 (**self).get_toml(crate_name)
286 }
287
288 fn get_toml_path(&self, crate_name: &str) -> Option<Utf8PathBuf> {
289 (**self).get_toml_path(crate_name)
290 }
291
292 fn get_udl(&self, crate_name: &str, udl_name: &str) -> Result<String> {
293 (**self).get_udl(crate_name, udl_name)
294 }
295}
296
297/// A convenience function for the CLI to help avoid using static libs
298/// in places cdylibs are required.
299pub fn is_cdylib(library_file: impl AsRef<Utf8Path>) -> bool {
300 library_mode::calc_cdylib_name(library_file.as_ref()).is_some()
301}
302
303/// Generate bindings for single crate via a UDL file.
304///
305/// This only works if you have exactly 1 crate and no shared types.
306/// It should be considered deprecated, you should use the multi-crate options instead.
307///
308/// # Arguments
309/// - `binding_generator`: Type that implements BindingGenerator
310/// - `udl_file`: The path to the UDL file
311/// - `config_file_override`: The path to the configuration toml file, most likely called `uniffi.toml`. If [`None`], the function will try to guess based on the crate's root.
312/// - `out_dir_override`: The path to write the bindings to. If [`None`], it will be the path to the parent directory of the `udl_file`
313/// - `library_file`: The path to a dynamic library to attempt to extract the definitions from and extend the component interface with. No extensions to component interface occur if it's [`None`]
314/// - `crate_name`: Override the default crate name that is guessed from UDL file path.
315///
316/// Deprecated: External crates are encouraged to use the `BindgenLoader` type instead, which lets
317/// you control the binding generation process more directly.
318pub fn generate_external_bindings<T: BindingGenerator>(
319 binding_generator: &T,
320 udl_file: impl AsRef<Utf8Path>,
321 config_file_override: Option<impl AsRef<Utf8Path>>,
322 out_dir_override: Option<impl AsRef<Utf8Path>>,
323 library_file: Option<impl AsRef<Utf8Path>>,
324 crate_name: Option<&str>,
325 try_format_code: bool,
326) -> Result<()> {
327 let crate_name = crate_name
328 .map(|c| Ok(c.to_string()))
329 .unwrap_or_else(|| crate_name_from_cargo_toml(udl_file.as_ref()))?;
330 let mut ci = parse_udl(udl_file.as_ref(), &crate_name)?;
331 if let Some(ref library_file) = library_file {
332 macro_metadata::add_to_ci_from_library(&mut ci, library_file.as_ref())?;
333 }
334 let crate_root = &guess_crate_root(udl_file.as_ref()).context("Failed to guess crate root")?;
335
336 let config_file_override = config_file_override.as_ref().map(|p| p.as_ref());
337
338 let config = {
339 let crate_config = load_toml_file(Some(&crate_root.join("uniffi.toml")))
340 .context("failed to load {crate_root}/uniffi.toml")?;
341 let toml_value =
342 overridden_config_value(crate_config.unwrap_or_default(), config_file_override)?;
343 binding_generator.new_config(&toml_value)?
344 };
345
346 let settings = GenerationSettings {
347 cdylib: match library_file {
348 Some(ref library_file) => {
349 library_mode::calc_cdylib_name(library_file.as_ref()).map(ToOwned::to_owned)
350 }
351 None => None,
352 },
353 out_dir: get_out_dir(
354 udl_file.as_ref(),
355 out_dir_override.as_ref().map(|p| p.as_ref()),
356 )?,
357 try_format_code,
358 };
359
360 let mut components = vec![Component { ci, config }];
361 binding_generator.update_component_configs(&settings, &mut components)?;
362
363 // need to derive ffi after the bindings have had a chance to update any types.
364 components[0]
365 .ci
366 .derive_ffi_funcs()
367 .context("Failed to derive FFI functions")?;
368
369 binding_generator.write_bindings(&settings, &components)
370}
371
372// Generate the infrastructural Rust code for implementing the UDL interface,
373// such as the `extern "C"` function definitions and record data types.
374// Locates and parses Cargo.toml to determine the name of the crate.
375pub fn generate_component_scaffolding(
376 udl_file: &Utf8Path,
377 out_dir_override: Option<&Utf8Path>,
378 format_code: bool,
379) -> Result<()> {
380 let component = parse_udl(udl_file, &crate_name_from_cargo_toml(udl_file)?)
381 .with_context(|| format!("parsing udl file {udl_file}"))?;
382 generate_component_scaffolding_inner(component, udl_file, out_dir_override, format_code)
383}
384
385// Generate the infrastructural Rust code for implementing the UDL interface,
386// such as the `extern "C"` function definitions and record data types, using
387// the specified crate name.
388pub fn generate_component_scaffolding_for_crate(
389 udl_file: &Utf8Path,
390 crate_name: &str,
391 out_dir_override: Option<&Utf8Path>,
392 format_code: bool,
393) -> Result<()> {
394 let component =
395 parse_udl(udl_file, crate_name).with_context(|| format!("parsing udl file {udl_file}"))?;
396 generate_component_scaffolding_inner(component, udl_file, out_dir_override, format_code)
397}
398
399fn generate_component_scaffolding_inner(
400 component: ComponentInterface,
401 udl_file: &Utf8Path,
402 out_dir_override: Option<&Utf8Path>,
403 format_code: bool,
404) -> Result<()> {
405 let file_stem = udl_file.file_stem().context("not a file")?;
406 let filename = format!("{file_stem}.uniffi.rs");
407 let out_path = get_out_dir(udl_file, out_dir_override)?.join(filename);
408 let mut f = File::create(&out_path)?;
409 write!(f, "{}", RustScaffolding::new(&component, file_stem))
410 .context("Failed to write output file")?;
411 if format_code {
412 format_code_with_rustfmt(&out_path).context("formatting generated Rust code")?;
413 }
414 Ok(())
415}
416
417// Generate the bindings in the target languages that call the scaffolding
418// Rust code.
419///
420/// Deprecated: External crates are encouraged to use the `BindgenLoader` type instead, which lets
421/// you control the binding generation process more directly.
422pub fn generate_bindings<T: BindingGenerator>(
423 udl_file: &Utf8Path,
424 config_file_override: Option<&Utf8Path>,
425 binding_generator: T,
426 out_dir_override: Option<&Utf8Path>,
427 library_file: Option<&Utf8Path>,
428 crate_name: Option<&str>,
429 try_format_code: bool,
430) -> Result<()> {
431 generate_external_bindings(
432 &binding_generator,
433 udl_file,
434 config_file_override,
435 out_dir_override,
436 library_file,
437 crate_name,
438 try_format_code,
439 )
440}
441
442pub fn print_repr(library_path: &Utf8Path) -> Result<()> {
443 let metadata = macro_metadata::extract_from_library(library_path)?;
444 println!("{metadata:#?}");
445 Ok(())
446}
447
448// Given the path to a UDL file, locate and parse the corresponding Cargo.toml to determine
449// the library crate name.
450// Note that this is largely a copy of code in uniffi_macros/src/util.rs, but sharing it
451// isn't trivial and it's not particularly complicated so we've just copied it.
452fn crate_name_from_cargo_toml(udl_file: &Utf8Path) -> Result<String> {
453 #[derive(Deserialize)]
454 struct CargoToml {
455 package: Package,
456 #[serde(default)]
457 lib: Lib,
458 }
459
460 #[derive(Deserialize)]
461 struct Package {
462 name: String,
463 }
464
465 #[derive(Default, Deserialize)]
466 struct Lib {
467 name: Option<String>,
468 }
469
470 let file = guess_crate_root(udl_file)?.join("Cargo.toml");
471 let cargo_toml_str =
472 fs::read_to_string(file).context("Can't find Cargo.toml to determine the crate name")?;
473
474 let cargo_toml = toml::from_str::<CargoToml>(&cargo_toml_str)?;
475
476 let lib_crate_name = cargo_toml
477 .lib
478 .name
479 .unwrap_or_else(|| cargo_toml.package.name.replace('-', "_"));
480
481 Ok(lib_crate_name)
482}
483
484/// Guess the root directory of the crate from the path of its UDL file.
485///
486/// For now, we assume that the UDL file is in `./src/something.udl` relative
487/// to the crate root. We might consider something more sophisticated in
488/// future.
489pub fn guess_crate_root(udl_file: &Utf8Path) -> Result<&Utf8Path> {
490 let path_guess = udl_file
491 .parent()
492 .context("UDL file has no parent folder!")?
493 .parent()
494 .context("UDL file has no grand-parent folder!")?;
495 if !path_guess.join("Cargo.toml").is_file() {
496 bail!("UDL file does not appear to be inside a crate")
497 }
498 Ok(path_guess)
499}
500
501fn get_out_dir(udl_file: &Utf8Path, out_dir_override: Option<&Utf8Path>) -> Result<Utf8PathBuf> {
502 Ok(match out_dir_override {
503 Some(s) => {
504 // Create the directory if it doesn't exist yet.
505 fs::create_dir_all(s)?;
506 s.canonicalize_utf8().context("Unable to find out-dir")?
507 }
508 None => udl_file
509 .parent()
510 .context("File has no parent directory")?
511 .to_owned(),
512 })
513}
514
515fn parse_udl(udl_file: &Utf8Path, crate_name: &str) -> Result<ComponentInterface> {
516 let udl = fs::read_to_string(udl_file)
517 .with_context(|| format!("Failed to read UDL from {udl_file}"))?;
518 let group = uniffi_udl::parse_udl(&udl, crate_name)?;
519 ComponentInterface::from_metadata(group)
520}
521
522fn format_code_with_rustfmt(path: &Utf8Path) -> Result<()> {
523 let status = Command::new("rustfmt").arg(path).status().map_err(|e| {
524 let ctx = match e.kind() {
525 ErrorKind::NotFound => "formatting was requested, but rustfmt was not found",
526 _ => "unknown error when calling rustfmt",
527 };
528 anyhow!(e).context(ctx)
529 })?;
530 if !status.success() {
531 bail!("rustmt failed when formatting scaffolding. Note: --no-format can be used to skip formatting");
532 }
533 Ok(())
534}
535
536/// Load TOML from file if the file exists.
537fn load_toml_file(source: Option<&Utf8Path>) -> Result<Option<toml::value::Table>> {
538 if let Some(source) = source {
539 if source.exists() {
540 let contents =
541 fs::read_to_string(source).with_context(|| format!("read file: {:?}", source))?;
542 return Ok(Some(
543 toml::de::from_str(&contents)
544 .with_context(|| format!("parse toml: {:?}", source))?,
545 ));
546 }
547 }
548
549 Ok(None)
550}
551
552/// Load the default `uniffi.toml` config, merge TOML trees with `config_file_override` if specified.
553fn overridden_config_value(
554 mut config: toml::value::Table,
555 config_file_override: Option<&Utf8Path>,
556) -> Result<toml::Value> {
557 let override_config = load_toml_file(config_file_override).context("override config")?;
558 if let Some(override_config) = override_config {
559 merge_toml(&mut config, override_config);
560 }
561 Ok(toml::Value::from(config))
562}
563
564fn merge_toml(a: &mut toml::value::Table, b: toml::value::Table) {
565 for (key, value) in b.into_iter() {
566 match a.get_mut(&key) {
567 Some(existing_value) => match (existing_value, value) {
568 (toml::Value::Table(ref mut t0), toml::Value::Table(t1)) => {
569 merge_toml(t0, t1);
570 }
571 (v, value) => *v = value,
572 },
573 None => {
574 a.insert(key, value);
575 }
576 }
577 }
578}
579
580// convert `anyhow::Error` and `&str` etc to askama errors.
581// should only be needed by "filters", otherwise anyhow etc work directly.
582pub fn to_askama_error<T: ToString + ?Sized>(t: &T) -> askama::Error {
583 askama::Error::Custom(Box::new(BindingsTemplateError(t.to_string())))
584}
585
586// Need a struct to define an error that implements std::error::Error, which neither String nor
587// anyhow::Error do.
588#[derive(Debug)]
589struct BindingsTemplateError(String);
590
591impl fmt::Display for BindingsTemplateError {
592 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
593 write!(f, "{}", self.0)
594 }
595}
596
597impl std::error::Error for BindingsTemplateError {}
598
599// FIXME(HACK):
600// Include the askama config file into the build.
601// That way cargo tracks the file and other tools relying on file tracking see it as well.
602// See https://bugzilla.mozilla.org/show_bug.cgi?id=1774585
603// In the future askama should handle that itself by using the `track_path::path` API,
604// see https://github.com/rust-lang/rust/pull/84029
605#[allow(dead_code)]
606mod __unused {
607 const _: &[u8] = include_bytes!("../askama.toml");
608}
609
610#[cfg(test)]
611mod test {
612 use super::*;
613
614 #[test]
615 fn test_guessing_of_crate_root_directory_from_udl_file() {
616 // When running this test, this will be the ./uniffi_bindgen directory.
617 let this_crate_root = Utf8PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
618
619 let example_crate_root = this_crate_root
620 .parent()
621 .expect("should have a parent directory")
622 .join("examples/arithmetic");
623 assert_eq!(
624 guess_crate_root(&example_crate_root.join("src/arthmetic.udl")).unwrap(),
625 example_crate_root
626 );
627
628 let not_a_crate_root = &this_crate_root.join("src/templates");
629 assert!(guess_crate_root(¬_a_crate_root.join("src/example.udl")).is_err());
630 }
631
632 #[test]
633 fn test_merge_toml() {
634 let default = r#"
635 foo = "foo"
636 bar = "bar"
637
638 [table1]
639 foo = "foo"
640 bar = "bar"
641 "#;
642 let mut default = toml::de::from_str(default).unwrap();
643
644 let override_toml = r#"
645 # update key
646 bar = "BAR"
647 # insert new key
648 baz = "BAZ"
649
650 [table1]
651 # update key
652 bar = "BAR"
653 # insert new key
654 baz = "BAZ"
655
656 # new table
657 [table1.table2]
658 bar = "BAR"
659 baz = "BAZ"
660 "#;
661 let override_toml = toml::de::from_str(override_toml).unwrap();
662
663 let expected = r#"
664 foo = "foo"
665 bar = "BAR"
666 baz = "BAZ"
667
668 [table1]
669 foo = "foo"
670 bar = "BAR"
671 baz = "BAZ"
672
673 [table1.table2]
674 bar = "BAR"
675 baz = "BAZ"
676 "#;
677 let expected: toml::value::Table = toml::de::from_str(expected).unwrap();
678
679 merge_toml(&mut default, override_toml);
680
681 assert_eq!(&expected, &default);
682 }
683}