set shell := ["bash", "-c"]
set export

RUST_LOG := "debug"

check-format:
	cargo +nightly fmt --all -- --check

check-clippy:
    cargo clippy --no-default-features --all-targets --workspace -- -D warnings
    cargo clippy --all-targets --all-features --workspace -- -D warnings
    cargo clippy -p lakekeeper --no-default-features -- -D warnings
    cargo clippy -p lakekeeper --no-default-features --features "test-utils" -- -D warnings
    cargo clippy -p lakekeeper-io --no-default-features --features "storage-in-memory" -- -D warnings
    cargo clippy -p lakekeeper-io --all-features -- -D warnings
    cargo clippy -p lakekeeper --no-default-features --features "sqlx-postgres,s3-signer,router" -- -D warnings
    cargo clippy -p lakekeeper-bin --all-features -- -D warnings
    cargo clippy -p lakekeeper-bin --no-default-features -- -D warnings

check-cargo-sort:
	cargo sort -c -w

check: check-clippy check-format check-cargo-sort

fix-format:
    cargo +nightly fmt --all
    cargo sort -w

fix:
    cargo clippy --all-targets --all-features --workspace --fix --allow-staged
    cargo +nightly fmt --all
    cargo sort -w

sqlx-prepare:
    # Exclude lakekeeper-bin so --all-features doesn't enable `ui` (pulls
    # the `lakekeeper-console` git dep). lakekeeper-bin has no sqlx::query!
    # macros, so nothing in its tree contributes to the .sqlx cache.
    cargo sqlx prepare --workspace -- --tests --workspace --exclude lakekeeper-bin --all-features

doc-test:
	cargo test --no-fail-fast --doc --all-features --workspace

unit-test: doc-test
	cargo test --profile ci --lib --all-features --workspace

test: doc-test
	cargo test --all-targets --all-features --workspace

update-rest-openapi:
    # Download from https://raw.githubusercontent.com/apache/iceberg/main/open-api/rest-catalog-open-api.yaml and put into api folder
    curl -o docs/docs/api/rest-catalog-open-api.yaml https://raw.githubusercontent.com/apache/iceberg/main/open-api/rest-catalog-open-api.yaml
    just add-return-uuid-to-rest-openapi
    just add-return-protection-status-to-rest-openapi
    just add-namespace-delete-extension-to-rest-openapi
    just clarify-table-purge-default-rest-openapi
    # Remove multiple empty lines due to https://github.com/mikefarah/yq/issues/2074
    if [[ "$(uname)" == "Darwin" ]]; then \
      sed -i '' '/^$/N;/^\n$/D' docs/docs/api/rest-catalog-open-api.yaml; \
    else \
      sed -i '/^$/N;/^\n$/D' docs/docs/api/rest-catalog-open-api.yaml; \
    fi

update-openfga:
    bash -c 'BASE_PATH=authz/openfga; \
    LAST_VERSION=$(ls $BASE_PATH | sort -r | head -n 1); \
    fga model transform --file $BASE_PATH/$LAST_VERSION/fga.mod > $BASE_PATH/$LAST_VERSION/schema.json'

test-openfga:
    bash -c 'BASE_PATH=authz/openfga; \
    LAST_VERSION=$(ls $BASE_PATH | sort -r | head -n 1); \
    fga model test --tests $BASE_PATH/$LAST_VERSION/store.fga.yaml'

check-opa:
    cd authz/opa-bridge && opa check --strict policies/
    cd authz/opa-bridge && opa fmt --diff --fail policies/ tests/
    cd authz/opa-bridge && opa test policies/ tests/ -v
    cd authz/opa-bridge && regal lint policies/

update-management-openapi:
    LAKEKEEPER__AUTHZ_BACKEND=openfga RUST_LOG=error cargo run -p lakekeeper-bin --features open-api -- management-openapi > docs/docs/api/management-open-api.yaml
    yq -i '.info.version = "0.0.0"' docs/docs/api/management-open-api.yaml

