Contrib compatibility: process only Canvas JSON Schema `$ref`s in SDCs
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3550169. -->
Reported by: [pdureau](https://www.drupal.org/user/1903334)
Related to !188
>>>
<h3 id="overview">Overview</h3>
<p>Canvas is using JSON Schema references. Example:<br>
<code>{$ref: 'json-schema-definitions://canvas.module/image'}</code></p>
<p>They are resolved by <a href="https://git.drupalcode.org/project/canvas/-/blob/1.x/src/JsonSchemaDefinitionsStreamwrapper.php">JsonSchemaDefinitionsStreamwrapper</a> which is looking fine:</p>
<pre> Drupal\canvas\JsonSchemaDefinitionsStreamwrapper:<br> public: true<br> tags:<br> - { name: stream_wrapper, scheme: json-schema-definitions }</pre><p>So it seems just letting the StreamWrapper do its job is enough. However, at a few places in Canvas code, the stream wrapper is replaced/overriden by a direct call to <code>json_decode()</code>.</p>
<p>AdapterBase::resolveSchemaReferences():</p>
<pre> if (isset($schema['$ref'])) {<br> $schema = json_decode(file_get_contents($schema['$ref']) ?: '{}', TRUE);<br> }</pre><p>PropShape::resolveSchemaReferences():</p>
<pre> if (isset($schema['$ref'])) {<br> $schema = json_decode(file_get_contents($schema['$ref']) ?: '{}', TRUE);<br> }</pre><p>JsonSchemaFieldInstanceMatcher::resolveSchemaReferences():</p>
<pre> if (isset($schema['$ref'])) {<br> $schema = json_decode(file_get_contents($schema['$ref']) ?: '{}', TRUE);<br> }</pre><p>JsonSchemaObject::__construct():</p>
<pre> $schema = \json_decode(\file_get_contents($ref) ?: '{}', TRUE, \JSON_THROW_ON_ERROR);</pre><p>
and:</p>
<pre> if (\array_key_exists('$ref', $detail)) {<br> $prop_schema = \json_decode(\file_get_contents($detail['$ref']) ?: '{}', TRUE, \JSON_THROW_ON_ERROR);</pre><p>Maybe more...</p>
<p>This is error prone for Canvas and this is breaking <strong>all</strong> other uses of JSON Schema references in SDC. The ones from <a href="https://www.drupal.org/project/ui_patterns">UI Patterns</a>, the module I am maintaining, but also any other a Drupaler may use (including the ones defined outside the Drupal ecosystem).</p>
<h3 id="proposed-resolution">Proposed resolution</h3>
<p>My knowledge of Canvas is not deep enough to propose a fix for the root causes (see "Follow-up"), but let's try a quick "damage control" fix:</p>
<ol>
<li>Some little changes to not block other SDC plugin managers decorators (necessary to test concurrent reference resolvers).</li>
<li>Add a reference resolver in the component plugin manager (without resolving <code>json-schema-definitions://</code> until <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3515074" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3515074</a></span> is OK)</li>
<li>Limit <code> json_decode(file_get_contents($schema['$ref']) </code> execution to <code>json-schema-definitions://</code> URI, to prevent the break of non Canvas JSON Schema references. </li>
</ol>
<h3>Follow-ups</h3>
<p>To do once this first fix is merged:</p>
<ol>
<li>The root cause of "Don't block other SDC plugin managers decorators": It seems Canvas's ComponentPluginManager decorator is doing inheritance over composition, so it doesn't chain properly. Issue to be created.</li>
<li>The root cause of the JSON schema issue: Remove all <code>json_decode(file_get_contents($schema['$ref'])</code>. <code>JsonSchemaDefinitionsStreamwrapper</code> is already executing <code>file_get_contents</code> and all parts of the Canvas app must work on the resolved JSON schema returned by this service.</li>
<li>A related, but orthogonal, issue about JSON Schema references: <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3515074" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3515074</a></span></li>
</ol>
<p>Also, there are some weird stuff about JSON Schema management:</p>
<ul>
<li><code>Prop "XXX" is of type "object" without a $ref, which is not supported</code>: the schema retrieved from <code>$ref</code> will be merged with the existing schema, so it is common to never have a <code>type</code> property alongside a <code>$ref</code>.</li>
<li><code>Prop "XXX" is required, but does not have example value</code>: this seems to be the role of the <code>default</code> property, no the <code>examples</code> property</li>
<li><code>Component has no props schema</code>: "Canvas always requires schema, even for theme components" which don't fit with the real world where many components have only slots.</li>
<li>...</li>
</ul>
<h3 id="ui-changes">User interface changes</h3>
<p>None.</p>
> Related issue: [Issue #3352063](https://www.drupal.org/node/3352063)
issue