Skip to main content

code_moniker_core/declare/
mod.rs

1use crate::core::code_graph::CodeGraph;
2use crate::core::moniker::Moniker;
3use crate::core::uri::{UriConfig, UriError, from_uri};
4
5mod build;
6mod parse;
7mod serialize;
8
9pub use crate::lang::Lang;
10pub use build::build_graph;
11pub use parse::parse_spec;
12pub use serialize::{SerializeError, graph_to_spec};
13
14pub(crate) fn parse_moniker_uri(uri: &str) -> Result<Moniker, UriError> {
15	let scheme_end = uri.find("://").ok_or(UriError::MissingProject)?;
16	from_uri(
17		uri,
18		&UriConfig {
19			scheme: &uri[..scheme_end + 3],
20		},
21	)
22}
23
24#[derive(Clone, Debug, Eq, PartialEq)]
25pub struct DeclareSpec {
26	pub root: Moniker,
27	pub lang: Lang,
28	pub symbols: Vec<DeclSymbol>,
29	pub edges: Vec<DeclEdge>,
30}
31
32#[derive(Clone, Debug, Eq, PartialEq)]
33pub struct DeclSymbol {
34	pub moniker: Moniker,
35	pub kind: String,
36	pub parent: Moniker,
37	pub visibility: Option<String>,
38	pub signature: Option<String>,
39}
40
41#[derive(Clone, Debug, Eq, PartialEq)]
42pub struct DeclEdge {
43	pub from: Moniker,
44	pub kind: EdgeKind,
45	pub to: Moniker,
46}
47
48#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
49pub enum EdgeKind {
50	DependsOn,
51	Calls,
52	InjectsProvide,
53	InjectsRequire,
54}
55
56impl EdgeKind {
57	pub fn from_tag(s: &str) -> Option<Self> {
58		match s {
59			"depends_on" => Some(Self::DependsOn),
60			"calls" => Some(Self::Calls),
61			"injects:provide" => Some(Self::InjectsProvide),
62			"injects:require" => Some(Self::InjectsRequire),
63			_ => None,
64		}
65	}
66
67	pub fn tag(self) -> &'static str {
68		match self {
69			Self::DependsOn => "depends_on",
70			Self::Calls => "calls",
71			Self::InjectsProvide => "injects:provide",
72			Self::InjectsRequire => "injects:require",
73		}
74	}
75}
76
77#[derive(Clone, Debug, Eq, PartialEq)]
78pub enum DeclareError {
79	JsonParse(String),
80	NotAnObject(&'static str),
81	MissingField {
82		path: String,
83		field: &'static str,
84	},
85	InvalidType {
86		path: String,
87		expected: &'static str,
88	},
89	UnknownLang(String),
90	UnknownEdgeKind(String),
91	InvalidMoniker {
92		path: String,
93		value: String,
94		reason: String,
95	},
96	KindNotInProfile {
97		lang: &'static str,
98		kind: String,
99	},
100	VisibilityNotInProfile {
101		lang: &'static str,
102		visibility: String,
103	},
104	KindMismatchMoniker {
105		path: String,
106		declared_kind: String,
107		moniker_last_kind: String,
108	},
109	DuplicateMoniker {
110		moniker: String,
111	},
112	UnknownParent {
113		path: String,
114		parent: String,
115	},
116	UnknownEdgeSource {
117		path: String,
118		from: String,
119	},
120	LangMismatch {
121		expected: &'static str,
122		actual: String,
123	},
124	GraphError(String),
125}
126
127impl std::fmt::Display for DeclareError {
128	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129		use DeclareError::*;
130		match self {
131			JsonParse(msg) => write!(f, "spec is not valid JSON: {msg}"),
132			NotAnObject(what) => write!(f, "{what} must be a JSON object"),
133			MissingField { path, field } => {
134				write!(f, "{path}: required field `{field}` is missing")
135			}
136			InvalidType { path, expected } => write!(f, "{path}: expected {expected}"),
137			UnknownLang(s) => write!(
138				f,
139				"unknown lang `{s}` (expected ts | rs | java | python | go | cs | sql)"
140			),
141			UnknownEdgeKind(s) => write!(
142				f,
143				"unknown edge kind `{s}` (expected depends_on | calls | injects:provide | injects:require)"
144			),
145			InvalidMoniker {
146				path,
147				value,
148				reason,
149			} => write!(f, "{path}: invalid moniker URI `{value}`: {reason}"),
150			KindNotInProfile { lang, kind } => write!(
151				f,
152				"kind `{kind}` is not allowed for lang={lang} (see profile)"
153			),
154			VisibilityNotInProfile { lang, visibility } => write!(
155				f,
156				"visibility `{visibility}` is not allowed for lang={lang} (see profile)"
157			),
158			KindMismatchMoniker {
159				path,
160				declared_kind,
161				moniker_last_kind,
162			} => write!(
163				f,
164				"{path}: declared kind `{declared_kind}` does not match the moniker's last segment kind `{moniker_last_kind}`"
165			),
166			DuplicateMoniker { moniker } => {
167				write!(f, "duplicate moniker in symbols: {moniker}")
168			}
169			UnknownParent { path, parent } => write!(
170				f,
171				"{path}: parent `{parent}` is neither the root nor a previously declared symbol"
172			),
173			UnknownEdgeSource { path, from } => {
174				write!(f, "{path}: edge `from` `{from}` is not a declared symbol")
175			}
176			LangMismatch { expected, actual } => write!(
177				f,
178				"spec.lang `{actual}` does not match the typed extractor's `{expected}` (use the dynamic-dispatch entry point if you do not know the language ahead of time)"
179			),
180			GraphError(msg) => write!(f, "graph build error: {msg}"),
181		}
182	}
183}
184
185impl std::error::Error for DeclareError {}
186
187pub fn declare_from_json_str(json: &str) -> Result<CodeGraph, DeclareError> {
188	let value: serde_json::Value =
189		serde_json::from_str(json).map_err(|e| DeclareError::JsonParse(e.to_string()))?;
190	declare_from_json_value(&value)
191}
192
193pub fn declare_from_json_value(json: &serde_json::Value) -> Result<CodeGraph, DeclareError> {
194	let spec = parse_spec(json)?;
195	build_graph(&spec)
196}