diff --git a/nix-js/runtime-ts/src/builtins/derivation.ts b/nix-js/runtime-ts/src/builtins/derivation.ts index afe9541..245789c 100644 --- a/nix-js/runtime-ts/src/builtins/derivation.ts +++ b/nix-js/runtime-ts/src/builtins/derivation.ts @@ -377,16 +377,16 @@ export const derivationStrict = (args: NixValue): NixAttrs => { const rustResult: { drvPath: string; outputs: [string, string][]; - } = Deno.core.ops.op_finalize_derivation({ - name: drvName, + } = Deno.core.ops.op_finalize_derivation( + drvName, builder, platform, outputs, - args: drvArgs, - env: envEntries, - context: contextArray, - fixedOutput: fixedOutputInfo, - }); + drvArgs, + envEntries, + contextArray, + fixedOutputInfo, + ); const result: NixAttrs = new Map(); diff --git a/nix-js/runtime-ts/src/builtins/hash.ts b/nix-js/runtime-ts/src/builtins/hash.ts index feb195f..ffdd609 100644 --- a/nix-js/runtime-ts/src/builtins/hash.ts +++ b/nix-js/runtime-ts/src/builtins/hash.ts @@ -30,9 +30,5 @@ export const convertHash = (args: NixValue): string => { const toHashFormat = forceStringNoCtx(select(attrs, ["toHashFormat"])); - return Deno.core.ops.op_convert_hash({ - hash, - hashAlgo, - toHashFormat, - }); + return Deno.core.ops.op_convert_hash(hash, hashAlgo, toHashFormat); }; diff --git a/nix-js/runtime-ts/src/builtins/io.ts b/nix-js/runtime-ts/src/builtins/io.ts index 407c408..91bc5bc 100644 --- a/nix-js/runtime-ts/src/builtins/io.ts +++ b/nix-js/runtime-ts/src/builtins/io.ts @@ -49,8 +49,7 @@ export const importFunc = (path: NixValue): NixValue => { return cached; } - const code = Deno.core.ops.op_import(pathStr); - const result = Function(`return (${code})`)(); + const result = Deno.core.ops.op_import(pathStr); importCache.set(pathStr, result); return result; @@ -85,24 +84,24 @@ export const fetchClosure = (_args: NixValue): never => { }; export interface FetchUrlResult { - store_path: string; + storePath: string; hash: string; } export interface FetchTarballResult { - store_path: string; - nar_hash: string; + storePath: string; + narHash: string; } export interface FetchGitResult { - out_path: string; + outPath: string; rev: string; - short_rev: string; - rev_count: number; - last_modified: number; - last_modified_date: string; + shortRev: string; + revCount: number; + lastModified: number; + lastModifiedDate: string; submodules: boolean; - nar_hash: string | null; + narHash: string | null; } const normalizeUrlInput = ( @@ -154,16 +153,16 @@ export const fetchurl = (args: NixValue): NixString => { executable ?? false, ); const context: NixStringContext = new Set(); - addOpaqueContext(context, result.store_path); - return mkStringWithContext(result.store_path, context); + addOpaqueContext(context, result.storePath); + return mkStringWithContext(result.storePath, context); }; export const fetchTarball = (args: NixValue): NixString => { const { url, name, sha256 } = normalizeTarballInput(args); const result: FetchTarballResult = Deno.core.ops.op_fetch_tarball(url, name ?? null, sha256 ?? null); const context: NixStringContext = new Set(); - addOpaqueContext(context, result.store_path); - return mkStringWithContext(result.store_path, context); + addOpaqueContext(context, result.storePath); + return mkStringWithContext(result.storePath, context); }; export const fetchGit = (args: NixValue): NixAttrs => { @@ -173,16 +172,16 @@ export const fetchGit = (args: NixValue): NixAttrs => { const url = coerceToString(forced, StringCoercionMode.Base, false, disposedContext); const result = Deno.core.ops.op_fetch_git(url, null, null, false, false, false, null); const outContext: NixStringContext = new Set(); - addOpaqueContext(outContext, result.out_path); + addOpaqueContext(outContext, result.outPath); return new Map([ - ["outPath", mkStringWithContext(result.out_path, outContext)], + ["outPath", mkStringWithContext(result.outPath, outContext)], ["rev", result.rev], - ["shortRev", result.short_rev], - ["revCount", BigInt(result.rev_count)], - ["lastModified", BigInt(result.last_modified)], - ["lastModifiedDate", result.last_modified_date], + ["shortRev", result.shortRev], + ["revCount", BigInt(result.revCount)], + ["lastModified", BigInt(result.lastModified)], + ["lastModifiedDate", result.lastModifiedDate], ["submodules", result.submodules], - ["narHash", result.nar_hash], + ["narHash", result.narHash], ]); } const attrs = forceAttrs(args); @@ -205,16 +204,16 @@ export const fetchGit = (args: NixValue): NixAttrs => { ); const outContext: NixStringContext = new Set(); - addOpaqueContext(outContext, result.out_path); + addOpaqueContext(outContext, result.outPath); return new Map([ - ["outPath", mkStringWithContext(result.out_path, outContext)], + ["outPath", mkStringWithContext(result.outPath, outContext)], ["rev", result.rev], - ["shortRev", result.short_rev], - ["revCount", BigInt(result.rev_count)], - ["lastModified", BigInt(result.last_modified)], - ["lastModifiedDate", result.last_modified_date], + ["shortRev", result.shortRev], + ["revCount", BigInt(result.revCount)], + ["lastModified", BigInt(result.lastModified)], + ["lastModifiedDate", result.lastModifiedDate], ["submodules", result.submodules], - ["narHash", result.nar_hash], + ["narHash", result.narHash], ]); }; @@ -307,12 +306,7 @@ const autoDetectAndFetch = (attrs: NixAttrs): NixAttrs => { export const readDir = (path: NixValue): NixAttrs => { const pathStr = realisePath(path); - const entries: Record = Deno.core.ops.op_read_dir(pathStr); - const result: NixAttrs = new Map(); - for (const [name, type] of Object.entries(entries)) { - result.set(name, type); - } - return result; + return Deno.core.ops.op_read_dir(pathStr); }; export const readFile = (path: NixValue): string => { diff --git a/nix-js/runtime-ts/src/helpers.ts b/nix-js/runtime-ts/src/helpers.ts index cafa473..3056cf0 100644 --- a/nix-js/runtime-ts/src/helpers.ts +++ b/nix-js/runtime-ts/src/helpers.ts @@ -305,7 +305,7 @@ export const assert = (assertion: NixValue, expr: NixValue, assertionRaw: string }; export const mkPos = (span: number): NixAttrs => { - return new Map(Object.entries(Deno.core.ops.op_decode_span(span))); + return Deno.core.ops.op_decode_span(span); }; interface WithScope { diff --git a/nix-js/runtime-ts/src/types/global.d.ts b/nix-js/runtime-ts/src/types/global.d.ts index 2ff239d..3933e82 100644 --- a/nix-js/runtime-ts/src/types/global.d.ts +++ b/nix-js/runtime-ts/src/types/global.d.ts @@ -15,7 +15,7 @@ import type { import type { op } from "../operators"; import type { createThunk, force } from "../thunk"; import type { forceBool } from "../type-assert"; -import type { mkAttrs, mkFunction } from "../types"; +import type { mkAttrs, mkFunction, NixAttrs } from "../types"; declare global { var Nix: NixRuntime; @@ -55,7 +55,7 @@ declare global { function op_read_file(path: string): string; function op_read_file_type(path: string): string; - function op_read_dir(path: string): Record; + function op_read_dir(path: string): Map; function op_path_exists(path: string): boolean; function op_walk_dir(path: string): [string, string][]; @@ -81,11 +81,7 @@ declare global { includePaths: string[], ): string; - function op_decode_span(span: number): { - file: string | null; - line: number | null; - column: number | null; - }; + function op_decode_span(span: number): NixAttrs; function op_to_file(name: string, contents: string, references: string[]): string; @@ -100,16 +96,16 @@ declare global { function op_from_toml(toml: string): unknown; function op_to_xml(e: NixValue): [string, string[]]; - function op_finalize_derivation(input: { - name: string; - builder: string; - platform: string; - outputs: string[]; - args: string[]; - env: [string, string][]; - context: string[]; - fixedOutput: { hashAlgo: string; hash: string; hashMode: string } | null; - }): { drvPath: string; outputs: [string, string][] }; + function op_finalize_derivation( + name: string, + builder: string, + platform: string, + outputs: string[], + args: string[], + env: [string, string][], + context: string[], + fixedOutput: { hashAlgo: string; hash: string; hashMode: string } | null, + ): { drvPath: string; outputs: [string, string][] }; function op_fetch_url( url: string, diff --git a/nix-js/src/fetcher.rs b/nix-js/src/fetcher.rs index 5ed3a9b..8fc7759 100644 --- a/nix-js/src/fetcher.rs +++ b/nix-js/src/fetcher.rs @@ -1,8 +1,8 @@ use deno_core::OpState; +use deno_core::ToV8; use deno_core::op2; use nix_compat::nixhash::HashAlgo; use nix_compat::nixhash::NixHash; -use serde::Serialize; use tracing::{debug, info, warn}; use crate::runtime::OpStateExt; @@ -22,19 +22,19 @@ pub use metadata_cache::MetadataCache; use crate::nar; use crate::runtime::NixRuntimeError; -#[derive(Serialize)] +#[derive(ToV8)] pub struct FetchUrlResult { pub store_path: String, pub hash: String, } -#[derive(Serialize)] +#[derive(ToV8)] pub struct FetchTarballResult { pub store_path: String, pub nar_hash: String, } -#[derive(Serialize)] +#[derive(ToV8)] pub struct FetchGitResult { pub out_path: String, pub rev: String, @@ -47,7 +47,6 @@ pub struct FetchGitResult { } #[op2] -#[serde] pub fn op_fetch_url( state: &mut OpState, #[string] url: String, @@ -152,7 +151,6 @@ pub fn op_fetch_url( } #[op2] -#[serde] pub fn op_fetch_tarball( state: &mut OpState, #[string] url: String, @@ -266,7 +264,6 @@ pub fn op_fetch_tarball( } #[op2] -#[serde] pub fn op_fetch_git( state: &mut OpState, #[string] url: String, diff --git a/nix-js/src/runtime/ops.rs b/nix-js/src/runtime/ops.rs index 1b0e786..9303550 100644 --- a/nix-js/src/runtime/ops.rs +++ b/nix-js/src/runtime/ops.rs @@ -1,9 +1,11 @@ +use std::collections::BTreeMap; +use std::convert::Infallible; use std::path::{Component, Path, PathBuf}; use std::str::FromStr; -use hashbrown::hash_map::{Entry, HashMap}; +use hashbrown::{HashMap, HashSet, hash_map::Entry}; -use deno_core::{FromV8, OpState, v8}; +use deno_core::{FromV8, OpState, ToV8, v8}; use regex::Regex; use rust_embed::Embed; @@ -36,6 +38,26 @@ impl RegexCache { } } +pub(super) struct Map(HashMap); +impl<'a, K, V> ToV8<'a> for Map +where + K: ToV8<'a>, + K::Error: ToString, + V: ToV8<'a>, + V::Error: ToString, +{ + type Error = NixRuntimeError; + fn to_v8<'i>(self, scope: &mut v8::PinScope<'a, 'i>) -> Result> { + let map = v8::Map::new(scope); + for (k, v) in self.0 { + let k = k.to_v8(scope).map_err(|err| err.to_string())?; + let v = v.to_v8(scope).map_err(|err| err.to_string())?; + map.set(scope, k, v).ok_or("Failed to set V8 Map KV")?; + } + Ok(map.into()) + } +} + #[derive(Embed)] #[folder = "src/runtime/corepkgs"] pub(crate) struct CorePkgs; @@ -93,7 +115,7 @@ pub(super) fn op_import( pub(super) fn op_scoped_import( state: &mut OpState, #[string] path: String, - #[serde] scope: Vec, + #[scoped] scope: Vec, ) -> Result { let _span = tracing::info_span!("op_scoped_import", path = %path).entered(); let ctx: &mut Ctx = state.get_ctx_mut(); @@ -161,10 +183,7 @@ pub(super) fn op_read_file_type(#[string] path: String) -> Result { } #[deno_core::op2] -#[serde] -pub(super) fn op_read_dir( - #[string] path: String, -) -> Result> { +pub(super) fn op_read_dir(#[string] path: String) -> Result> { let path = Path::new(&path); if !path.is_dir() { @@ -174,7 +193,7 @@ pub(super) fn op_read_dir( let entries = std::fs::read_dir(path) .map_err(|e| format!("Failed to read directory {}: {}", path.display(), e))?; - let mut result = std::collections::HashMap::new(); + let mut result = HashMap::new(); for entry in entries { let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?; @@ -198,10 +217,10 @@ pub(super) fn op_read_dir( "unknown" }; - result.insert(file_name, type_str.to_string()); + result.insert(file_name, type_str); } - Ok(result) + Ok(Map(result)) } #[deno_core::op2] @@ -255,12 +274,28 @@ pub(super) fn op_make_placeholder(#[string] output: String) -> String { format!("/{}", encoded) } +enum StringOrU32 { + String(String), + U32(u32), +} +impl<'a> ToV8<'a> for StringOrU32 { + type Error = Infallible; + fn to_v8<'i>( + self, + scope: &mut v8::PinScope<'a, 'i>, + ) -> std::result::Result, Self::Error> { + match self { + Self::String(x) => x.to_v8(scope), + Self::U32(x) => x.to_v8(scope), + } + } +} + #[deno_core::op2] -#[serde] pub(super) fn op_decode_span( state: &mut OpState, #[smi] span_id: u32, -) -> Result { +) -> Map<&'static str, StringOrU32> { let ctx: &Ctx = state.get_ctx(); let (source_id, range) = ctx.get_span(span_id as usize); let source = ctx.get_source(source_id); @@ -268,11 +303,11 @@ pub(super) fn op_decode_span( let (line, column) = byte_offset_to_line_col(&source.src, start as usize); - Ok(serde_json::json!({ - "file": source.get_name(), - "line": line, - "column": column - })) + Map(HashMap::from([ + ("file", StringOrU32::String(source.get_name())), + ("line", StringOrU32::U32(line)), + ("column", StringOrU32::U32(column)), + ])) } fn byte_offset_to_line_col(content: &str, offset: usize) -> (u32, u32) { @@ -294,14 +329,18 @@ fn byte_offset_to_line_col(content: &str, offset: usize) -> (u32, u32) { (line, col) } -#[derive(serde::Serialize)] -pub(super) struct ParsedHash { - hex: String, - algo: String, +mod private { + use deno_core::ToV8; + + #[derive(ToV8)] + pub(super) struct ParsedHash { + pub(super) hex: String, + pub(super) algo: String, + } } +use private::*; #[deno_core::op2] -#[serde] pub(super) fn op_parse_hash( #[string] hash_str: String, #[string] algo: Option, @@ -433,7 +472,7 @@ pub(super) fn op_to_file( state: &mut OpState, #[string] name: String, #[string] contents: String, - #[serde] references: Vec, + #[scoped] references: Vec, ) -> Result { let ctx: &Ctx = state.get_ctx(); let store = ctx.get_store(); @@ -487,7 +526,6 @@ pub(super) fn op_get_env(#[string] key: String) -> Result { } #[deno_core::op2] -#[serde] pub(super) fn op_walk_dir(#[string] path: String) -> Result> { fn walk_recursive( base: &Path, @@ -549,7 +587,7 @@ pub(super) fn op_add_filtered_path( #[string] name: Option, recursive: bool, #[string] sha256: Option, - #[serde] include_paths: Vec, + #[scoped] include_paths: Vec, ) -> Result { use nix_compat::nixhash::{HashAlgo, NixHash}; use sha2::{Digest, Sha256}; @@ -648,7 +686,6 @@ pub(super) fn op_add_filtered_path( } #[deno_core::op2] -#[serde] pub(super) fn op_match( state: &mut OpState, #[string] regex: String, @@ -673,7 +710,6 @@ pub(super) fn op_match( } #[deno_core::op2] -#[serde] pub(super) fn op_split( state: &mut OpState, #[string] regex: String, @@ -709,13 +745,30 @@ pub(super) fn op_split( Ok(ret) } -#[derive(serde::Serialize)] -#[serde(untagged)] pub(super) enum SplitResult { Text(String), Captures(Vec>), } +impl<'a> ToV8<'a> for SplitResult { + type Error = Infallible; + fn to_v8<'i>( + self, + scope: &mut v8::PinScope<'a, 'i>, + ) -> std::result::Result, Self::Error> { + Ok(match self { + Self::Text(text) => { + let Ok(value) = text.to_v8(scope); + value + } + Self::Captures(captures) => { + let Ok(value) = captures.to_v8(scope); + value + } + }) + } +} + pub(super) enum NixJsonValue { Null, Bool(bool), @@ -836,34 +889,24 @@ pub(super) fn op_from_toml(#[string] toml_str: String) -> Result { toml_to_nix(parsed) } -#[derive(serde::Deserialize)] -pub(super) struct FixedOutputInput { - #[serde(rename = "hashAlgo")] - hash_algo: String, - hash: String, - #[serde(rename = "hashMode")] - hash_mode: String, -} +mod scope { + use deno_core::{FromV8, ToV8}; -#[derive(serde::Deserialize)] -pub(super) struct FinalizeDerivationInput { - name: String, - builder: String, - platform: String, - outputs: Vec, - args: Vec, - env: Vec<(String, String)>, - context: Vec, - #[serde(rename = "fixedOutput")] - fixed_output: Option, -} + #[derive(FromV8)] + pub(super) struct FixedOutputInput { + pub(super) hash_algo: String, + pub(super) hash: String, + pub(super) hash_mode: String, + } -#[derive(serde::Serialize)] -pub(super) struct FinalizeDerivationOutput { - #[serde(rename = "drvPath")] - drv_path: String, - outputs: Vec<(String, String)>, + #[derive(ToV8)] + pub(super) struct FinalizeDerivationOutput { + // renamed to `drvPath` automatically + pub(super) drv_path: String, + pub(super) outputs: Vec<(String, String)>, + } } +use scope::*; fn output_path_name(drv_name: &str, output: &str) -> String { if output == "out" { @@ -874,10 +917,16 @@ fn output_path_name(drv_name: &str, output: &str) -> String { } #[deno_core::op2] -#[serde] pub(super) fn op_finalize_derivation( state: &mut OpState, - #[serde] input: FinalizeDerivationInput, + #[string] name: String, + #[string] builder: String, + #[string] platform: String, + #[scoped] outputs: Vec, + #[scoped] args: Vec, + #[scoped] env: Vec<(String, String)>, + #[scoped] context: Vec, + #[scoped] fixed_output: Option, ) -> Result { use crate::derivation::{DerivationData, OutputInfo}; use crate::string_context::extract_input_drvs_and_srcs; @@ -887,15 +936,15 @@ pub(super) fn op_finalize_derivation( let store_dir = store.get_store_dir().to_string(); let (input_drvs, input_srcs) = - extract_input_drvs_and_srcs(&input.context).map_err(NixRuntimeError::from)?; + extract_input_drvs_and_srcs(&context).map_err(NixRuntimeError::from)?; - let env: std::collections::BTreeMap = input.env.into_iter().collect(); + let env: BTreeMap = env.into_iter().collect(); let drv_path; let output_paths: Vec<(String, String)>; - if let Some(fixed) = &input.fixed_output { - let path_name = output_path_name(&input.name, "out"); + if let Some(fixed) = &fixed_output { + let path_name = output_path_name(&name, "out"); let out_path = crate::runtime::ops::op_make_fixed_output_path_impl( &store_dir, &fixed.hash_algo, @@ -910,7 +959,7 @@ pub(super) fn op_finalize_derivation( "" }; - let mut final_outputs = std::collections::BTreeMap::new(); + let mut final_outputs = BTreeMap::new(); final_outputs.insert( "out".to_string(), OutputInfo { @@ -924,13 +973,13 @@ pub(super) fn op_finalize_derivation( final_env.insert("out".to_string(), out_path.clone()); let drv = DerivationData { - name: input.name.clone(), + name: name.clone(), outputs: final_outputs, input_drvs: input_drvs.clone(), input_srcs: input_srcs.clone(), - platform: input.platform, - builder: input.builder, - args: input.args, + platform, + builder, + args, env: final_env, }; @@ -938,7 +987,7 @@ pub(super) fn op_finalize_derivation( let references = drv.collect_references(); drv_path = store - .add_text_to_store(&format!("{}.drv", input.name), &final_aterm, references) + .add_text_to_store(&format!("{}.drv", name), &final_aterm, references) .map_err(|e| NixRuntimeError::from(format!("failed to write derivation: {}", e)))?; let fixed_hash_fingerprint = format!( @@ -952,8 +1001,7 @@ pub(super) fn op_finalize_derivation( output_paths = vec![("out".to_string(), out_path)]; } else { - let masked_outputs: std::collections::BTreeMap = input - .outputs + let masked_outputs: std::collections::BTreeMap = outputs .iter() .map(|o| { ( @@ -968,18 +1016,18 @@ pub(super) fn op_finalize_derivation( .collect(); let mut masked_env = env.clone(); - for output in &input.outputs { + for output in &outputs { masked_env.insert(output.clone(), String::new()); } let masked_drv = DerivationData { - name: input.name.clone(), + name: name.clone(), outputs: masked_outputs, input_drvs: input_drvs.clone(), input_srcs: input_srcs.clone(), - platform: input.platform.clone(), - builder: input.builder.clone(), - args: input.args.clone(), + platform: platform.clone(), + builder: builder.clone(), + args: args.clone(), env: masked_env, }; @@ -1007,8 +1055,8 @@ pub(super) fn op_finalize_derivation( let mut final_env = env; let mut result_output_paths = Vec::new(); - for output_name in &input.outputs { - let path_name = output_path_name(&input.name, output_name); + for output_name in &outputs { + let path_name = output_path_name(&name, output_name); let out_path = crate::nix_utils::make_store_path( &store_dir, &format!("output:{}", output_name), @@ -1028,13 +1076,13 @@ pub(super) fn op_finalize_derivation( } let final_drv = DerivationData { - name: input.name, + name, outputs: final_outputs, input_drvs, input_srcs, - platform: input.platform, - builder: input.builder, - args: input.args, + platform, + builder, + args, env: final_env, }; @@ -1161,21 +1209,21 @@ pub(super) fn op_hash_file(#[string] algo: String, #[string] path: String) -> Re #[deno_core::op2] #[string] -pub(super) fn op_convert_hash(#[serde] input: ConvertHashInput) -> Result { +pub(super) fn op_convert_hash( + #[string] hash: &str, + #[string] algo: Option, + #[string] format: &str, +) -> Result { use nix_compat::nixhash::{HashAlgo, NixHash}; - let hash_algo = input - .hash_algo - .as_deref() - .and_then(|a| HashAlgo::from_str(a).ok()); + let hash_algo = algo.as_deref().and_then(|a| HashAlgo::from_str(a).ok()); - let hash = NixHash::from_str(&input.hash, hash_algo).map_err(|e| { - NixRuntimeError::from(format!("cannot convert hash '{}': {}", input.hash, e)) - })?; + let hash = NixHash::from_str(hash, hash_algo) + .map_err(|e| NixRuntimeError::from(format!("cannot convert hash '{}': {}", hash, e)))?; let bytes = hash.digest_as_bytes(); - match input.to_format.as_str() { + match format { "base16" => Ok(hex::encode(bytes)), "nix32" | "base32" => Ok(nix_compat::nixbase32::encode(bytes)), "base64" => { @@ -1188,20 +1236,11 @@ pub(super) fn op_convert_hash(#[serde] input: ConvertHashInput) -> Result Err(NixRuntimeError::from(format!( "unknown hash format '{}'", - input.to_format + format ))), } } -#[derive(serde::Deserialize)] -pub(super) struct ConvertHashInput { - hash: String, - #[serde(rename = "hashAlgo")] - hash_algo: Option, - #[serde(rename = "toHashFormat")] - to_format: String, -} - struct XmlCtx<'s> { force_fn: v8::Local<'s, v8::Function>, is_thunk: v8::Local<'s, v8::Symbol>, @@ -1243,7 +1282,7 @@ impl<'s> XmlCtx<'s> { struct XmlWriter { buf: String, context: Vec, - drvs_seen: hashbrown::HashSet, + drvs_seen: HashSet, } impl XmlWriter { @@ -1251,7 +1290,7 @@ impl XmlWriter { Self { buf: String::with_capacity(4096), context: Vec::new(), - drvs_seen: hashbrown::HashSet::new(), + drvs_seen: HashSet::new(), } } @@ -1742,7 +1781,6 @@ impl<'a> FromV8<'a> for ToXmlResult { } #[deno_core::op2] -#[serde] pub(super) fn op_to_xml(#[scoped] value: ToXmlResult) -> (String, Vec) { (value.xml, value.context) }