<?php 
 
declare(strict_types=1); 
 
namespace frdlweb\Api\Rpc; 
 
 
use frdlweb\Api\Rpc\DiscoverMethod; 
use frdlweb\Api\Rpc\MethodDiscoverableInterface; 
 
use frdlweb\Api\Rpc\SchemaLoader; 
 
use LogicException; 
use Psr\Container\ContainerExceptionInterface; 
use Psr\Container\ContainerInterface; 
use Psr\Container\NotFoundExceptionInterface; 
 
 
 
use TypeError; 
use UMA\JsonRpc\Error; 
use UMA\JsonRpc\Request; 
use UMA\JsonRpc\Response; 
use UMA\JsonRpc\Internal\Assert; 
use UMA\JsonRpc\Internal\Input; 
use UMA\JsonRpc\Internal\MiddlewareStack; 
use UMA\JsonRpc\Internal\Validator; 
use UMA\JsonRpc\Procedure; 
use Opis\JsonSchema\Validator as OpisValidator; 
 
 
class Server /* extends \UMA\JsonRpc\Server */ 
{ 
 
    protected $config = []; 
     
    /** 
     * @var ContainerInterface 
   */ 
    protected $container; 
   
    /** 
     * @var string[] 
   */ 
    protected $methods; 
   
    /** 
     * @var string[] 
    */ 
    protected $middlewares; 
  
    /** 
     * @var int|null 
    */ 
    protected $batchLimit; 
      
    public function __construct(ContainerInterface $container, int $batchLimit = null, array $config = null, bool $discovery = true) 
    { 
        if(!is_array($config)){ 
          $config = [];     
        } 
        $this->config = array_merge([ 
        'schemaLoaderPrefix' => '', 
        'schemaLoaderDirs' => [],     
    //    'schemaCacheDir' => __DIR__.\DIRECTORY_SEPARATOR.'schema-store'.\DIRECTORY_SEPARATOR,             
        'schemaCacheDir' => sys_get_temp_dir() . \DIRECTORY_SEPARATOR . get_current_user(). \DIRECTORY_SEPARATOR . 'json-schema-store' . \DIRECTORY_SEPARATOR, 
        'discovery' =>     $discovery, 
        'meta' => [ 
          'openrpc' => '1.0.0-rc1', 
          "info" => [ 
              "title" => "JSON-RPC Server", 
              "description" =>"This the RPC-part of an Frdlweb API Server definition https://look-up.webfan3.de/?goto=oid%3A1.3.6.1.4.1.37553.8.1.8.1.13878", 
              "version" => "1.0.0", 
          ], 
          'servers' => [ 
            [ 
             'name' => 'Webfan Homepagesystem RPC API', 
             'summary' => 'Webfan Homepagesystem RPC API methods description', 
             'description' => 'This is the RPC part of an implementation of the Frdlweb API Specification (1.3.6.1.4.1.37553.8.1.8.1.13878)', 
             'url' => 'https://'.$_SERVER['SERVER_NAME'].'/software-center/modules-api/rpc/0.0.2/', 
            ] 
               
          ], 
            'methods' => [], 
            'components' => [ 
                 'links' => [], 
                 'contentDescriptors' => [], 
                 'schemas' => [], 
                 'examples' => [], 
               
              ], 
         ],     
        ], $config); 
         
        /* 
        parent::__construct($container, $batchLimit); 
        */ 
        $this->container = $container; 
        $this->batchLimit = $batchLimit; 
        $this->methods = []; 
        $this->middlewares = [];         
 
         
        if(true === $this->config['discovery']){ 
            /* $this->setDiscovery(DiscoverMethod::class, [$this, 'discoveryFactory']); */ 
             
                $callable = [$this, 'discoveryFactory']; 
                $this->setDiscovery(DiscoverMethod::class,static function(ContainerInterface $c) use($callable){ 
                    return call_user_func_array($callable, func_get_args()); 
                }); 
                 
        } 
    }     
 
   public function discoveryFactory(ContainerInterface $c) : MethodDiscoverableInterface{ 
          $DiscoverMethod = new DiscoverMethod($this); 
          $DiscoverMethod->config(null, $this->config['meta']); 
        
        
       return $DiscoverMethod; 
   }     
 
 
     
  public function setDiscovery($serviceId, callable $factory){ 
   if(!$this->getContainer()->has( $serviceId)){   
     if( 
         $this->container instanceof \Di\CompiledContainer 
         || 'CompiledContainer' === basename(get_class($this->container)) 
        
       ) { 
         $this->getContainer()->set( $serviceId, call_user_func_array($factory, [$this->container]));         
     }elseif($factory instanceof \closure || 'ContainerBuilder' === basename(get_class($this->container)) ){  
         $this->getContainer()->set( $serviceId, $factory);           
     }else{  
         //$this->getContainer()->set( $serviceId, $factory);           
          $this->getContainer()->set( $serviceId, call_user_func_array($factory, [$this->container]));     
     } 
    } 
       
           
      $this->set('rpc.discover', $serviceId); 
/**/       
      return $this; 
  } 
     
  public function getMethodDefinitions(){ 
     return $this->methods;   
  } 
     
