Skip to content

Commit

Permalink
OAP-194 add $ref - reference to another schema
Browse files Browse the repository at this point in the history
  • Loading branch information
nofateg authored Aug 15, 2024
1 parent 1ff76ad commit 0316c10
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,16 @@ public PropertyParser<Pattern> asPattern( String property ) {
public PropertyParser<AbstractSchemaASTWrapper> asAST( String property, JsonSchemaParserContext context ) {
return new PropertyParser<>( property, properties,
Optional.ofNullable( context.node.get( property ) ).map( n -> {
JsonSchemaParserContext newContext = context.withNode( property, n );
AbstractSchemaASTWrapper aw = context.mapParser.apply( newContext );
newContext.astW.putIfAbsent( aw.id, aw );
NodeResponse nodeResponse = context.withNode( property, n );

AbstractSchemaASTWrapper aw;

if( nodeResponse.schema != null ) {
aw = nodeResponse.schema;
} else {
aw = context.mapParser.apply( nodeResponse.context );
}
context.astW.putIfAbsent( aw.id, aw );
return aw;
} ) );
}
Expand All @@ -150,11 +157,9 @@ public PropertyParser<AbstractSchemaASTWrapper> asAST( String property, JsonSche
private Optional<EnumFunction> toEnum( Object anEnum ) {
if( anEnum == null ) {
return Optional.empty();
} else if( anEnum instanceof List<?> ) {
return Optional.of( new ListObjectEnumFunction( ( List<Object> ) anEnum ) );
} else if( anEnum instanceof Map<?, ?> ) {
Map<?, ?> map = ( Map<?, ?> ) anEnum;

} else if( anEnum instanceof List list ) {
return Optional.of( new ListObjectEnumFunction( list ) );
} else if( anEnum instanceof Map<?, ?> map ) {
String jsonPath = ( String ) map.get( JSON_PATH );
Function<Object, List<Object>> sourceFunc = obj -> new JsonPath( jsonPath ).traverse( obj );

Expand Down Expand Up @@ -204,27 +209,36 @@ public PropertyParser<LinkedHashMap<String, AbstractSchemaASTWrapper<?>>> asMapA
map.ifPresent(
m -> m.forEach( ( okey, value ) -> {
String key = ( String ) okey;
JsonSchemaParserContext newContext = context.withNode( key, value );
AbstractSchemaASTWrapper astw = newContext.astW.get( newContext.getId() );
if( astw == null ) {
astw = context.mapParser.apply( newContext );
newContext.astW.put( newContext.getId(), astw );
NodeResponse nodeResponse = context.withNode( key, value );

AbstractSchemaASTWrapper astw;
if( nodeResponse.schema != null ) {
astw = nodeResponse.schema;
} else {
JsonSchemaParserContext newContext = nodeResponse.context;
astw = newContext.astW.get( nodeResponse.context.getId() );

if( astw == null ) {
astw = context.mapParser.apply( newContext );
}
}

context.astW.put( astw.id, astw );
p.put( key, astw );
} )
);

return new PropertyParser<>( property, properties, map.map( v -> p ) );
return new PropertyParser<>( property, properties, map.map( _ -> p ) );
}

public Optional<BooleanReference> asBooleanReference( String field ) {
Object enabled = properties.node.get( field );
if( enabled == null ) return Optional.empty();
Object enabledObj = properties.node.get( field );
if( enabledObj == null ) return Optional.empty();

if( enabled instanceof Boolean ) {
return Optional.of( ( Boolean ) enabled ? BooleanReference.TRUE : BooleanReference.FALSE );
if( enabledObj instanceof Boolean enabled ) {
return Optional.of( enabled ? BooleanReference.TRUE : BooleanReference.FALSE );
} else {
Map map = ( Map ) enabled;
Map map = ( Map ) enabledObj;
String jsonPath = ( String ) map.get( JSON_PATH );

OperationFunction of = getOperationFunction( map );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ public List<String> validate( Object json, boolean ignoreRequiredDefault ) {


private AbstractSchemaASTWrapper parse( String schema, JsonSchemaParserContext context ) {
return parse( context.withNode( "", parseWithTemplate( schema, context.storage ) ) );
NodeResponse nodeResponse = context.withNode( "", parseWithTemplate( schema, context.storage ) );
return nodeResponse.schema != null ? nodeResponse.schema : parse( nodeResponse.context );
}

AbstractSchemaASTWrapper parse( String schema, SchemaStorage storage ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,27 +64,37 @@ public JsonSchemaParserContext(
this.storage = storage;
}

public final JsonSchemaParserContext withNode( String field, Object mapObject ) {
if( !( mapObject instanceof Map<?, ?> ) )
throw new IllegalArgumentException( "object expected, but " + mapObject );
Map<?, ?> map = ( Map<?, ?> ) mapObject;
public final NodeResponse withNode( String field, Object mapObject ) {
if( !( mapObject instanceof Map<?, ?> map ) )
throw new JsonSchemaException( "object expected, but " + mapObject );

Object schemaType = map.get( "type" );
Object ref = map.get( "$ref" );
if( ref != null ) {
if( !( ref instanceof String url ) ) {
throw new JsonSchemaException( "object expected, but " + mapObject );
}

if( schemaType instanceof String ) {
return new JsonSchemaParserContext( schemaName, map,
( String ) schemaType,
AbstractSchemaASTWrapper astw = urlParser.apply( SchemaPath.resolve( rootPath, path ), url );
return new NodeResponse( astw );
} else {
Object schemaTypeObj = map.get( "type" );

if( schemaTypeObj instanceof String schemaType ) {
return new NodeResponse( new JsonSchemaParserContext( schemaName, map,
schemaType,
mapParser,
urlParser,
rootPath,
SchemaPath.resolve( path, field ),
astW,
ast,
storage );
} else {
throw new UnknownTypeValidationSyntaxException(
"Unknown SchemaType type: " + ( schemaType == null ? "nothing" : schemaType.getClass() ) + " for field: [" + field + "] and map: " + map
);
storage ) );
} else {
throw new UnknownTypeValidationSyntaxException(
"Unknown SchemaType type: " + ( schemaType == null ? "nothing"
: schemaType.getClass() ) + " for field: [" + field + "] and map: " + map
);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package oap.json.schema;

public class NodeResponse {
public final JsonSchemaParserContext context;
public final AbstractSchemaASTWrapper schema;

public NodeResponse( JsonSchemaParserContext context ) {
this.context = context;
this.schema = null;
}

public NodeResponse( AbstractSchemaASTWrapper schema ) {
this.schema = schema;
this.context = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ protected static void assertFailure( String schema, String json, String error )
protected static void assertFailure( String schema, String json, SchemaStorage storage, String... error ) {
List<String> result = JsonSchema.schemaFromString( schema, storage )
.validate( Binder.json.unmarshal( Object.class, json ), false );
if( result.isEmpty() ) Assert.fail( json + " -> " + error );
if( result.isEmpty() ) {
Assert.fail( json + " -> " + List.of( error ) );
}
assertThat( result ).containsOnly( error );
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* The MIT License (MIT)
*
* Copyright (c) Open Application Platform Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package oap.json.schema;

import org.testng.annotations.Test;

public class SchemaRefTest extends AbstractSchemaTest {
@Test
public void extendsSchema() {
String schema = """
{
type = object
additionalProperties = false
properties {
field1 = {"$ref" = "/schema/test2" }
}
}""";

String schema2 = """
{
type = object
additionalProperties = false
properties {
a {
type = string
}
}
}""";

assertOk( schema, "{'field1': {'a': 'test'}}", url -> schema2, false );
assertFailure( schema, "{'field1': {'a': 1}}",
_ -> schema2, "/field1/a: instance type is number, but allowed type is string"
);
}
}
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
</distributionManagement>

<properties>
<oap.project.version>22.4.10</oap.project.version>
<oap.project.version>22.4.11</oap.project.version>

<oap.deps.config.version>21.0.0</oap.deps.config.version>
<oap.deps.oap-teamcity.version>21.0.1</oap.deps.oap-teamcity.version>
Expand Down

0 comments on commit 0316c10

Please sign in to comment.