1: | <?php declare(strict_types=1); |
2: | |
3: | namespace Salient\Sync\Http; |
4: | |
5: | use Salient\Contract\Core\Pipeline\ArrayMapperInterface; |
6: | use Salient\Contract\Core\Pipeline\EntityPipelineInterface; |
7: | use Salient\Contract\Core\Pipeline\PipelineInterface; |
8: | use Salient\Contract\Core\Pipeline\StreamPipelineInterface; |
9: | use Salient\Contract\Core\Provider\ProviderContextInterface; |
10: | use Salient\Contract\Core\Buildable; |
11: | use Salient\Contract\Curler\Exception\HttpErrorException; |
12: | use Salient\Contract\Curler\CurlerInterface; |
13: | use Salient\Contract\Curler\CurlerPagerInterface; |
14: | use Salient\Contract\Http\HttpHeadersInterface; |
15: | use Salient\Contract\Http\HttpRequestMethod; |
16: | use Salient\Contract\Sync\EntitySource; |
17: | use Salient\Contract\Sync\FilterPolicy; |
18: | use Salient\Contract\Sync\SyncContextInterface; |
19: | use Salient\Contract\Sync\SyncEntityInterface; |
20: | use Salient\Contract\Sync\SyncOperation as OP; |
21: | use Salient\Core\Concern\BuildableTrait; |
22: | use Salient\Core\Concern\ImmutableTrait; |
23: | use Salient\Core\Pipeline; |
24: | use Salient\Sync\Exception\SyncEntityNotFoundException; |
25: | use Salient\Sync\Exception\SyncInvalidContextException; |
26: | use Salient\Sync\Exception\SyncInvalidEntitySourceException; |
27: | use Salient\Sync\Exception\SyncOperationNotImplementedException; |
28: | use Salient\Sync\Support\SyncPipelineArgument; |
29: | use Salient\Sync\AbstractSyncDefinition; |
30: | use Salient\Sync\SyncUtil; |
31: | use Salient\Utility\Arr; |
32: | use Salient\Utility\Env; |
33: | use Salient\Utility\Regex; |
34: | use Salient\Utility\Str; |
35: | use Closure; |
36: | use LogicException; |
37: | use UnexpectedValueException; |
38: | |
39: | |
40: | |
41: | |
42: | |
43: | |
44: | |
45: | |
46: | |
47: | |
48: | |
49: | |
50: | |
51: | |
52: | |
53: | |
54: | |
55: | |
56: | |
57: | |
58: | |
59: | |
60: | |
61: | |
62: | |
63: | |
64: | |
65: | |
66: | |
67: | |
68: | |
69: | |
70: | |
71: | |
72: | |
73: | |
74: | |
75: | |
76: | |
77: | |
78: | |
79: | final class HttpSyncDefinition extends AbstractSyncDefinition implements Buildable |
80: | { |
81: | |
82: | use BuildableTrait; |
83: | use ImmutableTrait; |
84: | |
85: | public const DEFAULT_METHOD_MAP = [ |
86: | OP::CREATE => HttpRequestMethod::POST, |
87: | OP::READ => HttpRequestMethod::GET, |
88: | OP::UPDATE => HttpRequestMethod::PUT, |
89: | OP::DELETE => HttpRequestMethod::DELETE, |
90: | OP::CREATE_LIST => HttpRequestMethod::POST, |
91: | OP::READ_LIST => HttpRequestMethod::GET, |
92: | OP::UPDATE_LIST => HttpRequestMethod::PUT, |
93: | OP::DELETE_LIST => HttpRequestMethod::DELETE, |
94: | ]; |
95: | |
96: | |
97: | |
98: | |
99: | |
100: | |
101: | |
102: | |
103: | |
104: | |
105: | |
106: | |
107: | |
108: | |
109: | |
110: | |
111: | |
112: | |
113: | |
114: | |
115: | |
116: | |
117: | |
118: | |
119: | |
120: | |
121: | |
122: | |
123: | |
124: | |
125: | |
126: | |
127: | |
128: | |
129: | |
130: | protected $Path; |
131: | |
132: | |
133: | |
134: | |
135: | |
136: | |
137: | |
138: | |
139: | |
140: | |
141: | protected ?array $Query; |
142: | |
143: | |
144: | |
145: | |
146: | |
147: | |
148: | |
149: | |
150: | protected ?HttpHeadersInterface $Headers; |
151: | |
152: | |
153: | |
154: | |
155: | |
156: | |
157: | |
158: | |
159: | protected ?CurlerPagerInterface $Pager; |
160: | |
161: | |
162: | |
163: | |
164: | protected bool $AlwaysPaginate; |
165: | |
166: | |
167: | |
168: | |
169: | |
170: | |
171: | |
172: | |
173: | |
174: | |
175: | |
176: | |
177: | |
178: | |
179: | |
180: | protected ?int $Expiry; |
181: | |
182: | |
183: | |
184: | |
185: | |
186: | |
187: | |
188: | |
189: | |
190: | |
191: | |
192: | |
193: | protected array $MethodMap; |
194: | |
195: | |
196: | |
197: | |
198: | |
199: | |
200: | |
201: | |
202: | |
203: | |
204: | |
205: | protected $CurlerCallback; |
206: | |
207: | |
208: | |
209: | |
210: | |
211: | protected bool $SyncOneEntityPerRequest; |
212: | |
213: | |
214: | |
215: | |
216: | |
217: | |
218: | |
219: | |
220: | |
221: | protected $Callback; |
222: | |
223: | |
224: | |
225: | |
226: | |
227: | |
228: | protected ?array $Args; |
229: | |
230: | |
231: | |
232: | |
233: | |
234: | |
235: | |
236: | |
237: | |
238: | |
239: | |
240: | |
241: | |
242: | |
243: | |
244: | |
245: | |
246: | |
247: | |
248: | |
249: | |
250: | |
251: | |
252: | |
253: | public function __construct( |
254: | string $entity, |
255: | HttpSyncProvider $provider, |
256: | array $operations = [], |
257: | $path = null, |
258: | ?array $query = null, |
259: | ?HttpHeadersInterface $headers = null, |
260: | ?CurlerPagerInterface $pager = null, |
261: | bool $alwaysPaginate = false, |
262: | ?callable $callback = null, |
263: | int $conformity = HttpSyncDefinition::CONFORMITY_NONE, |
264: | ?int $filterPolicy = null, |
265: | ?int $expiry = -1, |
266: | array $methodMap = HttpSyncDefinition::DEFAULT_METHOD_MAP, |
267: | ?callable $curlerCallback = null, |
268: | bool $syncOneEntityPerRequest = false, |
269: | array $overrides = [], |
270: | ?array $keyMap = null, |
271: | int $keyMapFlags = ArrayMapperInterface::ADD_UNMAPPED, |
272: | ?PipelineInterface $pipelineFromBackend = null, |
273: | ?PipelineInterface $pipelineToBackend = null, |
274: | bool $readFromList = false, |
275: | ?int $returnEntitiesFrom = EntitySource::PROVIDER_OUTPUT, |
276: | ?array $args = null |
277: | ) { |
278: | parent::__construct( |
279: | $entity, |
280: | $provider, |
281: | $operations, |
282: | $conformity, |
283: | $filterPolicy, |
284: | $overrides, |
285: | $keyMap, |
286: | $keyMapFlags, |
287: | $pipelineFromBackend, |
288: | $pipelineToBackend, |
289: | $readFromList, |
290: | $returnEntitiesFrom |
291: | ); |
292: | |
293: | $this->Path = $path; |
294: | $this->Query = $query; |
295: | $this->Headers = $headers; |
296: | $this->Pager = $pager; |
297: | $this->AlwaysPaginate = $pager && $alwaysPaginate; |
298: | $this->Callback = $callback; |
299: | $this->Expiry = $expiry; |
300: | $this->MethodMap = $methodMap; |
301: | $this->CurlerCallback = $curlerCallback; |
302: | $this->SyncOneEntityPerRequest = $syncOneEntityPerRequest; |
303: | $this->Args = $args === null ? null : array_values($args); |
304: | } |
305: | |
306: | |
307: | |
308: | |
309: | |
310: | |
311: | |
312: | public function withPath($path) |
313: | { |
314: | return $this->with('Path', $path); |
315: | } |
316: | |
317: | |
318: | |
319: | |
320: | |
321: | |
322: | |
323: | |
324: | public function withQuery(?array $query) |
325: | { |
326: | return $this->with('Query', $query); |
327: | } |
328: | |
329: | |
330: | |
331: | |
332: | |
333: | |
334: | |
335: | public function withHeaders(?HttpHeadersInterface $headers) |
336: | { |
337: | return $this->with('Headers', $headers); |
338: | } |
339: | |
340: | |
341: | |
342: | |
343: | |
344: | |
345: | |
346: | |
347: | public function withPager(?CurlerPagerInterface $pager, bool $alwaysPaginate = false) |
348: | { |
349: | return $this |
350: | ->with('Pager', $pager) |
351: | ->with('AlwaysPaginate', $pager && $alwaysPaginate); |
352: | } |
353: | |
354: | |
355: | |
356: | |
357: | |
358: | |
359: | |
360: | |
361: | |
362: | |
363: | |
364: | public function withExpiry(?int $expiry) |
365: | { |
366: | return $this->with('Expiry', $expiry); |
367: | } |
368: | |
369: | |
370: | |
371: | |
372: | |
373: | |
374: | |
375: | |
376: | public function withMethodMap(array $methodMap) |
377: | { |
378: | return $this->with('MethodMap', $methodMap); |
379: | } |
380: | |
381: | |
382: | |
383: | |
384: | |
385: | |
386: | |
387: | |
388: | public function withCurlerCallback(?callable $callback) |
389: | { |
390: | return $this->with('CurlerCallback', $callback); |
391: | } |
392: | |
393: | |
394: | |
395: | |
396: | |
397: | |
398: | |
399: | public function withArgs(?array $args) |
400: | { |
401: | return $this->with('Args', $args === null ? null : array_values($args)); |
402: | } |
403: | |
404: | |
405: | |
406: | |
407: | protected function getClosure(int $operation): ?Closure |
408: | { |
409: | |
410: | if ( |
411: | ($this->Path === null || $this->Path === []) |
412: | && $this->Callback === null |
413: | ) { |
414: | return null; |
415: | } |
416: | |
417: | switch ($operation) { |
418: | case OP::CREATE: |
419: | case OP::UPDATE: |
420: | case OP::DELETE: |
421: | return function ( |
422: | SyncContextInterface $ctx, |
423: | SyncEntityInterface $entity, |
424: | ...$args |
425: | ) use ($operation): SyncEntityInterface { |
426: | $arg = new SyncPipelineArgument($operation, $ctx, $args, null, $entity); |
427: | |
428: | $roundTrip = $this->getRoundTripPipeline($operation); |
429: | |
430: | $toBackend = $this |
431: | ->getPipelineToBackend() |
432: | ->send($entity, $arg); |
433: | |
434: | return $toBackend |
435: | ->then(fn($data) => $this->getRoundTripPayload( |
436: | $this->runHttpOperation($operation, $ctx, $data, ...$args), |
437: | $entity, |
438: | $operation, |
439: | )) |
440: | ->runInto($roundTrip) |
441: | ->withConformity($this->Conformity) |
442: | ->run(); |
443: | }; |
444: | |
445: | case OP::READ: |
446: | return function ( |
447: | SyncContextInterface $ctx, |
448: | $id, |
449: | ...$args |
450: | ) use ($operation): SyncEntityInterface { |
451: | $arg = new SyncPipelineArgument($operation, $ctx, $args, $id); |
452: | return $this |
453: | ->getPipelineFromBackend() |
454: | ->send( |
455: | $this->runHttpOperation($operation, $ctx, $id, ...$args), |
456: | $arg, |
457: | ) |
458: | ->withConformity($this->Conformity) |
459: | ->run(); |
460: | }; |
461: | |
462: | case OP::CREATE_LIST: |
463: | case OP::UPDATE_LIST: |
464: | case OP::DELETE_LIST: |
465: | return function ( |
466: | SyncContextInterface $ctx, |
467: | iterable $entities, |
468: | ...$args |
469: | ) use ($operation): iterable { |
470: | |
471: | $entity = null; |
472: | $arg = new SyncPipelineArgument($operation, $ctx, $args, null, $entity); |
473: | |
474: | $roundTrip = $this->getRoundTripPipeline($operation); |
475: | |
476: | $toBackend = $this |
477: | ->getPipelineToBackend() |
478: | ->stream($entities, $arg); |
479: | |
480: | if ($this->SyncOneEntityPerRequest) { |
481: | $payload = &$entity; |
482: | $after = function ($currentPayload) use (&$entity) { |
483: | return $entity = $currentPayload; |
484: | }; |
485: | $then = function ($data) use ($operation, $ctx, $args, &$payload) { |
486: | |
487: | return $this->getRoundTripPayload( |
488: | $this->runHttpOperation($operation, $ctx, $data, ...$args), |
489: | $payload, |
490: | $operation, |
491: | ); |
492: | }; |
493: | $toBackend = $toBackend |
494: | ->after($after) |
495: | ->then($then); |
496: | } else { |
497: | $payload = []; |
498: | $after = function ($currentPayload) use (&$entity, &$payload) { |
499: | return $payload[] = $entity = $currentPayload; |
500: | }; |
501: | $then = function ($data) use ($operation, $ctx, $args, &$payload) { |
502: | |
503: | return $this->getRoundTripPayload( |
504: | $this->runHttpOperation($operation, $ctx, $data, ...$args), |
505: | $payload, |
506: | $operation, |
507: | ); |
508: | }; |
509: | $toBackend = $toBackend |
510: | ->after($after) |
511: | ->collectThen($then); |
512: | } |
513: | |
514: | |
515: | return $toBackend |
516: | ->startInto($roundTrip) |
517: | ->withConformity($this->Conformity) |
518: | ->unlessIf(fn($entity) => $entity === null) |
519: | ->start(); |
520: | }; |
521: | |
522: | case OP::READ_LIST: |
523: | return function ( |
524: | SyncContextInterface $ctx, |
525: | ...$args |
526: | ) use ($operation): iterable { |
527: | |
528: | $payload = $this->runHttpOperation($operation, $ctx, ...$args); |
529: | $arg = new SyncPipelineArgument($operation, $ctx, $args); |
530: | |
531: | return $this |
532: | ->getPipelineFromBackend() |
533: | ->stream($payload, $arg) |
534: | ->withConformity($this->Conformity) |
535: | ->unlessIf(fn($entity) => $entity === null) |
536: | ->start(); |
537: | }; |
538: | } |
539: | |
540: | |
541: | throw new LogicException(sprintf( |
542: | 'Invalid SyncOperation: %d', |
543: | $operation, |
544: | )); |
545: | |
546: | } |
547: | |
548: | |
549: | |
550: | |
551: | |
552: | |
553: | |
554: | private function getHttpOperationClosure(int $operation): Closure |
555: | { |
556: | |
557: | if ( |
558: | SyncUtil::isWriteOperation($operation) |
559: | && Env::getDryRun() |
560: | ) { |
561: | |
562: | |
563: | return fn(CurlerInterface $curler, ?array $query, ?array $payload = null) => |
564: | $payload ?? []; |
565: | } |
566: | |
567: | |
568: | |
569: | |
570: | switch ([$operation, $this->MethodMap[$operation] ?? null]) { |
571: | case [OP::READ_LIST, HttpRequestMethod::GET]: |
572: | |
573: | return fn(CurlerInterface $curler, ?array $query) => |
574: | $curler->getPager() |
575: | ? $curler->getP($query) |
576: | : $curler->get($query); |
577: | |
578: | case [OP::READ_LIST, HttpRequestMethod::POST]: |
579: | |
580: | return fn(CurlerInterface $curler, ?array $query, ?array $payload = null) => |
581: | $curler->getPager() |
582: | ? $curler->postP($payload, $query) |
583: | : $curler->post($payload, $query); |
584: | |
585: | case [$operation, HttpRequestMethod::GET]: |
586: | |
587: | return fn(CurlerInterface $curler, ?array $query) => |
588: | $curler->get($query); |
589: | |
590: | case [$operation, HttpRequestMethod::POST]: |
591: | |
592: | return fn(CurlerInterface $curler, ?array $query, ?array $payload = null) => |
593: | $curler->post($payload, $query); |
594: | |
595: | case [$operation, HttpRequestMethod::PUT]: |
596: | |
597: | return fn(CurlerInterface $curler, ?array $query, ?array $payload = null) => |
598: | $curler->put($payload, $query); |
599: | |
600: | case [$operation, HttpRequestMethod::PATCH]: |
601: | |
602: | return fn(CurlerInterface $curler, ?array $query, ?array $payload = null) => |
603: | $curler->patch($payload, $query); |
604: | |
605: | case [$operation, HttpRequestMethod::DELETE]: |
606: | |
607: | return fn(CurlerInterface $curler, ?array $query, ?array $payload = null) => |
608: | $curler->delete($payload, $query); |
609: | } |
610: | |
611: | |
612: | throw new LogicException(sprintf( |
613: | 'Invalid SyncOperation or method map: %d', |
614: | $operation, |
615: | )); |
616: | |
617: | } |
618: | |
619: | |
620: | |
621: | |
622: | |
623: | |
624: | |
625: | |
626: | private function runHttpOperation(int $operation, SyncContextInterface $ctx, ...$args) |
627: | { |
628: | return ( |
629: | $this->Callback === null |
630: | ? $this |
631: | : ($this->Callback)($this, $operation, $ctx, ...$args) |
632: | )->doRunHttpOperation($operation, $ctx, ...$args); |
633: | } |
634: | |
635: | |
636: | |
637: | |
638: | |
639: | |
640: | private function doRunHttpOperation(int $operation, SyncContextInterface $ctx, ...$args) |
641: | { |
642: | if ($this->Path === null || $this->Path === []) { |
643: | throw new LogicException('Path required'); |
644: | } |
645: | |
646: | if ($this->Args !== null) { |
647: | $args = $this->Args; |
648: | } |
649: | |
650: | $id = $this->getIdFromArgs($operation, $args); |
651: | |
652: | $paths = (array) $this->Path; |
653: | while ($paths) { |
654: | $claim = []; |
655: | $idApplied = false; |
656: | $path = array_shift($paths); |
657: | |
658: | if (!Regex::matchAll( |
659: | '/:(?<name>[[:alpha:]_][[:alnum:]_]*+)/', |
660: | $path, |
661: | $matches, |
662: | \PREG_SET_ORDER |
663: | )) { |
664: | break; |
665: | } |
666: | |
667: | $matches = Arr::unique(Arr::pluck($matches, 'name')); |
668: | foreach ($matches as $name) { |
669: | if ( |
670: | $id !== null |
671: | && Str::snake($name) === 'id' |
672: | ) { |
673: | $idApplied = true; |
674: | $path = $this->applyParameterValue((string) $id, $name, $path); |
675: | continue; |
676: | } |
677: | |
678: | $value = $ctx->getFilter($name, false); |
679: | $isFilter = true; |
680: | if ($value === null) { |
681: | $value = $ctx->getValue($name); |
682: | $isFilter = false; |
683: | } |
684: | |
685: | if ($value === null) { |
686: | if ($paths) { |
687: | continue 2; |
688: | } |
689: | throw new SyncInvalidContextException( |
690: | sprintf("Unable to resolve '%s' in path '%s'", $name, $path) |
691: | ); |
692: | } |
693: | |
694: | if (is_array($value)) { |
695: | if ($paths) { |
696: | continue 2; |
697: | } |
698: | throw new SyncInvalidContextException( |
699: | sprintf("Cannot apply array to '%s' in path '%s'", $name, $path) |
700: | ); |
701: | } |
702: | |
703: | $path = $this->applyParameterValue((string) $value, $name, $path); |
704: | if ($isFilter) { |
705: | $claim[] = $name; |
706: | } |
707: | } |
708: | break; |
709: | } |
710: | |
711: | if ($claim) { |
712: | foreach ($claim as $name) { |
713: | $ctx->claimFilter($name); |
714: | } |
715: | } |
716: | |
717: | |
718: | |
719: | |
720: | if ( |
721: | $id !== null |
722: | && !$idApplied |
723: | && $this->Callback === null |
724: | && strpos($path, '?') === false |
725: | ) { |
726: | $path .= '/' . $this->filterParameterValue((string) $id, 'id', "$path/:id"); |
727: | } |
728: | |
729: | $curler = $this->Provider->getCurler( |
730: | $path, |
731: | $this->Expiry, |
732: | $this->Headers, |
733: | $this->Pager, |
734: | $this->AlwaysPaginate, |
735: | ); |
736: | |
737: | if ($this->CurlerCallback) { |
738: | $curler = ($this->CurlerCallback)($curler, $this, $operation, $ctx, ...$args); |
739: | } |
740: | |
741: | $httpClosure = $this->getHttpOperationClosure($operation); |
742: | $payload = isset($args[0]) && is_array($args[0]) |
743: | ? $args[0] |
744: | : null; |
745: | |
746: | return $this->Provider->runOperation( |
747: | $ctx, |
748: | function () use ($operation, $id, $curler, $httpClosure, $payload) { |
749: | try { |
750: | return $httpClosure($curler, $this->Query, $payload); |
751: | } catch (HttpErrorException $ex) { |
752: | |
753: | |
754: | if ($operation === OP::READ && $id !== null && ( |
755: | ($status = $ex->getResponse()->getStatusCode()) === 404 |
756: | || $status === 410 |
757: | )) { |
758: | throw new SyncEntityNotFoundException( |
759: | $this->Provider, |
760: | $this->Entity, |
761: | $id, |
762: | $ex, |
763: | ); |
764: | } |
765: | throw $ex; |
766: | } |
767: | }, |
768: | ); |
769: | } |
770: | |
771: | |
772: | |
773: | |
774: | |
775: | |
776: | private function getIdFromArgs(int $operation, array $args) |
777: | { |
778: | if (SyncUtil::isListOperation($operation)) { |
779: | return null; |
780: | } |
781: | |
782: | if ($operation === OP::READ) { |
783: | $id = $args[0] ?? null; |
784: | |
785: | if ($id === null || is_int($id) || is_string($id)) { |
786: | return $id; |
787: | } |
788: | |
789: | return null; |
790: | } |
791: | |
792: | $entity = $args[0] ?? null; |
793: | |
794: | if (!$entity instanceof SyncEntityInterface) { |
795: | return null; |
796: | } |
797: | |
798: | return $entity->getId(); |
799: | } |
800: | |
801: | private function applyParameterValue(string $value, string $name, string $path): string |
802: | { |
803: | $value = $this->filterParameterValue($value, $name, $path); |
804: | return Regex::replace("/:{$name}(?![[:alnum:]_])/", $value, $path); |
805: | } |
806: | |
807: | private function filterParameterValue(string $value, string $name, string $path): string |
808: | { |
809: | if (strpos($value, '/') !== false) { |
810: | throw new UnexpectedValueException( |
811: | sprintf("Cannot apply value of '%s' to path '%s': %s", $name, $path, $value), |
812: | ); |
813: | } |
814: | return rawurlencode($value); |
815: | } |
816: | |
817: | |
818: | |
819: | |
820: | |
821: | |
822: | |
823: | |
824: | |
825: | |
826: | |
827: | private function getRoundTripPayload($response, $requestPayload, int $operation) |
828: | { |
829: | switch ($this->ReturnEntitiesFrom) { |
830: | case EntitySource::PROVIDER_OUTPUT: |
831: | |
832: | return Env::getDryRun() |
833: | ? $requestPayload |
834: | : $response; |
835: | |
836: | case EntitySource::OPERATION_INPUT: |
837: | |
838: | return $requestPayload; |
839: | |
840: | default: |
841: | |
842: | throw new SyncInvalidEntitySourceException( |
843: | $this->Provider, |
844: | $this->Entity, |
845: | $operation, |
846: | $this->ReturnEntitiesFrom, |
847: | ); |
848: | |
849: | } |
850: | } |
851: | |
852: | |
853: | |
854: | |
855: | |
856: | private function getRoundTripPipeline(int $operation): PipelineInterface |
857: | { |
858: | switch ($this->ReturnEntitiesFrom) { |
859: | case EntitySource::PROVIDER_OUTPUT: |
860: | |
861: | return Env::getDryRun() |
862: | ? Pipeline::create() |
863: | : $this->getPipelineFromBackend(); |
864: | |
865: | case EntitySource::OPERATION_INPUT: |
866: | |
867: | return Pipeline::create(); |
868: | |
869: | default: |
870: | |
871: | throw new SyncInvalidEntitySourceException( |
872: | $this->Provider, |
873: | $this->Entity, |
874: | $operation, |
875: | $this->ReturnEntitiesFrom, |
876: | ); |
877: | |
878: | } |
879: | } |
880: | |
881: | |
882: | |
883: | |
884: | public static function getReadableProperties(): array |
885: | { |
886: | return [ |
887: | ...parent::getReadableProperties(), |
888: | 'Path', |
889: | 'Query', |
890: | 'Headers', |
891: | 'Pager', |
892: | 'AlwaysPaginate', |
893: | 'Expiry', |
894: | 'MethodMap', |
895: | 'CurlerCallback', |
896: | 'SyncOneEntityPerRequest', |
897: | 'Callback', |
898: | 'Args', |
899: | ]; |
900: | } |
901: | } |
902: | |