this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

internal/core/export: sort conjuncts in binary expressions

This is to help reduce diffs in Unity.

It sorts both the values in adt.Conjunction as well as
adt.BinaryExpr. In the latter case, it first flattens
the three, then sorts, then rebuilds the binary tree.

Signed-off-by: Marcel van Lohuizen <mpvl@gmail.com>
Change-Id: I9fa6b91816a15c3a6cfa78c25b34851bd791011e
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1205862
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>

+435 -74
+1 -1
cmd/cue/cmd/testdata/script/def_jsonschema.txtar
··· 49 49 lastName?: strings.MinRunes(1) 50 50 51 51 // Age in years which must be equal to or greater than zero. 52 - age?: >=0 & int 52 + age?: int & >=0 53 53 ... 54 54 } 55 55 -- schema.json --
+99
internal/core/export/adt.go
··· 16 16 17 17 import ( 18 18 "bytes" 19 + "cmp" 19 20 "fmt" 21 + "slices" 20 22 "strings" 21 23 22 24 "cuelang.org/go/cue/ast" ··· 230 232 } 231 233 232 234 case *adt.BinaryExpr: 235 + if x.Op == adt.AndOp || x.Op == adt.OrOp { 236 + return e.sortBinaryTree(env, x) 237 + } 233 238 return &ast.BinaryExpr{ 234 239 Op: x.Op.Token(), 235 240 X: e.innerExpr(env, x.X), ··· 298 303 299 304 default: 300 305 panic(fmt.Sprintf("unknown field %T", x)) 306 + } 307 + } 308 + 309 + // sortBinaryTree converte x to a binary tree and sorts it's elements 310 + // using sortLeafAdt. 311 + func (e *exporter) sortBinaryTree(env *adt.Environment, x *adt.BinaryExpr) (b ast.Expr) { 312 + var exprs []adt.Node 313 + 314 + var flatten func(expr adt.Expr) 315 + flatten = func(expr adt.Expr) { 316 + if y, ok := expr.(*adt.BinaryExpr); ok && x.Op == y.Op { 317 + flatten(y.X) 318 + flatten(y.Y) 319 + } else { 320 + exprs = append(exprs, expr) 321 + } 322 + } 323 + flatten(x) 324 + 325 + // Sort the expressions 326 + slices.SortStableFunc(exprs, cmpLeafNodes) 327 + 328 + nodes := make([]ast.Expr, 0, len(exprs)) 329 + for _, x := range exprs { 330 + switch y := x.(type) { 331 + case *adt.Top: 332 + case *adt.BasicType: 333 + if y.K != adt.TopKind { 334 + nodes = append(nodes, e.expr(env, y)) 335 + } 336 + default: 337 + nodes = append(nodes, e.innerExpr(env, y.(adt.Expr))) 338 + } 339 + } 340 + 341 + if len(nodes) == 0 { 342 + return e.adt(env, &adt.Top{}) 343 + } 344 + 345 + return ast.NewBinExpr(x.Op.Token(), nodes...) 346 + } 347 + 348 + // cmpConjuncts compares two Conjunct based on their element using cmpLeafNodes. 349 + func cmpConjuncts(a, b adt.Conjunct) int { 350 + return cmpLeafNodes(a.Expr(), b.Expr()) 351 + } 352 + 353 + // cmpLeafNodes compares two adt.Expr values. The values may not be a binary 354 + // expressions. It returns true if a is less than b. 355 + func cmpLeafNodes[T adt.Node](a, b T) int { 356 + if c := cmp.Compare(typeOrder(a), typeOrder(b)); c != 0 { 357 + return c 358 + } 359 + 360 + srcA := a.Source() 361 + srcB := b.Source() 362 + 363 + if srcA == nil || srcB == nil { 364 + // TODO: some tie breaker 365 + return 0 366 + } 367 + 368 + posA := srcA.Pos() 369 + posB := srcB.Pos() 370 + 371 + if c := cmp.Compare(posA.Filename(), posB.Filename()); c != 0 { 372 + return c 373 + } 374 + 375 + if c := cmp.Compare(posA.Offset(), posB.Offset()); c != 0 { 376 + return c 377 + } 378 + 379 + return 0 380 + } 381 + 382 + func typeOrder(x adt.Node) int { 383 + switch x.(type) { 384 + case *adt.Top: 385 + return 0 386 + case *adt.BasicType: 387 + return 1 388 + case *adt.FieldReference: 389 + return 2 // sometimes basic types are represented as field references. 390 + case *adt.Bool, *adt.Null, *adt.Num, *adt.String, *adt.Bytes: 391 + return 10 392 + case *adt.BoundValue: 393 + return 20 394 + case *adt.StructLit, *adt.ListLit: 395 + return 500 396 + case adt.Expr: 397 + return 25 398 + default: 399 + return 100 301 400 } 302 401 } 303 402
+258
internal/core/export/testdata/main/conjunctsort.txtar
··· 1 + -- in.cue -- 2 + import "list" 3 + 4 + basicFirst: { 5 + sort: Z & {a: _x} // string first 6 + _x: y + "" 7 + y: string 8 + Z: a: string 9 + } 10 + 11 + literalLast: { 12 + p1: { 13 + [string]: list.UniqueItems() 14 + a: [_, _] 15 + } 16 + p2: { 17 + a: [_, _] 18 + [string]: list.UniqueItems() 19 + } 20 + } 21 + 22 + posTieBreaker: { 23 + p1: { 24 + a: list.UniqueItems() 25 + b: list.MinItems(3) 26 + c: a & b 27 + } 28 + p2: { 29 + b: list.MinItems(3) 30 + a: list.UniqueItems() 31 + c: a & b 32 + } 33 + } 34 + -- out/definition -- 35 + import "list" 36 + 37 + basicFirst: { 38 + sort: Z & { 39 + a: _x 40 + } 41 + _x: y + "" 42 + y: string 43 + Z: { 44 + a: string 45 + } 46 + } 47 + literalLast: { 48 + p1: { 49 + [string]: list.UniqueItems() 50 + a: [_, _] 51 + } 52 + p2: { 53 + a: [_, _] 54 + [string]: list.UniqueItems() 55 + } 56 + } 57 + posTieBreaker: { 58 + p1: { 59 + a: list.UniqueItems() 60 + b: list.MinItems(3) 61 + c: a & b 62 + } 63 + p2: { 64 + b: list.MinItems(3) 65 + a: list.UniqueItems() 66 + c: a & b 67 + } 68 + } 69 + -- out/doc -- 70 + [] 71 + [basicFirst] 72 + [basicFirst sort] 73 + [basicFirst sort a] 74 + [basicFirst _x] 75 + [basicFirst y] 76 + [basicFirst Z] 77 + [basicFirst Z a] 78 + [literalLast] 79 + [literalLast p1] 80 + [literalLast p1 a] 81 + [literalLast p1 a 0] 82 + [literalLast p1 a 1] 83 + [literalLast p2] 84 + [literalLast p2 a] 85 + [literalLast p2 a 0] 86 + [literalLast p2 a 1] 87 + [posTieBreaker] 88 + [posTieBreaker p1] 89 + [posTieBreaker p1 a] 90 + [posTieBreaker p1 b] 91 + [posTieBreaker p1 c] 92 + [posTieBreaker p2] 93 + [posTieBreaker p2 b] 94 + [posTieBreaker p2 a] 95 + [posTieBreaker p2 c] 96 + -- out/value -- 97 + == Simplified 98 + { 99 + basicFirst: { 100 + sort: { 101 + a: string & _x 102 + } 103 + y: string 104 + Z: { 105 + a: string 106 + } 107 + } 108 + literalLast: { 109 + p1: { 110 + a: list.UniqueItems() & [_, _] 111 + } 112 + p2: { 113 + a: list.UniqueItems() & [_, _] 114 + } 115 + } 116 + posTieBreaker: { 117 + p1: { 118 + a: list.UniqueItems() 119 + b: list.MinItems(3) 120 + c: list.UniqueItems() & list.MinItems(3) 121 + } 122 + p2: { 123 + b: list.MinItems(3) 124 + a: list.UniqueItems() 125 + c: list.MinItems(3) & list.UniqueItems() 126 + } 127 + } 128 + } 129 + == Raw 130 + { 131 + basicFirst: { 132 + sort: { 133 + a: string & _x 134 + } 135 + _x: y + "" 136 + y: string 137 + Z: { 138 + a: string 139 + } 140 + } 141 + literalLast: { 142 + p1: { 143 + a: list.UniqueItems() & [_, _] 144 + } 145 + p2: { 146 + a: list.UniqueItems() & [_, _] 147 + } 148 + } 149 + posTieBreaker: { 150 + p1: { 151 + a: list.UniqueItems() 152 + b: list.MinItems(3) 153 + c: list.UniqueItems() & list.MinItems(3) 154 + } 155 + p2: { 156 + b: list.MinItems(3) 157 + a: list.UniqueItems() 158 + c: list.MinItems(3) & list.UniqueItems() 159 + } 160 + } 161 + } 162 + == Final 163 + { 164 + basicFirst: { 165 + sort: { 166 + a: _|_ // basicFirst.sort.a: non-concrete value string in operand to + 167 + } 168 + y: string 169 + Z: { 170 + a: string 171 + } 172 + } 173 + literalLast: { 174 + p1: { 175 + a: _|_ // literalLast.p1.a: invalid value [_,_] (does not satisfy list.UniqueItems): equal values at position 0 and 1 176 + } 177 + p2: { 178 + a: _|_ // literalLast.p2.a: invalid value [_,_] (does not satisfy list.UniqueItems): equal values at position 0 and 1 179 + } 180 + } 181 + posTieBreaker: { 182 + p1: { 183 + a: list.UniqueItems() 184 + b: list.MinItems(3) 185 + c: list.UniqueItems() & list.MinItems(3) 186 + } 187 + p2: { 188 + b: list.MinItems(3) 189 + a: list.UniqueItems() 190 + c: list.MinItems(3) & list.UniqueItems() 191 + } 192 + } 193 + } 194 + == All 195 + { 196 + basicFirst: { 197 + sort: { 198 + a: string & _x 199 + } 200 + _x: y + "" 201 + y: string 202 + Z: { 203 + a: string 204 + } 205 + } 206 + literalLast: { 207 + p1: { 208 + a: list.UniqueItems() & [_, _] 209 + } 210 + p2: { 211 + a: list.UniqueItems() & [_, _] 212 + } 213 + } 214 + posTieBreaker: { 215 + p1: { 216 + a: list.UniqueItems() 217 + b: list.MinItems(3) 218 + c: list.UniqueItems() & list.MinItems(3) 219 + } 220 + p2: { 221 + b: list.MinItems(3) 222 + a: list.UniqueItems() 223 + c: list.MinItems(3) & list.UniqueItems() 224 + } 225 + } 226 + } 227 + == Eval 228 + { 229 + basicFirst: { 230 + sort: { 231 + a: string & _x 232 + } 233 + y: string 234 + Z: { 235 + a: string 236 + } 237 + } 238 + literalLast: { 239 + p1: { 240 + a: list.UniqueItems() & [_, _] 241 + } 242 + p2: { 243 + a: list.UniqueItems() & [_, _] 244 + } 245 + } 246 + posTieBreaker: { 247 + p1: { 248 + a: list.UniqueItems() 249 + b: list.MinItems(3) 250 + c: list.UniqueItems() & list.MinItems(3) 251 + } 252 + p2: { 253 + b: list.MinItems(3) 254 + a: list.UniqueItems() 255 + c: list.MinItems(3) & list.UniqueItems() 256 + } 257 + } 258 + }
+4 -4
internal/core/export/testdata/main/simplify.txtar
··· 58 58 == Raw 59 59 { 60 60 x: { 61 - y: >=-9223372036854775808 & <=9223372036854775807 & int 61 + y: int & >=-9223372036854775808 & <=9223372036854775807 62 62 } 63 63 s: strings.MinRunes(4) & strings.MaxRunes(7) 64 64 additional: { ··· 95 95 == Eval 96 96 { 97 97 x: { 98 - y: >=-9223372036854775808 & <=9223372036854775807 & int 98 + y: int & >=-9223372036854775808 & <=9223372036854775807 99 99 } 100 100 s: strings.MinRunes(4) & strings.MaxRunes(7) 101 101 additional: { ··· 135 135 == Raw 136 136 { 137 137 x: { 138 - y: >=-9223372036854775808 & <=9223372036854775807 & int 138 + y: int & >=-9223372036854775808 & <=9223372036854775807 139 139 } 140 140 s: strings.MinRunes(4) & strings.MaxRunes(7) 141 141 additional: { ··· 172 172 == Eval 173 173 { 174 174 x: { 175 - y: >=-9223372036854775808 & <=9223372036854775807 & int 175 + y: int & >=-9223372036854775808 & <=9223372036854775807 176 176 } 177 177 s: strings.MinRunes(4) & strings.MaxRunes(7) 178 178 additional: {
+18 -5
internal/core/export/value.go
··· 16 16 17 17 import ( 18 18 "fmt" 19 + "slices" 19 20 "strings" 20 21 21 22 "cuelang.org/go/cue/ast" ··· 102 103 } 103 104 if result == nil { 104 105 // fall back to expression mode 105 - a := []ast.Expr{} 106 + a := []adt.Conjunct{} 106 107 n.VisitLeafConjuncts(func(c adt.Conjunct) bool { 108 + a = append(a, c) 109 + return true 110 + }) 111 + // Use stable sort to ensure that tie breaks (for instance if elements 112 + // are not associated with a position) are deterministic. 113 + slices.SortStableFunc(a, cmpConjuncts) 114 + 115 + exprs := make([]ast.Expr, 0, len(a)) 116 + for _, c := range a { 107 117 if x := e.expr(c.Env, c.Elem()); x != dummyTop { 108 - a = append(a, x) 118 + exprs = append(exprs, x) 109 119 } 110 - return true 111 - }) 112 - result = ast.NewBinExpr(token.AND, a...) 120 + } 121 + 122 + result = ast.NewBinExpr(token.AND, exprs...) 113 123 } 114 124 115 125 if len(s.Elts) > 0 { ··· 195 205 a = x.Values 196 206 } 197 207 208 + slices.SortStableFunc(a, cmpLeafNodes) 209 + 198 210 for _, x := range a { 199 211 result = wrapBin(result, e.bareValue(x), adt.AndOp) 200 212 } 201 213 202 214 case *adt.Disjunction: 203 215 a := []ast.Expr{} 216 + 204 217 for i, v := range x.Values { 205 218 var expr ast.Expr 206 219 if e.cfg.Simplify {
+1 -1
pkg/list/testdata/list.txtar
··· 182 182 } 183 183 // Issue #2099 184 184 minItems: { 185 - incomplete1: [...] & list.MinItems(1) 185 + incomplete1: list.MinItems(1) & [...] 186 186 fail1: _|_ // minItems.fail1: invalid value [] (does not satisfy list.MinItems(1)): len(list) < MinItems(1) (0 < 1) 187 187 ok1: [0, ...] 188 188 ok2: [0]
+21 -30
pkg/list/testdata/unique.txtar
··· 79 79 } 80 80 #abErr: { 81 81 a: 1 82 - b?: string & int 82 + b?: int & string 83 83 } 84 84 #b: { 85 85 b: 1 ··· 101 101 102 102 // These have all equal elements, but 103 103 incomplete: { 104 - top: [_, _] & list.UniqueItems() 104 + top: list.UniqueItems() & [_, _] 105 105 106 106 // These two elements are considered equal, but the error is an "incomplete" 107 107 // errors, as the items may still differ once they become more specific. 108 - withOptional1: [{ 108 + withOptional1: list.UniqueItems() & [{ 109 109 a: int 110 110 }, { 111 111 a: int 112 112 b?: string 113 - }] & list.UniqueItems() 113 + }] 114 114 115 115 // Ditto. This is an incomplete error, even though the matching elements 116 116 // are "final": there is still an optional field. 117 - withOptional2: [{ 117 + withOptional2: list.UniqueItems() & [{ 118 118 a: 1 119 119 }, { 120 120 a: 1 121 121 b?: string 122 - }] & list.UniqueItems() 122 + }] 123 123 124 124 // Ditto. This time with actually closed fields. 125 - withOptional3: [#a, #ab] & list.UniqueItems() 125 + withOptional3: list.UniqueItems() & [#a, #ab] 126 126 127 127 // Ditto. There are not optional fields, but the structs are open. 128 - openSpecific: [{ 128 + openSpecific: list.UniqueItems() & [{ 129 129 a: 1 130 130 }, { 131 131 a: 1 132 - }] & list.UniqueItems() 132 + }] 133 133 134 134 // Fully identical closed structs, but with non-concrete values. 135 - structs: [#c, #c] & list.UniqueItems() 135 + structs: list.UniqueItems() & [#c, #c] 136 136 } 137 137 fail: { 138 138 ints: _|_ // fail.ints: invalid value [1,2,1] (does not satisfy list.UniqueItems): equal value (1) at position 0 and 2 ··· 164 164 ./in.cue:3:21 165 165 ./in.cue:45:11 166 166 167 - @@ -21,7 +24,7 @@ 168 - } 169 - #abErr: { 170 - a: 1 171 - - b?: int & string 172 - + b?: string & int 173 - } 174 - #b: { 175 - b: 1 176 167 @@ -64,12 +67,7 @@ 177 - }] & list.UniqueItems() 168 + }] 178 169 179 170 // Ditto. This time with actually closed fields. 180 171 - withOptional3: [{ ··· 183 174 - a: 1 184 175 - b?: int 185 176 - }] 186 - + withOptional3: [#a, #ab] & list.UniqueItems() 177 + + withOptional3: list.UniqueItems() & [#a, #ab] 187 178 188 179 // Ditto. There are not optional fields, but the structs are open. 189 - openSpecific: [{ 180 + openSpecific: list.UniqueItems() & [{ 190 181 @@ -83,7 +81,7 @@ 191 182 } 192 183 fail: { ··· 254 245 255 246 // These have all equal elements, but 256 247 incomplete: { 257 - top: [_, _] & list.UniqueItems() 248 + top: list.UniqueItems() & [_, _] 258 249 259 250 // These two elements are considered equal, but the error is an "incomplete" 260 251 // errors, as the items may still differ once they become more specific. 261 - withOptional1: [{ 252 + withOptional1: list.UniqueItems() & [{ 262 253 a: int 263 254 }, { 264 255 a: int 265 256 b?: string 266 - }] & list.UniqueItems() 257 + }] 267 258 268 259 // Ditto. This is an incomplete error, even though the matching elements 269 260 // are "final": there is still an optional field. 270 - withOptional2: [{ 261 + withOptional2: list.UniqueItems() & [{ 271 262 a: 1 272 263 }, { 273 264 a: 1 274 265 b?: string 275 - }] & list.UniqueItems() 266 + }] 276 267 277 268 // Ditto. This time with actually closed fields. 278 269 withOptional3: [{ ··· 283 274 }] 284 275 285 276 // Ditto. There are not optional fields, but the structs are open. 286 - openSpecific: [{ 277 + openSpecific: list.UniqueItems() & [{ 287 278 a: 1 288 279 }, { 289 280 a: 1 290 - }] & list.UniqueItems() 281 + }] 291 282 292 283 // Fully identical closed structs, but with non-concrete values. 293 - structs: [#c, #c] & list.UniqueItems() 284 + structs: list.UniqueItems() & [#c, #c] 294 285 } 295 286 fail: { 296 287 ints: _|_ // fail.ints: invalid value [1,2,1] (does not satisfy list.UniqueItems): equal value (1) at position 0 and 2
+31 -31
pkg/struct/testdata/struct.txtar
··· 65 65 import "struct" 66 66 67 67 minFields1: { 68 - incomplete1: {} & struct.MinFields(1) 69 - optIncomplete: { 68 + incomplete1: struct.MinFields(1) & {} 69 + optIncomplete: struct.MinFields(1) & { 70 70 a?: string 71 - } & struct.MinFields(1) 71 + } 72 72 fail1: _|_ // minFields1.fail1: invalid value {} (does not satisfy struct.MinFields(1)): len(fields) < MinFields(1) (0 < 1) 73 - optCloseIncomplete: close({ 73 + optCloseIncomplete: struct.MinFields(1) & close({ 74 74 a?: 1 75 - }) & struct.MinFields(1) 75 + }) 76 76 failHidden1: _|_ // minFields1.failHidden1: invalid value {_a:1} (does not satisfy struct.MinFields(1)): len(fields) < MinFields(1) (0 < 1) 77 77 ok4: { 78 78 a: 1 ··· 92 92 } 93 93 } 94 94 minFields2: { 95 - incomplete1: close({ 95 + incomplete1: struct.MinFields(2) & close({ 96 96 a?: string 97 97 b: 1 98 - }) & struct.MinFields(2) 99 - incomplete2: close({ 98 + }) 99 + incomplete2: struct.MinFields(2) & close({ 100 100 a?: string 101 101 b?: int 102 - }) & struct.MinFields(2) 103 - incomplete3: close({ 102 + }) 103 + incomplete3: struct.MinFields(2) & close({ 104 104 a?: string 105 105 b?: int 106 106 c: 1 107 - }) & struct.MinFields(2) 108 - incomplete4: close({ 107 + }) 108 + incomplete4: struct.MinFields(2) & close({ 109 109 a?: string 110 110 b?: int 111 111 c?: int 112 - }) & struct.MinFields(2) 112 + }) 113 113 fail: _|_ // minFields2.fail: invalid value {a?:string} (does not satisfy struct.MinFields(2)): len(fields) < MinFields(2) (0 < 2) 114 114 } 115 115 maxFields: { ··· 151 151 @@ -61,9 +65,7 @@ 152 152 b?: int 153 153 c?: int 154 - }) & struct.MinFields(2) 155 - - fail: close({ 154 + }) 155 + - fail: struct.MinFields(2) & close({ 156 156 - a?: string 157 - - }) & struct.MinFields(2) 157 + - }) 158 158 + fail: _|_ // minFields2.fail: invalid value {a?:string} (does not satisfy struct.MinFields(2)): len(fields) < MinFields(2) (0 < 2) 159 159 } 160 160 maxFields: { ··· 183 183 import "struct" 184 184 185 185 minFields1: { 186 - incomplete1: {} & struct.MinFields(1) 187 - optIncomplete: { 186 + incomplete1: struct.MinFields(1) & {} 187 + optIncomplete: struct.MinFields(1) & { 188 188 a?: string 189 - } & struct.MinFields(1) 189 + } 190 190 fail1: _|_ // minFields1.fail1: invalid value {} (does not satisfy struct.MinFields(1)): len(fields) < MinFields(1) (0 < 1) 191 - optCloseIncomplete: close({ 191 + optCloseIncomplete: struct.MinFields(1) & close({ 192 192 a?: 1 193 - }) & struct.MinFields(1) 193 + }) 194 194 failHidden1: _|_ // minFields1.failHidden1: invalid value {_a:1} (does not satisfy struct.MinFields(1)): len(fields) < MinFields(1) (0 < 1) 195 195 ok4: { 196 196 a: 1 ··· 210 210 } 211 211 } 212 212 minFields2: { 213 - incomplete1: close({ 213 + incomplete1: struct.MinFields(2) & close({ 214 214 a?: string 215 215 b: 1 216 - }) & struct.MinFields(2) 217 - incomplete2: close({ 216 + }) 217 + incomplete2: struct.MinFields(2) & close({ 218 218 a?: string 219 219 b?: int 220 - }) & struct.MinFields(2) 221 - incomplete3: close({ 220 + }) 221 + incomplete3: struct.MinFields(2) & close({ 222 222 a?: string 223 223 b?: int 224 224 c: 1 225 - }) & struct.MinFields(2) 226 - incomplete4: close({ 225 + }) 226 + incomplete4: struct.MinFields(2) & close({ 227 227 a?: string 228 228 b?: int 229 229 c?: int 230 - }) & struct.MinFields(2) 231 - fail: close({ 230 + }) 231 + fail: struct.MinFields(2) & close({ 232 232 a?: string 233 - }) & struct.MinFields(2) 233 + }) 234 234 } 235 235 maxFields: { 236 236 ok1: {}
+2 -2
tools/flow/testdata/template.txtar
··· 101 101 env: {} | [] 102 102 stdout: "foo" 103 103 stderr: null 104 - - stdin: (*null | string | bytes) & GET.response.body 104 + - stdin: GET.response.body & (*null | string | bytes) 105 105 success: bool 106 106 mustSucceed: true 107 107 ··· 140 140 env: {} | [] 141 141 stdout: "foo" 142 142 stderr: null 143 - stdin: (*null | string | bytes) & GET.response.body 143 + stdin: GET.response.body & (*null | string | bytes) 144 144 success: bool 145 145 mustSucceed: true 146 146