update-generic-table-openapi:
    LAKEKEEPER__AUTHZ_BACKEND=openfga RUST_LOG=error cargo run -p lakekeeper-bin --features open-api -- generic-table-openapi > docs/docs/api/generic-table-open-api.yaml
    yq -i '.info.version = "0.0.0"' docs/docs/api/generic-table-open-api.yaml

add-return-uuid-to-rest-openapi:
    yq eval '.paths."/v1/{prefix}/namespaces".get.parameters += [{"name": "returnUuids", "in": "query", "description": "If true, include the `namespace-uuids` field in the response", "required": false, "schema": {"type": "boolean", "default": false}}]' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.paths."/v1/{prefix}/namespaces/{namespace}/tables".get.parameters += [{"name": "returnUuids", "in": "query", "description": "If true, include the `table-uuids` field in the response", "required": false, "schema": {"type": "boolean", "default": false}}]' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.paths."/v1/{prefix}/namespaces/{namespace}/views".get.parameters += [{"name": "returnUuids", "in": "query", "description": "If true, include the `table-uuids` field in the response", "required": false, "schema": {"type": "boolean", "default": false}}]' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.paths."/v1/{prefix}/namespaces/{namespace}".get.parameters += [{"name": "returnUuid", "in": "query", "description": "If true, include the `namespace-uuid` field in the response", "required": false, "schema": {"type": "boolean", "default": false}}]' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.components.schemas.ListNamespacesResponse.properties["namespace-uuids"] = {"type": "array", "uniqueItems": true, "nullable": true, "items": {"type": "string"}}' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.components.schemas.GetNamespaceResponse.properties["namespace-uuid"] = {"type": "string", "nullable": true, "type": "string"}' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.components.schemas.ListTablesResponse.properties["table-uuids"] = {"type": "array", "uniqueItems": true, "nullable": true, "items": {"type": "string"}}' -i docs/docs/api/rest-catalog-open-api.yaml

add-return-protection-status-to-rest-openapi:
    yq eval '.paths."/v1/{prefix}/namespaces".get.parameters += [{"name": "returnProtectionStatus", "in": "query", "description": "If true, include the `protection-status` field in the response", "required": false, "schema": {"type": "boolean", "default": false}}]' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.paths."/v1/{prefix}/namespaces/{namespace}/tables".get.parameters += [{"name": "returnProtectionStatus", "in": "query", "description": "If true, include the `protection-status` field in the response", "required": false, "schema": {"type": "boolean", "default": false}}]' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.paths."/v1/{prefix}/namespaces/{namespace}/views".get.parameters += [{"name": "returnProtectionStatus", "in": "query", "description": "If true, include the `protection-status` field in the response", "required": false, "schema": {"type": "boolean", "default": false}}]' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.components.schemas.ListNamespacesResponse.properties["protection-status"] = {"type": "array", "nullable": true, "items": {"type": "boolean"}}' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.components.schemas.ListTablesResponse.properties["protection-status"] = {"type": "array", "nullable": true, "items": {"type": "boolean"}}' -i docs/docs/api/rest-catalog-open-api.yaml


