Skip to content
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

Cli client #157

Merged
merged 16 commits into from
Feb 2, 2021
Merged

Cli client #157

merged 16 commits into from
Feb 2, 2021

Conversation

binoychitale
Copy link
Contributor

Signed-off-by: binoychitale binoychitale@gmail.com

Fixes #141

@binoychitale binoychitale mentioned this pull request Jan 25, 2021
@@ -160,9 +163,11 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
.get_matches();

let _address = matches.value_of("address").unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be renamed to address. Some of the variables in here were prefixed with an underscore temporarily to pass clippy, until they're used.


if let Some(_matches) = matches.subcommand_matches("ping") {
unimplemented!();
client.ping().await.map_err(|err| err.compat())?;
println!("PING OK");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just print ok.

}

panic!("unknown command");
Copy link
Member

@ysimonson ysimonson Jan 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If match fails on any of the nested ifs, a panic won't occur with this change. A couple of options here:

  1. Add the panic to each of the nested if arms.

  2. Keep the old panic, but call into a function on any of the if arms. e.g.:

if let Some(_matches) = matches.subcommand_matches("ping") {
    return ping(client).await;
} ...

As more code gets added, (2) will probably be cleaner.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.
I also found a setting for the App/Subcommand which enforces that at least one of the subcommands must be used.

I think this will error out more gracefully than panicking manually.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have pushed a few more changes.

If match fails on any of the nested ifs, a panic won't occur with this change.

