Skip to content

Commit bf7ae34

Browse files
committed
crates/{models,tables,agent}: refactor CatalogType, Capability, and RoleGrants
Push these down into the `models` and `tables` crates, with conditional sqlx support. Add new routines for efficient evaluation of transitive role grants.
1 parent 800fcf0 commit bf7ae34

File tree

12 files changed

+339
-143
lines changed

12 files changed

+339
-143
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/agent-sql/src/lib.rs

+3-82
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ pub mod live_specs;
99
pub mod publications;
1010
use serde::{Deserialize, Serialize};
1111
use sqlx::types::Uuid;
12-
use std::fmt::{self, Display};
13-
14-
pub use models::Id;
1512

1613
mod text_json;
1714
pub use text_json::TextJson;
1815

16+
pub use models::{Capability, CatalogType, Id};
17+
pub use tables::RoleGrant;
18+
1919
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)]
2020
#[sqlx(type_name = "flow_type")]
2121
#[sqlx(rename_all = "snake_case")]
@@ -38,85 +38,6 @@ impl From<CatalogType> for FlowType {
3838
}
3939
}
4040

41-
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)]
42-
#[sqlx(type_name = "catalog_spec_type")]
43-
#[sqlx(rename_all = "lowercase")]
44-
pub enum CatalogType {
45-
Capture,
46-
Collection,
47-
Materialization,
48-
Test,
49-
}
50-
51-
impl sqlx::postgres::PgHasArrayType for CatalogType {
52-
fn array_type_info() -> sqlx::postgres::PgTypeInfo {
53-
sqlx::postgres::PgTypeInfo::with_name("_catalog_spec_type")
54-
}
55-
}
56-
57-
impl Display for CatalogType {
58-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59-
let s = match *self {
60-
CatalogType::Capture => "capture",
61-
CatalogType::Collection => "collection",
62-
CatalogType::Materialization => "materialization",
63-
CatalogType::Test => "test",
64-
};
65-
f.write_str(s)
66-
}
67-
}
68-
69-
impl Into<models::CatalogType> for CatalogType {
70-
fn into(self) -> models::CatalogType {
71-
match self {
72-
CatalogType::Capture => models::CatalogType::Capture,
73-
CatalogType::Collection => models::CatalogType::Collection,
74-
CatalogType::Materialization => models::CatalogType::Materialization,
75-
CatalogType::Test => models::CatalogType::Test,
76-
}
77-
}
78-
}
79-
80-
impl From<models::CatalogType> for CatalogType {
81-
fn from(m: models::CatalogType) -> Self {
82-
match m {
83-
models::CatalogType::Capture => CatalogType::Capture,
84-
models::CatalogType::Collection => CatalogType::Collection,
85-
models::CatalogType::Materialization => CatalogType::Materialization,
86-
models::CatalogType::Test => CatalogType::Test,
87-
}
88-
}
89-
}
90-
91-
/// Note that the discriminants here align with those in the database type.
92-
#[derive(
93-
Debug,
94-
Copy,
95-
Clone,
96-
PartialEq,
97-
Eq,
98-
PartialOrd,
99-
Serialize,
100-
Deserialize,
101-
sqlx::Type,
102-
schemars::JsonSchema,
103-
)]
104-
#[sqlx(type_name = "grant_capability")]
105-
#[sqlx(rename_all = "lowercase")]
106-
#[serde(rename_all = "camelCase")]
107-
pub enum Capability {
108-
Read = 0x10,
109-
Write = 0x20,
110-
Admin = 0x30,
111-
}
112-
113-
#[derive(Debug, Serialize, Deserialize)]
114-
pub struct RoleGrant {
115-
pub subject_role: String,
116-
pub object_role: String,
117-
pub capability: Capability,
118-
}
119-
12041
/// Returns the user ID for the given email address, or an error if the email address is not found.
12142
pub async fn get_user_id_for_email(email: &str, db: &sqlx::PgPool) -> sqlx::Result<Uuid> {
12243
sqlx::query_scalar!(

crates/agent/src/api/create_data_plane.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub struct Manual {
2828
pub struct Request {
2929
/// Base name of this data-plane, such as "gcp-us-central1-c1".
3030
#[validate]
31-
name: models::PartitionField,
31+
name: models::Token,
3232

3333
/// Private tenant to which this data-plane is provisioned,
3434
/// or if None the data-plane is public.

crates/agent/src/directives/beta_onboard.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ pub struct Directive {}
1111
#[derive(Deserialize, Validate, schemars::JsonSchema)]
1212
#[serde(rename_all = "camelCase", deny_unknown_fields)]
1313
pub struct Claims {
14-
// TODO(johnny): Introduce models::Tenant which, like PartitionField, also uses TOKEN_RE.
1514
#[validate]
16-
requested_tenant: models::PartitionField,
15+
requested_tenant: models::Token,
1716
// Survey results for the tenant.
1817
// This is persisted in the DB but is not actually used by the agent.
1918
#[allow(dead_code)]

crates/agent/src/publications/specs.rs

+4-6
Original file line numberDiff line numberDiff line change
@@ -641,11 +641,9 @@ pub async fn resolve_live_specs(
641641
continue;
642642
}
643643
for source in reads_from {
644-
if !spec_row
645-
.spec_capabilities
646-
.iter()
647-
.any(|c| source.starts_with(&c.object_role) && c.capability >= Capability::Read)
648-
{
644+
if !spec_row.spec_capabilities.iter().any(|c| {
645+
source.starts_with(c.object_role.as_str()) && c.capability >= Capability::Read
646+
}) {
649647
live.errors.push(tables::Error {
650648
scope: scope.clone(),
651649
error: anyhow::anyhow!(
@@ -657,7 +655,7 @@ pub async fn resolve_live_specs(
657655
}
658656
for target in writes_to {
659657
if !spec_row.spec_capabilities.iter().any(|c| {
660-
target.starts_with(&c.object_role)
658+
target.starts_with(c.object_role.as_str())
661659
&& matches!(c.capability, Capability::Write | Capability::Admin)
662660
}) {
663661
live.errors.push(tables::Error {

crates/models/src/catalogs.rs

+70
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,37 @@ pub struct Catalog {
4242
pub tests: BTreeMap<Test, TestDef>,
4343
}
4444

45+
#[derive(Serialize, Deserialize, Clone, Copy, Debug, JsonSchema, PartialEq, Eq)]
46+
#[serde(rename_all = "lowercase")]
47+
#[cfg_attr(
48+
feature = "sqlx-support",
49+
derive(sqlx::Type),
50+
sqlx(type_name = "catalog_spec_type", rename_all = "lowercase")
51+
)]
52+
pub enum CatalogType {
53+
Capture,
54+
Collection,
55+
Materialization,
56+
Test,
57+
}
58+
59+
/// Capability within the Estuary role-based access control (RBAC) authorization system.
60+
#[derive(
61+
Serialize, Deserialize, Clone, Copy, Debug, JsonSchema, PartialEq, Eq, PartialOrd, Ord, Hash,
62+
)]
63+
#[serde(rename_all = "lowercase")]
64+
#[cfg_attr(
65+
feature = "sqlx-support",
66+
derive(sqlx::Type),
67+
sqlx(type_name = "grant_capability", rename_all = "lowercase")
68+
)]
69+
pub enum Capability {
70+
/// Note that the discriminants here align with those in the database type.
71+
Read = 10,
72+
Write = 20,
73+
Admin = 30,
74+
}
75+
4576
impl Catalog {
4677
/// Build a root JSON schema for the Catalog model.
4778
pub fn root_json_schema() -> schemars::schema::RootSchema {
@@ -199,3 +230,42 @@ fn tests_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::S
199230
}))
200231
.unwrap()
201232
}
233+
234+
#[cfg(feature = "sqlx-support")]
235+
impl sqlx::postgres::PgHasArrayType for CatalogType {
236+
fn array_type_info() -> sqlx::postgres::PgTypeInfo {
237+
sqlx::postgres::PgTypeInfo::with_name("_catalog_spec_type")
238+
}
239+
}
240+
241+
impl std::str::FromStr for CatalogType {
242+
type Err = ();
243+
244+
fn from_str(s: &str) -> Result<Self, Self::Err> {
245+
match s {
246+
"capture" => Ok(CatalogType::Capture),
247+
"collection" => Ok(CatalogType::Collection),
248+
"materialization" => Ok(CatalogType::Materialization),
249+
"test" => Ok(CatalogType::Test),
250+
_ => Err(()),
251+
}
252+
}
253+
}
254+
255+
impl std::fmt::Display for CatalogType {
256+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
257+
f.write_str(self.as_ref())
258+
}
259+
}
260+
261+
impl std::convert::AsRef<str> for CatalogType {
262+
fn as_ref(&self) -> &str {
263+
// These strings match what's used by serde, and also match the definitions in the database.
264+
match *self {
265+
CatalogType::Capture => "capture",
266+
CatalogType::Collection => "collection",
267+
CatalogType::Materialization => "materialization",
268+
CatalogType::Test => "test",
269+
}
270+
}
271+
}

crates/models/src/lib.rs

+5-48
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
pub mod collate;
1+
use std::collections::BTreeSet;
22

33
mod captures;
44
mod catalogs;
5+
pub mod collate;
56
mod collections;
67
mod connector;
78
mod derivation;
@@ -18,11 +19,9 @@ mod shards;
1819
mod source;
1920
mod tests;
2021

21-
use std::collections::BTreeSet;
22-
2322
pub use crate::labels::{Label, LabelSelector, LabelSet};
2423
pub use captures::{AutoDiscover, CaptureBinding, CaptureDef, CaptureEndpoint};
25-
pub use catalogs::Catalog;
24+
pub use catalogs::{Capability, Catalog, CatalogType};
2625
pub use collections::{CollectionDef, Projection};
2726
pub use connector::{split_image_tag, ConnectorConfig, LocalConfig};
2827
pub use derivation::{Derivation, DeriveUsing, Shuffle, ShuffleType, TransformDef};
@@ -40,11 +39,10 @@ pub use materializations::{
4039
};
4140
pub use raw_value::RawValue;
4241
pub use references::{
43-
Capture, Collection, CompositeKey, Field, JsonPointer, Materialization, PartitionField, Prefix,
44-
RelativeUrl, StorageEndpoint, Test, Transform, CATALOG_PREFIX_RE, TOKEN_RE,
42+
Capture, Collection, CompositeKey, Field, JsonPointer, Materialization, Name, PartitionField,
43+
Prefix, RelativeUrl, StorageEndpoint, Test, Token, Transform, CATALOG_PREFIX_RE, TOKEN_RE,
4544
};
4645
pub use schemas::Schema;
47-
use serde::{Deserialize, Serialize};
4846
pub use shards::ShardTemplate;
4947
pub use source::{FullSource, OnIncompatibleSchemaChange, PartitionSelector, Source};
5048
pub use tests::{TestDef, TestDocuments, TestStep, TestStepIngest, TestStepVerify};
@@ -98,47 +96,6 @@ pub trait ModelDef:
9896
}
9997
}
10098

101-
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
102-
#[serde(rename_all = "lowercase")]
103-
pub enum CatalogType {
104-
Capture,
105-
Collection,
106-
Materialization,
107-
Test,
108-
}
109-
110-
impl std::str::FromStr for CatalogType {
111-
type Err = ();
112-
113-
fn from_str(s: &str) -> Result<Self, Self::Err> {
114-
match s {
115-
"capture" => Ok(CatalogType::Capture),
116-
"collection" => Ok(CatalogType::Collection),
117-
"materialization" => Ok(CatalogType::Materialization),
118-
"test" => Ok(CatalogType::Test),
119-
_ => Err(()),
120-
}
121-
}
122-
}
123-
124-
impl std::fmt::Display for CatalogType {
125-
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
126-
f.write_str(self.as_ref())
127-
}
128-
}
129-
130-
impl std::convert::AsRef<str> for CatalogType {
131-
fn as_ref(&self) -> &str {
132-
// These strings match what's used by serde, and also match the definitions in the database.
133-
match *self {
134-
CatalogType::Capture => "capture",
135-
CatalogType::Collection => "collection",
136-
CatalogType::Materialization => "materialization",
137-
CatalogType::Test => "test",
138-
}
139-
}
140-
}
141-
14299
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
143100
#[serde(rename_all = "camelCase")]
144101
pub enum AnySpec {

crates/models/src/references.rs

+6
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ macro_rules! string_reference_types {
166166
// of passing it into another macro.
167167

168168
string_reference_types! {
169+
/// Token is Unicode letters, numbers, '-', '_', or '.'.
170+
pub struct Token("Token::schema", pattern = TOKEN_RE, example = "token");
171+
172+
/// Catalog Name is a series of tokens separated by a forward slash.
173+
pub struct Name("Name::schema", pattern = CATALOG_NAME_RE, example = "acmeCo/name");
174+
169175
/// Collection names are paths of Unicode letters, numbers, '-', '_', or '.'.
170176
/// Each path component is separated by a slash '/',
171177
/// and a name may not begin or end in a '/'.

crates/tables/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ serde_json = { workspace = true }
2626
superslice = { workspace = true }
2727
url = { workspace = true }
2828

29+
[dev-dependencies]
30+
31+
insta = { workspace = true }
32+
2933
[features]
3034
default = []
3135

0 commit comments

Comments
 (0)