Something went wrong. Try again.
A human-friendly DSL for ATProto Lexicons
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401use alloc::string::String;use alloc::vec::Vec;
use crate::span::{Span, Spanned};#[derive(Debug, Clone, PartialEq, Eq)]pub struct Ident { pub name: String, pub span: Span,}
impl Ident { pub fn new(name: String, span: Span) -> Self { Self { name, span } }}
impl Spanned for Ident { fn span(&self) -> Span { self.span }}
/// A complete lexicon (top-level AST node)#[derive(Debug, Clone, PartialEq)]pub struct Lexicon { pub items: Vec<Item>, pub span: Span,}
/// Top-level items in a lexicon#[derive(Debug, Clone, PartialEq)]pub enum Item { Record(Record), InlineType(InlineType), DefType(DefType), Token(Token), Query(Query), Procedure(Procedure), Subscription(Subscription), Use(Use), /// `self { }` — a declaration representing the lexicon itself. Docs /// and annotations attached to it map to the lexicon's top-level /// fields in JSON (e.g. `description`, vendor extensions). Body is /// empty in V1; the `{}` shape is reserved for future contents. SelfItem(SelfItem),}
impl Spanned for Item { fn span(&self) -> Span { match self { Item::Record(r) => r.span, Item::InlineType(i) => i.span, Item::DefType(d) => d.span, Item::Token(t) => t.span, Item::Query(q) => q.span, Item::Procedure(p) => p.span, Item::Subscription(s) => s.span, Item::Use(u) => u.span, Item::SelfItem(s) => s.span, } }}
/// The lexicon-as-item. See [`Item::SelfItem`] for the semantic role.#[derive(Debug, Clone, PartialEq)]pub struct SelfItem { pub docs: Vec<DocComment>, pub annotations: Vec<Annotation>, pub span: Span,}
/// Documentation comment#[derive(Debug, Clone, PartialEq, Eq)]pub struct DocComment { pub text: String, pub span: Span,}
/// Annotation (e.g., @deprecated, @rust:deprecated, @rust,typescript:deprecated)#[derive(Debug, Clone, PartialEq)]pub struct Annotation { /// Generator selectors (empty for bare annotations visible to all generators) pub selectors: Vec<Ident>, /// Annotation name pub name: Ident, /// Annotation arguments pub args: Vec<AnnotationArg>, pub span: Span,}
#[derive(Debug, Clone, PartialEq)]pub enum AnnotationArg { /// Positional argument Positional(AnnotationValue), /// Named argument Named { name: Ident, value: AnnotationValue },}
#[derive(Debug, Clone, PartialEq)]pub enum AnnotationValue { String(String), Number(f64), Boolean(bool), /// JSON `null`. Used by `@const` to represent explicit-null values /// that appear in source lexicons' extension fields. Null, /// JSON array. Element types are freely mixed, matching JSON semantics. Array(Vec<AnnotationValue>), /// JSON object literal — a map with string keys. Keys permit any /// JSON-legal form (including hyphens, e.g. `"x-vendor-flag"`). Object(Vec<(String, AnnotationValue)>), /// A type path resolved through the workspace. Used by `@reference` /// to name an MLF type by path; codegen resolves to an NSID string. Reference(Path),}
/// A record definition#[derive(Debug, Clone, PartialEq)]pub struct Record { pub docs: Vec<DocComment>, pub annotations: Vec<Annotation>, pub name: Ident, pub fields: Vec<Field>, pub span: Span,}
/// A field in a record or alias#[derive(Debug, Clone, PartialEq)]pub struct Field { pub docs: Vec<DocComment>, pub annotations: Vec<Annotation>, pub name: Ident, pub ty: Type, pub optional: bool, pub span: Span,}
/// An inline type definition (expands at point of use)#[derive(Debug, Clone, PartialEq)]pub struct InlineType { pub docs: Vec<DocComment>, pub annotations: Vec<Annotation>, pub name: Ident, pub ty: Type, pub span: Span,}
/// A def type definition (becomes a named def in lexicon)#[derive(Debug, Clone, PartialEq)]pub struct DefType { pub docs: Vec<DocComment>, pub annotations: Vec<Annotation>, pub name: Ident, pub ty: Type, pub span: Span,}
/// A token definition#[derive(Debug, Clone, PartialEq)]pub struct Token { pub docs: Vec<DocComment>, pub annotations: Vec<Annotation>, pub name: Ident, pub span: Span,}
/// A query definition#[derive(Debug, Clone, PartialEq)]pub struct Query { pub docs: Vec<DocComment>, pub annotations: Vec<Annotation>, pub name: Ident, pub params: Vec<Field>, pub returns: ReturnType, pub span: Span,}
/// A procedure definition#[derive(Debug, Clone, PartialEq)]pub struct Procedure { pub docs: Vec<DocComment>, pub annotations: Vec<Annotation>, pub name: Ident, pub params: Vec<Field>, pub returns: ReturnType, pub span: Span,}
/// A subscription definition#[derive(Debug, Clone, PartialEq)]pub struct Subscription { pub docs: Vec<DocComment>, pub annotations: Vec<Annotation>, pub name: Ident, pub params: Vec<Field>, pub messages: Option<Type>, // Union of message types (optional) pub span: Span,}
/// Return type for queries and procedures#[derive(Debug, Clone, PartialEq)]pub enum ReturnType { /// No return type specified None { span: Span }, /// Simple return type Type(Type), /// Return type with error handling TypeWithErrors { success: Type, errors: Vec<ErrorDef>, span: Span, },}
impl ReturnType { pub fn span(&self) -> Span { match self { ReturnType::None { span } => *span, ReturnType::Type(ty) => ty.span(), ReturnType::TypeWithErrors { span, .. } => *span, } }}
/// An error definition in a query/procedure#[derive(Debug, Clone, PartialEq)]pub struct ErrorDef { pub docs: Vec<DocComment>, pub name: Ident, pub span: Span,}
/// A use statement#[derive(Debug, Clone, PartialEq)]pub struct Use { pub path: Path, pub imports: UseImports, pub span: Span,}
/// What to import from a path#[derive(Debug, Clone, PartialEq)]pub enum UseImports { /// Import all (*) All, /// Import specific items Items(Vec<UseItem>),}
/// An individual use item#[derive(Debug, Clone, PartialEq)]pub struct UseItem { pub name: Ident, pub alias: Option<Ident>,}
/// A dotted path (e.g., app.bsky.feed.post)#[derive(Debug, Clone, PartialEq, Eq)]pub struct Path { pub segments: Vec<Ident>, pub span: Span,}
impl Path { pub fn to_string(&self) -> String { self.segments .iter() .map(|s| s.name.as_str()) .collect::<Vec<_>>() .join(".") }}
/// A type expression#[derive(Debug, Clone, PartialEq)]pub enum Type { /// Primitive type (string, integer, boolean, etc.) Primitive { kind: PrimitiveType, span: Span }, /// Reference to another type (local or cross-file) Reference { path: Path, span: Span }, /// Array type Array { inner: Box<Type>, span: Span }, /// Union type Union { types: Vec<Type>, closed: bool, span: Span, }, /// Object type (inline) Object { fields: Vec<Field>, span: Span }, /// Parenthesized type (for grouping, e.g., (A | B)[]) Parenthesized { inner: Box<Type>, span: Span }, /// Constrained type Constrained { base: Box<Type>, constraints: Vec<Constraint>, span: Span, }, /// Unknown type Unknown { span: Span },}
impl Spanned for Type { fn span(&self) -> Span { match self { Type::Primitive { span, .. } => *span, Type::Reference { span, .. } => *span, Type::Array { span, .. } => *span, Type::Union { span, .. } => *span, Type::Object { span, .. } => *span, Type::Parenthesized { span, .. } => *span, Type::Constrained { span, .. } => *span, Type::Unknown { span } => *span, } }}
/// Primitive types#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum PrimitiveType { Null, Boolean, Integer, String, Bytes, Blob,}
/// Type constraints#[derive(Debug, Clone, PartialEq)]pub enum Constraint { // String constraints MinLength { value: usize, span: Span }, MaxLength { value: usize, span: Span }, MinGraphemes { value: usize, span: Span }, MaxGraphemes { value: usize, span: Span }, Format { value: String, span: Span }, Enum { values: Vec<ValueRef>, span: Span }, KnownValues { values: Vec<ValueRef>, span: Span },
// Numeric constraints Minimum { value: i64, span: Span }, Maximum { value: i64, span: Span },
// Blob constraints Accept { mimes: Vec<String>, span: Span }, MaxSize { value: usize, span: Span },
// Value constraints Default { value: ConstraintValue, span: Span }, Const { value: ConstraintValue, span: Span },}
impl Spanned for Constraint { fn span(&self) -> Span { match self { Constraint::MinLength { span, .. } => *span, Constraint::MaxLength { span, .. } => *span, Constraint::MinGraphemes { span, .. } => *span, Constraint::MaxGraphemes { span, .. } => *span, Constraint::Format { span, .. } => *span, Constraint::Enum { span, .. } => *span, Constraint::KnownValues { span, .. } => *span, Constraint::Minimum { span, .. } => *span, Constraint::Maximum { span, .. } => *span, Constraint::Accept { span, .. } => *span, Constraint::MaxSize { span, .. } => *span, Constraint::Default { span, .. } => *span, Constraint::Const { span, .. } => *span, } }}
/// A value in a constraint default - can be a literal or reference#[derive(Debug, Clone, PartialEq)]pub enum ConstraintValue { String(String), Integer(i64), Boolean(bool), /// Reference to a named item (token, record, alias, etc.) Reference(Path),}
/// A value reference in enum/knownValues constraints - can be a string literal or reference to any named item#[derive(Debug, Clone, PartialEq)]pub enum ValueRef { /// String literal (e.g., "open") Literal(String), /// Reference to a named item - token, record, alias, etc. (e.g., open) Reference(Path),}
impl core::fmt::Display for ValueRef { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { ValueRef::Literal(s) => write!(f, "\"{}\"", s), ValueRef::Reference(path) => write!(f, "{}", path.to_string()), } }}