guzzle/guzzle — pull request #505 by jyokum (2013-12-21 23:18:19+00:00)
Title: Updated ServiceDescriptionLoader to merge parameter settings instead of overwrite

The operations below are what spawned my desire for this change. The NewStrengthTrainingActivity and UpdateStrengthTrainingActivity both extend from the same abstract operation and share almost all of the same parameters. The key difference is the "exercises" parameter which is required in one operation but optional in the other. When a simple parameter such as "Content-Type" needs to be different in the two operations it's not too much to just override it entirely but the "exercises" parameter is far more complicated and maintaining two versions of it which differ only by the required property is difficult to manage.

By modifying the resolveExtension function to merge recursively we could build upon parameters which are already defined instead of replacing just replacing them. It may be worth putting in a flag to enable control of this behavior and ensure backward compatibility but this version simply changes function being used.

#### Current description which works properly with current version of guzzle.
```json
{
"operations": {
"abstract.StrengthTrainingActivity": {
"parameters": {
"start_time": {
"type": "string",
"location": "json",
"description": "The starting time for the activity (e.g., Sat, 1 Jan 2011 00:00:00)"
},
"notes": {
"type": "string",
"location": "json",
"description": "Any notes that the user has associated with the activity",
"maxLength": 1024
},
"total_calories": {
"type": "number",
"location": "json",
"description": "The total calories burned"
},
"exercises": {
"type": "array",
"location": "json",
"description": "The exercises performed during this strength training activity",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"primary_type": {
"type": "string",
"description": "The primary exercise type",
"enum": ["Barbell Curl", "Dumbbell Curl", "Barbell Tricep Press", "Dumbbell Tricep Press", "Overhead Press", "Wrist Curl", "Tricep Kickback", "Bench Press", "Cable Crossover", "Dumbbell Fly", "Incline Bench", "Dips", "Pushup", "Pullup", "Back Raise", "Bent-Over Row", "Seated Row", "Chinup", "Lat Pulldown", "Seated Reverse Fly", "Military Press", "Upright Row", "Front Raise", "Side Lateral Raise", "Snatch", "Push Press", "Shrug", "Crunch Machine", "Crunch", "Ab Twist", "Bicycle Kick", "Hanging Leg Raise", "Hanging Knee Raise", "Reverse Crunch", "V Up", "Situp", "Squat", "Lunge", "Dead Lift", "Hamstring Curl", "Good Morning", "Clean", "Leg Press", "Leg Extension", "Other"]
},
"secondary_type": {
"type": "string",
"description": "The secondary exercise type"
},
"primary_muscle_group": {
"type": "string",
"description": "The primary muscle group",
"required": true,
"enum": ["Arms", "Chest", "Back", "Shoulders", "Abs", "Legs"]
},
"secondary_muscle_group": {
"type": "string",
"description": "The secondary muscle group",
"enum": ["Arms", "Chest", "Back", "Shoulders", "Abs", "Legs"]
},
"routine": {
"type": "string",
"description": "Free-form tag for the routine of which this exercise is a part",
"maxLength": 32
},
"notes": {
"type": "string",
"description": "Any notes for this exercise",
"maxLength": 1024
},
"sets": {
"type": "array",
"description": "The sets performed during this exercise",
"required": true,
"minItems": 1,
"items": {
"type": "object",
"properties": {
"weight": {
"type": "number",
"required": true
},
"repetitions": {
"type": "number",
"required": true
},
"notes": {
"type": "string",
"maxLength": 1024
}
}
}
}
}
}
},
"post_to_facebook": {
"type": "boolean",
"location": "json",
"description": "True to post this activity to Facebook, false to prevent posting (optional; if not specified, the user's default preference is used)"
},
"post_to_twitter": {
"type": "boolean",
"location": "json",
"description": "True to post this activity to Twitter, false to prevent posting (optional; if not specified, the user's default preference is used)"
}
}
},
"NewStrengthTrainingActivity": {
"extends": "abstract.StrengthTrainingActivity",
"uri": "{+hg.strength_training_activities}",
"httpMethod": "POST",
"responseClass": "GenericOutput",
"additionalParameters": false,
"parameters": {
"Content-Type": {
"location": "header",
"static": true,
"default": "application/vnd.com.runkeeper.NewStrengthTrainingActivity+json"
},
"exercises": {
"type": "array",
"location": "json",
"required": true,
"minItems": 1,
"items": {
"type": "object",
"properties": {
"primary_type": {
"type": "string",
"enum": ["Barbell Curl", "Dumbbell Curl", "Barbell Tricep Press", "Dumbbell Tricep Press", "Overhead Press", "Wrist Curl", "Tricep Kickback", "Bench Press", "Cable Crossover", "Dumbbell Fly", "Incline Bench", "Dips", "Pushup", "Pullup", "Back Raise", "Bent-Over Row", "Seated Row", "Chinup", "Lat Pulldown", "Seated Reverse Fly", "Military Press", "Upright Row", "Front Raise", "Side Lateral Raise", "Snatch", "Push Press", "Shrug", "Crunch Machine", "Crunch", "Ab Twist", "Bicycle Kick", "Hanging Leg Raise", "Hanging Knee Raise", "Reverse Crunch", "V Up", "Situp", "Squat", "Lunge", "Dead Lift", "Hamstring Curl", "Good Morning", "Clean", "Leg Press", "Leg Extension", "Other"]
},
"secondary_type": {
"type": "string"
},
"primary_muscle_group": {
"type": "string",
"required": true,
"enum": ["Arms", "Chest", "Back", "Shoulders", "Abs", "Legs"]
},
"secondary_muscle_group": {
"type": "string",
"enum": ["Arms", "Chest", "Back", "Shoulders", "Abs", "Legs"]
},
"routine": {
"type": "string",
"maxLength": 32
},
"notes": {
"type": "string",
"maxLength": 1024
},
"sets": {
"type": "array",
"required": true,
"minItems": 1,
"items": {
"type": "object",
"properties": {
"weight": {
"type": "number",
"required": true
},
"repetitions": {
"type": "number",
"required": true
},
"notes": {
"type": "string",
"maxLength": 1024
}
}
}
}
}
}
}
}
},
"UpdateStrengthTrainingActivity": {
"extends": "abstract.StrengthTrainingActivity",
"uri": "{+uri}",
"httpMethod": "PUT",
"responseClass": "GenericOutput",
"additionalParameters": false,
"parameters": {
"uri": {
"location": "uri",
"type": "string",
"required": true
},
"Content-Type": {
"location": "header",
"static": true,
"default": "application/vnd.com.runkeeper.StrengthTrainingActivity+json"
}
}
}
}
}
```

#### Updated version of the description which could be used if this change were made
```json
{
"operations": {
"abstract.StrengthTrainingActivity": {
"parameters": {
// long list of params
}
},
"NewStrengthTrainingActivity": {
"extends": "abstract.StrengthTrainingActivity",
"uri": "{+hg.strength_training_activities}",
"httpMethod": "POST",
"responseClass": "GenericOutput",
"additionalParameters": false,
"parameters": {
"Content-Type": {
"location": "header",
"static": true,
"default": "application/vnd.com.runkeeper.NewStrengthTrainingActivity+json"
},
"exercises": {
"required": true
}
}
},
"UpdateStrengthTrainingActivity": {
"extends": "abstract.StrengthTrainingActivity",
"uri": "{+uri}",
"httpMethod": "PUT",
"responseClass": "GenericOutput",
"additionalParameters": false,
"parameters": {
"uri": {
"location": "uri",
"type": "string",
"required": true
},
"Content-Type": {
"location": "header",
"static": true,
"default": "application/vnd.com.runkeeper.StrengthTrainingActivity+json"
}
}
}
}
}
```