* @return struct_subject */ function make( string $name = 'API', array $infoparagraphs = [] ) : struct_subject { return (new struct_subject($name, [], $infoparagraphs)); } /** * @param struct_subject $subject * @param string $name * @param array $options { * record< * ?active:function<(null|string),boolean>, * ?title:(null|string), * ?execution:function<(null|string),any,any,any>, * ?restriction:function<(null|string),any,any>, * ?input_type:function<(null|string),any>, * ?output_type:function<(null|string),any>, * ?description:string, * ?custom:any, * > * } * @author Christian Fraß */ function register( struct_subject $subject, string $name, array $options = [] ) : void { $options = array_merge( [ 'active' => function ($version) { return true; }, 'execution' => function ($version, $environment, $input) { throw (new \Exception('not implemented')); }, 'restriction' => function ($version, $environment) { return true; }, 'input_type' => function ($version) { return ['kind' => 'any']; }, 'output_type' => function ($version) { return ['kind' => 'any']; }, 'title' => null, 'description' => null, 'custom' => null, ], $options ); if (array_key_exists($name, $subject->actions)) { throw (new exception_action_already_registered($name)); } else { $action = new struct_action( $name, $options['active'], $options['execution'], $options['restriction'], fn($version) => \alveolata\type\make($options['input_type']($version)), fn($version) => \alveolata\type\make($options['output_type']($version)), $options['title'], $options['description'], $options['custom'] ); $subject->actions[$action->name] = $action; } } /** * @param struct_subject $subject * @param string $action_name * @param ?array $options { * record< * ?version:(null|string), * ?input:any, * ?environment:map<(null|string),any>, * ?checklevel_restriction:&enum_checklevel, * ?checklevel_input:&enum_checklevel, * ?checklevel_output:&enum_checklevel, * > * } * @return array {record} * @author Christian Fraß */ function call_with_custom( struct_subject $subject, string $action_name, array $options = [] ) : array { $options = array_merge( [ 'version' => null, 'input' => null, 'environment' => [], 'checklevel_restriction' => enum_checklevel::hard, 'checklevel_input' => enum_checklevel::soft, 'checklevel_output' => enum_checklevel::soft, ], $options ); if (! array_key_exists($action_name, $subject->actions)) { throw (new exception_action_missing($action_name)); } else { $action = $subject->actions[$action_name]; if (! ($action->active)($options['version'])) { throw (new exception_action_missing($action_name)); } else { switch ($options['checklevel_restriction']) { case enum_checklevel::none: { // do nothing break; } case enum_checklevel::soft: { if (! ($action->restriction)($options['version'], $options['environment'])) { \alveolata\log\warning( 'api_permission_denied', [ 'version' => $options['version'], 'environment' => $options['environment'], 'action_name' => $action->name, ] ); } break; } default: case enum_checklevel::hard: { if (! ($action->restriction)($options['version'], $options['environment'])) { throw (new exception_denied()); } break; } } switch ($options['checklevel_input']) { case enum_checklevel::none: { // do nothing break; } default: case enum_checklevel::soft: { $input_type = ($action->input_type)($options['version']); $flaws = \alveolata\type\investigate($input_type, $options['input']); if (! empty($flaws)) { \alveolata\log\warning( 'api_input_flawed', [ 'type_expected' => \alveolata\type\to_string($input_type), // 'type_derived' => \alveolata\type\derive($options['input']), 'value' => $options['input'], 'flaws' => $flaws, ] ); } break; } case enum_checklevel::hard: { $input_type = ($action->input_type)($options['version']); $flaws = \alveolata\type\investigate($input_type, $options['input']); if (! empty($flaws)) { throw (new exception_invalid_input($input_type, $options['input'], $flaws)); } break; } } $output = ($action->execution)($options['version'], $options['environment'], $options['input']); switch ($options['checklevel_output']) { case enum_checklevel::none: { // do nothing break; } default: case enum_checklevel::soft: { $output_type = ($action->output_type)($options['version']); $flaws = \alveolata\type\investigate($output_type, $output); if (! empty($flaws)) { \alveolata\log\warning( 'api_output_flawed', [ 'type_expected' => \alveolata\type\to_string($output_type), // 'type_derived' => \alveolata\type\derive($output_type), 'value' => $output, 'flaws' => $flaws, ] ); } break; } case enum_checklevel::hard: { $output_type = ($action->output_type)($options['version']); $flaws = \alveolata\type\investigate($output_type, $output); if (! empty($flaws)) { throw (new exception_invalid_output($output_type, $output, $flaws)); } break; } } return [ 'custom' => $action->custom, 'output' => $output, ]; } } } /** * @param struct_subject $subject * @param string $action_name * @param ?array $options { * record< * ?version:(null|string), * ?input:any, * ?environment:map, * ?checklevel_restriction:&enum_checklevel, * ?checklevel_input:&enum_checklevel, * ?checklevel_output:&enum_checklevel, * > * } * @return mixed {any} * @author Christian Fraß */ function call( struct_subject $subject, string $action_name, array $options = [] ) { $options = array_merge( [ 'version' => null, 'input' => null, 'environment' => [], 'checklevel_restriction' => enum_checklevel::hard, 'checklevel_input' => enum_checklevel::soft, 'checklevel_output' => enum_checklevel::soft, ], $options ); return call_with_custom( $subject, $action_name, [ 'version' => $options['version'], 'input' => $options['input'], 'environment' => $options['environment'], 'checklevel_restriction' => $options['checklevel_restriction'], 'checklevel_input' => $options['checklevel_input'], 'checklevel_output' => $options['checklevel_output'], ] )['output']; } /** * @param struct_subject $subject * @param array $options { * record< * ?version:(null|string), * > * } * @return string * @author Christian Fraß */ function doc_markdown( struct_subject $subject, array $options = [] ) : string { $options = array_merge( [ 'version' => null, ], $options ); $markdown = ''; $markdown .= \alveolata\markdown\headline(1, $subject->name); { { $markdown .= \alveolata\markdown\headline(2, 'Infos'); foreach ($subject->infoparagraphs as $infoparagraph) { $markdown .= \alveolata\markdown\paragraph($infoparagraph); } $markdown .= \alveolata\markdown\sectionend(); } { $markdown .= \alveolata\markdown\headline(2, 'Methods'); foreach ($subject->actions as $name => $action) { if (! ($action->active)($options['version'])) { // do nothing } else { $markdown .= \alveolata\markdown\headline(3, $action->name); // description { $markdown .= \alveolata\markdown\headline(4, 'Description'); $markdown .= \alveolata\markdown\paragraph($action->description); $markdown .= \alveolata\markdown\sectionend(); } // input type { $markdown .= \alveolata\markdown\headline(4, 'Input Type'); $markdown .= \alveolata\markdown\code(\alveolata\type\to_string(($action->input_type)($options['version']), true)); $markdown .= \alveolata\markdown\sectionend(); } // output type { $markdown .= \alveolata\markdown\headline(4, 'Output Type'); $markdown .= \alveolata\markdown\code(\alveolata\type\to_string(($action->output_type)($options['version']), true)); $markdown .= \alveolata\markdown\sectionend(); } $markdown .= \alveolata\markdown\sectionend(); $markdown .= \alveolata\markdown\rule(); } } $markdown .= \alveolata\markdown\sectionend(); } $markdown .= \alveolata\markdown\sectionend(); } return $markdown; } /** * @param struct_subject $subject * @param array $options { * record< * ?version:(null|string), * ?hue:integer, * > * } * @return string * @author Christian Fraß */ function doc_html( struct_subject $subject, array $options = [] ) : string { $options = array_merge( [ 'version' => null, 'hue' => 150, ], $options, ); $expose_type = (fn($type) => htmlentities( str_replace( "\t", ' ', \alveolata\type\to_string($type, true) ) )); $template_stylesheet = \alveolata\file\read(__DIR__ . '/template-stylesheet.css.tpl'); $template_doc = \alveolata\file\read(__DIR__ . '/template-doc.html.tpl'); $template_infoparagraph = \alveolata\file\read(__DIR__ . '/template-infoparagraph.html.tpl'); $template_tocentry = \alveolata\file\read(__DIR__ . '/template-tocentry.html.tpl'); $template_action = \alveolata\file\read(__DIR__ . '/template-action.html.tpl'); return \alveolata\string\coin( $template_doc, [ 'style' => \alveolata\string\coin( $template_stylesheet, [ 'hue' => strval($options['hue']), ] ), 'name' => $subject->name, 'version' => ($options['version'] ?? '-'), 'infoparagraphs' => \alveolata\string\join( \alveolata\list_\map( $subject->infoparagraphs, fn($infoparagraph) => \alveolata\string\coin( $template_infoparagraph, [ 'content' => $infoparagraph, ] ) ), "\n" ), 'tocentries' => \alveolata\string\join( \alveolata\list_\map( $subject->actions, fn($action) => \alveolata\string\coin( $template_tocentry, [ 'name' => $action->name, 'title' => ($action->title ?? $action->name), 'href' => sprintf('#%s', $action->name), ] ) ), "\n" ), 'actions' => \alveolata\string\join( \alveolata\list_\map( \alveolata\list_\filter( $subject->actions, fn($action) => ($action->active)($options['version']) ), fn($action) => \alveolata\string\coin( $template_action, [ 'name' => $action->name, 'anchor' => sprintf('%s', $action->name), 'href' => sprintf('#%s', $action->name), 'description' => $action->description, 'input_type' => $expose_type(($action->input_type)($options['version'])), 'output_type' => $expose_type(($action->output_type)($options['version'])), ] ) ), "\n" ), ] ); } ?>