-
Notifications
You must be signed in to change notification settings - Fork 283
Operation Builder Future #510
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -169,21 +169,8 @@ impl CosmosClient { | |
} | ||
|
||
/// Create a database | ||
pub async fn create_database<S: AsRef<str>>( | ||
&self, | ||
ctx: Context, | ||
database_name: S, | ||
options: CreateDatabaseOptions, | ||
) -> crate::Result<CreateDatabaseResponse> { | ||
let mut request = self.prepare_request_pipeline("dbs", http::Method::POST); | ||
|
||
options.decorate_request(&mut request, database_name.as_ref())?; | ||
let response = self | ||
.pipeline() | ||
.send(ctx.clone().insert(ResourceType::Databases), &mut request) | ||
.await?; | ||
|
||
Ok(CreateDatabaseResponse::try_from(response).await?) | ||
pub fn create_database<S: AsRef<str>>(&self, database_name: S) -> CreateDatabaseBuilder { | ||
CreateDatabaseBuilder::new(self.clone(), database_name.as_ref().to_owned()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm trying to figure out the best way to pass parameters in #520 #527 and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think your suggestion to use
Potentially we could; for example using In general I'd prefer it if we took a gradual approach to this. The change to using |
||
} | ||
|
||
/// List all databases | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -3,44 +3,68 @@ use crate::prelude::*; | |||||
use crate::resources::Database; | ||||||
use crate::ResourceQuota; | ||||||
use azure_core::headers::{etag_from_headers, session_token_from_headers}; | ||||||
use azure_core::{collect_pinned_stream, Request as HttpRequest, Response as HttpResponse}; | ||||||
use azure_core::{collect_pinned_stream, Context, Response as HttpResponse}; | ||||||
use chrono::{DateTime, Utc}; | ||||||
|
||||||
#[derive(Debug, Clone)] | ||||||
pub struct CreateDatabaseOptions { | ||||||
pub struct CreateDatabaseBuilder { | ||||||
client: CosmosClient, | ||||||
database_name: String, | ||||||
consistency_level: Option<ConsistencyLevel>, | ||||||
context: Context, | ||||||
} | ||||||
|
||||||
impl CreateDatabaseOptions { | ||||||
pub fn new() -> Self { | ||||||
impl CreateDatabaseBuilder { | ||||||
pub(crate) fn new(client: CosmosClient, database_name: String) -> Self { | ||||||
Self { | ||||||
client, | ||||||
database_name, | ||||||
consistency_level: None, | ||||||
context: Context::new(), | ||||||
} | ||||||
} | ||||||
|
||||||
setters! { | ||||||
consistency_level: ConsistencyLevel => Some(consistency_level), | ||||||
context: Context => context, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note on |
||||||
} | ||||||
} | ||||||
|
||||||
impl CreateDatabaseOptions { | ||||||
pub(crate) fn decorate_request( | ||||||
&self, | ||||||
request: &mut HttpRequest, | ||||||
database_name: &str, | ||||||
) -> crate::Result<()> { | ||||||
#[derive(Serialize)] | ||||||
struct CreateDatabaseRequest<'a> { | ||||||
pub id: &'a str, | ||||||
} | ||||||
let req = CreateDatabaseRequest { id: database_name }; | ||||||
pub fn insert<E: Send + Sync + 'static>(&mut self, entity: E) -> &mut Self { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should come up with a more explicit function name. Side note: What about having a trait for this function? It won't change much but it would signal unequivocally the meaning of this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We discussed this, and adding a trait means that users of the method would need to import it into scope before being able to use it. That's not necessarily a bad idea, but before we decide to do that we should probably understand better how often the context interface is used. We settled on keeping it as an inherent function for this proposal, but leaving the option open to re-evaluate once we have a better grip on usage. |
||||||
self.context.insert(entity); | ||||||
self | ||||||
} | ||||||
|
||||||
pub fn into_future(mut self) -> CreateDatabase { | ||||||
Box::pin(async move { | ||||||
let mut request = self | ||||||
.client | ||||||
.prepare_request_pipeline("dbs", http::Method::POST); | ||||||
|
||||||
let body = CreateDatabaseBody { | ||||||
id: self.database_name.as_str(), | ||||||
}; | ||||||
|
||||||
azure_core::headers::add_optional_header2(&self.consistency_level, request)?; | ||||||
request.set_body(bytes::Bytes::from(serde_json::to_string(&req)?).into()); | ||||||
Ok(()) | ||||||
azure_core::headers::add_optional_header2(&self.consistency_level, &mut request)?; | ||||||
request.set_body(bytes::Bytes::from(serde_json::to_string(&body)?).into()); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That was existing code, so we can put this in a new issue. No need to hold up this awesomeness. |
||||||
let response = self | ||||||
.client | ||||||
.pipeline() | ||||||
.send(self.context.insert(ResourceType::Databases), &mut request) | ||||||
.await?; | ||||||
|
||||||
CreateDatabaseResponse::try_from(response).await | ||||||
}) | ||||||
} | ||||||
} | ||||||
|
||||||
/// A future of a create database response | ||||||
type CreateDatabase = futures::future::BoxFuture<'static, crate::Result<CreateDatabaseResponse>>; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This type probably should be
Suggested change
|
||||||
|
||||||
#[derive(Serialize)] | ||||||
struct CreateDatabaseBody<'a> { | ||||||
pub id: &'a str, | ||||||
} | ||||||
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd)] | ||||||
pub struct CreateDatabaseResponse { | ||||||
pub database: Database, | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,11 +37,8 @@ async fn attachment() -> Result<(), azure_cosmos::Error> { | |
|
||
// create a temp database | ||
let _create_database_response = client | ||
.create_database( | ||
azure_core::Context::new(), | ||
DATABASE_NAME, | ||
CreateDatabaseOptions::new(), | ||
) | ||
.create_database(DATABASE_NAME) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice 💘! |
||
.into_future() | ||
.await | ||
.unwrap(); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,9 +17,7 @@ async fn collection_operations() -> Result<(), BoxedError> { | |
let database_name = "test-collection-operations"; | ||
let context = Context::new(); | ||
|
||
client | ||
.create_database(context.clone(), database_name, CreateDatabaseOptions::new()) | ||
.await?; | ||
client.create_database(database_name).into_future().await?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
||
// create collection! | ||
let db_client = client.clone().into_database_client(database_name.clone()); | ||
|
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.
Much better. To confirm, all required parameters will be in the function signature,. All optional parameters are setter methods. Correct?
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.
That's indeed the 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.
That is correct.
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.
This is close to what we do across other languages, but idiomatically there are some differences. For example, in .NET, once we hit ~6 (it's not a hard limit) required parameters, we do a
{MethodName}Options
class. In fact, that naming convention is consistent across languages when used. For Python, they use a combination of named params and kwargs. Java and JavaScript are similar in nature to .NET, though JS uses options bags (typed in TS, but of course doesn't really matter in pure JS).We should follow suit here. So any optional parameters should maybe be in a
{MethodName}Options
class for methods. For clients, it's typically{ClientName}Options
, but Java uses a builder pattern that basically follows what @cataggar mentioned above.