use std::{collections::BTreeMap, iter::once};
use crate::{
ast::{Annotation, Annotations, AnyId, Id, InternalName},
parser::{Loc, Node},
};
use itertools::{Either, Itertools};
use nonempty::NonEmpty;
use smol_str::SmolStr;
use smol_str::ToSmolStr;
use crate::validator::json_schema;
use super::err::UserError;
pub const BUILTIN_TYPES: [&str; 3] = ["Long", "String", "Bool"];
pub(super) const CEDAR_NAMESPACE: &str = "__cedar";
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Annotated<T> {
pub data: T,
pub annotations: Annotations,
}
pub type Schema = Vec<Annotated<Namespace>>;
#[expect(clippy::type_complexity, reason = "judged to be readable enough")]
pub fn deduplicate_annotations<T>(
data: T,
annotations: Vec<Node<(Node<AnyId>, Option<Node<SmolStr>>)>>,
) -> Result<Annotated<T>, UserError> {
let mut unique_annotations: BTreeMap<Node<AnyId>, Option<Node<SmolStr>>> = BTreeMap::new();
for annotation in annotations {
let (key, value) = annotation.node;
if let Some((old_key, _)) = unique_annotations.get_key_value(&key) {
return Err(UserError::DuplicateAnnotations(
key.node,
Node::with_maybe_source_loc((), old_key.loc.clone()),
Node::with_maybe_source_loc((), key.loc),
));
} else {
unique_annotations.insert(key, value);
}
}
Ok(Annotated {
data,
annotations: unique_annotations
.into_iter()
.map(|(key, value)| {
let (val, loc) = match value {
Some(n) => (Some(n.node), n.loc),
None => (None, None),
};
(key.node, Annotation::with_optional_value(val, loc))
})
.collect(),
})
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Path(Node<PathInternal>);
impl Path {
pub fn single(basename: Id, loc: Option<Loc>) -> Self {
Self(Node::with_maybe_source_loc(
PathInternal {
basename,
namespace: vec![],
},
loc,
))
}
pub fn new(basename: Id, namespace: impl IntoIterator<Item = Id>, loc: Option<Loc>) -> Self {
let namespace = namespace.into_iter().collect();
Self(Node::with_maybe_source_loc(
PathInternal {
basename,
namespace,
},
loc,
))
}
pub fn iter(&self) -> impl Iterator<Item = &Id> {
self.0.node.iter()
}
pub fn loc(&self) -> Option<&Loc> {
self.0.loc.as_ref()
}
#[expect(
clippy::should_implement_trait,
reason = "difficult to write the `IntoIter` type for this implementation"
)]
pub fn into_iter(self) -> impl Iterator<Item = Node<Id>> {
let loc = self.0.loc;
self.0
.node
.into_iter()
.map(move |x| Node::with_maybe_source_loc(x, loc.clone()))
}
pub fn split_last(self) -> (Vec<Id>, Id) {
(self.0.node.namespace, self.0.node.basename)
}
pub fn is_in_cedar(&self) -> bool {
self.0.node.is_in_cedar()
}
}
impl From<Path> for InternalName {
fn from(value: Path) -> Self {
InternalName::new(value.0.node.basename, value.0.node.namespace, value.0.loc)
}
}
impl std::fmt::Display for Path {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.node)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct PathInternal {
basename: Id,
namespace: Vec<Id>,
}
impl PathInternal {
fn iter(&self) -> impl Iterator<Item = &Id> {
self.namespace.iter().chain(once(&self.basename))
}
fn is_in_cedar(&self) -> bool {
match self.namespace.as_slice() {
[id] => id.as_ref() == CEDAR_NAMESPACE,
_ => false,
}
}
}
impl IntoIterator for PathInternal {
type Item = Id;
type IntoIter = std::iter::Chain<<Vec<Id> as IntoIterator>::IntoIter, std::iter::Once<Id>>;
fn into_iter(self) -> Self::IntoIter {
self.namespace.into_iter().chain(once(self.basename))
}
}
impl std::fmt::Display for PathInternal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.namespace.is_empty() {
write!(f, "{}", self.basename)
} else {
let namespace = self.namespace.iter().map(|id| id.as_ref()).join("::");
write!(f, "{namespace}::{}", self.basename)
}
}
}
#[derive(Debug, Clone)]
pub struct QualName {
pub path: Option<Path>,
pub eid: SmolStr,
}
impl QualName {
pub fn unqualified(eid: SmolStr) -> Self {
Self { path: None, eid }
}
pub fn qualified(path: Path, eid: SmolStr) -> Self {
Self {
path: Some(path),
eid,
}
}
}
#[derive(Debug, Clone)]
pub struct Namespace {
pub name: Option<Path>,
pub decls: Vec<Annotated<Node<Declaration>>>,
pub loc: Option<Loc>,
}
impl Namespace {
pub fn is_unqualified(&self) -> bool {
self.name.is_none()
}
}
pub trait Decl {
fn names(&self) -> Vec<Node<SmolStr>>;
}
#[derive(Debug, Clone)]
pub enum Declaration {
Entity(EntityDecl),
Action(ActionDecl),
Type(TypeDecl),
}
#[derive(Debug, Clone)]
pub struct TypeDecl {
pub name: Node<Id>,
pub def: Node<Type>,
}
impl Decl for TypeDecl {
fn names(&self) -> Vec<Node<SmolStr>> {
vec![self.name.clone().map(|id| id.to_smolstr())]
}
}
#[derive(Debug, Clone)]
pub enum EntityDecl {
Standard(StandardEntityDecl),
Enum(EnumEntityDecl),
}
impl EntityDecl {
pub fn names(&self) -> impl Iterator<Item = &Node<Id>> + '_ {
match self {
Self::Enum(d) => d.names.iter(),
Self::Standard(d) => d.names.iter(),
}
}
}
#[derive(Debug, Clone)]
pub struct StandardEntityDecl {
pub names: NonEmpty<Node<Id>>,
pub member_of_types: Vec<Path>,
pub attrs: Node<Vec<Node<Annotated<AttrDecl>>>>,
pub tags: Option<Node<Type>>,
}
#[derive(Debug, Clone)]
pub struct EnumEntityDecl {
pub names: NonEmpty<Node<Id>>,
pub choices: NonEmpty<Node<SmolStr>>,
}
#[derive(Debug, Clone)]
pub enum Type {
Set(Box<Node<Type>>),
Ident(Path),
Record(Vec<Node<Annotated<AttrDecl>>>),
}
#[derive(Debug, Clone)]
pub enum PrimitiveType {
Long,
String,
Bool,
}
impl<N> From<PrimitiveType> for json_schema::TypeVariant<N> {
fn from(value: PrimitiveType) -> Self {
match value {
PrimitiveType::Long => json_schema::TypeVariant::Long,
PrimitiveType::String => json_schema::TypeVariant::String,
PrimitiveType::Bool => json_schema::TypeVariant::Boolean,
}
}
}
#[derive(Debug, Clone)]
pub struct AttrDecl {
pub name: Node<SmolStr>,
pub required: bool,
pub ty: Node<Type>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PR {
Principal,
Resource,
}
impl std::fmt::Display for PR {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PR::Principal => write!(f, "principal"),
PR::Resource => write!(f, "resource"),
}
}
}
#[derive(Debug, Clone)]
pub struct PRAppDecl {
pub kind: Node<PR>,
pub entity_tys: Option<NonEmpty<Path>>,
}
#[derive(Debug, Clone)]
pub enum AppDecl {
PR(PRAppDecl),
Context(Either<Path, Node<Vec<Node<Annotated<AttrDecl>>>>>),
}
#[derive(Debug, Clone)]
pub struct ActionDecl {
pub names: NonEmpty<Node<SmolStr>>,
pub parents: Option<NonEmpty<Node<QualName>>>,
pub app_decls: Option<Node<NonEmpty<Node<AppDecl>>>>,
}
impl Decl for ActionDecl {
fn names(&self) -> Vec<Node<SmolStr>> {
self.names.iter().cloned().collect()
}
}
#[cfg(test)]
mod test {
use std::sync::Arc;
use super::*;
fn loc() -> Loc {
Loc::new((1, 1), Arc::from("foo"))
}
#[test]
fn path_iter() {
let p = Path::new(
"baz".parse().unwrap(),
["foo".parse().unwrap(), "bar".parse().unwrap()],
Some(loc()),
);
let expected: Vec<Id> = vec![
"foo".parse().unwrap(),
"bar".parse().unwrap(),
"baz".parse().unwrap(),
];
let expected_borrowed = expected.iter().collect::<Vec<_>>();
let borrowed = p.iter().collect::<Vec<_>>();
assert_eq!(borrowed, expected_borrowed);
let moved = p.into_iter().map(|n| n.node).collect::<Vec<_>>();
assert_eq!(moved, expected);
}
}