github.com/kaptinlin/jsonschema@v0.4.6/examples/custom-formats/main.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/base64"
     5  	"fmt"
     6  	"math"
     7  
     8  	"github.com/kaptinlin/jsonschema"
     9  )
    10  
    11  // --- OpenAPI Format Validators ---
    12  
    13  // validateInt32 checks if the value is a valid 32-bit integer.
    14  func validateInt32(v interface{}) bool {
    15  	switch val := v.(type) {
    16  	case int:
    17  		return val >= math.MinInt32 && val <= math.MaxInt32
    18  	case int32:
    19  		return true
    20  	case int64:
    21  		return val >= math.MinInt32 && val <= math.MaxInt32
    22  	case float64:
    23  		// Check if it's a whole number in valid range
    24  		return val == float64(int64(val)) && val >= math.MinInt32 && val <= math.MaxInt32
    25  	default:
    26  		return false
    27  	}
    28  }
    29  
    30  // validateInt64 checks if the value is a valid 64-bit integer.
    31  func validateInt64(v interface{}) bool {
    32  	switch val := v.(type) {
    33  	case int, int32, int64:
    34  		return true
    35  	case float64:
    36  		// Check if it's a whole number
    37  		return val == float64(int64(val))
    38  	default:
    39  		return false
    40  	}
    41  }
    42  
    43  // validateFloat checks if the value is a valid 32-bit float.
    44  func validateFloat(v interface{}) bool {
    45  	switch val := v.(type) {
    46  	case float32:
    47  		return true
    48  	case float64:
    49  		return val >= -math.MaxFloat32 && val <= math.MaxFloat32
    50  	default:
    51  		return false
    52  	}
    53  }
    54  
    55  // validateDouble checks if the value is a valid 64-bit float (double).
    56  func validateDouble(v interface{}) bool {
    57  	_, ok := v.(float64)
    58  	return ok
    59  }
    60  
    61  // validateByte checks if the value is a valid base64 string.
    62  func validateByte(v interface{}) bool {
    63  	s, ok := v.(string)
    64  	if !ok {
    65  		return true
    66  	}
    67  	_, err := base64.StdEncoding.DecodeString(s)
    68  	return err == nil
    69  }
    70  
    71  // validateBinary always returns true, as it's for any binary data.
    72  func validateBinary(v interface{}) bool {
    73  	_, ok := v.(string)
    74  	return ok
    75  }
    76  
    77  // validatePassword always returns true, as it's a hint for UI.
    78  func validatePassword(v interface{}) bool {
    79  	_, ok := v.(string)
    80  	return ok
    81  }
    82  
    83  // registerOpenAPIFormats demonstrates how to register OpenAPI 3.0 built-in formats.
    84  func registerOpenAPIFormats(c *jsonschema.Compiler) {
    85  	// Number formats (including integers)
    86  	c.RegisterFormat("int32", validateInt32, "number")
    87  	c.RegisterFormat("int64", validateInt64, "number")
    88  	c.RegisterFormat("float", validateFloat, "number")
    89  	c.RegisterFormat("double", validateDouble, "number")
    90  
    91  	// String formats
    92  	c.RegisterFormat("byte", validateByte, "string")
    93  	c.RegisterFormat("binary", validateBinary, "string")
    94  	c.RegisterFormat("password", validatePassword, "string")
    95  
    96  	// Note: `date` and `date-time` are standard formats already included in the library.
    97  	fmt.Println("Registered custom formats to support OpenAPI 3.0 built-ins.")
    98  }
    99  
   100  func main() {
   101  	// Create a new compiler and register OpenAPI formats
   102  	compiler := jsonschema.NewCompiler()
   103  	compiler.SetAssertFormat(true) // Enable format validation
   104  	registerOpenAPIFormats(compiler)
   105  
   106  	// Define a schema that uses OpenAPI formats
   107  	schemaBytes := []byte(`{
   108  		"$schema": "https://json-schema.org/draft/2020-12/schema",
   109  		"title": "User Profile",
   110  		"type": "object",
   111  		"properties": {
   112  			"userId": {
   113  				"type": "number",
   114  				"format": "int64"
   115  			},
   116  			"age": {
   117  				"type": "number",
   118  				"format": "int32"
   119  			},
   120  			"avatar": {
   121  				"type": "string",
   122  				"format": "byte"
   123  			},
   124  			"apiKey": {
   125  				"type": "string",
   126  				"format": "password"
   127  			}
   128  		},
   129  		"required": ["userId", "age", "avatar"]
   130  	}`)
   131  
   132  	schema, err := compiler.Compile(schemaBytes)
   133  	if err != nil {
   134  		fmt.Printf("Error compiling schema: %s\n", err)
   135  		return
   136  	}
   137  
   138  	// --- Test with valid data ---
   139  	fmt.Println("\n--- 1. Validation with valid data ---")
   140  	validData := map[string]interface{}{
   141  		"userId": 9223372036854775807, // Max int64
   142  		"age":    30,
   143  		"avatar": "SGVsbG8sIHdvcmxkIQ==", // "Hello, world!" in base64
   144  		"apiKey": "a-secret-key",
   145  	}
   146  	result := schema.Validate(validData)
   147  	fmt.Printf("Result: IsValid=%v\n", result.IsValid())
   148  
   149  	// --- Test with invalid data ---
   150  	fmt.Println("\n--- 2. Validation with invalid data ---")
   151  	invalidData := map[string]interface{}{
   152  		"userId": 9223372036854775807,
   153  		"age":    2147483648,           // Exceeds max int32
   154  		"avatar": "this is not base64", // Not a valid base64 string
   155  	}
   156  	result = schema.Validate(invalidData)
   157  	fmt.Printf("Result: IsValid=%v\n", result.IsValid())
   158  	if !result.IsValid() {
   159  		fmt.Println("Errors:")
   160  		for _, detail := range result.Details {
   161  			for _, err := range detail.Errors {
   162  				if err.Keyword == "format" {
   163  					fmt.Printf(" - Location: %s, Message: %s\n", detail.InstanceLocation, err.Message)
   164  				}
   165  			}
   166  		}
   167  	}
   168  }