Skip to content

Custom Formats not evaluated #832

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
zsblevins opened this issue Jun 23, 2023 · 6 comments · Fixed by #834
Closed

Custom Formats not evaluated #832

zsblevins opened this issue Jun 23, 2023 · 6 comments · Fixed by #834

Comments

@zsblevins
Copy link

zsblevins commented Jun 23, 2023

I've created several custom formats but they don't seem to be evaluating:

Sample custom format:

public class IpHostPrefixFormat extends AbstractFormat {
    public IpHostPrefixFormat() {
        super("ip_host_prefix", "must be a valid host IP with prefix length");
    }

    public IpHostPrefixFormat(String name, String errorMessageDescription) {
        super(name, errorMessageDescription);
    }

    protected IPAddress convertToAddress(String value) {
        try {
            IPAddressString addressString = new IPAddressString(value);
            return addressString.toAddress();
        } catch(AddressStringException e) {
            return null;
        }
    }

    protected boolean validateAddress(String value, IPAddress address) {
        //Ensure full CIDR supplied
        if (address.getNetworkPrefixLength() == null) {
            return false;
        }
        //Ensure not a network address
        if (address.isPrefixBlock()){
            // Exceptions for /31-32 and /127-128
            if (address.getIPVersion().isIPv4() && address.getNetworkPrefixLength() < 31) {
                return false;
            }
            if (address.getIPVersion().isIPv6() && address.getNetworkPrefixLength() < 127) {
                return false;
            }
        }
        return address.toString().equals(value.toLowerCase());
    }
    @Override
    public boolean matches(String value) {
        IPAddress address = convertToAddress(value);
        if (address == null) {
            return false;
        }
        return validateAddress(value, address);
    }
}

I create my JSONSchema as follows:

private JsonSchema buildSchema(JsonNode schemaJson) {
        String schemaUri = schemaJson.get("$schema").asText();
        JsonMetaSchema jsonMetaSchema =
                new JsonMetaSchema.Builder(schemaUri).addFormats(formats).build();
        JsonSchemaFactory jsonSchemaFactory =
                new JsonSchemaFactory.Builder().defaultMetaSchemaURI(schemaUri).addMetaSchema(jsonMetaSchema).build();
        return jsonSchemaFactory.getSchema(schemaJson);
    }

Where formats includes new IpHostPrefixFormat() in the list.

If I then test the following schema against the json, it passes validation when it should fail.

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "cidr": {
      "type": "string",
      "format": "ip_host_prefix"
    }
  },
  "required": ["cidr"],
  "title": "Sample ip schema",
  "type": "object"
}
{
  "cidr": "bogus"
}

I wrote test cases for the matches method on every custom formatter and confirmed they work as expected, it just seems like the schema isn't using them. Am I constructing the Meta schema improperly?

Note that standard validators (e.g. string, number, etc) are all working fine in my tests, it's only the custom formats that do not work.

@zsblevins
Copy link
Author

zsblevins commented Jun 23, 2023

I'm fairly certain my issue was that I was assuming passing the schema URI would load the right "base" validator and that I was adding additional formats. Looking more closely at the code, that doesn't seem to be the case, but doing:

private JsonSchema buildSchema(JsonNode schemaJson) {
        String schemaUri = schemaJson.get("$schema").asText();
        JsonMetaSchema jsonMetaSchema = new JsonMetaSchema.Builder(schemaUri)
                .idKeyword("$id")
                .addFormats(formats)
                .addKeywords(ValidatorTypeCode.getNonFormatKeywords(SpecVersion.VersionFlag.V7))
                // keywords that may validly exist, but have no validation aspect to them
                .addKeywords(Arrays.asList(
                        new NonValidationKeyword("$schema"),
                        new NonValidationKeyword("$id"),
                        new NonValidationKeyword("title"),
                        new NonValidationKeyword("description"),
                        new NonValidationKeyword("default"),
                        new NonValidationKeyword("definitions"),
                        new NonValidationKeyword("$comment"),
                        new NonValidationKeyword("contentMediaType"),
                        new NonValidationKeyword("contentEncoding"),
                        new NonValidationKeyword("examples"),
                        new NonValidationKeyword("then"),
                        new NonValidationKeyword("else"),
                        new NonValidationKeyword("additionalItems"),
                        new NonValidationKeyword("message")
                ))
                .build();
        JsonSchemaFactory jsonSchemaFactory =
                new JsonSchemaFactory.Builder().defaultMetaSchemaURI(schemaUri).addMetaSchema(jsonMetaSchema).build();
        return jsonSchemaFactory.getSchema(schemaJson);
    }

Doesn't seem to help.

@fdutton
Copy link
Contributor

fdutton commented Jun 26, 2023

@zsblevins Take a look at this unit-test and see if this helps. I cannot test your code since I don't have IPAddress and IPAddressString.

https://github.com/networknt/json-schema-validator/blob/master/src/test/java/com/networknt/schema/Issue784Test.java

@zsblevins
Copy link
Author

I don't really see anything I'm doing differently than that test case aside from using new names instead of overwriting existing. Here's another example that fails that doesn't use an IP addressing library:

import com.networknt.schema.format.PatternFormat;

public class VrfNameFormat extends PatternFormat {
    public static final String PATTERN = "^[A-Z0-9\\-_]+$";
    public VrfNameFormat() {
        super("vrf_name", PATTERN,"must match " + PATTERN);
    }
}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "vrf": {
      "type": "string",
      "format": "vrf_name"
    }
  },
  "required": ["vrf"],
  "title": "Sample regex schema",
  "type": "object"
}
{ "vrf": "test 123"}

@stacywsmith
Copy link
Contributor

I believe I see the issue:
The custom metaschema is saved into JsonSchemaFactory.jsonMetaSchemas here with the uri of http://json-schema.org/draft-07/schema# as the key.

Later, an attempt to read from this hash map is made here with the normalized uri of https://json-schema.org/draft-07/schema as the key. That key does not exist, so the standard V7 meta schema is loaded instead. This explains why the built-in formats are working, but the custom formats are not.

@zsblevins
Copy link
Author

Just wanted to check in on when a release will be created containing this change. While I was able to work around it in my tests, we have a ton of legacy JSON Schemas that we're using that have the non-normalized schema URL format and that we can't safely change without concern that we'd break the existing tooling that interacts with them.

@stevehu
Copy link
Contributor

stevehu commented Jul 5, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants