github.com/confluentinc/confluent-kafka-go@v1.9.2/schemaregistry/schemaregistry_client.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 schemaregistry
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"net/url"
    23  	"strings"
    24  	"sync"
    25  
    26  	"github.com/confluentinc/confluent-kafka-go/schemaregistry/cache"
    27  )
    28  
    29  /* Schema Registry API endpoints
    30  *
    31  * ====Schemas====
    32  * Fetch string: schema(escaped) identified by the input id.
    33  * -GET /schemas/ids/{int: id} returns: JSON blob: schema; raises: 404[03], 500[01]
    34  *
    35  * ====Subjects====
    36  * Fetch JSON array str:subject of all registered subjects
    37  * -GET /subjects returns: JSON array string: subjects; raises: 500[01]
    38  * Fetch JSON array int:versions
    39  * GET /subjects/{string: subject}/versions returns: JSON array of int: versions; raises: 404[01], 500[01]
    40  *
    41  * GET /subjects/{string: subject}/versions/{int|string('latest'): version} returns: JSON blob *schemaMetadata*; raises: 404[01, 02], 422[02], 500[01]
    42  * GET /subjects/{string: subject}/versions/{int|string('latest'): version}/schema returns : JSON blob: schema(unescaped); raises: 404, 422, 500[01, 02, 03]
    43  *
    44  * Delete subject and it's associated subject configuration subjectConfig
    45  * -DELETE /subjects/{string: subject}) returns: JSON array int: version; raises: 404[01], 500[01]
    46  * Delete subject version
    47  * -DELETE /subjects/{string: subject}/versions/{int|str('latest'): version} returns int: deleted version id; raises: 404[01, 02]
    48  *
    49  * Register new schema under subject
    50  * -POST /subjects/{string: subject}/versions returns JSON blob ; raises: 409, 422[01], 500[01, 02, 03]
    51  * Return SchemaMetadata for the subject version (if any) associated with the schema in the request body
    52  * -POST /subjects/{string: subject} returns JSON *schemaMetadata*; raises: 404[01, 03]
    53  *
    54  * ====Compatibility====
    55  * Test schema (http body) against configured comparability for subject version
    56  * -POST /compatibility/subjects/{string: subject}/versions/{int:string('latest'): version} returns: JSON bool:is_compatible; raises: 404[01,02], 422[01,02], 500[01]
    57  *
    58  * ====SerializerConfig====
    59  * Returns global configuration
    60  * -GET /config  returns: JSON string:comparability; raises: 500[01]
    61  * Update global SR config
    62  * -PUT /config returns: JSON string:compatibility; raises: 422[03], 500[01, 03]
    63  * Update subject level subjectConfig
    64  * -PUT /config/{string: subject} returns: JSON string:compatibility; raises: 422[03], 500[01,03]
    65  * Returns compatibility level of subject
    66  * GET /config/(string: subject) returns: JSON string:compatibility; raises: 404, 500[01]
    67   */
    68  
    69  // Reference represents a schema reference
    70  type Reference struct {
    71  	Name    string `json:"name"`
    72  	Subject string `json:"subject"`
    73  	Version int    `json:"version"`
    74  }
    75  
    76  // SchemaInfo represents basic schema information
    77  type SchemaInfo struct {
    78  	Schema     string      `json:"schema,omitempty"`
    79  	SchemaType string      `json:"schemaType,omitempty"`
    80  	References []Reference `json:"references,omitempty"`
    81  }
    82  
    83  // MarshalJSON implements the json.Marshaler interface
    84  func (sd *SchemaInfo) MarshalJSON() ([]byte, error) {
    85  	return json.Marshal(&struct {
    86  		Schema     string      `json:"schema,omitempty"`
    87  		SchemaType string      `json:"schemaType,omitempty"`
    88  		References []Reference `json:"references,omitempty"`
    89  	}{
    90  		sd.Schema,
    91  		sd.SchemaType,
    92  		sd.References,
    93  	})
    94  }
    95  
    96  // UnmarshalJSON implements the json.Unmarshaller interface
    97  func (sd *SchemaInfo) UnmarshalJSON(b []byte) error {
    98  	var err error
    99  	var tmp struct {
   100  		Schema     string      `json:"schema,omitempty"`
   101  		SchemaType string      `json:"schemaType,omitempty"`
   102  		References []Reference `json:"references,omitempty"`
   103  	}
   104  
   105  	err = json.Unmarshal(b, &tmp)
   106  
   107  	sd.Schema = tmp.Schema
   108  	sd.SchemaType = tmp.SchemaType
   109  	sd.References = tmp.References
   110  
   111  	return err
   112  }
   113  
   114  // SchemaMetadata represents schema metadata
   115  type SchemaMetadata struct {
   116  	SchemaInfo
   117  	ID      int    `json:"id,omitempty"`
   118  	Subject string `json:"subject,omitempty"`
   119  	Version int    `json:"version,omitempty"`
   120  }
   121  
   122  // MarshalJSON implements the json.Marshaler interface
   123  func (sd *SchemaMetadata) MarshalJSON() ([]byte, error) {
   124  	return json.Marshal(&struct {
   125  		Schema     string      `json:"schema,omitempty"`
   126  		SchemaType string      `json:"schemaType,omitempty"`
   127  		References []Reference `json:"references,omitempty"`
   128  		ID         int         `json:"id,omitempty"`
   129  		Subject    string      `json:"subject,omitempty"`
   130  		Version    int         `json:"version,omitempty"`
   131  	}{
   132  		sd.Schema,
   133  		sd.SchemaType,
   134  		sd.References,
   135  		sd.ID,
   136  		sd.Subject,
   137  		sd.Version,
   138  	})
   139  }
   140  
   141  // UnmarshalJSON implements the json.Unmarshaller interface
   142  func (sd *SchemaMetadata) UnmarshalJSON(b []byte) error {
   143  	var err error
   144  	var tmp struct {
   145  		Schema     string      `json:"schema,omitempty"`
   146  		SchemaType string      `json:"schemaType,omitempty"`
   147  		References []Reference `json:"references,omitempty"`
   148  		ID         int         `json:"id,omitempty"`
   149  		Subject    string      `json:"subject,omitempty"`
   150  		Version    int         `json:"version,omitempty"`
   151  	}
   152  
   153  	err = json.Unmarshal(b, &tmp)
   154  
   155  	sd.Schema = tmp.Schema
   156  	sd.SchemaType = tmp.SchemaType
   157  	sd.References = tmp.References
   158  	sd.ID = tmp.ID
   159  	sd.Subject = tmp.Subject
   160  	sd.Version = tmp.Version
   161  
   162  	return err
   163  }
   164  
   165  type subjectJSON struct {
   166  	subject string
   167  	json    string
   168  }
   169  
   170  type subjectID struct {
   171  	subject string
   172  	id      int
   173  }
   174  
   175  /* HTTP(S) Schema Registry Client and schema caches */
   176  type client struct {
   177  	sync.Mutex
   178  	restService      *restService
   179  	schemaCache      cache.Cache
   180  	schemaCacheLock  sync.RWMutex
   181  	idCache          cache.Cache
   182  	idCacheLock      sync.RWMutex
   183  	versionCache     cache.Cache
   184  	versionCacheLock sync.RWMutex
   185  }
   186  
   187  var _ Client = new(client)
   188  
   189  // Client is an interface for clients interacting with the Confluent Schema Registry.
   190  // The Schema Registry's REST interface is further explained in Confluent's Schema Registry API documentation
   191  // https://github.com/confluentinc/schema-registry/blob/master/client/src/main/java/io/confluent/kafka/schemaregistry/client/SchemaRegistryClient.java
   192  type Client interface {
   193  	Register(subject string, schema SchemaInfo, normalize bool) (id int, err error)
   194  	GetBySubjectAndID(subject string, id int) (schema SchemaInfo, err error)
   195  	GetID(subject string, schema SchemaInfo, normalize bool) (id int, err error)
   196  	GetLatestSchemaMetadata(subject string) (SchemaMetadata, error)
   197  	GetSchemaMetadata(subject string, version int) (SchemaMetadata, error)
   198  	GetAllVersions(subject string) ([]int, error)
   199  	GetVersion(subject string, schema SchemaInfo, normalize bool) (version int, err error)
   200  	GetAllSubjects() ([]string, error)
   201  	DeleteSubject(subject string, permanent bool) ([]int, error)
   202  	DeleteSubjectVersion(subject string, version int, permanent bool) (deletes int, err error)
   203  	GetCompatibility(subject string) (compatibility Compatibility, err error)
   204  	UpdateCompatibility(subject string, update Compatibility) (compatibility Compatibility, err error)
   205  	TestCompatibility(subject string, version int, schema SchemaInfo) (compatible bool, err error)
   206  	GetDefaultCompatibility() (compatibility Compatibility, err error)
   207  	UpdateDefaultCompatibility(update Compatibility) (compatibility Compatibility, err error)
   208  }
   209  
   210  // NewClient returns a Client implementation
   211  func NewClient(conf *Config) (Client, error) {
   212  
   213  	urlConf := conf.SchemaRegistryURL
   214  	if strings.HasPrefix(urlConf, "mock://") {
   215  		url, err := url.Parse(urlConf)
   216  		if err != nil {
   217  			return nil, err
   218  		}
   219  		mock := &mockclient{
   220  			url:                url,
   221  			schemaCache:        make(map[subjectJSON]idCacheEntry),
   222  			idCache:            make(map[subjectID]*SchemaInfo),
   223  			versionCache:       make(map[subjectJSON]versionCacheEntry),
   224  			compatibilityCache: make(map[string]Compatibility),
   225  		}
   226  		return mock, nil
   227  	}
   228  
   229  	restService, err := newRestService(conf)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	var schemaCache cache.Cache
   235  	var idCache cache.Cache
   236  	var versionCache cache.Cache
   237  	if conf.CacheCapacity != 0 {
   238  		schemaCache, err = cache.NewLRUCache(conf.CacheCapacity)
   239  		if err != nil {
   240  			return nil, err
   241  		}
   242  		idCache, err = cache.NewLRUCache(conf.CacheCapacity)
   243  		if err != nil {
   244  			return nil, err
   245  		}
   246  		versionCache, err = cache.NewLRUCache(conf.CacheCapacity)
   247  		if err != nil {
   248  			return nil, err
   249  		}
   250  	} else {
   251  		schemaCache = cache.NewMapCache()
   252  		idCache = cache.NewMapCache()
   253  		versionCache = cache.NewMapCache()
   254  	}
   255  	handle := &client{
   256  		restService:  restService,
   257  		schemaCache:  schemaCache,
   258  		idCache:      idCache,
   259  		versionCache: versionCache,
   260  	}
   261  	return handle, nil
   262  }
   263  
   264  // Register registers Schema aliased with subject
   265  func (c *client) Register(subject string, schema SchemaInfo, normalize bool) (id int, err error) {
   266  	schemaJSON, err := schema.MarshalJSON()
   267  	if err != nil {
   268  		return -1, err
   269  	}
   270  	cacheKey := subjectJSON{
   271  		subject: subject,
   272  		json:    string(schemaJSON),
   273  	}
   274  	c.schemaCacheLock.RLock()
   275  	idValue, ok := c.schemaCache.Get(cacheKey)
   276  	c.schemaCacheLock.RUnlock()
   277  	if ok {
   278  		return idValue.(int), nil
   279  	}
   280  
   281  	metadata := SchemaMetadata{
   282  		SchemaInfo: schema,
   283  	}
   284  	c.schemaCacheLock.Lock()
   285  	// another goroutine could have already put it in cache
   286  	idValue, ok = c.schemaCache.Get(cacheKey)
   287  	if !ok {
   288  		err = c.restService.handleRequest(newRequest("POST", versionNormalize, &metadata, url.PathEscape(subject), normalize), &metadata)
   289  		if err == nil {
   290  			c.schemaCache.Put(cacheKey, metadata.ID)
   291  		} else {
   292  			metadata.ID = -1
   293  		}
   294  	} else {
   295  		metadata.ID = idValue.(int)
   296  	}
   297  	c.schemaCacheLock.Unlock()
   298  	return metadata.ID, err
   299  }
   300  
   301  // GetBySubjectAndID returns the schema identified by id
   302  // Returns Schema object on success
   303  func (c *client) GetBySubjectAndID(subject string, id int) (schema SchemaInfo, err error) {
   304  	cacheKey := subjectID{
   305  		subject: subject,
   306  		id:      id,
   307  	}
   308  	c.idCacheLock.RLock()
   309  	infoValue, ok := c.idCache.Get(cacheKey)
   310  	c.idCacheLock.RUnlock()
   311  	if ok {
   312  		return *infoValue.(*SchemaInfo), nil
   313  	}
   314  
   315  	metadata := SchemaMetadata{}
   316  	newInfo := &SchemaInfo{}
   317  	c.idCacheLock.Lock()
   318  	// another goroutine could have already put it in cache
   319  	infoValue, ok = c.idCache.Get(cacheKey)
   320  	if !ok {
   321  		if len(subject) > 0 {
   322  			err = c.restService.handleRequest(newRequest("GET", schemasBySubject, nil, id, url.QueryEscape(subject)), &metadata)
   323  		} else {
   324  			err = c.restService.handleRequest(newRequest("GET", schemas, nil, id), &metadata)
   325  		}
   326  		if err == nil {
   327  			newInfo = &SchemaInfo{
   328  				Schema:     metadata.Schema,
   329  				SchemaType: metadata.SchemaType,
   330  				References: metadata.References,
   331  			}
   332  			c.idCache.Put(cacheKey, newInfo)
   333  		}
   334  	} else {
   335  		newInfo = infoValue.(*SchemaInfo)
   336  	}
   337  	c.idCacheLock.Unlock()
   338  	return *newInfo, err
   339  }
   340  
   341  // GetID checks if a schema has been registered with the subject. Returns ID if the registration can be found
   342  func (c *client) GetID(subject string, schema SchemaInfo, normalize bool) (id int, err error) {
   343  	schemaJSON, err := schema.MarshalJSON()
   344  	if err != nil {
   345  		return -1, err
   346  	}
   347  	cacheKey := subjectJSON{
   348  		subject: subject,
   349  		json:    string(schemaJSON),
   350  	}
   351  	c.schemaCacheLock.RLock()
   352  	idValue, ok := c.schemaCache.Get(cacheKey)
   353  	c.schemaCacheLock.RUnlock()
   354  	if ok {
   355  		return idValue.(int), nil
   356  	}
   357  
   358  	metadata := SchemaMetadata{
   359  		SchemaInfo: schema,
   360  	}
   361  	c.schemaCacheLock.Lock()
   362  	// another goroutine could have already put it in cache
   363  	idValue, ok = c.schemaCache.Get(cacheKey)
   364  	if !ok {
   365  		err = c.restService.handleRequest(newRequest("POST", subjectsNormalize, &metadata, url.PathEscape(subject), normalize), &metadata)
   366  		if err == nil {
   367  			c.schemaCache.Put(cacheKey, metadata.ID)
   368  		} else {
   369  			metadata.ID = -1
   370  		}
   371  	} else {
   372  		metadata.ID = idValue.(int)
   373  	}
   374  	c.schemaCacheLock.Unlock()
   375  	return metadata.ID, err
   376  }
   377  
   378  // GetLatestSchemaMetadata fetches latest version registered with the provided subject
   379  // Returns SchemaMetadata object
   380  func (c *client) GetLatestSchemaMetadata(subject string) (result SchemaMetadata, err error) {
   381  	err = c.restService.handleRequest(newRequest("GET", versions, nil, url.PathEscape(subject), "latest"), &result)
   382  
   383  	return result, err
   384  }
   385  
   386  // GetSchemaMetadata fetches the requested subject schema identified by version
   387  // Returns SchemaMetadata object
   388  func (c *client) GetSchemaMetadata(subject string, version int) (result SchemaMetadata, err error) {
   389  	err = c.restService.handleRequest(newRequest("GET", versions, nil, url.PathEscape(subject), version), &result)
   390  
   391  	return result, err
   392  }
   393  
   394  // GetAllVersions fetches a list of all version numbers associated with the provided subject registration
   395  // Returns integer slice on success
   396  func (c *client) GetAllVersions(subject string) (results []int, err error) {
   397  	var result []int
   398  	err = c.restService.handleRequest(newRequest("GET", version, nil, url.PathEscape(subject)), &result)
   399  
   400  	return result, err
   401  }
   402  
   403  // GetVersion finds the Subject SchemaMetadata associated with the provided schema
   404  // Returns integer SchemaMetadata number
   405  func (c *client) GetVersion(subject string, schema SchemaInfo, normalize bool) (version int, err error) {
   406  	schemaJSON, err := schema.MarshalJSON()
   407  	if err != nil {
   408  		return -1, err
   409  	}
   410  	cacheKey := subjectJSON{
   411  		subject: subject,
   412  		json:    string(schemaJSON),
   413  	}
   414  	c.versionCacheLock.RLock()
   415  	versionValue, ok := c.versionCache.Get(cacheKey)
   416  	c.versionCacheLock.RUnlock()
   417  	if ok {
   418  		return versionValue.(int), nil
   419  	}
   420  
   421  	metadata := SchemaMetadata{
   422  		SchemaInfo: schema,
   423  	}
   424  	c.versionCacheLock.Lock()
   425  	// another goroutine could have already put it in cache
   426  	versionValue, ok = c.versionCache.Get(cacheKey)
   427  	if !ok {
   428  		err = c.restService.handleRequest(newRequest("POST", subjectsNormalize, &metadata, url.PathEscape(subject), normalize), &metadata)
   429  		if err == nil {
   430  			c.versionCache.Put(cacheKey, metadata.Version)
   431  		} else {
   432  			metadata.Version = -1
   433  		}
   434  	} else {
   435  		metadata.Version = versionValue.(int)
   436  	}
   437  	c.versionCacheLock.Unlock()
   438  	return metadata.Version, err
   439  }
   440  
   441  // Fetch all Subjects registered with the schema Registry
   442  // Returns a string slice containing all registered subjects
   443  func (c *client) GetAllSubjects() ([]string, error) {
   444  	var result []string
   445  	err := c.restService.handleRequest(newRequest("GET", subject, nil), &result)
   446  
   447  	return result, err
   448  }
   449  
   450  // Deletes provided Subject from registry
   451  // Returns integer slice of versions removed by delete
   452  func (c *client) DeleteSubject(subject string, permanent bool) (deleted []int, err error) {
   453  	c.schemaCacheLock.Lock()
   454  	for keyValue := range c.schemaCache.ToMap() {
   455  		key := keyValue.(subjectJSON)
   456  		if key.subject == subject {
   457  			c.schemaCache.Delete(key)
   458  		}
   459  	}
   460  	c.schemaCacheLock.Unlock()
   461  	c.versionCacheLock.Lock()
   462  	for keyValue := range c.versionCache.ToMap() {
   463  		key := keyValue.(subjectJSON)
   464  		if key.subject == subject {
   465  			c.versionCache.Delete(key)
   466  		}
   467  	}
   468  	c.versionCacheLock.Unlock()
   469  	c.idCacheLock.Lock()
   470  	for keyValue := range c.idCache.ToMap() {
   471  		key := keyValue.(subjectID)
   472  		if key.subject == subject {
   473  			c.idCache.Delete(key)
   474  		}
   475  	}
   476  	c.idCacheLock.Unlock()
   477  	var result []int
   478  	err = c.restService.handleRequest(newRequest("DELETE", subjectsDelete, nil, url.PathEscape(subject), permanent), &result)
   479  	return result, err
   480  }
   481  
   482  // DeleteSubjectVersion removes the version identified by delete from the subject's registration
   483  // Returns integer id for the deleted version
   484  func (c *client) DeleteSubjectVersion(subject string, version int, permanent bool) (deleted int, err error) {
   485  	c.versionCacheLock.Lock()
   486  	for keyValue, value := range c.versionCache.ToMap() {
   487  		key := keyValue.(subjectJSON)
   488  		if key.subject == subject && value == version {
   489  			c.versionCache.Delete(key)
   490  			schemaJSON := key.json
   491  			cacheKeySchema := subjectJSON{
   492  				subject: subject,
   493  				json:    string(schemaJSON),
   494  			}
   495  			c.schemaCacheLock.Lock()
   496  			idValue, ok := c.schemaCache.Get(cacheKeySchema)
   497  			if ok {
   498  				c.schemaCache.Delete(cacheKeySchema)
   499  			}
   500  			c.schemaCacheLock.Unlock()
   501  			if ok {
   502  				id := idValue.(int)
   503  				c.idCacheLock.Lock()
   504  				cacheKeyID := subjectID{
   505  					subject: subject,
   506  					id:      id,
   507  				}
   508  				c.idCache.Delete(cacheKeyID)
   509  				c.idCacheLock.Unlock()
   510  			}
   511  		}
   512  	}
   513  	c.versionCacheLock.Unlock()
   514  	var result int
   515  	err = c.restService.handleRequest(newRequest("DELETE", versionsDelete, nil, url.PathEscape(subject), version, permanent), &result)
   516  	return result, err
   517  
   518  }
   519  
   520  // Compatibility options
   521  type Compatibility int
   522  
   523  const (
   524  	_ = iota
   525  	// None is no compatibility
   526  	None
   527  	// Backward compatibility
   528  	Backward
   529  	// Forward compatibility
   530  	Forward
   531  	// Full compatibility
   532  	Full
   533  	// BackwardTransitive compatibility
   534  	BackwardTransitive
   535  	// ForwardTransitive compatibility
   536  	ForwardTransitive
   537  	// FullTransitive compatibility
   538  	FullTransitive
   539  )
   540  
   541  var compatibilityEnum = []string{
   542  	"",
   543  	"NONE",
   544  	"BACKWARD",
   545  	"FORWARD",
   546  	"FULL",
   547  	"BACKWARD_TRANSITIVE",
   548  	"FORWARD_TRANSITIVE",
   549  	"FULL_TRANSITIVE",
   550  }
   551  
   552  /* NOTE: GET uses compatibilityLevel, POST uses compatibility */
   553  type compatibilityLevel struct {
   554  	CompatibilityUpdate Compatibility `json:"compatibility,omitempty"`
   555  	Compatibility       Compatibility `json:"compatibilityLevel,omitempty"`
   556  }
   557  
   558  // MarshalJSON implements json.Marshaler
   559  func (c Compatibility) MarshalJSON() ([]byte, error) {
   560  	return json.Marshal(c.String())
   561  }
   562  
   563  // UnmarshalJSON implements json.Unmarshaler
   564  func (c *Compatibility) UnmarshalJSON(b []byte) error {
   565  	val := string(b[1 : len(b)-1])
   566  	return c.ParseString(val)
   567  }
   568  
   569  type compatibilityValue struct {
   570  	Compatible bool `json:"is_compatible,omitempty"`
   571  }
   572  
   573  func (c Compatibility) String() string {
   574  	return compatibilityEnum[c]
   575  }
   576  
   577  // ParseString returns a Compatibility for the given string
   578  func (c *Compatibility) ParseString(val string) error {
   579  	for idx, elm := range compatibilityEnum {
   580  		if elm == val {
   581  			*c = Compatibility(idx)
   582  			return nil
   583  		}
   584  	}
   585  
   586  	return fmt.Errorf("failed to unmarshal Compatibility")
   587  }
   588  
   589  // Fetch compatibility level currently configured for provided subject
   590  // Returns compatibility level string upon success
   591  func (c *client) GetCompatibility(subject string) (compatibility Compatibility, err error) {
   592  	var result compatibilityLevel
   593  	err = c.restService.handleRequest(newRequest("GET", subjectConfig, nil, url.PathEscape(subject)), &result)
   594  
   595  	return result.Compatibility, err
   596  }
   597  
   598  // UpdateCompatibility updates subject's compatibility level
   599  // Returns new compatibility level string upon success
   600  func (c *client) UpdateCompatibility(subject string, update Compatibility) (compatibility Compatibility, err error) {
   601  	result := compatibilityLevel{
   602  		CompatibilityUpdate: update,
   603  	}
   604  	err = c.restService.handleRequest(newRequest("PUT", subjectConfig, &result, url.PathEscape(subject)), &result)
   605  
   606  	return result.CompatibilityUpdate, err
   607  }
   608  
   609  // TestCompatibility verifies schema against the subject's compatibility policy
   610  // Returns true if the schema is compatible, false otherwise
   611  func (c *client) TestCompatibility(subject string, version int, schema SchemaInfo) (ok bool, err error) {
   612  	var result compatibilityValue
   613  	candidate := SchemaMetadata{
   614  		SchemaInfo: schema,
   615  	}
   616  
   617  	err = c.restService.handleRequest(newRequest("POST", compatibility, &candidate, url.PathEscape(subject), version), &result)
   618  
   619  	return result.Compatible, err
   620  }
   621  
   622  // GetDefaultCompatibility fetches the global(default) compatibility level
   623  // Returns global(default) compatibility level
   624  func (c *client) GetDefaultCompatibility() (compatibility Compatibility, err error) {
   625  	var result compatibilityLevel
   626  	err = c.restService.handleRequest(newRequest("GET", config, nil), &result)
   627  
   628  	return result.Compatibility, err
   629  }
   630  
   631  // UpdateDefaultCompatibility updates the global(default) compatibility level level
   632  // Returns new string compatibility level
   633  func (c *client) UpdateDefaultCompatibility(update Compatibility) (compatibility Compatibility, err error) {
   634  	result := compatibilityLevel{
   635  		CompatibilityUpdate: update,
   636  	}
   637  	err = c.restService.handleRequest(newRequest("PUT", config, &result), &result)
   638  
   639  	return result.CompatibilityUpdate, err
   640  }