By enforcing that "at least one" of the nested subcommands is used at runtime (using AppSettings::SubcommandRequiredElseHelp), we should not run into this condition

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh cool, nice find!

} else if let Some(matches) = matches.subcommand_matches("set") {
if let Some(_matches) = matches.subcommand_matches("vertex") {
unimplemented!();
let link_type = indradb::Type::new(_matches.value_of("type").unwrap()).map_err(|err| err.compat())?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would more appropriately be called vertex_type


if let Some(_matches) = matches.subcommand_matches("ping") {
unimplemented!();
client.ping().await.map_err(|err| err.compat())?;
println!("ok");
} else if let Some(matches) = matches.subcommand_matches("set") {
if let Some(_matches) = matches.subcommand_matches("vertex") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The underscore prefix for this can be removed now

.await.map_err(|err| err.compat())?
.create_vertex(&vertex)
.await.map_err(|err| err.compat())?;
if res == false {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified to if !res {

.create_vertex(&vertex)
.await.map_err(|err| err.compat())?;
if res == false {
panic!("Failed to create vertex with id: {} and type: {}", vertex.id, vertex.t.0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should return a new error rather than panicking

if res == false {
panic!("Failed to create vertex with id: {} and type: {}", vertex.id, vertex.t.0);
}
println!("Created vertex with id: {} and type: {}", vertex.id, vertex.t.0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just print the debug version of the vertex (i.e., println!("{:?}", vertex);), that way it should be easier to parse if this is used in a script

@binoychitale
Copy link
Contributor Author

For setting vertex properties, we expect the vertex query as a JSON.
There are different types of vertex queries which would need to be de-serialized according to the specific type of vertex query.

  1. Is there a sample JSON vertex query to use as a reference?
  2. What would be the best way to de-serialize? using serde can be an option

@ysimonson
Copy link
Member

In a separate PR, I'll make VertexQuery and EdgeQuery auto-derive serde::Deserialize. Once that's done, deserializing should be as simple as let query: VertexQuery = serde_json::from_str(arg_string)?. It would be the more pleasant JSON, but it'll work.

For deserializing to a straight-up JSON value, you should similarly be able to do something like let value: JsonValue = serde_json::from_str(arg_string)?.

@ysimonson
Copy link
Member

@ysimonson
Copy link
Member

Instead of supporting full-blown vertex/edge queries, let's just support operations on individual vertices (by UUID) / edges (by edge key.) That'll make things a lot easier, and still deliver a good amount of value.

@binoychitale
Copy link
Contributor Author

Since we're now only supporting vertex queries by Uuid and edges by EdgeKey, I suppose it no longer makes sense to have get vertex and get edge commands, since the only supported lookups would be by uuid and edgekey

@ysimonson
Copy link
Member

There is still some marginal utility, in that you’d get back info on whether the vertex/edge exists, as well as it’s update time stamp. But yeah it’s pretty limited, and not needed for initial version of the client.

@binoychitale
Copy link
Contributor Author

Got it. In that case ill just implement it, doesnt seem like too much effort

@binoychitale binoychitale marked this pull request as ready for review January 29, 2021 17:42
Signed-off-by: binoychitale <binoychitale@gmail.com>
Signed-off-by: binoychitale <binoychitale@gmail.com>
Signed-off-by: binoychitale <binoychitale@gmail.com>
Signed-off-by: binoychitale <binoychitale@gmail.com>
Signed-off-by: binoychitale <binoychitale@gmail.com>
Signed-off-by: binoychitale <binoychitale@gmail.com>
Signed-off-by: binoychitale <binoychitale@gmail.com>
Signed-off-by: binoychitale <binoychitale@gmail.com>
Signed-off-by: binoychitale <binoychitale@gmail.com>
Signed-off-by: binoychitale <binoychitale@gmail.com>
Signed-off-by: binoychitale <binoychitale@gmail.com>
let edge_type_arg = Arg::with_name("type").help("the edge type").required(true);
let inbound_id_arg = Arg::with_name("inbound_id")
.help("the inbound vertex ID")
.required(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these args require either a .takes_value(true) or .index(true)? Maybe they don't, not sure.

@@ -17,6 +17,8 @@ pub enum Error {
Sled { inner: SledError },
#[fail(display = "UUID already taken")]
UuidTaken,
#[fail(display = "Invalid vertex")]
VertexInvalid,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a separate error in the bin crate, since it's not produced in lib ever. Thankfully, because main returns Box<dyn Error>, different types of errors can be returned.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if wrong here but I think this actually would be useful in lib, because if create_edge returns a false from a non-CLI call, it would still be useful error to throw. (maybe pave the way to return an error instead of a boolean value from this method in the future)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the library level, it's not considered an error to insert a redundant vertex. This has a couple of distinct advantages:

  1. For datastores that provide all-or-none transaction semantics, inserting a vertex redundantly won't invalidate the entire transaction.
  2. Similarly, for bulk inserts, this ensures that an attempt to create a vertex won't invalidate the entire bulk insert operation.

Some(_) => indradb::EdgeDirection::Inbound,
None => indradb::EdgeDirection::Outbound,
};
let edge_type = match matches.value_of("type") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use Option::map to simplify this a bit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to cleanly bubble errors out of the closure without making the code equally verbose
For e.g if I write -

let edge_type = matches.value_of("type")
.map(|edge_type| indradb::Type::new(edge_type).map_err(|err| err.compat()));

the error thrown by the new() method needs to be handled in the outer function

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, yeah a match seems like the right move here


if let Some(_matches) = matches.subcommand_matches("ping") {
unimplemented!();
println!("ok");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only command that doesn't acquire a transaction, so to shorten the code, it might be good to restructure to something like this:

if matches.subcommand_matches("ping").is_some() {
    // ...ping
    return Ok(());
}

let trans = client.transaction().await.map_err(|err| err.compat())?;
// ...various subcommand matches here

.help("the JSON edge query")
.required(true)
.index(1);
let edge_query_arg = [outbound_id_arg, edge_type_arg, inbound_id_arg];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This + the various build functions are a nice way to shorten the code 👍

Signed-off-by: binoychitale <binoychitale@gmail.com>
Signed-off-by: binoychitale <binoychitale@gmail.com>
Signed-off-by: binoychitale <binoychitale@gmail.com>
@ysimonson
Copy link
Member

This looks almost ready to merge! Could you take a look at the warnings triggered by github actions in the diff? Once that's done, I'll take a final pass.

Signed-off-by: binoychitale <binoychitale@gmail.com>
@binoychitale
Copy link
Contributor Author

I've resolved the clippy warnings, we're good to go :)

@ysimonson ysimonson merged commit a704385 into indradb:master Feb 2, 2021
@ysimonson
Copy link
Member

The CLI client just went out in the latest release (v2.1.0), and is available on linux/macos as a pre-built binary. Congrats and thanks again for this substantial contribution, @binoychitale!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add CLI client
2 participants