@@ -91,3 +91,96 @@ func TestForType(t *testing.T) {
9191 })
9292 }
9393}
94+
95+ func TestForWithMutation (t * testing.T ) {
96+ // This test ensures that the cached schema is not mutated when the caller
97+ // mutates the returned schema.
98+ type S struct {
99+ A int
100+ }
101+ type T struct {
102+ A int `json:"A"`
103+ B map [string ]int
104+ C []S
105+ D [3 ]S
106+ E * bool
107+ }
108+ s , err := jsonschema .For [T ]()
109+ if err != nil {
110+ t .Fatalf ("For: %v" , err )
111+ }
112+ s .Required [0 ] = "mutated"
113+ s .Properties ["A" ].Type = "mutated"
114+ s .Properties ["C" ].Items .Type = "mutated"
115+ s .Properties ["D" ].MaxItems = jsonschema .Ptr (10 )
116+ s .Properties ["D" ].MinItems = jsonschema .Ptr (10 )
117+ s .Properties ["E" ].Types [0 ] = "mutated"
118+
119+ s2 , err := jsonschema .For [T ]()
120+ if err != nil {
121+ t .Fatalf ("For: %v" , err )
122+ }
123+ if s2 .Properties ["A" ].Type == "mutated" {
124+ t .Fatalf ("ForWithMutation: expected A.Type to not be mutated" )
125+ }
126+ if s2 .Properties ["B" ].AdditionalProperties .Type == "mutated" {
127+ t .Fatalf ("ForWithMutation: expected B.AdditionalProperties.Type to not be mutated" )
128+ }
129+ if s2 .Properties ["C" ].Items .Type == "mutated" {
130+ t .Fatalf ("ForWithMutation: expected C.Items.Type to not be mutated" )
131+ }
132+ if * s2 .Properties ["D" ].MaxItems == 10 {
133+ t .Fatalf ("ForWithMutation: expected D.MaxItems to not be mutated" )
134+ }
135+ if * s2 .Properties ["D" ].MinItems == 10 {
136+ t .Fatalf ("ForWithMutation: expected D.MinItems to not be mutated" )
137+ }
138+ if s2 .Properties ["E" ].Types [0 ] == "mutated" {
139+ t .Fatalf ("ForWithMutation: expected E.Types[0] to not be mutated" )
140+ }
141+ if s2 .Required [0 ] == "mutated" {
142+ t .Fatalf ("ForWithMutation: expected Required[0] to not be mutated" )
143+ }
144+ }
145+
146+ type x struct {
147+ Y y
148+ }
149+ type y struct {
150+ X []x
151+ }
152+
153+ func TestForWithCycle (t * testing.T ) {
154+ type a []* a
155+ type b1 struct { b * b1 } // unexported field should be skipped
156+ type b2 struct { B * b2 }
157+ type c1 struct { c map [string ]* c1 } // unexported field should be skipped
158+ type c2 struct { C map [string ]* c2 }
159+
160+ tests := []struct {
161+ name string
162+ shouldErr bool
163+ fn func () error
164+ }{
165+ {"slice alias (a)" , true , func () error { _ , err := jsonschema .For [a ](); return err }},
166+ {"unexported self cycle (b1)" , false , func () error { _ , err := jsonschema .For [b1 ](); return err }},
167+ {"exported self cycle (b2)" , true , func () error { _ , err := jsonschema .For [b2 ](); return err }},
168+ {"unexported map self cycle (c1)" , false , func () error { _ , err := jsonschema .For [c1 ](); return err }},
169+ {"exported map self cycle (c2)" , true , func () error { _ , err := jsonschema .For [c2 ](); return err }},
170+ {"cross-cycle x -> y -> x" , true , func () error { _ , err := jsonschema .For [x ](); return err }},
171+ {"cross-cycle y -> x -> y" , true , func () error { _ , err := jsonschema .For [y ](); return err }},
172+ }
173+
174+ for _ , test := range tests {
175+ test := test // prevent loop shadowing
176+ t .Run (test .name , func (t * testing.T ) {
177+ err := test .fn ()
178+ if test .shouldErr && err == nil {
179+ t .Errorf ("expected cycle error, got nil" )
180+ }
181+ if ! test .shouldErr && err != nil {
182+ t .Errorf ("unexpected error: %v" , err )
183+ }
184+ })
185+ }
186+ }
0 commit comments