6de3451d286d — Sean Russell 13 years ago
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.
2 files changed, 189 insertions(+), 156 deletions(-)

M config.go
M server.go
M config.go +47 -138
@@ 2,7 2,6 @@ package main
 
 import (
 	"strings"
-	"strconv"
 )
 
 type Store struct {

          
@@ 21,81 20,15 @@ type Configuration struct {
 }
 
 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 @@ func getWorkspaces( h Configuration, pat
 	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 keys( m StoreMap ) []string {
 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

          
M server.go +142 -18
@@ 7,37 7,161 @@ import (
 	"log"
 	"flag"
 	"strconv"
+	"os"
 )
 
 ///////////////////////////////////////////////////////////////////////////////
 // REST server ////////////////////////////////////////////////////////////////
+
+const (
+	Workspaces = "workspaces"
+	Components = "components"
+	Usage = `<pre>
+		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: ""
+			
+		</pre>`
+)
+
 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 := "<html>"+
+		"	<head>"+
+		"		<title>Malformed request</title>"+
+		"	</head>"+
+		"	<body>"+
+		"		<h1>Malformed request</h1>"+
+		"		<p/>"+
+		"		Request: <pre>"+request.URL.String()+"</pre>"+
+		"		<p/>"+
+		"		<h2>Usage</h2>"+
+		"		<p/>"+
+				Usage +
+		"	</body>"+
+		"</html>"
+		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() {