void parseMethods(JProperty jpMethods, Service svc, string connectionString, IDictionary<string, ParameterType> parameterTypes, IDictionary<string, Method> methods, Func<string, string> tokenLookup) { if (jpMethods.Value.Type != JTokenType.Object) { svc.Errors.Add("The `methods` property is expected to be of type object"); return; } var joMethods = (JObject)jpMethods.Value; // Parse each method: foreach (var jpMethod in joMethods.Properties()) { // Is the method set to null? if (jpMethod.Value.Type == JTokenType.Null) { // Remove it: methods.Remove(jpMethod.Name); continue; } if (jpMethod.Value.Type != JTokenType.Object) { svc.Errors.Add("The method property `{0}` is expected to be of type object".F(jpMethod.Name)); continue; } var joMethod = ((JObject)jpMethod.Value); // Create a clone of the inherited descriptor or a new descriptor: Method method; if (methods.TryGetValue(jpMethod.Name, out method)) method = method.Clone(); else { method = new Method() { Name = jpMethod.Name, ConnectionString = connectionString, Errors = new List<string>(5) }; } methods[jpMethod.Name] = method; method.Service = svc; Debug.Assert(method.Errors != null); // Parse the definition: method.Description = getString(joMethod.Property("description")).Interpolate(tokenLookup); method.DeprecatedMessage = getString(joMethod.Property("deprecated")).Interpolate(tokenLookup); // Parse connection: var jpConnection = joMethod.Property("connection"); if (jpConnection != null) { method.ConnectionString = parseConnection(jpConnection, method.Errors, (s) => s.Interpolate(tokenLookup)); } // Parse the parameters: var jpParameters = joMethod.Property("parameters"); if (jpParameters != null) { parseParameters(jpParameters, method, parameterTypes, tokenLookup); } // Parse query: var jpQuery = joMethod.Property("query"); if (jpQuery != null) { parseQuery(jpQuery, method, tokenLookup); } if (method.Query == null) { method.Errors.Add("No query specified"); } // Parse result mapping: var jpMapping = joMethod.Property("result"); if (jpMapping != null) { var joMapping = (JObject)jpMapping.Value; method.Mapping = parseMapping(joMapping, method.Errors); } } // foreach (var method) }
IHttpResponseAction errorsMethod(SHA1Hashed<ServicesOffering> main, Service service, Method method) { return new JsonRootResponse( links: new RestfulLink[] { }, meta: new { configHash = main.HashHexString, serviceName = service.Name, methodName = method.Name, methodErrors = method.Errors } ); }
IHttpResponseAction errorsService(SHA1Hashed<ServicesOffering> main, Service service) { return new JsonRootResponse( links: new RestfulLink[] { }, meta: new { configHash = main.HashHexString, serviceName = service.Name, serviceErrors = service.Errors, methodsErrors = service.Methods.Where(m => m.Value.Errors.Any()).ToDictionary( m => m.Key, m => new { errors = m.Value.Errors }, StringComparer.OrdinalIgnoreCase ) } ); }
ServiceCollection ParseConfigData(JObject joConfig) { // Create the ServiceCollection that will be returned: var coll = new ServiceCollection() { Errors = new List<string>(5), Services = new Dictionary<string, Service>(StringComparer.OrdinalIgnoreCase) }; // Parse the root token dictionary first: var rootTokens = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var jpTokens = joConfig.Property("$"); if (jpTokens != null) { // Extract the key/value pairs onto a copy of the token dictionary: foreach (var prop in ((JObject)jpTokens.Value).Properties()) { // Newtonsoft.Json already guarantees that property names must be unique; it will throw a JsonReaderException in that case. #if false if (rootTokens.ContainsKey(prop.Name)) { coll.Errors.Add("A token named '{0}' already exists in the '$' collection; cannot add a duplicate".F(prop.Name)); continue; } #endif rootTokens[prop.Name] = getString(prop); } } // Parse root parameter types: var rootParameterTypes = new Dictionary<string, ParameterType>(StringComparer.OrdinalIgnoreCase); var jpParameterTypes = joConfig.Property("parameterTypes"); if (jpParameterTypes != null) { var errors = new List<string>(5); parseParameterTypes(jpParameterTypes, errors, rootParameterTypes, (s) => s); if (errors.Count > 0) { // Add errors encountered and keep going: coll.Errors.AddRange(errors); } } // 'services' section is not optional: JToken jtServices; if (!joConfig.TryGetValue("services", out jtServices)) { coll.Errors.Add("A 'services' section is required"); return coll; } var joServices = (JObject)jtServices; // Parse each service descriptor: foreach (var jpService in joServices.Properties()) { if (jpService.Name == "$") continue; var joService = (JObject)jpService.Value; // This property is a service: var svcErrors = new List<string>(5); Service baseService = null; IDictionary<string, string> tokens; string connectionString; IDictionary<string, ParameterType> parameterTypes; IDictionary<string, Method> methods; // Go through the properties of the named service object: var jpBase = joService.Property("base"); if (jpBase != null) { // NOTE(jsd): Forward references are not allowed. Base service must be defined before the current service in document order. string baseName = getString(jpBase); if (!coll.Services.TryGetValue(baseName, out baseService)) { coll.Errors.Add("Unknown base service name '{0}' for service '{1}'; services must declared in document order".F(baseName, jpService.Name)); continue; } // Create copies of what's inherited from the base service to mutate: connectionString = baseService.ConnectionString; tokens = new Dictionary<string, string>(baseService.Tokens); parameterTypes = new Dictionary<string, ParameterType>(baseService.ParameterTypes, StringComparer.OrdinalIgnoreCase); methods = new Dictionary<string, Method>(baseService.Methods, StringComparer.OrdinalIgnoreCase); } else { // Nothing inherited: connectionString = null; tokens = new Dictionary<string, string>(rootTokens, StringComparer.OrdinalIgnoreCase); parameterTypes = new Dictionary<string, ParameterType>(rootParameterTypes, StringComparer.OrdinalIgnoreCase); methods = new Dictionary<string, Method>(StringComparer.OrdinalIgnoreCase); } // Parse tokens: jpTokens = joService.Property("$"); if (jpTokens != null) { // Assigning a `null`? if (jpTokens.Value.Type == JTokenType.Null) { // Clear out all inherited tokens: tokens.Clear(); } else { // Extract the key/value pairs onto our token dictionary: foreach (var prop in ((JObject)jpTokens.Value).Properties()) // NOTE(jsd): No interpolation over tokens themselves. tokens[prop.Name] = getString(prop); } } // A lookup-or-null function used with `Interpolate`: Func<string, string> tokenLookup = (key) => { string value; // TODO: add to a Warnings collection! if (!tokens.TryGetValue(key, out value)) return null; return value; }; // Parse connection: var jpConnection = joService.Property("connection"); if (jpConnection != null) { connectionString = parseConnection(jpConnection, svcErrors, (s) => s.Interpolate(tokenLookup)); } // Parse the parameter types: jpParameterTypes = joService.Property("parameterTypes"); if (jpParameterTypes != null) { // Assigning a `null`? if (jpParameterTypes.Value.Type == JTokenType.Null) { // Clear out all inherited parameter types: parameterTypes.Clear(); } else { parseParameterTypes(jpParameterTypes, svcErrors, parameterTypes, (s) => s.Interpolate(tokenLookup)); } } // Create the service descriptor: Service svc = new Service() { Name = jpService.Name, BaseService = baseService, ConnectionString = connectionString, ParameterTypes = parameterTypes, Methods = methods, Tokens = tokens, Errors = svcErrors }; // Parse the methods: var jpMethods = joService.Property("methods"); if (jpMethods != null) { parseMethods(jpMethods, svc, connectionString, parameterTypes, methods, tokenLookup); } // Add the parsed service descriptor: coll.Services.Add(jpService.Name, svc); } // 'aliases' section is optional: var jpAliases = joConfig.Property("aliases"); if (jpAliases != null) { // Parse the named aliases: var joAliases = (JObject)jpAliases.Value; foreach (var jpAlias in joAliases.Properties()) { // Add the existing Service reference to the new name: string svcName = getString(jpAlias); // Must find the existing service by its name first: Service svcref; if (!coll.Services.TryGetValue(svcName, out svcref)) { coll.Errors.Add("Unknown service name '{0}' for alias '{1}'".F(svcName, jpAlias.Name)); continue; } // Can't add an alias name that already exists: if (coll.Services.ContainsKey(jpAlias.Name)) { coll.Errors.Add("Cannot add alias name '{0}' because that name is already in use".F(jpAlias.Name)); continue; } coll.Services.Add(jpAlias.Name, svcref); } } return coll; }
IHttpResponseAction debugService(SHA1Hashed<ServicesOffering> main, Service service) { return new JsonRootResponse( links: new RestfulLink[] { RestfulLink.Create("self", "/debug/{0}".F(service.Name), "self"), RestfulLink.Create("parent", "/debug", "parent"), RestfulLink.Create("meta", "/meta/{0}".F(service.Name)), RestfulLink.Create("errors", "/errors/{0}".F(service.Name)) }, meta: new { configHash = main.HashHexString, service = new ServiceDebug(service) } ); }
IHttpResponseAction debugMethod(SHA1Hashed<ServicesOffering> main, Service service, Method method) { return new JsonRootResponse( links: new RestfulLink[] { RestfulLink.Create("self", "/debug/{0}/{1}".F(service.Name, method.Name), "self"), RestfulLink.Create("parent", "/debug/{0}".F(service.Name), "parent"), RestfulLink.Create("meta", "/meta/{0}/{1}".F(service.Name, method.Name)), RestfulLink.Create("errors", "/errors/{0}/{1}".F(service.Name, method.Name)), RestfulLink.Create("data", "/data/{0}/{1}".F(service.Name, method.Name)) }, meta: new { configHash = main.HashHexString, method = new MethodDebug(method) } ); }
internal ServiceDebug(Service desc) { this.desc = desc; }
internal ServiceMetadata(Service desc) { this.desc = desc; }