-
-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEATURE] Resolve $refs relative to the parsed file's directory by default #178
Comments
Welcome to AsyncAPI. Thanks a lot for reporting your first issue. Please check out our contributors guide and the instructions about a basic recommended setup useful for opening a pull request. |
@Laupetin |
@aeworxet I created an example for demonstration purposes, see #179 (comment) |
I have requested additional opinions on this subject in https://asyncapi.slack.com/archives/CQVJXFNQL/p1721883187801609 The exerpt: While the default behavior of
around which the So, based on this, I lean toward adding something like the
Does the addition of such an option make sense? Should it be doing what I described? Do you have other thoughts on this subject? |
I think there is a misunderstanding there 😅 The issue is that the bundler does not do that currently. For example if your project and working directory is located in
|
I'm sorry if me talking about about glob in the PR made things more confusing 😅 This issue was just meant for trying to change the behaviour to be resolved to "relative to the document". Before I didn't know it was defined in the standard. Since it is I guess this could also be considered a bug then instead of a "feature"? |
It is still not fully understandable whether you expect Please read https://asyncapi.slack.com/archives/CQVJXFNQL/p1716582167100109 and suggest whether it is the |
The
All other scripts like |
I'll compare how different tools resolve paths. |
Other tools never had to deal with the resolution of
{
base: 'index.yaml',
baseDir: 'docs/asyncapi',
traverseSubdirsFor: 'asyncapi.yaml',
} With these options specified, the
Without |
I am a bit confused about why you bring up a traverse subdirectories option 😅 The reason why it is not required is that the bundler resolves the $refs before bundling. I did already propose a solution to the issue with the linked pull request that makes sure any loaded asyncapi document uses its own folder when resolving $refs.
This is not what it does currently, it resolves $refs based on the current working directory by default, not based on the folder of the main yaml/json |
Your directory structure is potentially a new convention for structuring complex systems built on asynchronous APIs, where each operation is the responsibility of a separate team, up to having its own separate Git repository. For now, there is no convention that a bundling tool should look exactly for a file Should their naming repeat JSON Pointers' structure of the main AsyncAPI Document that describes the full system, so it could be parsed? Should there be a switch that lays down bundled YAMLs next to To me, the matter raised in this issue is not a Therefore, letting the current SIG group know about this idea just in case. However, if I was fixing this issue as a plain bug, I would use one loop utilizing the tools I already have, without introducing new ones, and it would still be doing the same thing as #179 Please, copy&paste this code into your `index.ts`import { readFileSync } from 'fs';
import path from 'path';
import { toJS, resolve, versionCheck, getSpecVersion } from './util';
import { Document } from './document';
import { parse } from './parser';
import type { AsyncAPIObject } from './spec-types';
// remember the directory where execution of the program started
const originDir = String(process.cwd());
/**
*
* @param {string | string[]} files One or more relative/absolute paths to
* AsyncAPI Documents that should be bundled.
* @param {Object} [options]
* @param {string} [options.base] One relative/absolute path to base object whose
* properties will be retained.
* @param {string} [options.baseDir] One relative/absolute path to directory
* relative to which paths to AsyncAPI Documents that should be bundled will be
* resolved.
* @param {boolean} [options.xOrigin] Pass `true` to generate properties
* `x-origin` that will contain historical values of dereferenced `$ref`s.
*
* @return {Document}
*
* @example
*
***TypeScript**
*```ts
*import { writeFileSync } from 'fs';
*import bundle from '@asyncapi/bundler';
*
*async function main() {
* const document = await bundle(['social-media/comments-service/main.yaml'], {
* baseDir: 'example-data',
* xOrigin: true,
* });
*
* console.log(document.yml()); // the complete bundled AsyncAPI document
* writeFileSync('asyncapi.yaml', document.yml()); // the complete bundled AsyncAPI document
*}
*
*main().catch(e => console.error(e));
*```
*
***JavaScript CJS module system**
*```js
*'use strict';
*
*const { writeFileSync } = require('fs');
*const bundle = require('@asyncapi/bundler');
*
*async function main() {
* const document = await bundle(['social-media/comments-service/main.yaml'], {
* baseDir: 'example-data',
* xOrigin: true,
* });
* writeFileSync('asyncapi.yaml', document.yml());
*}
*
*main().catch(e => console.error(e));
*```
*
***JavaScript ESM module system**
*```js
*'use strict';
*
*import { writeFileSync } from 'fs';
*import bundle from '@asyncapi/bundler';
*
*async function main() {
* const document = await bundle(['social-media/comments-service/main.yaml'], {
* baseDir: 'example-data',
* xOrigin: true,
* });
* writeFileSync('asyncapi.yaml', document.yml());
*}
*
*main().catch(e => console.error(e));
*```
*
*/
export default async function bundle(
files: string[] | string,
options: any = {}
) {
// if one string was passed, convert it to an array
if (typeof files === 'string') {
files = Array.from(files.split(' '));
}
if (options.baseDir && typeof options.baseDir === 'string') {
process.chdir(path.resolve(originDir, options.baseDir));
} else if (options.baseDir && Array.isArray(options.baseDir)) {
process.chdir(path.resolve(originDir, String(options.baseDir[0]))); // guard against passing an array
}
// -------- CHANGED CODE --------
const readFiles: AsyncAPIObject[] = []
for (const file of files) {
const prevDir = process.cwd();
let filePath: any = file.split(path.sep)
filePath.pop()
filePath = filePath.join(path.sep)
let readFile: any = readFileSync(file, 'utf-8')
readFile = toJS(readFile)
if (filePath) {
process.chdir(filePath);
}
readFile = await parse(readFile, getSpecVersion(readFile), options)
readFiles.push(readFile)
if (prevDir) {
process.chdir(prevDir);
}
}
// -------- CHANGED CODE --------
const parsedJsons = readFiles.map(file => toJS(file)) as AsyncAPIObject[];
const majorVersion = versionCheck(parsedJsons);
if (typeof options.base !== 'undefined') {
if (typeof options.base === 'string') {
options.base = readFileSync(options.base, 'utf-8'); // eslint-disable-line
} else if (Array.isArray(options.base)) {
options.base = readFileSync(String(options.base[0]), 'utf-8'); // eslint-disable-line
}
options.base = toJS(options.base);
await parse(options.base, majorVersion, options);
}
const resolvedJsons: AsyncAPIObject[] = await resolve(
parsedJsons,
majorVersion,
options
);
// return to the starting directory before finishing the execution
if (options.baseDir) {
process.chdir(originDir);
}
return new Document(resolvedJsons, options.base);
}
// 'module.exports' is added to maintain backward compatibility with Node.js
// projects, that use CJS module system.
module.exports = bundle; |
This functionality is implemented in |
This functionality is added in |
Why do we need this improvement?
Resolving relative paths from asyncapi documents located in different folders is currently not possible in a way that would keep the scope of the single files locally.
For example i am trying to destructure our asyncapi spec into smaller documents to reduce complexity.
To do this i tried to use the following folder structure:
index.yaml
would be the base file and our different operations would be split into separate files.Now when trying to bundle this into a single asyncapi spec i am running into a problem when trying to reference
schema.yaml
from anyasyncapi.yaml
via./schema.yaml
.By default it will take the current working directory which is try to resolve it to
docs/schema.yaml
.With the help of the
--baseDir
arg I would only be able to set the baseDir todocs
.I would still have to reference
schema.yaml
via a path that is not relative to the files that define each operation (event-zero, event-one):receive/event-zero/schema.yaml
.While that works, it is easy to make a mistake when creating a new operation and it also makes it impossible to validate each document separately, since validating always assumes $refs relative to the document's directory.
How will this change help?
asyncapi generate fromTemplate
parsing expects the paths to be relative to the document without the possibility to specify a "baseDir"Screenshots
No response
How could it be implemented/designed?
Change directory to the folder of each asyncapi document when dereferencing any $refs, unless a
--baseDir
option is specified (to not break the functionality of it).Alternatively it is also possible to implement this in a non-breaking way in which it would only apply when a new option like
--path-relative-to-document
is specified in case a breaking change by changing default behaviour is undesired.🚧 Breaking changes
Yes
👀 Have you checked for similar open issues?
🏢 Have you read the Contributing Guidelines?
Are you willing to work on this issue?
Yes I am willing to submit a PR!
The text was updated successfully, but these errors were encountered: