From 20dafe1dfb498ba5c0843bc7633990f47910f407 Mon Sep 17 00:00:00 2001 From: Malte Isberner Date: Tue, 31 Dec 2024 16:21:32 +0100 Subject: [PATCH 1/2] Add a custom mechanism for looking up comments. --- fixtures/custom_comments.json | 114 ++++++++++++++++++++++++++++++++++ reflect.go | 25 ++++---- reflect_comments.go | 20 ++++++ reflect_comments_test.go | 26 +++++++- 4 files changed, 170 insertions(+), 15 deletions(-) create mode 100644 fixtures/custom_comments.json 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..73ce7e4 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 } @@ -558,19 +568,6 @@ func appendUniqueString(base []string, value string) []string { return append(base, value) } -func (r *Reflector) lookupComment(t reflect.Type, name string) string { - if r.CommentMap == nil { - return "" - } - - n := fullyQualifiedTypeName(t) - if name != "" { - n = n + "." + name - } - - return r.CommentMap[n] -} - // addDefinition will append the provided schema. If needed, an ID and anchor will also be added. func (r *Reflector) addDefinition(definitions Definitions, t reflect.Type, s *Schema) { name := r.typeName(t) diff --git a/reflect_comments.go b/reflect_comments.go index eaa498a..ff374c7 100644 --- a/reflect_comments.go +++ b/reflect_comments.go @@ -5,6 +5,7 @@ import ( "io/fs" gopath "path" "path/filepath" + "reflect" "strings" "go/ast" @@ -124,3 +125,22 @@ func (r *Reflector) extractGoComments(base, path string, commentMap map[string]s return nil } + +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 "" + } + + n := fullyQualifiedTypeName(t) + if name != "" { + n = n + "." + name + } + + return r.CommentMap[n] +} diff --git a/reflect_comments_test.go b/reflect_comments_test.go index e162b2b..3e752bb 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.TypeOf(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 +} From 83557f43cf57dde4314dbeb9a4ee320d0d7168f5 Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Tue, 31 Dec 2024 18:14:56 +0000 Subject: [PATCH 2/2] Trying to fix running test workflow --- .github/workflows/test.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e2a175a..099d883 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,5 +1,11 @@ name: Test Go -on: [push] +on: + push: + tags: + - v* + branches: + - main + pull_request: jobs: test: name: Test