コード例 #1
0
ファイル: handlers.go プロジェクト: fiatjaf/summadb
/* requests with raw string bodies:
   - curl -X PUT http://db/path/to/key/_val -d 'some value'
 and complete JSON objects
   - curl -X PUT http://db/path -d '{"to": {"_val": "nothing here", "key": "some value"}}' -H 'content-type: application/json'

While setting the raw string body of a path will only update that path and do not change others, a full JSON request will replace all keys under the specified path. PUT is idempotent.

The "_val" key is optional when setting, but can be used to set values right to the key to which they refer. It is sometimes needed, like in this example, here "path/to" had some children values to be set, but also needed a value of its own.
Other use of the "_val" key is to set a value to null strictly, because setting the nude key to null will delete it instead of setting it.
*/
func Put(w http.ResponseWriter, r *http.Request) {
	ctx := getContext(r)

	if ctx.lastKey == "_security" {
		WriteSecurity(w, r)
		return
	}

	var err error
	var rev string

	if ctx.exists && ctx.currentRev != ctx.providedRev {
		log.WithFields(log.Fields{
			"given":   ctx.providedRev,
			"current": ctx.currentRev,
		}).Debug("rev mismatch.")
		res := responses.ConflictError()
		w.WriteHeader(res.Code)
		json.NewEncoder(w).Encode(res)
		return
	}

	if ctx.lastKey == "_val" || ctx.jsonBody == nil {
		// updating the value at only this specific path, as raw bytes
		rev, err = db.SaveValueAt(ctx.path, db.ToLevel(ctx.body))
	} else if ctx.jsonBody != nil {
		// saving a JSON tree, as its structure demands, expanding to subpaths and so on
		if ctx.lastKey[0] == '_' {
			log.WithFields(log.Fields{
				"path":    ctx.path,
				"lastkey": ctx.lastKey,
			}).Debug("couldn't update special key.")
			res := responses.BadRequest("you can't update special keys")
			w.WriteHeader(res.Code)
			json.NewEncoder(w).Encode(res)
			return
		}

		if ctx.localDoc {
			rev, err = db.SaveLocalDocAt(ctx.path, ctx.jsonBody)
		} else {
			rev, err = db.ReplaceTreeAt(ctx.path, ctx.jsonBody, false)
		}
	} else {
		err = errors.New("value received at " + ctx.path + " is not JSON: " + string(ctx.body))
	}

	if err != nil {
		log.Debug("couldn't save value: ", err)
		res := responses.UnknownError(err.Error())
		w.WriteHeader(res.Code)
		json.NewEncoder(w).Encode(res)
		return
	}

	w.WriteHeader(201)
	w.Header().Add("Content-Type", "application/json")
	json.NewEncoder(w).Encode(responses.Success{true, ctx.lastKey, rev})
}
コード例 #2
0
ファイル: handlers.go プロジェクト: fiatjaf/summadb
/*
   should accept PATCH requests with JSON objects:
   `curl -X PATCH http://db/path -d '{"to": {"key": "some value"}}' -H 'content-type: application/json'`
   this will not replace all values under /path, but only modify the values which the JSON object refers to.
*/
func Patch(w http.ResponseWriter, r *http.Request) {
	ctx := getContext(r)
	var err error
	var rev string

	if ctx.lastKey[0] == '_' {
		res := responses.BadRequest("you can't update special keys")
		w.WriteHeader(res.Code)
		json.NewEncoder(w).Encode(res)
		return
	}

	if !ctx.exists {
		res := responses.NotFound()
		w.WriteHeader(res.Code)
		json.NewEncoder(w).Encode(res)
		return
	}

	if ctx.currentRev != ctx.providedRev {
		log.WithFields(log.Fields{
			"given":   ctx.providedRev,
			"current": ctx.currentRev,
		}).Debug("rev mismatch.")
		res := responses.ConflictError()
		w.WriteHeader(res.Code)
		json.NewEncoder(w).Encode(res)
		return
	}

	// update the tree as the JSON structure demands
	rev, err = db.SaveTreeAt(ctx.path, ctx.jsonBody)

	if err != nil {
		log.Debug("couldn't save value: ", err)
		res := responses.UnknownError(err.Error())
		w.WriteHeader(res.Code)
		json.NewEncoder(w).Encode(res)
		return
	}

	w.WriteHeader(200)
	w.Header().Add("Content-Type", "application/json")
	json.NewEncoder(w).Encode(responses.Success{true, ctx.lastKey, rev})
}
コード例 #3
0
ファイル: handlers.go プロジェクト: fiatjaf/summadb
func Delete(w http.ResponseWriter, r *http.Request) {
	ctx := getContext(r)
	var err error
	var rev string

	if !ctx.exists {
		res := responses.NotFound()
		w.WriteHeader(res.Code)
		json.NewEncoder(w).Encode(res)
		return
	}

	if ctx.lastKey[0] == '_' {
		res := responses.BadRequest("you can't delete special keys")
		w.WriteHeader(res.Code)
		json.NewEncoder(w).Encode(res)
		return
	}

	if ctx.currentRev != "" && ctx.currentRev != ctx.providedRev {
		log.WithFields(log.Fields{
			"given":   ctx.providedRev,
			"current": ctx.currentRev,
		}).Debug("rev mismatch.")
		res := responses.ConflictError()
		w.WriteHeader(res.Code)
		json.NewEncoder(w).Encode(res)
		return
	}

	rev, err = db.DeleteAt(ctx.path)
	if err != nil {
		log.Error("couldn't delete key at ", ctx.path, ": ", err)
		res := responses.NotFound()
		w.WriteHeader(res.Code)
		json.NewEncoder(w).Encode(res)
		return
	}

	w.WriteHeader(200)
	w.Header().Add("Content-Type", "application/json")
	json.NewEncoder(w).Encode(responses.Success{true, ctx.lastKey, rev})
}
コード例 #4
0
func TestCouchDBSpecialEndpoints(t *testing.T) {
	g := Goblin(t)
	RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) })

	g.Describe("couchdb db special endpoints", func() {
		g.BeforeEach(func() {
			rec = httptest.NewRecorder()
			server = handle.BuildHandler()
		})

		g.Before(func() {
			db.Erase()
			db.Start()
			populateDB()
		})

		g.After(func() {
			db.End()
		})

		var rev string
		var oldrev string
		var id string

		g.It("_all_docs for a sub db", func() {
			r, _ = http.NewRequest("GET", "/vehicles/_all_docs", nil)
			server.ServeHTTP(rec, r)

			var res responses.AllDocs
			json.Unmarshal(rec.Body.Bytes(), &res)
			Expect(res.Rows).To(HaveLen(3))
			Expect(res.Rows[2].Id).To(Equal(res.Rows[2].Key))
			rev, _ := res.Rows[2].Value.(map[string]interface{})["rev"]
			Expect(rev).To(HavePrefix("1-"))

			keys := []string{res.Rows[0].Id, res.Rows[1].Key, res.Rows[2].Id}
			Expect(keys).To(ConsistOf("airplane", "boat", "car"))
		})

		g.It("_all_docs with selected keys", func() {
			r, _ = http.NewRequest("GET", "/vehicles/_all_docs?keys=%5B%22airplane%22,%22boat%22%5D", nil)
			server.ServeHTTP(rec, r)

			var res responses.AllDocs
			json.Unmarshal(rec.Body.Bytes(), &res)
			Expect(res.Rows).To(HaveLen(2))

			keys := []string{res.Rows[0].Id, res.Rows[1].Key}
			Expect(keys).To(ConsistOf("airplane", "boat"))
		})

		g.It("all_docs with include_docs -- for another sub db", func() {
			r, _ = http.NewRequest("GET", "/vehicles/airplane/_all_docs?include_docs=true", nil)
			server.ServeHTTP(rec, r)

			var res responses.AllDocs
			json.Unmarshal(rec.Body.Bytes(), &res)
			Expect(res.Rows).To(HaveLen(3))

			docid, _ := res.Rows[1].Doc["_id"]
			Expect(res.Rows[1].Key).To(Equal(docid))

			rev, _ := res.Rows[1].Value.(map[string]interface{})["rev"]
			docrev, _ := res.Rows[1].Doc["_rev"]
			Expect(rev).To(Equal(docrev))

			keys := []string{res.Rows[0].Id, res.Rows[1].Key, res.Rows[2].Id}
			sort.Strings(keys)
			Expect(keys).To(Equal([]string{"air", "land", "water"}))

			docs := map[string]interface{}{
				res.Rows[0].Key: res.Rows[0].Doc,
				res.Rows[1].Key: res.Rows[1].Doc,
				res.Rows[2].Key: res.Rows[2].Doc,
			}
			Expect(docs).To(HaveKey("air"))
			Expect(res.Rows[0].Doc).To(HaveKey("_rev"))
			Expect(res.Rows[0].Doc).To(HaveKeyWithValue("_id", res.Rows[0].Id))
		})

		g.It("_bulk_get", func() {
			r, _ = http.NewRequest("POST", "/vehicles/_bulk_get", bytes.NewReader([]byte(`{
                "docs": [
                    {"id": "nonexisting-doc"},
                    {"id": "car"},
                    {"_id": "airplane"}
                ]
            }`)))
			server.ServeHTTP(rec, r)

			var res responses.BulkGet
			json.Unmarshal(rec.Body.Bytes(), &res)
			Expect(res.Results[0].Docs[0].Ok).To(BeNil())
			Expect(res.Results[0].Docs[0].Error).ToNot(BeNil())
			Expect(res.Results[1].Docs[0].Ok).ToNot(BeNil())
			Expect(res.Results[1].Docs[0].Error).To(BeNil())
			doc := *res.Results[1].Docs[0].Ok
			id, _ := doc["_id"]
			irev, _ := doc["_rev"]
			rev = irev.(string)
			water, _ := doc["water"]
			Expect(id.(string)).To(Equal("car"))
			Expect(res.Results[1].Id).To(Equal(id))
			Expect(water).To(BeEquivalentTo(value(false)))
			Expect(res.Results[2].Docs[0].Ok).To(BeNil())
			Expect(res.Results[0].Docs[0].Error).ToNot(BeNil())
		})

		g.It("_bulk_docs", func() {
			r, _ = http.NewRequest("POST", "/vehicles/_bulk_docs", bytes.NewReader([]byte(`{
                "docs": [
                    {"everywhere": true},
                    {"_id": "car", "_rev": "`+rev+`", "space": false, "land": true},
                    {"_id": "airplane", "nowhere": false},
                    {"_id": "_local/.abchtru", "replication+data": "k"},
                    {"_id": "empty-doc"},
                    {"_id": "doc-with-a-rev-already-set", "_rev": "4-sa98hsa3i4", "val": 33}
                ]
            }`)))
			server.ServeHTTP(rec, r)

			Expect(rec.Code).To(Equal(201))
			var res []responses.BulkDocsResult
			json.Unmarshal(rec.Body.Bytes(), &res)

			Expect(res).To(HaveLen(6))
			Expect(res[0].Error).To(Equal(""))
			Expect(res[0].Ok).To(Equal(true))
			Expect(res[0].Rev).To(HavePrefix("1-"))
			id = res[0].Id
			Expect(res[1].Id).To(Equal("car"))
			prevn, _ := strconv.Atoi(strings.Split(rev, "-")[0])
			Expect(res[1].Rev).To(HavePrefix(fmt.Sprintf("%d-", prevn+1)))
			oldrev = rev
			rev = res[1].Rev
			cfe := responses.ConflictError()
			Expect(res[2].Error).To(Equal(cfe.Error))
			Expect(res[3].Id).To(Equal("_local/.abchtru"))
			Expect(res[3].Ok).To(Equal(true))
			Expect(res[4].Ok).To(Equal(true))
			Expect(res[4].Rev).To(HavePrefix("1-"))
			Expect(res[5].Ok).To(Equal(true))
			Expect(res[5].Rev).To(HavePrefix("5-"))
		})

		g.It("_bulk_docs with new_edits=false", func() {
			r, _ = http.NewRequest("POST", "/animals/_bulk_docs", bytes.NewReader([]byte(`{
                "docs": [
                    {"_id": "0", "_rev": "34-83fsop4", "name": "albatroz"},
                    {"_id": "1", "_rev": "0-a0a0a0a0", "name": "puppy"},
                    {"_id": "2"}
                ],
                "new_edits": false
            }`)))
			server.ServeHTTP(rec, r)

			Expect(rec.Code).To(Equal(201))
			var res []responses.BulkDocsResult
			json.Unmarshal(rec.Body.Bytes(), &res)

			Expect(res).To(HaveLen(3))
			Expect(res[0].Ok).To(Equal(true))
			Expect(res[1].Ok).To(Equal(true))
			Expect(res[2].Ok).To(Equal(false))

			Expect(db.GetRev("/animals/0")).To(BeEquivalentTo("34-83fsop4"))
			Expect(db.GetRev("/animals/1")).ToNot(BeEquivalentTo("0-a0a0a0a0"))
			Expect(db.GetValueAt("/animals/0/name")).To(BeEquivalentTo(`"albatroz"`))
			Expect(db.GetValueAt("/animals/1/name")).To(BeEquivalentTo(`"dog"`))
		})

		g.It("should have the correct docs saved", func() {
			Expect(db.GetValueAt("/vehicles/" + id + "/everywhere")).To(BeEquivalentTo("true"))
			Expect(db.GetLocalDocJsonAt("/vehicles/_local/.abchtru")).To(MatchJSON(`{
                "_id": "_local/.abchtru",
                "_rev": "0-1",
                "replication+data": "k"
            }`))
		})

		g.It("shouldn't show _local docs on _all_docs", func() {
			r, _ = http.NewRequest("GET", "/vehicles/_all_docs", nil)
			server.ServeHTTP(rec, r)
			var res responses.AllDocs
			json.Unmarshal(rec.Body.Bytes(), &res)
			Expect(res.Rows).To(HaveLen(5))
		})

		g.It("_revs_diff", func() {
			r, _ = http.NewRequest("POST", "/vehicles/_revs_diff", bytes.NewReader([]byte(`{
                "everywhere": ["2-invalidrev"],
                "car": ["`+oldrev+`", "`+rev+`", "1-invalidrev"],
                "airplane": ["1-nonexisting"]
            }`)))
			server.ServeHTTP(rec, r)

			Expect(rec.Code).To(Equal(200))
			var res map[string]responses.RevsDiffResult
			json.Unmarshal(rec.Body.Bytes(), &res)

			everywhere, _ := res["everywhere"]
			car, _ := res["car"]
			airplane, _ := res["airplane"]
			Expect(everywhere.Missing).To(Equal([]string{"2-invalidrev"}))
			Expect(car.Missing).To(Equal([]string{oldrev, "1-invalidrev"}))
			Expect(airplane.Missing).To(Equal([]string{"1-nonexisting"}))
		})
	})
}
コード例 #5
0
ファイル: specialhandlers.go プロジェクト: fiatjaf/summadb
// this method only exists for compatibility with PouchDB and should not be used elsewhere.
func BulkDocs(w http.ResponseWriter, r *http.Request) {
	ctx := getContext(r)
	var idocs interface{}
	var iid interface{}
	var irev interface{}
	var id string
	var rev string
	var ok bool

	if idocs, ok = ctx.jsonBody["docs"]; !ok {
		res := responses.BadRequest("You're supposed to send an array of docs to input on the database, and you didn't.")
		w.WriteHeader(res.Code)
		json.NewEncoder(w).Encode(res)
		return
	}

	/* options */
	new_edits := true
	if inew_edits, ok := ctx.jsonBody["new_edits"]; ok {
		new_edits = inew_edits.(bool)
	}

	path := db.CleanPath(ctx.path)
	docs := idocs.([]interface{})
	res := make([]responses.BulkDocsResult, len(docs))

	var err error
	if new_edits { /* procedure for normal document saving */
		for i, idoc := range docs {

			var newrev string
			localDoc := false
			doc := idoc.(map[string]interface{})
			if iid, ok = doc["_id"]; ok {
				id = iid.(string)
				if strings.HasPrefix(id, "_local/") {
					localDoc = true
				}
				if irev, ok = doc["_rev"]; ok {
					rev = irev.(string)
				} else {
					rev = ""
				}
			} else {
				id = db.Random(5)
			}

			if !localDoc {
				// procedure for normal documents

				// check rev matching:
				if iid != nil /* when iid is nil that means the doc had no _id, so we don't have to check. */ {
					currentRev := db.GetRev(path + "/" + db.EscapeKey(id))
					if string(currentRev) != rev && !bytes.HasPrefix(currentRev, []byte{'0', '-'}) {
						// rev is not matching, don't input this document
						e := responses.ConflictError()
						res[i] = responses.BulkDocsResult{
							Id:     id,
							Error:  e.Error,
							Reason: e.Reason,
						}
						continue
					}
				}

				// proceed to write the document.
				newrev, err = db.ReplaceTreeAt(path+"/"+db.EscapeKey(id), doc, false)
				if err != nil {
					e := responses.UnknownError()
					res[i] = responses.BulkDocsResult{
						Id:     id,
						Error:  e.Error,
						Reason: e.Reason,
					}
					continue
				}

			} else {
				// procedure for local documents

				// check rev matching:
				currentRev := db.GetLocalDocRev(path + "/" + id)
				if currentRev != "0-0" && currentRev != rev {
					// rev is not matching, don't input this document
					e := responses.ConflictError()
					res[i] = responses.BulkDocsResult{
						Id:     id,
						Error:  e.Error,
						Reason: e.Reason,
					}
					continue
				}

				// proceed to write the document.
				newrev, err = db.SaveLocalDocAt(path+"/"+id, doc)
				if err != nil {
					e := responses.UnknownError()
					res[i] = responses.BulkDocsResult{
						Id:     id,
						Error:  e.Error,
						Reason: e.Reason,
					}
					continue
				}
			}

			res[i] = responses.BulkDocsResult{
				Id:  id,
				Ok:  true,
				Rev: newrev,
			}
		}
	} else { /* procedure for replication-type _bulk_docs (new_edits=false)*/
		for i, idoc := range docs {
			id = ""
			rev = ""
			doc := idoc.(map[string]interface{})
			if iid, ok = doc["_id"]; ok {
				id = iid.(string)
				if irev, ok = doc["_rev"]; ok {
					rev = irev.(string)
				}
			}
			if id == "" || rev == "" {
				e := responses.UnknownError()
				res[i] = responses.BulkDocsResult{
					Id:     id,
					Error:  e.Error,
					Reason: e.Reason,
				}
				continue
			}

			// proceed to write
			currentRev := db.GetRev(path + "/" + db.EscapeKey(id))
			if rev < string(currentRev) {
				// will write only the rev if it is not the winning rev
				db.AcknowledgeRevFor(path+"/"+db.EscapeKey(id), rev)

			} else {
				// otherwise will write the whole doc normally (replacing the current)
				rev, err = db.ReplaceTreeAt(path+"/"+db.EscapeKey(id), doc, true)
			}

			// handle write error
			if err != nil {
				e := responses.UnknownError()
				res[i] = responses.BulkDocsResult{
					Id:     id,
					Error:  e.Error,
					Reason: e.Reason,
				}
				continue
			}

			res[i] = responses.BulkDocsResult{
				Id:  id,
				Ok:  true,
				Rev: rev,
			}
		}
	}

	w.WriteHeader(201)
	json.NewEncoder(w).Encode(res)
}