Skip to content

Commit

Permalink
feat: ensure profile schemas are always valid
Browse files Browse the repository at this point in the history
* ensure that schemas generated for profile input, result and error are valid json schemas even in case of parsing errors (although they might be trivial in that case)
* this allows better integration with json schema types in POD
* emit error when profile has no Use cases
  • Loading branch information
TheEdward162 committed Jan 29, 2024
1 parent 34dc584 commit c0f90bb
Show file tree
Hide file tree
Showing 6 changed files with 28 additions and 17 deletions.
2 changes: 1 addition & 1 deletion core/comlink/src/json.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub type JsonValue = serde_json::Value;
pub type JsonNumber = serde_json::Number;
pub type JsonMap = serde_json::Map<String, JsonValue>;
pub type JsonSchema = JsonValue;
pub type JsonSchema = JsonMap;

macro_rules! json_map {
(
Expand Down
4 changes: 2 additions & 2 deletions core/comlink/src/json_schema_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fmt::Display;
use jsonschema::JSONSchema;
use thiserror::Error;

use super::json::{JsonSchema, JsonValue};
use super::json::JsonValue;

#[derive(Debug)]
pub enum JsonSchemaValidatorError {
Expand Down Expand Up @@ -51,7 +51,7 @@ pub struct JsonSchemaValidator {
compiled: JSONSchema,
}
impl JsonSchemaValidator {
pub fn new(schema: &JsonSchema) -> Result<Self, JsonSchemaValidatorError> {
pub fn new(schema: &JsonValue) -> Result<Self, JsonSchemaValidatorError> {
match JSONSchema::compile(schema) {
Err(error) => Err(JsonSchemaValidatorError::SchemaError {
kind: format!("{:?}", error.kind),
Expand Down
2 changes: 2 additions & 0 deletions core/comlink/src/typescript_parser/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub enum DiagnosticCode {
// 10x
GlobalTypeUnknown = 101,
GlobalTypeInvalid,
ProfileInvalid,
// 21x
UseCaseInvalid = 211,
UseCaseNameInvalid,
Expand All @@ -39,6 +40,7 @@ impl DiagnosticCode {
Self::Unknown => "Unknown",
Self::GlobalTypeUnknown => "Global type unknown",
Self::GlobalTypeInvalid => "Global type error",
Self::ProfileInvalid => "The document is not a valid profile",
Self::UseCaseInvalid => "Use case options error",
Self::UseCaseNameInvalid => "Use case name error",
Self::UseCaseMemberUnknown => "Use case member unknown",
Expand Down
19 changes: 14 additions & 5 deletions core/comlink/src/typescript_parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,31 @@ pub fn parse_profile(source: &str) -> (Profile, ProfileSpans, Vec<Diagnostic>) {
for (example, example_spans) in usecase.examples.iter().zip(usecase_spans.examples.iter()) {
match example {
UseCaseExample::Success { input, result, .. } => {
check_profile_examples_schemas( &usecase.input, usecase_spans.input, input, example_spans.input, &mut diagnostics);
check_profile_examples_schemas( &usecase.result, usecase_spans.result, result, example_spans.output, &mut diagnostics);
check_profile_examples_schemas(&usecase.input, usecase_spans.input, input, example_spans.input, &mut diagnostics);
check_profile_examples_schemas(&usecase.result, usecase_spans.result, result, example_spans.output, &mut diagnostics);
}
UseCaseExample::Failure { input, error, .. } => {
check_profile_examples_schemas( &usecase.input, usecase_spans.input, input, example_spans.input, &mut diagnostics);
check_profile_examples_schemas( &usecase.error, usecase_spans.error, error, example_spans.output, &mut diagnostics);
check_profile_examples_schemas(&usecase.input, usecase_spans.input, input, example_spans.input, &mut diagnostics);
check_profile_examples_schemas(&usecase.error, usecase_spans.error, error, example_spans.output, &mut diagnostics);
}
}
}
}
if profile.usecases.len() == 0 {
diagnostics.push(Diagnostic {
severity: DiagnosticSeverity::Error,
code: DiagnosticCode::ProfileInvalid as u16,
message: format!("{}: Profile must contain at least one Use case", DiagnosticCode::ProfileInvalid.description()),
span: spans.entire
});
}

(profile, spans, diagnostics)
}

fn check_profile_examples_schemas(schema: &JsonSchema, schema_span: TextSpan, value: &JsonValue, value_span: TextSpan, diag: &mut Vec<Diagnostic>) {
let validator = match super::json_schema_validator::JsonSchemaValidator::new(schema) {
let schema = JsonValue::Object(schema.clone());
let validator = match super::json_schema_validator::JsonSchemaValidator::new(&schema) {
Ok(v) => v,
Err(err) => {
diag.push(Diagnostic {
Expand Down
16 changes: 8 additions & 8 deletions core/comlink/src/typescript_parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,12 +412,12 @@ impl<'a> ProfileParser<'a> {
err(.ty)
] catch(err) {
self.diag.error(err);
return (JsonSchema::Null, TextSpan::default());
return (json_map!({ "type": "null" }), TextSpan::default());
});
let span = ty.range().into();

let mut raw_schmea = self.parse_type_schema(ty);
raw_schmea.insert(
let mut schema = self.parse_type_schema(ty);
schema.insert(
"$defs".into(),
json!({
"AnyValue": anyvalue_schema("#/$defs/AnyValue")
Expand All @@ -426,13 +426,13 @@ impl<'a> ProfileParser<'a> {

let (Documentation { title, description }, _) = self.parse_documentation(root.syntax());
if let Some(title) = title {
raw_schmea.insert("title".into(), title.into());
schema.insert("title".into(), title.into());
}
if let Some(description) = description {
raw_schmea.insert("description".into(), description.into());
schema.insert("description".into(), description.into());
}

(JsonValue::Object(raw_schmea), span)
(schema, span)
}
/// Entry point into parsing schema of any type.
fn parse_type_schema(&mut self, ty: AnyTsType) -> JsonMap {
Expand Down Expand Up @@ -1081,7 +1081,7 @@ fn doc_line_common_prefix<'a>(left: &'a str, right: &str) -> &'a str {
}

fn anyvalue_schema(ref_path: &str) -> JsonSchema {
json!({
json_map!({
"oneOf": [
{ "type": "null" },
{ "type": "string" },
Expand Down Expand Up @@ -1113,7 +1113,7 @@ mod test {

#[test]
fn test_anyvalue_schema() {
let schema = anyvalue_schema("#");
let schema: JsonValue = anyvalue_schema("#").into();
let validator = crate::json_schema_validator::JsonSchemaValidator::new(&schema).unwrap();

let values = [
Expand Down
2 changes: 1 addition & 1 deletion packages/nodejs_comlink/src/model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export type JsonValue = number | boolean | string | null | { [k: number]: JsonValue } | { [k: string]: JsonValue };
export type JsonSchema = JsonValue;
export type JsonSchema = { [k: string]: JsonValue };

export type Documentation = {
title?: string,
Expand Down

0 comments on commit c0f90bb

Please sign in to comment.