  public function getContainer():ContainerInterface{ 
     return $this->container;   
  } 
     
  public function getConfig(){ 
     return $this->config;   
  } 
     
     
     
    public function set(string $method, string $serviceId): Server 
    { 
        if (!$this->container->has($serviceId)) { 
            throw new LogicException("Cannot find service '$serviceId' in the container"); 
        } 
 
        $this->methods[$method] = $serviceId; 
 
        return $this; 
    } 
 
    public function attach(string $serviceId): Server 
    { 
        if (!$this->container->has($serviceId)) { 
            throw new LogicException("Cannot find service '$serviceId' in the container"); 
        } 
 
        $this->middlewares[$serviceId] = null; 
 
        return $this; 
    } 
 
    /** 
     * @throws TypeError 
     */ 
    public function run(string $raw): ?string 
    { 
     
         
        $input = Input::fromString($raw, true); 
 
        if (!$input->parsable()) { 
            return self::end(Error::parsing()); 
        } 
 
        if ($input->isArray()) { 
            if ($this->tooManyBatchRequests($input)) { 
                return self::end(Error::tooManyBatchRequests($this->batchLimit)); 
            } 
 
            return $this->batch($input); 
        } 
 
        return $this->single($input); 
    } 
 
    protected function batch(Input $input): ?string 
    { 
        \assert($input->isArray()); 
 
        $responses = []; 
        foreach ($input->data() as $request) { 
            $pseudoInput = Input::fromSafeData($request); 
 
            if (null !== $response = $this->single($pseudoInput)) { 
                $responses[] = $response; 
            } 
        } 
 
        return empty($responses) ? 
            null : \sprintf('[%s]', \implode(',', $responses)); 
    } 
 
    /** 
     * @throws TypeError 
     */ 
    protected function single(Input $input): ?string 
    { 
        if (!$input->isRpcRequest()) { 
            return self::end(Error::invalidRequest()); 
        } 
 
        $request = new Request($input); 
 
        if (!\array_key_exists($request->method(), $this->methods)) { 
            return self::end(Error::unknownMethod($request->id()), $request); 
        } 
 
        try { 
            $procedure = Assert::isProcedure( 
                $this->container->get($this->methods[$request->method()]) 
            ); 
        } catch (ContainerExceptionInterface | NotFoundExceptionInterface $e) { 
            return self::end(Error::internal($request->id()), $request); 
        } 
 
         
         
        if (!Validator::validate($procedure->getSpec(), $request->params())) { 
            return self::end(Error::invalidParams($request->id()), $request); 
        } 
 
        $stack = MiddlewareStack::compose( 
            $procedure, 
            ...\array_map(function(string $serviceId) { 
                return $this->container->get($serviceId); 
            }, \array_keys($this->middlewares)) 
        ); 
 
         
         
        return self::end($stack($request), $request, $procedure, $this); 
    } 
 
    protected function tooManyBatchRequests(Input $input): bool 
    { 
        \assert($input->isArray()); 
 
        return \is_int($this->batchLimit) && $this->batchLimit < \count($input->data()); 
    } 
 
    protected static function end(Response $response, Request $request = null, Procedure $procedure = null, Server $Server = null): ?string 
    { 
        if( $procedure && true !== $response instanceof Error && $procedure instanceof MethodDiscoverableInterface){ 
             
           $spec =     $procedure->getResultSpec(); 
                 
            $result = json_decode(json_encode($response)); 
 
           if (!self::validateResponse($validation, $spec, $result->result, $Server)) { 
               $ea=$validation->getFirstError()->errorArgs(); 
              return self::end(new Error($request->id(), 'Invalid result '.print_r($ea,true),  $result->result), $request);  
           }                 
        } 
         
        //$spec =  $this->container->get($this->methods[$request->method()])->getResultSpec(); 
        //if (true !== $response instanceof Error && !Validator::validate( $this->container->get($this->methods[$request->method()])->getResultSpec(), $request->params())) { 
         //   return self::end(Error::invalidParams($request->id()), $request); 
      //  }         
         
        return $request instanceof Request && null === $request->id() ? 
            null : \json_encode($response); 
    }     
     
     
     
    public static function validateResponse(&$validation = null, \stdClass $schema, $data, Server $Server = null): bool 
    { 
         
         
        \assert(false !== \json_encode($data)); 
 
         
         
        if(null!==$Server){ 
          $config =$Server->getConfig();     
        }else{ 
            $config = [ 
                        'schemaLoaderPrefix' => 'https://json-schema.org', 
                        'schemaLoaderDirs' => [],     
                         'schemaCacheDir' =>sys_get_temp_dir() . \DIRECTORY_SEPARATOR . get_current_user(). \DIRECTORY_SEPARATOR . 'json-schema-store' . \DIRECTORY_SEPARATOR,     
                ]; 
        } 
     
        $validation = (new OpisValidator) 
            ->setLoader(new SchemaLoader($config['schemaLoaderPrefix'], 
                                     $config['schemaLoaderDirs'],  
                                     $config['schemaCacheDir'])) 
         
            ->dataValidation($data, $schema); 
         
         
     
        return $validation->isValid(); 
    } 
     
} 
 
 |