PageSourceSearch

https://asyncapi-website.netlify.app/_next/static/chunks/pages/blo…e-reference-rabbit-hole-2fe56e45bd2ae5dd.js

js asyncapi-website.netlify.app collected 2026-10-03 10:32:46 UTC 26,701 bytes, 1 lines download raw bytes

1(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[96884],{19937:(e,s,n)=>{"use strict";n.d(s,{R:()=>i});var t=n(14232),a=n(4047);function i(e){let s=(0,t.useId)();return{...(0,a.o)(s),...e}}},26022:(e,s,n)=>{"use strict";n.r(s),n.d(s,{default:()=>r});var t=n(37876),a=n(19937);function i(e){let s={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",ul:"ul",...(0,a.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)(s.p,{children:[(0,t.jsx)(s.a,{href:"https://github.com/smoya",children:"Sergio"})," and I went down a bit of a rabbit hole the last couple of days while discussing ",(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/spec/issues/618",children:"Fran's proposal to solve the publish/subscribe confusion"}),"; I thought I would share the journey."]}),"\n",(0,t.jsx)(s.p,{children:"A lot of this can be seen as nitpicking... And I totally get this, as we need to venture deep into the specifications to fully understand the differences."}),"\n",(0,t.jsx)(s.p,{children:"I'm going to try to not use any complex words and explanations so that everyone can understand the problems, whether you're a novice or an experienced AsyncAPI user."}),"\n",(0,t.jsx)(s.p,{children:"So let's split up the understanding of what references are, where references can be used, and what's down this rabbit hole."}),"\n",(0,t.jsx)(s.h1,{id:"asyncapi-references",children:"AsyncAPI references"}),"\n",(0,t.jsxs)(s.p,{children:["In AsyncAPI, we have something called a ",(0,t.jsx)(s.a,{href:"https://www.asyncapi.com/docs/specifications/v2.2.0#referenceObject",children:"Reference Object"}),", which simply enables reusability in your AsyncAPI documents. This is possible through the simple keyword ",(0,t.jsx)(s.code,{children:"$ref"}),". If we take a look at the ",(0,t.jsx)(s.a,{href:"https://www.asyncapi.com/docs/tutorials/streetlights",children:"streetlight tutorial"}),", to utilize reusability, we could change ",(0,t.jsx)(s.a,{href:"https://www.asyncapi.com/docs/tutorials/streetlights#creating-the-asyncapi-file",children:"the document"})," to:"]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-yaml",children:"asyncapi: '2.2.0'\n...\nchannels:\n  light/measured:\n    publish:\n      summary: Inform about environmental lighting conditions for a particular streetlight.\n      operationId: onLightMeasured\n      message:\n        $ref: '#/components/messages/LightMeasured'\ncomponents:\n  messages:\n    LightMeasured: \n      name: LightMeasured\n      payload:\n        $ref: '#/components/schemas/LightMeasurement'\n  schemas:\n    LightMeasurement:\n      # Ignore the specifics here for now.\n"})}),"\n",(0,t.jsx)(s.p,{children:"Here you can see that we simply reference where the definition of messages and payload schema is located."}),"\n",(0,t.jsx)(s.h1,{id:"schema-object-references",children:"Schema Object references"}),"\n",(0,t.jsxs)(s.p,{children:["As seen in the streetlight example, to define your message payloads in AsyncAPI, we use a ",(0,t.jsx)(s.a,{href:"https://www.asyncapi.com/docs/specifications/v2.2.0#schemaObject",children:"Schema Object"}),", which is a superset of ",(0,t.jsx)(s.a,{href:"https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-01",children:"JSON Schema draft 7"}),"."]}),"\n",(0,t.jsxs)(s.p,{children:["What ",(0,t.jsx)(s.code,{children:"superset"})," means is we follow the JSON Schema draft 7 specification, but with a few modifications and additions to keywords."]}),"\n",(0,t.jsxs)(s.p,{children:["The message ",(0,t.jsx)(s.code,{children:"LightMeasured"}),", contains a keyword called ",(0,t.jsx)(s.code,{children:"payload"}),", which is by default defined as a ",(0,t.jsx)(s.strong,{children:"Schema Object"}),"."]}),"\n",(0,t.jsxs)(s.p,{children:["This is where the confusion starts, what behavior does the ",(0,t.jsx)(s.code,{children:"$ref"})," keyword follow? More precisely, which specification?"]}),"\n",(0,t.jsx)(s.h1,{id:"the-confusion-creeps-in",children:"The confusion creeps in"}),"\n",(0,t.jsxs)(s.p,{children:["Let's take a closer look at the ",(0,t.jsx)(s.a,{href:"https://www.asyncapi.com/docs/specifications/v2.2.0#schemaObject",children:"Schema Object"})," to see if we can figure out the answer."]}
1),"\n",(0,t.jsxs)(s.blockquote,{children:["\n",(0,t.jsx)(s.p,{children:"Further information about the properties can be found in JSON Schema Core and JSON Schema Validation. Unless stated otherwise, the property definitions follow the JSON Schema specification as referenced here."}),"\n"]}),"\n",(0,t.jsxs)(s.p,{children:["So what this means is that unless stated otherwise in the ",(0,t.jsx)(s.strong,{children:"Schema Object"}),", it should follow the official JSON Schema draft 7 specification. So let's try to read further, to see if anything is stated about references."]}),"\n",(0,t.jsxs)(s.blockquote,{children:["\n",(0,t.jsxs)(s.p,{children:["Alternatively, any time a Schema Object can be used, a ",(0,t.jsx)(s.strong,{children:"Reference Object"})," can be used in its place. This allows referencing definitions in place of defining them inline."]}),"\n"]}),"\n",(0,t.jsxs)(s.p,{children:["Okay... So that must mean that if we ever encounter a reference, we follow the ",(0,t.jsx)(s.strong,{children:"Reference Object"})," description."]}),"\n",(0,t.jsx)(s.p,{children:"Well, that was easy; I see no rabbit hole here, Jonas!?"}),"\n",(0,t.jsx)(s.h1,{id:"welcome-to-the-rabbit-hole",children:"Welcome to the rabbit hole"}),"\n",(0,t.jsxs)(s.p,{children:["During the discussion, Sergio brought up that Fran was using an illegal reference, as he, in one of the examples, was using a ",(0,t.jsx)(s.strong,{children:"Reference Object"})," for a server, which was not allowed.  More specifically, it was this example where he references the ",(0,t.jsx)(s.code,{children:"mosquitto"})," server:"]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-yaml",children:"...\nservers:\n  mosquitto:\n    $ref: 'common.asyncapi.yaml#/components/servers/mosquitto'\n"})}),"\n",(0,t.jsx)(s.p,{children:'My immediate reaction was "wait... It\'s not?!"'}),"\n",(0,t.jsxs)(s.p,{children:["I had always used ",(0,t.jsx)(s.code,{children:"$ref"})," quite extensively in my AsyncAPI documents and specifically used a reference for servers. And I knew that the tooling had no problems with the ",(0,t.jsx)(s.code,{children:"$ref"})," as long as it was a valid reference."]}),"\n",(0,t.jsxs)(s.p,{children:["But Sergio was absolutely right; a second look at the specification showed that ",(0,t.jsx)(s.code,{children:"servers"})," are defined using the ",(0,t.jsx)(s.a,{href:"https://www.asyncapi.com/docs/specifications/v2.2.0#serversObject",children:"Servers Object"}),", which is defined by using a map of ",(0,t.jsx)(s.a,{href:"https://www.asyncapi.com/docs/specifications/v2.2.0#serverObject",children:"Server Object"}),"s. ",(0,t.jsx)(s.strong,{children:"NOT"})," ",(0,t.jsx)(s.code,{children:"Server Object | Reference Object"})," as I expected."]}),"\n",(0,t.jsxs)(s.p,{children:["After that, we started to realize that there is quite a big difference between when and where ",(0,t.jsx)(s.strong,{children:"Reference Object"}),"s are allowed. For the full list of discrepancies, check out ",(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/spec/issues/650",children:"spec #650"}),"."]}),"\n",(0,t.jsx)(s.p,{children:"But... Why did I think it was allowed to do so?"}),"\n",(0,t.jsx)(s.h2,{id:"discrepancies-in-asyncapi-tooling",children:"Discrepancies in AsyncAPI Tooling"}),"\n",(0,t.jsxs)(s.p,{children:["So back to my own experience, why was I so sure that the tooling allowed for me to use ",(0,t.jsx)(s.strong,{children:"Reference Object"}),"s for servers?"]}),"\n",(0,t.jsxs)(s.p,{children:["Well, as it turns out, it's because the ",(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/parser-js",children:"JS parser"})," dereferences before it validates the AsyncAPI document. This means that if I defined my AsyncAPI document as follows:"]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-yaml",children:"asyncapi: '2.2.0'\n...\nservers:\n  test-server:\n    $ref: './servers/testServer.yaml'\n...\n"})}),"\n",(0,t.jsxs)(s.p,{children:["Together with ",(0,t.jsx)(s.code,{children:"testServer.yaml"}),":"]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-yaml",children:"url: ws://mycompany.com/ws\nprotocol: ws\n"})}),"\n",(0,t.jsxs)(s.p,{children:["Validating the AsyncAPI document using a tool such as ",(0,t.jsx)(s.a,{href:"https://ajv.js.org/",children:"ajv"})," against the ",(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/spec-json-schemas/blob/master/schemas/2.2.0.json",children:"JSON Schema representation for 2.2.0"}),", it would reject it."]}),"\n",(0,t.jsx)(s.p,{children:"However, because the parser dereferences first, the document that is being validated is this:"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-yaml",children:"asyncapi: '2.2.0'\n...\nservers:\n  test-server:\n    url: ws://mycompany.com/ws\n    protocol: ws\n...\n"})}),"\n",(0,t.jsxs)(s.p,{children:["Checkout ",(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/parser-js/issues/405",children:"parser-js #405"})," for more information."]}),"\n",(0,t.jsxs)(s.h2,{id:"what-about-id-keyword",children:["What about ",(0,t.jsx)(s.code,{children:"$id"})," keyword"]}),"\n",(0,t.jsxs)(s.p,{children:["One of the key differences between our ",(0,t.jsx)(s.strong,{children:"Reference Object"}),", and how ",(0,t.jsx)(s.code,{children:"$ref"}
1)," is resolved in JSON Schema Draft 7, is the ",(0,t.jsx)(s.a,{href:"https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-01#section-8.2",children:"$id keyword"}),". This allows you to define a URI that is used as a base URI. This means that for example a message such as this:"]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-yaml",children:"asyncapi: '2.2.0'\n...\nchannels:\n  test/channel:\n    publish:\n      message:\n        schemaFormat: application/schema+json;version=draft-07\n        payload: \n          $id: https://example.com/schemas/test\n          type: object\n          properties:\n            address: \n              $ref: \"address\"\n...\n"})}),"\n",(0,t.jsxs)(s.p,{children:["This will result in the reference for the ",(0,t.jsx)(s.code,{children:"address"})," property, to be looked up at ",(0,t.jsx)(s.code,{children:"https://example.com/schemas/address"}),", because it uses the Base URI in ",(0,t.jsx)(s.code,{children:"$id"})," from the parent schema (",(0,t.jsx)(s.code,{children:"https://example.com/schemas"}),")."]}),"\n",(0,t.jsxs)(s.p,{children:["I tried a little test in the ",(0,t.jsx)(s.a,{href:"https://studio.asyncapi.com/",children:"new Studio tool"})," (Studio uses the parser, so it could be used for an easy test), ",(0,t.jsx)(s.a,{href:"https://studio.asyncapi.com/?base64=YXN5bmNhcGk6ICcyLjIuMCcKaW5mbzoKICB0aXRsZTogVGVzdCBvdmVycmlkaW5nIGRlcmVmZXJlbmNlZCBvYmplY3RzIAogIHZlcnNpb246ICcxLjAuMCcKY2hhbm5lbHM6CiAgdGVzdC9jaGFubmVsOgogICAgcHVibGlzaDoKICAgICAgbWVzc2FnZToKICAgICAgICBzY2hlbWFGb3JtYXQ6IGFwcGxpY2F0aW9uL3NjaGVtYStqc29uO3ZlcnNpb249ZHJhZnQtMDcKICAgICAgICBwYXlsb2FkOiAKICAgICAgICAgICRpZDogaHR0cHM6Ly9leGFtcGxlLmNvbS9zY2hlbWFzL3Rlc3QKICAgICAgICAgIHR5cGU6IG9iamVjdAogICAgICAgICAgcHJvcGVydGllczoKICAgICAgICAgICAgYWRkcmVzczogCiAgICAgICAgICAgICAgJHJlZjogImFkZHJlc3Mi",children:"which showed that this was not supported by the parser"}),". The library tries to resolve the reference at ",(0,t.jsx)(s.code,{children:"https:///address"})," when it should have tried to resolve it from ",(0,t.jsx)(s.code,{children:"http://example.com/schemas/address"}),". See ",(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/parser-js/issues/403",children:"parser-js #403"})," for more information."]}),"\n",(0,t.jsxs)(s.h2,{id:"what-about-schema",children:["What about ",(0,t.jsx)(s.code,{children:"$schema"}),"?"]}),"\n",(0,t.jsxs)(s.p,{children:["Before getting into ",(0,t.jsx)(s.code,{children:"$schema"})," I first need to mention a keyword in AsyncAPI called ",(0,t.jsx)(s.a,{href:"https://www.asyncapi.com/docs/specifications/v2.2.0#messageObject",children:"schemaFormat which is part of the Message Object"}),". What this keyword is used for is to change what format the payload is defined with. By defining it with ",(0,t.jsx)(s.code,{children:"application/vnd.aai.asyncapi+yaml;version=2.2.0"})," it is the same as the default format."]}),"\n",(0,t.jsxs)(s.p,{children:["In JSON Schema Draft 7, and in the ",(0,t.jsx)(s.strong,{children:"Schema Object"}),", there exists a keyword, similar to what ",(0,t.jsx)(s.code,{children:"schemaFormat"})," is for AsyncAPI, that can be used to define what version of JSON Schema ",(0,t.jsx)(s.code,{children:"LightMeasurement"})," follows."]}),"\n",(0,t.jsx)(s.p,{children:"So what if both are defined at the same time, and they contradict each other?"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-yaml",children:"asyncapi: '2.2.0'\n...\ncomponents:\n  messages:\n    LightMeasured: \n      name: LightMeasured\n      schemaFormat: application/vnd.aai.asyncapi+yaml;version=2.2.0\n      payload:\n        $ref: '#/components/schemas/LightMeasurement'\n  schemas:\n    LightMeasurement:\n      $schema: 'http://json-schema.org/draft-04/schema#'\n      ...\n"})}),"\n",(0,t.jsxs)(s.p,{children:["With such contradicting information, how should tooling handle this? This sparked ",(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/spec/issues/655",children:"spec #655"}),"."]}),"\n",(0,t.jsx)(s.h2,{id:"what-about-extra-keywords",children:"What about extra keywords?"}),"\n",(0,t.jsxs)(s.p,{children:["Following that, by taking a closer look at the ",(0,t.jsx)(s.a,{href:"https://datatracker.ietf.org/doc/html/draft-pbryan-zyp-json-ref-03",children:"JSON reference"})," specification the ",(0,t.jsx)(s.strong,{children:"Reference Object"})," follows, we find the ",(0,t.jsx)(s.a,{href:"https://datatracker.ietf.org/doc/html/draft-pbryan-zyp-json-ref-03#section-3",children:"sentence"}),":"]}),"\n",(0,t.jsxs)(s.blockquote,{children:["\n",(0,t.jsx)(s.p,{children:'Any members other than "$ref" in a JSON Reference object SHALL be ignored.'}),"\n"]}),"\n",(0,t.jsx)(s.p,{children:"What this means, is that if we have a reference defined such as:"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-yaml",children:"...\ncomponents:\n  messages:\n    LightMeasured:\n      payload:\n        type: boolean\n        $ref: '#/components/schemas/LightMeasurement'\n  schemas:\n    LightMeasurement:\n      type: string\n"})}),"\n",(0,t.jsxs)(s.p,{children:["The ",(0,t.jsx)(s.code,{children:"type"})," property for the message payload, should be completely ignored. So let's try and see what happens when we try this in ",(0,t.jsx)(s.a,{href:"https://studio.asyncapi.com/?base64=YXN5bmNhcGk6ICcyLjIuMCcKaW5mbzoKICB0aXRsZTogVGVzdCBvdmVycmlkaW5nIHByb3BlcnRpZXMgd2l0aCBkZXJlZmVyZW5jZWQgb2JqZWN0cyAKICB2ZXJzaW9uOiAnMS4wLjAnCmNoYW5uZWxzOgogIHRlc3Q6CiAgICBwdWJsaXNoOgogICAgICBtZXNzYWdlOgogICAgICAgICRyZWY6ICcjL2NvbXBvbmVudHMvbWVzc2FnZXMvTGlnaHRNZWFzdXJlbWVudCcKY29tcG9uZW50czoKICBtZXNzYWdlczoKICAgIExpZ2h0TWVhc3VyZW1lbnQ6IAogICAgICBuYW1lOiBMaWdodE1lYXN1cmVtZW50CiAgICAgI
1HBheWxvYWQ6CiAgICAgICAgdHlwZTogYm9vbGVhbgogICAgICAgICRyZWY6ICcjL2NvbXBvbmVudHMvc2NoZW1hcy9MaWdodE1lYXN1cmVtZW50JwogIHNjaGVtYXM6CiAgICBMaWdodE1lYXN1cmVtZW50OgogICAgICB0eXBlOiBzdHJpbmc=",children:"Studio"}),"."]}),"\n",(0,t.jsxs)(s.p,{children:["Once the schema is parsed, all that remains is ",(0,t.jsx)(s.code,{children:"type: boolean"}),", and not the expected ",(0,t.jsx)(s.code,{children:"type: string"})," from the referenced schema. This is clearly the opposite of what the specification defines. For more information see ",(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/parser-js/issues/404",children:"parser-js #404"}),"."]}),"\n",(0,t.jsxs)(s.p,{children:["We then asked ourselves, what about JSON Schema, does it define a different behavior? The answer to this question can be found ",(0,t.jsx)(s.a,{href:"https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-01#section-8.3",children:"here"}),":"]}),"\n",(0,t.jsxs)(s.blockquote,{children:["\n",(0,t.jsx)(s.p,{children:'All other properties in a "$ref" object MUST be ignored.'}),"\n"]}),"\n",(0,t.jsx)(s.p,{children:"Luckily, they both match the same behavior in terms of extra keywords. Both Reference Object and JSON Schema should ignore extra keywords."}),"\n",(0,t.jsx)(s.p,{children:"But, what if I use one of the newer JSON Schema versions, what then?"}),"\n",(0,t.jsx)(s.h2,{id:"upgrading-to-json-schema-draft-2020-12",children:"Upgrading to JSON Schema draft 2020-12"}),"\n",(0,t.jsxs)(s.p,{children:["We started to correlate the findings with the feature request from ",(0,t.jsx)(s.a,{href:"https://github.com/magicmatatjahu",children:"Maciej"})," about updating AsyncAPI Schema Object to point towards ",(0,t.jsx)(s.a,{href:"https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-00",children:"JSON Schema Draft 2020-12"}),"."]}),"\n",(0,t.jsxs)(s.p,{children:["What would this mean for our little ",(0,t.jsx)(s.code,{children:"$ref"})," keywords?"]}),"\n",(0,t.jsxs)(s.p,{children:["OpenAPI have in its most recent version 3.1, switched its default JSON Schema version to Draft 2020-12, the exact feature request for AsyncAPI. This, however, introduced a huge change to how you bundle references. I don't want to spend much time on this as ",(0,t.jsx)(s.a,{href:"https://twitter.com/relequestual",children:"Ben"})," and ",(0,t.jsx)(s.a,{href:"https://twitter.com/PermittedSoc",children:"Mike"})," described this entire change and what it means in terms of bundling in this great blog post ",(0,t.jsx)(s.a,{href:"https://json-schema.org/blog/posts/bundling-json-schema-compound-documents#bundling-simple-external-resources",children:"Bundling simple external resources"}),". Besides this, the release notes for Draft 2020-12 also offers some guidance which can be found here: ",(0,t.jsx)(s.a,{href:"https://json-schema.org/draft/2020-12/release-notes.html",children:"https://json-schema.org/draft/2020-12/release-notes.html"})]}),"\n",(0,t.jsxs)(s.p,{children:["Besides having a bunch of new keywords that change the referencing behavior, such as ",(0,t.jsx)(s.code,{children:"$dynamicRef"}),", ",(0,t.jsx)(s.code,{children:"$dynamicAnchor"}),", ",(0,t.jsx)(s.code,{children:"$anchor"}),", one of the key differences is that in ",(0,t.jsx)(s.a,{href:"https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-02",children:"JSON Schema draft 2019-09"}),", they changed their behavior of references so that extra keywords are now allowed adjacent to ",(0,t.jsx)(s.code,{children:"$ref"}),"."]}),"\n",(0,t.jsxs)(s.p,{children:["But what does this mean exactly? Does this mean ",(0,t.jsx)(s.code,{children:"$ref"})," overwrites any duplicated properties? Or is it the other way around?"]}),"\n",(0,t.jsx)(s.p,{children:"Well, there is one thing we need to remember about JSON Schema. It is primarily built for validation rules and how a validator can take input data and determine whether that input is valid against the Schema."}),"\n",(0,t.jsxs)(s.p,{children:["This means, that if you have a JSON Schema using ",(0,t.jsx)(s.code,{children:"$ref"})," such as:"]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-json",children:'{ "$ref": "./test.json", "minLength": 7, "maxLength": 12}\n'})}),"\n",(0,t.jsxs)(s.p,{children:["and ",(0,t.jsx)(s.code,{children:"test.json"})," is defined as:"]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-json",children:'{"minLength": 5, "format": "email"}\n'})}),"\n",(0,t.jsx)(s.p,{children:"JSON Schema draft 2019-09, assumes that the references are resolved similar to:"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-json",children:'{"$ref": {"minLength": 5, "format": "email"}, "minLength": 7, "maxLength": 12}\n'})}),"\n",(0,t.jsxs)(s.p,{children:["This is because in validation, you want to validate that the input data is valid against the referenced schema and should ",(0,t.jsxs)(s.a,{href:"https://github.com/APIDevTools/json-schema-ref-parser/issues/145",children:[(0,t.jsx)(s.strong,{children:"not"})," be seen as a kind of merging behavior"]}),":"]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-json",children:'{"format": "email", "minLength": 7, "maxLength": 12}\n'})}),"\n",(0,t.jsx)(s.p,{children:"This behavior is different from what is assumed when using AsyncAPI, as the last option, is more al
1igned with expected behavior."}),"\n",(0,t.jsxs)(s.p,{children:["Furthermore, now, each schema can define it's own ",(0,t.jsx)(s.code,{children:"$schema"})," that they follow, instead of ONLY being available at the root..."]}),"\n",(0,t.jsxs)(s.p,{children:["This leaves the question, how can we make sure that we stay consistent and don't introduce more confusion into the AsyncAPI specification? This difference is what triggered the last issue in ",(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/spec/issues/649",children:"spec 649"}),"."]}),"\n",(0,t.jsx)(s.h2,{id:"hard-to-find-tooling",children:"Hard to find tooling"}),"\n",(0,t.jsx)(s.p,{children:"This leaves us with one huge deficit, that there are so many different behaviors for references that tooling mix and matches between the specifications and what they solve."}),"\n",(0,t.jsxs)(s.p,{children:["One of the most used tooling for dereferencing stuff in JS, and the one we are using is from ",(0,t.jsx)(s.a,{href:"https://github.com/APIDevTools/json-schema-ref-parser",children:"APIDevTools called json-schema-ref-parser"}),". We actually use this tool to ensure ",(0,t.jsx)(s.strong,{children:"ANY"})," encounters of ",(0,t.jsx)(s.code,{children:"$ref"})," are dereferenced, so the tool has direct access to the schema, without it having to look elsewhere for it."]}),"\n",(0,t.jsxs)(s.p,{children:["However, the tool started out being built ",(0,t.jsx)(s.strong,{children:"ONLY"})," for dereferencing ",(0,t.jsx)(s.code,{children:"$ref"})," based on the ",(0,t.jsx)(s.a,{href:"https://github.com/APIDevTools/json-schema-ref-parser/issues/22#issuecomment-231783185",children:"JSON Reference specification and the JSON Pointer specification"}),".  At least it was, now it's not easy to figure out what it is for, as ",(0,t.jsx)(s.a,{href:"https://github.com/APIDevTools/json-schema-ref-parser/issues/232",children:"it allows extra properties"})," but ",(0,t.jsx)(s.a,{href:"https://github.com/APIDevTools/json-schema-ref-parser/issues/136",children:"$id is not taken into account"}),"."]}),"\n",(0,t.jsxs)(s.p,{children:["This leaves us in a bit of a struggle, as ",(0,t.jsx)(s.a,{href:"https://json-schema.org/implementations.html#general-processing",children:"there are not many alternatives"}),"; JS ",(0,t.jsx)(s.a,{href:"https://github.com/jdesrosiers/json-schema-core",children:"@hyperjump/json-schema-core"})," looks promising, but there's no tooling that our ",(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/parser-go",children:"Go parser"})," can use."]}),"\n",(0,t.jsxs)(s.p,{children:["And with no official or community tooling, we are left with having to develop it ourselves to adopt the spec... There are luckily efforts being made in ",(0,t.jsx)(s.a,{href:"https://github.com/json-schema-org/community/discussions/113",children:"JSON Schema to adopt to such a change"}),"."]}),"\n",(0,t.jsx)(s.h1,{id:"final-word",children:"Final word"}),"\n",(0,t.jsxs)(s.p,{children:["That concludes the rabbit hole that Sergio and I went down, for a simple ",(0,t.jsx)(s.code,{children:"$ref"})," keyword... (ONE KEYWORD! ",(0,t.jsx)(s.span,{role:"img","aria-label":"grinning face with sweat",children:"\uD83D\uDE05"}),")"]}),"\n",(0,t.jsx)(s.p,{children:"If you have any comments or issues with what was described here, please go into the respective issues and make a comment - also if you think we are wrong!"}),"\n",(0,t.jsx)(s.p,{children:"In case you are interested, we are also looking for contributors, to help us solve these issues. If you want to take one up, just write a comment in the respective issue."}),"\n",(0,t.jsx)(s.p,{children:"Overview of issues:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/spec/issues/650",children:"spec #650"}),", highlights the discrepancies when the Reference Object can be used."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/spec/issues/649",children:"spec #649"}),", tries to solve the core issue that ",(0,t.jsx)(s.code,{children:"$ref"})," means two different things, depending on when it's used."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/spec/issues/655",children:"spec #655"}),", what do you do when encountering ",(0,t.jsx)(s.code,{children:"$schema"}
1)," and Message Object ",(0,t.jsx)(s.code,{children:"schemaFormat"}),", especially when they are contradicting."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/parser-js/issues/405",children:"parser-js #405"}),", highlights that the parser accurately validates incorrect AsyncAPI documents, because it bundles references before validating."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/parser-js/issues/404",children:"parser-js #404"}),", highlights that the parser allows for keywords to be defined together with ",(0,t.jsx)(s.code,{children:"$ref"})," and are not being ignored."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"https://github.com/asyncapi/parser-js/issues/403",children:"parser-js #403"}),", highlights that the parser does not care about ",(0,t.jsx)(s.code,{children:"$id"})," in the Schema Object when it should."]}),"\n"]}),"\n",(0,t.jsxs)(s.blockquote,{children:["\n",(0,t.jsxs)(s.p,{children:["Photo by ",(0,t.jsx)("a",{href:"https://unsplash.com/@nxvision?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText",children:"Nigel Tadyanehondo"})," on ",(0,t.jsx)("a",{href:"https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText",children:"Unsplash"})]}),"\n"]})]})}function r(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},{wrapper:s}={...(0,a.R)(),...e.components};return s?(0,t.jsx)(s,{...e,children:(0,t.jsx)(i,{...e})}):i(e)}},61016:(e,s,n)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/blog/the-reference-rabbit-hole",function(){return n(26022)}])}},e=>{e.O(0,[56166,90096,6729,9583,51550,4047,90636,46593,38792],()=>e(e.s=61016)),_N_E=e.O()}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.