# HG changeset patch # User Sean Russell # Date 1308884932 14400 # Thu Jun 23 23:08:52 2011 -0400 # Branch documentation-updates # Node ID 6de3451d286dd50ea7c0a2c103705dc40a2a3c8d # Parent 3a45603f7e75ce106850359db8113aee3e9e1cf7 Refactoring to improve encapsulation. config got smaller and more focused; server got larger and now handles all HTTP-related stuff. Previously, config didn't explicitly do HTTP, but it was pretty obviously REST-oriented. diff --git a/config.go b/config.go --- a/config.go +++ b/config.go @@ -2,7 +2,6 @@ import ( "strings" - "strconv" ) type Store struct { @@ -21,81 +20,15 @@ } const ( - WORKSPACES = "workspaces" - COMPONENTS = "components" WORKSPACE = "0" ) - -// - GET retrieves a component config -// -// /components -// Retrieves a list of all components -// Response: JSON - List of strings of component names -// -// /components/COMPONENT[/..][?rev=REV] -// Retrieves a configuration for a single component, optionally by version. -// Response: JSON - JSON serialization of component. -// -// /workspaces[?limit=COUNT] -// Retrieves a list of all workspace revisions and tags. If limit is supplied, only -// the last COUNT revisions are returned; all tags and branch names are always -// included -// Response: JSON - A hash containing "revisions" List, "tags" List, -// and "branches" List -// -// /workspaces/ID -// Retrieves an entire workspace (all components) by revision or tag -// Response: JSON - JSON serialization of the workspace -func (h Configuration) Get( path []string, m map[string]string ) (response string, code int) { - var serial Serializer - switch m["format"] { - case "props" : serial = new(PropsSerializer) - default : serial = new(JsonSerializer) - } - - malformed := "Malformed request: "+to_string(path)+ - "; must be /components[/COMPONENT[/..][?rev=REV]] or /workspaces[/ID] or "+ - "/workspaces[?limit=COUNT]" - path_len := len(path) - if path_len < 1 { - return malformed, 400 - } else if path_len == 1 { - switch path[0] { - case WORKSPACES: - return getWorkspaces( h, path, m, malformed, serial ) - case COMPONENTS: - component_names := keys(h.workspace) - return serial.Serialize( component_names ), 200 - default: - return malformed, 400 - } - } else { - switch path[0] { - case WORKSPACES: - revision := h.repo.Get( path[1] ) - return revision, 200 - case COMPONENTS: - return getComponent( h, path, m, serial ) - default: - return malformed, 400 - } - } - return "Internal server error", 500 -} - -func getWorkspaces( h Configuration, path []string, m map[string]string, - malformed string, serial Serializer) (string, int) { +func (h Configuration) GetWorkspaces ( limit int ) map[string]([]string) { revs := h.repo.Revs() - limit := m["limit"] - if (limit != "") { - lim,e := strconv.Atoi(limit) - if e != nil { - return malformed, 400 - } + if limit != 0 { length := len(revs)-1 - revs = revs[length-lim:] + revs = revs[length-limit:] } tags := h.repo.Tags() branches := h.repo.Branches() @@ -103,82 +36,57 @@ return_map["revisions"] = revs return_map["tags"] = tags return_map["branches"] = branches - return serial.Serialize( return_map ), 200 + return return_map } -func getComponent( h Configuration, path []string, m map[string]string, - serial Serializer) (string, int) { - json := new( JsonSerializer ) - component_name := to_string(path[1:]) - components := h.workspace // Get the workspace by default - if m["rev"] != "" { - var revision StoreMap - json.Deserialize(h.repo.Get(m["rev"]), &revision) - components = revision - } - component := components[ component_name ] - attrset := expand( components, path[1:], component ) - return serial.Serialize(attrset), 200 + +func (h Configuration) GetWorkspace ( rev string ) (json string) { + return h.repo.Get( rev ) +} + + +func (h Configuration) GetComponents () []string { + return keys(h.workspace) } -// - PUT stores a unit -// -// /components/COMPONENT[/COMPONENT] -// Stores a component in the workspace. This *replaces* the current component -// definition. The inheritance hierarchy is defined in the payload, which -// should be a JSON encoding of a hash of attributes and parents. -// Response: "" -// -// /workspaces[?tag=TAGNAME] -// Checks in the workspace -// Response: revision number created -func (h Configuration) Put( path []string, m map[string]string, body string ) (response string, code int) { - malformed := "Malformed request: "+to_string(path)+ - "; must be /components/COMPONENT[/..] or /workspaces/NUM." - if (len(path) < 2) { - return malformed, 400 +func (h Configuration) GetComponent ( component_name string, rev string ) Store { + json := new( JsonSerializer ) + components := h.workspace // Get the workspace by default + if rev != "" { + var revision StoreMap + json.Deserialize(h.repo.Get(rev), &revision) + components = revision } - - json := new(JsonSerializer) - switch path[0] { - case WORKSPACES: - tag := m[ "tag" ] - rev := h.repo.Commit( "config.json", json.Serialize( h.workspace ), tag ) - return rev, 200 - case COMPONENTS: - var config Store - json.Deserialize( body, &config ) - reduce( h.workspace, path, &config ) - component_name := to_string( path[1:] ) - h.workspace[ component_name ] = config - return "", 200 - default: - return malformed, 400 - } - return "Internal server error", 500 + component := components[ component_name ] + path := strings.Split( component_name, "/", -1 ) + attrset := expand( components, path, component ) + return attrset } -// POST creates a new component or revision -// -// /workspaces[/PARENT_REVISION] -// Creates a new workspace, optionally branching off of a specified version. -// Response: "" -func (h Configuration) Post( path []string ) (response string, code int) { - malformed := "Malformed request: "+to_string(path)+ - "; must be /workspaces[/PARENT_REVISION]" - if len(path) < 1 || len(path) > 2 || path[0] != WORKSPACES { - return malformed, 400 - } + +func (h Configuration) PutWorkspace( body string, tag string ) (newRev string) { json := new(JsonSerializer) - if len(path) == 2 { - parent_revision := path[1] - h.repo.Branch( parent_revision ) - h.workspace = *new(StoreMap) - json.Deserialize(h.repo.Get( parent_revision ), &h.workspace) - } - return "", 200 + return h.repo.Commit( "config.json", json.Serialize( h.workspace ), tag ) +} + + +func (h Configuration) PutComponent( component_name string, body string ) { + var config Store + json := new( JsonSerializer ) + json.Deserialize( body, &config ) + reduce( h.workspace, strings.Split(component_name,"/",-1), &config ) + h.workspace[ component_name ] = config +} + + + +func (h Configuration) Branch( parent_revision string ) { + json := new(JsonSerializer) + h.repo.Branch( parent_revision ) + h.workspace = *new(StoreMap) + json.Deserialize(h.repo.Get( parent_revision ), &h.workspace) } @@ -206,8 +114,9 @@ func reduce( workspace StoreMap, path []string, config *Store ) { length := len(path) if length == 1 { return } - parent := workspace[ to_string(path[0:length-2]) ] - parent = expand( workspace, path, parent ) + parent_path := path[0:length-2] + parent := workspace[ to_string(parent_path) ] + parent = expand( workspace, parent_path, parent ) for k,v := range parent.attrs { if v == config.attrs[k] { config.attrs[k] = "", false diff --git a/server.go b/server.go --- a/server.go +++ b/server.go @@ -7,37 +7,161 @@ "log" "flag" "strconv" + "os" ) /////////////////////////////////////////////////////////////////////////////// // REST server //////////////////////////////////////////////////////////////// + +const ( + Workspaces = "workspaces" + Components = "components" + Usage = `
+		GET retrieves a component config
+			
+			/components
+				Retrieves a list of all components
+				Response: JSON - List of strings of component names
+				
+			/components/COMPONENT[/..][?rev=REV]
+				Retrieves a configuration for a single component, optionally by version.
+				Response: JSON - JSON serialization of component.
+
+			/workspaces[?limit=COUNT]
+				Retrieves a list of all workspace revisions and tags. If limit is supplied, only
+				the last COUNT revisions are returned; all tags and branch names are always 
+				included
+				Response: JSON - A hash containing "revisions" List, "tags" List, 
+												 and "branches" List
+
+			/workspaces/REV
+				Retrieves an entire workspace (all components) by revision or tag.  Use 0 for
+				the editable workspace.
+				Response: JSON - JSON serialization of the workspace
+
+		- PUT stores a unit
+			
+			/components/COMPONENT[/COMPONENT]
+				Stores a component in the workspace.  This *replaces* the current component
+				definition.  The inheritance hierarchy is defined in the payload, which
+				should be a JSON encoding of a hash of attributes and parents.
+				Response: ""
+				
+			/workspaces[?tag=TAGNAME]
+				Checks in the workspace
+				Response: revision number created
+
+		POST creates a new component or revision
+		
+			/workspaces[/PARENT_REVISION]
+				Creates a new workspace, optionally branching off of a specified version.
+				Response: ""
+			
+		
` +) + func (h Configuration) ServeHTTP( response http.ResponseWriter, request *http.Request ) { + // Get the domain (workspaces or components) and the path name path := strings.Split( request.URL.Path, "/", -1 ) - if len(path) > 0 { path = path[1:] } + var domain, component string + if len(path) > 0 { + domain = path[1] + if len(path) > 1 { + component = strings.Join( path[2:], "/" ) + } else { + component = "" + } + } + if domain != Workspaces && domain != Components { + malformed := ""+ + " "+ + " Malformed request"+ + " "+ + " "+ + "

Malformed request

"+ + "

"+ + " Request:

"+request.URL.String()+"
"+ + "

"+ + "

Usage

"+ + "

"+ + Usage + + " "+ + "" + response.Header().Set("Content-Type", "text/html") + response.WriteHeader( http.StatusBadRequest ) + response.Write([]byte(malformed)) + return + } + values := request.URL.Query() - params := make( map[string]string, len(values) ) - for k,_ := range values { - params[k] = values.Get(k) - } - var payload string - var code int + + var payload interface{} + code := http.StatusOK switch request.Method { - case "GET": payload, code = h.Get( path, params ) + case "GET": + var err os.Error + malformed := "Malformed request: "+to_string(path)+ + "; must be /components[/COMPONENT[/..][?rev=REV]] or /workspaces[/REV] or "+ + "/workspaces[?limit=COUNT]" + if component == "" { + if domain == Workspaces { + limitStr := values.Get("limit") + var limit int + limit,err = strconv.Atoi(limitStr) + payload = h.GetWorkspaces( limit ) + } else { + payload = h.GetComponents( ) + } + } else { + if domain == Workspaces { + payload = h.GetWorkspace( component ) + } else { + rev := values.Get("rev") + payload = h.GetComponent( component, rev ) + } + } + if err != nil { + payload = malformed + code = http.StatusBadRequest + } + case "PUT": + //malformed := "Malformed request: "+to_string(path)+ + // "; must be /components/COMPONENT[/..] or /workspaces" var buffer bytes.Buffer buffer.ReadFrom( request.Body ) - payload, code = h.Put( path, params, buffer.String() ) - case "POST": payload, code = h.Post( path ) - case "DELETE": payload, code = h.Delete( path ) - default: payload, code = "Unhandled rquest type: "+request.Method, 400 + body := buffer.String() + if domain == Workspaces { + tag := values.Get("tag") + payload = h.PutWorkspace( body, tag ) + } else { + h.PutComponent( component, body ) + } + + case "POST": + //malformed := "Malformed request: "+to_string(path)+ + // "; must be /workspaces[/PARENT_REVISION]" + payload = "" + h.Branch( component ) + + case "DELETE": + payload, code = "Not yet implemented", http.StatusNotImplemented + + default: + payload, code = "Unhandled rquest type: "+request.Method, 400 + } - respond( response, payload, code ) -} - -func respond( response http.ResponseWriter, payload string, status int ) { - response.Header().Set("Content-Type", "text/json") - response.Write([]byte(payload)) + var serial Serializer + var content_type string + switch values.Get("format") { + case "props" : serial, content_type = new(PropsSerializer), "text/json" + default : serial, content_type = new(JsonSerializer), "text/plain" + } + retVal := serial.Serialize( payload ) + response.Header().Set("Content-Type", content_type) + response.WriteHeader(code) + response.Write([]byte(retVal)) } func main() {