Skip to content
Merged
16 changes: 8 additions & 8 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,15 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run Lints
run: |
cargo clippy --fix
cargo clippy
cargo run -p rules_check
- name: Check for git diff after lint fix
run: |
if [[ $(git status --porcelain) ]]; then
git status
git diff
exit 1
fi
# - name: Check for git diff after lint fix
# run: |
# if [[ $(git status --porcelain) ]]; then
# git status
# git diff
# exit 1
# fi

# check-dependencies:
# name: Check Dependencies
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
- name: 🛠️ Run Build
run: cargo build -p pglt_cli --release --target ${{ matrix.config.target }}

# windows is a special snowflake to, it saves binaries as .exe
# windows is a special snowflake too, it saves binaries as .exe
- name: 👦 Name the Binary
if: matrix.config.os == 'windows-latest'
run: |
Expand Down Expand Up @@ -124,7 +124,9 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
body: ${{ steps.create_changelog.outputs.content }}
tag_name: ${{ steps.create_changelog.outputs.version }}
files: pglt_*
files: |
pglt_*
docs/schemas/latest/schema.json
fail_on_unmatched_files: true
draft: true

Expand Down
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions crates/pglt_configuration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ version = "0.0.0"


[dependencies]
biome_deserialize = { workspace = true }
biome_deserialize = { workspace = true, features = ["schema"] }
biome_deserialize_macros = { workspace = true }
bpaf = { workspace = true }
indexmap = { workspace = true }
pglt_analyse = { workspace = true }
pglt_analyser = { workspace = true }
pglt_console = { workspace = true }
Expand All @@ -30,4 +31,4 @@ toml = { workspace = true }
doctest = false

