/** * Get the default value of each field. * * @return array */ public function getDefaults() { if ($this->spec instanceof Spec) { return $this->spec->getDefaults(); } return []; }
/** * Handle an incoming request. * * @param Request $request * @param Closure $next * * @return mixed */ public function handle($request, Closure $next) { $validationResult = Spec::define(['content-hash' => PrimitiveTypeConstraint::forType(ScalarTypes::SCALAR_STRING), 'authorization' => PrimitiveTypeConstraint::forType(ScalarTypes::SCALAR_STRING)], [], ['content-hash', 'authorization'])->check(array_map(function ($entry) { return $entry[0]; }, $request->headers->all())); if ($validationResult->failed()) { return ApiResponse::makeFromSpec($validationResult)->toResponse(); } $authorization = str_replace('Hash ', '', $request->headers->get('Authorization')); $content = $request->getContent(); try { $pair = $this->finder->byPublicId($authorization, KeyPairTypes::TYPE_HMAC); $hasher = new HmacHasher(); $verificationResult = $hasher->verify($request->headers->get('Content-Hash'), $content . Carbon::now()->format($this->format), $pair->getSecretKey()); if ($verificationResult) { $request->attributes->set(static::ATTRIBUTE_KEYPAIR, $pair); return $next($request); } return ApiResponse::create([], ApiResponse::STATUS_INVALID, ['HMAC content hash does not match the expected hash.'])->toResponse(); } catch (ModelNotFoundException $ex) { if ($ex->getModel() === KeyPair::class) { return ApiResponse::create([], ApiResponse::STATUS_INVALID, ['Unable to locate public ID. Check your credentials'])->toResponse(); } throw $ex; } }
/** * Check that the spec matches and overlay help messaged. * * The resulting SpecResult instance should have more user-friendly * messages. This allows one to use Specs for validation on a website or * even an API. * * @param array $input * * @return SpecResult */ public function check(array $input) { $result = $this->spec->check($input); return new SpecResult($result->getMissing(), Arr::walkCopy($result->getFailed(), function ($key, $value, &$array, $path) { $array[$key] = Std::coalesce(Std::firstBias(Arr::dotGet($this->messages, Std::nonempty($path, $key)) !== null, [Arr::dotGet($this->messages, Std::nonempty($path, $key))], null), Std::firstBias($value instanceof AbstractConstraint, function () use($value) { return $value->getDescription(); }, null), Std::firstBias(is_array($value), function () use($value) { return array_map(function (AbstractConstraint $item) { return $item->getDescription(); }, $value); }, $value)); }, true, '', false), $result->getStatus()); }
/** * Encode constraints into JSON schema types/constraints. * * @param Spec $spec * @param AbstractConstraint|AbstractConstraint[]|Spec $input * @param string $title * * @throws CoreException * @return array */ public function encodeConstraints(Spec $spec, $input, $title) { if ($input instanceof AbstractConstraint) { $schema = ['description' => $input->getDescription()]; if ($input instanceof PrimitiveTypeConstraint) { $schema['type'] = $input->toString(); } return $schema; } elseif (is_array($input)) { $schema = []; $descriptions = []; foreach ($input as $constraint) { $descriptions[] = $constraint->getDescription(); if ($constraint instanceof PrimitiveTypeConstraint) { $schema['type'] = $constraint->toString(); } } $schema['description'] = implode('. ', $descriptions); return $schema; } elseif ($input instanceof Spec) { return (new static())->encode($input, $title); } throw new CoreException('Unexpected constraint type.'); }
/** * Render the Node. * * @throws CoreException * @throws InvalidAttributesException * @return string */ public function render() { $result = $this->spec->check($this->attributes); if ($result->failed()) { throw new InvalidAttributesException($result); } $attributes = $this->renderAttributes(); if (strlen($attributes)) { $attributes = ' ' . $attributes; } if ($this->selfClosing) { return vsprintf('<%s%s/>', [$this->tagName, $attributes]); } return vsprintf('<%s%s>%s</%s>', [$this->tagName, $attributes, $this->renderContent(), $this->tagName]); }
public function testComplexSpec() { $graph = new SpecGraph(); $graph->add('input', [], Spec::define(['sleepy' => Boa::boolean(), 'tennis_balls' => Boa::integer(), 'message' => Boa::either(Boa::string(), Boa::integer())], [], ['message'])); $graph->add('allowedMessage', ['input'], Spec::define(['message' => [Boa::in(['hi', 'how are you?', 'you dumb']), Boa::in(['hi', 'how are you?', 'you are smart'])]], [], ['message'])); $graph->add('validBallCount', ['input'], Spec::define(['tennis_balls' => Boa::between(1, 10)])); $graph->add('additionalBallProps', ['validBallCount'], Spec::define(['ball_color' => [Boa::string(), Boa::in(['blue', 'red', 'yellow'])]], [], ['ball_color'])); $result = $graph->check(['sleepy' => true, 'tennis_balls' => 3, 'message' => 'hi', 'ball_color' => 'blue']); $this->assertTrue($result->passed()); $result2 = $graph->check(['sleepy' => 1, 'tennis_balls' => 3]); $this->assertEqualsMatrix([[true, $result2->failed()], [1, count($result2->getFailed())], [['message'], $result2->getMissing()]]); $result3 = $graph->check(['sleepy' => true, 'tennis_balls' => -30, 'message' => 'hello']); $this->assertEqualsMatrix([[true, $result3->failed()], [2, count($result3->getFailed())], [[], $result3->getMissing()]]); $result4 = $graph->check(['sleepy' => true, 'tennis_balls' => 3, 'message' => 'how are you?']); $this->assertEqualsMatrix([[true, $result4->failed()], [0, count($result4->getFailed())], [['ball_color'], $result4->getMissing()]]); $result5 = $graph->check(['sleepy' => true, 'tennis_balls' => 3, 'message' => 'how are you?', 'ball_color' => 'liquid_gold']); $this->assertEqualsMatrix([[true, $result5->failed()], [1, count($result5->getFailed())], [[], $result5->getMissing()]]); }
/** * Get all the constraints for a field. * * @param string $fieldName * * @return Iterable */ protected function getInternalFieldConstraints($fieldName) { return parent::getInternalFieldConstraints($fieldName)->append(ArrayList::of([$this->getFieldType($fieldName)])); }
/** * The inverse operation of calling toResponse. * * This function will attempt to parse a Response object into an * ApiResponse object, which can be useful for introspection and testing. * * @param Response $response * * @throws CoreException * @return static */ public static function fromResponse(Response $response) { $body = Std::jsonDecode($response->getContent()); $result = Spec::define(['messages' => Boa::arrOf(Boa::string()), 'status' => Boa::in(static::getValidStatuses()), 'code' => Boa::integer()])->check($body); if ($result->failed()) { throw new CoreException('Unable to parse an ApiResponse out of the content of a' . ' Response object. Make sure that the Response object was' . ' actually generated by an ApiResponse or a compatible' . ' implementation.'); } return new static(Arr::except($body, static::getReservedKeys()), $body['status'], $body['messages']); }
/** * Get a RAML parameter definition from a Spec field. * * @param Spec $spec * @param string $field * * @return array */ protected function specFieldToParameter(Spec $spec, $field) { $constraints = $spec->getConstraints(); $defaults = $spec->getDefaults(); $required = $spec->getRequired(); $parameter = []; if (Arr::has($constraints, $field)) { $input = $constraints[$field]; if (is_array($input) || $input instanceof Spec) { if ($input instanceof Spec) { $input = [$input]; } foreach ($input as $constraint) { if ($constraint instanceof PrimitiveTypeConstraint) { switch ($constraint->toString()) { case ScalarTypes::SCALAR_STRING: $parameter['type'] = 'string'; break; case ScalarTypes::SCALAR_FLOAT: case ScalarTypes::SCALAR_INTEGER: $parameter['type'] = 'number'; break; case ScalarTypes::SCALAR_BOOLEAN: $parameter['type'] = 'boolean'; break; } } } } } if (Arr::has($defaults, $field)) { $parameter['default'] = $defaults[$field]; } $parameter['required'] = in_array($field, $required); return $parameter; }
/** * @inheritDoc */ public function getSpec() { return Spec::define(['days' => Boa::integer(), 'repeat_in' => Boa::integer(), 'expire_after' => Boa::integer()], ['days' => 30, 'repeat_in' => -1, 'expire_after' => 1440]); }
/** * @param Spec $spec * @param string $field * * @return TableRow */ protected function renderConstraint(Spec $spec, $field) { $constraints = $spec->getConstraints(); $defaults = $spec->getDefaults(); $default = ''; if (array_key_exists($field, $defaults)) { $default = $defaults[$field]; } $constraint = $constraints[$field]; if ($constraint instanceof AbstractConstraint) { return new TableRow([], [new TableCell([], $field), new TableCell([], $constraint->toString()), new TableCell([], (string) $default)]); } return new TableRow([], [new TableCell([], $field), new TableCell([], Std::map(function (AbstractConstraint $constraint) { return $constraint->toString() . ', '; }, $constraint)), new TableCell([], (string) $default)]); }
public function testFireWithValidSpec() { $laravelJob = m::mock(LaravelJob::class); $laravelJob->shouldReceive('delete')->atLeast()->once(); $job = new Job(); $job->state = JobState::QUEUED; $job->data = json_encode(['omg' => false, 'why_not' => 'because']); $handler = $this->expectationsToMock(BaseTask::class, [$this->on('fire', [$job, m::type(JobSchedulerInterface::class)]), $this->on('getSpec', [], Spec::define(['omg' => Boa::boolean()], ['yes' => 'please'], ['why_not'])), $this->on('isSelfDeleting', [], false)]); $impersonator = new Impersonator(); $impersonator->mock(JobRepositoryInterface::class, function (MockInterface $mock) use($job) { $mock->shouldReceive('find')->with(1337)->atLeast()->once()->andReturn($job); $mock->shouldReceive('started')->with($job, m::type('string'))->atLeast()->once(); $mock->shouldReceive('complete')->with($job)->atLeast()->once(); }); $impersonator->mock(HandlerResolverInterface::class, function (MockInterface $mock) use($job, $handler) { $mock->shouldReceive('resolve')->with($job)->atLeast()->once()->andReturn($handler); }); /** @var RunTaskCommand $command */ $command = $impersonator->make(RunTaskCommand::class); $command->fire($laravelJob, ['job_id' => 1337]); }
public function testAccessors() { $strings = Boa::string(); $spec = new Spec(['name' => $strings], ['name' => 'Bobby'], ['name']); $this->assertEqualsMatrix([[['name' => $strings], $spec->getConstraints()], [['name' => 'Bobby'], $spec->getDefaults()], [['name'], $spec->getRequired()]]); }