1// Copyright 2015 go-swagger maintainers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package spec
16
17import (
18	"bytes"
19	"encoding/gob"
20	"encoding/json"
21	"net/http"
22	"os"
23	"path/filepath"
24
25	"github.com/go-openapi/jsonreference"
26)
27
28// Refable is a struct for things that accept a $ref property
29type Refable struct {
30	Ref Ref
31}
32
33// MarshalJSON marshals the ref to json
34func (r Refable) MarshalJSON() ([]byte, error) {
35	return r.Ref.MarshalJSON()
36}
37
38// UnmarshalJSON unmarshalss the ref from json
39func (r *Refable) UnmarshalJSON(d []byte) error {
40	return json.Unmarshal(d, &r.Ref)
41}
42
43// Ref represents a json reference that is potentially resolved
44type Ref struct {
45	jsonreference.Ref
46}
47
48// RemoteURI gets the remote uri part of the ref
49func (r *Ref) RemoteURI() string {
50	if r.String() == "" {
51		return r.String()
52	}
53
54	u := *r.GetURL()
55	u.Fragment = ""
56	return u.String()
57}
58
59// IsValidURI returns true when the url the ref points to can be found
60func (r *Ref) IsValidURI(basepaths ...string) bool {
61	if r.String() == "" {
62		return true
63	}
64
65	v := r.RemoteURI()
66	if v == "" {
67		return true
68	}
69
70	if r.HasFullURL {
71		rr, err := http.Get(v)
72		if err != nil {
73			return false
74		}
75
76		return rr.StatusCode/100 == 2
77	}
78
79	if !(r.HasFileScheme || r.HasFullFilePath || r.HasURLPathOnly) {
80		return false
81	}
82
83	// check for local file
84	pth := v
85	if r.HasURLPathOnly {
86		base := "."
87		if len(basepaths) > 0 {
88			base = filepath.Dir(filepath.Join(basepaths...))
89		}
90		p, e := filepath.Abs(filepath.ToSlash(filepath.Join(base, pth)))
91		if e != nil {
92			return false
93		}
94		pth = p
95	}
96
97	fi, err := os.Stat(filepath.ToSlash(pth))
98	if err != nil {
99		return false
100	}
101
102	return !fi.IsDir()
103}
104
105// Inherits creates a new reference from a parent and a child
106// If the child cannot inherit from the parent, an error is returned
107func (r *Ref) Inherits(child Ref) (*Ref, error) {
108	ref, err := r.Ref.Inherits(child.Ref)
109	if err != nil {
110		return nil, err
111	}
112	return &Ref{Ref: *ref}, nil
113}
114
115// NewRef creates a new instance of a ref object
116// returns an error when the reference uri is an invalid uri
117func NewRef(refURI string) (Ref, error) {
118	ref, err := jsonreference.New(refURI)
119	if err != nil {
120		return Ref{}, err
121	}
122	return Ref{Ref: ref}, nil
123}
124
125// MustCreateRef creates a ref object but panics when refURI is invalid.
126// Use the NewRef method for a version that returns an error.
127func MustCreateRef(refURI string) Ref {
128	return Ref{Ref: jsonreference.MustCreateRef(refURI)}
129}
130
131// MarshalJSON marshals this ref into a JSON object
132func (r Ref) MarshalJSON() ([]byte, error) {
133	str := r.String()
134	if str == "" {
135		if r.IsRoot() {
136			return []byte(`{"$ref":""}`), nil
137		}
138		return []byte("{}"), nil
139	}
140	v := map[string]interface{}{"$ref": str}
141	return json.Marshal(v)
142}
143
144// UnmarshalJSON unmarshals this ref from a JSON object
145func (r *Ref) UnmarshalJSON(d []byte) error {
146	var v map[string]interface{}
147	if err := json.Unmarshal(d, &v); err != nil {
148		return err
149	}
150	return r.fromMap(v)
151}
152
153// GobEncode provides a safe gob encoder for Ref
154func (r Ref) GobEncode() ([]byte, error) {
155	var b bytes.Buffer
156	raw, err := r.MarshalJSON()
157	if err != nil {
158		return nil, err
159	}
160	err = gob.NewEncoder(&b).Encode(raw)
161	return b.Bytes(), err
162}
163
164// GobDecode provides a safe gob decoder for Ref
165func (r *Ref) GobDecode(b []byte) error {
166	var raw []byte
167	buf := bytes.NewBuffer(b)
168	err := gob.NewDecoder(buf).Decode(&raw)
169	if err != nil {
170		return err
171	}
172	return json.Unmarshal(raw, r)
173}
174
175func (r *Ref) fromMap(v map[string]interface{}) error {
176	if v == nil {
177		return nil
178	}
179
180	if vv, ok := v["$ref"]; ok {
181		if str, ok := vv.(string); ok {
182			ref, err := jsonreference.New(str)
183			if err != nil {
184				return err
185			}
186			*r = Ref{Ref: ref}
187		}
188	}
189
190	return nil
191}
192