add-namespace-delete-extension-to-rest-openapi:
    yq eval '.paths."/v1/{prefix}/namespaces/{namespace}/tables/{table}".delete.parameters += [{"name": "force", "in": "query", "description": "If true, ignore `protection-status` when dropping.", "required": false, "schema": {"type": "boolean", "default": false}}]' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.paths."/v1/{prefix}/namespaces/{namespace}/views/{view}".delete.parameters += [{"name": "force", "in": "query", "description": "If true, ignore `protection-status` when dropping.", "required": false, "schema": {"type": "boolean", "default": false}}]' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.paths."/v1/{prefix}/namespaces/{namespace}".delete.parameters += [{"name": "force", "in": "query", "description": "If force and recursive are set to true, immediately delete all contents of the namespace without considering soft-delete policies. Force has no effect without recursive=true.", "required": false, "schema": {"type": "boolean", "default": false}}, {"name": "recursive", "in": "query", "description": "Delete a namespace and its contents. This means all tables, views, and namespaces under this namespace will be deleted. The namespace itself will also be deleted. If the warehouse containing the namespace is configured with a soft-deletion profile, the `force` flag has to be provided. The deletion will not be a soft-deletion. Every table, view and namespace will be gone as soon as this call returns. Depending on whether the `purge` flag was set to true, the data will be queued for deletion too. Any pending soft-deletion expiration will be cancelled. If there is a running soft-deletion expiration, this call will fail with a `409 Conflict` error.", "required": false, "schema": {"type": "boolean", "default": false}},{"name": "purge", "in": "query", "description": "If recursive is true, also deletes table and view data. If false, only metadata is dropped from the catalog, table location remains untouched. Defaults to true for all tables managed by Lakekeeper.", "required": false, "schema": {"type": "boolean", "default": true}}]' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.paths."/v1/{prefix}/namespaces/{namespace}".delete.summary = "Drop a namespace from the catalog."' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.paths."/v1/{prefix}/namespaces/{namespace}".delete.description = "Drop a namespace from the catalog. By default, the namespace needs to be empty. You can however set `recursive=true` which will delete all tables, views and namespaces under this namespace. The namespace itself will also be deleted. If the warehouse containing the namespace is configured with a soft-deletion profile, the `force` flag has to be provided. The deletion will not be a soft-deletion. Every table, view and namespace will be gone as soon as this call returns. Depending on whether the `purge` flag was set to true, the data will be queued for deletion too. Any pending soft-deletion expiration will be cancelled. If there is a running soft-deletion expiration, this call will fail with a `409 Conflict` error."' -i docs/docs/api/rest-catalog-open-api.yaml
    # The 409 upstream only documents the "namespace not empty" case. Recursive
    # force-deletion adds a second 409: a soft-deletion expiration is running for
    # a contained table/view. Broaden the description and add an example.
    yq eval '.paths."/v1/{prefix}/namespaces/{namespace}".delete.responses."409".description = "Conflict - the namespace cannot be deleted. Either it is not empty and `recursive=true` was not provided (NamespaceNotEmptyError), or a soft-deletion expiration is currently running for a contained table or view, which blocks force-deletion (NamespaceHasRunningTabularExpirations); retry once it completes."' -i docs/docs/api/rest-catalog-open-api.yaml
    yq eval '.paths."/v1/{prefix}/namespaces/{namespace}".delete.responses."409".content."application/json".examples.NamespaceHasRunningTabularExpirationsExample = {"summary": "A soft-deletion expiration is running for a table or view in the namespace", "value": {"error": {"message": "A soft-deletion expiration is currently running for a table or view in this namespace. Retry once it completes.", "type": "NamespaceHasRunningTabularExpirations", "code": 409}}}' -i docs/docs/api/rest-catalog-open-api.yaml

# Keep the Iceberg-standard default (false) on purgeRequested, but document Lakekeeper's actual behaviour: an omitted flag is treated as purge=true for managed tables (see crates/lakekeeper/src/api/iceberg/types.rs). Clarifies the doc mismatch reported in #1832 without diverging the schema from the standard.
clarify-table-purge-default-rest-openapi:
    yq eval '(.paths."/v1/{prefix}/namespaces/{namespace}/tables/{table}".delete.parameters[] | select(.name == "purgeRequested")).description = "Whether the user requested to purge the underlying table data and metadata. Note: when omitted, Lakekeeper treats the drop as a purge for tables it manages, so the data is removed. This differs from the Iceberg REST default of false. See the Dropping Tables section of the Lakekeeper documentation."' -i docs/docs/api/rest-catalog-open-api.yaml
