diff --git a/README.md b/README.md index 79f6556..95bd6f5 100644 --- a/README.md +++ b/README.md @@ -34,34 +34,34 @@ gleam add jscheam ### Basic Types ```gleam -import jscheam +import jscheam/schema import gleam/json // Create simple types -let name_schema = jscheam.string() -let age_schema = jscheam.integer() -let active_schema = jscheam.boolean() -let score_schema = jscheam.float() +let name_schema = schema.string() +let age_schema = schema.integer() +let active_schema = schema.boolean() +let score_schema = schema.float() // Generate JSON Schema -let json_schema = jscheam.to_json(name_schema) |> json.to_string() +let json_schema = schema.to_json(name_schema) |> json.to_string() // Result: {"type":"string"} ``` ### Object Schemas ```gleam -import jscheam +import jscheam/schema import gleam/json // Create an object with default additional properties behavior (allows any) -let user_schema = jscheam.object([ - jscheam.prop("name", jscheam.string()), - jscheam.prop("age", jscheam.integer()), - jscheam.prop("email", jscheam.string()) +let user_schema = schema.object([ + schema.prop("name", schema.string()), + schema.prop("age", schema.integer()), + schema.prop("email", schema.string()) ]) -let json_schema = jscheam.to_json(user_schema) |> json.to_string() +let json_schema = schema.to_json(user_schema) |> json.to_string() // Result: { // "type": "object", // "properties": { @@ -70,45 +70,45 @@ let json_schema = jscheam.to_json(user_schema) |> json.to_string() // "email": {"type": "string"} // }, // "required": ["name", "age", "email"] -// // Note: additionalProperties is omitted (defaults to true as per JSON Schema Draft 7) +// Note: additionalProperties is omitted (defaults to true as per JSON Schema Draft 7) // } -// Create an object with strict additional properties -let strict_user_schema = jscheam.object([jscheam.prop("name", jscheam.string())]) - |> jscheam.disallow_additional_props() - |> jscheam.to_json(strict_user_schema) |> json.to_string() +// Create an object with strict additional properties +let strict_user_schema = schema.object([schema.prop("name", schema.string())]) + |> schema.disallow_additional_props() + |> schema.to_json(strict_user_schema) |> json.to_string() // Result: {..., "additionalProperties": false} -// Create an object with constrained additional properties -let constrained_user_schema = jscheam.object([jscheam.prop("id", jscheam.string())]) - |> jscheam.constrain_additional_props(jscheam.string()) - |> jscheam.to_json(constrained_user_schema) |> json.to_string() +// Create an object with constrained additional properties +let constrained_user_schema = schema.object([schema.prop("id", schema.string())]) + |> schema.constrain_additional_props(schema.string()) + |> schema.to_json(constrained_user_schema) |> json.to_string() // Result: {..., "additionalProperties": {"type": "string"}} // Explicitly allow additional properties -let explicit_allow_schema = jscheam.object([ - jscheam.prop("name", jscheam.string()) -]) -|> jscheam.allow_additional_props() -|> jscheam.to_json(explicit_allow_schema) |> json.to_string() +let explicit_allow_schema = schema.object([ + schema.prop("name", schema.string()) +]) +|> schema.allow_additional_props() +|> schema.to_json(explicit_allow_schema) |> json.to_string() // Result: {..., "additionalProperties": true} ``` ### Optional Properties and Descriptions ```gleam -import jscheam +import jscheam/schema import gleam/json -let user_schema = jscheam.object([ - jscheam.prop("name", jscheam.string()) |> jscheam.description("User's full name"), - jscheam.prop("age", jscheam.integer()) |> jscheam.optional(), - jscheam.prop("email", jscheam.string()) - |> jscheam.description("User's email address") - |> jscheam.optional() +let user_schema = schema.object([ + schema.prop("name", schema.string()) |> schema.description("User's full name"), + schema.prop("age", schema.integer()) |> schema.optional(), + schema.prop("email", schema.string()) + |> schema.description("User's email address") + |> schema.optional() ]) -let json_schema = jscheam.to_json(user_schema) |> json.to_string() +let json_schema = schema.to_json(user_schema) |> json.to_string() // Result: { // "type": "object", // "properties": { @@ -123,21 +123,21 @@ let json_schema = jscheam.to_json(user_schema) |> json.to_string() ### Arrays ```gleam -import jscheam +import jscheam/schema import gleam/json // Array of strings -let tags_schema = jscheam.array(jscheam.string()) +let tags_schema = schema.array(schema.string()) // Array of objects -let users_schema = jscheam.array( - jscheam.object([ - jscheam.prop("name", jscheam.string()), - jscheam.prop("age", jscheam.integer()) |> jscheam.optional() +let users_schema = schema.array( + schema.object([ + schema.prop("name", schema.string()), + schema.prop("age", schema.integer()) |> schema.optional() ]) ) -let json_schema = jscheam.to_json(tags_schema) |> json.to_string() +let json_schema = schema.to_json(tags_schema) |> json.to_string() // Result: { // "type": "array", // "items": {"type": "string"} @@ -147,23 +147,24 @@ let json_schema = jscheam.to_json(tags_schema) |> json.to_string() ### Union Types Union types allow a property to accept multiple types. -Some API require all fields to be required (no optional fields), so the only way to add nullability is to use union types. +Some API require all fields to be "required"" (no optional fields), +so the only way to add nullability is to use union types. ```gleam -import jscheam +import jscheam/schema import gleam/json // Simple union: string or null -let nullable_string_schema = jscheam.union([jscheam.string(), jscheam.null()]) +let nullable_string_schema = schema.union([schema.string(), schema.null()]) // Used in an object -let user_schema = jscheam.object([ - jscheam.prop("name", jscheam.string()), - jscheam.prop("nickname", jscheam.union([jscheam.string(), jscheam.null()])) - |> jscheam.description("Optional nickname, can be string or null") +let user_schema = schema.object([ + schema.prop("name", schema.string()), + schema.prop("nickname", schema.union([schema.string(), schema.null()])) + |> schema.description("Optional nickname, can be string or null") ]) -let json_schema = jscheam.to_json(user_schema) |> json.to_string() +let json_schema = schema.to_json(user_schema) |> json.to_string() // Result: { // "type": "object", // "properties": { @@ -179,40 +180,42 @@ let json_schema = jscheam.to_json(user_schema) |> json.to_string() ### Constraints -Constraints allow you to add validation rules to your schema properties. jscheam supports enum and pattern constraints with more to come in the future. +Constraints allow you to add validation rules to your schema properties. +jscheam supports enum and pattern constraints with more to come in the future. #### Enum Constraints -Enum constraints restrict values to a fixed set of allowed values. It uses the `json` module to define the allowed values. as enum values can be any valid JSON type (string, number, boolean, null, ...) +Enum constraints restrict values to a fixed set of allowed values. +It uses the `json` module to define the allowed values as enum values can be any valid JSON type (string, number, boolean, null, ...) ```gleam -import jscheam +import jscheam/schema import gleam/json // String enum -let color_schema = jscheam.object([ - jscheam.prop("color", jscheam.string()) - |> jscheam.enum([ +let color_schema = schema.object([ + schema.prop("color", schema.string()) + |> schema.enum([ json.string("red"), - json.string("green"), + json.string("green"), json.string("blue") ]) - |> jscheam.description("Primary colors only") + |> schema.description("Primary colors only") ]) // Mixed type enum with union -let status_schema = jscheam.object([ - jscheam.prop("status", jscheam.union([jscheam.string(), jscheam.null(), jscheam.integer()])) - |> jscheam.enum([ +let status_schema = schema.object([ + schema.prop("status", schema.union([schema.string(), schema.null(), schema.integer()])) + |> schema.enum([ json.string("active"), json.string("inactive"), json.null(), json.int(42) ]) - |> jscheam.description("Status with mixed types") + |> schema.description("Status with mixed types") ]) -let json_schema = jscheam.to_json(color_schema) |> json.to_string() +let json_schema = schema.to_json(color_schema) |> json.to_string() // Result: { // "type": "object", // "properties": { @@ -235,19 +238,19 @@ import jscheam import gleam/json // Email validation -let user_schema = jscheam.object([ - jscheam.prop("email", jscheam.string()) - |> jscheam.pattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$") - |> jscheam.description("Valid email address"), - - jscheam.prop("phone", jscheam.string()) - |> jscheam.pattern("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$") - |> jscheam.description("Phone number in US format") +let user_schema = schema.object([ + schema.prop("email", schema.string()) + |> schema.pattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$") + |> schema.description("Valid email address"), + + schema.prop("phone", schema.string()) + |> schema.pattern("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$") + |> schema.description("Phone number in US format") ]) -let json_schema = jscheam.to_json(user_schema) |> json.to_string() +let json_schema = schema.to_json(user_schema) |> json.to_string() // Result: { -// "type": "object", +// "type": "object", // "properties": { // "email": { // "type": "string", @@ -255,7 +258,7 @@ let json_schema = jscheam.to_json(user_schema) |> json.to_string() // "description": "Valid email address" // }, // "phone": { -// "type": "string", +// "type": "string", // "pattern": "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$", // "description": "Phone number in US format" // } @@ -270,19 +273,19 @@ let json_schema = jscheam.to_json(user_schema) |> json.to_string() import jscheam import gleam/json -let profile_schema = jscheam.object([ - jscheam.prop("user", jscheam.object([ - jscheam.prop("name", jscheam.string()), - jscheam.prop("age", jscheam.integer()) |> jscheam.optional() +let profile_schema = schema.object([ + schema.prop("user", schema.object([ + schema.prop("name", schema.string()), + schema.prop("age", schema.integer()) |> schema.optional() ])), - jscheam.prop("preferences", jscheam.object([ - jscheam.prop("theme", jscheam.string()) |> jscheam.description("UI theme preference"), - jscheam.prop("notifications", jscheam.boolean()) |> jscheam.optional() + schema.prop("preferences", schema.object([ + schema.prop("theme", schema.string()) |> schema.description("UI theme preference"), + schema.prop("notifications", schema.boolean()) |> schema.optional() ])), - jscheam.prop("tags", jscheam.array(jscheam.string())) |> jscheam.description("User tags") + schema.prop("tags", schema.array(schema.string())) |> schema.description("User tags") ]) -let json_schema = jscheam.to_json(profile_schema) |> json.to_string() +let json_schema = schema.to_json(profile_schema) |> json.to_string() // Result: { // "type": "object", // "properties": { diff --git a/src/jscheam/property.gleam b/src/jscheam/property.gleam deleted file mode 100644 index 09e7ea5..0000000 --- a/src/jscheam/property.gleam +++ /dev/null @@ -1,50 +0,0 @@ -import gleam/json -import gleam/option - -/// Constraints that can be applied to properties -pub type Constraint { - /// Restrict values to a fixed set of values (can be any JSON value) - Enum(values: List(json.Json)) - /// Pattern constraint using regex - Pattern(regex: String) -} - -/// A property in a JSON Schema object -pub type Property { - Property( - name: String, - property_type: Type, - is_required: Bool, - description: option.Option(String), - constraints: List(Constraint), - ) -} - -/// Additional properties configuration for object types -pub type AdditionalProperties { - /// Allow any additional properties (JSON Schema default behavior) - /// This is the default and will omit the additionalProperties field from the schema - AllowAny - /// Explicitly allow any additional properties (outputs "additionalProperties": true) - AllowExplicit - /// Disallow any additional properties - Disallow - /// Additional properties must conform to the specified schema - Schema(Type) -} - -/// A JSON Schema type -pub type Type { - Integer - String - Boolean - Float - Null - Object( - properties: List(Property), - additional_properties: AdditionalProperties, - ) - Array(Type) - /// Union type for multiple allowed types (e.g., ["string", "null"]) - Union(List(Type)) -} diff --git a/src/jscheam.gleam b/src/jscheam/schema.gleam similarity index 80% rename from src/jscheam.gleam rename to src/jscheam/schema.gleam index 31d3df4..76138eb 100644 --- a/src/jscheam.gleam +++ b/src/jscheam/schema.gleam @@ -1,36 +1,31 @@ import gleam/json import gleam/list import gleam/option -import jscheam/property.{ - type AdditionalProperties, type Constraint, type Property, type Type, AllowAny, - AllowExplicit, Array, Boolean, Disallow, Enum, Float, Integer, Null, Object, - Pattern, Property, Schema, String, Union, -} -// Property builders -/// Creates a property with the specified name and type -/// Properties are required by default -pub fn prop(name: String, property_type: Type) -> Property { - Property( - name: name, - property_type: property_type, - is_required: True, - description: option.None, - constraints: [], - ) -} - -/// Makes a property optional (not required in the schema) -pub fn optional(property: Property) -> Property { - Property(..property, is_required: False) +/// Constraints that can be applied to properties +pub type Constraint { + /// Restrict values to a fixed set of values (can be any JSON value) + Enum(values: List(json.Json)) + /// Pattern constraint using regex + Pattern(regex: String) } -/// Adds a description to a property for documentation purposes -pub fn description(property: Property, desc: String) -> Property { - Property(..property, description: option.Some(desc)) +/// A type definition for JSON Schema +pub type Type { + Integer + String + Boolean + Float + Null + Object( + properties: List(Property), + additional_properties: AdditionalProperties, + ) + Array(Type) + /// Union type for multiple allowed types (e.g., ["string", "null"]) + Union(List(Type)) } -// Type builders /// Creates a string type for JSON Schema pub fn string() -> Type { String @@ -67,26 +62,13 @@ pub fn union(types: List(Type)) -> Type { Union(types) } -/// Adds an enum constraint to a property that restricts values to a fixed set -/// Example: prop("color", string()) |> enum(enum_strings(["red", "green", "blue"])) -pub fn enum(property: Property, values: List(json.Json)) -> Property { - let new_constraint = Enum(values: values) - Property(..property, constraints: [new_constraint, ..property.constraints]) -} - -/// Adds a pattern constraint to a property that restricts values to match a regex pattern -/// Example: prop("phone", string()) |> pattern("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$") -pub fn pattern(property: Property, regex: String) -> Property { - let new_constraint = Pattern(regex: regex) - Property(..property, constraints: [new_constraint, ..property.constraints]) -} - /// Creates an object type with the specified properties /// By default allows any additional properties (JSON Schema default behavior - omits the field) pub fn object(properties: List(Property)) -> Type { Object(properties: properties, additional_properties: AllowAny) } +/// Update an object type to allow any additional properties /// Explicitly allows any additional properties (outputs "additionalProperties": true) pub fn allow_additional_props(object_type: Type) -> Type { case object_type { @@ -96,6 +78,7 @@ pub fn allow_additional_props(object_type: Type) -> Type { } } +/// Update an object type to disallow additional properties /// Disallows additional properties (outputs "additionalProperties": false) pub fn disallow_additional_props(object_type: Type) -> Type { case object_type { @@ -105,7 +88,9 @@ pub fn disallow_additional_props(object_type: Type) -> Type { } } -/// Constrains additional properties to conform to the specified schema +/// Update an object type to constrain additional properties to a specific schema +/// Example: object([prop("name", string())]) |> constrain_additional_props(string()) +/// This will set "additionalProperties" to the specified schema type pub fn constrain_additional_props(object_type: Type, schema: Type) -> Type { case object_type { Object(properties: props, additional_properties: _) -> @@ -114,12 +99,76 @@ pub fn constrain_additional_props(object_type: Type, schema: Type) -> Type { } } +/// A property in a object type +/// Represents a field in an object with a name, type, and optional constraints +pub type Property { + Property( + name: String, + property_type: Type, + is_required: Bool, + description: option.Option(String), + constraints: List(Constraint), + ) +} + +/// Additional properties configuration for object types +pub type AdditionalProperties { + /// Allow any additional properties (JSON Schema Draft 7 default behavior) + /// This is the default and will omit the additionalProperties field from the schema + AllowAny + /// Explicitly allow any additional properties (outputs "additionalProperties": true) + AllowExplicit + /// Disallow any additional properties + Disallow + /// Additional properties must conform to the specified schema + Schema(Type) +} + +// Property builders +/// Creates a property with the specified name and type +/// Properties are required by default +pub fn prop(name: String, property_type: Type) -> Property { + Property( + name: name, + property_type: property_type, + is_required: True, + description: option.None, + constraints: [], + ) +} + +/// Makes a property optional (not required in the schema) +/// Example: object([prop("name", string()) |> optional()]) +pub fn optional(property: Property) -> Property { + Property(..property, is_required: False) +} + +/// Adds a description to a property for documentation purposes +/// Example: prop("name", string()) |> description("The name of the person") +pub fn description(property: Property, desc: String) -> Property { + Property(..property, description: option.Some(desc)) +} + +/// Adds an enum constraint to a property that restricts values to a fixed set +/// Example: prop("color", string()) |> enum(enum_strings(["red", "green", "blue"])) +pub fn enum(property: Property, values: List(json.Json)) -> Property { + let new_constraint = Enum(values: values) + Property(..property, constraints: [new_constraint, ..property.constraints]) +} + +/// Adds a pattern constraint to a property that restricts values to match a regex pattern +/// Example: prop("phone", string()) |> pattern("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$") +pub fn pattern(property: Property, regex: String) -> Property { + let new_constraint = Pattern(regex: regex) + Property(..property, constraints: [new_constraint, ..property.constraints]) +} + fn additional_properties_to_json( add_props: AdditionalProperties, ) -> List(#(String, json.Json)) { case add_props { + // Omit the field entirely (JSON Schema Draft 7 default equivalent to "additionalProperties": true) AllowAny -> [] - // Omit the field entirely (JSON Schema default) AllowExplicit -> [#("additionalProperties", json.bool(True))] Disallow -> [#("additionalProperties", json.bool(False))] Schema(schema_type) -> [ @@ -273,6 +322,7 @@ fn fields_to_required(fields: List(Property)) -> json.Json { /// Converts a Type to a JSON Schema document /// This is the main function to generate JSON Schema from your type definitions +/// Example: object([prop("name", string()), prop("age", integer())]) |> to_json() pub fn to_json(object_type: Type) -> json.Json { type_to_json_value(object_type) } diff --git a/test/constraints_test.gleam b/test/constraints_test.gleam index a9fd29d..8665ca7 100644 --- a/test/constraints_test.gleam +++ b/test/constraints_test.gleam @@ -1,19 +1,18 @@ import gleam/json import gleam/string import gleeunit/should -import jscheam -import jscheam/property.{Enum, Pattern, Property} +import jscheam/schema.{Enum, Pattern, Property} // Test enum with string base type pub fn enum_string_test() { let schema = - jscheam.object([ - jscheam.prop("units", jscheam.string()) - |> jscheam.enum([json.string("celsius"), json.string("fahrenheit")]) - |> jscheam.description("Units the temperature will be returned in."), + schema.object([ + schema.prop("units", schema.string()) + |> schema.enum([json.string("celsius"), json.string("fahrenheit")]) + |> schema.description("Units the temperature will be returned in."), ]) - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() string.contains(json, "\"type\":\"object\"") |> should.be_true() string.contains(json, "\"units\":{") |> should.be_true() @@ -36,8 +35,8 @@ pub fn enum_constraint_test() { json.string("blue"), ] let property = - jscheam.prop("color", jscheam.string()) - |> jscheam.enum(expected_values) + schema.prop("color", schema.string()) + |> schema.enum(expected_values) // Test that the constraint was applied let Property(_name, _type, _required, _description, constraints) = property @@ -58,16 +57,16 @@ pub fn enum_mixed_types_test() { ] let schema = - jscheam.object([ - jscheam.prop( + schema.object([ + schema.prop( "status", - jscheam.union([jscheam.string(), jscheam.null(), jscheam.integer()]), + schema.union([schema.string(), schema.null(), schema.integer()]), ) - |> jscheam.enum(mixed_values) - |> jscheam.description("Traffic light status with special values"), + |> schema.enum(mixed_values) + |> schema.description("Traffic light status with special values"), ]) - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() string.contains(json, "\"type\":\"object\"") |> should.be_true() string.contains(json, "\"status\":{") |> should.be_true() @@ -87,14 +86,14 @@ pub fn pattern_constraint_test() { let phone_regex = "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$" let schema = - jscheam.object([ - jscheam.prop("phone", jscheam.string()) - |> jscheam.pattern(phone_regex) - |> jscheam.description("Phone number in US format"), + schema.object([ + schema.prop("phone", schema.string()) + |> schema.pattern(phone_regex) + |> schema.description("Phone number in US format"), ]) - |> jscheam.disallow_additional_props() + |> schema.disallow_additional_props() - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() string.contains(json, "\"type\":\"object\"") |> should.be_true() string.contains(json, "\"phone\":{") |> should.be_true() @@ -115,8 +114,8 @@ pub fn pattern_constraint_application_test() { let email_regex = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" let property = - jscheam.prop("email", jscheam.string()) - |> jscheam.pattern(email_regex) + schema.prop("email", schema.string()) + |> schema.pattern(email_regex) // Test that the constraint was applied let Property(_name, _type, _required, _description, constraints) = property @@ -136,14 +135,14 @@ pub fn multiple_constraints_test() { ] let schema = - jscheam.object([ - jscheam.prop("color", jscheam.string()) - |> jscheam.pattern(color_regex) - |> jscheam.enum(color_values) - |> jscheam.description("Hex color code from predefined set"), + schema.object([ + schema.prop("color", schema.string()) + |> schema.pattern(color_regex) + |> schema.enum(color_values) + |> schema.description("Hex color code from predefined set"), ]) - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() string.contains(json, "\"type\":\"string\"") |> should.be_true() string.contains(json, "\"pattern\":\"^#[0-9a-fA-F]{6}$\"") |> should.be_true() @@ -159,12 +158,12 @@ pub fn multiple_constraints_test() { // Test simple pattern constraint output format pub fn simple_pattern_output_test() { let schema = - jscheam.object([ - jscheam.prop("test", jscheam.string()) - |> jscheam.pattern("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$"), + schema.object([ + schema.prop("test", schema.string()) + |> schema.pattern("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$"), ]) - let json_string = jscheam.to_json(schema) |> json.to_string() + let json_string = schema.to_json(schema) |> json.to_string() // Should produce output similar to your example string.contains(json_string, "\"type\":\"string\"") |> should.be_true() diff --git a/test/jscheam_test.gleam b/test/jscheam_test.gleam index d508752..9e4446c 100644 --- a/test/jscheam_test.gleam +++ b/test/jscheam_test.gleam @@ -2,7 +2,7 @@ import gleam/json import gleam/string import gleeunit import gleeunit/should -import jscheam +import jscheam/schema pub fn main() -> Nil { gleeunit.main() @@ -11,12 +11,12 @@ pub fn main() -> Nil { // Test default additional properties behavior (allow any - omit field) pub fn additional_properties_test() { let schema_default = - jscheam.object([ - jscheam.prop("name", jscheam.string()), - jscheam.prop("age", jscheam.integer()) |> jscheam.optional(), + schema.object([ + schema.prop("name", schema.string()), + schema.prop("age", schema.integer()) |> schema.optional(), ]) - let json_default = jscheam.to_json(schema_default) |> json.to_string() + let json_default = schema.to_json(schema_default) |> json.to_string() // Should NOT contain additionalProperties field (defaults to true) string.contains(json_default, "additionalProperties") |> should.be_false() @@ -25,10 +25,10 @@ pub fn additional_properties_test() { // Test additional properties with schema constraint pub fn additional_properties_with_schema_test() { let schema = - jscheam.object([jscheam.prop("name", jscheam.string())]) - |> jscheam.constrain_additional_props(jscheam.string()) + schema.object([schema.prop("name", schema.string())]) + |> schema.constrain_additional_props(schema.string()) - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() string.contains(json, "\"additionalProperties\":{\"type\":\"string\"}") |> should.be_true() @@ -37,10 +37,10 @@ pub fn additional_properties_with_schema_test() { // Test strict additional properties (false) pub fn additional_properties_strict_test() { let schema = - jscheam.object([jscheam.prop("name", jscheam.string())]) - |> jscheam.disallow_additional_props() + schema.object([schema.prop("name", schema.string())]) + |> schema.disallow_additional_props() - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() string.contains(json, "\"additionalProperties\":false") |> should.be_true() } @@ -48,10 +48,10 @@ pub fn additional_properties_strict_test() { // Test explicit additional properties (true) pub fn additional_properties_explicit_test() { let schema = - jscheam.object([jscheam.prop("name", jscheam.string())]) - |> jscheam.allow_additional_props() + schema.object([schema.prop("name", schema.string())]) + |> schema.allow_additional_props() - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() string.contains(json, "\"additionalProperties\":true") |> should.be_true() } @@ -59,13 +59,13 @@ pub fn additional_properties_explicit_test() { // Test optional properties pub fn optional_property_test() { let schema = - jscheam.object([ - jscheam.prop("name", jscheam.string()), - jscheam.prop("bio", jscheam.string()) |> jscheam.optional(), + schema.object([ + schema.prop("name", schema.string()), + schema.prop("bio", schema.string()) |> schema.optional(), ]) - |> jscheam.disallow_additional_props() + |> schema.disallow_additional_props() - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() string.contains(json, "\"name\":{\"type\":\"string\"}") |> should.be_true() string.contains(json, "\"bio\":{\"type\":\"string\"}") |> should.be_true() @@ -75,15 +75,15 @@ pub fn optional_property_test() { // Test property descriptions pub fn description_test() { let schema = - jscheam.object([ - jscheam.prop("name", jscheam.string()) - |> jscheam.description("User's full name"), - jscheam.prop("age", jscheam.integer()) - |> jscheam.description("User's age in years"), + schema.object([ + schema.prop("name", schema.string()) + |> schema.description("User's full name"), + schema.prop("age", schema.integer()) + |> schema.description("User's age in years"), ]) - |> jscheam.disallow_additional_props() + |> schema.disallow_additional_props() - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() string.contains(json, "\"description\":\"User's full name\"") |> should.be_true() @@ -92,14 +92,14 @@ pub fn description_test() { // Test chaining optional and description pub fn chained_modifiers_test() { let schema = - jscheam.object([ - jscheam.prop("nickname", jscheam.string()) - |> jscheam.optional() - |> jscheam.description("Optional user nickname"), + schema.object([ + schema.prop("nickname", schema.string()) + |> schema.optional() + |> schema.description("Optional user nickname"), ]) - |> jscheam.disallow_additional_props() + |> schema.disallow_additional_props() - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() string.contains(json, "\"required\":[]") |> should.be_true() string.contains(json, "\"description\":\"Optional user nickname\"") diff --git a/test/types_test.gleam b/test/types_test.gleam index 741509b..0f95824 100644 --- a/test/types_test.gleam +++ b/test/types_test.gleam @@ -1,19 +1,18 @@ import gleam/json import gleam/string import gleeunit/should -import jscheam -import jscheam/property.{Array, Boolean, Float, Integer, Null, String, Union} +import jscheam/schema.{Array, Boolean, Float, Integer, Null, String, Union} // Test basic object structure pub fn simple_object_test() { let schema = - jscheam.object([ - jscheam.prop("name", jscheam.string()), - jscheam.prop("age", jscheam.integer()), + schema.object([ + schema.prop("name", schema.string()), + schema.prop("age", schema.integer()), ]) - |> jscheam.disallow_additional_props() + |> schema.disallow_additional_props() - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() string.contains(json, "\"type\":\"object\"") |> should.be_true() string.contains(json, "\"name\":{\"type\":\"string\"}") |> should.be_true() @@ -25,10 +24,10 @@ pub fn simple_object_test() { // Test arrays with proper JSON Schema structure pub fn array_test() { let schema = - jscheam.object([jscheam.prop("scores", jscheam.array(jscheam.float()))]) - |> jscheam.disallow_additional_props() + schema.object([schema.prop("scores", schema.array(schema.float()))]) + |> schema.disallow_additional_props() - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() string.contains(json, "\"type\":\"array\"") |> should.be_true() string.contains(json, "\"items\":{\"type\":\"number\"}") |> should.be_true() @@ -37,19 +36,19 @@ pub fn array_test() { // Test nested objects pub fn nested_object_test() { let schema = - jscheam.object([ - jscheam.prop( + schema.object([ + schema.prop( "profile", - jscheam.object([ - jscheam.prop("bio", jscheam.string()) |> jscheam.optional(), - jscheam.prop("avatar_url", jscheam.string()), + schema.object([ + schema.prop("bio", schema.string()) |> schema.optional(), + schema.prop("avatar_url", schema.string()), ]) - |> jscheam.disallow_additional_props(), + |> schema.disallow_additional_props(), ), ]) - |> jscheam.disallow_additional_props() + |> schema.disallow_additional_props() - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() // Should contain nested object structure string.contains(json, "\"profile\":{\"type\":\"object\"") |> should.be_true() @@ -60,25 +59,25 @@ pub fn nested_object_test() { // Test type constructors pub fn type_constructors_test() { - jscheam.string() |> should.equal(String) - jscheam.integer() |> should.equal(Integer) - jscheam.boolean() |> should.equal(Boolean) - jscheam.float() |> should.equal(Float) - jscheam.null() |> should.equal(Null) - jscheam.array(jscheam.string()) |> should.equal(Array(String)) - jscheam.union([jscheam.string(), jscheam.null()]) + schema.string() |> should.equal(String) + schema.integer() |> should.equal(Integer) + schema.boolean() |> should.equal(Boolean) + schema.float() |> should.equal(Float) + schema.null() |> should.equal(Null) + schema.array(schema.string()) |> should.equal(Array(String)) + schema.union([schema.string(), schema.null()]) |> should.equal(Union([String, Null])) } // Test union types pub fn union_type_test() { let schema = - jscheam.object([ - jscheam.prop("units", jscheam.union([jscheam.string(), jscheam.null()])) - |> jscheam.description("Units the temperature will be returned in."), + schema.object([ + schema.prop("units", schema.union([schema.string(), schema.null()])) + |> schema.description("Units the temperature will be returned in."), ]) - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() string.contains(json, "\"type\":\"object\"") |> should.be_true() string.contains(json, "\"units\":{") |> should.be_true() @@ -94,16 +93,16 @@ pub fn union_type_test() { // Test enum with union base type pub fn enum_union_test() { let schema = - jscheam.object([ - jscheam.prop("location", jscheam.string()) - |> jscheam.description("City and country e.g. Bogotá, Colombia"), - jscheam.prop("units", jscheam.union([jscheam.string(), jscheam.null()])) - |> jscheam.enum([json.string("celsius"), json.string("fahrenheit")]) - |> jscheam.description("Units the temperature will be returned in."), + schema.object([ + schema.prop("location", schema.string()) + |> schema.description("City and country e.g. Bogotá, Colombia"), + schema.prop("units", schema.union([schema.string(), schema.null()])) + |> schema.enum([json.string("celsius"), json.string("fahrenheit")]) + |> schema.description("Units the temperature will be returned in."), ]) - |> jscheam.disallow_additional_props() + |> schema.disallow_additional_props() - let json = jscheam.to_json(schema) |> json.to_string() + let json = schema.to_json(schema) |> json.to_string() string.contains(json, "\"type\":\"object\"") |> should.be_true() string.contains(json, "\"location\":{") |> should.be_true()