<commit_before>// Package push sends notifications over HTTP/2 to
// Apple's Push Notification Service.
package push

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"time"
)

// Apple host locations.
const (
	Sandbox = "https://api.sandbox.push.apple.com"
	Live    = "https://api.push.apple.com"
)

// Service is the Apple Push Notification Service that you send notifications to.
type Service struct {
	Client *http.Client
	Host   string
}

// Headers sent with a push to control the notification (optional)
type Headers struct {
	// ID for the notification. Apple generates one if ommitted.
	// This should be a UUID with 32 lowercase hexadecimal digits.
	// TODO: use a UUID type.
	ID string

	// Apple will retry delivery until this time. The default behavior only tries once.
	Expiration time.Time

	// Allow Apple to group messages to together to reduce power consumption.
	// By default messages are sent immediately.
	LowPriority bool

	// Topic is the bundle ID for your app.
	Topic string
}

// Service error responses.
var (
	ErrBadDeviceToken = errors.New("bad device token")
	ErrForbidden      = errors.New("forbidden, check your certificate")
)

type response struct {
	Reason string `json:"reason"`
	// timestamp, other fields?
}

// Push notification to APN service after performing serialization.
func (s *Service) Push(deviceToken string, headers *Headers, payload json.Marshaler) error {
	b, err := json.Marshal(payload)
	if err != nil {
		return err
	}
	return s.PushBytes(deviceToken, headers, b)
}

// PushBytes notification to APN service.
func (s *Service) PushBytes(deviceToken string, headers *Headers, payload []byte) error {
	urlStr := fmt.Sprintf("%v/3/device/%v", s.Host, deviceToken)

	req, err := http.NewRequest("POST", urlStr, bytes.NewReader(payload))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	headers.set(req)

	resp, err := s.Client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusOK {
		return nil
	}

	// read entire response body
	// TODO: could decode while reading instead if not logging body too
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	// http.StatusBadRequest, http.StatusForbidden
	log.Println(resp.StatusCode)
	// logging full responses while learning the API
	log.Println(string(body))

	var response response
	json.Unmarshal(body, &response)

	switch response.Reason {
	case "BadDeviceToken":
		return ErrBadDeviceToken
	case "Forbidden":
		return ErrForbidden
	}
	return fmt.Errorf("Error response: %v", response.Reason)
}

// set headers on an HTTP request
func (h *Headers) set(req *http.Request) {
	// headers are optional
	if h == nil {
		return
	}

	if h.ID != "" {
		req.Header.Set("apns-id", h.ID)
	} // when ommitted, Apple will generate a UUID for you

	if !h.Expiration.IsZero() {
		req.Header.Set("apns-expiration", strconv.FormatInt(h.Expiration.Unix(), 10))
	}

	if h.LowPriority {
		req.Header.Set("apns-priority", "5")
	} // when ommitted, the default priority is 10

	if h.Topic != "" {
		req.Header.Set("apns-topic", h.Topic)
	}
}
<commit_msg>service: remove logging code<commit_after>// Package push sends notifications over HTTP/2 to
// Apple's Push Notification Service.
package push

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"time"
)

// Apple host locations.
const (
	Sandbox = "https://api.sandbox.push.apple.com"
	Live    = "https://api.push.apple.com"
)

// Service is the Apple Push Notification Service that you send notifications to.
type Service struct {
	Client *http.Client
	Host   string
}

// Headers sent with a push to control the notification (optional)
type Headers struct {
	// ID for the notification. Apple generates one if ommitted.
	// This should be a UUID with 32 lowercase hexadecimal digits.
	// TODO: use a UUID type.
	ID string

	// Apple will retry delivery until this time. The default behavior only tries once.
	Expiration time.Time

	// Allow Apple to group messages to together to reduce power consumption.
	// By default messages are sent immediately.
	LowPriority bool

	// Topic is the bundle ID for your app.
	Topic string
}

// Service error responses.
var (
	ErrBadDeviceToken = errors.New("bad device token")
	ErrForbidden      = errors.New("forbidden, check your certificate")
)

type response struct {
	Reason string `json:"reason"`
	// timestamp, other fields?
}

// Push notification to APN service after performing serialization.
func (s *Service) Push(deviceToken string, headers *Headers, payload json.Marshaler) error {
	b, err := json.Marshal(payload)
	if err != nil {
		return err
	}
	return s.PushBytes(deviceToken, headers, b)
}

// PushBytes notification to APN service.
func (s *Service) PushBytes(deviceToken string, headers *Headers, payload []byte) error {
	urlStr := fmt.Sprintf("%v/3/device/%v", s.Host, deviceToken)

	req, err := http.NewRequest("POST", urlStr, bytes.NewReader(payload))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	headers.set(req)

	resp, err := s.Client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusOK {
		return nil
	}

	// read entire response body
	// TODO: could decode while reading instead
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	var response response
	json.Unmarshal(body, &response)

	switch response.Reason {
	case "BadDeviceToken":
		return ErrBadDeviceToken
	case "Forbidden":
		return ErrForbidden
	}
	return fmt.Errorf("Error response: %v", response.Reason)
}

// set headers on an HTTP request
func (h *Headers) set(req *http.Request) {
	// headers are optional
	if h == nil {
		return
	}

	if h.ID != "" {
		req.Header.Set("apns-id", h.ID)
	} // when ommitted, Apple will generate a UUID for you

	if !h.Expiration.IsZero() {
		req.Header.Set("apns-expiration", strconv.FormatInt(h.Expiration.Unix(), 10))
	}

	if h.LowPriority {
		req.Header.Set("apns-priority", "5")
	} // when ommitted, the default priority is 10

	if h.Topic != "" {
		req.Header.Set("apns-topic", h.Topic)
	}
}

{'fraction_non_alphanumeric': 0.12000644329896908, 'fraction_numerical': 0.0028994845360824743, 'mean_word_length': 3.6335820895522386, 'pattern_counts': {'":': 4, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2706172', 'n_tokens_mistral': 2084, 'n_tokens_neox': 2002, 'n_words': 662}