github.com/confluentinc/confluent-kafka-go@v1.9.2/schemaregistry/serde/jsonschema/json_schema.go (about)

     1  /**
     2   * Copyright 2022 Confluent Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   * http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package jsonschema
    18  
    19  import (
    20  	"encoding/json"
    21  	"io"
    22  	"strings"
    23  
    24  	"github.com/confluentinc/confluent-kafka-go/schemaregistry"
    25  	"github.com/confluentinc/confluent-kafka-go/schemaregistry/serde"
    26  	"github.com/invopop/jsonschema"
    27  	jsonschema2 "github.com/santhosh-tekuri/jsonschema/v5"
    28  )
    29  
    30  // Serializer represents a JSON Schema serializer
    31  type Serializer struct {
    32  	serde.BaseSerializer
    33  	validate bool
    34  }
    35  
    36  // Deserializer represents a JSON Schema deserializer
    37  type Deserializer struct {
    38  	serde.BaseDeserializer
    39  	validate bool
    40  }
    41  
    42  var _ serde.Serializer = new(Serializer)
    43  var _ serde.Deserializer = new(Deserializer)
    44  
    45  // NewSerializer creates a JSON serializer for generic objects
    46  func NewSerializer(client schemaregistry.Client, serdeType serde.Type, conf *SerializerConfig) (*Serializer, error) {
    47  	s := &Serializer{
    48  		validate: conf.EnableValidation,
    49  	}
    50  	err := s.ConfigureSerializer(client, serdeType, &conf.SerializerConfig)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	return s, nil
    55  }
    56  
    57  // Serialize implements serialization of generic data to JSON
    58  func (s *Serializer) Serialize(topic string, msg interface{}) ([]byte, error) {
    59  	if msg == nil {
    60  		return nil, nil
    61  	}
    62  	jschema := jsonschema.Reflect(msg)
    63  	raw, err := json.Marshal(jschema)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	info := schemaregistry.SchemaInfo{
    68  		Schema:     string(raw),
    69  		SchemaType: "JSON",
    70  	}
    71  	id, err := s.GetID(topic, msg, info)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	raw, err = json.Marshal(msg)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	if s.validate {
    80  		// Need to unmarshal to pure interface
    81  		var obj interface{}
    82  		err = json.Unmarshal(raw, &obj)
    83  		if err != nil {
    84  			return nil, err
    85  		}
    86  		jschema, err := toJSONSchema(s.Client, info)
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  		err = jschema.Validate(obj)
    91  		if err != nil {
    92  			return nil, err
    93  		}
    94  	}
    95  	payload, err := s.WriteBytes(id, raw)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	return payload, nil
   100  }
   101  
   102  // NewDeserializer creates a JSON deserializer for generic objects
   103  func NewDeserializer(client schemaregistry.Client, serdeType serde.Type, conf *DeserializerConfig) (*Deserializer, error) {
   104  	s := &Deserializer{
   105  		validate: conf.EnableValidation,
   106  	}
   107  	err := s.ConfigureDeserializer(client, serdeType, &conf.DeserializerConfig)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	return s, nil
   112  }
   113  
   114  // Deserialize implements deserialization of generic data from JSON
   115  func (s *Deserializer) Deserialize(topic string, payload []byte) (interface{}, error) {
   116  	if payload == nil {
   117  		return nil, nil
   118  	}
   119  	info, err := s.GetSchema(topic, payload)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	if s.validate {
   124  		// Need to unmarshal to pure interface
   125  		var obj interface{}
   126  		err = json.Unmarshal(payload[5:], &obj)
   127  		if err != nil {
   128  			return nil, err
   129  		}
   130  		jschema, err := toJSONSchema(s.Client, info)
   131  		if err != nil {
   132  			return nil, err
   133  		}
   134  		err = jschema.Validate(obj)
   135  		if err != nil {
   136  			return nil, err
   137  		}
   138  	}
   139  	subject, err := s.SubjectNameStrategy(topic, s.SerdeType, info)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	msg, err := s.MessageFactory(subject, "")
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	err = json.Unmarshal(payload[5:], msg)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  	return msg, nil
   152  }
   153  
   154  // DeserializeInto implements deserialization of generic data from JSON to the given object
   155  func (s *Deserializer) DeserializeInto(topic string, payload []byte, msg interface{}) error {
   156  	if payload == nil {
   157  		return nil
   158  	}
   159  	info, err := s.GetSchema(topic, payload)
   160  	if err != nil {
   161  		return err
   162  	}
   163  	if s.validate {
   164  		// Need to unmarshal to pure interface
   165  		var obj interface{}
   166  		err = json.Unmarshal(payload[5:], &obj)
   167  		if err != nil {
   168  			return err
   169  		}
   170  		jschema, err := toJSONSchema(s.Client, info)
   171  		if err != nil {
   172  			return err
   173  		}
   174  		err = jschema.Validate(obj)
   175  		if err != nil {
   176  			return err
   177  		}
   178  	}
   179  	err = json.Unmarshal(payload[5:], msg)
   180  	if err != nil {
   181  		return err
   182  	}
   183  	return nil
   184  }
   185  
   186  func toJSONSchema(c schemaregistry.Client, schema schemaregistry.SchemaInfo) (*jsonschema2.Schema, error) {
   187  	deps := make(map[string]string)
   188  	err := serde.ResolveReferences(c, schema, deps)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	compiler := jsonschema2.NewCompiler()
   193  	compiler.LoadURL = func(url string) (io.ReadCloser, error) {
   194  		return io.NopCloser(strings.NewReader(deps[url])), nil
   195  	}
   196  	url := "schema.json"
   197  	if err := compiler.AddResource(url, strings.NewReader(schema.Schema)); err != nil {
   198  		return nil, err
   199  	}
   200  	return compiler.Compile(url)
   201  }