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