Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Resolving nested references #125

Closed
CsatariGergely opened this issue Oct 9, 2017 · 10 comments
Closed

Resolving nested references #125

CsatariGergely opened this issue Oct 9, 2017 · 10 comments

Comments

@CsatariGergely
Copy link

Hi,
I would like to use nested references, when a reference contains an other reference. The aim to do this is to collect all external OpenAPI definitions into the main OpenAPI file, but not to inline the definitions.
I have an OpenAPI file a.yaml, like:

{
  "swagger": "2.0",
  "info": {
    "version": "2",
    "title": "a"
  },
  "basePath": "/a/v1",
  "schemes": [
    "https"
  ],
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "paths": {
    "/a": {
      "post": {
        "description": "A.",
        "parameters": [
          {
            "name": "A",
            "description": "A",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "definitions/definitions.yaml#/definitions/A"
            }
          }
        ]
      }
    }
  },
  "definitions": {
    "A": {
      "type": "object",
      "required": [
        "Aa"
      ],
      "properties": null,
      "Aa": {
        "type": "string"
      },
      "Ab": {
        "description": "Ab\n",
        "type": "string"
      },
      "Ac": {
        "description": "Ac\n",
        "type": "string"
      }
    }
  }
}

and the definitions are in an external file ./definitions/definitions.yaml:

definitions:
    A:
        type: object
        required:
        - Aa
        properties:
        Aa:
            description: >
                Aa
            $ref: "#/definitions/Aa"
        Ab:
            description: >
                Ab
            type: string
        Ac:
            description: >
                Ac
            type: string

    Aa:
        type: string 

After json-refs-ing I get:

{
  "swagger": "2.0",
  "info": {
    "version": "2",
    "title": "a"
  },
  "basePath": "/a/v1",
  "schemes": [
    "https"
  ],
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "paths": {
    "/a": {
      "post": {
        "description": "A.",
        "parameters": [
          {
            "name": "A",
            "description": "A",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "definitions/definitions.yaml#/definitions/A"
            }
          }
        ]
      }
    }
  },
  "definitions": {
    "A": {
      "type": "object",
      "required": [
        "Aa"
      ],
      "properties": null,
      "Aa": {
        "type": "string"
      },
      "Ab": {
        "description": "Ab\n",
        "type": "string"
      },
      "Ac": {
        "description": "Ac\n",
        "type": "string"
      }
    }
  }
}

While my target would be:

{
  "swagger": "2.0",
  "info": {
    "version": "2",
    "title": "a"
  },
  "basePath": "/a/v1",
  "schemes": [
    "https"
  ],
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "paths": {
    "/a": {
      "post": {
        "description": "A.",
        "parameters": [
          {
            "name": "A",
            "description": "A",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/A"
            }
          }
        ]
      }
    }
  },
  "definitions": {
    "A": {
      "type": "object",
      "required": [
        "Aa"
      ],
      "properties": null,
      "Aa": {
        "type": "string"
      },
      "Ab": {
        "description": "Ab\n",
        "type": "string"
      },
      "Ac": {
        "description": "Ac\n",
        "type": "string"
      }
    }
  }
}

I'm open to any solution ideas.
Any ideas if this is a new feature or a bug in json-refs?

Thanks

@whitlockjc
Copy link
Owner

Things are working as designed and is documented here: https://github.com/whitlockjc/json-refs/tree/master/docs#resolution When it comes to resolution, you can do remote-only resolution but anytime you reference a remote document, or a portion of the document outside of options.subDocPath, these references will always be resolved completely. The reason for this is that if you do not do this, you can end up with a partially resolved document.

If after using json-refs you have unresolved references, like you do at ['paths', '/a', 'post', 'parameters', '0', 'schema'], that means there was an error resolving your references. To identify what the resolution issue was, when you use JsonRefs.resolveRefs, you will get resolution metadata and any errors will be listed there. I'll try to reproduce locally and share what I find.

@whitlockjc
Copy link
Owner

whitlockjc commented Oct 9, 2017

When I tried to reproduce your issue, I get a complete resolution as expected:

{
  "swagger": "2.0",
  "info": {
    "version": "2",
    "title": "a"
  },
  "basePath": "/a/v1",
  "schemes": [
    "https"
  ],
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "paths": {
    "/a": {
      "post": {
        "description": "A.",
        "parameters": [
          {
            "name": "A",
            "description": "A",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "Aa"
              ],
              "properties": null,
              "Aa": {
                "type": "string"
              },
              "Ab": {
                "description": "Ab\n",
                "type": "string"
              },
              "Ac": {
                "description": "Ac\n",
                "type": "string"
              }
            }
          }
        ]
      }
    }
  },
  "definitions": {
    "A": {
      "type": "object",
      "required": [
        "Aa"
      ],
      "properties": null,
      "Aa": {
        "type": "string"
      },
      "Ab": {
        "description": "Ab\n",
        "type": "string"
      },
      "Ac": {
        "description": "Ac\n",
        "type": "string"
      }
    }
  }
}

@CsatariGergely
Copy link
Author

Thanks for the answer. It is interesting that you had a different result than me. I use json-refs via the CLI. I added my example files and the result here: json-refs-example.zip
Is there a way to know what options.subDocPath is used and what is the metadata of my resolution?
What I would like to achieve is to get the definitions of my OpenAPI into the same file, but still referring to them. This is becouse these definitions are used in a lots of places and resolving them inplace creates very big files.

@whitlockjc
Copy link
Owner

What version of json-refs are you using?

@CsatariGergely
Copy link
Author

I use version 3.0.0.

@whitlockjc
Copy link
Owner

Yes, you can view the resolution metadata. Basically, JsonRefs#resolveRefs returns a Promise that itself returns a ResolvedRefsResults object which contains a refs property containing each unique reference identified and the result of the resolution.

As for the objects being referenced a lot and creating large files, I get it. The good news is that in memory, json-refs is extremely efficient and will only resolve references once, no matter the lineage to the reference. And since JavaScript treats array/object references as cheap,a s in not copied, json-refs handles complex documents with tons of references. Where things are not ideal is when you stringify the resolved document and this is due to the nature of JSON representation. We could potentially update json-refs to resolve only remote references without having to resolve all nested references completely but this isn't a simple task. It would require us turning local references in remote documents into local references in the resulting document by merging these referenced objects into the resolved document. I thought about this once and my brain started hurting. I'm not saying it's impossible, it's just not something I wanted to tackle when rewriting the resolver for 3.0.0. Maybe it's time to revisit it?

Lastly, I'll look into your example and report what I find.

@whitlockjc
Copy link
Owner

I think this is a bug. It looks like the local reference is being resolved prior to the remote reference and that shouldn't happen. I'll see if I can reproduce this in a simpler test and go from there.

@whitlockjc
Copy link
Owner

This has been fixed and is resolved in json-refs@3.0.2.

@CsatariGergely
Copy link
Author

Thanks for the fix.

A possible workaround would be when json-refs would not resolve the local references only the remote ones. In this way several occurences could point to one local reference which would point to the remote one?

@whitlockjc
Copy link
Owner

The issue is more about how we're updating values as part of resolution. If I update #/A to point to #/B, so long as I only modify the content of #/B and do not replace #/B in its entirety, things will work fine. So if #/B contains references within it, things will work fine. It's only when #/B itself is a reference, so resolving it replaces the complete content of #/B, that this is an issue. In all honesty, we probably could had reproduced this with local references only.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants