Skip to main content

strixonomy_lsp/
protocol.rs

1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3use strixonomy_catalog::{CatalogStats, ClassHierarchy, EntityDetail, SubclassEdge};
4use strixonomy_core::{Diagnostic, Entity, OntologyDocument};
5use strixonomy_reasoner::ReasonerSnapshot;
6
7#[derive(Debug, Clone, Serialize)]
8pub struct DiagnosticSummary {
9    pub code: String,
10    pub severity: String,
11    pub message: String,
12    pub file: String,
13    pub line: Option<u64>,
14    pub column: Option<u64>,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub entity_iri: Option<String>,
17}
18
19impl From<&Diagnostic> for DiagnosticSummary {
20    fn from(d: &Diagnostic) -> Self {
21        Self {
22            code: d.display_code(),
23            severity: d.severity.as_str().to_string(),
24            message: d.message.clone(),
25            file: d.file.display().to_string(),
26            line: d.range.line,
27            column: d.range.column,
28            entity_iri: d.entity_iri.clone(),
29        }
30    }
31}
32
33#[derive(Debug, Deserialize)]
34pub struct IndexWorkspaceParams {
35    /// Workspace root URI (`file://…`). Accepts legacy camelCase `workspaceUri` during migration.
36    #[serde(alias = "workspaceUri", default)]
37    pub workspace_uri: Option<String>,
38    /// Persist parse snapshots under `.strixonomy/cache/`.
39    #[serde(default)]
40    pub disk_cache: bool,
41}
42
43#[derive(Debug, Serialize)]
44pub struct IndexWorkspaceResult {
45    pub stats: CatalogStats,
46    pub indexed_at: u64,
47}
48
49#[derive(Debug, Serialize)]
50pub struct CatalogSnapshot {
51    pub documents: Vec<OntologyDocument>,
52    pub entities: Vec<Entity>,
53    pub hierarchy: ClassHierarchy,
54    pub diagnostics: Vec<DiagnosticSummary>,
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub reasoner: Option<ReasonerSnapshot>,
57    /// Workspace catalog statistics (same shape as `indexWorkspace` stats).
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub stats: Option<CatalogStats>,
60    /// Active ontology document id (path or ontology IRI) when set by the client.
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub active_ontology_id: Option<String>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
66#[serde(rename_all = "snake_case")]
67pub enum CommandEnablement {
68    Always,
69    HasOntology,
70    IsDirty,
71    HasSelection,
72    ReasonerRunning,
73    ReasonerIdle,
74    CanEditSelection,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct CommandDescriptor {
79    pub id: String,
80    pub title: String,
81    pub category: String,
82    #[serde(default, skip_serializing_if = "Vec::is_empty")]
83    pub enablement: Vec<CommandEnablement>,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub undo_label: Option<String>,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub dialog_id: Option<String>,
88}
89
90#[derive(Debug, Serialize)]
91pub struct ListCommandsResult {
92    pub commands: Vec<CommandDescriptor>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
96pub struct OntologyRegistryEntrySnapshot {
97    pub id: String,
98    pub uri: String,
99    pub path: String,
100    pub format: String,
101    pub role: String,
102    pub editable: bool,
103    pub dirty: bool,
104    pub version: u32,
105    pub active: bool,
106}
107
108#[derive(Debug, Deserialize)]
109pub struct WorkspaceUiStateParams {
110    /// Optional focused entity IRI from the client.
111    #[serde(default)]
112    pub selection_iri: Option<String>,
113    /// Client-reported dirty document count.
114    #[serde(default)]
115    pub dirty_document_count: u32,
116    /// Active ontology id preferred by the client.
117    #[serde(default)]
118    pub active_ontology_id: Option<String>,
119    /// Host-owned ontology registry snapshot (v0.20 workspace runtime).
120    #[serde(default)]
121    pub ontology_registry: Vec<OntologyRegistryEntrySnapshot>,
122}
123
124#[derive(Debug, Serialize)]
125pub struct WorkspaceUiState {
126    pub has_ontology: bool,
127    pub ontology_count: usize,
128    pub is_dirty: bool,
129    pub has_selection: bool,
130    pub selection_iri: Option<String>,
131    pub selection_editable: bool,
132    pub reasoner_running: bool,
133    pub reasoner_dirty: bool,
134    pub reasoner_consistent: Option<bool>,
135    pub active_ontology_id: Option<String>,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub stats: Option<CatalogStats>,
138    #[serde(skip_serializing_if = "Vec::is_empty", default)]
139    pub ontology_registry: Vec<OntologyRegistryEntrySnapshot>,
140}
141
142#[derive(Debug, Deserialize)]
143pub struct GetDialogSchemaParams {
144    pub dialog_id: String,
145}
146
147#[derive(Debug, Serialize)]
148pub struct DialogFieldSchema {
149    pub id: String,
150    pub label: String,
151    pub field_type: String,
152    #[serde(default)]
153    pub required: bool,
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub placeholder: Option<String>,
156    #[serde(default, skip_serializing_if = "Vec::is_empty")]
157    pub validation: Vec<String>,
158}
159
160#[derive(Debug, Serialize)]
161pub struct DialogSchema {
162    pub id: String,
163    pub title: String,
164    pub fields: Vec<DialogFieldSchema>,
165    pub primary_action: String,
166}
167
168#[derive(Debug, Serialize)]
169pub struct GetDialogSchemaResult {
170    pub schema: DialogSchema,
171}
172
173#[derive(Debug, Deserialize)]
174pub struct CreateOntologyParams {
175    pub path: String,
176    pub ontology_iri: String,
177    #[serde(default)]
178    pub version_iri: Option<String>,
179    #[serde(default)]
180    pub format: Option<String>,
181    #[serde(default)]
182    pub prefixes: Option<BTreeMap<String, String>>,
183}
184
185#[derive(Debug, Serialize)]
186pub struct CreateOntologyResult {
187    pub path: String,
188    pub ontology_iri: String,
189}
190
191#[derive(Debug, Deserialize)]
192pub struct ExportOntologyParams {
193    pub source_path: String,
194    pub output_path: String,
195    #[serde(default)]
196    pub format: Option<String>,
197}
198
199#[derive(Debug, Serialize)]
200pub struct ExportOntologyResult {
201    pub output_path: String,
202    pub success: bool,
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub logs: Option<String>,
205}
206
207#[derive(Debug, Deserialize)]
208pub struct SetActiveOntologyParams {
209    pub ontology_id: String,
210}
211
212#[derive(Debug, Serialize)]
213pub struct SetActiveOntologyResult {
214    pub active_ontology_id: String,
215}
216
217#[derive(Debug, Deserialize)]
218pub struct DeleteImpactParams {
219    pub entity_iri: String,
220}
221
222#[derive(Debug, Serialize)]
223pub struct DeleteImpactResult {
224    pub entity_iri: String,
225    pub usage_count: usize,
226    pub axiom_count: usize,
227    pub referencing_entities: Vec<String>,
228    pub warnings: Vec<String>,
229}
230
231#[derive(Debug, Deserialize)]
232pub struct GetEntityParams {
233    pub iri: String,
234}
235
236#[derive(Debug, Serialize)]
237pub struct GetEntityResult {
238    pub detail: EntityDetail,
239}
240
241#[derive(Debug, Deserialize)]
242pub struct ApplyAxiomPatchParams {
243    pub document_uri: String,
244    /// JSON array of patch operations (Turtle [`strixonomy_owl::PatchOp`] or OBO [`strixonomy_obo::OboPatchOp`]).
245    pub patches: serde_json::Value,
246    #[serde(default)]
247    pub preview_only: bool,
248}
249
250#[derive(Debug, Deserialize)]
251pub struct QueryParams {
252    pub sql: String,
253}
254
255#[derive(Debug, Deserialize)]
256pub struct SparqlParams {
257    pub query: String,
258}
259
260#[derive(Debug, Deserialize)]
261pub struct DlQueryParams {
262    pub expression: String,
263    #[serde(default = "default_dl_query_profile")]
264    pub profile: String,
265    /// `inferred` (default) or `asserted`
266    #[serde(default = "default_dl_query_mode")]
267    pub mode: String,
268    #[serde(default)]
269    pub document_uri: Option<String>,
270}
271
272fn default_dl_query_profile() -> String {
273    "dl".to_string()
274}
275
276fn default_dl_query_mode() -> String {
277    "inferred".to_string()
278}
279
280#[derive(Debug, Serialize)]
281pub struct DlQueryResult {
282    pub expression: String,
283    pub normalized: String,
284    pub query_class_iri: String,
285    pub subclasses: Vec<String>,
286    pub superclasses: Vec<String>,
287    pub equivalents: Vec<String>,
288    pub instances: Vec<String>,
289    pub profile: String,
290    pub mode: String,
291    pub duration_ms: u64,
292    #[serde(default, skip_serializing_if = "Vec::is_empty")]
293    pub warnings: Vec<String>,
294    #[serde(default, skip_serializing_if = "Vec::is_empty")]
295    pub diagnostics: Vec<String>,
296}
297
298#[derive(Debug, Deserialize)]
299pub struct SearchParams {
300    pub query: String,
301    /// Max hits to return (default 100, capped at 500).
302    #[serde(default)]
303    pub limit: Option<usize>,
304}
305
306#[derive(Debug, Serialize)]
307pub struct SearchResult {
308    pub entities: Vec<EntityDetail>,
309}
310
311#[derive(Debug, Serialize)]
312pub struct TabularQueryResult {
313    pub columns: Vec<String>,
314    pub rows: Vec<BTreeMap<String, String>>,
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub truncated: Option<bool>,
317}
318
319#[derive(Debug, Deserialize)]
320pub struct ParseManchesterParams {
321    pub expression: String,
322    pub axiom_kind: String,
323    #[serde(default)]
324    pub entity_iri: Option<String>,
325    #[serde(default)]
326    pub document_uri: Option<String>,
327}
328
329#[derive(Debug, Serialize)]
330pub struct ManchesterCompletions {
331    pub classes: Vec<String>,
332    pub object_properties: Vec<String>,
333    pub data_properties: Vec<String>,
334    pub datatypes: Vec<String>,
335}
336
337#[derive(Debug, Serialize)]
338pub struct ParseManchesterResult {
339    pub normalized: String,
340    pub turtle_fragment: String,
341    pub tree: serde_json::Value,
342    pub diagnostics: Vec<strixonomy_owl::PatchDiagnostic>,
343    pub completions: ManchesterCompletions,
344}
345
346#[derive(Debug, Serialize)]
347pub struct ApplyAxiomPatchResult {
348    #[serde(flatten)]
349    pub patch: strixonomy_owl::ApplyPatchResult,
350    #[serde(skip_serializing_if = "Option::is_none")]
351    pub entity_detail: Option<EntityDetail>,
352    /// Set when the patch was written but workspace reindex failed.
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub reindex_warning: Option<String>,
355    /// Full-document edit so the client can sync open editors with disk.
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub workspace_edit: Option<lsp_types::WorkspaceEdit>,
358    /// Inverted patch ops for workspace-level semantic undo (v0.20+).
359    #[serde(skip_serializing_if = "Option::is_none")]
360    pub undo_patches: Option<serde_json::Value>,
361}
362
363/// LSP JSON error payload for custom `strixonomy/*` methods (not [`strixonomy_core::StrixonomyError`]).
364#[derive(Debug, Serialize)]
365pub struct LspErrorPayload {
366    pub code: String,
367    pub message: String,
368    pub recoverable: bool,
369    pub user_action: Option<String>,
370}
371
372impl LspErrorPayload {
373    pub fn not_indexed() -> Self {
374        Self {
375            code: "NOT_INDEXED".to_string(),
376            message: "Workspace has not been indexed yet".to_string(),
377            recoverable: true,
378            user_action: Some("Run Strixonomy: Index Workspace".to_string()),
379        }
380    }
381
382    pub fn not_found(iri: &str) -> Self {
383        Self {
384            code: "ENTITY_NOT_FOUND".to_string(),
385            message: format!("Entity not found: {iri}"),
386            recoverable: true,
387            user_action: None,
388        }
389    }
390
391    pub fn index_failed(message: String) -> Self {
392        Self {
393            code: "INDEX_FAILED".to_string(),
394            message,
395            recoverable: true,
396            user_action: Some("Check ontology files for parse errors".to_string()),
397        }
398    }
399
400    pub fn invalid_params(message: String) -> Self {
401        Self { code: "INVALID_PARAMS".to_string(), message, recoverable: true, user_action: None }
402    }
403
404    pub fn graph_failed(message: String) -> Self {
405        Self {
406            code: "GRAPH_FAILED".to_string(),
407            message,
408            recoverable: true,
409            user_action: Some("Adjust graph kind, root IRI, or filters".to_string()),
410        }
411    }
412
413    pub fn robot_failed(message: String) -> Self {
414        Self {
415            code: "ROBOT_FAILED".to_string(),
416            message,
417            recoverable: true,
418            user_action: Some("Check ROBOT CLI installation and arguments".to_string()),
419        }
420    }
421
422    pub fn patch_invalid(message: String) -> Self {
423        Self {
424            code: "PATCH_INVALID".to_string(),
425            message,
426            recoverable: true,
427            user_action: Some("Check patch parameters and entity IRIs".to_string()),
428        }
429    }
430
431    pub fn unsupported_format(message: String) -> Self {
432        Self {
433            code: "UNSUPPORTED_FORMAT".to_string(),
434            message,
435            recoverable: true,
436            user_action: Some("Save as Turtle (.ttl) for write-back".to_string()),
437        }
438    }
439
440    pub fn query_failed(message: String) -> Self {
441        Self {
442            code: "QUERY_FAILED".to_string(),
443            message,
444            recoverable: true,
445            user_action: Some("Check query syntax and virtual table names".to_string()),
446        }
447    }
448
449    pub fn manchester_invalid(message: String) -> Self {
450        Self {
451            code: "MANCHESTER_INVALID".to_string(),
452            message,
453            recoverable: true,
454            user_action: Some("Fix the Manchester class expression".to_string()),
455        }
456    }
457
458    pub fn applied_not_indexed(message: String) -> Self {
459        Self {
460            code: "APPLIED_NOT_INDEXED".to_string(),
461            message,
462            recoverable: true,
463            user_action: Some("Patch was saved; run Strixonomy: Index Workspace".to_string()),
464        }
465    }
466
467    pub fn reasoner_failed(message: String) -> Self {
468        Self {
469            code: "REASONER_FAILED".to_string(),
470            message,
471            recoverable: true,
472            user_action: Some(
473                "Try a different reasoner profile or fix ontology axioms".to_string(),
474            ),
475        }
476    }
477
478    pub fn explanation_failed(message: String) -> Self {
479        Self {
480            code: "EXPLANATION_FAILED".to_string(),
481            message,
482            recoverable: true,
483            user_action: Some("Run the reasoner first or choose another class".to_string()),
484        }
485    }
486
487    pub fn refactor_failed(message: String) -> Self {
488        Self {
489            code: "REFACTOR_FAILED".to_string(),
490            message,
491            recoverable: true,
492            user_action: Some("Preview the refactor plan and check Turtle files".to_string()),
493        }
494    }
495}
496
497#[derive(Debug, Deserialize)]
498pub struct RunReasonerParams {
499    #[serde(default = "default_reasoner_profile")]
500    pub profile: String,
501    #[serde(default = "default_auto_profile")]
502    pub auto_detect: bool,
503}
504
505fn default_reasoner_profile() -> String {
506    "el".to_string()
507}
508
509fn default_auto_profile() -> bool {
510    true
511}
512
513#[derive(Debug, Serialize)]
514pub struct RunReasonerResult {
515    pub profile_used: String,
516    pub consistent: bool,
517    pub unsatisfiable: Vec<String>,
518    pub inferred_edge_count: usize,
519    pub new_inferences: Vec<SubclassEdge>,
520    pub warnings: Vec<strixonomy_reasoner::ReasonerWarning>,
521    pub duration_ms: u64,
522    pub snapshot: ReasonerSnapshot,
523}
524
525#[derive(Debug, Deserialize)]
526pub struct GetExplanationParams {
527    pub class_iri: String,
528    #[serde(default = "default_reasoner_profile")]
529    pub profile: String,
530}
531
532#[derive(Debug, Serialize)]
533pub struct GetGraphResult {
534    pub graph: strixonomy_catalog::GraphPayload,
535}
536
537#[derive(Debug, Deserialize)]
538pub struct RunRobotParams {
539    pub subcommand: String,
540    #[serde(default)]
541    pub args: Vec<String>,
542    #[serde(default)]
543    pub robot_path: Option<String>,
544}
545
546#[derive(Debug, Serialize)]
547pub struct RunRobotResult {
548    pub exit_code: i32,
549    pub stdout: String,
550    pub stderr: String,
551}
552
553#[derive(Debug, Deserialize)]
554pub struct CheckInstanceParams {
555    pub individual_iri: String,
556    pub class_iri: String,
557    #[serde(default = "default_reasoner_profile")]
558    pub profile: String,
559}
560
561#[derive(Debug, Serialize)]
562pub struct CheckInstanceResult {
563    pub individual_iri: String,
564    pub class_iri: String,
565    pub entailed: bool,
566    pub profile_used: String,
567    pub duration_ms: u64,
568}
569
570#[derive(Debug, Serialize)]
571pub struct GetExplanationResult {
572    pub class_iri: String,
573    pub steps: Vec<strixonomy_reasoner::ExplanationStep>,
574    pub text: String,
575    #[serde(default, skip_serializing_if = "Vec::is_empty")]
576    pub alternatives: Vec<strixonomy_reasoner::ExplanationResult>,
577    pub indexed_at: u64,
578    pub content_hash: String,
579}
580
581#[derive(Debug, Deserialize)]
582pub struct FindUsagesParams {
583    pub iri: String,
584}
585
586#[derive(Debug, Serialize)]
587pub struct UsageSummary {
588    pub iri: String,
589    pub referenced_iri: String,
590    pub file: String,
591    pub line: Option<u64>,
592    pub column: Option<u64>,
593    pub kind: String,
594    pub context: String,
595}
596
597#[derive(Debug, Serialize)]
598pub struct FindUsagesResult {
599    pub usages: Vec<UsageSummary>,
600}
601
602#[derive(Debug, Deserialize)]
603pub struct PreviewRefactorParams {
604    #[serde(flatten)]
605    pub request: strixonomy_refactor::RefactorRequest,
606}
607
608#[derive(Debug, Serialize)]
609pub struct PreviewRefactorResult {
610    #[serde(flatten)]
611    pub plan: strixonomy_refactor::RefactorPlan,
612}
613
614#[derive(Debug, Deserialize)]
615pub struct ApplyRefactorParams {
616    pub plan: strixonomy_refactor::RefactorPlan,
617    pub request: strixonomy_refactor::RefactorRequest,
618    #[serde(default)]
619    pub preview_only: bool,
620}
621
622#[derive(Debug, Deserialize)]
623pub struct SemanticDiffParams {
624    /// Git left ref (e.g. `main`) or `WORKSPACE` for indexed catalog.
625    #[serde(default)]
626    pub left_ref: Option<String>,
627    /// Git right ref or `WORKTREE` for working tree / indexed workspace.
628    #[serde(default)]
629    pub right_ref: Option<String>,
630    /// Optional left directory when comparing two paths on disk.
631    #[serde(default)]
632    pub left_path: Option<String>,
633    /// Optional right directory.
634    #[serde(default)]
635    pub right_path: Option<String>,
636    /// When true, enrich the diff with reasoner unsatisfiability changes.
637    #[serde(default)]
638    pub reasoner: bool,
639    /// Output format hint: `pr-summary` returns Markdown in `formatted`.
640    #[serde(default)]
641    pub format: Option<String>,
642}
643
644#[derive(Debug, Serialize)]
645pub struct SemanticDiffResult {
646    pub diff: strixonomy_diff::DiffResult,
647    #[serde(skip_serializing_if = "Option::is_none")]
648    pub formatted: Option<String>,
649}
650
651#[derive(Debug, Serialize)]
652pub struct ListSqlSchemaResult {
653    pub tables: Vec<strixonomy_query::SqlTableSchema>,
654}
655
656#[derive(Debug, Serialize)]
657pub struct ApplyRefactorResult {
658    pub files_written: usize,
659    #[serde(skip_serializing_if = "Option::is_none")]
660    pub reindex_warning: Option<String>,
661    #[serde(skip_serializing_if = "Option::is_none")]
662    pub workspace_edit: Option<lsp_types::WorkspaceEdit>,
663}
664
665#[derive(Debug, Serialize)]
666pub struct ListPluginsResult {
667    pub plugins: Vec<strixonomy_plugin::PluginDescriptor>,
668}
669
670/// SWRL rule summary for Rule Browser (v0.23).
671#[derive(Debug, Clone, Serialize, Deserialize)]
672pub struct SwrlRuleListItem {
673    pub id: String,
674    pub label: String,
675    pub body_count: usize,
676    pub head_count: usize,
677    pub enabled: bool,
678    /// Full JSON for editor round-trip when known.
679    #[serde(skip_serializing_if = "Option::is_none")]
680    pub rule_json: Option<String>,
681    #[serde(skip_serializing_if = "Option::is_none")]
682    pub document_uri: Option<String>,
683    #[serde(skip_serializing_if = "Option::is_none")]
684    pub ontology_iri: Option<String>,
685}
686
687#[derive(Debug, Serialize)]
688pub struct ListSwrlRulesResult {
689    pub rules: Vec<SwrlRuleListItem>,
690}
691
692#[derive(Debug, Deserialize)]
693pub struct ValidateSwrlRuleParams {
694    pub rule_json: String,
695}
696
697#[derive(Debug, Serialize)]
698pub struct ValidateSwrlRuleResult {
699    pub diagnostics: Vec<strixonomy_swrl::SwrlDiagnostic>,
700}
701
702#[derive(Debug, Deserialize)]
703pub struct ParseSwrlRuleParams {
704    pub rule_json: String,
705}
706
707#[derive(Debug, Serialize)]
708pub struct ParseSwrlRuleResult {
709    pub rule: strixonomy_swrl::SwrlRule,
710    pub diagnostics: Vec<strixonomy_swrl::SwrlDiagnostic>,
711}
712
713#[derive(Debug, Deserialize)]
714pub struct RunPluginParams {
715    pub plugin_id: String,
716    #[serde(default = "default_validate_action")]
717    pub action: String,
718    #[serde(default)]
719    pub step: Option<String>,
720    /// Used with `action = "ui_view"`.
721    #[serde(default)]
722    pub view_id: Option<String>,
723    /// Query text for `query.run`.
724    #[serde(default)]
725    pub query: Option<String>,
726    /// Focus / root IRI for `refactor.preview` or `graph.build`.
727    #[serde(default)]
728    pub focus_iri: Option<String>,
729}
730
731fn default_validate_action() -> String {
732    "validate".to_string()
733}
734
735#[derive(Debug, Serialize)]
736pub struct RunPluginResult {
737    pub diagnostics: Vec<DiagnosticSummary>,
738    #[serde(default, skip_serializing_if = "Vec::is_empty")]
739    pub output_paths: Vec<String>,
740    #[serde(skip_serializing_if = "Option::is_none")]
741    pub logs: Option<String>,
742    /// Optional HTML payload for plugin-defined views.
743    #[serde(skip_serializing_if = "Option::is_none")]
744    pub view_html: Option<String>,
745    pub success: bool,
746    #[serde(skip_serializing_if = "Option::is_none")]
747    pub result: Option<serde_json::Value>,
748    #[serde(skip_serializing_if = "Option::is_none")]
749    pub columns: Option<Vec<String>>,
750    #[serde(skip_serializing_if = "Option::is_none")]
751    pub rows: Option<Vec<Vec<String>>>,
752    #[serde(skip_serializing_if = "Option::is_none")]
753    pub unsatisfiable: Option<Vec<String>>,
754    #[serde(skip_serializing_if = "Option::is_none")]
755    pub affected_iris: Option<Vec<String>>,
756    #[serde(skip_serializing_if = "Option::is_none")]
757    pub root_iris: Option<Vec<String>>,
758    #[serde(skip_serializing_if = "Option::is_none")]
759    pub graph_kind: Option<String>,
760    #[serde(skip_serializing_if = "Option::is_none")]
761    pub hints: Option<Vec<String>>,
762    #[serde(skip_serializing_if = "Option::is_none")]
763    pub profile: Option<String>,
764}