From 12a2510c730df5ffa32857ed03e29f20aa2ffc74 Mon Sep 17 00:00:00 2001 From: Norbert Hartl Date: Thu, 30 Jul 2026 11:46:06 +0200 Subject: [PATCH] Fix four real-world read/write bugs found validating against the Stripe API spec Found while driving OpenAPI-Client against the actual Stripe OpenAPI spec and stripe-mock (not just the Petstore test fixture): - JSONSchemaArray>>read: computed its result but never returned it (no ^), so array-typed values always silently became the schema object itself. The tuple-validation branch also iterated the schema's own #items instead of the actual data collection. - JSONSchemaObject>>readObject: sent #ifTrue: directly to #additionalProperties, which crashes whenever a schema sets additionalProperties to a nested schema (or leaves it unset) rather than a literal boolean - both are valid per spec. Stripe's metadata schemas do exactly this. - JSONSchemaAnyObject had no #read:, hit whenever a property with no fixed shape (e.g. an anyOf collapsing to "accept anything") is read back. - JSONSchemaString/JSONPrimitiveSchema sent #ifTrue: directly to #nullable, which is nil (not a boolean) for any schema object built by NeoJSON's reflective instantiation - i.e. every schema parsed from a document, primed or not. Changed to a proper accessor that treats unset as permissive, since OpenAPI's "nullable" keyword on a property is not currently threaded through nested schema reads at all (properties are always parsed as base JSONSchemaDefinition, which does not carry it) - a separate, larger gap left for follow-up. --- .../JSONSchema-Core/JSONFormatDate.class.st | 22 +- .../JSONFormatDateTime.class.st | 12 +- source/JSONSchema-Core/JSONFormatURI.class.st | 16 +- .../JSONPrimitiveSchema.class.st | 13 +- .../JSONSchemaAnyObject.class.st | 5 + .../JSONSchema-Core/JSONSchemaArray.class.st | 6 +- .../JSONSchemaDefinition.class.st | 286 +++++++++--------- .../JSONSchemaNotConstraint.class.st | 10 +- .../JSONSchema-Core/JSONSchemaObject.class.st | 2 +- .../JSONSchemaOneOfConstraint.class.st | 10 +- .../JSONSchemaPatternConstraint.class.st | 10 +- .../JSONSchemaRequiredConstraint.class.st | 10 +- ...NSchemaSingleItemSchemaConstraint.class.st | 16 +- .../JSONSchema-Core/JSONSchemaString.class.st | 6 +- 14 files changed, 218 insertions(+), 206 deletions(-) diff --git a/source/JSONSchema-Core/JSONFormatDate.class.st b/source/JSONSchema-Core/JSONFormatDate.class.st index 0fcd4bd..e64ecde 100644 --- a/source/JSONSchema-Core/JSONFormatDate.class.st +++ b/source/JSONSchema-Core/JSONFormatDate.class.st @@ -11,11 +11,6 @@ JSONFormatDate class >> basicConvertString: aString [ ^ Date fromString: aString ] -{ #category : 'as yet unclassified' } -JSONFormatDate class >> encodeJSON: aDate [ - ^ aDate yyyymmdd -] - { #category : 'validation' } JSONFormatDate class >> daysInMonth: aMonth year: aYear [ | leap | @@ -24,6 +19,11 @@ JSONFormatDate class >> daysInMonth: aMonth year: aYear [ ^ #(31 28 31 30 31 30 31 31 30 31 30 31) at: aMonth ] +{ #category : 'as yet unclassified' } +JSONFormatDate class >> encodeJSON: aDate [ + ^ aDate yyyymmdd +] + { #category : 'formatting' } JSONFormatDate class >> formatName [ ^ #date @@ -44,12 +44,6 @@ JSONFormatDate class >> isValidFullDate: aString [ ^ (month between: 1 and: 12) and: [ day between: 1 and: (self daysInMonth: month year: year) ] ] -{ #category : 'validation' } -JSONFormatDate class >> validateString: aString [ - (self isValidFullDate: aString) ifFalse: [ - JSONConstraintError signal: aString printString, ' is not a valid date' ] -] - { #category : 'services' } JSONFormatDate class >> validate: aDate [ (aDate isKindOf: Date) ifFalse: [ @@ -58,6 +52,12 @@ JSONFormatDate class >> validate: aDate [ ] +{ #category : 'validation' } +JSONFormatDate class >> validateString: aString [ + (self isValidFullDate: aString) ifFalse: [ + JSONConstraintError signal: aString printString, ' is not a valid date' ] +] + { #category : 'writing' } JSONFormatDate class >> write: aDate [ "convert to DateaAndTime without the sub-second precision" diff --git a/source/JSONSchema-Core/JSONFormatDateTime.class.st b/source/JSONSchema-Core/JSONFormatDateTime.class.st index 6c082a9..d724c95 100644 --- a/source/JSONSchema-Core/JSONFormatDateTime.class.st +++ b/source/JSONSchema-Core/JSONFormatDateTime.class.st @@ -33,12 +33,6 @@ JSONFormatDateTime class >> isValidDateTime: aString [ ^ (JSONFormatDate isValidFullDate: datePart) and: [ JSONFormatTime isValidString: timePart ] ] -{ #category : 'validation' } -JSONFormatDateTime class >> validateString: aString [ - (self isValidDateTime: aString) ifFalse: [ - JSONConstraintError signal: aString printString, ' is not a valid date-time' ] -] - { #category : 'services' } JSONFormatDateTime class >> validate: aDate [ (aDate isKindOf: DateAndTime) ifFalse: [ @@ -47,6 +41,12 @@ JSONFormatDateTime class >> validate: aDate [ ] +{ #category : 'validation' } +JSONFormatDateTime class >> validateString: aString [ + (self isValidDateTime: aString) ifFalse: [ + JSONConstraintError signal: aString printString, ' is not a valid date-time' ] +] + { #category : 'writing' } JSONFormatDateTime class >> write: aDateAndTime [ "convert to DateaAndTime without the sub-second precision" diff --git a/source/JSONSchema-Core/JSONFormatURI.class.st b/source/JSONSchema-Core/JSONFormatURI.class.st index 4032c47..e2cdf03 100644 --- a/source/JSONSchema-Core/JSONFormatURI.class.st +++ b/source/JSONSchema-Core/JSONFormatURI.class.st @@ -22,14 +22,6 @@ JSONFormatURI class >> formatName [ ^ #uri ] -{ #category : 'services' } -JSONFormatURI class >> validate: aUrl [ - (aUrl isKindOf: ZnUrl) ifFalse: [ - JSONTypeError - signal: aUrl printString , ' is not a URL' ] - -] - { #category : 'validation' } JSONFormatURI class >> isValidURIAuthority: anAuthority [ "authority = [ userinfo @ ] host [ : port ]. Necessary check: after stripping any @@ -84,6 +76,14 @@ JSONFormatURI class >> legalURICharacters [ ^ 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~:/?#[]@!$&''()*+,;=%' ] +{ #category : 'services' } +JSONFormatURI class >> validate: aUrl [ + (aUrl isKindOf: ZnUrl) ifFalse: [ + JSONTypeError + signal: aUrl printString , ' is not a URL' ] + +] + { #category : 'validation' } JSONFormatURI class >> validateString: aString [ (self isValidURIString: aString allowUnicode: false) ifFalse: [ diff --git a/source/JSONSchema-Core/JSONPrimitiveSchema.class.st b/source/JSONSchema-Core/JSONPrimitiveSchema.class.st index 0a88522..e09b8c0 100644 --- a/source/JSONSchema-Core/JSONPrimitiveSchema.class.st +++ b/source/JSONSchema-Core/JSONPrimitiveSchema.class.st @@ -75,8 +75,13 @@ JSONPrimitiveSchema >> initialize [ ] { #category : 'accessing' } -JSONPrimitiveSchema >> nullable: aBoolean [ - nullable := aBoolean +JSONPrimitiveSchema >> nullable [ + "Unset (not explicitly declared) defaults to permissive: nested property + schemas are parsed via base JSONSchemaDefinition, which does not carry the + OpenAPI-3.0-only 'nullable' keyword, so most real-world nullable fields + never reach here as true. Erring towards accepting null is the safe + direction for a response reader; explicit false is still honored." + ^ nullable ~~ false ] { #category : 'reading' } @@ -111,7 +116,7 @@ JSONPrimitiveSchema >> readUsing: aReader [ | value | value := aReader parseValue. value ifNil: [ - nullable + self nullable ifTrue: [ ^ nil ] ifFalse: [ JSONTypeError signal: self class typeName, ' cannot be nil' ] ]. ^ self read: value @@ -138,7 +143,7 @@ JSONPrimitiveSchema >> write: anObject [ { #category : 'writing' } JSONPrimitiveSchema >> write: anObject on: aWriter [ anObject ifNil: [ - nullable ifTrue: [ + self nullable ifTrue: [ ^ aWriter writeNull ] ]. self validate: anObject. aWriter nextPut: anObject diff --git a/source/JSONSchema-Core/JSONSchemaAnyObject.class.st b/source/JSONSchema-Core/JSONSchemaAnyObject.class.st index 9f15cfe..5ef35ab 100644 --- a/source/JSONSchema-Core/JSONSchemaAnyObject.class.st +++ b/source/JSONSchema-Core/JSONSchemaAnyObject.class.st @@ -16,6 +16,11 @@ JSONSchemaAnyObject >> acceptJSONSchema: aVisitor [ ^ aVisitor visitAnyObject: self ] +{ #category : 'reading-primitive data' } +JSONSchemaAnyObject >> read: anObject [ + ^ anObject +] + { #category : 'reading' } JSONSchemaAnyObject >> read: string object: object [ | json | diff --git a/source/JSONSchema-Core/JSONSchemaArray.class.st b/source/JSONSchema-Core/JSONSchemaArray.class.st index fda599e..b35583e 100644 --- a/source/JSONSchema-Core/JSONSchemaArray.class.st +++ b/source/JSONSchema-Core/JSONSchemaArray.class.st @@ -41,11 +41,13 @@ JSONSchemaArray >> items: aSchema [ { #category : 'meta-object-protocol' } JSONSchemaArray >> read: aCollection [ - items isCollection + ^ items isCollection ifTrue: [ - items withIndexCollect: [ :item :idx | + "tuple validation: items is an array of per-position schemas" + aCollection withIndexCollect: [ :item :idx | (items at: idx) read: item ] ] ifFalse: [ + "homogeneous array: items is a single schema applied to every element" aCollection collect: [ :item | items read: item ] ] ] diff --git a/source/JSONSchema-Core/JSONSchemaDefinition.class.st b/source/JSONSchema-Core/JSONSchemaDefinition.class.st index 70453cc..619868f 100644 --- a/source/JSONSchema-Core/JSONSchemaDefinition.class.st +++ b/source/JSONSchema-Core/JSONSchemaDefinition.class.st @@ -52,97 +52,6 @@ JSONSchemaDefinition class >> fromString: aString [ ^ self readFrom: aString readStream ] -{ #category : 'instance creation' } -JSONSchemaDefinition class >> readDependencyValueFrom: jsonReader [ - "A dependencies-map value is either an array of required property names - (property dependency) or a schema (schema dependency, which may itself be a - bare true/false). Returns an Array of Strings for the former, a - JSONSchemaDefinition for the latter." - | matched value names | - matched := false. - value := nil. - jsonReader parseConstantDo: [ :v | matched := true. value := v ]. - matched ifTrue: [ ^ JSONSchemaDefinition new booleanValue: value; yourself ]. - (jsonReader matchChar: $[) ifFalse: [ ^ jsonReader nextAs: JSONSchemaDefinition ]. - names := OrderedCollection new. - (jsonReader matchChar: $]) ifFalse: [ - [ names add: jsonReader parseString. - jsonReader matchChar: $, ] whileTrue. - jsonReader expectChar: $] ]. - ^ names asArray -] - -{ #category : 'instance creation' } -JSONSchemaDefinition class >> readItemsValueFrom: jsonReader [ - "items is either a single schema (object or bare true/false) or an array of - schemas (tuple/positional validation). Returns a JSONSchemaDefinition for the - former, an Array of JSONSchemaDefinition for the latter." - | matched value | - matched := false. - value := nil. - jsonReader parseConstantDo: [ :v | matched := true. value := v ]. - matched ifTrue: [ ^ JSONSchemaDefinition new booleanValue: value; yourself ]. - (jsonReader matchChar: $[) ifFalse: [ ^ jsonReader nextAs: JSONSchemaDefinition ]. - ^ Array streamContents: [ :stream | - (jsonReader matchChar: $]) ifFalse: [ - [ | m v elem | - m := false. v := nil. - jsonReader parseConstantDo: [ :x | m := true. v := x ]. - elem := (m and: [ v isKindOf: Boolean ]) - ifTrue: [ JSONSchemaDefinition new booleanValue: v; yourself ] - ifFalse: [ jsonReader nextAs: JSONSchemaDefinition ]. - stream nextPut: elem. - jsonReader matchChar: $, ] whileTrue. - jsonReader expectChar: $] ] ] -] - -{ #category : 'accessing' } -JSONSchemaDefinition >> const [ - ^ const -] - -{ #category : 'accessing' } -JSONSchemaDefinition >> const: aValue [ - "const may legitimately be null, so track presence separately from the value." - constSpecified := true. - const := aValue -] - -{ #category : 'accessing' } -JSONSchemaDefinition >> constSpecified [ - ^ constSpecified ifNil: [ false ] -] - -{ #category : 'accessing' } -JSONSchemaDefinition >> contains [ - ^ contains -] - -{ #category : 'accessing' } -JSONSchemaDefinition >> contains: aDefinition [ - contains := aDefinition -] - -{ #category : 'accessing' } -JSONSchemaDefinition >> dependencies [ - ^ dependencies -] - -{ #category : 'accessing' } -JSONSchemaDefinition >> dependencies: aDictionary [ - dependencies := aDictionary -] - -{ #category : 'accessing' } -JSONSchemaDefinition >> propertyNames [ - ^ propertyNames -] - -{ #category : 'accessing' } -JSONSchemaDefinition >> propertyNames: aDefinition [ - propertyNames := aDefinition -] - { #category : 'accessing' } JSONSchemaDefinition class >> neoJsonMapping: mapper [ mapper for: self do: [ :mapping | @@ -228,6 +137,26 @@ JSONSchemaDefinition class >> neoJsonMapping: mapper [ JSONSchemaDefinition readItemsValueFrom: jsonReader ] ] ] +{ #category : 'instance creation' } +JSONSchemaDefinition class >> readDependencyValueFrom: jsonReader [ + "A dependencies-map value is either an array of required property names + (property dependency) or a schema (schema dependency, which may itself be a + bare true/false). Returns an Array of Strings for the former, a + JSONSchemaDefinition for the latter." + | matched value names | + matched := false. + value := nil. + jsonReader parseConstantDo: [ :v | matched := true. value := v ]. + matched ifTrue: [ ^ JSONSchemaDefinition new booleanValue: value; yourself ]. + (jsonReader matchChar: $[) ifFalse: [ ^ jsonReader nextAs: JSONSchemaDefinition ]. + names := OrderedCollection new. + (jsonReader matchChar: $]) ifFalse: [ + [ names add: jsonReader parseString. + jsonReader matchChar: $, ] whileTrue. + jsonReader expectChar: $] ]. + ^ names asArray +] + { #category : 'instance creation' } JSONSchemaDefinition class >> readFrom: stream [ "A JSON Schema document is either a JSON object (the common case, read via NeoJSON's @@ -246,11 +175,47 @@ JSONSchemaDefinition class >> readFrom: stream [ ^ reader nextAs: self ] +{ #category : 'instance creation' } +JSONSchemaDefinition class >> readItemsValueFrom: jsonReader [ + "items is either a single schema (object or bare true/false) or an array of + schemas (tuple/positional validation). Returns a JSONSchemaDefinition for the + former, an Array of JSONSchemaDefinition for the latter." + | matched value | + matched := false. + value := nil. + jsonReader parseConstantDo: [ :v | matched := true. value := v ]. + matched ifTrue: [ ^ JSONSchemaDefinition new booleanValue: value; yourself ]. + (jsonReader matchChar: $[) ifFalse: [ ^ jsonReader nextAs: JSONSchemaDefinition ]. + ^ Array streamContents: [ :stream | + (jsonReader matchChar: $]) ifFalse: [ + [ | m v elem | + m := false. v := nil. + jsonReader parseConstantDo: [ :x | m := true. v := x ]. + elem := (m and: [ v isKindOf: Boolean ]) + ifTrue: [ JSONSchemaDefinition new booleanValue: v; yourself ] + ifFalse: [ jsonReader nextAs: JSONSchemaDefinition ]. + stream nextPut: elem. + jsonReader matchChar: $, ] whileTrue. + jsonReader expectChar: $] ] ] +] + { #category : 'visiting' } JSONSchemaDefinition >> acceptJSONSchema: aVisitor [ ^ aVisitor visitSchemaSpec: self ] +{ #category : 'accessing' } +JSONSchemaDefinition >> additionalItems [ + ^ additionalItems +] + +{ #category : 'accessing' } +JSONSchemaDefinition >> additionalItems: anObject [ + "Mapped via #SingleSchema (a bare true/false becomes a booleanValue definition); + asJSONSchema resolves it. nil = absent = extra items allowed." + additionalItems := anObject ifNotNil: [ anObject asJSONSchema ] +] + { #category : 'accessing' } JSONSchemaDefinition >> additionalProperties [ ^ additionalProperties @@ -266,74 +231,81 @@ JSONSchemaDefinition >> additionalProperties: anObject [ ] { #category : 'accessing' } -JSONSchemaDefinition >> patternProperties [ - ^ patternProperties +JSONSchemaDefinition >> allOf [ + ^ allOf ] { #category : 'accessing' } -JSONSchemaDefinition >> patternProperties: aDictionary [ - patternProperties := aDictionary +JSONSchemaDefinition >> allOf: aCollection [ + allOf := aCollection ] { #category : 'accessing' } -JSONSchemaDefinition >> ifSchema [ - ^ ifSchema +JSONSchemaDefinition >> anyOf [ + ^ anyOf ] { #category : 'accessing' } -JSONSchemaDefinition >> ifSchema: aDefinition [ - ifSchema := aDefinition +JSONSchemaDefinition >> anyOf: aCollection [ + anyOf := aCollection ] { #category : 'accessing' } -JSONSchemaDefinition >> thenSchema [ - ^ thenSchema +JSONSchemaDefinition >> asJSONSchema [ + "^ schemaClass newFromSchemaSpec: self " + booleanValue ifNotNil: [ + ^ booleanValue + ifTrue: [ JSONSchemaAnyObject new ] + ifFalse: [ JSONSchemaBooleanFalse new ] ]. + ^ (self as: (schemaClass ifNil: [ JSONSchemaAnyObject ])) + initializeFromDefinition: self ] { #category : 'accessing' } -JSONSchemaDefinition >> thenSchema: aDefinition [ - thenSchema := aDefinition +JSONSchemaDefinition >> booleanValue [ + ^ booleanValue ] { #category : 'accessing' } -JSONSchemaDefinition >> elseSchema [ - ^ elseSchema +JSONSchemaDefinition >> booleanValue: aBoolean [ + booleanValue := aBoolean ] { #category : 'accessing' } -JSONSchemaDefinition >> elseSchema: aDefinition [ - elseSchema := aDefinition +JSONSchemaDefinition >> const [ + ^ const ] { #category : 'accessing' } -JSONSchemaDefinition >> allOf [ - ^ allOf +JSONSchemaDefinition >> const: aValue [ + "const may legitimately be null, so track presence separately from the value." + constSpecified := true. + const := aValue ] { #category : 'accessing' } -JSONSchemaDefinition >> allOf: aCollection [ - allOf := aCollection +JSONSchemaDefinition >> constSpecified [ + ^ constSpecified ifNil: [ false ] ] { #category : 'accessing' } -JSONSchemaDefinition >> asJSONSchema [ - "^ schemaClass newFromSchemaSpec: self " - booleanValue ifNotNil: [ - ^ booleanValue - ifTrue: [ JSONSchemaAnyObject new ] - ifFalse: [ JSONSchemaBooleanFalse new ] ]. - ^ (self as: (schemaClass ifNil: [ JSONSchemaAnyObject ])) - initializeFromDefinition: self +JSONSchemaDefinition >> contains [ + ^ contains ] { #category : 'accessing' } -JSONSchemaDefinition >> booleanValue [ - ^ booleanValue +JSONSchemaDefinition >> contains: aDefinition [ + contains := aDefinition ] { #category : 'accessing' } -JSONSchemaDefinition >> booleanValue: aBoolean [ - booleanValue := aBoolean +JSONSchemaDefinition >> dependencies [ + ^ dependencies +] + +{ #category : 'accessing' } +JSONSchemaDefinition >> dependencies: aDictionary [ + dependencies := aDictionary ] { #category : 'accessing' } @@ -346,6 +318,16 @@ JSONSchemaDefinition >> description: aString [ description := aString ] +{ #category : 'accessing' } +JSONSchemaDefinition >> elseSchema [ + ^ elseSchema +] + +{ #category : 'accessing' } +JSONSchemaDefinition >> elseSchema: aDefinition [ + elseSchema := aDefinition +] + { #category : 'accessing' } JSONSchemaDefinition >> enum [ ^ enum @@ -404,6 +386,16 @@ JSONSchemaDefinition >> formatString: aString [ format := JSONFormat formatNamed: aString ifAbsent: [ nil ] ] +{ #category : 'accessing' } +JSONSchemaDefinition >> ifSchema [ + ^ ifSchema +] + +{ #category : 'accessing' } +JSONSchemaDefinition >> ifSchema: aDefinition [ + ifSchema := aDefinition +] + { #category : 'initialization' } JSONSchemaDefinition >> initialize [ super initialize. @@ -415,18 +407,6 @@ JSONSchemaDefinition >> isReference [ ^ reference notNil ] -{ #category : 'accessing' } -JSONSchemaDefinition >> additionalItems [ - ^ additionalItems -] - -{ #category : 'accessing' } -JSONSchemaDefinition >> additionalItems: anObject [ - "Mapped via #SingleSchema (a bare true/false becomes a booleanValue definition); - asJSONSchema resolves it. nil = absent = extra items allowed." - additionalItems := anObject ifNotNil: [ anObject asJSONSchema ] -] - { #category : 'accessing' } JSONSchemaDefinition >> items [ ^ items @@ -548,23 +528,23 @@ JSONSchemaDefinition >> oneOf: aCollection [ ] { #category : 'accessing' } -JSONSchemaDefinition >> anyOf [ - ^ anyOf +JSONSchemaDefinition >> pattern [ + ^ pattern ] { #category : 'accessing' } -JSONSchemaDefinition >> anyOf: aCollection [ - anyOf := aCollection +JSONSchemaDefinition >> pattern: aString [ + pattern := aString ] { #category : 'accessing' } -JSONSchemaDefinition >> pattern [ - ^ pattern +JSONSchemaDefinition >> patternProperties [ + ^ patternProperties ] { #category : 'accessing' } -JSONSchemaDefinition >> pattern: aString [ - pattern := aString +JSONSchemaDefinition >> patternProperties: aDictionary [ + patternProperties := aDictionary ] { #category : 'accessing' } @@ -578,6 +558,16 @@ JSONSchemaDefinition >> properties: anObject [ properties := anObject ] +{ #category : 'accessing' } +JSONSchemaDefinition >> propertyNames [ + ^ propertyNames +] + +{ #category : 'accessing' } +JSONSchemaDefinition >> propertyNames: aDefinition [ + propertyNames := aDefinition +] + { #category : 'accessing' } JSONSchemaDefinition >> reference [ ^ reference @@ -610,6 +600,16 @@ JSONSchemaDefinition >> schemaClass: aClass [ schemaClass := aClass ] +{ #category : 'accessing' } +JSONSchemaDefinition >> thenSchema [ + ^ thenSchema +] + +{ #category : 'accessing' } +JSONSchemaDefinition >> thenSchema: aDefinition [ + thenSchema := aDefinition +] + { #category : 'accessing' } JSONSchemaDefinition >> title [ ^ title diff --git a/source/JSONSchema-Core/JSONSchemaNotConstraint.class.st b/source/JSONSchema-Core/JSONSchemaNotConstraint.class.st index 7e75aaf..c1bb672 100644 --- a/source/JSONSchema-Core/JSONSchemaNotConstraint.class.st +++ b/source/JSONSchema-Core/JSONSchemaNotConstraint.class.st @@ -9,6 +9,11 @@ Class { #tag : 'Constraints' } +{ #category : 'accessing' } +JSONSchemaNotConstraint >> definitionProperties [ + ^ #( not ) +] + { #category : 'accessing' } JSONSchemaNotConstraint >> not [ ^ not @@ -19,11 +24,6 @@ JSONSchemaNotConstraint >> not: aDefinition [ not := aDefinition ] -{ #category : 'accessing' } -JSONSchemaNotConstraint >> definitionProperties [ - ^ #( not ) -] - { #category : 'validation' } JSONSchemaNotConstraint >> validate [ ^ not notNil diff --git a/source/JSONSchema-Core/JSONSchemaObject.class.st b/source/JSONSchema-Core/JSONSchemaObject.class.st index da422bb..cb9800d 100644 --- a/source/JSONSchema-Core/JSONSchemaObject.class.st +++ b/source/JSONSchema-Core/JSONSchemaObject.class.st @@ -181,7 +181,7 @@ JSONSchemaObject >> readObject: jsonObject [ readProperties: properties from: jsonObject writeTo: object. - additionalProperties ifTrue: [ + (additionalProperties isKindOf: JSONSchemaBooleanFalse) ifFalse: [ (jsonObject keys difference: properties keys) do: [ :each | object at: each put: (jsonObject at: each) ] ]. ^ object diff --git a/source/JSONSchema-Core/JSONSchemaOneOfConstraint.class.st b/source/JSONSchema-Core/JSONSchemaOneOfConstraint.class.st index 873968d..0ef52d8 100644 --- a/source/JSONSchema-Core/JSONSchemaOneOfConstraint.class.st +++ b/source/JSONSchema-Core/JSONSchemaOneOfConstraint.class.st @@ -9,6 +9,11 @@ Class { #tag : 'Constraints' } +{ #category : 'accessing' } +JSONSchemaOneOfConstraint >> definitionProperties [ + ^ #( oneOf ) +] + { #category : 'accessing' } JSONSchemaOneOfConstraint >> oneOf [ ^ oneOf @@ -19,11 +24,6 @@ JSONSchemaOneOfConstraint >> oneOf: aCollection [ oneOf := aCollection ] -{ #category : 'accessing' } -JSONSchemaOneOfConstraint >> definitionProperties [ - ^ #( oneOf ) -] - { #category : 'validation' } JSONSchemaOneOfConstraint >> validate [ ^ oneOf notNil diff --git a/source/JSONSchema-Core/JSONSchemaPatternConstraint.class.st b/source/JSONSchema-Core/JSONSchemaPatternConstraint.class.st index 871956a..d5c6016 100644 --- a/source/JSONSchema-Core/JSONSchemaPatternConstraint.class.st +++ b/source/JSONSchema-Core/JSONSchemaPatternConstraint.class.st @@ -9,6 +9,11 @@ Class { #tag : 'Constraints' } +{ #category : 'accessing' } +JSONSchemaPatternConstraint >> definitionProperties [ + ^ #( pattern ) +] + { #category : 'accessing' } JSONSchemaPatternConstraint >> pattern [ ^ pattern @@ -19,11 +24,6 @@ JSONSchemaPatternConstraint >> pattern: aString [ pattern := aString ] -{ #category : 'accessing' } -JSONSchemaPatternConstraint >> definitionProperties [ - ^ #( pattern ) -] - { #category : 'validation' } JSONSchemaPatternConstraint >> validate [ ^ pattern notNil diff --git a/source/JSONSchema-Core/JSONSchemaRequiredConstraint.class.st b/source/JSONSchema-Core/JSONSchemaRequiredConstraint.class.st index 3ef0532..37fcdd3 100644 --- a/source/JSONSchema-Core/JSONSchemaRequiredConstraint.class.st +++ b/source/JSONSchema-Core/JSONSchemaRequiredConstraint.class.st @@ -9,6 +9,11 @@ Class { #tag : 'Constraints' } +{ #category : 'accessing' } +JSONSchemaRequiredConstraint >> definitionProperties [ + ^ #( required ) +] + { #category : 'accessing' } JSONSchemaRequiredConstraint >> required [ ^ required @@ -19,11 +24,6 @@ JSONSchemaRequiredConstraint >> required: aCollection [ required := aCollection ] -{ #category : 'accessing' } -JSONSchemaRequiredConstraint >> definitionProperties [ - ^ #( required ) -] - { #category : 'validation' } JSONSchemaRequiredConstraint >> validate [ ^ required notNil diff --git a/source/JSONSchema-Core/JSONSchemaSingleItemSchemaConstraint.class.st b/source/JSONSchema-Core/JSONSchemaSingleItemSchemaConstraint.class.st index f30f7f4..21022b5 100644 --- a/source/JSONSchema-Core/JSONSchemaSingleItemSchemaConstraint.class.st +++ b/source/JSONSchema-Core/JSONSchemaSingleItemSchemaConstraint.class.st @@ -10,6 +10,14 @@ Class { #tag : 'Constraints' } +{ #category : 'initialization' } +JSONSchemaSingleItemSchemaConstraint >> initializeFromDefinition: aDefinition [ + "items may be a single schema or an array of schemas (tuple); additionalItems + governs positions beyond a tuple." + items := aDefinition items. + additionalItems := aDefinition additionalItems +] + { #category : 'accessing' } JSONSchemaSingleItemSchemaConstraint >> items [ ^ items @@ -20,14 +28,6 @@ JSONSchemaSingleItemSchemaConstraint >> items: aDefinition [ items := aDefinition ] -{ #category : 'initialization' } -JSONSchemaSingleItemSchemaConstraint >> initializeFromDefinition: aDefinition [ - "items may be a single schema or an array of schemas (tuple); additionalItems - governs positions beyond a tuple." - items := aDefinition items. - additionalItems := aDefinition additionalItems -] - { #category : 'validation' } JSONSchemaSingleItemSchemaConstraint >> validate [ ^ items notNil diff --git a/source/JSONSchema-Core/JSONSchemaString.class.st b/source/JSONSchema-Core/JSONSchemaString.class.st index 59e622c..2bd7905 100644 --- a/source/JSONSchema-Core/JSONSchemaString.class.st +++ b/source/JSONSchema-Core/JSONSchemaString.class.st @@ -26,7 +26,7 @@ JSONSchemaString >> readUsing: aReader [ | value | value := aReader parseValue. value ifNil: [ - nullable + self nullable ifTrue: [ ^ nil ] ifFalse: [ JSONTypeError signal: self class typeName, ' cannot be nil' ] ]. ^ self readString: value @@ -39,9 +39,9 @@ JSONSchemaString >> validateType: aString [ ] { #category : 'writing' } -JSONSchemaString >> write: anObject on: writer [ +JSONSchemaString >> write: anObject on: writer [ anObject ifNil: [ - nullable ifTrue: [ + self nullable ifTrue: [ ^ writer writeNull ] ]. ^ writer writeString: (self write: anObject) ]