func (env *Environment) loadSchemaIncludes() error { manager := schema.GetManager() schemaIncludeValue, err := env.VM.Get(schemaIncludesVar) if err != nil { return fmt.Errorf("%s string array not specified", schemaIncludesVar) } schemaIncludesFilenames, err := gohan_otto.GetStringList(schemaIncludeValue) if err != nil { return fmt.Errorf("Bad type of %s - expected an array of strings but the type is %s", schemaIncludesVar, schemaIncludeValue.Class()) } for _, schemaIncludes := range schemaIncludesFilenames { var data []byte if data, err = ioutil.ReadFile(schemaIncludes); err != nil { return err } schemas := strings.Split(string(data), "\n") for _, schema := range schemas { if schema == "" || strings.HasPrefix(schema, "#") { continue } if err = manager.LoadSchemaFromFile(schema); err != nil { return err } } } return nil }
func clearTable(tx transaction.Transaction, s *schema.Schema) error { if s.IsAbstract() { return nil } for _, schema := range schema.GetManager().Schemas() { if schema.ParentSchema == s { err := clearTable(tx, schema) if err != nil { return err } } else { for _, property := range schema.Properties { if property.Relation == s.Singular { err := clearTable(tx, schema) if err != nil { return err } } } } } resources, _, err := tx.List(s, nil, nil) if err != nil { return err } for _, resource := range resources { err = tx.Delete(s, resource.ID()) if err != nil { return err } } return nil }
func (env *Environment) loadSchemas() error { schemaValue, err := env.VM.Get(schemasVar) if err != nil { return fmt.Errorf("%s string array not specified", schemasVar) } schemaFilenames, err := gohan_otto.GetStringList(schemaValue) if err != nil { return fmt.Errorf("Bad type of %s - expected an array of strings", schemasVar) } manager := schema.GetManager() for _, schema := range schemaFilenames { err = manager.LoadSchemaFromFile(schema) if err != nil { return err } } environmentManager := extension.GetManager() for schemaID := range manager.Schemas() { environmentManager.RegisterEnvironment(schemaID, env) } pathValue, err := env.VM.Get(pathVar) if err != nil || !pathValue.IsString() { return fmt.Errorf("%s string not specified", pathVar) } pathString, _ := pathValue.ToString() return env.LoadExtensionsForPath(manager.Extensions, pathString) }
//GenTableDef generates create table sql func (db *DB) GenTableDef(s *schema.Schema, cascade bool) string { schemaManager := schema.GetManager() cols, relations := db.genTableCols(s, cascade, nil) if s.Parent != "" { foreignSchema, _ := schemaManager.Schema(s.Parent) cascadeString := "" if cascade || s.OnParentDeleteCascade { cascadeString = "on delete cascade" } relations = append(relations, fmt.Sprintf("foreign key(`%s_id`) REFERENCES `%s`(id) %s", s.Parent, foreignSchema.GetDbTableName(), cascadeString)) } if s.StateVersioning() { cols = append(cols, quote(configVersionColumnName)+"int not null default 1") cols = append(cols, quote(stateVersionColumnName)+"int not null default 0") cols = append(cols, quote(stateErrorColumnName)+"text not null default ''") cols = append(cols, quote(stateColumnName)+"text not null default ''") cols = append(cols, quote(stateMonitoringColumnName)+"text not null default ''") } cols = append(cols, relations...) tableSQL := fmt.Sprintf("create table `%s` (%s);\n", s.GetDbTableName(), strings.Join(cols, ",")) log.Debug("Creating table: " + tableSQL) return tableSQL }
//GetSchema returns the schema filtered and trimmed for a specific user or nil when the user shouldn't see it at all func GetSchema(s *schema.Schema, authorization schema.Authorization) (result *schema.Resource, err error) { manager := schema.GetManager() metaschema, _ := manager.Schema("schema") policy, _ := manager.PolicyValidate("read", s.GetPluralURL(), authorization) if policy == nil { return } if s.IsAbstract() { return } rawSchema := s.JSON() filteredSchema := util.ExtendMap(nil, s.JSONSchema) rawSchema["schema"] = filteredSchema schemaProperties, schemaPropertiesOrder, schemaRequired := policy.FilterSchema( util.MaybeMap(s.JSONSchema["properties"]), util.MaybeStringList(s.JSONSchema["propertiesOrder"]), util.MaybeStringList(s.JSONSchema["required"])) filteredSchema["properties"] = schemaProperties filteredSchema["propertiesOrder"] = schemaPropertiesOrder filteredSchema["required"] = schemaRequired result, err = schema.NewResource(metaschema, rawSchema) if err != nil { log.Warning("%s %s", result, err) return } return }
// mapChildNamespaceRoute sets a handler returning a dictionary of resources // supported by a certain API version identified by the given namespace func mapChildNamespaceRoute(route martini.Router, namespace *schema.Namespace) { log.Debug("[Path] %s", namespace.GetFullPrefix()) route.Get( namespace.GetFullPrefix(), func(w http.ResponseWriter, r *http.Request, p martini.Params, context martini.Context) { resources := []schema.NamespaceResource{} for _, s := range schema.GetManager().Schemas() { if s.NamespaceID == namespace.ID { resources = append(resources, schema.NamespaceResource{ Links: []schema.Link{ schema.Link{ Href: s.GetPluralURL(), Rel: "self", }, }, Name: s.Singular, Collection: s.Plural, }) } } routes.ServeJson(w, map[string][]schema.NamespaceResource{"resources": resources}) }, ) }
// mapTopLevelNamespaceRoute maps route listing available subnamespaces (versions) // for a top-level namespace func mapTopLevelNamespaceRoute(route martini.Router, namespace *schema.Namespace) { log.Debug("[Path] %s/", namespace.GetFullPrefix()) route.Get( namespace.GetFullPrefix()+"/", func(w http.ResponseWriter, r *http.Request, p martini.Params, context martini.Context) { versions := []schema.Version{} for _, childNamespace := range schema.GetManager().Namespaces() { if childNamespace.Parent == namespace.ID { versions = append(versions, schema.Version{ Status: "SUPPORTED", ID: childNamespace.Prefix, Links: []schema.Link{ schema.Link{ Href: childNamespace.GetFullPrefix() + "/", Rel: "self", }, }, }) } } if len(versions) != 0 { versions[len(versions)-1].Status = "CURRENT" } routes.ServeJson(w, map[string][]schema.Version{"versions": versions}) }) }
func getInitDbCommand() cli.Command { return cli.Command{ Name: "init-db", ShortName: "idb", Usage: "Initialize DB backend with given schema file", Description: ` Initialize empty database with given schema. Setting meta-schema option will additionaly populate meta-schema table with schema resources. Useful for development purposes.`, Flags: []cli.Flag{ cli.StringFlag{Name: "database-type, t", Value: "sqlite3", Usage: "Backend datebase type"}, cli.StringFlag{Name: "database, d", Value: "gohan.db", Usage: "DB connection string"}, cli.StringFlag{Name: "schema, s", Value: "etc/schema/gohan.json", Usage: "Schema definition"}, cli.BoolFlag{Name: "drop-on-create", Usage: "If true, old database will be dropped"}, cli.BoolFlag{Name: "cascade", Usage: "If true, FOREIGN KEYS in database will be created with ON DELETE CASCADE"}, cli.StringFlag{Name: "meta-schema, m", Value: "", Usage: "Meta-schema file (optional)"}, }, Action: func(c *cli.Context) { dbType := c.String("database-type") dbConnection := c.String("database") schemaFile := c.String("schema") metaSchemaFile := c.String("meta-schema") dropOnCreate := c.Bool("drop-on-create") cascade := c.Bool("cascade") manager := schema.GetManager() manager.LoadSchemasFromFiles(schemaFile, metaSchemaFile) err := db.InitDBWithSchemas(dbType, dbConnection, dropOnCreate, cascade) if err != nil { util.ExitFatal(err) } fmt.Println("DB is initialized") }, } }
func getValidateCommand() cli.Command { return cli.Command{ Name: "validate", ShortName: "v", Usage: "Validate document", Description: ` Validate document against schema. It's especially useful to validate schema files against gohan meta-schema.`, Flags: []cli.Flag{ cli.StringFlag{Name: "schema, s", Value: "etc/schema/gohan.json", Usage: "Schema path"}, cli.StringFlag{Name: "document, d", Value: "etc/apps/example.json", Usage: "Document path"}, }, Action: func(c *cli.Context) { schemaPath := c.String("schema") documentPath := c.String("document") manager := schema.GetManager() err := manager.LoadSchemaFromFile(schemaPath) if err != nil { util.ExitFatal("Failed to parse schema:", err) } err = manager.LoadSchemaFromFile(documentPath) if err == nil { fmt.Println("Schema is valid") } else { util.ExitFatalf("Schema is not valid, see errors below:\n%s\n", err) } }, } }
//DBList lists data from database. func DBList(tx transaction.Transaction, schemaID string, filter map[string]interface{}) ([]interface{}, error) { manager := schema.GetManager() schemaObj, ok := manager.Schema(schemaID) if !ok { return nil, fmt.Errorf("Schema %s not found", schemaID) } for key, value := range filter { switch v := value.(type) { case string: filter[key] = []string{v} case bool: filter[key] = []string{fmt.Sprintf("%v", v)} case int: filter[key] = []string{fmt.Sprintf("%v", v)} case []interface{}: filterList := make([]string, len(v)) for _, item := range v { filterList = append(filterList, fmt.Sprintf("%v", item)) } filter[key] = filterList } } resources, _, err := tx.List(schemaObj, filter, nil) resp := []interface{}{} for _, resource := range resources { resp = append(resp, resource.Data()) } return resp, err }
//InitDBWithSchemas initializes database using schemas stored in Manager func InitDBWithSchemas(dbType, dbConnection string, dropOnCreate, cascade bool) error { aDb, err := ConnectDB(dbType, dbConnection) if err != nil { return err } schemaManager := schema.GetManager() schemas := schemaManager.OrderedSchemas() if len(schemas) == 0 { return fmt.Errorf(noSchemasInManagerError) } if dropOnCreate { for i := len(schemas) - 1; i >= 0; i-- { s := schemas[i] log.Debug("Dropping table '%s'", s.Plural) err = aDb.DropTable(s) if err != nil { log.Fatal("Error during deleting table:", err.Error()) } } } for _, s := range schemas { log.Debug("Registering schema %s", s.ID) err = aDb.RegisterTable(s, cascade) if err != nil { message := "Error during registering table: %s" if strings.Contains(err.Error(), "already exists") { log.Warning(message, err.Error()) } else { log.Fatal(message, err.Error()) } } } return nil }
//GohanModelCreate creates gohan resource and running extensions func GohanModelCreate(context map[string]interface{}, schemaID string, dataMap map[string]interface{}) (interface{}, error) { currentSchema, err := getSchema(schemaID) if err != nil { return nil, err } context["schema"] = currentSchema context["path"] = currentSchema.GetPluralURL() manager := schema.GetManager() resourceObj, err := manager.LoadResource(currentSchema.ID, dataMap) if err != nil { return nil, err } if err := resources.CreateResourceInTransaction( context, resourceObj); err != nil { return nil, err } response, ok := context["response"].(map[string]interface{}) if !ok { return nil, fmt.Errorf("No response") } return response[currentSchema.Singular], nil }
func getValidateCommand() cli.Command { return cli.Command{ Name: "validate", ShortName: "v", Usage: "Validate document", Description: ` Validate document against schema. It's especially useful to validate schema files against gohan meta-schema.`, Flags: []cli.Flag{ cli.StringFlag{Name: "schema, s", Value: "etc/schema/gohan.json", Usage: "Schema path"}, cli.StringSliceFlag{Name: "document, d", Usage: "Document path"}, }, Action: func(c *cli.Context) { schemaPath := c.String("schema") documentPaths := c.StringSlice("document") if len(documentPaths) == 0 { util.ExitFatalf("At least one document should be specified for validation\n") } manager := schema.GetManager() err := manager.LoadSchemaFromFile(schemaPath) if err != nil { util.ExitFatal("Failed to parse schema:", err) } for _, documentPath := range documentPaths { err = manager.LoadSchemaFromFile(documentPath) if err != nil { util.ExitFatalf("Schema is not valid, see errors below:\n%s\n", err) } } fmt.Println("Schema is valid") }, } }
func (tl *transactionEventLogger) logEvent(eventType string, resource *schema.Resource, version int64) error { schemaManager := schema.GetManager() eventSchema, ok := schemaManager.Schema("event") if !ok { return fmt.Errorf("event schema not found") } if resource.Schema().Metadata["nosync"] == true { log.Debug("skipping event logging for schema: %s", resource.Schema().ID) return nil } body, err := resource.JSONString() if err != nil { return fmt.Errorf("Error during event resource deserialisation: %s", err.Error()) } eventResource, err := schema.NewResource(eventSchema, map[string]interface{}{ "type": eventType, "path": resource.Path(), "version": version, "body": body, "timestamp": int64(time.Now().Unix()), }) tl.eventLogged = true return tl.Transaction.Create(eventResource) }
// UpdateResourceInTransaction updates resource in db in transaction func UpdateResourceInTransaction( context middleware.Context, resourceSchema *schema.Schema, resourceID string, dataMap map[string]interface{}, tenantIDs []string) error { manager := schema.GetManager() mainTransaction := context["transaction"].(transaction.Transaction) environmentManager := extension.GetManager() environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) if !ok { return fmt.Errorf("No environment for schema") } resource, err := mainTransaction.Fetch( resourceSchema, resourceID, tenantIDs) if err != nil { return ResourceError{err, err.Error(), WrongQuery} } policy := context["policy"].(*schema.Policy) // apply property filter err = policy.ApplyPropertyConditionFilter(schema.ActionUpdate, resource.Data(), dataMap) if err != nil { return ResourceError{err, "", Unauthorized} } err = resource.Update(dataMap) if err != nil { return ResourceError{err, err.Error(), WrongData} } context["resource"] = resource.Data() if err := extension.HandleEvent(context, environment, "pre_update_in_transaction"); err != nil { return err } dataMap, ok = context["resource"].(map[string]interface{}) if !ok { return fmt.Errorf("Resource not JSON: %s", err) } resource, err = manager.LoadResource(resourceSchema.ID, dataMap) if err != nil { return fmt.Errorf("Loading Resource failed: %s", err) } err = mainTransaction.Update(resource) if err != nil { return ResourceError{err, fmt.Sprintf("Failed to store data in database: %v", err), UpdateFailed} } response := map[string]interface{}{} response[resourceSchema.Singular] = resource.Data() context["response"] = response if err := extension.HandleEvent(context, environment, "post_update_in_transaction"); err != nil { return err } return nil }
//DBUpdate updates a resource in a db. func DBUpdate(tx transaction.Transaction, schemaID string, data map[string]interface{}) error { manager := schema.GetManager() resource, err := manager.LoadResource(schemaID, data) if err != nil { return err } return tx.Update(resource) }
//DBColumn makes partiall part of sql query from schema func DBColumn(schemaID string, join bool) (string, error) { manager := schema.GetManager() schemaObj, ok := manager.Schema(schemaID) if !ok { return "", fmt.Errorf("Schema %s not found", schemaID) } return strings.Join(sql.MakeColumns(schemaObj, schemaObj.GetDbTableName(), join), ", "), nil }
//DBDelete deletes a resource in a db. func DBDelete(tx transaction.Transaction, schemaID string, id string) error { manager := schema.GetManager() schemaObj, ok := manager.Schema(schemaID) if !ok { return fmt.Errorf("Schema %s not found", schemaID) } return tx.Delete(schemaObj, id) }
func getSchema(schemaID string) (*schema.Schema, error) { manager := schema.GetManager() schema, ok := manager.Schema(schemaID) if !ok { return nil, fmt.Errorf(unknownSchemaErrorMesssageFormat, schemaID) } return schema, nil }
func (server *Server) syncEvent(resource *schema.Resource) error { schemaManager := schema.GetManager() eventSchema, _ := schemaManager.Schema("event") tx, err := server.db.Begin() if err != nil { return err } defer tx.Close() eventType := resource.Get("type").(string) resourcePath := resource.Get("path").(string) body := resource.Get("body").(string) path := generatePath(resourcePath, body) version, ok := resource.Get("version").(int) if !ok { log.Debug("cannot cast version value in int for %s", path) } log.Debug("event %s", eventType) if eventType == "create" || eventType == "update" { log.Debug("set %s on sync", path) content, err := json.Marshal(map[string]interface{}{ "body": body, "version": version, }) if err != nil { log.Error(fmt.Sprintf("When marshalling sync object: %s", err)) return err } err = server.sync.Update(path, string(content)) if err != nil { log.Error(fmt.Sprintf("%s on sync", err)) return err } } else if eventType == "delete" { log.Debug("delete %s", path) err = server.sync.Delete(path) if err != nil { log.Error(fmt.Sprintf("Delete from sync failed %s", err)) return err } } log.Debug("delete event %d", resource.Get("id")) id := resource.Get("id") err = tx.Delete(eventSchema, id) if err != nil { log.Error(fmt.Sprintf("delete failed: %s", err)) return err } err = tx.Commit() if err != nil { log.Error(fmt.Sprintf("commit failed: %s", err)) return err } return nil }
func getConvertCommand() cli.Command { return cli.Command{ Name: "convert", ShortName: "conv", Usage: "Convert DB", Description: ` Gohan convert can be used to migrate Gohan resources between different types of databases. Setting meta-schema option will additionaly convert meta-schema table with schema resources. Useful for development purposes.`, Flags: []cli.Flag{ cli.StringFlag{Name: "in-type, it", Value: "", Usage: "Input db type (yaml, json, sqlite3, mysql)"}, cli.StringFlag{Name: "in, i", Value: "", Usage: "Input db connection spec (or filename)"}, cli.StringFlag{Name: "out-type, ot", Value: "", Usage: "Output db type (yaml, json, sqlite3, mysql)"}, cli.StringFlag{Name: "out, o", Value: "", Usage: "Output db connection spec (or filename)"}, cli.StringFlag{Name: "schema, s", Value: "", Usage: "Schema file"}, cli.StringFlag{Name: "meta-schema, m", Value: "", Usage: "Meta-schema file (optional)"}, }, Action: func(c *cli.Context) { inType, in := c.String("in-type"), c.String("in") if inType == "" || in == "" { util.ExitFatal("Need to provide input database specification") } outType, out := c.String("out-type"), c.String("out") if outType == "" || out == "" { util.ExitFatal("Need to provide output database specification") } schemaFile := c.String("schema") if schemaFile == "" { util.ExitFatal("Need to provide schema file") } metaSchemaFile := c.String("meta-schema") schemaManager := schema.GetManager() err := schemaManager.LoadSchemasFromFiles(schemaFile, metaSchemaFile) if err != nil { util.ExitFatal("Error loading schema:", err) } inDB, err := db.ConnectDB(inType, in) if err != nil { util.ExitFatal(err) } outDB, err := db.ConnectDB(outType, out) if err != nil { util.ExitFatal(err) } err = db.CopyDBResources(inDB, outDB) if err != nil { util.ExitFatal(err) } fmt.Println("Conversion complete") }, } }
//SNMP Process //Experimental func startSNMPProcess(server *Server) { manager := schema.GetManager() config := util.GetConfig() enabled := config.GetParam("snmp", nil) if enabled == nil { return } host := config.GetString("snmp/address", "localhost:162") path := "snmp://" env := newEnvironment(server.db, server.keystoneIdentity, server.timelimit) err := env.LoadExtensionsForPath(manager.Extensions, path) if err != nil { log.Fatal(fmt.Sprintf("Extensions parsing error: %v", err)) } addr, err := net.ResolveUDPAddr("udp", host) if err != nil { log.Fatal(err) } conn, err := net.ListenUDP("udp", addr) if err != nil { log.Fatal(err) } buf := make([]byte, 1024) go func() { defer conn.Close() for server.running { rlen, remote, err := conn.ReadFromUDP(buf) if err != nil { log.Error(fmt.Sprintf("[SNMP] failed read bytes %s", err)) return } decoded, err := wapsnmp.DecodeSequence(buf[:rlen]) if err != nil { log.Error(fmt.Sprintf("[SNMP] failed decode bytes %s", err)) continue } infos := decoded[3].([]interface{})[4].([]interface{})[1:] trap := map[string]string{} for _, info := range infos { listInfo := info.([]interface{}) oid := listInfo[1].(wapsnmp.Oid) trap[oid.String()] = fmt.Sprintf("%v", listInfo[2]) } context := map[string]interface{}{ "trap": trap, "remote": remote, } if err := env.HandleEvent("notification", context); err != nil { log.Warning(fmt.Sprintf("extension error: %s", err)) } } }() }
//GohanSchema returns gohan schema object by schemaID. func GohanSchema(schemaID string) (*schema.Schema, error) { var err error manager := schema.GetManager() schema, ok := manager.Schema(schemaID) if !ok { err = fmt.Errorf("Schema %s isn't loaded", schemaID) } return schema, err }
//GenTableDef generates table create sql func (db *DB) GenTableDef(s *schema.Schema, cascade bool) string { schemaManager := schema.GetManager() var cols []string var relations []string cascadeString := "" if cascade { cascadeString = "on delete cascade" } for _, property := range s.Properties { handler := db.handlers[property.Type] dataType := property.SQLType if db.sqlType == "sqlite3" { dataType = strings.Replace(dataType, "auto_increment", "autoincrement", 1) } if dataType == "" { dataType = handler.dataType(&property) if property.ID == "id" { dataType += " primary key" } else { if property.Nullable { dataType += " null" } else { dataType += " not null" } if property.Unique { dataType += " unique" } } } sql := "`" + property.ID + "`" + dataType cols = append(cols, sql) if property.Relation != "" { foreignSchema, _ := schemaManager.Schema(property.Relation) if foreignSchema != nil { relations = append(relations, fmt.Sprintf("foreign key(`%s`) REFERENCES `%s`(id) %s", property.ID, foreignSchema.GetDbTableName(), cascadeString)) } } } if s.Parent != "" { foreignSchema, _ := schemaManager.Schema(s.Parent) relations = append(relations, fmt.Sprintf("foreign key(`%s_id`) REFERENCES `%s`(id) %s", s.Parent, foreignSchema.GetDbTableName(), cascadeString)) } if s.StateVersioning() { cols = append(cols, quote(configVersionColumnName)+"int not null default 1") cols = append(cols, quote(stateVersionColumnName)+"int not null default 0") cols = append(cols, quote(stateErrorColumnName)+"text not null default ''") cols = append(cols, quote(stateColumnName)+"text not null default ''") cols = append(cols, quote(stateMonitoringColumnName)+"text not null default ''") } cols = append(cols, relations...) tableSQL := fmt.Sprintf("create table `%s` (%s);\n", s.GetDbTableName(), strings.Join(cols, ",")) log.Debug("Creating table: " + tableSQL) return tableSQL }
func (env *Environment) registerEnvironments() error { manager := schema.GetManager() environmentManager := extension.GetManager() for schemaID := range manager.Schemas() { // Note: the following code ignores errors related to registration // of an environment that has already been registered environmentManager.RegisterEnvironment(schemaID, env) } return nil }
func (env *Environment) loadExtensions() error { manager := schema.GetManager() pathValue, err := env.VM.Get(pathVar) if err != nil || !pathValue.IsString() { return fmt.Errorf("%s string not specified", pathVar) } pathString, _ := pathValue.ToString() return env.LoadExtensionsForPath(manager.Extensions, pathString) }
func initBenchmarkDatabase() error { schema.ClearManager() manager := schema.GetManager() manager.LoadSchemasFromFiles("../etc/apps/example.yaml", "../etc/schema/gohan.json") err := db.InitDBWithSchemas("mysql", "root@tcp(localhost:3306)/gohan_test", false, false) if err != nil { return err } return nil }
// NewEnvironmentForPath creates an extension environment and loads extensions for path func (server *Server) NewEnvironmentForPath(name string, path string) (env extension.Environment, err error) { manager := schema.GetManager() env = server.newEnvironment(name) err = env.LoadExtensionsForPath(manager.Extensions, path) if err != nil { err = fmt.Errorf("Extensions parsing error: %v", err) } return }
func loadPolicy(context middleware.Context, action, path string, auth schema.Authorization) (*schema.Policy, error) { manager := schema.GetManager() policy, role := manager.PolicyValidate(action, path, auth) if policy == nil { err := fmt.Errorf(fmt.Sprintf("No matching policy: %s %s", action, path)) return nil, ResourceError{err, err.Error(), Unauthorized} } context["policy"] = policy context["role"] = role return policy, nil }
// MapNamespacesRoutes maps routes for all namespaces func MapNamespacesRoutes(route martini.Router) { manager := schema.GetManager() for _, namespace := range manager.Namespaces() { if namespace.IsTopLevel() { mapTopLevelNamespaceRoute(route, namespace) } else { mapChildNamespaceRoute(route, namespace) } } }