diff --git a/CHANGELOG.md b/CHANGELOG.md index b1ba58d..cae3baa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ ## v0.1.0 (2024-07-24) * Built initial Blueprint implementation * Added Blueprint Factory +* Constructed JSON Blueprint schemas diff --git a/examples/artist-profile-setup.json b/examples/artist-profile-setup.json index 87a18ff..6102f4b 100644 --- a/examples/artist-profile-setup.json +++ b/examples/artist-profile-setup.json @@ -1,5 +1,6 @@ { - "$schema": "https://schema.decodelabs.com/chronos/0.1/program", + "-$schema": "https://schema.decodelabs.com/chronos/0.1/program.json", + "$schema": "../schema/0.1/program.json", "id": "artist-profile-setup", "name": "Artist Profile Setup", "description": "Get your artist profile in check - get your biography written, photos uploaded and best foot forward.", @@ -42,7 +43,15 @@ "priority": "low", "duration": "1 hour", "actions": { - "Profile.UploadPhotos": { } + "Profile.UploadPhotos": { + "id": "hi hi", + "count": 5, + "test": { + "Test.Jam": { + "hello": "world" + } + } + } } } ] } diff --git a/schema/0.1/actions.json b/schema/0.1/actions.json new file mode 100644 index 0000000..eb40056 --- /dev/null +++ b/schema/0.1/actions.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://schema.decodelabs.com/chronos/0.1/actions.json", + "$anchor": "actions", + "type": "object", + "description": "A list of functional actions that must be completed to finish the step.", + "patternProperties": { + "^(([A-Z][a-zA-Z0-9]+)\\.)?([A-Z][a-zA-Z0-9]+)(\\:\\$?([a-zA-Z0-9]+))?$": { + "type": "object", + "description": "A functional action that must be completed to finish the step.", + "patternProperties": { + "^([a-zA-Z0-9]+)$": { + "anyOf": [ + { "type": [ "string", "number" ] }, + { "type": "array", "items": { "type": "string" } }, + { "$ref": "#actions" } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/schema/0.1/program.json b/schema/0.1/program.json new file mode 100644 index 0000000..95504ef --- /dev/null +++ b/schema/0.1/program.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://schema.decodelabs.com/chronos/0.1/program.json", + "type": "object", + "properties": { + "$schema": { + "type": "string", + "description": "The schema that this document should conform to.", + "enum": [ + "https://schema.decodelabs.com/chronos/0.1/program.json", + "../schema/0.1/program.json" + ] + }, + "id": { + "$ref": "shared.json#id", + "description": "The unique identifier for this program." + }, + "name": { + "type": "string", + "description": "The name of the program." + }, + "description": { + "type": "string", + "description": "A short description of what the program does." + }, + "categories": { + "type": "array", + "description": "A list of IDs of categories that this program belongs to.", + "items": { + "$ref": "shared.json#id", + "description": "The unique identifier for a category." + } + }, + "duration": { + "type": "string", + "description": "A string representation of the estimated time it will take to complete the program." + }, + "priority": { + "$ref": "shared.json#priority", + "description": "A string representation of the priority of the program." + }, + "steps": { + "type": "array", + "items": { + "$ref": "step.json" + } + } + }, + "required": [ + "id", + "name", + "description", + "categories", + "duration", + "priority", + "steps" + ] +} diff --git a/schema/0.1/shared.json b/schema/0.1/shared.json new file mode 100644 index 0000000..0a35083 --- /dev/null +++ b/schema/0.1/shared.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://schema.decodelabs.com/chronos/0.1/shared.json", + "type": "object", + "properties": { + "id": { + "$anchor": "id", + "type": "string", + "description": "The unique identifier for this item.", + "pattern": "^[a-z0-9-_]+$", + "minLength": 5, + "maxLength": 64 + }, + "priority": { + "$anchor": "priority", + "type": "string", + "description": "A string representation of the priority of this item.", + "default": "medium", + "enum": [ + "low", "Low", + "medium", "Medium", + "high", "High", + "critical", "Critical" + ] + } + } +} diff --git a/schema/0.1/step.json b/schema/0.1/step.json new file mode 100644 index 0000000..f11dbc6 --- /dev/null +++ b/schema/0.1/step.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://schema.decodelabs.com/chronos/0.1/step.json", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The unique identifier for this step." + }, + "name": { + "type": "string", + "description": "The readable name of the step." + }, + "description": { + "type": "string", + "description": "A short description of what the step does." + }, + "priority": { + "$ref": "shared.json#priority", + "description": "A string representation of the priority of the step." + }, + "duration": { + "type": "string", + "description": "A string representation of the estimated time it will take to complete the step." + }, + "await": { + "type": "object", + "description": "A list of actions and optional time offsets that must be completed before this step can be started.", + "patternProperties": { + "^[a-z0-9-_]+$": { + "type": [ "string", "null" ] + } + } + }, + "actions": { + "$ref": "actions.json" + } + }, + "required": [ + "id", + "actions" + ] +} diff --git a/src/Blueprint/Factory.php b/src/Blueprint/Factory.php index 0e44089..4b00d64 100644 --- a/src/Blueprint/Factory.php +++ b/src/Blueprint/Factory.php @@ -101,15 +101,19 @@ protected function getSchemaClass( ); } - if (!str_starts_with($schema, self::BASE_URL)) { + if ( + ( + !str_starts_with($schema, self::BASE_URL) && + !preg_match('/^(\.\.)?\//', $schema) + ) || + !preg_match('/\/[0-9\.]{1,4}\/([a-z0-9-]+)+\.json$/', $schema, $matches) + ) { throw Exceptional::UnexpectedValue( 'Unknown blueprint schema: ' . $schema ); } - $name = substr($schema, strlen(self::BASE_URL)); - $parts = explode('/', $name, 2); - $name = $parts[1] ?? ''; + $name = $matches[1]; if (!isset(self::SCHEMAS[$name])) { throw Exceptional::UnexpectedValue( diff --git a/src/Blueprint/WithIdentityTrait.php b/src/Blueprint/WithIdentityTrait.php index 8425f13..d726e64 100644 --- a/src/Blueprint/WithIdentityTrait.php +++ b/src/Blueprint/WithIdentityTrait.php @@ -36,7 +36,7 @@ public function __construct( $name = Dictum::name($id); } - $this->id = $id; + $this->setId($id); $this->name = $name; $this->description = $description; } @@ -47,6 +47,16 @@ public function __construct( public function setId( string $id ): void { + if ( + !preg_match('/^[a-z0-9-_]+$/', $id) || + strlen($id) < 5 || + strlen($id) > 64 + ) { + throw Exceptional::InvalidArgument( + 'Invalid blueprint ID: ' . $id + ); + } + $this->id = $id; }