public void CorrectlyReferencedServicesShouldBeIncludedInSMD() { var xmlDocSource = new XmlDocSource(); xmlDocSource.Dtos.Add(AssemblyWithXmlDocs.CreateFromName("TestAssembly.DTO, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", ".")); xmlDocSource.RouteAssembly = AssemblyWithXmlDocs.CreateFromName("TestAssembly.Service, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "."); xmlDocSource.Routes = new List <RouteElement> { new RouteElement { Endpoint = "/session", Name = "session", Type = "TestAssembly.Service.ITestService, TestAssembly.Service, Version=1.0.0.0" }, new RouteElement { Endpoint = "/session", Name = "session_logout", Type = "TestAssembly.Service.ITestService, TestAssembly.Service, Version=1.0.0.0" } }; var jsonSchema = new JsonSchemaDtoEmitter().EmitDtoJson(xmlDocSource); var smdSchema = new WcfSMD.Emitter().EmitSmdJson(xmlDocSource, true, jsonSchema); Console.WriteLine(smdSchema.SMD); Assert.That(smdSchema.SMD["services"]["rpc"]["services"]["CreateSession"], Is.Not.Null); Assert.That(smdSchema.SMD["services"]["rpc"]["services"]["DeleteSession"], Is.Not.Null); }
public MetadataGenerationResult EmitSmdJson(XmlDocSource xmlDocSource, bool includeDemoValue, JObject schema) { var result = new MetadataGenerationResult(); JObject smd = new JObject { {"SMDVersion","2.6"}, {"version",xmlDocSource.RouteAssembly.Version}, {"description","CIAPI SMD"}, {"services", new JObject()} }; var rpc = new JObject(); smd["services"]["rpc"] = rpc; rpc["target"] = ""; JObject rpcServices = new JObject(); rpc["services"] = rpcServices; var seenTypes = new List<Type>(); // just to keep track of types so we don't map twice foreach (RouteElement route in xmlDocSource.Routes) { var serviceResult = BuildServiceMapping(xmlDocSource, route, seenTypes, rpcServices, includeDemoValue, schema); result.AddValidationResults(serviceResult); } result.SMD = smd; return result; }
public MetadataValidationResult AuditTypes(XmlDocSource xmlDocSource) { var results = new MetadataValidationResult(); foreach (var assembly in xmlDocSource.Dtos.Select(a => a.Assembly)) { foreach (Type type in assembly.GetTypes()) { // we don't emit interfaces if (type.IsInterface) { continue; } try { AuditType(type, false); results.AddMetadataGenerationSuccess(new MetadataGenerationSuccess(MetadataType.JsonSchema, type)); } catch (MetadataValidationException e) { results.AddMetadataGenerationError(new MetadataGenerationError(MetadataType.JsonSchema, type, e)); } } } return results; }
public MetadataValidationResult AuditTypes(XmlDocSource xmlDocSource) { var results = new MetadataValidationResult(); foreach (var assembly in xmlDocSource.Dtos.Select(a => a.Assembly)) { foreach (Type type in assembly.GetTypes()) { // we don't emit interfaces if (type.IsInterface) { continue; } try { AuditType(type, false); results.AddMetadataGenerationSuccess(new MetadataGenerationSuccess(MetadataType.JsonSchema, type)); } catch (MetadataValidationException e) { results.AddMetadataGenerationError(new MetadataGenerationError(MetadataType.JsonSchema, type, e)); } } } return(results); }
public MetadataGenerationResult EmitSmdJson(XmlDocSource xmlDocSource, bool includeDemoValue, JObject schema) { var result = new MetadataGenerationResult(); JObject smd = new JObject { { "SMDVersion", "2.6" }, { "version", xmlDocSource.RouteAssembly.Version }, { "description", "CIAPI SMD" }, { "services", new JObject() } }; var rpc = new JObject(); smd["services"]["rpc"] = rpc; rpc["target"] = ""; JObject rpcServices = new JObject(); rpc["services"] = rpcServices; var seenTypes = new List <Type>(); // just to keep track of types so we don't map twice foreach (RouteElement route in xmlDocSource.Routes) { var serviceResult = BuildServiceMapping(xmlDocSource, route, seenTypes, rpcServices, includeDemoValue, schema); result.AddValidationResults(serviceResult); } result.SMD = smd; return(result); }
protected static XmlDocSource CreateXmlDocSourceForTestAssembly(List<RouteElement> routes) { var xmlDocSource = new XmlDocSource(); xmlDocSource.Dtos.Add(AssemblyWithXmlDocs.CreateFromName("TestAssembly.DTO, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", ".")); xmlDocSource.RouteAssembly = AssemblyWithXmlDocs.CreateFromName("TestAssembly.Service, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "."); xmlDocSource.Routes = routes; return xmlDocSource; }
protected static XmlDocSource CreateXmlDocSourceForTestAssembly(List <RouteElement> routes) { var xmlDocSource = new XmlDocSource(); xmlDocSource.Dtos.Add(AssemblyWithXmlDocs.CreateFromName("TestAssembly.DTO, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", ".")); xmlDocSource.RouteAssembly = AssemblyWithXmlDocs.CreateFromName("TestAssembly.Service, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "."); xmlDocSource.Routes = routes; return(xmlDocSource); }
public void AllArrayTypesShouldValidate() { var xmlDocSource = new XmlDocSource(); xmlDocSource.Dtos.Add(AssemblyWithXmlDocs.CreateFromName("TestAssembly.DTO, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", @"TestData\valid")); var result = new Auditor().AuditTypes(xmlDocSource); result.MetadataGenerationErrors.ForEach(e => Console.WriteLine(e.ToString())); Assert.AreEqual(0, result.MetadataGenerationErrors.Count, "No errors should have been reported"); }
private JObject GenerateJsonSchemaForTestAssemblyDTO() { var xmlDocSource = new XmlDocSource(); xmlDocSource.Dtos.Add(AssemblyWithXmlDocs.CreateFromName("TestAssembly.DTO, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", @"TestData\valid")); xmlDocSource.RouteAssembly = new AssemblyWithXmlDocs() { Version = "1.0.0.0" }; return(new JsonSchemaDtoEmitter().EmitDtoJson(xmlDocSource)); }
public void Context() { _xmlDocSource = CreateXmlDocSourceForTestAssembly( new List<RouteElement>{ new RouteElement { Endpoint = ENDPOINT1, Name = "GetDetail", Type = "TestAssembly.Service.ISimpleService, TestAssembly.Service, Version=1.0.0.0"} , }); _jsonSchema = new JsonSchemaDtoEmitter().EmitDtoJson(_xmlDocSource); _smdSchema = new WcfSMD.Emitter().EmitSmdJson(_xmlDocSource, true, _jsonSchema); }
public void Context() { _xmlDocSource = CreateXmlDocSourceForTestAssembly( new List <RouteElement> { new RouteElement { Endpoint = ENDPOINT1, Name = "GetDetail", Type = "TestAssembly.Service.ISimpleService, TestAssembly.Service, Version=1.0.0.0" } , }); _jsonSchema = new JsonSchemaDtoEmitter().EmitDtoJson(_xmlDocSource); _smdSchema = new WcfSMD.Emitter().EmitSmdJson(_xmlDocSource, true, _jsonSchema); }
public void CorrectlyReferencedServicesShouldBeIncludedInSMD() { var xmlDocSource = new XmlDocSource(); xmlDocSource.Dtos.Add(AssemblyWithXmlDocs.CreateFromName("TestAssembly.DTO, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", ".")); xmlDocSource.RouteAssembly = AssemblyWithXmlDocs.CreateFromName("TestAssembly.Service, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "."); xmlDocSource.Routes = new List<RouteElement>{ new RouteElement { Endpoint = "/session", Name = "session", Type = "TestAssembly.Service.ITestService, TestAssembly.Service, Version=1.0.0.0"}, new RouteElement { Endpoint = "/session", Name = "session_logout", Type = "TestAssembly.Service.ITestService, TestAssembly.Service, Version=1.0.0.0"} }; var jsonSchema = new JsonSchemaDtoEmitter().EmitDtoJson(xmlDocSource); var smdSchema = new WcfSMD.Emitter().EmitSmdJson(xmlDocSource, true, jsonSchema); Console.WriteLine(smdSchema.SMD); Assert.That(smdSchema.SMD["services"]["rpc"]["services"]["CreateSession"], Is.Not.Null); Assert.That(smdSchema.SMD["services"]["rpc"]["services"]["DeleteSession"], Is.Not.Null); }
private JObject GenerateJsonSchemaForTestAssemblyDTO() { var xmlDocSource = new XmlDocSource(); xmlDocSource.Dtos.Add(AssemblyWithXmlDocs.CreateFromName("TestAssembly.DTO, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", @"TestData\valid")); xmlDocSource.RouteAssembly = new AssemblyWithXmlDocs() { Version = "1.0.0.0" }; return new JsonSchemaDtoEmitter().EmitDtoJson(xmlDocSource); }
public JObject EmitDtoJson(XmlDocSource xmlDocSource) { var schemaObj = new JObject(); var assemblies = xmlDocSource.Dtos.Select(a => a.Assembly).ToArray(); schemaObj["version"] = xmlDocSource.RouteAssembly.Version; var schemaProperties = new JObject(); schemaObj["properties"] = schemaProperties; var types = UtilityExtensions.GetSchemaTypes(assemblies); var exception = new MetadataValidationException(typeof (object), "", "Errors generating meta for types", ""); foreach (Type type in types) { try { var typeNode = type.GetXmlDocTypeNodeWithJSchema(); var jschemaXml = JschemaXmlComment.CreateFromXml(typeNode.XPathSelectElement("jschema")); if (jschemaXml.Exclude) continue; //Skip to next type var typeObj = new JObject(); typeObj["id"] = type.Name; if (type.IsEnum) { RenderEnum(type, typeObj); } else if (type.IsClass) { RenderType(type, typeObj); } else { throw new NotSupportedException(type.Name + " is not supported "); } ApplyDescription(typeObj, typeNode); if (jschemaXml.DemoValue != null) { typeObj["demoValue"] = jschemaXml.DemoValue; } schemaProperties.Add(type.Name, typeObj); } catch (MetadataValidationException ex) { exception.AggregatedExceptions.Add(ex); } } if (exception.AggregatedExceptions.Count>0) { throw exception; } return schemaObj; }
private MetadataValidationResult BuildServiceMapping(XmlDocSource xmlDocSource, RouteElement route, List <Type> seenTypes, JObject smdBase, bool includeDemoValue, JObject schema) { var result = new MetadataValidationResult(); Type type = xmlDocSource.RouteAssembly.Assembly.GetType(route.Type.Substring(0, route.Type.IndexOf(","))); if (seenTypes.Contains(type)) { return(result); } seenTypes.Add(type); var typeElement = type.GetXmlDocTypeNodeWithSMD(); if (typeElement == null) { return(result); } var methodTarget = route.Endpoint.Trim('/'); foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) { var methodElement = type.GetXmlDocMemberNodeWithSMD(type.FullName + "." + method.Name); if (methodElement == null) { continue; } // get smd xml, if present var methodSmdElement = methodElement.XPathSelectElement("smd"); if (methodSmdElement == null) { result.AddMetadataGenerationError(new MetadataGenerationError(MetadataType.SMD, type, "should not have gotten a method element without smd", "All services that have XML comments must have a <smd> tag. See https://github.com/cityindex/RESTful-Webservice-Schema/wiki/Howto-write-XML-comments-for-SMD for details")); continue; //advance to next service method } var smdXmlComment = SmdXmlComment.CreateFromXml(methodSmdElement); //Don't document methods that are marked exclude if (smdXmlComment.Exclude) { continue; //advance to next service method } JObject service = null; var opContract = ReflectionUtils.GetAttribute <OperationContractAttribute>(method); if (opContract != null) { var webGet = ReflectionUtils.GetAttribute <WebGetAttribute>(method); var methodName = method.Name; if (!string.IsNullOrEmpty(smdXmlComment.MethodName)) { methodName = smdXmlComment.MethodName; } string methodTransport = null; string methodEnvelope = null; string methodUriTemplate = null; if (webGet != null) { service = new JObject(); methodUriTemplate = ResolveUriTemplate(smdXmlComment, webGet.UriTemplate); methodTransport = "GET"; methodEnvelope = "URL"; } else { var webInvoke = ReflectionUtils.GetAttribute <WebInvokeAttribute>(method); if (webInvoke != null) { service = new JObject(); methodUriTemplate = ResolveUriTemplate(smdXmlComment, webInvoke.UriTemplate); switch (webInvoke.Method.ToUpper()) { case "POST": methodTransport = "POST"; methodEnvelope = "JSON"; break; case "GET": methodTransport = "GET"; methodEnvelope = "URL"; break; default: result.AddMetadataGenerationError(new MetadataGenerationError(MetadataType.SMD, type, string.Format("The {0} service has transport method of type {1} that is not supported", methodName, webInvoke.Method), "Service transports like DELETE or PUT are poorly supported by client http clients, so you advised to only use GET or POST")); continue; //advance to next service method } } } if (service != null) { JsonSchemaUtilities.ApplyDescription(service, methodElement); service.Add("target", ResolveEndpoint(smdXmlComment, methodTarget)); if (!string.IsNullOrWhiteSpace(methodUriTemplate)) { service.Add("uriTemplate", methodUriTemplate); } service.Add("contentType", "application/json"); // TODO: declare this in meta or get from WebGet/WebInvoke service.Add("responseContentType", "application/json"); // TODO: declare this in meta or get from WebGet/WebInvoke service.Add("transport", methodTransport); try { smdBase.Add(methodName, service); } catch (ArgumentException e) { result.AddMetadataGenerationError(new MetadataGenerationError(MetadataType.SMD, type, string.Format("A service with the method name {0} already exists", methodName), "Ensure that methods names are unique across services")); return(result); } // this is not accurate/valid SMD for GET but dojox.io.services is not, yet, a very good // implementation of the SMD spec, which is funny as they were both written by the same person. service.Add("envelope", methodEnvelope); // determine if return type is object or primitive JObject returnType = null; if (Type.GetTypeCode(method.ReturnType) == TypeCode.Object) { if (method.ReturnType.Name != "Void") { string methodReturnTypeName = method.ReturnType.Name; if (schema["properties"][methodReturnTypeName] == null) { result.AddMetadataGenerationError(new MetadataGenerationError(MetadataType.SMD, type, "Schema missing referenced return type " + methodReturnTypeName + " for method " + method.Name, "All types used by services must be decorated with the <jschema> tag. See https://github.com/cityindex/RESTful-Webservice-Schema/wiki/Howto-write-XML-comments-for-JSchema")); } returnType = new JObject(new JProperty("$ref", JsonSchemaUtilities.RootDelimiter + methodReturnTypeName)); } else { returnType = null; } } else if (Type.GetTypeCode(method.ReturnType) == TypeCode.Empty) { returnType = null; } else { returnType = new JObject(new JProperty("type", method.ReturnType.GetSchemaType()["type"].Value <string>())); } if (returnType != null) { service.Add("returns", returnType); } SetStringAttribute(methodSmdElement, service, "group"); SetIntAttribute(methodSmdElement, service, "cacheDuration"); SetStringAttribute(methodSmdElement, service, "throttleScope"); var paramResult = AddParameters(type, method, methodElement, service, includeDemoValue, schema); if (paramResult.HasErrors) { result.AddMetadataGenerationErrors(paramResult.MetadataGenerationErrors); } } } if (!result.HasErrors) { result.AddMetadataGenerationSuccess(new MetadataGenerationSuccess(MetadataType.SMD, type)); } } return(result); }
private MetadataValidationResult BuildServiceMapping(XmlDocSource xmlDocSource, RouteElement route, List<Type> seenTypes, JObject smdBase, bool includeDemoValue, JObject schema) { var result = new MetadataValidationResult(); Type type = xmlDocSource.RouteAssembly.Assembly.GetType(route.Type.Substring(0, route.Type.IndexOf(","))); if (seenTypes.Contains(type)) { return result; } seenTypes.Add(type); var typeElement = type.GetXmlDocTypeNodeWithSMD(); if (typeElement == null) { return result; } var methodTarget = route.Endpoint.Trim('/'); foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) { var methodElement = type.GetXmlDocMemberNodeWithSMD(type.FullName + "." + method.Name); if (methodElement == null) { continue; } // get smd xml, if present var methodSmdElement = methodElement.XPathSelectElement("smd"); if (methodSmdElement == null) { result.AddMetadataGenerationError(new MetadataGenerationError(MetadataType.SMD, type, "should not have gotten a method element without smd", "All services that have XML comments must have a <smd> tag. See https://github.com/cityindex/RESTful-Webservice-Schema/wiki/Howto-write-XML-comments-for-SMD for details")); continue; //advance to next service method } var smdXmlComment = SmdXmlComment.CreateFromXml(methodSmdElement); //Don't document methods that are marked exclude if (smdXmlComment.Exclude) continue; //advance to next service method JObject service = null; var opContract = ReflectionUtils.GetAttribute<OperationContractAttribute>(method); if (opContract != null) { var webGet = ReflectionUtils.GetAttribute<WebGetAttribute>(method); var methodName = method.Name; if (!string.IsNullOrEmpty(smdXmlComment.MethodName)) methodName = smdXmlComment.MethodName; string methodTransport = null; string methodEnvelope = null; string methodUriTemplate = null; if (webGet != null) { service = new JObject(); methodUriTemplate = ResolveUriTemplate(smdXmlComment, webGet.UriTemplate); methodTransport = "GET"; methodEnvelope = "URL"; } else { var webInvoke = ReflectionUtils.GetAttribute<WebInvokeAttribute>(method); if (webInvoke != null) { service = new JObject(); methodUriTemplate = ResolveUriTemplate(smdXmlComment, webInvoke.UriTemplate); switch (webInvoke.Method.ToUpper()) { case "POST": methodTransport = "POST"; methodEnvelope = "JSON"; break; case "GET": methodTransport = "GET"; methodEnvelope = "URL"; break; default: result.AddMetadataGenerationError(new MetadataGenerationError(MetadataType.SMD, type, string.Format("The {0} service has transport method of type {1} that is not supported", methodName, webInvoke.Method), "Service transports like DELETE or PUT are poorly supported by client http clients, so you advised to only use GET or POST")); continue; //advance to next service method } } } if (service != null) { JsonSchemaUtilities.ApplyDescription(service, methodElement); service.Add("target", ResolveEndpoint(smdXmlComment, methodTarget)); if (!string.IsNullOrWhiteSpace(methodUriTemplate)) { service.Add("uriTemplate", methodUriTemplate); } service.Add("contentType", "application/json");// TODO: declare this in meta or get from WebGet/WebInvoke service.Add("responseContentType", "application/json");// TODO: declare this in meta or get from WebGet/WebInvoke service.Add("transport", methodTransport); try { smdBase.Add(methodName, service); } catch (ArgumentException e) { result.AddMetadataGenerationError(new MetadataGenerationError(MetadataType.SMD, type, string.Format("A service with the method name {0} already exists", methodName), "Ensure that methods names are unique across services")); return result; } // this is not accurate/valid SMD for GET but dojox.io.services is not, yet, a very good // implementation of the SMD spec, which is funny as they were both written by the same person. service.Add("envelope", methodEnvelope); // determine if return type is object or primitive JObject returnType = null; if (Type.GetTypeCode(method.ReturnType) == TypeCode.Object) { if (method.ReturnType.Name != "Void") { string methodReturnTypeName = method.ReturnType.Name; if (schema["properties"][methodReturnTypeName] == null) { result.AddMetadataGenerationError(new MetadataGenerationError(MetadataType.SMD, type, "Schema missing referenced return type " + methodReturnTypeName + " for method " + method.Name, "All types used by services must be decorated with the <jschema> tag. See https://github.com/cityindex/RESTful-Webservice-Schema/wiki/Howto-write-XML-comments-for-JSchema")); } returnType = new JObject(new JProperty("$ref", JsonSchemaUtilities.RootDelimiter + methodReturnTypeName)); } else { returnType = null; } } else if (Type.GetTypeCode(method.ReturnType) == TypeCode.Empty) { returnType = null; } else { returnType = new JObject(new JProperty("type", method.ReturnType.GetSchemaType()["type"].Value<string>())); } if (returnType != null) { service.Add("returns", returnType); } SetStringAttribute(methodSmdElement, service, "group"); SetIntAttribute(methodSmdElement, service, "cacheDuration"); SetStringAttribute(methodSmdElement, service, "throttleScope"); var paramResult = AddParameters(type, method, methodElement, service, includeDemoValue, schema); if (paramResult.HasErrors) result.AddMetadataGenerationErrors(paramResult.MetadataGenerationErrors); } } if (!result.HasErrors) result.AddMetadataGenerationSuccess(new MetadataGenerationSuccess(MetadataType.SMD, type)); } return result; }
public JObject EmitDtoJson(XmlDocSource xmlDocSource) { var schemaObj = new JObject(); var assemblies = xmlDocSource.Dtos.Select(a => a.Assembly).ToArray(); schemaObj["version"] = xmlDocSource.RouteAssembly.Version; var schemaProperties = new JObject(); schemaObj["properties"] = schemaProperties; var types = UtilityExtensions.GetSchemaTypes(assemblies); var exception = new MetadataValidationException(typeof(object), "", "Errors generating meta for types", ""); foreach (Type type in types) { try { var typeNode = type.GetXmlDocTypeNodeWithJSchema(); var jschemaXml = JschemaXmlComment.CreateFromXml(typeNode.XPathSelectElement("jschema")); if (jschemaXml.Exclude) { continue; //Skip to next type } var typeObj = new JObject(); typeObj["id"] = type.Name; if (type.IsEnum) { RenderEnum(type, typeObj); } else if (type.IsClass) { RenderType(type, typeObj); } else { throw new NotSupportedException(type.Name + " is not supported "); } ApplyDescription(typeObj, typeNode); if (jschemaXml.DemoValue != null) { typeObj["demoValue"] = jschemaXml.DemoValue; } schemaProperties.Add(type.Name, typeObj); } catch (MetadataValidationException ex) { exception.AggregatedExceptions.Add(ex); } } if (exception.AggregatedExceptions.Count > 0) { throw exception; } return(schemaObj); }