12个版本 (6个重大更改)
0.8.0 | 2024年1月22日 |
---|---|
0.6.1 | 2020年12月28日 |
0.6.0 | 2020年1月22日 |
0.5.0 | 2019年9月12日 |
0.1.0 | 2019年8月21日 |
#107 in 身份验证
每月下载量232
135KB
2.5K SLoC
Firestore API和身份验证

此包允许您通过服务帐户或OAuth模拟的Google Firebase身份验证凭据轻松访问Google Firestore数据库。最低Rust版本:1.38
功能
- 异步API
- Firestore v1 API的子集
- 可选地为您处理身份验证和令牌刷新
- 支持从Google Cloud console下载的Google服务帐户json文件。 (请参阅https://cloud.google.com/storage/docs/reference/libraries#client-libraries-install-cpp)
用例
- 严格类型文档读写/查询访问
- 云函数(Google Compute,AWS Lambda)访问Firestore
限制
- 尚不支持监听文档/集合更改
Cargo功能
-
native-tls,default-tls,rustls-tls:选择这些功能之一用于加密连接(https)。rustls-tls是默认选项(将使用rustls包)。
-
rocket_support:Rocket是一个Web框架。此功能启用Rocket集成并添加了一个请求守卫。只有经过Firestore Auth授权的请求才能通过此守卫。
文档操作
此包使用DTOs(数据传输对象)进行类型安全的Firestore数据库操作。
use firestore_db_and_auth::{Credentials, ServiceSession, documents, errors::Result};
use serde::{Serialize,Deserialize};
#[derive(Serialize, Deserialize)]
struct DemoDTO {
a_string: String,
an_int: u32,
another_int: u32,
}
#[derive(Serialize, Deserialize)]
struct DemoPartialDTO {
#[serde(skip_serializing_if = "Option::is_none")]
a_string: Option<String>,
an_int: u32,
}
/// Write the given object with the document id "service_test" to the "tests" collection.
/// You do not need to provide a document id (use "None" instead) and let Firestore generate one for you.
///
/// In either way a document is created or updated (overwritten).
///
/// The write method will return document metadata (including a possible generated document id)
fn write(session: &ServiceSession) -> Result<()> {
let obj = DemoDTO { a_string: "abcd".to_owned(), an_int: 14, another_int: 10 };
let result = documents::write(session, "tests", Some("service_test"), &obj, documents::WriteOptions::default())?;
println!("id: {}, created: {}, updated: {}", result.document_id, result.create_time.unwrap(), result.update_time.unwrap());
Ok(())
}
/// Only write some fields and do not overwrite the entire document.
/// Either via Option<> or by not having the fields in the structure, see DemoPartialDTO.
fn write_partial(session: &ServiceSession) -> Result<()> {
let obj = DemoPartialDTO { a_string: None, an_int: 16 };
let result = documents::write(session, "tests", Some("service_test"), &obj, documents::WriteOptions{merge:true})?;
println!("id: {}, created: {}, updated: {}", result.document_id, result.create_time.unwrap(), result.update_time.unwrap());
Ok(())
}
读取"tests"集合中ID为"service_test"的文档
use firestore_db_and_auth::{documents};
let obj : DemoDTO = documents::read(&session, "tests", "service_test")?;
要列出"tests"集合中的所有文档,您需要使用list
方法,它实现了一个异步流。这隐藏了分页API的复杂性,并在必要时获取新文档。
use firestore_db_and_auth::{documents};
let mut stream = documents::list(&session, "tests");
while let Some(Ok(doc_result)) = stream.next().await {
// The document is wrapped in a Result<> because fetching new data could have failed
let (doc, _metadata) = doc_result;
let doc: DemoDTO = doc;
println!("{:?}", doc);
}
注意:结果列表或游标是具有有限生命期的快照视图。您不能长时间保留迭代器/流,也不能期望在持续迭代中出现新文档。
要查询数据库,您将使用query
方法。以下示例中,查询"tests"集合,查找"ID"字段等于"Sam Weiss"的文档。
use firestore_db_and_auth::{documents, dto};
let values = documents::query(&session, "tests", "Sam Weiss".into(), dto::FieldOperator::EQUAL, "id").await?;
for metadata in values {
println!("id: {}, created: {}, updated: {}", metadata.name.as_ref().unwrap(), metadata.create_time.as_ref().unwrap(), metadata.update_time.as_ref().unwrap());
// Fetch the actual document
// The data is wrapped in a Result<> because fetching new data could have failed
let doc : DemoDTO = documents::read_by_name(&session, metadata.name.as_ref().unwrap())?;
println!("{:?}", doc);
}
你注意到 into
吗?在 "Sam Weiss".into()
上?Firestore 以强类型存储文档字段。查询值可以是字符串、整数、浮点数,甚至可能是数组或对象(未测试)。
注意:查询方法返回一个向量,因为查询可能会返回多个匹配的文档。
错误处理
返回的 Result
在任何错误情况下都将设置一个 FirebaseError
。这种自定义错误类型封装了所有可能的错误(IO、Reqwest、JWT 错误等)以及 Google REST API 错误。如果您想特别检查 API 错误,可以这样做
use firestore_db_and_auth::{documents, errors::FirebaseError};
let r = documents::delete(&session, "tests/non_existing", true).await;
if let Err(e) = r.err() {
if let FirebaseError::APIError(code, message, context) = e {
assert_eq!(code, 404);
assert!(message.contains("No document to update"));
assert_eq!(context, "tests/non_existing");
}
}
代码是数字的,消息是 Google 服务器返回的消息。上下文字符串取决于调用方法。可能是集合或文档 ID 或其他任何上下文信息。
通过服务帐户访问文档
- 下载服务帐户凭据文件并将其存储为 "firebase-service-account.json"。文件应包含
"private_key_id": ...
。 - 添加另一个字段
"api_key" : "YOUR_API_KEY"
并将 YOUR_API_KEY 替换为您的 Web API 密钥,可在 Google Firebase 控制台 的 "项目概览 -> 设置 -> 通用" 中找到。
use firestore_db_and_auth::{Credentials, ServiceSession};
/// Create credentials object. You may as well do that programmatically.
let cred = Credentials::from_file("firebase-service-account.json").await
.expect("Read credentials file")
.download_jwkset().await
.expect("Failed to download public keys");
/// To use any of the Firestore methods, you need a session first. You either want
/// an impersonated session bound to a Firebase Auth user or a service account session.
let session = ServiceSession::new(&cred)
.expect("Create a service account session");
通过 Firebase 用户访问令牌/刷新令牌或用户 ID 访问文档
您可以通过多种方式创建用户会话。如果您只有 firebase Auth 用户_id,您将遵循以下步骤
use firestore_db_and_auth::{Credentials, sessions};
/// Create credentials object. You may as well do that programmatically.
let cred = Credentials::from_file("firebase-service-account.json").await
.expect("Read credentials file")
.download_jwkset().await
.expect("Failed to download public keys");
/// To use any of the Firestore methods, you need a session first.
/// Create an impersonated session bound to a Firebase Auth user via your service account credentials.
let session = UserSession::by_user_id(&cred, "the_user_id").await
.expect("Create a user session");
如果您已经有一个有效的刷新令牌并想生成一个访问令牌(以及一个会话对象),您将这样做
let refresh_token = "fkjandsfbajsbfd;asbfdaosa.asduabsifdabsda,fd,a,sdbasfadfasfas.dasdasbfadusbflansf";
let session = UserSession::by_refresh_token(&cred, &refresh_token).await?;
获取会话对象的另一种方式是提供有效的访问令牌,如下所示
let access_token = "fkjandsfbajsbfd;asbfdaosa.asduabsifdabsda,fd,a,sdbasfadfasfas.dasdasbfadusbflansf";
let session = UserSession::by_access_token(&cred, &access_token).await?;
by_access_token
方法将失败,如果令牌不再有效。请注意,以这种方式创建的会话无法自动刷新其访问令牌。(没有与之关联的 refresh_token。)
云函数:改善冷启动时间
通常的启动程序包括三个 IO 操作
- 从 Google 服务器下载两个公共 jwks 密钥,
- 并读取 json 凭据文件。
通过将凭据和公钥文件嵌入到您的应用程序中,避免这些操作。
首先下载 2 个公钥文件
- https://www.googleapis.com/service_accounts/v1/jwk/[email protected] -> 存储为
securetoken.jwks
- https://www.googleapis.com/service_accounts/v1/jwk/{your-service-account-email} -> 存储为
service-account.jwks
- 将两个文件合并为一个
firebase-service-account.jwks
创建一个 Credentials
对象,如下所示
use firestore_db_and_auth::Credentials;
let c = Credentials::new(include_str!("firebase-service-account.json")).await?
.with_jwkset(&JWKSet::new(include_str!("firebase-service-account.jwks"))?).await?;
请注意,Google 的 JWK 密钥定期更改。您可能希望大约每三周重新部署服务,并使用新的公钥。
对于长期运行的服务,您需要定期调用 Credentials::download_google_jwks()。
更多信息
- Firestore Auth:背景信息
- 使用您自己的身份验证实现
- Http Rocket Server 集成
- 在本地构建文档,使用以下命令:
cargo +nightly doc --features external_doc,rocket_support
测试
要执行完整的集成测试(cargo test
),您需要一个有效的“firebase-service-account.json”文件。测试期望一个ID在examples/test_user_id.txt
中给出的Firebase用户存在。更多信息
如何使这个crate更加出色
这个库没有雄心去完全映射http/gRPC API 1:1。为此有自动生成的库。但是以下内容符合crates架构
- 期望的功能:Firestore的批量获取支持、事务处理
依赖
~14–48MB
~1M SLoC