use serde::{Deserialize, Serialize};
pub const REPORT_VERSION: u32 = 2;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Report {
pub report_version: u32,
pub tool: Tool,
pub project: Project,
pub read_only: ReadOnlyEvidence,
pub coverage: Coverage,
pub summary: Summary,
pub dispositions: DispositionsReport,
pub schemas: Vec<Schema>,
pub tables: Vec<Table>,
pub views: Vec<View>,
pub sequences: Vec<Sequence>,
pub enums: Vec<EnumType>,
pub extensions: Vec<Extension>,
pub functions: Vec<Function>,
pub triggers: Vec<Trigger>,
pub policies: Vec<Policy>,
pub managed_policies: Vec<ManagedPolicy>,
pub api_role_grants: Vec<RoleGrants>,
pub auth: Auth,
pub storage: Storage,
pub edge_functions: EdgeFunctions,
pub realtime: Realtime,
pub cron_jobs: Option<Vec<CronJob>>,
pub findings: Vec<Finding>,
pub warnings: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tool {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Project {
#[serde(rename = "ref")]
pub project_ref: String,
pub host: String,
pub port: u16,
pub database: String,
pub server_version: String,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReadOnlyEvidence {
pub transaction_read_only: bool,
pub session_read_only: bool,
pub no_transaction_id_assigned: bool,
pub role: String,
pub role_is_superuser: bool,
pub role_can_write: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Coverage {
pub database: SourceStatus,
pub management_api: SourceStatus,
pub policy_classifier: SourceStatus,
pub sections: Vec<SectionCoverage>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SectionCoverage {
pub section: String,
pub coverage: SourceStatus,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub reason: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub fix: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceStatus {
Inspected,
NotInspected,
NotVisible,
Failed,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Summary {
pub automatic: usize,
pub needs_work: usize,
pub blockers: usize,
pub decided: usize,
pub undecided: usize,
pub ready: bool,
pub tables: usize,
pub estimated_rows: u64,
pub data_bytes: u64,
pub index_bytes: u64,
pub storage_objects: Option<u64>,
pub storage_bytes: Option<u64>,
pub transfer_assumed_mbps: u32,
pub estimated_transfer_seconds: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Schema {
pub name: String,
pub kind: SchemaKind,
pub target: Option<String>,
pub tables: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SchemaKind {
User,
SupabaseManaged,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Table {
pub schema: String,
pub name: String,
pub kind: String,
pub estimated_rows: Option<u64>,
pub data_bytes: u64,
pub index_bytes: u64,
pub rls_enabled: bool,
pub rls_forced: bool,
pub primary_key: Vec<String>,
pub columns: Vec<Column>,
pub constraints: Vec<Constraint>,
pub indexes: Vec<Index>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Column {
pub name: String,
pub data_type: String,
pub nullable: bool,
pub default: Option<String>,
pub identity: Option<String>,
pub generated: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Constraint {
pub name: String,
pub kind: String,
pub definition: String,
pub references: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Index {
pub name: String,
pub unique: bool,
pub primary: bool,
pub definition: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct View {
pub schema: String,
pub name: String,
pub materialized: bool,
pub references_auth: bool,
pub references_storage: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Sequence {
pub schema: String,
pub name: String,
pub owned_by: Option<String>,
pub data_type: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EnumType {
pub schema: String,
pub name: String,
pub labels: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Extension {
pub name: String,
pub version: String,
pub schema: String,
pub support: ExtensionSupport,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExtensionSupport {
Supported,
SupabasePlatform,
Unsupported,
Unknown,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Function {
pub schema: String,
pub name: String,
pub arguments: String,
pub kind: String,
pub language: String,
pub returns: String,
pub security_definer: bool,
pub references_auth: bool,
pub references_storage: bool,
pub references_net: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Trigger {
pub schema: String,
pub table: String,
pub name: String,
pub function: String,
pub definition: String,
pub enabled: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Policy {
pub schema: String,
pub table: String,
pub name: String,
pub command: String,
pub permissive: bool,
pub roles: Vec<String>,
pub using: Option<String>,
pub with_check: Option<String>,
pub pattern: PolicyPattern,
pub confidence: f32,
pub source: PolicySource,
pub classifier_label: Option<String>,
pub suggested_equivalent: String,
pub test_stub: String,
pub disposition: Disposition,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ManagedPolicy {
pub schema: String,
pub table: String,
pub name: String,
pub command: String,
pub roles: Vec<String>,
pub using: Option<String>,
pub with_check: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BucketPolicy {
pub table: String,
pub name: String,
pub command: String,
pub roles: Vec<String>,
pub using: Option<String>,
pub with_check: Option<String>,
pub all_buckets: bool,
pub suggested_equivalent: String,
pub disposition: Disposition,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Disposition {
Undecided,
Covered,
Waived,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PolicyPattern {
OwnerOnly,
TenantScoped,
PublicRead,
PublicWrite,
RoleBased,
ServiceRoleOnly,
OwnerViaParent,
TenantViaParent,
PublicReadFiltered,
DenyAll,
Composite,
CustomLogic,
NeedsReview,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PolicySource {
Rule,
Classifier,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoleGrants {
pub role: String,
pub tables: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Auth {
pub present: bool,
pub users: Option<u64>,
pub users_without_password: Option<u64>,
pub users_unconfirmed: Option<u64>,
pub anonymous_users: Option<u64>,
pub identities_by_provider: Option<Vec<ProviderCount>>,
pub mfa_factors: Option<u64>,
pub sso_providers: Option<u64>,
pub enabled_providers: Option<Vec<String>>,
pub enabled_mfa: Option<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProviderCount {
pub provider: String,
pub identities: u64,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Storage {
pub present: bool,
pub buckets: Option<Vec<Bucket>>,
pub unattached_policies: Vec<BucketPolicy>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Bucket {
pub id: String,
pub name: String,
pub public: bool,
pub file_size_limit: Option<u64>,
pub allowed_mime_types: Vec<String>,
pub objects: Option<u64>,
pub bytes: Option<u64>,
pub objects_over_blob_cap: Option<u64>,
pub policies: Vec<BucketPolicy>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EdgeFunctions {
pub status: SourceStatus,
pub functions: Vec<EdgeFunction>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EdgeFunction {
pub slug: String,
pub name: String,
pub status: String,
pub verify_jwt: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Realtime {
pub publications: Vec<Publication>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Publication {
pub name: String,
pub all_tables: bool,
pub operations: Vec<String>,
pub tables: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CronJob {
pub name: String,
pub schedule: String,
pub command: String,
pub active: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Finding {
pub id: String,
pub kind: String,
pub object: String,
pub classification: Classification,
pub phase: String,
pub reason: String,
pub cratefield_equivalent: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Classification {
Automatic,
NeedsWork,
Blocker,
}
impl Classification {
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Automatic => "automatic",
Self::NeedsWork => "needs work",
Self::Blocker => "blocker",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct DispositionsReport {
pub by_kind: Vec<KindDispositions>,
pub decided: Vec<DecidedItem>,
pub stale: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct KindDispositions {
pub kind: String,
pub covered: usize,
pub waived: usize,
pub undecided: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DecidedItem {
pub id: String,
pub status: Disposition,
#[serde(rename = "ref", skip_serializing_if = "Option::is_none")]
pub reference: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
impl Report {
#[must_use]
pub fn to_json(&self) -> String {
let mut out = serde_json::to_string_pretty(self).expect("a report always serializes");
out.push('\n');
out
}
}