-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ask cli #1457
Ask cli #1457
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apart from some minor naming issues (you know, naming is hard) and some missing docs, I think you are on the right track.
rust/agama-cli/src/main.rs
Outdated
async fn run_command(cli: Cli) -> anyhow::Result<()> { | ||
match cli.command { | ||
async fn run_command(cli: Cli) -> Result<(), ServiceError> { | ||
Ok(match cli.command { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am unsure whether I would like to put the full match inside the Ok()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can use let result = match
and then Ok(result)
impl HTTPClient { | ||
pub async fn new() -> Result<Self, ServiceError> { | ||
Ok(Self { | ||
client: crate::http_client::HTTPClient::new().await?, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finding a good name and importing it is a better (and more readable) approach IMHO.
rust/agama-lib/src/http_client.rs
Outdated
|
||
const API_URL: &str = "http://localhost/api"; | ||
|
||
impl HTTPClient { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, document this client with some examples.
rust/agama-lib/src/http_client.rs
Outdated
|
||
let mut headers = header::HeaderMap::new(); | ||
let value = header::HeaderValue::from_str(format!("Bearer {}", token).as_str()) | ||
.map_err(|e| ServiceError::NetworkClientError(e.to_string()))?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NetworkClientError
sounds like specific to the network scope only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, my fault. It should be generic one. Thanks for catch ( c&p error )
rust/agama-lib/src/http_client.rs
Outdated
} | ||
|
||
const NO_TEXT: &'static str = "No text"; | ||
// Simple wrapper around Response to get target type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What "target type" means?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is basically to get object you expect from backend. Something like let questions : Vec<Question> = client.get_type("/questions")
rust/agama-lib/src/http_client.rs
Outdated
// For more advanced usage use directly get method | ||
pub async fn get_type<T: DeserializeOwned>(&self, path: &str) -> Result<T, ServiceError> { | ||
let response = self.get(path).await?; | ||
if !response.status().is_success() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
np: What about returning early if is_success()
? I am saying just because the error handling seems to be longer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW one more idea I had...I know that we use 400
for error and in body we use json for ServiceError...so do you think it make sense to have something like ErrorFromBackend
that gets as argument ServiceError
from response?
rust/agama-lib/src/http_client.rs
Outdated
.map_err(|e| e.into()) | ||
} | ||
|
||
fn target_path(&self, path: &str) -> String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about using request_url
? (or just url
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fine for me...url
is probably better as it is internal only method..so shorter is better.
rust/agama-lib/src/http_client.rs
Outdated
const NO_TEXT: &'static str = "No text"; | ||
// Simple wrapper around Response to get target type. | ||
// For more advanced usage use directly get method | ||
pub async fn get_type<T: DeserializeOwned>(&self, path: &str) -> Result<T, ServiceError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea but get_type
is kind of confusing (am I getting the "type"?).
In any case, I would call this method get
and rename the get
method below to something like get_raw
as I expect us to use this more often.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed. I am even thinking about naming this one as get_object
but it is right, that plain get here and maybe get_response
would be better
/// is that it contain both question and answer. It works for dbus | ||
/// API which has both as attributes, but web API separate | ||
/// question and its answer. So here it is split into GenericQuestion | ||
/// and GenericAnswer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we should consider refactoring the GenericQuestion
from lib to be a structure composed of question and answer structs from this module. This is just an idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think whole questions DBus service needs a bit refactoring as this code does not make me proud to be author :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general it is OK. Just some comments about the documentation.
let mut headers = header::HeaderMap::new(); | ||
// just use generic anyhow error here as Bearer format is constructed by us, so failures can come only from token | ||
let value = header::HeaderValue::from_str(format!("Bearer {}", token).as_str()) | ||
.map_err(|e| anyhow::Error::new(e))?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am still no convinced of using anyhow, especially in a library. But we can postpone that until we refactor the ServiceError
stuff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
me neither...I basically use it for trash error which should not happen instead of unwrap
@@ -19,6 +19,8 @@ pub enum QuestionsCommands { | |||
/// Path to a file containing the answers in YAML format. | |||
path: String, | |||
}, | |||
/// prints list of questions that is waiting for answer in YAML format |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether we should use JSON for everything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is fine for me if agreed on it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reason why I use yaml for it is that in answers file we used for automatic answers we also use yaml.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I know, we use JSON for profiles and YAML for the answers file. I am not saying that we should fix it now, but let's have into account in the future.
}) | ||
} | ||
|
||
const NO_TEXT: &'static str = "No text"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure about the "No text" value. Why not an empty string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To see that error from backend does not provide any text. For me less confusing to write explicitelly that no text received from backend.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move the const definition closer to the method that uses it. That also makes it more apparent what it should say
.map_err(|e| e.into()) | ||
} | ||
|
||
pub async fn build_backend_error(&self, response: Response) -> ServiceError { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps you might want to implement something like From<Response>
(or TryFrom
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can it be asynchronous? As it is not simple conversion but it also ask for text.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also it is a bit against documentation of traits which mention that it should be obvious to convert...which I am not sure about this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, async could be a problem. Let's keep it as it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About when to implement those traits, I am usually unsure 😅
Co-authored-by: Imobach González Sosa <igonzalezsosa@suse.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here reviewing just the BaseHTTPClient part
impl BaseHTTPClient { | ||
// if there is need for client without authorization, create new constructor for it | ||
pub fn new() -> Result<Self, ServiceError> { | ||
let token = AuthToken::find().context("You are not logged in")?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message is correct (I hope) but even better would be a hint how to fix the situation: "... Try: agama auth login"
?
let text = response | ||
.text() | ||
.await | ||
.unwrap_or_else(|_| Self::NO_TEXT.to_string()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"No text" -> "(failed to extract error text from HTTP response)"
is perhaps too verbose but I would appreciate it when debugging it as an operator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I check quite deeply when call to text can fail and to be honest I do not find it either in documentation neither from code. So lets use your suggestion.
}) | ||
} | ||
|
||
const NO_TEXT: &'static str = "No text"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move the const definition closer to the method that uses it. That also makes it more apparent what it should say
} | ||
|
||
/// post object to given path and report error if response is not success | ||
pub async fn post(&self, path: &str, object: &impl Serialize) -> Result<(), ServiceError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stupid question, is it normal that a successful HTTP POST
returns no useful text? At least in Agama?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, response is optional in POST. I check what we have in agama-web-server openapi
and it is like half to half if we return something or not...if we need it more often we can create variant that also return object...I am just not sure what will be good name :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, a POST call does not need to return anything. I am not convinced about using "no text" string. Why not just return an empty body? In any case, we can evolve this API when we use it.
Co-authored-by: Martin Vidner <mvidner@suse.cz>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just minor comments. Feel free to merge.
@@ -19,6 +19,8 @@ pub enum QuestionsCommands { | |||
/// Path to a file containing the answers in YAML format. | |||
path: String, | |||
}, | |||
/// prints list of questions that is waiting for answer in YAML format |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I know, we use JSON for profiles and YAML for the answers file. I am not saying that we should fix it now, but let's have into account in the future.
async fn list_questions() -> Result<(), ServiceError> { | ||
let client = HTTPClient::new().await?; | ||
let questions = client.list_questions().await?; | ||
// FIXME: that conversion to anyhow error is nasty, but we do not expect issue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could have a generic error for these cases. But let's keep it as it is now. At some point we should improve our error types.
.map_err(|e| e.into()) | ||
} | ||
|
||
pub async fn build_backend_error(&self, response: Response) -> ServiceError { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, async could be a problem. Let's keep it as it is.
Co-authored-by: Imobach González Sosa <igonzalezsosa@suse.com>
Prepare for releasing Agama 10· * #1263 * #1330 * #1407 * #1408 * #1410 * #1411 * #1412 * #1416 * #1417 * #1419 * #1420 * #1421 * #1422 * #1423 * #1424 * #1425 * #1428 * #1429 * #1430 * #1431 * #1432 * #1433 * #1436 * #1437 * #1438 * #1439 * #1440 * #1441 * #1443 * #1444 * #1445 * #1449 * #1450 * #1451 * #1452 * #1453 * #1454 * #1455 * #1456 * #1457 * #1459 * #1460 * #1462 * #1464 * #1465 * #1466 * #1467 * #1468 * #1469 * #1470 * #1471 * #1472 * #1473 * #1475 * #1476 * #1477 * #1478 * #1479 * #1480 * #1481 * #1482 * #1483 * #1484 * #1485 * #1486 * #1487 * #1488 * #1489 * #1491 * #1492 * #1493 * #1494 * #1496 * #1497 * #1498 * #1499 * #1500 * #1501 * #1502 * #1503 * #1504 * #1505 * #1506 * #1507 * #1508 * #1510 * #1511 * #1512 * #1513 * #1514 * #1515 * #1516 * #1517 * #1518 * #1519 * #1520 * #1522 * #1523 * #1524 * #1525 * #1526 * #1527 * #1528 * #1529 * #1530 * #1531 * #1532 * #1533 * #1534 * #1535 * #1536 * #1537 * #1540 * #1541 * #1543 * #1544 * #1545 * #1546 * #1547 * #1548 * #1549 * #1550 * #1552 * #1553 * #1554 * #1555 * #1556 * #1557 * #1558 * #1559 * #1560 * #1562 * #1563 * #1565 * #1566 * #1567 * #1568 * #1569 * #1570 * #1571 * #1572 * #1573 * #1574 * #1575 * #1576 * #1577 * #1578 * #1579 * #1580 * #1581 * #1583 * #1584 * #1585 * #1586 * #1587 * #1588 * #1589 * #1590 * #1591 * #1592 * #1593 * #1596 * #1597 * #1598 * #1600 * #1602 * #1605 * #1606 * #1607 * #1608 * #1610 * #1611 * #1612 * #1613 * #1614 * #1619 * #1620 * #1621
Problem
There is no CLI command to ask new question. It is needed to ask questions from autoyast convert script.
Also there is a need to share functionality between various CLI calls that will use HTTP API as current one in network has limitation and is not designed as generic http agent that fits agama.
Solution
Implement needed functionality in questions http client.
Implement BaseHttpClient that shares code for doing http requests on agama http server.
Implement ask and list subcommand for questions to allow asking questions and also listing currently unanswered ones.