[features]
schema = ["dep:schemars"]
schema = ["dep:schemars", "schemars/indexmap"]
1 change: 1 addition & 0 deletions crates/pglt_configuration/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
/// The configuration of the database connection.
#[derive(Clone, Debug, Deserialize, Eq, Partial, PartialEq, Serialize)]
#[partial(derive(Bpaf, Clone, Eq, PartialEq, Merge))]
#[partial(cfg_attr(feature = "schema", derive(schemars::JsonSchema)))]
#[partial(serde(rename_all = "snake_case", default, deny_unknown_fields))]
pub struct DatabaseConfiguration {
/// The host of the database.
Expand Down
1 change: 1 addition & 0 deletions crates/pglt_configuration/src/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub const DEFAULT_FILE_SIZE_LIMIT: NonZeroU64 =
/// The configuration of the filesystem
#[derive(Clone, Debug, Deserialize, Eq, Partial, PartialEq, Serialize)]
#[partial(derive(Bpaf, Clone, Eq, PartialEq, Merge))]
#[partial(cfg_attr(feature = "schema", derive(schemars::JsonSchema)))]
#[partial(serde(rename_all = "snake_case", default, deny_unknown_fields))]
pub struct FilesConfiguration {
/// The maximum allowed size for source code files in bytes. Files above
Expand Down
1 change: 1 addition & 0 deletions crates/pglt_configuration/src/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Eq, Partial, PartialEq, Serialize, Default)]
#[partial(derive(Bpaf, Clone, Eq, PartialEq, Merge))]
#[partial(serde(rename_all = "snake_case", default, deny_unknown_fields))]
#[partial(cfg_attr(feature = "schema", derive(schemars::JsonSchema)))]
pub struct MigrationsConfiguration {
/// The directory where the migration files are stored
#[partial(bpaf(long("migrations-dir")))]
Expand Down
7 changes: 5 additions & 2 deletions docs/codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ regex = { workspace = true }
toml = { workspace = true }
anyhow = { workspace = true }
bpaf = { workspace = true, features = ["docgen"] }
schemars = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
pulldown-cmark = "0.12.2"

pglt_configuration = { workspace = true }
pglt_configuration = { workspace = true, features = ["schema"] }
pglt_flags = { workspace = true }
pglt_cli = { workspace = true }
pglt_analyse = { workspace = true }
Expand All @@ -28,5 +32,4 @@ pglt_workspace = { workspace = true }
pglt_statement_splitter = { workspace = true }
pglt_console = { workspace = true }
biome_string_case = { workspace = true }
pulldown-cmark = "0.12.2"

1 change: 1 addition & 0 deletions docs/codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ pub mod env_variables;
pub mod rules_docs;
pub mod rules_index;
pub mod rules_sources;
pub mod schema;

mod utils;
2 changes: 2 additions & 0 deletions docs/codegen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use docs_codegen::env_variables::generate_env_variables;
use docs_codegen::rules_docs::generate_rules_docs;
use docs_codegen::rules_index::generate_rules_index;
use docs_codegen::rules_sources::generate_rule_sources;
use docs_codegen::schema::generate_schema;

fn docs_root() -> PathBuf {
let dir =
Expand All @@ -23,6 +24,7 @@ fn main() -> anyhow::Result<()> {
generate_rules_docs(&docs_root)?;
generate_rules_index(&docs_root)?;
generate_rule_sources(&docs_root)?;
generate_schema(&docs_root)?;

Ok(())
}
201 changes: 201 additions & 0 deletions docs/codegen/src/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
use pglt_configuration::{PartialConfiguration, VERSION};
use schemars::{
schema::{RootSchema, Schema, SchemaObject},
schema_for,
};
use serde_json::to_string_pretty;
use std::{fs, path::Path};

/// Generates the lint rules index.
///
/// * `docs_dir`: Path to the docs directory.
pub fn generate_schema(docs_dir: &Path) -> anyhow::Result<()> {
let schemas_dir = docs_dir.join("schemas");
let latest_schema_dir = schemas_dir.join("latest");
let latest_schema_path = latest_schema_dir.join("schema.json");

let version_schema_dir = schemas_dir.join(VERSION);
let version_schema_path = version_schema_dir.join("schema.json");

if !latest_schema_dir.exists() {
fs::create_dir_all(&latest_schema_dir)?;
}

if !version_schema_dir.exists() {
fs::create_dir_all(&version_schema_dir)?;
}

let schema_content = get_configuration_schema_content()?;

fs::write(latest_schema_path, &schema_content)?;
fs::write(version_schema_path, &schema_content)?;

Ok(())
}

/// Get the content of the configuration schema
pub(crate) fn get_configuration_schema_content() -> anyhow::Result<String> {
let schema = rename_partial_references_in_schema(schema_for!(PartialConfiguration));

Ok(to_string_pretty(&schema)?)
}

/// Strips all "Partial" prefixes from type names in the schema.
///
/// We do this to avoid leaking our `Partial` derive macro to the outside world,
/// since it should be just an implementation detail.
fn rename_partial_references_in_schema(mut schema: RootSchema) -> RootSchema {
if let Some(meta) = schema.schema.metadata.as_mut() {
if let Some(title) = meta.title.as_ref() {
if let Some(stripped) = title.strip_prefix("Partial") {
meta.title = Some(stripped.to_string());
} else if title == "RuleWithOptions_for_Null" {
meta.title = Some("RuleWithNoOptions".to_string());
} else if title == "RuleWithFixOptions_for_Null" {
meta.title = Some("RuleWithFixNoOptions".to_string());
} else if title == "RuleConfiguration_for_Null" {
meta.title = Some("RuleConfiguration".to_string());
} else if title == "RuleFixConfiguration_for_Null" {
meta.title = Some("RuleFixConfiguration".to_string());
} else if let Some(stripped) = title.strip_prefix("RuleWithOptions_for_") {
meta.title = Some(format!("RuleWith{stripped}"));
} else if let Some(stripped) = title.strip_prefix("RuleWithFixOptions_for_") {
meta.title = Some(format!("RuleWith{stripped}"));
} else if let Some(stripped) = title
.strip_prefix("RuleConfiguration_for_")
.map(|x| x.strip_suffix("Options").unwrap_or(x))
{
meta.title = Some(format!("{stripped}Configuration"));
} else if let Some(stripped) = title
.strip_prefix("RuleFixConfiguration_for_")
.map(|x| x.strip_suffix("Options").unwrap_or(x))
{
meta.title = Some(format!("{stripped}Configuration"));
}
}
}

rename_partial_references_in_schema_object(&mut schema.schema);

schema.definitions = schema
.definitions
.into_iter()
.map(|(mut key, mut schema)| {
if let Some(stripped) = key.strip_prefix("Partial") {
key = stripped.to_string();
} else if key == "RuleWithOptions_for_Null" || key == "RuleWithFixOptions_for_Null" {
key = if key == "RuleWithOptions_for_Null" {
"RuleWithNoOptions".to_string()
} else {
"RuleWithFixNoOptions".to_string()
};
if let Schema::Object(schema_object) = &mut schema {
if let Some(object) = &mut schema_object.object {
object.required.remove("options");
object.properties.remove("options");
}
}
} else if key == "RuleConfiguration_for_Null" {
key = "RuleConfiguration".to_string();
} else if key == "RuleFixConfiguration_for_Null" {
key = "RuleFixConfiguration".to_string();
} else if let Some(stripped) = key.strip_prefix("RuleWithOptions_for_") {
key = format!("RuleWith{stripped}");
} else if let Some(stripped) = key.strip_prefix("RuleWithFixOptions_for_") {
key = format!("RuleWith{stripped}");
} else if let Some(stripped) = key
.strip_prefix("RuleConfiguration_for_")
.map(|x| x.strip_suffix("Options").unwrap_or(x))
{
key = format!("{stripped}Configuration");
} else if let Some(stripped) = key
.strip_prefix("RuleFixConfiguration_for_")
.map(|x| x.strip_suffix("Options").unwrap_or(x))
{
key = format!("{stripped}Configuration");
}

if let Schema::Object(object) = &mut schema {
rename_partial_references_in_schema_object(object);
}

(key, schema)
})
.collect();

schema
}

fn rename_partial_references_in_schema_object(object: &mut SchemaObject) {
if let Some(object) = &mut object.object {
for prop_schema in object.properties.values_mut() {
if let Schema::Object(object) = prop_schema {
rename_partial_references_in_schema_object(object);
}
}
}

if let Some(reference) = &mut object.reference {
if let Some(stripped) = reference.strip_prefix("#/definitions/Partial") {
*reference = format!("#/definitions/{stripped}");
} else if reference == "#/definitions/RuleWithOptions_for_Null" {
*reference = "#/definitions/RuleWithNoOptions".to_string();
} else if reference == "#/definitions/RuleWithFixOptions_for_Null" {
*reference = "#/definitions/RuleWithFixNoOptions".to_string();
} else if reference == "#/definitions/RuleConfiguration_for_Null" {
*reference = "#/definitions/RuleConfiguration".to_string();
} else if reference == "#/definitions/RuleFixConfiguration_for_Null" {
*reference = "#/definitions/RuleFixConfiguration".to_string();
} else if let Some(stripped) = reference.strip_prefix("#/definitions/RuleWithOptions_for_")
{
*reference = format!("#/definitions/RuleWith{stripped}");
} else if let Some(stripped) =
reference.strip_prefix("#/definitions/RuleWithFixOptions_for_")
{
*reference = format!("#/definitions/RuleWith{stripped}");
} else if let Some(stripped) = reference
.strip_prefix("#/definitions/RuleConfiguration_for_")
.map(|x| x.strip_suffix("Options").unwrap_or(x))
{
*reference = format!("#/definitions/{stripped}Configuration");
} else if let Some(stripped) = reference
.strip_prefix("#/definitions/RuleFixConfiguration_for_")
.map(|x| x.strip_suffix("Options").unwrap_or(x))
{
*reference = format!("#/definitions/{stripped}Configuration");
}
}

if let Some(subschemas) = &mut object.subschemas {
rename_partial_references_in_optional_schema_vec(&mut subschemas.all_of);
rename_partial_references_in_optional_schema_vec(&mut subschemas.any_of);
rename_partial_references_in_optional_schema_vec(&mut subschemas.one_of);

rename_partial_references_in_optional_schema_box(&mut subschemas.not);
rename_partial_references_in_optional_schema_box(&mut subschemas.if_schema);
rename_partial_references_in_optional_schema_box(&mut subschemas.then_schema);
rename_partial_references_in_optional_schema_box(&mut subschemas.else_schema);
}
}

fn rename_partial_references_in_optional_schema_box(schema: &mut Option<Box<Schema>>) {
if let Some(schema) = schema {
if let Schema::Object(object) = schema.as_mut() {
rename_partial_references_in_schema_object(object);
}
}
}

fn rename_partial_references_in_optional_schema_vec(schemas: &mut Option<Vec<Schema>>) {
if let Some(schemas) = schemas {
rename_partial_references_in_schema_slice(schemas);
}
}

fn rename_partial_references_in_schema_slice(schemas: &mut [Schema]) {
for schema in schemas {
if let Schema::Object(object) = schema {
rename_partial_references_in_schema_object(object);
}
}
}
6 changes: 3 additions & 3 deletions docs/codegen/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ pub(crate) fn replace_section(
#[derive(Default)]
pub(crate) struct LintRulesVisitor {
/// This is mapped to:
/// group (e.g. "safety") -> <list of rules>
/// where <list of rules> is:
/// <rule name> -> metadata
/// - group (correctness) -> list of rules
/// - list or rules is mapped to
/// - rule name -> metadata
pub(crate) groups: BTreeMap<&'static str, BTreeMap<&'static str, RuleMetadata>>,
}

Expand Down
Loading