From 9e2ed78e32bc55fef230c29f2d5b3504566966dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 21 Jan 2025 13:21:57 +0000 Subject: [PATCH 1/2] docs(rust): describe the licenses API in OpenAPI * Additionally, move the "license" module to agama-lib. --- rust/Cargo.lock | 14 ++- rust/agama-lib/Cargo.toml | 2 + rust/agama-lib/src/software/model.rs | 107 ++---------------- .../src/software/model}/license.rs | 21 ++-- rust/agama-lib/src/software/model/packages.rs | 71 ++++++++++++ .../src/software/model/registration.rs | 70 ++++++++++++ rust/agama-server/src/software.rs | 1 - rust/agama-server/src/software/web.rs | 12 +- rust/agama-server/src/web/docs/software.rs | 13 ++- 9 files changed, 184 insertions(+), 127 deletions(-) rename rust/{agama-server/src/software => agama-lib/src/software/model}/license.rs (93%) create mode 100644 rust/agama-lib/src/software/model/packages.rs create mode 100644 rust/agama-lib/src/software/model/registration.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index d14e624000..3762917113 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -56,10 +56,12 @@ dependencies = [ "jsonschema", "jsonwebtoken", "log", + "regex", "reqwest 0.12.8", "serde", "serde_json", "serde_repr", + "serde_with", "strum", "tempfile", "thiserror", @@ -3078,9 +3080,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -3472,9 +3474,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.10.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9720086b3357bcb44fce40117d769a4d068c70ecfa190850a980a71755f66fcc" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" dependencies = [ "base64 0.22.1", "chrono", @@ -3490,9 +3492,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.10.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f1abbfe725f27678f4663bcacb75a83e829fd464c25d78dd038a3a29e307cec" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ "darling", "proc-macro2", diff --git a/rust/agama-lib/Cargo.toml b/rust/agama-lib/Cargo.toml index c004dd590b..439ec1b6ab 100644 --- a/rust/agama-lib/Cargo.toml +++ b/rust/agama-lib/Cargo.toml @@ -35,6 +35,8 @@ chrono = { version = "0.4.38", default-features = false, features = [ home = "0.5.9" strum = { version = "0.26.3", features = ["derive"] } fs_extra = "1.3.0" +serde_with = "3.12.0" +regex = "1.11.1" [dev-dependencies] httpmock = "0.7.0" diff --git a/rust/agama-lib/src/software/model.rs b/rust/agama-lib/src/software/model.rs index bb290803fd..0bca26c619 100644 --- a/rust/agama-lib/src/software/model.rs +++ b/rust/agama-lib/src/software/model.rs @@ -1,4 +1,4 @@ -// Copyright (c) [2024] SUSE LLC +// Copyright (c) [2025] SUSE LLC // // All Rights Reserved. // @@ -18,103 +18,10 @@ // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +mod license; +mod packages; +mod registration; -/// Software service configuration (product, patterns, etc.). -#[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)] -pub struct SoftwareConfig { - /// A map where the keys are the pattern names and the values whether to install them or not. - pub patterns: Option>, - /// Name of the product to install. - pub product: Option, -} - -/// Software service configuration (product, patterns, etc.). -#[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)] -pub struct RegistrationParams { - /// Registration key. - pub key: String, - /// Registration email. - pub email: String, -} - -/// Information about registration configuration (product, patterns, etc.). -#[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct RegistrationInfo { - /// Registration key. Empty value mean key not used or not registered. - pub key: String, - /// Registration email. Empty value mean email not used or not registered. - pub email: String, -} - -#[derive( - Clone, - Default, - Debug, - Serialize, - Deserialize, - strum::Display, - strum::EnumString, - utoipa::ToSchema, -)] -#[strum(serialize_all = "camelCase")] -#[serde(rename_all = "camelCase")] -pub enum RegistrationRequirement { - /// Product does not require registration - #[default] - No = 0, - /// Product has optional registration - Optional = 1, - /// It is mandatory to register the product - Mandatory = 2, -} - -#[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)] -pub struct RegistrationError { - /// ID of error. See dbus API for possible values - pub id: u32, - /// human readable error string intended to be displayed to user - pub message: String, -} - -/// Software resolvable type (package or pattern). -#[derive(Deserialize, Serialize, strum::Display, utoipa::ToSchema)] -#[strum(serialize_all = "camelCase")] -#[serde(rename_all = "camelCase")] -pub enum ResolvableType { - Package = 0, - Pattern = 1, -} - -/// Resolvable list specification. -#[derive(Deserialize, Serialize, utoipa::ToSchema)] -pub struct ResolvableParams { - /// List of resolvables. - pub names: Vec, - /// Resolvable type. - pub r#type: ResolvableType, - /// Whether the resolvables are optional or not. - pub optional: bool, -} - -/// Repository list specification. -#[derive(Deserialize, Serialize, utoipa::ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct Repository { - /// repository identifier - pub id: i32, - /// repository alias. Has to be unique - pub alias: String, - /// repository name - pub name: String, - /// Repository url (raw format without expanded variables) - pub url: String, - /// product directory (currently not used, valid only for multiproduct DVDs) - pub product_dir: String, - /// Whether the repository is enabled - pub enabled: bool, - /// Whether the repository is loaded - pub loaded: bool, -} +pub use license::*; +pub use packages::*; +pub use registration::*; diff --git a/rust/agama-server/src/software/license.rs b/rust/agama-lib/src/software/model/license.rs similarity index 93% rename from rust/agama-server/src/software/license.rs rename to rust/agama-lib/src/software/model/license.rs index 1d769a4f7d..f33d6c07a8 100644 --- a/rust/agama-server/src/software/license.rs +++ b/rust/agama-lib/src/software/model/license.rs @@ -1,4 +1,4 @@ -// Copyright (c) [2024] SUSE LLC +// Copyright (c) [2024-2025] SUSE LLC // // All Rights Reserved. // @@ -34,9 +34,11 @@ use thiserror::Error; /// /// It contains the license ID and the list of languages that with a translation. #[serde_as] -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Serialize, utoipa::ToSchema)] pub struct License { + /// License ID. pub id: String, + /// Languages in which the license is translated. #[serde_as(as = "Vec")] pub languages: Vec, } @@ -46,9 +48,11 @@ pub struct License { /// It contains the license ID and the body. /// /// TODO: in the future it might contain a title, extracted from the text. -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Serialize, utoipa::ToSchema)] pub struct LicenseContent { + /// License ID. pub id: String, + /// License text. pub body: String, } @@ -106,13 +110,13 @@ impl LicensesRepo { } candidates.push(format!("license.{}.txt", language.language)); candidates.push("license.txt".to_string()); - tracing::info!("Searching for license: {:?}", &candidates); + log::info!("Searching for license: {:?}", &candidates); let license_path = candidates .into_iter() .map(|p| self.path.join(id).join(p)) .find(|p| p.exists())?; - tracing::info!("Reading license from {}", &license_path.display()); + log::info!("Reading license from {}", &license_path.display()); let body: String = std::fs::read_to_string(license_path).ok()?; @@ -146,7 +150,7 @@ impl LicensesRepo { /// The language is inferred from the file name (e.g., "es-ES" for license.es_ES.txt"). fn language_tag_from_file(name: &str) -> Option { if !name.starts_with("license") { - tracing::warn!("Unexpected file in the licenses directory: {}", &name); + log::warn!("Unexpected file in the licenses directory: {}", &name); return None; } let mut parts = name.split("."); @@ -175,7 +179,7 @@ impl Default for LicensesRepo { /// Simplified representation of the RFC 5646 language code. /// /// It only considers xx and xx-XX formats. -#[derive(Clone, Debug, Serialize, PartialEq)] +#[derive(Clone, Debug, Serialize, PartialEq, utoipa::ToSchema)] pub struct LanguageTag { // ISO-639 pub language: String, @@ -225,8 +229,7 @@ impl TryFrom<&str> for LanguageTag { #[cfg(test)] mod test { - use super::LicensesRepo; - use crate::software::license::LanguageTag; + use super::{LanguageTag, LicensesRepo}; use std::path::Path; fn build_repo() -> LicensesRepo { diff --git a/rust/agama-lib/src/software/model/packages.rs b/rust/agama-lib/src/software/model/packages.rs new file mode 100644 index 0000000000..f402c292a2 --- /dev/null +++ b/rust/agama-lib/src/software/model/packages.rs @@ -0,0 +1,71 @@ +// Copyright (c) [2025] SUSE LLC +// +// All Rights Reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, contact SUSE LLC. +// +// To contact SUSE LLC about this file by physical or electronic mail, you may +// find current contact information at www.suse.com. + +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Software service configuration (product, patterns, etc.). +#[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)] +pub struct SoftwareConfig { + /// A map where the keys are the pattern names and the values whether to install them or not. + pub patterns: Option>, + /// Name of the product to install. + pub product: Option, +} + +/// Software resolvable type (package or pattern). +#[derive(Deserialize, Serialize, strum::Display, utoipa::ToSchema)] +#[strum(serialize_all = "camelCase")] +#[serde(rename_all = "camelCase")] +pub enum ResolvableType { + Package = 0, + Pattern = 1, +} + +/// Resolvable list specification. +#[derive(Deserialize, Serialize, utoipa::ToSchema)] +pub struct ResolvableParams { + /// List of resolvables. + pub names: Vec, + /// Resolvable type. + pub r#type: ResolvableType, + /// Whether the resolvables are optional or not. + pub optional: bool, +} + +/// Repository list specification. +#[derive(Deserialize, Serialize, utoipa::ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct Repository { + /// repository identifier + pub id: i32, + /// repository alias. Has to be unique + pub alias: String, + /// repository name + pub name: String, + /// Repository url (raw format without expanded variables) + pub url: String, + /// product directory (currently not used, valid only for multiproduct DVDs) + pub product_dir: String, + /// Whether the repository is enabled + pub enabled: bool, + /// Whether the repository is loaded + pub loaded: bool, +} diff --git a/rust/agama-lib/src/software/model/registration.rs b/rust/agama-lib/src/software/model/registration.rs new file mode 100644 index 0000000000..8f2e143b63 --- /dev/null +++ b/rust/agama-lib/src/software/model/registration.rs @@ -0,0 +1,70 @@ +// Copyright (c) [2025] SUSE LLC +// +// All Rights Reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, contact SUSE LLC. +// +// To contact SUSE LLC about this file by physical or electronic mail, you may +// find current contact information at www.suse.com. + +use serde::{Deserialize, Serialize}; + +/// Software service configuration (product, patterns, etc.). +#[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)] +pub struct RegistrationParams { + /// Registration key. + pub key: String, + /// Registration email. + pub email: String, +} + +/// Information about registration configuration (product, patterns, etc.). +#[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct RegistrationInfo { + /// Registration key. Empty value mean key not used or not registered. + pub key: String, + /// Registration email. Empty value mean email not used or not registered. + pub email: String, +} + +#[derive( + Clone, + Default, + Debug, + Serialize, + Deserialize, + strum::Display, + strum::EnumString, + utoipa::ToSchema, +)] +#[strum(serialize_all = "camelCase")] +#[serde(rename_all = "camelCase")] +pub enum RegistrationRequirement { + /// Product does not require registration + #[default] + No = 0, + /// Product has optional registration + Optional = 1, + /// It is mandatory to register the product + Mandatory = 2, +} + +#[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)] +pub struct RegistrationError { + /// ID of error. See dbus API for possible values + pub id: u32, + /// human readable error string intended to be displayed to user + pub message: String, +} diff --git a/rust/agama-server/src/software.rs b/rust/agama-server/src/software.rs index 6bdfb1e0d1..b363de6ad1 100644 --- a/rust/agama-server/src/software.rs +++ b/rust/agama-server/src/software.rs @@ -20,4 +20,3 @@ pub mod web; pub use web::{software_service, software_streams}; -mod license; diff --git a/rust/agama-server/src/software/web.rs b/rust/agama-server/src/software/web.rs index 99c7e95d22..92d94e4bf2 100644 --- a/rust/agama-server/src/software/web.rs +++ b/rust/agama-server/src/software/web.rs @@ -27,7 +27,6 @@ use crate::{ error::Error, - software::license::LicensesRepo, web::{ common::{issues_router, progress_router, service_status_router, EventStreams}, Event, @@ -39,8 +38,8 @@ use agama_lib::{ product::{proxies::RegistrationProxy, Product, ProductClient}, software::{ model::{ - RegistrationError, RegistrationInfo, RegistrationParams, Repository, ResolvableParams, - SoftwareConfig, + License, LicenseContent, LicensesRepo, RegistrationError, RegistrationInfo, + RegistrationParams, Repository, ResolvableParams, SoftwareConfig, }, proxies::{Software1Proxy, SoftwareProductProxy}, Pattern, SelectedBy, SoftwareClient, UnknownSelectedBy, @@ -57,8 +56,6 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use tokio_stream::{Stream, StreamExt}; -use super::license::{License, LicenseContent}; - #[derive(Clone)] struct SoftwareState<'a> { product: ProductClient<'a>, @@ -501,7 +498,7 @@ async fn set_resolvables( path = "/licenses", context_path = "/api/software", responses( - (status = 200, description = "List of known licenses") + (status = 200, description = "List of known licenses", body = Vec) ) )] async fn licenses(State(state): State>) -> Result>, Error> { @@ -521,8 +518,9 @@ struct LicenseQuery { get, path = "/licenses/:id", context_path = "/api/software", + params(LicenseQuery), responses( - (status = 200, description = "License with the given ID"), + (status = 200, description = "License with the given ID", body = LicenseContent), (status = 400, description = "The specified language tag is not valid"), (status = 404, description = "There is not license with the given ID") ) diff --git a/rust/agama-server/src/web/docs/software.rs b/rust/agama-server/src/web/docs/software.rs index e6155cffd0..0062b56295 100644 --- a/rust/agama-server/src/web/docs/software.rs +++ b/rust/agama-server/src/web/docs/software.rs @@ -37,12 +37,14 @@ impl ApiDocBuilder for SoftwareApiDocBuilder { .path_from::() .path_from::() .path_from::() + .path_from::() + .path_from::() .path_from::() .path_from::() .path_from::() .path_from::() - .path_from::() .path_from::() + .path_from::() .path_from::() .path_from::() .build() @@ -53,14 +55,17 @@ impl ApiDocBuilder for SoftwareApiDocBuilder { .schema_from::() .schema_from::() .schema_from::() + .schema_from::() + .schema_from::() + .schema_from::() + .schema_from::() + .schema_from::() .schema_from::() .schema_from::() - .schema_from::() + .schema_from::() .schema_from::() .schema_from::() - .schema_from::() .schema_from::() - .schema_from::() .schema_from::() .schema_from::() .build() From 5de1ead4624bace6d6ece6a7de3419e1fe86336e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 21 Jan 2025 13:24:31 +0000 Subject: [PATCH 2/2] docs(rust): update changes file --- rust/package/agama.changes | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rust/package/agama.changes b/rust/package/agama.changes index 6c166b0533..d26e65f69b 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Tue Jan 21 13:24:07 UTC 2025 - Imobach Gonzalez Sosa + +- Describe licenses API in OpenAPI documentation + (gh#agama-project/agama#1929). + ------------------------------------------------------------------- Mon Jan 20 16:44:02 UTC 2025 - Ladislav Slezák