Skip to content
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

[Question] How to keep components.schemas when bundling openapi spec from multiple files? #294

Open
rucciva opened this issue May 21, 2024 · 3 comments
Labels
enhancement New feature or request

Comments

@rucciva
Copy link

rucciva commented May 21, 2024

Hello!

First of all, thanks a lot for creating this library. it's awesome.

Currently i'm trying to follow https://pb33f.io/libopenapi/bundling/. It works when combining multiple files into single file, but i noticed that all components.schemas no longer exists and all references are inlined.

Is it possible to bundle multiple files into single file without inlining all references?

My use case is i plan to generate go code from the bundled single file using oapi-codegen but would like to avoid inline struct definition inside another struct, which happens when a schema contains object inside object as a side effect of inlining all references.

@daveshanley
Copy link
Member

Hi,

Thanks for the compliments.

I have now had for this kind of a feature upgrade for the bundler a few times. Right now it simply inlines every single reference, regardless if they are local or external (because in reality, there is no concept, there is only a URI to a resource).

However the desire to suck in external references and re-compose the document to inline those references only, seems to be a popular one. Here is the same request in vacuum.

daveshanley/vacuum#421

So the answer is no, it does not support that.... yet.

@daveshanley daveshanley added the enhancement New feature or request label May 21, 2024
@rucciva
Copy link
Author

rucciva commented May 21, 2024

Got it @daveshanley , thanks a lot for the answer.

@rucciva rucciva closed this as completed May 21, 2024
@rucciva
Copy link
Author

rucciva commented May 25, 2024

hi @daveshanley , i was curious and i've been playing with the Bundle function. i managed somehow to achieve what i wanted though not yet tested with enough specs.

i modified the function to be like this

func bundle(model *v3.Document, inline bool) ([]byte, error) {
	model.Components = &v3.Components{
		Schemas: orderedmap.New[string, *base.SchemaProxy](),
	}

	compact := func(idx *index.SpecIndex, root bool) {
		sequencedReferences := idx.GetRawReferencesSequenced()
		mappedReferences := idx.GetMappedReferences()
		for _, sequenced := range sequencedReferences {
			// if we're in the root document, don't bundle anything.
			refExp := strings.Split(sequenced.FullDefinition, "#/")
			if len(refExp) == 2 {
				if refExp[0] == sequenced.Index.GetSpecAbsolutePath() || refExp[0] == "" {
					if root && !inline {
						idx.GetLogger().Debug("[bundler] skipping local root reference",
							"ref", sequenced.Definition)
						continue
					}
				}
			}

			mappedReference := mappedReferences[sequenced.FullDefinition]
			if mappedReference == nil {
				continue
			}
			if mappedReference.Circular {
				if idx.GetLogger() != nil {
					idx.GetLogger().Warn("[bundler] skipping circular reference",
						"ref", sequenced.FullDefinition)
				}
				continue
			}

			ref := ""
			switch {
			case strings.HasPrefix(sequenced.Definition, "#/components/schemas"):
				ref = "#/components/schemas/" + sequenced.Name
				schema := &baselow.Schema{}
				schema.Build(context.Background(), sequenced.Node, sequenced.Index)
				model.Components.Schemas.Set(sequenced.Name, base.CreateSchemaProxy(base.NewSchema(schema)))
			}
			if ref == "" {
				continue
			}

			sequenced.Node.Content = base.CreateSchemaProxyRef(ref).GetReferenceNode().Content
		}
	}

	rolodex := model.Rolodex
	indexes := rolodex.GetIndexes()
	for _, idx := range indexes {
		compact(idx, false)
	}
	compact(rolodex.GetRootIndex(), true)

	// copy components into root node in case new references need to be resolved, e.g. reference inside `allOf`
	components, err := toYamlNode("components", *model.Components)
	if err != nil {
		return nil, fmt.Errorf("fail to convert components into `*node.Yaml`: %w", err)
	}
	for _, idx := range append(indexes, rolodex.GetRootIndex()) {
		idx.GetRootNode().Content = components.Content
	}

	return model.Render()
}

func toYamlNode(key string, v interface{}) (n *yaml.Node, err error) {
	b, err := yaml.Marshal(map[string]interface{}{
		key: v,
	})
	if err != nil {
		return nil, err
	}
	y := yaml.Node{}
	return &y, yaml.Unmarshal(b, &y)
}

so far it works with limitation that:

  1. the reference name is unique (although i think adding prefix or postfix is possible to make it globally unique)
  2. i need to structure the yaml of non-root file in a way that i now what kind of reference it is (schema, response, etc). for example if it schema then it needs to be under the following yaml node:
    components: 
        schemas: 
            ....
  3. when i do the following modification inside any children of PathItem inside non-root file, i encountered unable to locate reference anywhere in the rolodex
    from:
    schema:
        $ref : "..."
    to:
    schema:
        allOf|anyOf:
            - $ref : "..."

my question is regarding no. 2. Can we somehow figure out whether a Reference is schema or response or any other kind? i know that the Reference has ParentNodeSchemaType but it somehow always hold empty string

p.s., the custom bundler is available in https://github.com/TelkomIndonesia/oapik?tab=readme-ov-file#bundle

@rucciva rucciva reopened this May 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants