public bool IsBackwardsCompatibleWith(Schema oldSchema, TextWriter errorLog) { // Allowed changes: // * Add a new type. // * Add a new property that is not required. // Disallowed changes: // * Remove a type. // * Change inheritance hierarchy. // * Add a new required property. // * Change uri. // Undetectable breaking changes: // * Logical changes in what stuff means. var isBackwardsCompatible = true; var allTypeNames = Types.Select(x => x.Name).Union(oldSchema.Types.Select(x => x.Name)); foreach (var typeName in allTypeNames) { var oldType = oldSchema.Types.FirstOrDefault(x => x.Name == typeName); var newType = Types.FirstOrDefault(x => x.Name == typeName); if (oldType == null) { if (newType == null) throw new InvalidOperationException("newType should not be null at this point."); // New type, accept this change! // TODO: (maybe not if it inherits from an existing type though?) continue; ; } if (newType == null) { // Removal of type not allowed. isBackwardsCompatible = false; errorLog.Write("ERROR: Removal of type {0} breaks backwards compability.\r\n", typeName); continue; } if (newType.Uri != oldType.Uri) { isBackwardsCompatible = false; errorLog.Write( "Change of uri from {0} to {1} for type {2} breaks backwards compability.\r\n", oldType.Uri, newType.Uri, typeName); } if (newType.Extends != oldType.Extends) { isBackwardsCompatible = false; errorLog.Write( "Change of baseclass from {0} to {1} for type {2} breaks backwards compability.\r\n", oldType.Extends, newType.Extends, typeName); } PropertiesAreBackwardsCompatible(errorLog, oldType, newType, ref isBackwardsCompatible, typeName); } return isBackwardsCompatible; }
public static Schema CreateSchema() { var schema = new Schema { Version = "1.3.3.7" }; schema.Types.Add(new SchemaTypeEntry { Extends = "Parent", Name = "Class", Properties = { { "wasReadonly", new SchemaPropertyEntry() { Name = "wasReadonly", Access = HttpMethod.Get, Type = "string" } }, { "wasWritable", new SchemaPropertyEntry() { Name = "wasWritable", Access = HttpMethod.Get | HttpMethod.Put | HttpMethod.Post | HttpMethod.Patch, Type = "string" } }, { "fooRequired", new SchemaPropertyEntry { Name = "fooRequired", Required = true, Type = "string" } }, { "barOptional", new SchemaPropertyEntry { Name = "barOptional", Required = false, Type = "string" } }, } }); return schema; }
public void VerifyCompatibility(Schema changedSchema) { foreach (var schemaFilename in Directory.GetFiles(schemaDirectory, "*.json")) { var content = File.ReadAllText(schemaFilename); Console.WriteLine(content); var oldSchema = Schema.FromJson(content); bool breaks; using (var errorWriter = new StringWriter()) { breaks = !changedSchema.IsBackwardsCompatibleWith(oldSchema, errorWriter); errorWriter.Flush(); if (breaks) throw new AssertException("Schema " + changedSchema.Version + " breaks compatibility with " + schemaFilename + ": " + errorWriter); } } }
public void MarkApiVersion(Schema schema) { var schemaFilename = Path.Combine(schemaDirectory, schema.Version + ".json"); File.WriteAllText(schemaFilename, schema.ToJson()); }