Skip to main content

microvms_protocol/
discovery.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Additive discovery, separate from the published exhaustive health response.
3
4use schemars::{JsonSchema, Schema, SchemaGenerator};
5use serde::{Deserialize, Serialize};
6
7/// `GET /v1/discovery`: authenticated image configuration, not resolved identity.
8///
9/// A missing route means an older daemon; a present response with `default_user:
10/// null` means no image default. Resolution still happens for each exec request.
11#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
12#[non_exhaustive]
13pub struct Discovery {
14    /// The image's explicit default or captured numeric uid:gid, or `None` for root.
15    // A custom reader makes absence an error while retaining explicit null. The
16    // schema function likewise makes the property required while keeping it nullable.
17    #[serde(deserialize_with = "Option::<String>::deserialize")]
18    #[schemars(schema_with = "optional_user_schema")]
19    pub default_user: Option<String>,
20}
21
22impl Discovery {
23    /// Reports the configured value without resolving or validating a passwd row.
24    pub fn new(default_user: Option<String>) -> Self {
25        Self { default_user }
26    }
27}
28
29fn optional_user_schema(generator: &mut SchemaGenerator) -> Schema {
30    generator.subschema_for::<Option<String>>()
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use serde_json::json;
37
38    #[test]
39    fn discovery_schema_requires_a_nullable_string_default() {
40        let schema = serde_json::to_value(schemars::schema_for!(Discovery)).unwrap();
41        assert_eq!(schema["required"], json!(["default_user"]));
42        assert_eq!(
43            schema["properties"]["default_user"]["type"],
44            json!(["string", "null"])
45        );
46        assert_ne!(schema["additionalProperties"], false);
47
48        // Future fields remain readable without weakening this field's value contract.
49        let discovery: Discovery = serde_json::from_value(json!({
50            "default_user": null,
51            "future_field": true
52        }))
53        .unwrap();
54        assert_eq!(discovery.default_user, None);
55        for invalid in [json!(true), json!(42), json!([]), json!({})] {
56            assert!(
57                serde_json::from_value::<Discovery>(json!({ "default_user": invalid })).is_err()
58            );
59        }
60    }
61}