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 }