github.com/iosif02/goja_nodejs@v1.0.1/url/urlsearchparams.go (about) 1 package url 2 3 import ( 4 "reflect" 5 "sort" 6 7 "github.com/iosif02/goja_nodejs/errors" 8 9 "github.com/iosif02/goja" 10 ) 11 12 var ( 13 reflectTypeURLSearchParams = reflect.TypeOf((*urlSearchParams)(nil)) 14 reflectTypeURLSearchParamsIterator = reflect.TypeOf((*urlSearchParamsIterator)(nil)) 15 ) 16 17 func newInvalidTupleError(r *goja.Runtime) *goja.Object { 18 return errors.NewTypeError(r, "ERR_INVALID_TUPLE", "Each query pair must be an iterable [name, value] tuple") 19 } 20 21 func newMissingArgsError(r *goja.Runtime, msg string) *goja.Object { 22 return errors.NewTypeError(r, errors.ErrCodeMissingArgs, msg) 23 } 24 25 func newInvalidArgsError(r *goja.Runtime) *goja.Object { 26 return errors.NewTypeError(r, "ERR_INVALID_ARG_TYPE", `The "callback" argument must be of type function.`) 27 } 28 29 func toUrlSearchParams(r *goja.Runtime, v goja.Value) *urlSearchParams { 30 if v.ExportType() == reflectTypeURLSearchParams { 31 if u := v.Export().(*urlSearchParams); u != nil { 32 return u 33 } 34 } 35 panic(errors.NewTypeError(r, errors.ErrCodeInvalidThis, `Value of "this" must be of type URLSearchParams`)) 36 } 37 38 func (m *urlModule) newURLSearchParams(sp *urlSearchParams) *goja.Object { 39 v := m.r.ToValue(sp).(*goja.Object) 40 v.SetPrototype(m.URLSearchParamsPrototype) 41 return v 42 } 43 44 func (m *urlModule) createURLSearchParamsConstructor() goja.Value { 45 f := m.r.ToValue(func(call goja.ConstructorCall) *goja.Object { 46 var sp searchParams 47 v := call.Argument(0) 48 if o, ok := v.(*goja.Object); ok { 49 sp = m.buildParamsFromObject(o) 50 } else if !goja.IsUndefined(v) { 51 sp = parseSearchQuery(v.String()) 52 } 53 54 return m.newURLSearchParams(&urlSearchParams{searchParams: sp}) 55 }).(*goja.Object) 56 57 m.URLSearchParamsPrototype = m.createURLSearchParamsPrototype() 58 f.Set("prototype", m.URLSearchParamsPrototype) 59 m.URLSearchParamsPrototype.DefineDataProperty("constructor", f, goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_FALSE) 60 61 return f 62 } 63 64 func (m *urlModule) buildParamsFromObject(o *goja.Object) searchParams { 65 var query searchParams 66 67 if o.GetSymbol(goja.SymIterator) != nil { 68 return m.buildParamsFromIterable(o) 69 } 70 71 for _, k := range o.Keys() { 72 val := o.Get(k).String() 73 query = append(query, searchParam{name: k, value: val}) 74 } 75 76 return query 77 } 78 79 func (m *urlModule) buildParamsFromIterable(o *goja.Object) searchParams { 80 var query searchParams 81 82 m.r.ForOf(o, func(val goja.Value) bool { 83 obj := val.ToObject(m.r) 84 var name, value string 85 i := 0 86 // Use ForOf to determine if the object is iterable 87 m.r.ForOf(obj, func(val goja.Value) bool { 88 if i == 0 { 89 name = val.String() 90 i++ 91 return true 92 } 93 if i == 1 { 94 value = val.String() 95 i++ 96 return true 97 } 98 // Array isn't a tuple 99 panic(newInvalidTupleError(m.r)) 100 }) 101 102 // Ensure we have two values 103 if i <= 1 { 104 panic(newInvalidTupleError(m.r)) 105 } 106 107 query = append(query, searchParam{ 108 name: name, 109 value: value, 110 }) 111 112 return true 113 }) 114 115 return query 116 } 117 118 func (m *urlModule) createURLSearchParamsPrototype() *goja.Object { 119 p := m.r.NewObject() 120 121 p.Set("append", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 122 if len(call.Arguments) < 2 { 123 panic(newMissingArgsError(m.r, `The "name" and "value" arguments must be specified`)) 124 } 125 126 u := toUrlSearchParams(m.r, call.This) 127 u.searchParams = append(u.searchParams, searchParam{ 128 name: call.Argument(0).String(), 129 value: call.Argument(1).String(), 130 }) 131 u.markUpdated() 132 133 return goja.Undefined() 134 })) 135 136 p.Set("delete", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 137 u := toUrlSearchParams(m.r, call.This) 138 139 if len(call.Arguments) < 1 { 140 panic(newMissingArgsError(m.r, `The "name" argument must be specified`)) 141 } 142 143 name := call.Argument(0).String() 144 isValid := func(v searchParam) bool { 145 if len(call.Arguments) == 1 { 146 return v.name != name 147 } else if v.name == name { 148 arg := call.Argument(1) 149 if !goja.IsUndefined(arg) && v.value == arg.String() { 150 return false 151 } 152 } 153 return true 154 } 155 156 j := 0 157 for i, v := range u.searchParams { 158 if isValid(v) { 159 if i != j { 160 u.searchParams[j] = v 161 } 162 j++ 163 } 164 } 165 u.searchParams = u.searchParams[:j] 166 u.markUpdated() 167 168 return goja.Undefined() 169 })) 170 171 entries := m.r.ToValue(func(call goja.FunctionCall) goja.Value { 172 return m.newURLSearchParamsIterator(toUrlSearchParams(m.r, call.This), urlSearchParamsIteratorEntries) 173 }) 174 p.Set("entries", entries) 175 p.DefineDataPropertySymbol(goja.SymIterator, entries, goja.FLAG_TRUE, goja.FLAG_FALSE, goja.FLAG_TRUE) 176 177 p.Set("forEach", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 178 u := toUrlSearchParams(m.r, call.This) 179 180 if len(call.Arguments) != 1 { 181 panic(newInvalidArgsError(m.r)) 182 } 183 184 if fn, ok := goja.AssertFunction(call.Argument(0)); ok { 185 for _, pair := range u.searchParams { 186 // name, value, searchParams 187 _, err := fn( 188 nil, 189 m.r.ToValue(pair.name), 190 m.r.ToValue(pair.value), 191 call.This, 192 ) 193 194 if err != nil { 195 panic(err) 196 } 197 } 198 } else { 199 panic(newInvalidArgsError(m.r)) 200 } 201 202 return goja.Undefined() 203 })) 204 205 p.Set("get", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 206 u := toUrlSearchParams(m.r, call.This) 207 208 if len(call.Arguments) == 0 { 209 panic(newMissingArgsError(m.r, `The "name" argument must be specified`)) 210 } 211 212 if val, exists := u.getFirstValue(call.Argument(0).String()); exists { 213 return m.r.ToValue(val) 214 } 215 216 return goja.Null() 217 })) 218 219 p.Set("getAll", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 220 u := toUrlSearchParams(m.r, call.This) 221 222 if len(call.Arguments) == 0 { 223 panic(newMissingArgsError(m.r, `The "name" argument must be specified`)) 224 } 225 226 vals := u.getValues(call.Argument(0).String()) 227 return m.r.ToValue(vals) 228 })) 229 230 p.Set("has", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 231 u := toUrlSearchParams(m.r, call.This) 232 233 if len(call.Arguments) == 0 { 234 panic(newMissingArgsError(m.r, `The "name" argument must be specified`)) 235 } 236 237 name := call.Argument(0).String() 238 value := call.Argument(1) 239 var res bool 240 if goja.IsUndefined(value) { 241 res = u.hasName(name) 242 } else { 243 res = u.hasValue(name, value.String()) 244 } 245 return m.r.ToValue(res) 246 })) 247 248 p.Set("keys", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 249 return m.newURLSearchParamsIterator(toUrlSearchParams(m.r, call.This), urlSearchParamsIteratorKeys) 250 })) 251 252 p.Set("set", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 253 u := toUrlSearchParams(m.r, call.This) 254 255 if len(call.Arguments) < 2 { 256 panic(newMissingArgsError(m.r, `The "name" and "value" arguments must be specified`)) 257 } 258 259 name := call.Argument(0).String() 260 found := false 261 j := 0 262 for i, sp := range u.searchParams { 263 if sp.name == name { 264 if found { 265 continue // Remove all values 266 } 267 268 u.searchParams[i].value = call.Argument(1).String() 269 found = true 270 } 271 if i != j { 272 u.searchParams[j] = sp 273 } 274 j++ 275 } 276 277 if !found { 278 u.searchParams = append(u.searchParams, searchParam{ 279 name: name, 280 value: call.Argument(1).String(), 281 }) 282 } else { 283 u.searchParams = u.searchParams[:j] 284 } 285 286 u.markUpdated() 287 288 return goja.Undefined() 289 })) 290 291 p.Set("sort", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 292 u := toUrlSearchParams(m.r, call.This) 293 sort.Stable(u.searchParams) 294 u.markUpdated() 295 return goja.Undefined() 296 })) 297 298 p.DefineAccessorProperty("size", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 299 u := toUrlSearchParams(m.r, call.This) 300 return m.r.ToValue(len(u.searchParams)) 301 }), nil, goja.FLAG_FALSE, goja.FLAG_TRUE) 302 303 p.Set("toString", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 304 u := toUrlSearchParams(m.r, call.This) 305 str := u.searchParams.Encode() 306 return m.r.ToValue(str) 307 })) 308 309 p.Set("values", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 310 return m.newURLSearchParamsIterator(toUrlSearchParams(m.r, call.This), urlSearchParamsIteratorValues) 311 })) 312 313 return p 314 } 315 316 func (sp *urlSearchParams) markUpdated() { 317 if sp.url != nil && sp.url.RawQuery != "" { 318 sp.url.RawQuery = "" 319 } 320 } 321 322 type urlSearchParamsIteratorType int 323 324 const ( 325 urlSearchParamsIteratorKeys urlSearchParamsIteratorType = iota 326 urlSearchParamsIteratorValues 327 urlSearchParamsIteratorEntries 328 ) 329 330 type urlSearchParamsIterator struct { 331 typ urlSearchParamsIteratorType 332 sp *urlSearchParams 333 idx int 334 } 335 336 func toURLSearchParamsIterator(r *goja.Runtime, v goja.Value) *urlSearchParamsIterator { 337 if v.ExportType() == reflectTypeURLSearchParamsIterator { 338 if u := v.Export().(*urlSearchParamsIterator); u != nil { 339 return u 340 } 341 } 342 343 panic(errors.NewTypeError(r, errors.ErrCodeInvalidThis, `Value of "this" must be of type URLSearchParamIterator`)) 344 } 345 346 func (m *urlModule) getURLSearchParamsIteratorPrototype() *goja.Object { 347 if m.URLSearchParamsIteratorPrototype != nil { 348 return m.URLSearchParamsIteratorPrototype 349 } 350 351 p := m.r.NewObject() 352 353 p.Set("next", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 354 it := toURLSearchParamsIterator(m.r, call.This) 355 res := m.r.NewObject() 356 if it.idx < len(it.sp.searchParams) { 357 param := it.sp.searchParams[it.idx] 358 switch it.typ { 359 case urlSearchParamsIteratorKeys: 360 res.Set("value", param.name) 361 case urlSearchParamsIteratorValues: 362 res.Set("value", param.value) 363 default: 364 res.Set("value", m.r.NewArray(param.name, param.value)) 365 } 366 res.Set("done", false) 367 it.idx++ 368 } else { 369 res.Set("value", goja.Undefined()) 370 res.Set("done", true) 371 } 372 return res 373 })) 374 375 p.DefineDataPropertySymbol(goja.SymToStringTag, m.r.ToValue("URLSearchParams Iterator"), goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_TRUE) 376 377 m.URLSearchParamsIteratorPrototype = p 378 return p 379 } 380 381 func (m *urlModule) newURLSearchParamsIterator(sp *urlSearchParams, typ urlSearchParamsIteratorType) goja.Value { 382 it := m.r.ToValue(&urlSearchParamsIterator{ 383 typ: typ, 384 sp: sp, 385 }).(*goja.Object) 386 387 it.SetPrototype(m.getURLSearchParamsIteratorPrototype()) 388 389 return it 390 }