Skip to content

Commit

Permalink
Merge pull request #159 from misberner/mi/custom-comment-lookup
Browse files Browse the repository at this point in the history
Add a custom mechanism for looking up comments.
  • Loading branch information
samlown authored Dec 31, 2024
2 parents 42b1bb0 + 83557f4 commit 328cc73
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 16 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
name: Test Go
on: [push]
on:
push:
tags:
- v*
branches:
- main
pull_request:
jobs:
test:
name: Test
Expand Down
114 changes: 114 additions & 0 deletions fixtures/custom_comments.json
Original file line number Diff line number Diff line change
@@ -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."
}
}
}
25 changes: 11 additions & 14 deletions reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down
20 changes: 20 additions & 0 deletions reflect_comments.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io/fs"
gopath "path"
"path/filepath"
"reflect"
"strings"

"go/ast"
Expand Down Expand Up @@ -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]
}
26 changes: 25 additions & 1 deletion reflect_comments_test.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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")
Expand All @@ -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
}

0 comments on commit 328cc73

Please sign in to comment.