@@ -2,9 +2,9 @@ import gleam/json
22import gleam/list
33import gleam/option
44import jscheam/property . {
5- type AdditionalProperties , type Property , type Type , AllowAny , AllowExplicit ,
6- Array , Boolean , Disallow , Float , Integer , Null , Object , Property , Schema ,
7- String , Union ,
5+ type AdditionalProperties , type Constraint , type Property , type Type , AllowAny ,
6+ AllowExplicit , Array , Boolean , Disallow , Enum , Float , Integer , Null , Object ,
7+ Pattern , Property , Schema , String , Union ,
88}
99
1010// Property builders
@@ -16,6 +16,7 @@ pub fn prop(name: String, property_type: Type) -> Property {
1616 property_type : property_type ,
1717 is_required : True ,
1818 description : option . None ,
19+ constraints : [ ] ,
1920 )
2021}
2122
@@ -66,6 +67,13 @@ pub fn union(types: List(Type)) -> Type {
6667 Union ( types )
6768}
6869
70+ /// Adds an enum constraint to a property that restricts values to a fixed set
71+ /// Example: prop("color", string()) |> enum(enum_strings(["red", "green", "blue"]))
72+ pub fn enum ( property : Property , values : List ( json . Json ) ) -> Property {
73+ let new_constraint = Enum ( values : values )
74+ Property ( .. property , constraints : [ new_constraint , .. property . constraints ] )
75+ }
76+
6977/// Creates an object type with the specified properties
7078/// By default allows any additional properties (JSON Schema default behavior - omits the field)
7179pub fn object ( properties : List ( Property ) ) -> Type {
@@ -157,69 +165,100 @@ fn type_to_json_value(property_type: Type) -> json.Json {
157165}
158166
159167fn property_to_field ( property : Property ) -> # ( String , json . Json ) {
160- let Property ( name , property_type , _is_required , description ) = property
161- let base_schema = type_to_json_value ( property_type )
162-
163- // Add description if provided
164- let schema_with_description = case description {
165- option . Some ( desc ) -> {
166- case base_schema {
167- _ -> {
168- case property_type {
169- String | Integer | Boolean | Float | Null ->
170- json . object ( [
171- # ( "type" , json . string ( type_to_type_string ( property_type ) ) ) ,
172- # ( "description" , json . string ( desc ) ) ,
173- ] )
174- Array ( item_type ) ->
175- json . object ( [
176- # ( "type" , json . string ( type_to_type_string ( property_type ) ) ) ,
177- # ( "items" , type_to_json_value ( item_type ) ) ,
178- # ( "description" , json . string ( desc ) ) ,
179- ] )
180- Object ( properties : props , additional_properties : add_props ) -> {
181- let properties_json =
182- list . map ( props , property_to_field ) |> json . object
183- let required_json = fields_to_required ( props )
184- let additional_props_fields =
185- additional_properties_to_json ( add_props )
186-
187- let base_fields = [
188- # ( "type" , json . string ( type_to_type_string ( property_type ) ) ) ,
189- # ( "properties" , properties_json ) ,
190- # ( "required" , required_json ) ,
191- # ( "description" , json . string ( desc ) ) ,
192- ]
193-
194- json . object ( list . append ( base_fields , additional_props_fields ) )
195- }
196- Union ( types ) -> {
197- let type_strings = list . map ( types , type_to_type_string )
198- json . object ( [
199- # ( "type" , json . array ( type_strings , json . string ) ) ,
200- # ( "description" , json . string ( desc ) ) ,
201- ] )
202- }
203- }
204- }
205- }
168+ let Property ( name , property_type , _is_required , description , constraints ) =
169+ property
170+
171+ let base_fields = get_base_type_fields ( property_type )
172+ let fields_with_constraints = add_constraint_fields ( base_fields , constraints )
173+ let final_fields = case description {
174+ option . Some ( desc ) -> [
175+ # ( "description" , json . string ( desc ) ) ,
176+ .. fields_with_constraints
177+ ]
178+ option . None -> fields_with_constraints
179+ }
180+
181+ # ( name , json . object ( final_fields ) )
182+ }
183+
184+ fn get_base_type_fields ( property_type : Type ) -> List ( # ( String , json . Json ) ) {
185+ case property_type {
186+ String | Integer | Boolean | Null | Float -> [
187+ # ( "type" , json . string ( type_to_type_string ( property_type ) ) ) ,
188+ ]
189+ Array ( item_type ) -> [
190+ # ( "type" , json . string ( type_to_type_string ( property_type ) ) ) ,
191+ # ( "items" , type_to_json_value ( item_type ) ) ,
192+ ]
193+ Object ( properties : props , additional_properties : add_props ) -> {
194+ let properties_json = list . map ( props , property_to_field ) |> json . object
195+ let required_json = fields_to_required ( props )
196+ let additional_props_fields = additional_properties_to_json ( add_props )
197+
198+ let base_fields = [
199+ # ( "type" , json . string ( type_to_type_string ( property_type ) ) ) ,
200+ # ( "properties" , properties_json ) ,
201+ # ( "required" , required_json ) ,
202+ ]
203+
204+ list . append ( base_fields , additional_props_fields )
205+ }
206+ Union ( types ) -> {
207+ let type_strings = list . map ( types , type_to_type_string )
208+ [ # ( "type" , json . array ( type_strings , json . string ) ) ]
206209 }
207- option . None -> base_schema
208210 }
211+ }
209212
210- # ( name , schema_with_description )
213+ fn add_constraint_fields (
214+ base_fields : List ( # ( String , json . Json ) ) ,
215+ constraints : List ( Constraint ) ,
216+ ) -> List ( # ( String , json . Json ) ) {
217+ case constraints {
218+ [ ] -> base_fields
219+ [ constraint , .. rest ] -> {
220+ let fields_with_constraint =
221+ add_single_constraint_field ( base_fields , constraint )
222+ add_constraint_fields ( fields_with_constraint , rest )
223+ }
224+ }
225+ }
226+
227+ fn add_single_constraint_field (
228+ fields : List ( # ( String , json . Json ) ) ,
229+ constraint : Constraint ,
230+ ) -> List ( # ( String , json . Json ) ) {
231+ case constraint {
232+ Enum ( values : values ) -> [
233+ # ( "enum" , json . array ( values , fn ( x ) { x } ) ) ,
234+ .. fields
235+ ]
236+ Pattern ( regex : regex ) -> [ # ( "pattern" , json . string ( regex ) ) , .. fields ]
237+ }
211238}
212239
213240fn fields_to_required ( fields : List ( Property ) ) -> json . Json {
214241 let required_fields =
215242 list . filter ( fields , fn ( property ) {
216- let Property ( _name , _property_type , is_required , _description ) = property
243+ let Property (
244+ _name ,
245+ _property_type ,
246+ is_required ,
247+ _description ,
248+ _constraints ,
249+ ) = property
217250 is_required
218251 } )
219252
220253 let names =
221254 list . map ( required_fields , fn ( property ) {
222- let Property ( name , _property_type , _is_required , _description ) = property
255+ let Property (
256+ name ,
257+ _property_type ,
258+ _is_required ,
259+ _description ,
260+ _constraints ,
261+ ) = property
223262 json . string ( name )
224263 } )
225264 json . array ( names , fn ( x ) { x } )
0 commit comments