huma icon indicating copy to clipboard operation
huma copied to clipboard

OpenAPI: How to prefix all schemas?

Open leonklingele opened this issue 3 months ago • 1 comments

For some unfortunate legacy reason, we require all OpenAPI spec schemas to have a common prefix.

We currently use the following code to fixup the generated spec. The way we do that is probably quite fickle.
Is there any way to make this more robust, e.g. via some builtin functionality?

// Add custom prefix to all supported schemas.
{
	const prefix = "SomePrefix"

	handled := make(map[any]struct{})
	handleOnce := func(v any, fn func()) {
		if _, ok := handled[v]; !ok {
			handled[v] = struct{}{}
			fn()
		}
	}

	m := oapi.Components.Schemas.Map()
	replaceSchemas := make(map[string]string, len(m))
	for _, mk := range slices.Collect(maps.Keys(m)) {
		v := m[mk]
		handleOnce(v, func() {
			delete(m, mk)
			nk := prefix + mk
			replaceSchemas[mk] = nk
			m[nk] = v
		})
	}

	for _, v := range m {
		for _, p := range v.Properties {
			if p.Ref != "" {
				if suffix, found := strings.CutPrefix(p.Ref, "#/components/schemas/"); found {
					p.Ref = "#/components/schemas/" + replaceSchemas[suffix]
				}
			}

			if p.Items != nil {
				handleOnce(p.Items, func() {
					if suffix, found := strings.CutPrefix(p.Items.Ref, "#/components/schemas/"); found {
						p.Items.Ref = "#/components/schemas/" + replaceSchemas[suffix]
					}
				})
			}

			for ek, ev := range p.Examples {
				handleOnce(ev, func() {
					if evs, ok := ev.(string); ok {
						if !strings.HasSuffix(evs, ".json") {
							return
						}
						evs = strings.TrimSuffix(evs, ".json")
						ss := strings.Split(evs, "/")
						ss[len(ss)-1] = replaceSchemas[ss[len(ss)-1]] + ".json"
						p.Examples[ek] = strings.Join(ss, "/")
					}
				})
			}
		}
	}

	for _, p := range oapi.Paths {
		for _, op := range []*huma.Operation{
			p.Get,
			p.Put,
			p.Post,
			p.Delete,
			p.Options,
			p.Head,
			p.Patch,
			p.Trace,
		} {
			if op == nil {
				continue
			}

			contents := make([]*huma.MediaType, 0)

			if op.RequestBody != nil {
				for _, rc := range op.RequestBody.Content {
					contents = append(contents, rc)
				}
			}

			for _, r := range op.Responses {
				for _, rc := range r.Content {
					contents = append(contents, rc)
				}
			}

			for _, c := range contents {
				handleOnce(c.Schema, func() {
					if suffix, found := strings.CutPrefix(c.Schema.Ref, "#/components/schemas/"); found {
						c.Schema.Ref = "#/components/schemas/" + replaceSchemas[suffix]
					}
				})
				if c.Schema.Items != nil {
					handleOnce(c.Schema.Items, func() {
						if suffix, found := strings.CutPrefix(c.Schema.Items.Ref, "#/components/schemas/"); found {
							c.Schema.Items.Ref = "#/components/schemas/" + replaceSchemas[suffix]
						}
					})
				}
			}
		}
	}
}

leonklingele avatar Nov 17 '25 06:11 leonklingele

you could try something like this

	registry := huma.NewMapRegistry("#/components/schemas/", CustomSchemaNamer)
	humaConfig.OpenAPI.Components = &huma.Components{
		Schemas: registry,
	}


func CustomSchemaNamer(t reflect.Type, hint string) string {
	name := huma.DefaultSchemaNamer(t, hint)

	parts := strings.Split(t.PkgPath(), "/")
	if len(parts) > 1 {
		return parts[len(parts)-1] + "." + name
	}
	return name
}

sjayach avatar Dec 10 '25 15:12 sjayach