// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // author xeipuuv // author-github https://github.com/xeipuuv // author-mail xeipuuv@gmail.com // // repository-name gojsonschema // repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. // // description Different strategies to load JSON files. // Includes References (file and HTTP), JSON strings and Go types. // // created 01-02-2015 package gojsonschema import ( "bytes" "encoding/json" "errors" "io" "io/ioutil" "net/http" "os" "path/filepath" "runtime" "strings" "github.com/xeipuuv/gojsonreference" ) var osFS = osFileSystem(os.Open) // JSON loader interface type JSONLoader interface { JsonSource() interface{} LoadJSON() (interface{}, error) JsonReference() (gojsonreference.JsonReference, error) LoaderFactory() JSONLoaderFactory } type JSONLoaderFactory interface { New(source string) JSONLoader } type DefaultJSONLoaderFactory struct { } type FileSystemJSONLoaderFactory struct { fs http.FileSystem } func (d DefaultJSONLoaderFactory) New(source string) JSONLoader { return &jsonReferenceLoader{ fs: osFS, source: source, } } func (f FileSystemJSONLoaderFactory) New(source string) JSONLoader { return &jsonReferenceLoader{ fs: f.fs, source: source, } } // osFileSystem is a functional wrapper for os.Open that implements http.FileSystem. type osFileSystem func(string) (*os.File, error) func (o osFileSystem) Open(name string) (http.File, error) { return o(name) } // JSON Reference loader // references are used to load JSONs from files and HTTP type jsonReferenceLoader struct { fs http.FileSystem source string } func (l *jsonReferenceLoader) JsonSource() interface{} { return l.source } func (l *jsonReferenceLoader) JsonReference() (gojsonreference.JsonReference, error) { return gojsonreference.NewJsonReference(l.JsonSource().(string)) } func (l *jsonReferenceLoader) LoaderFactory() JSONLoaderFactory { return &FileSystemJSONLoaderFactory{ fs: l.fs, } } // NewReferenceLoader returns a JSON reference loader using the given source and the local OS file system. func NewReferenceLoader(source string) JSONLoader { return &jsonReferenceLoader{ fs: osFS, source: source, } } // NewReferenceLoaderFileSystem returns a JSON reference loader using the given source and file system. func NewReferenceLoaderFileSystem(source string, fs http.FileSystem) JSONLoader { return &jsonReferenceLoader{ fs: fs, source: source, } } func (l *jsonReferenceLoader) LoadJSON() (interface{}, error) { var err error reference, err := gojsonreference.NewJsonReference(l.JsonSource().(string)) if err != nil { return nil, err } refToUrl := reference refToUrl.GetUrl().Fragment = "" var document interface{} if reference.HasFileScheme { filename := strings.TrimPrefix(refToUrl.String(), "file://") if runtime.GOOS == "windows" { // on Windows, a file URL may have an extra leading slash, use slashes // instead of backslashes, and have spaces escaped filename = strings.TrimPrefix(filename, "/") filename = filepath.FromSlash(filename) } document, err = l.loadFromFile(filename) if err != nil { return nil, err } } else { document, err = l.loadFromHTTP(refToUrl.String()) if err != nil { return nil, err } } return document, nil } func (l *jsonReferenceLoader) loadFromHTTP(address string) (interface{}, error) { // returned cached versions for metaschemas for drafts 4, 6 and 7 // for performance and allow for easier offline use if metaSchema := drafts.GetMetaSchema(address); metaSchema != "" { return decodeJsonUsingNumber(strings.NewReader(metaSchema)) } resp, err := http.Get(address) if err != nil { return nil, err } // must return HTTP Status 200 OK if resp.StatusCode != http.StatusOK { return nil, errors.New(formatErrorDescription(Locale.HttpBadStatus(), ErrorDetails{"status": resp.Status})) } bodyBuff, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } return decodeJsonUsingNumber(bytes.NewReader(bodyBuff)) } func (l *jsonReferenceLoader) loadFromFile(path string) (interface{}, error) { f, err := l.fs.Open(path) if err != nil { return nil, err } defer f.Close() bodyBuff, err := ioutil.ReadAll(f) if err != nil { return nil, err } return decodeJsonUsingNumber(bytes.NewReader(bodyBuff)) } // JSON string loader type jsonStringLoader struct { source string } func (l *jsonStringLoader) JsonSource() interface{} { return l.source } func (l *jsonStringLoader) JsonReference() (gojsonreference.JsonReference, error) { return gojsonreference.NewJsonReference("#") } func (l *jsonStringLoader) LoaderFactory() JSONLoaderFactory { return &DefaultJSONLoaderFactory{} } func NewStringLoader(source string) JSONLoader { return &jsonStringLoader{source: source} } func (l *jsonStringLoader) LoadJSON() (interface{}, error) { return decodeJsonUsingNumber(strings.NewReader(l.JsonSource().(string))) } // JSON bytes loader type jsonBytesLoader struct { source []byte } func (l *jsonBytesLoader) JsonSource() interface{} { return l.source } func (l *jsonBytesLoader) JsonReference() (gojsonreference.JsonReference, error) { return gojsonreference.NewJsonReference("#") } func (l *jsonBytesLoader) LoaderFactory() JSONLoaderFactory { return &DefaultJSONLoaderFactory{} } func NewBytesLoader(source []byte) JSONLoader { return &jsonBytesLoader{source: source} } func (l *jsonBytesLoader) LoadJSON() (interface{}, error) { return decodeJsonUsingNumber(bytes.NewReader(l.JsonSource().([]byte))) } // JSON Go (types) loader // used to load JSONs from the code as maps, interface{}, structs ... type jsonGoLoader struct { source interface{} } func (l *jsonGoLoader) JsonSource() interface{} { return l.source } func (l *jsonGoLoader) JsonReference() (gojsonreference.JsonReference, error) { return gojsonreference.NewJsonReference("#") } func (l *jsonGoLoader) LoaderFactory() JSONLoaderFactory { return &DefaultJSONLoaderFactory{} } func NewGoLoader(source interface{}) JSONLoader { return &jsonGoLoader{source: source} } func (l *jsonGoLoader) LoadJSON() (interface{}, error) { // convert it to a compliant JSON first to avoid types "mismatches" jsonBytes, err := json.Marshal(l.JsonSource()) if err != nil { return nil, err } return decodeJsonUsingNumber(bytes.NewReader(jsonBytes)) } type jsonIOLoader struct { buf *bytes.Buffer } func NewReaderLoader(source io.Reader) (JSONLoader, io.Reader) { buf := &bytes.Buffer{} return &jsonIOLoader{buf: buf}, io.TeeReader(source, buf) } func NewWriterLoader(source io.Writer) (JSONLoader, io.Writer) { buf := &bytes.Buffer{} return &jsonIOLoader{buf: buf}, io.MultiWriter(source, buf) } func (l *jsonIOLoader) JsonSource() interface{} { return l.buf.String() } func (l *jsonIOLoader) LoadJSON() (interface{}, error) { return decodeJsonUsingNumber(l.buf) } func (l *jsonIOLoader) JsonReference() (gojsonreference.JsonReference, error) { return gojsonreference.NewJsonReference("#") } func (l *jsonIOLoader) LoaderFactory() JSONLoaderFactory { return &DefaultJSONLoaderFactory{} } // JSON raw loader // In case the JSON is already marshalled to interface{} use this loader // This is used for testing as otherwise there is no guarantee the JSON is marshalled // "properly" by using https://golang.org/pkg/encoding/json/#Decoder.UseNumber type jsonRawLoader struct { source interface{} } func NewRawLoader(source interface{}) *jsonRawLoader { return &jsonRawLoader{source: source} } func (l *jsonRawLoader) JsonSource() interface{} { return l.source } func (l *jsonRawLoader) LoadJSON() (interface{}, error) { return l.source, nil } func (l *jsonRawLoader) JsonReference() (gojsonreference.JsonReference, error) { return gojsonreference.NewJsonReference("#") } func (l *jsonRawLoader) LoaderFactory() JSONLoaderFactory { return &DefaultJSONLoaderFactory{} } func decodeJsonUsingNumber(r io.Reader) (interface{}, error) { var document interface{} decoder := json.NewDecoder(r) decoder.UseNumber() err := decoder.Decode(&document) if err != nil { return nil, err } return document, nil }