@@ -17,6 +17,7 @@ limitations under the License.
1717package composed
1818
1919import (
20+ "errors"
2021 "fmt"
2122 "testing"
2223
@@ -201,3 +202,167 @@ func TestFrom(t *testing.T) {
201202 })
202203 }
203204}
205+
206+ func ExampleTo () {
207+ // Add all v1beta1 types to the scheme so that From can automatically
208+ // determine their apiVersion and kind.
209+ v1beta1 .AddToScheme (Scheme )
210+
211+ // Create a unstructured object as we would receive by the function (observed/desired).
212+ ub := & Unstructured {Unstructured : unstructured.Unstructured {Object : map [string ]any {
213+ "apiVersion" : v1beta1 .CRDGroupVersion .String (),
214+ "kind" : v1beta1 .Bucket_Kind ,
215+ "metadata" : map [string ]any {
216+ "name" : "cool-bucket" ,
217+ },
218+ "spec" : map [string ]any {
219+ "forProvider" : map [string ]any {
220+ "region" : "us-east-2" ,
221+ },
222+ },
223+ "status" : map [string ]any {
224+ "observedGeneration" : float64 (0 ),
225+ },
226+ }}}
227+
228+ // Create a strongly typed object from the unstructured object.
229+ sb := & v1beta1.Bucket {}
230+ err := To (ub , sb )
231+ if err != nil {
232+ panic (err )
233+ }
234+ // Now you have a strongly typed Bucket object.
235+ objectLock := true
236+ sb .Spec .ForProvider .ObjectLockEnabled = & objectLock
237+ }
238+
239+ // Test the To function
240+ func TestTo (t * testing.T ) {
241+ v1beta1 .AddToScheme (Scheme )
242+ type args struct {
243+ un * Unstructured
244+ obj interface {}
245+ }
246+ type want struct {
247+ obj interface {}
248+ err error
249+ }
250+
251+ cases := map [string ]struct {
252+ reason string
253+ args args
254+ want want
255+ }{
256+ "SuccessfulConversion" : {
257+ reason : "A valid unstructured object should convert to a structured object without errors" ,
258+ args : args {
259+ un : & Unstructured {Unstructured : unstructured.Unstructured {Object : map [string ]any {
260+ "apiVersion" : v1beta1 .CRDGroupVersion .String (),
261+ "kind" : v1beta1 .Bucket_Kind ,
262+ "metadata" : map [string ]any {
263+ "name" : "cool-bucket" ,
264+ },
265+ "spec" : map [string ]any {
266+ "forProvider" : map [string ]any {
267+ "region" : "us-east-2" ,
268+ },
269+ },
270+ "status" : map [string ]any {
271+ "observedGeneration" : float64 (0 ),
272+ },
273+ }}},
274+ obj : & v1beta1.Bucket {},
275+ },
276+ want : want {
277+ obj : & v1beta1.Bucket {
278+ TypeMeta : metav1.TypeMeta {
279+ Kind : v1beta1 .Bucket_Kind ,
280+ APIVersion : v1beta1 .CRDGroupVersion .String (),
281+ },
282+ ObjectMeta : metav1.ObjectMeta {
283+ Name : "cool-bucket" ,
284+ },
285+ Spec : v1beta1.BucketSpec {
286+ ForProvider : v1beta1.BucketParameters {
287+ Region : ptr.To [string ]("us-east-2" ),
288+ },
289+ },
290+ },
291+ err : nil ,
292+ },
293+ },
294+ "InvalidGVK" : {
295+ reason : "An unstructured object with mismatched GVK should result in an error" ,
296+ args : args {
297+ un : & Unstructured {Unstructured : unstructured.Unstructured {Object : map [string ]any {
298+ "apiVersion" : "test.example.io" ,
299+ "kind" : "Unknown" ,
300+ "metadata" : map [string ]any {
301+ "name" : "cool-bucket" ,
302+ },
303+ "spec" : map [string ]any {
304+ "forProvider" : map [string ]any {
305+ "region" : "us-east-2" ,
306+ },
307+ },
308+ "status" : map [string ]any {
309+ "observedGeneration" : float64 (0 ),
310+ },
311+ }}},
312+ obj : & v1beta1.Bucket {},
313+ },
314+ want : want {
315+ obj : & v1beta1.Bucket {},
316+ err : errors .New ("GVK /test.example.io, Kind=Unknown is not known by the scheme for the provided object type" ),
317+ },
318+ },
319+ "NoRuntimeObject" : {
320+ reason : "Should only convert to a object if the object is a runtime.Object" ,
321+ args : args {
322+ un : & Unstructured {Unstructured : unstructured.Unstructured {Object : map [string ]any {
323+ "apiVersion" : v1beta1 .CRDGroupVersion .String (),
324+ "kind" : v1beta1 .Bucket_Kind ,
325+ "metadata" : map [string ]any {
326+ "name" : "cool-bucket" ,
327+ },
328+ }}},
329+ obj : "not-a-runtime-object" ,
330+ },
331+ want : want {
332+ obj : string ("not-a-runtime-object" ),
333+ err : errors .New ("object is not a compatible runtime.Object" ),
334+ },
335+ },
336+ }
337+
338+ for name , tc := range cases {
339+ t .Run (name , func (t * testing.T ) {
340+ err := To (tc .args .un , tc .args .obj )
341+
342+ // Compare the resulting object with the expected one
343+ if diff := cmp .Diff (tc .want .obj , tc .args .obj ); diff != "" {
344+ t .Errorf ("\n %s\n To(...): -want, +got:\n %s" , tc .reason , diff )
345+ }
346+ // Compare the error with the expected error
347+ if diff := cmp .Diff (tc .want .err , err , EquateErrors ()); diff != "" {
348+ t .Errorf ("\n %s\n To(...): -want error, +got error:\n %s" , tc .reason , diff )
349+ }
350+ })
351+ }
352+ }
353+
354+ // EquateErrors returns true if the supplied errors are of the same type and
355+ // produce identical strings. This mirrors the error comparison behaviour of
356+ // https://github.com/go-test/deep,
357+ //
358+ // This differs from cmpopts.EquateErrors, which does not test for error strings
359+ // and instead returns whether one error 'is' (in the errors.Is sense) the
360+ // other.
361+ func EquateErrors () cmp.Option {
362+ return cmp .Comparer (func (a , b error ) bool {
363+ if a == nil || b == nil {
364+ return a == nil && b == nil
365+ }
366+ return a .Error () == b .Error ()
367+ })
368+ }
0 commit comments