Skip to content

Commit 58022d1

Browse files
authored
feat(system): Print the full static configuration in the project. (#817)
This allows to use the configuration in actions against a project, for example reading the watching custom routes and using them to configure an API Gateway.
1 parent 9537f8d commit 58022d1

File tree

10 files changed

+720
-71
lines changed

10 files changed

+720
-71
lines changed

Cargo.lock

Lines changed: 302 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cargo-lambda-build/src/zig.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use miette::{IntoDiagnostic, Result};
99
pub fn print_install_options(options: &[InstallOption]) {
1010
println!("Zig is not installed in your system.");
1111
if is_stdin_tty() {
12-
println!("Run `cargo lambda system --setup` to install Zig.")
12+
println!("Run `cargo lambda system --install-zig` to install Zig.")
1313
}
1414

1515
if !options.is_empty() {
@@ -51,6 +51,7 @@ pub async fn check_installation() -> Result<()> {
5151
install_zig(options).await
5252
}
5353

54+
#[derive(Debug)]
5455
pub enum InstallOption {
5556
#[cfg(not(windows))]
5657
Brew,
@@ -63,6 +64,15 @@ pub enum InstallOption {
6364
Scoop,
6465
}
6566

67+
impl serde::Serialize for InstallOption {
68+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
69+
where
70+
S: serde::Serializer,
71+
{
72+
serializer.serialize_str(self.usage())
73+
}
74+
}
75+
6676
impl std::fmt::Display for InstallOption {
6777
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6878
match self {

crates/cargo-lambda-cli/src/main.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ impl LambdaSubcommand {
129129
Self::Init(mut i) => i.run().await,
130130
Self::Invoke(i) => i.run().await,
131131
Self::New(mut n) => n.run().await,
132-
Self::System(s) => s.run().await,
132+
Self::System(s) => Self::run_system(s, global, context, admerge).await,
133133
Self::Watch(w) => Self::run_watch(w, color, global, context, admerge).await,
134134
}
135135
}
@@ -206,6 +206,23 @@ impl LambdaSubcommand {
206206

207207
cargo_lambda_deploy::run(&deploy, &metadata).await
208208
}
209+
210+
async fn run_system(
211+
system: System,
212+
global: Option<PathBuf>,
213+
context: Option<String>,
214+
admerge: bool,
215+
) -> Result<()> {
216+
let metadata = load_metadata(system.manifest_path())?;
217+
218+
let options = ConfigOptions {
219+
global,
220+
context,
221+
admerge,
222+
name: system.package(),
223+
};
224+
cargo_lambda_system::run(&system, &metadata, &options).await
225+
}
209226
}
210227

211228
fn print_version() -> Result<()> {

crates/cargo-lambda-metadata/src/cargo/deploy.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ use std::{collections::HashMap, fmt::Debug, path::PathBuf};
88
use strum_macros::{Display, EnumString};
99

1010
use crate::{
11-
cargo::deserialize_vec_or_map,
1211
env::EnvOptions,
1312
error::MetadataError,
1413
lambda::{Memory, MemoryValueParser, Timeout, Tracing},
1514
};
1615

16+
use crate::cargo::deserialize_vec_or_map;
17+
1718
const DEFAULT_MANIFEST_PATH: &str = "Cargo.toml";
1819
const DEFAULT_COMPATIBLE_RUNTIMES: &str = "provided.al2,provided.al2023";
1920
const DEFAULT_RUNTIME: &str = "provided.al2023";
@@ -452,10 +453,14 @@ pub struct VpcConfig {
452453

453454
/// Allow outbound IPv6 traffic on VPC functions that are connected to dual-stack subnets
454455
#[arg(long)]
455-
#[serde(default)]
456+
#[serde(default, skip_serializing_if = "is_false")]
456457
pub ipv6_allowed_for_dual_stack: bool,
457458
}
458459

460+
fn is_false(b: &bool) -> bool {
461+
!b
462+
}
463+
459464
impl VpcConfig {
460465
fn count_fields(&self) -> usize {
461466
self.subnet_ids.is_some() as usize
@@ -512,7 +517,7 @@ mod tests {
512517
use crate::{
513518
cargo::load_metadata,
514519
config::{ConfigOptions, load_config_without_cli_flags},
515-
lambda::{Memory, Timeout},
520+
lambda::Timeout,
516521
tests::fixture_metadata,
517522
};
518523

crates/cargo-lambda-metadata/src/cargo/watch.rs

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ pub struct WatchConfig {
228228
#[derive(Clone, Debug, Default)]
229229
pub struct FunctionRouter {
230230
inner: Router<FunctionRoutes>,
231-
pub(crate) raw: Vec<(String, FunctionRoutes)>,
231+
pub(crate) raw: Vec<Route>,
232232
}
233233

234234
impl FunctionRouter {
@@ -254,6 +254,14 @@ impl FunctionRouter {
254254
}
255255
}
256256

257+
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
258+
pub struct Route {
259+
path: String,
260+
#[serde(skip_serializing_if = "Option::is_none")]
261+
methods: Option<Vec<String>>,
262+
function: String,
263+
}
264+
257265
#[derive(Clone, Debug, PartialEq)]
258266
pub enum FunctionRoutes {
259267
Single(String),
@@ -284,27 +292,73 @@ impl<'de> Visitor<'de> for FunctionRouterVisitor {
284292
{
285293
let routes: HashMap<String, FunctionRoutes> =
286294
Deserialize::deserialize(serde::de::value::MapAccessDeserializer::new(map))?;
295+
287296
let mut inner = Router::new();
297+
let mut raw = Vec::new();
298+
299+
let mut inverse = HashMap::new();
288300

289301
for (path, route) in &routes {
290302
inner.insert(path, route.clone()).map_err(|e| {
291303
serde::de::Error::custom(format!("Failed to insert route {path}: {e}"))
292304
})?;
305+
306+
match route {
307+
FunctionRoutes::Single(function) => {
308+
raw.push(Route {
309+
path: path.clone(),
310+
methods: None,
311+
function: function.clone(),
312+
});
313+
}
314+
FunctionRoutes::Multiple(routes) => {
315+
for (method, function) in routes {
316+
inverse
317+
.entry((path.clone(), function.clone()))
318+
.and_modify(|route: &mut Route| {
319+
let mut methods = route.methods.clone().unwrap_or_default();
320+
methods.push(method.clone());
321+
route.methods = Some(methods);
322+
})
323+
.or_insert_with(|| Route {
324+
path: path.clone(),
325+
methods: Some(vec![method.clone()]),
326+
function: function.clone(),
327+
});
328+
}
329+
}
330+
}
331+
}
332+
333+
for (_, route) in inverse {
334+
raw.push(route);
293335
}
294336

295-
let raw: Vec<(String, FunctionRoutes)> = routes.into_iter().collect();
296337
Ok(FunctionRouter { inner, raw })
297338
}
298339

299340
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
300341
where
301342
A: serde::de::SeqAccess<'de>,
302343
{
303-
let raw: Vec<(String, FunctionRoutes)> =
344+
let routes: Vec<Route> =
304345
Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))?;
346+
305347
let mut inner = Router::new();
348+
let mut raw = Vec::new();
349+
350+
let mut routes_by_path = HashMap::new();
351+
352+
for route in &routes {
353+
routes_by_path
354+
.entry(route.path.clone())
355+
.and_modify(|routes| merge_routes(routes, route))
356+
.or_insert_with(|| decode_route(route));
357+
358+
raw.push(route.clone());
359+
}
306360

307-
for (path, route) in &raw {
361+
for (path, route) in &routes_by_path {
308362
inner.insert(path, route.clone()).map_err(|e| {
309363
serde::de::Error::custom(format!("Failed to insert route {path}: {e}"))
310364
})?;
@@ -314,6 +368,43 @@ impl<'de> Visitor<'de> for FunctionRouterVisitor {
314368
}
315369
}
316370

371+
fn merge_routes(routes: &mut FunctionRoutes, route: &Route) {
372+
let methods = route.methods.clone().unwrap_or_default();
373+
match routes {
374+
FunctionRoutes::Single(function) if !methods.is_empty() => {
375+
let mut tmp = HashMap::new();
376+
for method in methods {
377+
tmp.insert(method.clone(), function.clone());
378+
}
379+
*routes = FunctionRoutes::Multiple(tmp);
380+
}
381+
FunctionRoutes::Multiple(_) if methods.is_empty() => {
382+
*routes = FunctionRoutes::Single(route.function.clone());
383+
}
384+
FunctionRoutes::Multiple(routes) => {
385+
for method in methods {
386+
routes.insert(method.clone(), route.function.clone());
387+
}
388+
}
389+
FunctionRoutes::Single(_) => {
390+
*routes = FunctionRoutes::Single(route.function.clone());
391+
}
392+
}
393+
}
394+
395+
fn decode_route(route: &Route) -> FunctionRoutes {
396+
match &route.methods {
397+
Some(methods) => {
398+
let mut routes = HashMap::new();
399+
for method in methods {
400+
routes.insert(method.clone(), route.function.clone());
401+
}
402+
FunctionRoutes::Multiple(routes)
403+
}
404+
None => FunctionRoutes::Single(route.function.clone()),
405+
}
406+
}
407+
317408
impl<'de> Deserialize<'de> for FunctionRouter {
318409
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
319410
where

crates/cargo-lambda-metadata/src/config.rs

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,41 @@ pub fn load_config_without_cli_flags(
7474
}
7575

7676
fn figment_from_metadata(metadata: &CargoMetadata, options: &ConfigOptions) -> Result<Figment> {
77+
let (bin_metadata, package_metadata, mut figment) = general_config_figment(metadata, options)?;
78+
79+
if let Some(bin_metadata) = bin_metadata {
80+
let mut bin_serialized = Serialized::defaults(bin_metadata);
81+
if let Some(context) = &options.context {
82+
bin_serialized = bin_serialized.profile(context);
83+
}
84+
85+
if options.admerge {
86+
figment = figment.admerge(bin_serialized);
87+
} else {
88+
figment = figment.merge(bin_serialized);
89+
}
90+
}
91+
92+
if let Some(package_metadata) = package_metadata {
93+
let mut package_serialized = Serialized::defaults(package_metadata);
94+
if let Some(context) = &options.context {
95+
package_serialized = package_serialized.profile(context);
96+
}
97+
98+
if options.admerge {
99+
figment = figment.admerge(package_serialized);
100+
} else {
101+
figment = figment.merge(package_serialized);
102+
}
103+
}
104+
105+
Ok(figment)
106+
}
107+
108+
pub fn general_config_figment(
109+
metadata: &CargoMetadata,
110+
options: &ConfigOptions,
111+
) -> Result<(Option<Config>, Option<Config>, Figment)> {
77112
let (ws_metadata, bin_metadata) = workspace_metadata(metadata, options.name.as_deref())?;
78113
let package_metadata = package_metadata(metadata, options.name.as_deref())?;
79114

@@ -82,6 +117,7 @@ fn figment_from_metadata(metadata: &CargoMetadata, options: &ConfigOptions) -> R
82117
.as_ref()
83118
.map(Toml::file)
84119
.unwrap_or_else(|| Toml::file("CargoLambda.toml"));
120+
85121
if options.context.is_some() {
86122
config_file = config_file.nested()
87123
}
@@ -95,8 +131,8 @@ fn figment_from_metadata(metadata: &CargoMetadata, options: &ConfigOptions) -> R
95131
if let Some(context) = &options.context {
96132
env_serialized = env_serialized.profile(context);
97133
}
98-
figment = figment.merge(env_serialized);
99134

135+
figment = figment.merge(env_serialized);
100136
figment = if options.admerge {
101137
figment.admerge(config_file)
102138
} else {
@@ -107,39 +143,14 @@ fn figment_from_metadata(metadata: &CargoMetadata, options: &ConfigOptions) -> R
107143
if let Some(context) = &options.context {
108144
ws_serialized = ws_serialized.profile(context);
109145
}
146+
110147
if options.admerge {
111148
figment = figment.admerge(ws_serialized);
112149
} else {
113150
figment = figment.merge(ws_serialized);
114151
}
115152

116-
if let Some(bin_metadata) = bin_metadata {
117-
let mut bin_serialized = Serialized::defaults(bin_metadata);
118-
if let Some(context) = &options.context {
119-
bin_serialized = bin_serialized.profile(context);
120-
}
121-
122-
if options.admerge {
123-
figment = figment.admerge(bin_serialized);
124-
} else {
125-
figment = figment.merge(bin_serialized);
126-
}
127-
}
128-
129-
if let Some(package_metadata) = package_metadata {
130-
let mut package_serialized = Serialized::defaults(package_metadata);
131-
if let Some(context) = &options.context {
132-
package_serialized = package_serialized.profile(context);
133-
}
134-
135-
if options.admerge {
136-
figment = figment.admerge(package_serialized);
137-
} else {
138-
figment = figment.merge(package_serialized);
139-
}
140-
}
141-
142-
Ok(figment)
153+
Ok((bin_metadata, package_metadata, figment))
143154
}
144155

145156
fn workspace_metadata(
@@ -215,6 +226,26 @@ fn get_config_from_packages(
215226
Ok(None)
216227
}
217228

229+
pub fn get_config_from_all_packages(metadata: &CargoMetadata) -> Result<HashMap<String, Config>> {
230+
let kind_condition = |pkg: &Package, target: &Target| {
231+
target.kind.iter().any(|kind| kind == "bin") && pkg.metadata.is_object()
232+
};
233+
234+
let mut configs = HashMap::new();
235+
for pkg in &metadata.packages {
236+
for target in &pkg.targets {
237+
if kind_condition(pkg, target) {
238+
let meta: Metadata =
239+
serde_json::from_value(pkg.metadata.clone()).into_diagnostic()?;
240+
241+
configs.insert(pkg.name.clone(), meta.lambda.package.into());
242+
}
243+
}
244+
}
245+
246+
Ok(configs)
247+
}
248+
218249
fn get_config_from_root(metadata: &CargoMetadata) -> Result<Option<Config>> {
219250
let Some(root) = metadata.root_package() else {
220251
return Ok(None);

0 commit comments

Comments
 (0)