Skip to content

Commit

Permalink
new: Add more JSON schema options. (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
milesj authored Feb 14, 2024
1 parent b93d54d commit 66456ab
Show file tree
Hide file tree
Showing 11 changed files with 614 additions and 37 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Changelog

## Unreleased

#### 🚀 Updates

- Added new JSONSchema renderer options:
- `allow_newlines_in_description` - Allows newlines in descriptions, otherwise strips them.
Defaults to `false`.
- `mark_struct_fields_required` - Mark all non-option struct fields as required. Defaults to
`true` for backwards compatibility.
- `set_field_name_as_title` - Sets the field's name as the `title` of each schema entry. Defaults
to `false`.

#### ⚙️ Internal

- Updated to Rust v1.76.
- Updated dependencies.

## 0.14.0

#### 💥 Breaking
Expand Down
32 changes: 16 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ resolver = "2"
members = ["crates/*"]

[workspace.dependencies]
chrono = "0.4.33"
indexmap = "2.2.2"
chrono = "0.4.34"
indexmap = "2.2.3"
miette = "7.0.0"
regex = "1.10.3"
relative-path = "1.9.2"
rust_decimal = "1.34.2"
rust_decimal = "1.34.3"
semver = "1.0.21"
serde = { version = "1.0.196", features = ["derive"] }
serde_json = "1.0.113"
serde_yaml = "0.9.31"
toml = "0.8.9"
toml = "0.8.10"
url = "2.5.0"
2 changes: 1 addition & 1 deletion crates/macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ proc-macro = true

[dependencies]
convert_case = "0.6.0"
darling = "0.20.5"
darling = "0.20.6"
proc-macro2 = "1.0.78"
quote = "1.0.35"
syn = { version = "2.0.48", features = ["full"] }
Expand Down
2 changes: 1 addition & 1 deletion crates/schematic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ all-features = true
schematic_macros = { version = "0.14.0", path = "../macros" }
schematic_types = { version = "0.6.0", path = "../types" }
miette = { workspace = true }
thiserror = "1.0.56"
thiserror = "1.0.57"
tracing = "0.1.40"

# config
Expand Down
76 changes: 66 additions & 10 deletions crates/schematic/src/schema/renderers/json_schema.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,47 @@
use crate::schema::{RenderResult, SchemaRenderer};
use indexmap::IndexMap;
use miette::IntoDiagnostic;
use schemars::gen::SchemaSettings;
use schemars::gen::{GenVisitor, SchemaSettings};
use schemars::schema::*;
use schematic_types::*;
use serde_json::{Number, Value};
use std::collections::{BTreeMap, BTreeSet, HashSet};

pub type JsonSchemaOptions = SchemaSettings;
pub struct JsonSchemaOptions {
/// Allows newlines in descriptions, otherwise strips them.
pub allow_newlines_in_description: bool,
/// Marks all non-option struct fields as required.
pub mark_struct_fields_required: bool,
/// Sets the field's name as the `title` of each schema entry.
/// This overrides any `title` manually defined by a type.
pub set_field_name_as_title: bool,

// Inherited from schemars.
pub option_nullable: bool,
pub option_add_null_type: bool,
pub definitions_path: String,
pub meta_schema: Option<String>,
pub visitors: Vec<Box<dyn GenVisitor>>,
pub inline_subschemas: bool,
}

impl Default for JsonSchemaOptions {
fn default() -> Self {
let settings = SchemaSettings::draft07();

Self {
allow_newlines_in_description: false,
mark_struct_fields_required: true,
set_field_name_as_title: false,
option_nullable: settings.option_nullable,
option_add_null_type: settings.option_add_null_type,
definitions_path: settings.definitions_path,
meta_schema: settings.meta_schema,
visitors: settings.visitors,
inline_subschemas: settings.inline_subschemas,
}
}
}

/// Renders JSON schema documents from a schema.
#[derive(Default)]
Expand All @@ -16,8 +50,14 @@ pub struct JsonSchemaRenderer {
references: HashSet<String>,
}

fn clean_comment(comment: String) -> String {
comment.trim().replace('\n', " ")
fn clean_comment(comment: String, allow_newlines: bool) -> String {
let comment = comment.trim();

if allow_newlines {
comment.to_owned()
} else {
comment.replace('\n', " ")
}
}

fn lit_to_value(lit: &LiteralValue) -> Value {
Expand All @@ -44,8 +84,15 @@ impl JsonSchemaRenderer {

if let Schema::Object(ref mut inner) = schema {
let mut metadata = Metadata {
// title: field.name.clone(),
description: field.description.clone().map(clean_comment),
title: if self.options.set_field_name_as_title {
Some(field.name.clone())
} else {
None
},
description: field
.description
.clone()
.map(|desc| clean_comment(desc, self.options.allow_newlines_in_description)),
deprecated: field.deprecated.is_some(),
read_only: field.read_only,
write_only: field.write_only,
Expand Down Expand Up @@ -139,7 +186,10 @@ impl SchemaRenderer<Schema> for JsonSchemaRenderer {

let metadata = Metadata {
title: enu.name.clone(),
description: enu.description.clone().map(clean_comment),
description: enu
.description
.clone()
.map(|desc| clean_comment(desc, self.options.allow_newlines_in_description)),
..Default::default()
};

Expand Down Expand Up @@ -267,7 +317,7 @@ impl SchemaRenderer<Schema> for JsonSchemaRenderer {
continue;
}

if !field.optional {
if !field.optional && self.options.mark_struct_fields_required {
required.insert(field.name.clone());
}

Expand All @@ -278,7 +328,10 @@ impl SchemaRenderer<Schema> for JsonSchemaRenderer {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
metadata: Some(Box::new(Metadata {
title: structure.name.clone(),
description: structure.description.clone().map(clean_comment),
description: structure
.description
.clone()
.map(|desc| clean_comment(desc, self.options.allow_newlines_in_description)),
..Default::default()
})),
object: Some(Box::new(ObjectValidation {
Expand Down Expand Up @@ -319,7 +372,10 @@ impl SchemaRenderer<Schema> for JsonSchemaRenderer {

let mut metadata = Metadata {
title: uni.name.clone(),
description: uni.description.clone().map(clean_comment),
description: uni
.description
.clone()
.map(|desc| clean_comment(desc, self.options.allow_newlines_in_description)),
..Default::default()
};

Expand Down
42 changes: 39 additions & 3 deletions crates/schematic/tests/generator_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,45 @@ mod json_schema {

assert_snapshot!(fs::read_to_string(file).unwrap());
}

#[test]
fn not_required() {
let sandbox = create_empty_sandbox();
let file = sandbox.path().join("schema.json");

create_generator()
.generate(
&file,
JsonSchemaRenderer::new(JsonSchemaOptions {
mark_struct_fields_required: false,
..JsonSchemaOptions::default()
}),
)
.unwrap();

assert_snapshot!(fs::read_to_string(file).unwrap());
}

#[test]
fn with_titles() {
let sandbox = create_empty_sandbox();
let file = sandbox.path().join("schema.json");

create_generator()
.generate(
&file,
JsonSchemaRenderer::new(JsonSchemaOptions {
set_field_name_as_title: true,
..JsonSchemaOptions::default()
}),
)
.unwrap();

assert_snapshot!(fs::read_to_string(file).unwrap());
}
}

#[cfg(all(feature = "template", feature = "json"))]
#[cfg(all(feature = "renderer_template", feature = "json"))]
mod template_json {
use super::*;
use schematic::schema::*;
Expand Down Expand Up @@ -195,7 +231,7 @@ mod template_json {
}
}

#[cfg(all(feature = "template", feature = "toml"))]
#[cfg(all(feature = "renderer_template", feature = "toml"))]
mod template_toml {
use super::*;
use schematic::schema::*;
Expand All @@ -213,7 +249,7 @@ mod template_toml {
}
}

#[cfg(all(feature = "template", feature = "yaml"))]
#[cfg(all(feature = "renderer_template", feature = "yaml"))]
mod template_yaml {
use super::*;
use schematic::schema::*;
Expand Down
Loading

0 comments on commit 66456ab

Please sign in to comment.