diff --git a/source/JSONSchema-Core-Tests/JSONSchemaTests.class.st b/source/JSONSchema-Core-Tests/JSONSchemaTests.class.st index 4c111c8..44681d4 100644 --- a/source/JSONSchema-Core-Tests/JSONSchemaTests.class.st +++ b/source/JSONSchema-Core-Tests/JSONSchemaTests.class.st @@ -25,6 +25,40 @@ JSONSchemaTests >> testMultipleConstraintsOnAnyObject [ should: [ schema validate: '1234567' ] raise: JSONConstraintError ] +{ #category : 'tests' } +JSONSchemaTests >> testPatternPropertiesWithEscapedDotInCharacterClassIsSupported [ + "Regression: the OAI OpenAPI-3.0 meta-schema uses [a-zA-Z0-9\.\-_]+ for component-name + patternProperties keys (components.schemas/responses/parameters/etc.) - Pharo's Regex + engine raised 'bad backslash escape' on the escaped dot inside the character class." + | schema | + schema := JSONSchema fromString: '{"type":"object","patternProperties":{"^[a-zA-Z0-9\\.\\-_]+$":{"type":"string"}},"additionalProperties":false}'. + self shouldnt: [ schema validate: {'Foo.Bar-1_2'->'ok'} asDictionary ] raise: Error. + self should: [ schema validate: {'has space'->'ok'} asDictionary ] raise: JSONSchemaError +] + +{ #category : 'tests' } +JSONSchemaTests >> testPatternPropertiesWithNonCapturingGroupIsSupported [ + "Same regression as #testPatternWithNonCapturingGroupIsSupported, but for the + patternProperties keyword (matches property *names*, separate code path/call site)." + | schema | + schema := JSONSchema fromString: '{"type":"object","patternProperties":{"^[1-5](?:\\d{2}|XX)$":{"type":"string"}},"additionalProperties":false}'. + self shouldnt: [ schema validate: {'200'->'ok'} asDictionary ] raise: Error. + self shouldnt: [ schema validate: {'2XX'->'ok'} asDictionary ] raise: Error. + self should: [ schema validate: {'999'->'ok'} asDictionary ] raise: JSONSchemaError +] + +{ #category : 'tests' } +JSONSchemaTests >> testPatternWithNonCapturingGroupIsSupported [ + "Regression: the OAI OpenAPI-3.0 meta-schema's Responses object uses the pattern + '^[1-5](?:\d{2}|XX)$' - Pharo's Regex engine used to raise 'Invalid lookaround + expression ?:' on the (?:...) syntax, crashing any validation that hit it." + | schema | + schema := JSONSchema fromString: '{"type":"string","pattern":"^[1-5](?:\\d{2}|XX)$"}'. + self shouldnt: [ schema validate: '200' ] raise: Error. + self shouldnt: [ schema validate: '2XX' ] raise: Error. + self should: [ schema validate: '999' ] raise: JSONConstraintError +] + { #category : 'tests' } JSONSchemaTests >> testSchemaReadArray [ diff --git a/source/JSONSchema-Core/JSONSchemaPatternConstraint.class.st b/source/JSONSchema-Core/JSONSchemaPatternConstraint.class.st index d5c6016..c203b63 100644 --- a/source/JSONSchema-Core/JSONSchemaPatternConstraint.class.st +++ b/source/JSONSchema-Core/JSONSchemaPatternConstraint.class.st @@ -31,7 +31,7 @@ JSONSchemaPatternConstraint >> validate [ { #category : 'validation' } JSONSchemaPatternConstraint >> validate: aString [ - (pattern asRegex search: aString) ifFalse: [ + (pattern asECMARegex search: aString) ifFalse: [ JSONConstraintError signal: aString, ' does not match pattern ', pattern ] ] diff --git a/source/JSONSchema-Core/JSONSchemaPatternPropertiesConstraint.class.st b/source/JSONSchema-Core/JSONSchemaPatternPropertiesConstraint.class.st index d245efe..341565e 100644 --- a/source/JSONSchema-Core/JSONSchemaPatternPropertiesConstraint.class.st +++ b/source/JSONSchema-Core/JSONSchemaPatternPropertiesConstraint.class.st @@ -36,7 +36,7 @@ JSONSchemaPatternPropertiesConstraint >> validate: aDictionary [ is Boolean true = accept all)." | resolvedPatterns | resolvedPatterns := patternProperties associations collect: [ :assoc | - { assoc key asRegex. self resolveVisitor visitSchemaSpec: assoc value } ]. + { assoc key asECMARegex. self resolveVisitor visitSchemaSpec: assoc value } ]. aDictionary keysAndValuesDo: [ :key :value | | matchedPattern | matchedPattern := false. diff --git a/source/JSONSchema-Core/String.extension.st b/source/JSONSchema-Core/String.extension.st index 971baf5..7016c77 100644 --- a/source/JSONSchema-Core/String.extension.st +++ b/source/JSONSchema-Core/String.extension.st @@ -1,5 +1,34 @@ Extension { #name : 'String' } +{ #category : '*jsonschema-core' } +String >> asECMARegex [ + "Pharo's Regex engine doesn't accept some ECMA-262 syntax that real-world schemas use + (e.g. the OAI OpenAPI meta-schema), even though it is semantically equivalent for the + boolean match/no-match checks pattern/patternProperties need: + - non-capturing groups (?:...) - rewritten to a plain capturing group (...) + - an escaped literal dot \. inside a character class [...] - rewritten to a bare . (both + mean 'literal dot' inside a class; Pharo's engine only accepts the unescaped form there)" + ^ self asECMARegexSource asRegex +] + +{ #category : '*jsonschema-core' } +String >> asECMARegexSource [ + "See #asECMARegex. Kept separate so the rewritten source itself is testable/inspectable." + | source result inClass i char | + source := self copyReplaceAll: '(?:' with: '('. + result := WriteStream on: String new. + i := 1. + inClass := false. + [ i <= source size ] whileTrue: [ + char := source at: i. + (char == $[ and: [ inClass not ]) ifTrue: [ inClass := true ]. + (char == $] and: [ inClass ]) ifTrue: [ inClass := false ]. + (inClass and: [ char == $\ and: [ i < source size and: [ (source at: i + 1) == $. ] ] ]) + ifTrue: [ result nextPut: $.. i := i + 2 ] + ifFalse: [ result nextPut: char. i := i + 1 ] ]. + ^ result contents +] + { #category : '*jsonschema-core' } String >> asJSONSchema [ ^ JSONSchema string