From da3cff5d5562513e26f15a646d501a3cadf45810 Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Wed, 10 Jul 2024 11:26:43 +0200 Subject: [PATCH 1/5] add post method to create new question --- rust/agama-server/src/questions/web.rs | 93 ++++++++++++++++++++------ 1 file changed, 73 insertions(+), 20 deletions(-) diff --git a/rust/agama-server/src/questions/web.rs b/rust/agama-server/src/questions/web.rs index 24e68658b1..5a0c90022e 100644 --- a/rust/agama-server/src/questions/web.rs +++ b/rust/agama-server/src/questions/web.rs @@ -8,7 +8,7 @@ use crate::{error::Error, web::Event}; use agama_lib::{ error::ServiceError, - proxies::{GenericQuestionProxy, QuestionWithPasswordProxy}, + proxies::{GenericQuestionProxy, QuestionWithPasswordProxy, Questions1Proxy}, }; use anyhow::Context; use axum::{ @@ -16,6 +16,7 @@ use axum::{ routing::{get, put}, Json, Router, }; +use regex::Regex; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, pin::Pin}; use tokio_stream::{Stream, StreamExt}; @@ -25,11 +26,12 @@ use zbus::{ zvariant::{ObjectPath, OwnedObjectPath}, }; -// TODO: move to lib +// TODO: move to lib or maybe not and just have in lib client for http API? #[derive(Clone)] struct QuestionsClient<'a> { connection: zbus::Connection, objects_proxy: ObjectManagerProxy<'a>, + questions_proxy: Questions1Proxy<'a>, } impl<'a> QuestionsClient<'a> { @@ -38,6 +40,7 @@ impl<'a> QuestionsClient<'a> { OwnedObjectPath::from(ObjectPath::try_from("/org/opensuse/Agama1/Questions")?); Ok(Self { connection: dbus.clone(), + questions_proxy: Questions1Proxy::new(&dbus).await?, objects_proxy: ObjectManagerProxy::builder(&dbus) .path(question_path)? .destination("org.opensuse.Agama1")? @@ -46,6 +49,46 @@ impl<'a> QuestionsClient<'a> { }) } + pub async fn create_question(&self, question: Question) -> Result { + // TODO: ugly API is caused by dbus method to create question. It can be changed in future as DBus is internal only API + let generic = &question.generic; + let options: Vec<&str> = generic.options.iter().map(String::as_ref).collect(); + let data: HashMap<&str, &str> = generic + .data + .iter() + .map(|(k, v)| (k.as_str(), v.as_str())) + .collect(); + let path = if question.with_password.is_some() { + self.questions_proxy + .new_with_password( + &generic.class, + &generic.text, + &options, + &generic.default_option, + data, + ) + .await? + } else { + self.questions_proxy + .new_question( + &generic.class, + &generic.text, + &options, + &generic.default_option, + data, + ) + .await? + }; + let mut res = question.clone(); + // we are sure that regexp is correct, so use unwrap + let id_matcher = Regex::new(r"/(?\d+)$").unwrap(); + let Some(id_cap) = id_matcher.captures(path.as_str()) else { + panic!("Id not in path") + }; // TODO: better error if path does not contain id + res.generic.id = id_cap["id"].parse::().unwrap(); + Ok(question) + } + pub async fn questions(&self) -> Result, ServiceError> { let objects = self .objects_proxy @@ -59,15 +102,15 @@ impl<'a> QuestionsClient<'a> { ); for (path, interfaces_hash) in objects.iter() { if interfaces_hash.contains_key(&password_interface) { - result.push(self.create_question_with_password(path).await?) + result.push(self.build_question_with_password(path).await?) } else { - result.push(self.create_generic_question(path).await?) + result.push(self.build_generic_question(path).await?) } } Ok(result) } - async fn create_generic_question( + async fn build_generic_question( &self, path: &OwnedObjectPath, ) -> Result { @@ -91,19 +134,12 @@ impl<'a> QuestionsClient<'a> { Ok(result) } - async fn create_question_with_password( + async fn build_question_with_password( &self, path: &OwnedObjectPath, ) -> Result { - let dbus_question = QuestionWithPasswordProxy::builder(&self.connection) - .path(path)? - .cache_properties(zbus::CacheProperties::No) - .build() - .await?; - let mut result = self.create_generic_question(path).await?; - result.with_password = Some(QuestionWithPassword { - password: dbus_question.password().await?, - }); + let mut result = self.build_generic_question(path).await?; + result.with_password = Some(QuestionWithPassword {}); Ok(result) } @@ -171,12 +207,13 @@ pub struct GenericQuestion { /// is that it is not composition as used here, but more like /// child of generic question and contain reference to Base. /// Here for web API we want to have in json that separation that would -/// allow to compose any possible future specialization of question +/// allow to compose any possible future specialization of question. +/// Also note that question is empty as QuestionWithPassword does not +/// provide more details for question, but require additional answer. +/// Can be potentionally extended in future e.g. with list of allowed characters? #[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)] #[serde(rename_all = "camelCase")] -pub struct QuestionWithPassword { - password: String, -} +pub struct QuestionWithPassword {} #[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)] #[serde(rename_all = "camelCase")] @@ -204,7 +241,7 @@ pub async fn questions_service(dbus: zbus::Connection) -> Result>, + Json(question): Json, +) -> Result, Error> { + let res = state.questions.create_question(question).await?; + Ok(Json(res)) +} From 9c270e0a624746f5c718f884155d8f6cf2aa6e14 Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Wed, 10 Jul 2024 12:01:13 +0200 Subject: [PATCH 2/5] do not panic if id cannot be obtained --- rust/agama-server/src/questions/web.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/agama-server/src/questions/web.rs b/rust/agama-server/src/questions/web.rs index 5a0c90022e..e4c999881f 100644 --- a/rust/agama-server/src/questions/web.rs +++ b/rust/agama-server/src/questions/web.rs @@ -83,7 +83,8 @@ impl<'a> QuestionsClient<'a> { // we are sure that regexp is correct, so use unwrap let id_matcher = Regex::new(r"/(?\d+)$").unwrap(); let Some(id_cap) = id_matcher.captures(path.as_str()) else { - panic!("Id not in path") + let msg = format!("Failed to get ID for new question: {}", path.as_str()).to_string(); + return Err(ServiceError::UnsuccessfulAction(msg)) }; // TODO: better error if path does not contain id res.generic.id = id_cap["id"].parse::().unwrap(); Ok(question) From 2828f8515865cf0102660ca09b07e4a7e170d9b4 Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Wed, 10 Jul 2024 12:02:17 +0200 Subject: [PATCH 3/5] add changelog entry --- rust/package/agama.changes | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rust/package/agama.changes b/rust/package/agama.changes index bd81b1d32d..e139a29486 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Wed Jul 10 10:01:18 UTC 2024 - Josef Reidinger + +- Add to HTTP API method POST for question to ask new question + (gh#openSUSE/agam#1451) + ------------------------------------------------------------------- Fri Jul 5 13:17:17 UTC 2024 - José Iván López González From 91e7b44820fb33663b06dee7a04dd6c2332628bc Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Wed, 10 Jul 2024 12:02:41 +0200 Subject: [PATCH 4/5] format code --- rust/agama-server/src/questions/web.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/agama-server/src/questions/web.rs b/rust/agama-server/src/questions/web.rs index e4c999881f..2866ddfca6 100644 --- a/rust/agama-server/src/questions/web.rs +++ b/rust/agama-server/src/questions/web.rs @@ -84,7 +84,7 @@ impl<'a> QuestionsClient<'a> { let id_matcher = Regex::new(r"/(?\d+)$").unwrap(); let Some(id_cap) = id_matcher.captures(path.as_str()) else { let msg = format!("Failed to get ID for new question: {}", path.as_str()).to_string(); - return Err(ServiceError::UnsuccessfulAction(msg)) + return Err(ServiceError::UnsuccessfulAction(msg)); }; // TODO: better error if path does not contain id res.generic.id = id_cap["id"].parse::().unwrap(); Ok(question) From ef9a2bb51797b21f29e2e2af1f6c51d6bf1d3efc Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Wed, 10 Jul 2024 12:06:05 +0200 Subject: [PATCH 5/5] Update rust/package/agama.changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Imobach González Sosa --- rust/package/agama.changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/package/agama.changes b/rust/package/agama.changes index e139a29486..4e45ff2716 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -2,7 +2,7 @@ Wed Jul 10 10:01:18 UTC 2024 - Josef Reidinger - Add to HTTP API method POST for question to ask new question - (gh#openSUSE/agam#1451) + (gh#openSUSE/agama#1451) ------------------------------------------------------------------- Fri Jul 5 13:17:17 UTC 2024 - José Iván López González