diff --git a/fixtures/custom_comments.json b/fixtures/custom_comments.json new file mode 100644 index 0000000..db4eec4 --- /dev/null +++ b/fixtures/custom_comments.json @@ -0,0 +1,114 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/invopop/jsonschema/examples/user", + "$ref": "#/$defs/User", + "$defs": { + "NamedPets": { + "additionalProperties": { + "$ref": "#/$defs/Pet" + }, + "type": "object", + "description": "NamedPets is a map of animal names to pets." + }, + "Pet": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of the animal." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name" + ], + "description": "Pet defines the user's fury friend." + }, + "Pets": { + "items": { + "$ref": "#/$defs/Pet" + }, + "type": "array", + "description": "Pets is a collection of Pet objects." + }, + "Plant": { + "properties": { + "variant": { + "type": "string", + "title": "Variant", + "description": "This comment will be used" + }, + "multicellular": { + "type": "boolean", + "title": "Multicellular", + "description": "Multicellular is true if the plant is multicellular" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "variant" + ], + "description": "Plant represents the plants the user might have and serves as a test of structs inside a `type` set." + }, + "User": { + "properties": { + "id": { + "type": "integer", + "description": "Field ID of Go type github.com/invopop/jsonschema/examples.User." + }, + "name": { + "type": "string", + "maxLength": 20, + "minLength": 1, + "pattern": ".*", + "title": "the name", + "description": "this is a property", + "default": "alex", + "examples": [ + "joe", + "lucy" + ] + }, + "friends": { + "items": { + "type": "integer" + }, + "type": "array", + "description": "list of IDs, omitted when empty" + }, + "tags": { + "type": "object", + "description": "Field Tags of Go type github.com/invopop/jsonschema/examples.User." + }, + "pets": { + "$ref": "#/$defs/Pets", + "description": "Field Pets of Go type github.com/invopop/jsonschema/examples.User." + }, + "named_pets": { + "$ref": "#/$defs/NamedPets", + "description": "Field NamedPets of Go type github.com/invopop/jsonschema/examples.User." + }, + "plants": { + "items": { + "$ref": "#/$defs/Plant" + }, + "type": "array", + "title": "Plants", + "description": "Field Plants of Go type github.com/invopop/jsonschema/examples.User." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id", + "name", + "pets", + "named_pets", + "plants" + ], + "description": "Go type User, defined in package github.com/invopop/jsonschema/examples." + } + } +} \ No newline at end of file diff --git a/reflect.go b/reflect.go index b39af10..3df1119 100644 --- a/reflect.go +++ b/reflect.go @@ -143,6 +143,16 @@ type Reflector struct { // AdditionalFields allows adding structfields for a given type AdditionalFields func(reflect.Type) []reflect.StructField + // LookupComment allows customizing comment lookup. Given a reflect.Type and optionally + // a field name, it should return the comment string associated with this type or field. + // + // If the field name is empty, it should return the type's comment; otherwise, the field's + // comment should be returned. If no comment is found, an empty string should be returned. + // + // When set, this function is called before the below CommentMap lookup mechanism. However, + // if it returns an empty string, the CommentMap is still consulted. + LookupComment func(reflect.Type, string) string + // CommentMap is a dictionary of fully qualified go types and fields to comment // strings that will be used if a description has not already been provided in // the tags. Types and fields are added to the package path using "." as a @@ -156,7 +166,7 @@ type Reflector struct { // // map[string]string{"github.com/invopop/jsonschema.Reflector.DoNotReference": "Do not reference definitions."} // - // See also: AddGoComments + // See also: AddGoComments, LookupComment CommentMap map[string]string } @@ -559,6 +569,12 @@ func appendUniqueString(base []string, value string) []string { } func (r *Reflector) lookupComment(t reflect.Type, name string) string { + if r.LookupComment != nil { + if comment := r.LookupComment(t, name); comment != "" { + return comment + } + } + if r.CommentMap == nil { return "" } diff --git a/reflect_comments_test.go b/reflect_comments_test.go index e162b2b..db1cfe9 100644 --- a/reflect_comments_test.go +++ b/reflect_comments_test.go @@ -1,12 +1,15 @@ package jsonschema import ( + "fmt" "path/filepath" + "reflect" "strings" "testing" - "github.com/invopop/jsonschema/examples" "github.com/stretchr/testify/require" + + "github.com/invopop/jsonschema/examples" ) func TestCommentsSchemaGeneration(t *testing.T) { @@ -17,6 +20,7 @@ func TestCommentsSchemaGeneration(t *testing.T) { }{ {&examples.User{}, prepareCommentReflector(t), "fixtures/go_comments.json"}, {&examples.User{}, prepareCommentReflector(t, WithFullComment()), "fixtures/go_comments_full.json"}, + {&examples.User{}, prepareCustomCommentReflector(t), "fixtures/custom_comments.json"}, } for _, tt := range tests { name := strings.TrimSuffix(filepath.Base(tt.fixture), ".json") @@ -35,3 +39,23 @@ func prepareCommentReflector(t *testing.T, opts ...CommentOption) *Reflector { require.NoError(t, err, "did not expect error while adding comments") return r } + +func prepareCustomCommentReflector(t *testing.T) *Reflector { + t.Helper() + r := new(Reflector) + r.LookupComment = func(t reflect.Type, f string) string { + if t != reflect.TypeFor[examples.User]() { + // To test the interaction between a custom LookupComment function and the + // AddGoComments function, we only override comments for the User type. + return "" + } + if f == "" { + return fmt.Sprintf("Go type %s, defined in package %s.", t.Name(), t.PkgPath()) + } + return fmt.Sprintf("Field %s of Go type %s.%s.", f, t.PkgPath(), t.Name()) + } + // Also add the Go comments. + err := r.AddGoComments("github.com/invopop/jsonschema", "./examples") + require.NoError(t, err, "did not expect error while adding comments") + return r +}