github.com/kaptinlin/jsonschema@v0.4.6/tests/custom_format_test.go (about)

     1  package tests
     2  
     3  import (
     4  	"math"
     5  	"regexp"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/kaptinlin/jsonschema"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  // --- Test Helpers for OpenAPI Formats ---
    15  
    16  func validateTestInt32(v interface{}) bool {
    17  	switch val := v.(type) {
    18  	case int:
    19  		return val >= math.MinInt32 && val <= math.MaxInt32
    20  	case int64:
    21  		return val >= math.MinInt32 && val <= math.MaxInt32
    22  	case float64:
    23  		return val == float64(int64(val)) && val >= math.MinInt32 && val <= math.MaxInt32
    24  	default:
    25  		return false
    26  	}
    27  }
    28  
    29  func validateTestInt64(v interface{}) bool {
    30  	switch val := v.(type) {
    31  	case int, int64:
    32  		// Any int/int64 fits into int64 range and is already integral
    33  		return true
    34  	case float64:
    35  		// Must be an integer value
    36  		if val != float64(int64(val)) {
    37  			return false
    38  		}
    39  		// Check bounds using float64 limits of int64
    40  		return val >= float64(math.MinInt64) && val <= float64(math.MaxInt64)
    41  	default:
    42  		return false
    43  	}
    44  }
    45  
    46  func registerTestOpenAPIFormats(c *jsonschema.Compiler) {
    47  	c.RegisterFormat("int32", validateTestInt32, "number")
    48  	c.RegisterFormat("int64", validateTestInt64, "number")
    49  }
    50  
    51  func TestCustomFormatRegistration(t *testing.T) {
    52  	compiler := jsonschema.NewCompiler()
    53  	compiler.SetAssertFormat(true)
    54  
    55  	compiler.RegisterFormat("identifier", func(v interface{}) bool {
    56  		s, ok := v.(string)
    57  		if !ok {
    58  			return true
    59  		}
    60  		matched, _ := regexp.MatchString(`^[a-z$_][a-zA-Z$_0-9]*$`, s)
    61  		return matched
    62  	}, "string")
    63  
    64  	schema, err := compiler.Compile([]byte(`{"properties": {"name": {"type": "string", "format": "identifier"}}}`))
    65  	require.NoError(t, err)
    66  
    67  	assert.True(t, schema.Validate(map[string]interface{}{"name": "validName"}).IsValid())
    68  	assert.False(t, schema.Validate(map[string]interface{}{"name": "123invalid"}).IsValid())
    69  }
    70  
    71  func TestTypeSpecificFormats(t *testing.T) {
    72  	compiler := jsonschema.NewCompiler()
    73  	compiler.SetAssertFormat(true)
    74  
    75  	compiler.RegisterFormat("percentage", func(v interface{}) bool {
    76  		switch val := v.(type) {
    77  		case float64:
    78  			return val >= 0 && val <= 100
    79  		case int:
    80  			return val >= 0 && val <= 100
    81  		}
    82  		return false
    83  	}, "number")
    84  
    85  	schema, err := compiler.Compile([]byte(`{"properties": {"score": {"type": "number", "format": "percentage"}, "name": {"type": "string", "format": "percentage"}}}`))
    86  	require.NoError(t, err)
    87  
    88  	assert.True(t, schema.Validate(map[string]interface{}{"score": 85.5, "name": "test"}).IsValid())
    89  	assert.False(t, schema.Validate(map[string]interface{}{"score": 150.0, "name": "test"}).IsValid())
    90  }
    91  
    92  func TestCustomFormatOverridesGlobal(t *testing.T) {
    93  	compiler := jsonschema.NewCompiler()
    94  	compiler.SetAssertFormat(true)
    95  
    96  	compiler.RegisterFormat("email", func(v interface{}) bool {
    97  		return strings.Contains(v.(string), "@") && len(v.(string)) > 5
    98  	}, "string")
    99  
   100  	schema, err := compiler.Compile([]byte(`{"properties": {"email": {"type": "string", "format": "email"}}}`))
   101  	require.NoError(t, err)
   102  
   103  	assert.True(t, schema.Validate(map[string]interface{}{"email": "test@example.com"}).IsValid())
   104  	assert.False(t, schema.Validate(map[string]interface{}{"email": "short"}).IsValid())
   105  }
   106  
   107  func TestUnregisterCustomFormat(t *testing.T) {
   108  	compiler := jsonschema.NewCompiler()
   109  	compiler.SetAssertFormat(true)
   110  
   111  	compiler.RegisterFormat("test-format", func(v interface{}) bool { return false }, "string")
   112  	compiler.UnregisterFormat("test-format")
   113  
   114  	schema, err := compiler.Compile([]byte(`{"type": "string", "format": "test-format"}`))
   115  	require.NoError(t, err)
   116  
   117  	assert.False(t, schema.Validate("test").IsValid(), "Validation should fail for an unregistered format when AssertFormat is true")
   118  }
   119  
   120  func TestOpenAPICustomFormatValidation(t *testing.T) {
   121  	compiler := jsonschema.NewCompiler()
   122  	compiler.SetAssertFormat(true)
   123  	registerTestOpenAPIFormats(compiler)
   124  
   125  	t.Run("int32", func(t *testing.T) {
   126  		schema, err := compiler.Compile([]byte(`{"type": "number", "format": "int32"}`))
   127  		require.NoError(t, err)
   128  
   129  		assert.True(t, schema.Validate(123).IsValid())
   130  		assert.True(t, schema.Validate(float64(2147483647)).IsValid())
   131  		assert.False(t, schema.Validate(float64(2147483648)).IsValid(), "int32 overflow")
   132  		assert.False(t, schema.Validate(123.45).IsValid(), "int32 with fraction")
   133  	})
   134  
   135  	t.Run("int64", func(t *testing.T) {
   136  		schema, err := compiler.Compile([]byte(`{"type": "number", "format": "int64"}`))
   137  		require.NoError(t, err)
   138  
   139  		assert.True(t, schema.Validate(1234567890).IsValid())
   140  		assert.False(t, schema.Validate(float64(math.MaxInt64)*2).IsValid(), "int64 overflow")
   141  		assert.False(t, schema.Validate(12345.67).IsValid(), "int64 with fraction")
   142  	})
   143  }