Last active
October 17, 2022 19:07
-
-
Save owulveryck/8af03b6711c84f6672efc3e8b979a536 to your computer and use it in GitHub Desktop.
Simple webservice to validate data with CUE
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package sample | |
import ( | |
"bytes" | |
"fmt" | |
"io" | |
"io/ioutil" | |
"net/http" | |
"net/http/httptest" | |
"testing" | |
"cuelang.org/go/cue" | |
"cuelang.org/go/cue/cuecontext" | |
) | |
// DataProduct is a structure that holds the definition as a CUE value | |
type DataProduct struct { | |
definition cue.Value | |
// ... | |
} | |
// ServeHTTP to make the *DataProduct a http handler | |
// This is an example, we do not handle the method properly nor we check the content type | |
// This methods reads the payload from the request an calls the ExtractData method for validation | |
func (d *DataProduct) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
b, _ := ioutil.ReadAll(r.Body) | |
defer r.Body.Close() | |
_, err := d.ExtractData(b) | |
if err != nil { | |
http.Error(w, err.Error(), http.StatusBadRequest) | |
return | |
} | |
fmt.Fprint(w, "ok") | |
} | |
// ExtractData tries to reconstruct a data from the payload b, and | |
// unifies it with the data definition. | |
// then it validates the resulting value | |
func (d *DataProduct) ExtractData(b []byte) (cue.Value, error) { | |
data := d.definition.Context().CompileBytes(b) | |
unified := d.definition.Unify(data) | |
opts := []cue.Option{ | |
cue.Attributes(true), | |
cue.Definitions(true), | |
cue.Hidden(true), | |
} | |
return data, unified.Validate(opts...) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TestHandler is triggering a http test server with the DataProduct's handler, | |
// send a post request with various payload and check the result | |
func TestHandler(t *testing.T) { | |
ctx := cuecontext.New() | |
val := ctx.CompileString(` | |
{ | |
// first name of the person | |
first: =~ "[A-Z].*" | |
// Last name of the person | |
Last: =~ "[A-Z].*" | |
// Age of the person | |
Age?: number & < 130 | |
} | |
`) | |
ts := httptest.NewServer(&DataProduct{ | |
definition: val, | |
}) | |
defer ts.Close() | |
tests := []struct { | |
name string | |
payload io.Reader | |
expectedStatus int | |
}{ | |
{ | |
name: "valid", | |
payload: bytes.NewBufferString(` | |
{ | |
"first": "John", | |
"Last": "Doe", | |
"Age": 40 | |
} | |
`), | |
expectedStatus: http.StatusOK, | |
}, | |
{ | |
name: "invalid", | |
payload: bytes.NewBufferString(` | |
{ | |
"first": "John", | |
"Last": "Doe", | |
"Age": 140 | |
} | |
`), | |
expectedStatus: http.StatusBadRequest, | |
}, | |
} | |
for _, tt := range tests { | |
t.Run(tt.name, func(t *testing.T) { | |
req, _ := http.NewRequest("POST", ts.URL, tt.payload) | |
res, err := http.DefaultClient.Do(req) | |
if err != nil { | |
t.Error(err) | |
} | |
if res.StatusCode != tt.expectedStatus { | |
t.Errorf("expected %v, got %v", tt.expectedStatus, res.StatusCode) | |
} | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment