use std::collections::BTreeMap;
use std::sync::Arc;
use serde_json::Value as JsonValue;
use crate::diagnostics::{
CustomRuleDefinition, CustomRuleId, DiagnosticLocation, DocId, LintStage,
};
use super::ids::{QualifierId, ResourceId, ValueKey, VariableId, WorkspacePath};
use super::targets::RegisteredLintSelector;
pub(in crate::lint) struct ManifestNode {
pub(in crate::lint) doc: DocId,
pub(in crate::lint) location: DiagnosticLocation,
pub(in crate::lint) extends: WorkspaceExtendsCollection,
}
pub(in crate::lint) struct WorkspaceExtendNode {
pub(in crate::lint) source: String,
pub(in crate::lint) location: DiagnosticLocation,
}
pub(in crate::lint) enum WorkspaceExtendsCollection {
Missing,
Invalid {
location: DiagnosticLocation,
},
Sources {
location: DiagnosticLocation,
values: Vec<WorkspaceExtendNode>,
},
}
pub(in crate::lint) struct QualifierNode {
pub(in crate::lint) doc: DocId,
pub(in crate::lint) id: QualifierId,
pub(in crate::lint) location: DiagnosticLocation,
pub(in crate::lint) schema_version: ProjectField<i64>,
pub(in crate::lint) description: Option<ProjectField<String>>,
pub(in crate::lint) predicates: PredicateCollection,
}
pub(in crate::lint) struct PredicateNode {
pub(in crate::lint) index: usize,
pub(in crate::lint) location: DiagnosticLocation,
pub(in crate::lint) attribute: ProjectField<String>,
pub(in crate::lint) op: ProjectField<PredicateOp>,
pub(in crate::lint) value: Option<ValueShapeNode>,
pub(in crate::lint) salt: Option<ProjectField<String>>,
pub(in crate::lint) range: Option<BucketRangeNode>,
pub(in crate::lint) has_bucket_value: bool,
}
pub(in crate::lint) enum PredicateCollection {
Missing { location: DiagnosticLocation },
Invalid { location: DiagnosticLocation },
Predicates(Vec<PredicateNode>),
}
#[derive(Clone)]
pub(in crate::lint) enum PredicateOp {
Eq,
Neq,
In,
NotIn,
Gt,
Gte,
Lt,
Lte,
Bucket,
Unknown(String),
}
impl PredicateOp {
pub(in crate::lint) const COMPLETION_LABELS: &'static [&'static str] = &[
"eq", "neq", "in", "not_in", "gt", "gte", "lt", "lte", "bucket",
];
pub(in crate::lint) fn from_str(op: &str) -> Self {
match op {
"eq" => Self::Eq,
"neq" => Self::Neq,
"in" => Self::In,
"not_in" => Self::NotIn,
"gt" => Self::Gt,
"gte" => Self::Gte,
"lt" => Self::Lt,
"lte" => Self::Lte,
"bucket" => Self::Bucket,
op => Self::Unknown(op.to_owned()),
}
}
pub(in crate::lint) fn as_str(&self) -> &str {
match self {
Self::Eq => "eq",
Self::Neq => "neq",
Self::In => "in",
Self::NotIn => "not_in",
Self::Gt => "gt",
Self::Gte => "gte",
Self::Lt => "lt",
Self::Lte => "lte",
Self::Bucket => "bucket",
Self::Unknown(op) => op,
}
}
}
pub(in crate::lint) struct BucketRangeNode {
pub(in crate::lint) location: DiagnosticLocation,
pub(in crate::lint) is_array: bool,
pub(in crate::lint) len: usize,
pub(in crate::lint) start: Option<i64>,
pub(in crate::lint) end: Option<i64>,
}
pub(in crate::lint) struct VariableNode {
pub(in crate::lint) doc: DocId,
pub(in crate::lint) id: VariableId,
pub(in crate::lint) location: DiagnosticLocation,
pub(in crate::lint) schema_version: ProjectField<i64>,
pub(in crate::lint) description: Option<ProjectField<String>>,
pub(in crate::lint) type_source: TypeSourceNode,
pub(in crate::lint) values: ValuesNode,
pub(in crate::lint) resolve: ResolveNode,
}
pub(in crate::lint) enum TypeSourceNode {
Primitive(Spanned<String>),
Resource(Spanned<String>),
Schema(Spanned<String>),
Missing { location: DiagnosticLocation },
Conflict { location: DiagnosticLocation },
Invalid { location: DiagnosticLocation },
}
impl TypeSourceNode {
pub(in crate::lint) fn location(&self) -> DiagnosticLocation {
match self {
Self::Primitive(type_name) => type_name.location.clone(),
Self::Resource(resource) => resource.location.clone(),
Self::Schema(schema) => schema.location.clone(),
Self::Missing { location }
| Self::Conflict { location }
| Self::Invalid { location } => location.clone(),
}
}
}
pub(in crate::lint) struct ResourceNode {
pub(in crate::lint) doc: DocId,
pub(in crate::lint) id: ResourceId,
pub(in crate::lint) location: DiagnosticLocation,
pub(in crate::lint) schema_version: ProjectField<i64>,
pub(in crate::lint) description: Option<ProjectField<String>>,
pub(in crate::lint) schema: ProjectField<String>,
}
pub(in crate::lint) struct ResourceObjectNode {
pub(in crate::lint) resource_id: ResourceId,
pub(in crate::lint) key: ValueKey,
pub(in crate::lint) location: DiagnosticLocation,
pub(in crate::lint) value: JsonValue,
}
pub(in crate::lint) struct ValuesNode {
pub(in crate::lint) location: DiagnosticLocation,
pub(in crate::lint) inline_values: BTreeMap<ValueKey, ValueNode>,
pub(in crate::lint) invalid_shape: bool,
}
pub(in crate::lint) struct ValueNode {
pub(in crate::lint) variable_id: VariableId,
pub(in crate::lint) key: ValueKey,
pub(in crate::lint) location: DiagnosticLocation,
pub(in crate::lint) value: JsonValue,
pub(in crate::lint) origin: ValueOrigin,
}
pub(in crate::lint) enum ValueOrigin {
Inline { variable_doc: DocId },
}
pub(in crate::lint) struct SchemaNode {
#[allow(dead_code)]
pub(in crate::lint) doc: DocId,
pub(in crate::lint) path: WorkspacePath,
pub(in crate::lint) location: DiagnosticLocation,
pub(in crate::lint) json: Option<JsonValue>,
pub(in crate::lint) validator: Option<Arc<jsonschema::Validator>>,
pub(in crate::lint) invalid_message: Option<String>,
}
#[derive(Default)]
pub(in crate::lint) struct CustomLintRegistry {
pub(in crate::lint) rules: BTreeMap<CustomRuleId, CustomRuleDefinitionNode>,
pub(in crate::lint) files: BTreeMap<WorkspacePath, CustomLintFileNode>,
pub(in crate::lint) registrations: Vec<CustomLintRegistration>,
}
#[derive(Clone)]
pub(in crate::lint) struct CustomRuleDefinitionNode {
pub(in crate::lint) definition: CustomRuleDefinition,
#[allow(dead_code)]
pub(in crate::lint) location: DiagnosticLocation,
}
#[derive(Clone)]
pub(in crate::lint) struct CustomLintFileNode {
pub(in crate::lint) path: WorkspacePath,
pub(in crate::lint) doc: DocId,
pub(in crate::lint) location: DiagnosticLocation,
}
#[derive(Clone)]
pub(in crate::lint) struct CustomLintRegistration {
pub(in crate::lint) file_path: WorkspacePath,
pub(in crate::lint) rule: CustomRuleId,
pub(in crate::lint) stage: LintStage,
pub(in crate::lint) selector: RegisteredLintSelector,
pub(in crate::lint) handler: String,
#[allow(dead_code)]
pub(in crate::lint) location: DiagnosticLocation,
}
pub(in crate::lint) enum ResolveNode {
Missing {
location: DiagnosticLocation,
},
Invalid {
location: DiagnosticLocation,
},
Resolve {
location: DiagnosticLocation,
default: Box<ProjectField<String>>,
rules: RuleCollection,
},
}
impl ResolveNode {
pub(in crate::lint) fn location(&self) -> DiagnosticLocation {
match self {
Self::Missing { location }
| Self::Invalid { location }
| Self::Resolve { location, .. } => location.clone(),
}
}
}
pub(in crate::lint) enum RuleCollection {
Rules(Vec<VariableRuleNode>),
Invalid { location: DiagnosticLocation },
}
pub(in crate::lint) struct VariableRuleNode {
pub(in crate::lint) index: usize,
pub(in crate::lint) location: DiagnosticLocation,
pub(in crate::lint) qualifier: ProjectField<String>,
pub(in crate::lint) value: ProjectField<String>,
pub(in crate::lint) invalid_shape: bool,
}
#[derive(Clone)]
pub(in crate::lint) struct Spanned<T> {
pub(in crate::lint) value: T,
pub(in crate::lint) location: DiagnosticLocation,
}
pub(in crate::lint) enum ProjectField<T> {
Present(Spanned<T>),
Invalid { location: DiagnosticLocation },
Missing { location: DiagnosticLocation },
}
impl<T> ProjectField<T> {
pub(in crate::lint) fn location(&self) -> DiagnosticLocation {
match self {
Self::Present(value) => value.location.clone(),
Self::Invalid { location } | Self::Missing { location } => location.clone(),
}
}
}
pub(in crate::lint) struct ValueShapeNode {
pub(in crate::lint) location: DiagnosticLocation,
pub(in crate::lint) shape: ValueShape,
pub(in crate::lint) value: JsonValue,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(in crate::lint) enum ValueShape {
String,
Integer,
Float,
Boolean,
Array,
Table,
}
impl ValueShape {
pub(in crate::lint) fn as_str(self) -> &'static str {
match self {
Self::String => "string",
Self::Integer => "int",
Self::Float => "number",
Self::Boolean => "bool",
Self::Array => "list",
Self::Table => "table",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn predicate_completion_labels_stay_in_sync_with_known_ops() {
for label in PredicateOp::COMPLETION_LABELS {
let op = PredicateOp::from_str(label);
assert_eq!(op.as_str(), *label);
assert!(!matches!(op, PredicateOp::Unknown(_)));
}
let known = [
PredicateOp::Eq,
PredicateOp::Neq,
PredicateOp::In,
PredicateOp::NotIn,
PredicateOp::Gt,
PredicateOp::Gte,
PredicateOp::Lt,
PredicateOp::Lte,
PredicateOp::Bucket,
];
for op in known {
assert!(
PredicateOp::COMPLETION_LABELS.contains(&op.as_str()),
"missing completion label for {}",
op.as_str()
);
}
}
}