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 }