1// Copyright 2015 Google Inc. All rights reserved.
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
15// Package fifo provides Group, which is a list of modifiers that are executed
16// consecutively. By default, when an error is returned by a modifier, the
17// execution of the modifiers is halted, and the error is returned. Optionally,
18// when errror aggregation is enabled (by calling SetAggretateErrors(true)), modifier
19// execution is not halted, and errors are aggretated and returned after all
20// modifiers have been executed.
21package fifo
22
23import (
24	"encoding/json"
25	"net/http"
26	"sync"
27
28	"github.com/google/martian/v3"
29	"github.com/google/martian/v3/log"
30	"github.com/google/martian/v3/parse"
31	"github.com/google/martian/v3/verify"
32)
33
34// Group is a martian.RequestResponseModifier that maintains lists of
35// request and response modifiers executed on a first-in, first-out basis.
36type Group struct {
37	reqmu   sync.RWMutex
38	reqmods []martian.RequestModifier
39
40	resmu   sync.RWMutex
41	resmods []martian.ResponseModifier
42
43	aggregateErrors bool
44}
45
46type groupJSON struct {
47	Modifiers       []json.RawMessage    `json:"modifiers"`
48	Scope           []parse.ModifierType `json:"scope"`
49	AggregateErrors bool                 `json:"aggregateErrors"`
50}
51
52func init() {
53	parse.Register("fifo.Group", groupFromJSON)
54}
55
56// NewGroup returns a modifier group.
57func NewGroup() *Group {
58	return &Group{}
59}
60
61// SetAggregateErrors sets the error behavior for the Group. When true, the Group will
62// continue to execute consecutive modifiers when a modifier in the group encounters an
63// error. The Group will then return all errors returned by each modifier after all
64// modifiers have been executed.  When false, if an error is returned by a modifier, the
65// error is returned by ModifyRequest/Response and no further modifiers are run.
66// By default, error aggregation is disabled.
67func (g *Group) SetAggregateErrors(aggerr bool) {
68	g.aggregateErrors = aggerr
69}
70
71// AddRequestModifier adds a RequestModifier to the group's list of request modifiers.
72func (g *Group) AddRequestModifier(reqmod martian.RequestModifier) {
73	g.reqmu.Lock()
74	defer g.reqmu.Unlock()
75
76	g.reqmods = append(g.reqmods, reqmod)
77}
78
79// AddResponseModifier adds a ResponseModifier to the group's list of response modifiers.
80func (g *Group) AddResponseModifier(resmod martian.ResponseModifier) {
81	g.resmu.Lock()
82	defer g.resmu.Unlock()
83
84	g.resmods = append(g.resmods, resmod)
85}
86
87// ModifyRequest modifies the request. By default, aggregateErrors is false; if an error is
88// returned by a RequestModifier the error is returned and no further modifiers are run. When
89// aggregateErrors is set to true, the errors returned by each modifier in the group are
90// aggregated.
91func (g *Group) ModifyRequest(req *http.Request) error {
92	log.Debugf("fifo.ModifyRequest: %s", req.URL)
93	g.reqmu.RLock()
94	defer g.reqmu.RUnlock()
95
96	merr := martian.NewMultiError()
97
98	for _, reqmod := range g.reqmods {
99		if err := reqmod.ModifyRequest(req); err != nil {
100			if g.aggregateErrors {
101				merr.Add(err)
102				continue
103			}
104
105			return err
106		}
107	}
108
109	if merr.Empty() {
110		return nil
111	}
112
113	return merr
114}
115
116// ModifyResponse modifies the request. By default, aggregateErrors is false; if an error is
117// returned by a RequestModifier the error is returned and no further modifiers are run. When
118// aggregateErrors is set to true, the errors returned by each modifier in the group are
119// aggregated.
120func (g *Group) ModifyResponse(res *http.Response) error {
121	requ := ""
122	if res.Request != nil {
123		requ = res.Request.URL.String()
124		log.Debugf("fifo.ModifyResponse: %s", requ)
125	}
126	g.resmu.RLock()
127	defer g.resmu.RUnlock()
128
129	merr := martian.NewMultiError()
130
131	for _, resmod := range g.resmods {
132		if err := resmod.ModifyResponse(res); err != nil {
133			if g.aggregateErrors {
134				merr.Add(err)
135				continue
136			}
137
138			return err
139		}
140	}
141
142	if merr.Empty() {
143		return nil
144	}
145
146	return merr
147}
148
149// VerifyRequests returns a MultiError containing all the
150// verification errors returned by request verifiers.
151func (g *Group) VerifyRequests() error {
152	log.Debugf("fifo.VerifyRequests()")
153	g.reqmu.Lock()
154	defer g.reqmu.Unlock()
155
156	merr := martian.NewMultiError()
157	for _, reqmod := range g.reqmods {
158		reqv, ok := reqmod.(verify.RequestVerifier)
159		if !ok {
160			continue
161		}
162
163		if err := reqv.VerifyRequests(); err != nil {
164			merr.Add(err)
165		}
166	}
167
168	if merr.Empty() {
169		return nil
170	}
171
172	return merr
173}
174
175// VerifyResponses returns a MultiError containing all the
176// verification errors returned by response verifiers.
177func (g *Group) VerifyResponses() error {
178	log.Debugf("fifo.VerifyResponses()")
179	g.resmu.Lock()
180	defer g.resmu.Unlock()
181
182	merr := martian.NewMultiError()
183	for _, resmod := range g.resmods {
184		resv, ok := resmod.(verify.ResponseVerifier)
185		if !ok {
186			continue
187		}
188
189		if err := resv.VerifyResponses(); err != nil {
190			merr.Add(err)
191		}
192	}
193
194	if merr.Empty() {
195		return nil
196	}
197
198	return merr
199}
200
201// ResetRequestVerifications resets the state of the contained request verifiers.
202func (g *Group) ResetRequestVerifications() {
203	log.Debugf("fifo.ResetRequestVerifications()")
204	g.reqmu.Lock()
205	defer g.reqmu.Unlock()
206
207	for _, reqmod := range g.reqmods {
208		if reqv, ok := reqmod.(verify.RequestVerifier); ok {
209			reqv.ResetRequestVerifications()
210		}
211	}
212}
213
214// ResetResponseVerifications resets the state of the contained request verifiers.
215func (g *Group) ResetResponseVerifications() {
216	log.Debugf("fifo.ResetResponseVerifications()")
217	g.resmu.Lock()
218	defer g.resmu.Unlock()
219
220	for _, resmod := range g.resmods {
221		if resv, ok := resmod.(verify.ResponseVerifier); ok {
222			resv.ResetResponseVerifications()
223		}
224	}
225}
226
227// groupFromJSON builds a fifo.Group from JSON.
228//
229// Example JSON:
230// {
231//   "fifo.Group" : {
232//     "scope": ["request", "result"],
233//     "modifiers": [
234//       { ... },
235//       { ... },
236//     ]
237//   }
238// }
239func groupFromJSON(b []byte) (*parse.Result, error) {
240	msg := &groupJSON{}
241	if err := json.Unmarshal(b, msg); err != nil {
242		return nil, err
243	}
244
245	g := NewGroup()
246	if msg.AggregateErrors {
247		g.SetAggregateErrors(true)
248	}
249
250	for _, m := range msg.Modifiers {
251		r, err := parse.FromJSON(m)
252		if err != nil {
253			return nil, err
254		}
255
256		reqmod := r.RequestModifier()
257		if reqmod != nil {
258			g.AddRequestModifier(reqmod)
259		}
260
261		resmod := r.ResponseModifier()
262		if resmod != nil {
263			g.AddResponseModifier(resmod)
264		}
265	}
266
267	return parse.NewResult(g, msg.Scope)
268}
269