Comments (10)
Wow this is really nice :)
i had to change the code a bit to avoid nil pointer panic
func opsPath(in map[string]*huma.PathItem) (r []*huma.Operation) {
r = make([]*huma.Operation, 0)
for _, v := range in {
if v.Get != nil {
r = append(r, v.Get)
}
if v.Post != nil {
r = append(r, v.Post)
}
if v.Put != nil {
r = append(r, v.Put)
}
if v.Patch != nil {
r = append(r, v.Patch)
}
if v.Delete != nil {
r = append(r, v.Delete)
}
}
return
}
// replaceDocs crawls the component schemas and replaces relative links in the
// description with the contents of the file from the embedded FS.
func replaceDocs(doc *huma.OpenAPI) {
for _, op := range opsPath(doc.Paths) {
for _, param := range op.Parameters {
checkSchema(param.Schema)
}
}
for _, schema := range doc.Components.Schemas.Map() {
checkSchema(schema)
}
}
from huma.
@jonathanyuechun unfortunately the Go compiler strips comments, so I can't access these at runtime. This could be accomplished by analyzing the Go source code and generating additional code, but that introduces extra steps. You could go in after the fact and update the generated huma.Schema
object in OpenAPI.Components.Schemas
with extra docs, but that's not ideal either.
I'm open to ideas on this one and will think about ways this could be done.
FYI it is possible to use files/variables for doc/description of the OpenAPI itself and operations, just not struct fields.
from huma.
I see.
How about for a starter, providing an optional interface to implement/or a function that can enrich the schema by taking as input a ref to the struct we want to enrich , a huma schema
and the generated openapi obj that only overrides json schema fields provided by the end user, like a merge from user input on top of the default schema generator?
Another 3 banger features would also be the ability to
- add arbitrary schemas (dynamic) at runtime
- that can be referenced in description/doc section.
- Validate schema outside of http request in two ways
- a ref t a struct and the json to validate
- a json schema as string/byte and the json datq to validate
from huma.
I thought about this some more and I think what you want to do is actually possible today in Huma with a couple small utility functions since you have full access to the generated OpenAPI before service startup. Here's a quick example:
- Create some markdown files in a
docs
directory, e.g.docs/message.md
. - Use Go's built-in
embed
package and//go:embed docs/*
to bundle docs into the executable (this makes deployment easier and would be optional). - Use a sentinel starting value e.g.
@
in the docs field tag to denote you want to load from a file, likedoc:"@docs/message.md"
- Create a recursive utility to crawl the params and schemas, replacing
@file...
in schema descriptions with the contents of the file. Call this from yourmain
func after registering all the operations.
This modified hello world example works for me if I create a docs
directory and put some markdown files in it:
package main
import (
"context"
"embed"
"fmt"
"net/http"
"strings"
"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/adapters/humachi"
"github.com/go-chi/chi/v5"
)
//go:embed docs/*
var docs embed.FS
// Options for the CLI.
type Options struct {
Port int `help:"Port to listen on" short:"p" default:"8888"`
}
// GreetingInput represents the greeting operation request.
type GreetingInput struct {
Name string `path:"name" maxLength:"30" example:"world" doc:"@docs/name.md"`
}
// GreetingOutput represents the greeting operation response.
type GreetingOutput struct {
Body struct {
Message string `json:"message" example:"Hello, world!" doc:"@docs/message.md"`
}
}
func checkSchema(s *huma.Schema) {
if strings.HasPrefix(s.Description, "@") {
b, err := docs.ReadFile(s.Description[1:])
if err != nil {
panic(err)
}
s.Description = string(b)
}
// If it's an array, check the array entry type.
if s.Items != nil {
checkSchema(s.Items)
}
// If it's an object, check all the property types.
for _, propSchema := range s.Properties {
checkSchema(propSchema)
}
}
// replaceDocs crawls the component schemas and replaces relative links in the
// description with the contents of the file from the embedded FS.
func replaceDocs(doc *huma.OpenAPI) {
for _, path := range doc.Paths {
for _, op := range []*huma.Operation{path.Get, path.Post, path.Put, path.Patch, path.Delete} {
if op != nil {
for _, param := range op.Parameters {
checkSchema(param.Schema)
}
}
}
}
for _, schema := range doc.Components.Schemas.Map() {
checkSchema(schema)
}
}
func main() {
// Create a CLI app which takes a port option.
cli := huma.NewCLI(func(hooks huma.Hooks, options *Options) {
// Create a new router & API
router := chi.NewMux()
api := humachi.New(router, huma.DefaultConfig("My API", "1.0.0"))
// Register GET /greeting/{name}
huma.Register(api, huma.Operation{
OperationID: "get-greeting",
Summary: "Get a greeting",
Method: http.MethodGet,
Path: "/greeting/{name}",
}, func(ctx context.Context, input *GreetingInput) (*GreetingOutput, error) {
resp := &GreetingOutput{}
resp.Body.Message = fmt.Sprintf("Hello, %s!", input.Name)
return resp, nil
})
replaceDocs(api.OpenAPI())
// Tell the CLI how to start your router.
hooks.OnStart(func() {
http.ListenAndServe(fmt.Sprintf(":%d", options.Port), router)
})
})
// Run the CLI. When passed no commands, it starts the server.
cli.Run()
}
As for your other feature ideas, you can already add arbitrary schemas for types to the registry. For example:
api.OpenAPI().Components.Schemas.Schema(reflect.TypeOf(MyCustomType{}), true, "FallbackNameHint")
You can also load schema objects like this (or manually instantiate *huma.Schema
instances):
var s *huma.Schema
json.Unmarshal([]byte(`{"type": "string"}`), &s)
Then validate them outside of the normal request flow:
pb := huma.NewPathBuffer([]byte{}, 0)
res := &huma.ValidateResult{}
data := 1234 // this is the wrong type and will fail!
huma.Validate(api.OpenAPI().Components.Schemas, s, pb, huma.ModeReadFromServer, data, res)
for _, err := range res.Errors {
fmt.Println(err.Error())
}
if len(res.Errors) > 0 {
panic("validation failed")
}
Some of this is wrapped up in a small utility at https://huma.rocks/features/model-validation/.
from huma.
another small question,
is the json schema dynamic ref supported ?
from huma.
@jonathanyuechun if you're talking about $dynamicRef
/ $dynamicAnchor
like in https://json-schema.org/blog/posts/dynamicref-and-generics then no, that's not something that is currently supported directly. You can use the schema extensions if you wanted to:
s := huma.Schema{
Extensions: map[string]any{
"$dynamicRef": "#T",
},
}
The extensions are serialized in-line with the other props, so you can use this to set anything you want on the schema. The above schema s
would serialize to YAML like:
$dynamicRef: "#T"
I'll be honest here and say I'm not super familiar with these advanced features, I haven't had a need for them myself, and I'm not sure how they would apply to schema generation from Go structs.
from huma.
No problem !
I dont have a use case right now that would need such a feature.
Last but not least.
I have a requirement:
I have a struct request that has a field whose type is a list of json objects.
Each object has a known set of common fields mapped against a schema. Their schema is mapped against a kind field of type string. Again, there are a known set if kind value and a set of unknown one (provided externally by end user)
My endgoal is to make all those schema
- Appear in openapi.json at runtime
- Huma validation works based on the enriched openapi
Do you know the best way to achieve this goal ?
from huma.
@jonathanyuechun sorry I'm not sure I understand the example. It sounds like maybe there is a discriminator field and you want one-of the schemas to match based on that field? Some examples might help... for example what would your schema look like and do you have some example inputs?
from huma.
yes of course
let's say i have this yaml (json)
things:
- kind: table
legs: 4
surface: 2M
- kind: house
surface: 100
units: M
So in this example, i have a list of objects where each kind may have a fixed known of fields.
For kind table, valid fields are legs of type integer and surface of type string.
For kind house, it shares a common property named surface but of different type (string != integer) with the kind table.
House also has a property units not present in table
These schemas cannot be defined as structs as they are not known in advance.
They are user provided inputs.
My end goal is to use them
- as validation method; i.e. Validate(jsonschemastring, userjsondatainputstring) [ schema_of_table, data: { "kind": "table", ... } ]
- add those json schema string to the already generated OpenAPI struct
from huma.
@jonathanyuechun I wanted to follow up on your latest question. I think that use case is quite complex. You may be able to use the pieces available in Huma to accomplish your desired goal, for example by using manually-created schemas and the validation utility functions available. However, this is more advanced than the out-of-the-box Huma supports, so there isn't a quick/easy function you can call.
My suggestion would be to load the schemas from strings (e.g. json unmarshal into *huma.Schema
), make sure to call schema.PrecomputeMessages()
and then you can use the huma.Validate(...)
function with user-provided inputs you want to validate. In terms of adding to the OpenAPI, you can do that at any point via custom schemas during operation registration (huma.Register(...)
) or after registration but before starting the server using the API config config.OpenAPI.Components.Schemas...
.
Good luck and let me know how else I can help. I'm going to close this issue but feel free to open new ones if things come up!
from huma.
Related Issues (20)
- How to add versioning to my API using huma? HOT 7
- Register routes functionality with middlewares HOT 3
- Api grouping bug - /openapi.yaml request from the docs page does not consider the groups HOT 2
- huma.NewError doesn't support error wrapping. HOT 3
- Support Discriminator and DiscriminatorMap HOT 2
- Setting headers in error response HOT 6
- Add more tests for cookie HOT 3
- Add custom header from SSE Handler HOT 3
- How should nullable query params be handled without pointers? HOT 4
- Add support for netip.Addr HOT 1
- Forwarding headers through the middleware not working HOT 5
- Override ErrorModel for single operations HOT 6
- Feature: Accept valueless path/query/header/cookie tags
- Docs UI doesn't handle multiple cookies properly HOT 1
- Allow custom Transform for nested objects HOT 2
- Error thrown when setting cli name HOT 2
- Error return for huma.StreamResponse.Body HOT 1
- Schema(r huma.Registry) *huma.Schema not working on Pointer
- Validation errors localization HOT 2
- Question: Is there a way to add comments to fields? HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from huma.