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

     1  package jsonschema
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  	"net/url"
     7  	"path"
     8  	"reflect"
     9  	"strings"
    10  
    11  	"github.com/goccy/go-json"
    12  )
    13  
    14  // replace substitutes placeholders in a template string with actual parameter values.
    15  func replace(template string, params map[string]interface{}) string {
    16  	for key, value := range params {
    17  		placeholder := "{" + key + "}"
    18  		template = strings.ReplaceAll(template, placeholder, fmt.Sprint(value))
    19  	}
    20  
    21  	return template
    22  }
    23  
    24  // mergeIntMaps merges two integer maps. The values in the second map overwrite the first where keys overlap.
    25  func mergeIntMaps(map1, map2 map[int]bool) map[int]bool {
    26  	for key, value := range map2 {
    27  		map1[key] = value
    28  	}
    29  	return map1
    30  }
    31  
    32  // mergeStringMaps merges two string maps. The values in the second map overwrite the first where keys overlap.
    33  func mergeStringMaps(map1, map2 map[string]bool) map[string]bool {
    34  	for key, value := range map2 {
    35  		map1[key] = value
    36  	}
    37  	return map1
    38  }
    39  
    40  // getDataType identifies the JSON schema type for a given Go value.
    41  func getDataType(v interface{}) string {
    42  	switch v := v.(type) {
    43  	case nil:
    44  		return "null"
    45  	case bool:
    46  		return "boolean"
    47  	case json.Number:
    48  		// Try as an integer first
    49  		if _, ok := new(big.Int).SetString(string(v), 10); ok {
    50  			return "integer" // json.Number without a decimal part, can be considered an integer
    51  		}
    52  		// Fallback to big float to check if it is an integer
    53  		if bigFloat, ok := new(big.Float).SetString(string(v)); ok {
    54  			if _, acc := bigFloat.Int(nil); acc == big.Exact {
    55  				return "integer"
    56  			}
    57  			return "number"
    58  		}
    59  	case float32, float64:
    60  		// Convert to big.Float to check if it can be considered an integer
    61  		bigFloat := new(big.Float).SetFloat64(reflect.ValueOf(v).Float())
    62  		if _, acc := bigFloat.Int(nil); acc == big.Exact {
    63  			return "integer" // Treated as integer if no fractional part
    64  		}
    65  		return "number"
    66  	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
    67  		return "integer"
    68  	case string:
    69  		return "string"
    70  	case []interface{}:
    71  		return "array"
    72  	case []bool, []json.Number, []float32, []float64, []int, []int8, []int16, []int32, []int64, []uint, []uint8, []uint16, []uint32, []uint64, []string:
    73  		return "array"
    74  	case map[string]interface{}:
    75  		return "object"
    76  	default:
    77  		// Use reflection for other types (struct, slices, maps, etc.)
    78  		return getDataTypeReflect(v)
    79  	}
    80  	return "unknown"
    81  }
    82  
    83  // getDataTypeReflect uses reflection to determine the JSON schema type for complex Go types.
    84  func getDataTypeReflect(v interface{}) string {
    85  	rv := reflect.ValueOf(v)
    86  
    87  	// Handle pointers by dereferencing them
    88  	for rv.Kind() == reflect.Ptr {
    89  		if rv.IsNil() {
    90  			return "null"
    91  		}
    92  		rv = rv.Elem()
    93  	}
    94  
    95  	switch rv.Kind() {
    96  	case reflect.Invalid:
    97  		return "null"
    98  	case reflect.Bool:
    99  		return "boolean"
   100  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
   101  		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   102  		return "integer"
   103  	case reflect.Float32, reflect.Float64:
   104  		f := rv.Float()
   105  		if f == float64(int64(f)) {
   106  			return "integer"
   107  		}
   108  		return "number"
   109  	case reflect.String:
   110  		return "string"
   111  	case reflect.Slice, reflect.Array:
   112  		return "array"
   113  	case reflect.Map:
   114  		// Only consider string-keyed maps as objects
   115  		if rv.Type().Key().Kind() == reflect.String {
   116  			return "object"
   117  		}
   118  		return "unknown"
   119  	case reflect.Struct:
   120  		return "object"
   121  	case reflect.Interface:
   122  		if rv.IsNil() {
   123  			return "null"
   124  		}
   125  		return getDataType(rv.Interface())
   126  	case reflect.Uintptr, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Ptr, reflect.UnsafePointer:
   127  		return "unknown"
   128  	default:
   129  		return "unknown"
   130  	}
   131  }
   132  
   133  // getURLScheme extracts the scheme component of a URL string.
   134  func getURLScheme(urlStr string) string {
   135  	parsedUrl, err := url.Parse(urlStr)
   136  	if err != nil {
   137  		return ""
   138  	}
   139  	return parsedUrl.Scheme
   140  }
   141  
   142  // isValidURI verifies if the provided string is a valid URI.
   143  func isValidURI(s string) bool {
   144  	_, err := url.ParseRequestURI(s)
   145  	return err == nil
   146  }
   147  
   148  // resolveRelativeURI resolves a relative URI against a base URI.
   149  func resolveRelativeURI(baseURI, relativeURL string) string {
   150  	if isAbsoluteURI(relativeURL) {
   151  		return relativeURL
   152  	}
   153  	base, err := url.Parse(baseURI)
   154  	if err != nil || base.Scheme == "" || base.Host == "" {
   155  		return relativeURL // Return the original if there's a base URL parsing error
   156  	}
   157  	rel, err := url.Parse(relativeURL)
   158  	if err != nil {
   159  		return relativeURL // Return the original if there's a relative URL parsing error
   160  	}
   161  	return base.ResolveReference(rel).String()
   162  }
   163  
   164  // isAbsoluteURI checks if the given URL is absolute.
   165  func isAbsoluteURI(urlStr string) bool {
   166  	u, err := url.Parse(urlStr)
   167  	return err == nil && u.Scheme != "" && u.Host != ""
   168  }
   169  
   170  // getBaseURI extracts the base URL from an $id URI, falling back if not valid.
   171  func getBaseURI(id string) string {
   172  	if id == "" {
   173  		return ""
   174  	}
   175  	u, err := url.Parse(id)
   176  	if err != nil {
   177  		return ""
   178  	}
   179  	if strings.HasSuffix(u.Path, "/") {
   180  		return u.String()
   181  	}
   182  	u.Path = path.Dir(u.Path)
   183  	if u.Path == "." {
   184  		u.Path = "/"
   185  	}
   186  	if u.Path != "/" && !strings.HasSuffix(u.Path, "/") {
   187  		u.Path += "/"
   188  	}
   189  	if u.Scheme == "" || u.Host == "" {
   190  		return ""
   191  	}
   192  	return u.String()
   193  }
   194  
   195  // splitRef separates a URI into its base URI and anchor parts.
   196  func splitRef(ref string) (baseURI string, anchor string) {
   197  	parts := strings.SplitN(ref, "#", 2)
   198  	if len(parts) == 2 {
   199  		return parts[0], parts[1]
   200  	}
   201  	return ref, ""
   202  }
   203  
   204  // isJSONPointer checks if a string is a JSON Pointer.
   205  func isJSONPointer(s string) bool {
   206  	return strings.HasPrefix(s, "/")
   207  }